Improve parse of spawn arguments

This commit is contained in:
Griatch 2018-03-04 16:25:18 +01:00
parent 9e7dc14cbb
commit ce602716f1
2 changed files with 89 additions and 86 deletions

View file

@ -13,7 +13,8 @@ from evennia.utils import create, utils, search
from evennia.utils.utils import inherits_from, class_from_module from evennia.utils.utils import inherits_from, class_from_module
from evennia.utils.eveditor import EvEditor from evennia.utils.eveditor import EvEditor
from evennia.utils.evmore import EvMore from evennia.utils.evmore import EvMore
from evennia.utils.spawner import spawn, search_prototype, list_prototypes, store_prototype from evennia.utils.spawner import (spawn, search_prototype, list_prototypes,
store_prototype, build_metaproto)
from evennia.utils.ansi import raw from evennia.utils.ansi import raw
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS) COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
@ -27,12 +28,8 @@ __all__ = ("ObjManipCommand", "CmdSetObjAlias", "CmdCopy",
"CmdLock", "CmdExamine", "CmdFind", "CmdTeleport", "CmdLock", "CmdExamine", "CmdFind", "CmdTeleport",
"CmdScript", "CmdTag", "CmdSpawn") "CmdScript", "CmdTag", "CmdSpawn")
try: # used by @set
# used by @set from ast import literal_eval as _LITERAL_EVAL
from ast import literal_eval as _LITERAL_EVAL
except ImportError:
# literal_eval is not available before Python 2.6
_LITERAL_EVAL = None
# used by @find # used by @find
CHAR_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS CHAR_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS
@ -1450,17 +1447,16 @@ def _convert_from_string(cmd, strobj):
# if nothing matches, return as-is # if nothing matches, return as-is
return obj return obj
if _LITERAL_EVAL: # Use literal_eval to parse python structure exactly.
# Use literal_eval to parse python structure exactly. try:
try: return _LITERAL_EVAL(strobj)
return _LITERAL_EVAL(strobj) except (SyntaxError, ValueError):
except (SyntaxError, ValueError): # treat as string
# treat as string strobj = utils.to_str(strobj)
strobj = utils.to_str(strobj) string = "|RNote: name \"|r%s|R\" was converted to a string. " \
string = "|RNote: name \"|r%s|R\" was converted to a string. " \ "Make sure this is acceptable." % strobj
"Make sure this is acceptable." % strobj cmd.caller.msg(string)
cmd.caller.msg(string) return strobj
return strobj
else: else:
# fall back to old recursive solution (does not support # fall back to old recursive solution (does not support
# nested lists/dicts) # nested lists/dicts)
@ -2786,46 +2782,44 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS):
def func(self): def func(self):
"""Implements the spawner""" """Implements the spawner"""
def _parse_prototype(inp, allow_key=False): def _parse_prototype(inp, expect=dict):
err = None
try: try:
# make use of _convert_from_string from the SetAttribute command prototype = _LITERAL_EVAL(inp)
prototype = _convert_from_string(self, inp) except (SyntaxError, ValueError) as err:
except SyntaxError: # treat as string
# this means literal_eval tried to parse a faulty string prototype = utils.to_str(inp)
string = ("|RCritical Python syntax error in argument. Only primitive " finally:
"Python structures are allowed. \nYou also need to use correct " if not isinstance(prototype, expect):
"Python syntax. Remember especially to put quotes around all " if err:
"strings inside lists and dicts.|n") string = ("{}\n|RCritical Python syntax error in argument. Only primitive "
self.caller.msg(string) "Python structures are allowed. \nYou also need to use correct "
return None "Python syntax. Remember especially to put quotes around all "
if isinstance(prototype, dict): "strings inside lists and dicts.|n".format(err))
else:
string = "Expected {}, got {}.".format(expect, type(prototype))
self.caller.msg(string)
return None
if expect == dict:
# an actual prototype. We need to make sure it's safe. Don't allow exec # an actual prototype. We need to make sure it's safe. Don't allow exec
if "exec" in prototype and not self.caller.check_permstring("Developer"): if "exec" in prototype and not self.caller.check_permstring("Developer"):
self.caller.msg("Spawn aborted: You don't have access to " self.caller.msg("Spawn aborted: You don't have access to "
"use the 'exec' prototype key.") "use the 'exec' prototype key.")
return None return None
elif isinstance(prototype, basestring): return prototype
# a prototype key
if allow_key:
return prototype
else:
self.caller.msg("The prototype must be defined as a Python dictionary.")
else:
caller.msg("The prototype must be given either as a Python dictionary or a key")
return None
def _search_show_prototype(query, metaprots=None):
def _search_show_prototype(query):
# prototype detail # prototype detail
strings = [] strings = []
metaprots = search_prototype(key=query, return_meta=True) if not metaprots:
metaprots = search_prototype(key=query, return_meta=True)
if metaprots: if metaprots:
for metaprot in metaprots: for metaprot in metaprots:
header = ( header = (
"|cprototype key:|n {}, |ctags:|n {}, |clocks:|n {} \n" "|cprototype key:|n {}, |ctags:|n {}, |clocks:|n {} \n"
"|cdesc:|n {} \n|cprototype:|n ".format( "|cdesc:|n {} \n|cprototype:|n ".format(
metaprot.key, ", ".join(metaprot.tags), metaprot.key, ", ".join(metaprot.tags),
metaprot.locks, metaprot.desc)) "; ".join(metaprot.locks), metaprot.desc))
prototype = ("{{\n {} \n}}".format("\n ".join("{!r}: {!r},".format(key, value) prototype = ("{{\n {} \n}}".format("\n ".join("{!r}: {!r},".format(key, value)
for key, value in for key, value in
sorted(metaprot.prototype.items())).rstrip(","))) sorted(metaprot.prototype.items())).rstrip(",")))
@ -2869,11 +2863,12 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS):
if 'save' in self.switches: if 'save' in self.switches:
if not self.args or not self.rhs: if not self.args or not self.rhs:
caller.msg("Usage: @spawn/save <key>[;desc[;tag,tag[,...][;lockstring]]] = <prototype_dict>") caller.msg(
"Usage: @spawn/save <key>[;desc[;tag,tag[,...][;lockstring]]] = <prototype_dict>")
return return
# handle lhs # handle lhs
parts = self.rhs.split(";", 3) parts = self.lhs.split(";", 3)
key, desc, tags, lockstring = "", "", [], "" key, desc, tags, lockstring = "", "", [], ""
nparts = len(parts) nparts = len(parts)
if nparts == 1: if nparts == 1:
@ -2889,17 +2884,26 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS):
tags = [tag.strip().lower() for tag in tags.split(",")] tags = [tag.strip().lower() for tag in tags.split(",")]
# handle rhs: # handle rhs:
prototype = _parse_prototype(caller, self.rhs) prototype = _parse_prototype(self.rhs)
if not prototype: if not prototype:
return return
# check for existing prototype # present prototype to save
matchstring = _search_show_prototype(key) new_matchstring = _search_show_prototype(
if matchstring: "", metaprots=[build_metaproto(key, desc, [lockstring], tags, prototype)])
caller.msg("|yExisting saved prototype found:|n\n{}".format(matchstring)) string = "|yCreating new prototype:|n\n{}".format(new_matchstring)
answer = ("Do you want to replace the existing prototype? Y/[N]") question = "\nDo you want to continue saving? [Y]/N"
if not answer.lower() not in ["y", "yes"]:
caller.msg("Save cancelled.") # check for existing prototype,
old_matchstring = _search_show_prototype(key)
if old_matchstring:
string += "\n|yExisting saved prototype found:|n\n{}".format(old_matchstring)
question = "\n|yDo you want to replace the existing prototype?|n [Y]/N"
answer = yield(string + question)
if answer.lower() in ["n", "no"]:
caller.msg("|rSave cancelled.|n")
return
# all seems ok. Try to save. # all seems ok. Try to save.
try: try:
@ -2907,8 +2911,7 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS):
except PermissionError as err: except PermissionError as err:
caller.msg("|rError saving:|R {}|n".format(err)) caller.msg("|rError saving:|R {}|n".format(err))
return return
caller.msg("Saved prototype:") caller.msg("|gSaved prototype:|n {}".format(key))
caller.execute_cmd("spawn/show {}".format(key))
return return
if not self.args: if not self.args:
@ -2919,12 +2922,16 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS):
# A direct creation of an object from a given prototype # A direct creation of an object from a given prototype
prototype = _parse_prototype(self.args, allow_key=True) prototype = _parse_prototype(
self.args, expect=dict if self.args.strip().startswith("{") else basestring)
if not prototype: if not prototype:
# this will only let through dicts or strings
return return
key = '<unnamed>'
if isinstance(prototype, basestring): if isinstance(prototype, basestring):
# A prototype key we are looking to apply # A prototype key we are looking to apply
key = prototype
metaprotos = search_prototype(prototype) metaprotos = search_prototype(prototype)
nprots = len(metaprotos) nprots = len(metaprotos)
if not metaprotos: if not metaprotos:
@ -2945,5 +2952,8 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS):
prototype["location"] = self.caller.location prototype["location"] = self.caller.location
# proceed to spawning # proceed to spawning
for obj in spawn(prototype): try:
self.caller.msg("Spawned %s." % obj.get_display_name(self.caller)) for obj in spawn(prototype):
self.caller.msg("Spawned %s." % obj.get_display_name(self.caller))
except RuntimeError as err:
caller.msg(err)

