List lockfuncs in menu, more elaborate doc strings

This commit is contained in:
Griatch 2018-07-24 20:48:54 +02:00
parent 058f35650a
commit 89ffa84c01
2 changed files with 284 additions and 56 deletions

View file

@ -663,6 +663,19 @@ def validate_lockstring(lockstring):
return _LOCK_HANDLER.validate(lockstring)
def get_all_lockfuncs():
"""
Get a dict of available lock funcs.
Returns:
lockfuncs (dict): Mapping {lockfuncname:func}.
"""
if not _LOCKFUNCS:
_cache_lockfuncs()
return _LOCKFUNCS
def _test():
# testing

View file

@ -11,6 +11,7 @@ from evennia.utils.evmenu import EvMenu, list_node
from evennia.utils import evmore
from evennia.utils.ansi import strip_ansi
from evennia.utils import utils
from evennia.locks.lockhandler import get_all_lockfuncs
from evennia.prototypes import prototypes as protlib
from evennia.prototypes import spawner
@ -219,6 +220,16 @@ def _format_protfuncs():
return "\n ".join(out)
def _format_lockfuncs():
out = []
sorted_funcs = [(key, func) for key, func in
sorted(get_all_lockfuncs(), key=lambda tup: tup[0])]
for lockfunc_name, lockfunc in sorted_funcs:
out.append("- |c${name}|n - |W{docs}".format(
name=lockfunc_name,
docs=utils.justify(lockfunc.__doc__.strip(), align='l', indent=10).strip()))
# Menu nodes ------------------------------
@ -694,17 +705,37 @@ def node_attrs(caller):
prot = _get_menu_prototype(caller)
attrs = prot.get("attrs")
text = ["Set the prototype's |yAttributes|n. Enter attributes on one of these forms:\n"
" attrname=value\n attrname;category=value\n attrname;category;lockstring=value\n"
"To give an attribute without a category but with a lockstring, leave that spot empty "
"(attrname;;lockstring=value)."
"Separate multiple attrs with commas. Use quotes to escape inputs with commas and "
"semi-colon."]
text = """
|cAttributes|n are custom properties of the object. Enter attributes on one of these forms:
attrname=value
attrname;category=value
attrname;category;lockstring=value
To give an attribute without a category but with a lockstring, leave that spot empty
(attrname;;lockstring=value). Attribute values can have embedded $protfuncs.
{current}
"""
helptext = """
Most commonly, Attributes don't need any categories or locks. If using locks, the lock-types
'attredit', 'attrread' are used to limiting editing and viewing of the Attribute. Putting
the lock-type `attrcreate` in the |clocks|n prototype key can be used to restrict builders
to add new Attributes.
|c$protfuncs
{pfuncs}
""".format(pfuncs=_format_protfuncs())
if attrs:
text.append("Current attrs are '|y{attrs}|n'.".format(attrs=attrs))
text.format(current="Current attrs {attrs}.".format(
attrs=attrs))
else:
text.append("No attrs are set.")
text = "\n\n".join(text)
text.format(current="No attrs are set.")
text = (text, helptext)
options = _wizard_options("attrs", "aliases", "tags")
options.append({"key": "_default",
"goto": (_set_property,
@ -797,9 +828,24 @@ def _edit_tag(caller, old_tag, new_tag, **kwargs):
@list_node(_caller_tags)
def node_tags(caller):
text = ("Set the prototype's |yTags|n. Enter tags on one of the following forms:\n"
" tag\n tag;category\n tag;category;data\n"
"Note that 'data' is not commonly used.")
text = """
|cTags|n are used to group objects so they can quickly be found later. Enter tags on one of
the following forms:
tagname
tagname;category
tagname;category;data
"""
helptext = """
Tags are shared between all objects with that tag. So the 'data' field (which is not
commonly used) can only hold eventual info about the Tag itself, not about the individual
object on which it sits.
All objects created with this prototype will automatically get assigned a tag named the same
as the |cprototype_key|n and with a category "{tag_category}". This allows the spawner to
optionally update previously spawned objects when their prototype changes.
""".format(protlib._PROTOTYPE_TAG_CATEGORY)
text = (text, helptext)
options = _wizard_options("tags", "attrs", "locks")
return text, options
@ -811,13 +857,39 @@ def node_locks(caller):
prototype = _get_menu_prototype(caller)
locks = prototype.get("locks")
text = ["Set the prototype's |yLock string|n. Separate multiple locks with semi-colons. "
"Will retain case sensitivity."]
text = """
The |cLock string|n defines limitations for accessing various properties of the object once
it's spawned. The string should be on one of the following forms:
locktype:[NOT] lockfunc(args)
locktype: [NOT] lockfunc(args) [AND|OR|NOT] lockfunc(args) [AND|OR|NOT] ...
Separate multiple lockstrings by semicolons (;).
{current}
"""
helptext = """
Here is an example of a lock string constisting of two locks:
edit:false();call:tag(Foo) OR perm(Builder)
Above locks limit two things, 'edit' and 'call'. Which lock types are actually checked
depend on the typeclass of the object being spawned. Here 'edit' is never allowed by anyone
while 'call' is allowed to all accessors with a |ctag|n 'Foo' OR which has the
|cPermission|n 'Builder'.
|c$lockfuncs|n
{lfuncs}
""".format(lfuncs=_format_lockfuncs())
if locks:
text.append("Current locks are '|y{locks}|n'.".format(locks=locks))
text.format(current="Current locks are '|y{locks}|n'.".format(locks=locks))
else:
text.append("No locks are set.")
text = "\n\n".join(text)
text.format(current="No locks are set.")
text = (text, helptext)
options = _wizard_options("locks", "tags", "permissions")
options.append({"key": "_default",
"goto": (_set_property,
@ -834,13 +906,32 @@ def node_permissions(caller):
prototype = _get_menu_prototype(caller)
permissions = prototype.get("permissions")
text = ["Set the prototype's |yPermissions|n. Separate multiple permissions with commas. "
"Will retain case sensitivity."]
text = """
|cPermissions|n are simple strings used to grant access to this object. A permission is used
when a |clock|n is checked that contains the |wperm|n or |wpperm|n lock functions.
{current}
"""
helptext = """
Any string can act as a permission as long as a lock is set to look for it. Depending on the
lock, having a permission could even be negative (i.e. the lock is only passed if you
|wdon't|n have the 'permission'). The most common permissions are the hierarchical
permissions:
{permissions}.
For example, a |clock|n string like "edit:perm(Builder)" will grant access to accessors
having the |cpermission|n "Builder" or higher.
""".format(settings.PERMISSION_HIERARCHY)
if permissions:
text.append("Current permissions are '|y{permissions}|n'.".format(permissions=permissions))
text.format(current="Current permissions are {permissions}.".format(
permissions=permissions))
else:
text.append("No permissions are set.")
text = "\n\n".join(text)
text.format(current="No permissions are set.")
text = (text, helptext)
options = _wizard_options("permissions", "destination", "location")
options.append({"key": "_default",
"goto": (_set_property,
@ -857,12 +948,28 @@ def node_location(caller):
prototype = _get_menu_prototype(caller)
location = prototype.get("location")
text = ["Set the prototype's |yLocation|n"]
text = """
The |cLocation|n of this object in the world. If not given, the object will spawn
in the inventory of |c{caller}|n instead.
{current}
""".format(caller=caller.key)
helptext = """
You get the most control by not specifying the location - you can then teleport the spawned
objects as needed later. Setting the location may be useful for quickly populating a given
location. One could also consider randomizing the location using a $protfunc.
|c$protfuncs|n
{pfuncs}
""".format(pfuncs=_format_protfuncs)
if location:
text.append("Current location is |y{location}|n.".format(location=location))
text.format(current="Current location is {location}.".format(location=location))
else:
text.append("Default location is {}'s inventory.".format(caller))
text = "\n\n".join(text)
text.format(current="Default location is {}'s inventory.".format(caller))
text = (text, helptext)
options = _wizard_options("location", "permissions", "home")
options.append({"key": "_default",
"goto": (_set_property,
@ -879,12 +986,28 @@ def node_home(caller):
prototype = _get_menu_prototype(caller)
home = prototype.get("home")
text = ["Set the prototype's |yHome location|n"]
text = """
The |cHome|n location of an object is often only used as a backup - this is where the object
will be moved to if its location is deleted. The home location can also be used as an actual
home for characters to quickly move back to. If unset, the global home default will be used.
{current}
"""
helptext = """
The location can be specified as as #dbref but can also be explicitly searched for using
$obj(name).
The home location is often not used except as a backup. It should never be unset.
"""
if home:
text.append("Current home location is |y{home}|n.".format(home=home))
text.format(current="Current home location is {home}.".format(home=home))
else:
text.append("Default home location (|y{home}|n) used.".format(home=settings.DEFAULT_HOME))
text = "\n\n".join(text)
text.format(
current="Default home location ({home}) used.".format(home=settings.DEFAULT_HOME))
text = (text, helptext)
options = _wizard_options("home", "aliases", "destination")
options.append({"key": "_default",
"goto": (_set_property,
@ -901,12 +1024,24 @@ def node_destination(caller):
prototype = _get_menu_prototype(caller)
dest = prototype.get("dest")
text = ["Set the prototype's |yDestination|n. This is usually only used for Exits."]
text = """
The object's |cDestination|n is usually only set for Exit-like objects and designates where
the exit 'leads to'. It's usually unset for all other types of objects.
{current}
"""
helptext = """
The destination can be given as a #dbref but can also be explicitly searched for using
$obj(name).
"""
if dest:
text.append("Current destination is |y{dest}|n.".format(dest=dest))
text.format(current="Current destination is {dest}.".format(dest=dest))
else:
text.append("No destination is set (default).")
text = "\n\n".join(text)
text.format("No destination is set (default).")
text = (text, helptext)
options = _wizard_options("destination", "home", "prototype_desc")
options.append({"key": "_default",
"goto": (_set_property,
@ -922,15 +1057,25 @@ def node_destination(caller):
def node_prototype_desc(caller):
prototype = _get_menu_prototype(caller)
text = ["The |wPrototype-Description|n briefly describes the prototype for "
"viewing in listings."]
desc = prototype.get("prototype_desc", None)
text = """
The |cPrototype-Description|n optionally briefly describes the prototype when it's viewed in
listings.
{current}
"""
helptext = """
Giving a brief description helps you and others to locate the prototype for use later.
"""
if desc:
text.append("The current meta desc is:\n\"|w{desc}|n\"".format(desc=desc))
text.format(current="The current meta desc is:\n\"|w{desc}|n\"".format(desc=desc))
else:
text.append("Description is currently unset.")
text = "\n\n".join(text)
text.format(current="Prototype-Description is currently unset.")
text = (text, helptext)
options = _wizard_options("prototype_desc", "prototype_key", "prototype_tags")
options.append({"key": "_default",
"goto": (_set_property,
@ -946,16 +1091,25 @@ def node_prototype_desc(caller):
def node_prototype_tags(caller):
prototype = _get_menu_prototype(caller)
text = ["|wPrototype-Tags|n can be used to classify and find prototypes. "
"Tags are case-insensitive. "
"Separate multiple by tags by commas."]
text = """
|cPrototype-Tags|n can be used to classify and find prototypes in listings Tag names are not
case-sensitive and can have not have a custom category. Separate multiple tags by commas.
"""
helptext = """
Using prototype-tags is a good way to organize and group large numbers of prototypes by
genre, type etc. Under the hood, prototypes' tags will all be stored with the category
'{tagmetacategory}'.
""".format(tagmetacategory=protlib._PROTOTYPE_TAG_META_CATEGORY)
tags = prototype.get('prototype_tags', [])
if tags:
text.append("The current tags are:\n|w{tags}|n".format(tags=tags))
text.format(current="The current tags are:\n|w{tags}|n".format(tags=tags))
else:
text.append("No tags are currently set.")
text = "\n\n".join(text)
text.format(current="No tags are currently set.")
text = (text, helptext)
options = _wizard_options("prototype_tags", "prototype_desc", "prototype_locks")
options.append({"key": "_default",
"goto": (_set_property,
@ -971,16 +1125,35 @@ def node_prototype_tags(caller):
def node_prototype_locks(caller):
prototype = _get_menu_prototype(caller)
text = ["Set |wPrototype-Locks|n on the prototype. There are two valid lock types: "
"'edit' (who can edit the prototype) and 'spawn' (who can spawn new objects with this "
"prototype)\n(If you are unsure, leave as default.)"]
locks = prototype.get('prototype_locks', '')
text = """
|cPrototype-Locks|n are used to limit access to this prototype when someone else is trying
to access it. By default any prototype can be edited only by the creator and by Admins while
they can be used by anyone with access to the spawn command. There are two valid lock types
the prototype access tools look for:
- 'edit': Who can edit the prototype.
- 'spawn': Who can spawn new objects with this prototype.
If unsure, leave as default.
{current}
"""
helptext = """
Prototype locks can be used when there are different tiers of builders or for developers to
produce 'base prototypes' only meant for builders to inherit and expand on rather than
change.
"""
if locks:
text.append("Current lock is |w'{lockstring}'|n".format(lockstring=locks))
text.format(current="Current lock is |w'{lockstring}'|n".format(lockstring=locks))
else:
text.append("Lock unset - if not changed the default lockstring will be set as\n"
" |w'spawn:all(); edit:id({dbref}) or perm(Admin)'|n".format(dbref=caller.id))
text = "\n\n".join(text)
text.format(
current="Default lock set: |w'spawn:all(); edit:id({dbref}) or perm(Admin)'|n".format(dbref=caller.id))
text = (text, helptext)
options = _wizard_options("prototype_locks", "prototype_tags", "index")
options.append({"key": "_default",
"goto": (_set_property,
@ -1039,7 +1212,7 @@ def node_update_objects(caller, **kwargs):
obj = choice(update_objects)
diff, obj_prototype = spawner.prototype_diff_from_object(prototype, obj)
text = ["Suggested changes to {} objects".format(len(update_objects)),
text = ["Suggested changes to {} objects. ".format(len(update_objects)),
"Showing random example obj to change: {name} (#{dbref}))\n".format(obj.key, obj.dbref)]
options = []
io = 0
@ -1073,6 +1246,17 @@ def node_update_objects(caller, **kwargs):
{"key": "|wb|rack ({})".format(back_node[5:], 'b'),
"goto": back_node}])
helptext = """
Be careful with this operation! The upgrade mechanism will try to automatically estimate
what changes need to be applied. But the estimate is |wonly based on the analysis of one
randomly selected object|n among all objects spawned by this prototype. If that object
happens to be unusual in some way the estimate will be off and may lead to unexpected
results for other objects. Always test your objects carefully after an upgrade and
consider being conservative (switch to KEEP) or even do the update manually if you are
unsure that the results will be acceptable. """
text = (text, helptext)
return text, options
@ -1144,7 +1328,17 @@ def node_prototype_save(caller, **kwargs):
"goto": ("node_prototype_save",
{"accept": True, "prototype": prototype})})
return "\n".join(text), options
helptext = """
Saving the prototype makes it available for use later. It can also be used to inherit from,
by name. Depending on |cprototype-locks|n it also makes the prototype usable and/or
editable by others. Consider setting good |cPrototype-tags|n and to give a useful, brief
|cPrototype-desc|n to make the prototype easy to find later.
"""
text = (text, helptext)
return text, options
# spawning node
@ -1212,6 +1406,16 @@ def node_prototype_spawn(caller, **kwargs):
dict(prototype=prototype, opjects=spawned_objects,
back_node="node_prototype_spawn"))})
options.extend(_wizard_options("prototype_spawn", "prototype_save", "index"))
helptext = """
Spawning is the act of instantiating a prototype into an actual object. As a new object is
spawned, every $protfunc in the prototype is called anew. Since this is a common thing to
do, you may also temporarily change the |clocation|n of this prototype to bypass whatever
value is set in the prototype.
"""
text = (text, helptext)
return text, options
@ -1232,11 +1436,22 @@ def _prototype_load_select(caller, prototype_key):
@list_node(_all_prototype_parents, _prototype_load_select)
def node_prototype_load(caller, **kwargs):
text = ["Select a prototype to load. This will replace any currently edited prototype."]
"""Load prototype"""
text = """
Select a prototype to load. This will replace any prototype currently being edited!
"""
helptext = """
Loading a prototype will load it and return you to the main index. It can be a good idea to
examine the prototype before loading it.
"""
text = (text, helptext)
options = _wizard_options("prototype_load", "prototype_save", "index")
options.append({"key": "_default",
"goto": _prototype_parent_examine})
return "\n".join(text), options
return text, options
# EvMenu definition, formatting and access functions