View file

@ -359,6 +359,14 @@ class PersistentPrototype(DefaultScript):
self.desc = "A prototype" self.desc = "A prototype"
def build_metaproto(key, desc, locks, tags, prototype):
"""
Create a metaproto from combinant parts.
"""
return MetaProto(key, desc, locks, tags, dict(prototype))
def store_prototype(caller, key, prototype, desc="", tags=None, locks="", delete=False): def store_prototype(caller, key, prototype, desc="", tags=None, locks="", delete=False):
""" """
Store a prototype persistently. Store a prototype persistently.
@ -386,7 +394,7 @@ def store_prototype(caller, key, prototype, desc="", tags=None, locks="", delete
""" """
key_orig = key key_orig = key
key = key.lower() key = key.lower()
locks = locks if locks else "use:all();edit:id({}) or edit:perm(Admin)".format(caller.id) locks = locks if locks else "use:all();edit:id({}) or perm(Admin)".format(caller.id)
tags = [(tag, "persistent_prototype") for tag in make_iter(tags)] tags = [(tag, "persistent_prototype") for tag in make_iter(tags)]
if key in _READONLY_PROTOTYPES: if key in _READONLY_PROTOTYPES:
@ -506,34 +514,19 @@ def search_prototype(key=None, tags=None, return_meta=True):
be found. be found.
""" """
matches = [] readonly_prototypes = search_readonly_prototype(key, tags)
if key and key in _READONLY_PROTOTYPES: persistent_prototypes = search_persistent_prototype(key, tags)
if return_meta:
matches.append(_READONLY_PROTOTYPES[key])
else:
matches.append(_READONLY_PROTOTYPES[key][3])
elif tags:
if return_meta:
matches.extend(
[MetaProto(prot.key, prot.desc, prot.locks.all(),
prot.tags.all(), prot.attributes.get("prototype"))
for prot in search_persistent_prototype(key, tags)])
else:
matches.extend([prot.attributes.get("prototype")
for prot in search_persistent_prototype(key, tags)])
else:
# neither key nor tags given. Return all.
if return_meta:
matches = [MetaProto(prot.key, prot.desc, prot.locks.all(),
prot.tags.all(), prot.attributes.get("prototype"))
for prot in search_persistent_prototype(key, tags)] + \
list(_READONLY_PROTOTYPES.values())
else:
matches = [prot.attributes.get("prototype")
for prot in search_persistent_prototype()] + \
[metaprot[3] for metaprot in _READONLY_PROTOTYPES.values()]
return matches
if return_meta:
persistent_prototypes = [
build_metaproto(prot.key, prot.desc, prot.locks.all(),
prot.tags.all(), prot.attributes.get("prototype"))
for prot in persistent_prototypes]
else:
readonly_prototypes = [metaprot.prototyp for metaprot in readonly_prototypes]
persistent_prototypes = [prot.attributes.get("prototype") for prot in persistent_prototypes]
return persistent_prototypes + readonly_prototypes
def list_prototypes(caller, key=None, tags=None, show_non_use=False, show_non_edit=True): def list_prototypes(caller, key=None, tags=None, show_non_use=False, show_non_edit=True):
""" """