mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Make examine command more modular; show attr-categories and value types.
See #1805.
This commit is contained in:
parent
fa3c2aacb7
commit
09f51a644a
4 changed files with 377 additions and 339 deletions
|
|
@ -124,6 +124,8 @@ Up requirements to Django 3.2+, Twisted 21+
|
|||
by another command (like `open` and `@open`. If no duplicate, @ is optional.
|
||||
- Move legacy channel-management commands (`ccreate`, `addcom` etc) to a contrib
|
||||
since their work is now fully handled by the single `channel` command.
|
||||
- Expand `examine` command's code to much more extensible and modular. Show
|
||||
attribute categories and value types (when not strings).
|
||||
|
||||
### Evennia 0.9.5 (2019-2020)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ from evennia.objects.models import ObjectDB
|
|||
from evennia.locks.lockhandler import LockException
|
||||
from evennia.commands.cmdhandler import get_and_merge_cmdsets
|
||||
from evennia.utils import create, utils, search, logger, funcparser
|
||||
from evennia.utils.dbserialize import deserialize
|
||||
from evennia.utils.utils import (
|
||||
inherits_from,
|
||||
class_from_module,
|
||||
|
|
@ -2430,379 +2431,407 @@ class CmdExamine(ObjManipCommand):
|
|||
quell_color = "|r"
|
||||
separator = "-"
|
||||
|
||||
def list_attribute(self, crop, attr, category, value):
|
||||
def msg(self, text):
|
||||
"""
|
||||
Formats a single attribute line.
|
||||
Central point for sending messages to the caller. This tags
|
||||
the message as 'examine' for eventual custom markup in the client.
|
||||
|
||||
Attributes:
|
||||
text (str): The text to send.
|
||||
|
||||
Args:
|
||||
crop (bool): If output should be cropped if too long.
|
||||
attr (str): Attribute key.
|
||||
category (str): Attribute category.
|
||||
value (any): Attribute value.
|
||||
Returns:
|
||||
"""
|
||||
self.caller.msg(text=(text, {"type": "examine"}))
|
||||
|
||||
def format_key(self, obj):
|
||||
return f"{obj.name} ({obj.dbref})"
|
||||
|
||||
def format_aliases(self, obj):
|
||||
if hasattr(obj, "aliases") and obj.aliases.all():
|
||||
return ", ".join(utils.make_iter(str(obj.aliases)))
|
||||
|
||||
def format_typeclass(self, obj):
|
||||
if hasattr(obj, "typeclass"):
|
||||
return f"{obj.typename} ({obj.typeclass_path})"
|
||||
|
||||
def format_sessions(self, obj):
|
||||
if hasattr(obj, "sessions"):
|
||||
sessions = obj.sessions.all()
|
||||
if sessions:
|
||||
return ", ".join(f"#{sess.sessid}" for sess in obj.sessions.all())
|
||||
|
||||
def format_email(self, obj):
|
||||
if hasattr(obj, "email") and obj.email:
|
||||
return f"{self.detail_color}{obj.email}|n"
|
||||
|
||||
def format_account_key(self, account):
|
||||
return f"{self.detail_color}{account.name}|n ({account.dbref})"
|
||||
|
||||
def format_account_typeclass(self, account):
|
||||
return f"{account.typename} ({account.typeclass_path})"
|
||||
|
||||
def format_account_permissions(self, account):
|
||||
perms = account.permissions.all()
|
||||
if account.is_superuser:
|
||||
perms = ["<Superuser>"]
|
||||
elif not perms:
|
||||
perms = ["<None>"]
|
||||
perms = ", ".join(perms)
|
||||
if account.attributes.has("_quell"):
|
||||
perms += f" {self.quell_color}(quelled)|n"
|
||||
return perms
|
||||
|
||||
def format_location(self, obj):
|
||||
if hasattr(obj, "location") and obj.location:
|
||||
return f"{obj.location.key} (#{obj.location.id})"
|
||||
|
||||
def format_home(self, obj):
|
||||
if hasattr(obj, "home") and obj.home:
|
||||
return f"{obj.home.key} (#{obj.home.id})"
|
||||
|
||||
def format_destination(self, obj):
|
||||
if hasattr(obj, "destination") and obj.destination:
|
||||
return f"{obj.destination.key} (#{obj.destination.id})"
|
||||
|
||||
def format_permissions(self, obj):
|
||||
perms = obj.permissions.all()
|
||||
if perms:
|
||||
perms_string = ", ".join(perms)
|
||||
if obj.is_superuser:
|
||||
perms_string += " <Superuser>"
|
||||
return perms_string
|
||||
|
||||
def format_locks(self, obj):
|
||||
locks = str(obj.locks)
|
||||
if locks:
|
||||
return utils.fill(
|
||||
"; ".join([lock for lock in locks.split(";")]), indent=2
|
||||
)
|
||||
return "Default"
|
||||
|
||||
def format_scripts(self, obj):
|
||||
if hasattr(obj, "scripts") and hasattr(obj.scripts, "all") and obj.scripts.all():
|
||||
return f"{obj.scripts}"
|
||||
|
||||
def format_single_tag(self, tag):
|
||||
if tag.db_category:
|
||||
return f"{tag.db_key}[{tag.db_category}]"
|
||||
else:
|
||||
return f"{tag.db_key}"
|
||||
|
||||
def format_tags(self, obj):
|
||||
if hasattr(obj, "tags"):
|
||||
tags = sorted(obj.tags.all(return_objs=True))
|
||||
if tags:
|
||||
formatted_tags = [self.format_single_tag(tag) for tag in tags]
|
||||
return utils.fill(", ".join(formatted_tags), indent=2)
|
||||
|
||||
def format_single_cmdset_options(self, cmdset):
|
||||
def _truefalse(string, value):
|
||||
if value is None:
|
||||
return ""
|
||||
if value:
|
||||
return f"{string}: T"
|
||||
return f"{string}: F"
|
||||
return ", ".join(
|
||||
_truefalse(opt, getattr(cmdset, opt))
|
||||
for opt in ("no_exits", "no_objs", "no_channels", "duplicates")
|
||||
if getattr(cmdset, opt) is not None
|
||||
)
|
||||
|
||||
def format_single_cmdset(self, cmdset):
|
||||
options = self.format_single_cmdset_options(cmdset)
|
||||
return f"{cmdset.path} [{cmdset.key}] ({cmdset.mergetype}, prio {cmdset.priority}{options}"
|
||||
|
||||
def format_stored_cmdsets(self, obj):
|
||||
if hasattr(obj, "cmdset"):
|
||||
stored_cmdset_strings = []
|
||||
stored_cmdsets = sorted(obj.cmdset.all(), key=lambda x: x.priority, reverse=True)
|
||||
for cmdset in stored_cmdsets:
|
||||
if cmdset.key != "_EMPTY_CMDSET":
|
||||
stored_cmdset_strings.append(self.format_single_cmdset(cmdset))
|
||||
return "\n ".join(stored_cmdset_strings)
|
||||
|
||||
def format_merged_cmdsets(self, obj, current_cmdset):
|
||||
if not hasattr(obj, "cmdset"):
|
||||
return None
|
||||
|
||||
all_cmdsets = [(cmdset.key, cmdset) for cmdset in current_cmdset.merged_from]
|
||||
# we always at least try to add account- and session sets since these are ignored
|
||||
# if we merge on the object level.
|
||||
if hasattr(obj, "account") and obj.account:
|
||||
# get Attribute-cmdsets if they exist
|
||||
all_cmdsets.extend([(cmdset.key, cmdset) for cmdset in obj.account.cmdset.all()])
|
||||
if obj.sessions.count():
|
||||
# if there are more sessions than one on objects it's because of multisession mode
|
||||
# we only show the first session's cmdset here (it is -in principle- possible
|
||||
# that different sessions have different cmdsets but for admins who want such
|
||||
# madness it is better that they overload with their own CmdExamine to handle it).
|
||||
all_cmdsets.extend([(cmdset.key, cmdset)
|
||||
for cmdset in obj.account.sessions.all()[0].cmdset.all()])
|
||||
else:
|
||||
try:
|
||||
# we have to protect this since many objects don't have sessions.
|
||||
all_cmdsets.extend([(cmdset.key, cmdset)
|
||||
for cmdset in obj.get_session(obj.sessions.get()).cmdset.all()])
|
||||
except (TypeError, AttributeError):
|
||||
# an error means we are merging an object without a session
|
||||
pass
|
||||
all_cmdsets = [cmdset for cmdset in dict(all_cmdsets).values()]
|
||||
all_cmdsets.sort(key=lambda x: x.priority, reverse=True)
|
||||
|
||||
merged_cmdset_strings = []
|
||||
for cmdset in all_cmdsets:
|
||||
if cmdset.key != "_EMPTY_CMDSET":
|
||||
merged_cmdset_strings.append(self.format_single_cmdset(cmdset))
|
||||
return "\n ".join(merged_cmdset_strings)
|
||||
|
||||
def format_current_cmds(self, obj, current_cmdset):
|
||||
current_commands = sorted([cmd.key for cmd in current_cmdset if cmd.access(obj, "cmd")])
|
||||
return "\n" + utils.fill(", ".join(current_commands), indent=2)
|
||||
|
||||
def _get_attribute_value_type(self, attrvalue):
|
||||
typ = ""
|
||||
if not isinstance(attrvalue, str):
|
||||
try:
|
||||
name = attrvalue.__class__.__name__
|
||||
except AttributeError:
|
||||
try:
|
||||
name = attrvalue.__name__
|
||||
except AttributeError:
|
||||
name = attrvalue
|
||||
if str(name).startswith("_Saver"):
|
||||
try:
|
||||
typ = str(type(deserialize(attrvalue)))
|
||||
except Exception:
|
||||
typ = str(type(deserialize(attrvalue)))
|
||||
else:
|
||||
typ = str(type(attrvalue))
|
||||
return typ
|
||||
|
||||
def format_single_attribute_detail(self, obj, attr):
|
||||
global _FUNCPARSER
|
||||
if not _FUNCPARSER:
|
||||
_FUNCPARSER = funcparser.FuncParser(settings.FUNCPARSER_OUTGOING_MESSAGES_MODULES)
|
||||
|
||||
if attr is None:
|
||||
return "No such attribute was found."
|
||||
key, category, value = attr.db_key, attr.db_category, attr.value
|
||||
typ = self._get_attribute_value_type(value)
|
||||
typ = f" |B[type: {typ}]|n" if typ else ""
|
||||
value = utils.to_str(value)
|
||||
if crop:
|
||||
value = utils.crop(value)
|
||||
value = _FUNCPARSER.parse(ansi_raw(value), escape=True)
|
||||
return (f"Attribute {obj.name}/{self.header_color}{key}|n "
|
||||
f"[category={category}]{typ}:\n\n{value}")
|
||||
|
||||
def format_single_attribute(self, attr):
|
||||
global _FUNCPARSER
|
||||
if not _FUNCPARSER:
|
||||
_FUNCPARSER = funcparser.FuncParser(settings.FUNCPARSER_OUTGOING_MESSAGES_MODULES)
|
||||
|
||||
key, category, value = attr.db_key, attr.db_category, attr.value
|
||||
typ = self._get_attribute_value_type(value)
|
||||
typ = f" |B[type: {typ}]|n" if typ else ""
|
||||
value = utils.to_str(value)
|
||||
value = _FUNCPARSER.parse(ansi_raw(value), escape=True)
|
||||
value = utils.crop(value)
|
||||
if category:
|
||||
return f"{attr}[{category}] = {value}"
|
||||
return f"{self.header_color}{key}|n[{category}]={value}{typ}"
|
||||
else:
|
||||
return f"{attr} = {value}"
|
||||
return f"{self.header_color}{key}|n={value}{typ}"
|
||||
|
||||
def format_attributes(self, obj, attrname=None, crop=True):
|
||||
"""
|
||||
Helper function that returns info about attributes and/or
|
||||
non-persistent data stored on object
|
||||
def format_attributes(self, obj):
|
||||
return "\n " + "\n ".join(
|
||||
sorted(self.format_single_attribute(attr)
|
||||
for attr in obj.db_attributes.all())
|
||||
)
|
||||
|
||||
"""
|
||||
if attrname:
|
||||
if obj.attributes.has(attrname):
|
||||
db_attr = [(attrname, obj.attributes.get(attrname), None)]
|
||||
else:
|
||||
db_attr = None
|
||||
try:
|
||||
ndb_attr = [(attrname, object.__getattribute__(obj.ndb, attrname))]
|
||||
except Exception:
|
||||
ndb_attr = None
|
||||
if not (db_attr or ndb_attr):
|
||||
return {"Attribue(s)": f"\n No Attribute '{attrname}' found on {obj.name}"}
|
||||
else:
|
||||
db_attr = [(attr.key, attr.value, attr.category) for attr in obj.db_attributes.all()]
|
||||
try:
|
||||
ndb_attr = obj.nattributes.all(return_tuples=True)
|
||||
except Exception:
|
||||
ndb_attr = (None, None, None)
|
||||
def format_nattributes(self, obj):
|
||||
try:
|
||||
ndb_attr = obj.nattributes.all(return_tuples=True)
|
||||
except Exception:
|
||||
return
|
||||
|
||||
output = {}
|
||||
if db_attr and db_attr[0]:
|
||||
output["Persistent attribute(s)"] = "\n " + "\n ".join(
|
||||
sorted(
|
||||
self.list_attribute(crop, attr, category, value)
|
||||
for attr, value, category in db_attr
|
||||
)
|
||||
)
|
||||
if ndb_attr and ndb_attr[0]:
|
||||
output["Non-Persistent attribute(s)"] = " \n" + " \n".join(
|
||||
sorted(self.list_attribute(crop, attr, None, value) for attr, value in ndb_attr)
|
||||
return "\n " + " \n".join(
|
||||
sorted(self.format_single_attribute(attr)
|
||||
for attr, value in ndb_attr)
|
||||
)
|
||||
return output
|
||||
|
||||
def format_exits(self, obj):
|
||||
if hasattr(obj, "exits"):
|
||||
exits = ", ".join(f"{exit.name}({exit.dbref})" for exit in obj.exits)
|
||||
return exits if exits else None
|
||||
|
||||
def format_chars(self, obj):
|
||||
if hasattr(obj, "contents"):
|
||||
chars = ", ".join(f"{obj.name}({obj.dbref})" for obj in obj.contents
|
||||
if obj.account)
|
||||
return chars if chars else None
|
||||
|
||||
def format_things(self, obj):
|
||||
if hasattr(obj, "contents"):
|
||||
things = ", ".join(f"{obj.name}({obj.dbref})" for obj in obj.contents
|
||||
if not obj.account and not obj.destination)
|
||||
return things if things else None
|
||||
|
||||
def get_formatted_obj_data(self, obj, current_cmdset):
|
||||
"""
|
||||
Calls all other `format_*` methods.
|
||||
|
||||
"""
|
||||
objdata = {}
|
||||
objdata["Name/key"] = self.format_key(obj)
|
||||
objdata["Aliases"] = self.format_aliases(obj)
|
||||
objdata["Typeclass"] = self.format_typeclass(obj)
|
||||
objdata["Sessions"] = self.format_sessions(obj)
|
||||
objdata["Email"] = self.format_email(obj)
|
||||
if hasattr(obj, "has_account") and obj.has_account:
|
||||
objdata["Account"] = self.format_account_key(obj.account)
|
||||
objdata[" Account Typeclass"] = self.format_account_typeclass(obj.account)
|
||||
objdata[" Account Permissions"] = self.format_account_permissions(obj.account)
|
||||
objdata["Location"] = self.format_location(obj)
|
||||
objdata["Home"] = self.format_home(obj)
|
||||
objdata["Destination"] = self.format_destination(obj)
|
||||
objdata["Permissions"] = self.format_permissions(obj)
|
||||
objdata["Locks"] = self.format_locks(obj)
|
||||
if (current_cmdset
|
||||
and not (len(obj.cmdset.all()) == 1
|
||||
and obj.cmdset.current.key == "_EMPTY_CMDSET")):
|
||||
objdata["Stored Cmdset(s)"] = self.format_stored_cmdsets(obj)
|
||||
objdata["Merged Cmdset(s)"] = self.format_merged_cmdsets(obj, current_cmdset)
|
||||
objdata[f"Commands vailable to {obj.key} (result of Merged Cmdset(s))"] = (
|
||||
self.format_current_cmds(obj, current_cmdset))
|
||||
objdata["Scripts"] = self.format_scripts(obj)
|
||||
objdata["Tags"] = self.format_tags(obj)
|
||||
objdata["Persistent Attributes"] = self.format_attributes(obj)
|
||||
objdata["Non-Persistent Attributes"] = self.format_nattributes(obj)
|
||||
objdata["Exits"] = self.format_exits(obj)
|
||||
objdata["Characters"] = self.format_chars(obj)
|
||||
objdata["Content"] = self.format_things(obj)
|
||||
return objdata
|
||||
|
||||
def format_output(self, obj, current_cmdset):
|
||||
"""
|
||||
Helper function that creates a nice report about an object.
|
||||
|
||||
Args:
|
||||
obj (any): Object to analyze.
|
||||
current_cmdset (CmdSet): Current cmdset for object.
|
||||
|
||||
Returns:
|
||||
str: The formatted string.
|
||||
Formats the full examine page return.
|
||||
|
||||
"""
|
||||
hclr = self.header_color
|
||||
dclr = self.detail_color
|
||||
qclr = self.quell_color
|
||||
objdata = self.get_formatted_obj_data(obj, current_cmdset)
|
||||
|
||||
output = {}
|
||||
# main key
|
||||
output["Name/key"] = f"{dclr}{obj.name}|n ({obj.dbref})"
|
||||
# aliases
|
||||
if hasattr(obj, "aliases") and obj.aliases.all():
|
||||
output["Aliases"] = ", ".join(utils.make_iter(str(obj.aliases)))
|
||||
# typeclass
|
||||
output["Typeclass"] = f"{obj.typename} ({obj.typeclass_path})"
|
||||
# sessions
|
||||
if hasattr(obj, "sessions") and obj.sessions.all():
|
||||
output["Session id(s)"] = ", ".join(f"#{sess.sessid}" for sess in obj.sessions.all())
|
||||
# email, if any
|
||||
if hasattr(obj, "email") and obj.email:
|
||||
output["Email"] = f"{dclr}{obj.email}|n"
|
||||
# account, for puppeted objects
|
||||
if hasattr(obj, "has_account") and obj.has_account:
|
||||
output["Account"] = f"{dclr}{obj.account.name}|n ({obj.account.dbref})"
|
||||
# account typeclass
|
||||
output[" Account Typeclass"] = f"{obj.account.typename} ({obj.account.typeclass_path})"
|
||||
# account permissions
|
||||
perms = obj.account.permissions.all()
|
||||
if obj.account.is_superuser:
|
||||
perms = ["<Superuser>"]
|
||||
elif not perms:
|
||||
perms = ["<None>"]
|
||||
perms = ", ".join(perms)
|
||||
if obj.account.attributes.has("_quell"):
|
||||
perms += f" {qclr}(quelled)|n"
|
||||
output[" Account Permissions"] = perms
|
||||
# location
|
||||
if hasattr(obj, "location"):
|
||||
loc = str(obj.location)
|
||||
if obj.location:
|
||||
loc += f" (#{obj.location.id})"
|
||||
output["Location"] = loc
|
||||
# home
|
||||
if hasattr(obj, "home"):
|
||||
home = str(obj.home)
|
||||
if obj.home:
|
||||
home += f" (#{obj.home.id})"
|
||||
output["Home"] = home
|
||||
# destination, for exits
|
||||
if hasattr(obj, "destination") and obj.destination:
|
||||
dest = str(obj.destination)
|
||||
if obj.destination:
|
||||
dest += f" (#{obj.destination.id})"
|
||||
output["Destination"] = dest
|
||||
# main permissions
|
||||
perms = obj.permissions.all()
|
||||
perms_string = ""
|
||||
if perms:
|
||||
perms_string = ", ".join(perms)
|
||||
if obj.is_superuser:
|
||||
perms_string += " <Superuser>"
|
||||
if perms_string:
|
||||
output["Permissions"] = perms_string
|
||||
# locks
|
||||
locks = str(obj.locks)
|
||||
if locks:
|
||||
locks_string = "\n" + utils.fill(
|
||||
"; ".join([lock for lock in locks.split(";")]), indent=2
|
||||
)
|
||||
else:
|
||||
locks_string = " Default"
|
||||
output["Locks"] = locks_string
|
||||
# cmdsets
|
||||
if current_cmdset and not (
|
||||
len(obj.cmdset.all()) == 1 and obj.cmdset.current.key == "_EMPTY_CMDSET"):
|
||||
# all() returns a 'stack', so make a copy to sort.
|
||||
|
||||
def _format_options(cmdset):
|
||||
"""helper for cmdset-option display"""
|
||||
def _truefalse(string, value):
|
||||
if value is None:
|
||||
return ""
|
||||
if value:
|
||||
return f"{string}: T"
|
||||
return f"{string}: F"
|
||||
options = ", ".join(
|
||||
_truefalse(opt, getattr(cmdset, opt))
|
||||
for opt in ("no_exits", "no_objs", "no_channels", "duplicates")
|
||||
if getattr(cmdset, opt) is not None
|
||||
)
|
||||
options = ", " + options if options else ""
|
||||
return options
|
||||
|
||||
# cmdset stored on us
|
||||
stored_cmdsets = sorted(obj.cmdset.all(), key=lambda x: x.priority, reverse=True)
|
||||
stored = []
|
||||
for cmdset in stored_cmdsets:
|
||||
if cmdset.key == "_EMPTY_CMDSET":
|
||||
continue
|
||||
options = _format_options(cmdset)
|
||||
stored.append(
|
||||
f"{cmdset.path} [{cmdset.key}] ({cmdset.mergetype}, prio {cmdset.priority}{options})")
|
||||
output["Stored Cmdset(s)"] = "\n " + "\n ".join(stored)
|
||||
|
||||
# this gets all components of the currently merged set
|
||||
all_cmdsets = [(cmdset.key, cmdset) for cmdset in current_cmdset.merged_from]
|
||||
# we always at least try to add account- and session sets since these are ignored
|
||||
# if we merge on the object level.
|
||||
if hasattr(obj, "account") and obj.account:
|
||||
all_cmdsets.extend([(cmdset.key, cmdset) for cmdset in obj.account.cmdset.all()])
|
||||
if obj.sessions.count():
|
||||
# if there are more sessions than one on objects it's because of multisession mode 3.
|
||||
# we only show the first session's cmdset here (it is -in principle- possible that
|
||||
# different sessions have different cmdsets but for admins who want such madness
|
||||
# it is better that they overload with their own CmdExamine to handle it).
|
||||
all_cmdsets.extend(
|
||||
[
|
||||
(cmdset.key, cmdset)
|
||||
for cmdset in obj.account.sessions.all()[0].cmdset.all()
|
||||
]
|
||||
)
|
||||
else:
|
||||
try:
|
||||
# we have to protect this since many objects don't have sessions.
|
||||
all_cmdsets.extend(
|
||||
[
|
||||
(cmdset.key, cmdset)
|
||||
for cmdset in obj.get_session(obj.sessions.get()).cmdset.all()
|
||||
]
|
||||
)
|
||||
except (TypeError, AttributeError):
|
||||
# an error means we are merging an object without a session
|
||||
pass
|
||||
all_cmdsets = [cmdset for cmdset in dict(all_cmdsets).values()]
|
||||
all_cmdsets.sort(key=lambda x: x.priority, reverse=True)
|
||||
|
||||
# the resulting merged cmdset
|
||||
options = _format_options(current_cmdset)
|
||||
merged = [
|
||||
f"<Current merged cmdset> ({current_cmdset.mergetype} prio {current_cmdset.priority}{options})"]
|
||||
|
||||
# the merge stack
|
||||
for cmdset in all_cmdsets:
|
||||
options = _format_options(cmdset)
|
||||
merged.append(
|
||||
f"{cmdset.path} [{cmdset.key}] ({cmdset.mergetype} prio {cmdset.priority}{options})")
|
||||
output["Merged Cmdset(s)"] = "\n " + "\n ".join(merged)
|
||||
|
||||
# list the commands available to this object
|
||||
current_commands = sorted([cmd.key for cmd in current_cmdset if cmd.access(obj, "cmd")])
|
||||
cmdsetstr = "\n" + utils.fill(", ".join(current_commands), indent=2)
|
||||
output[f"Commands available to {obj.key} (result of Merged CmdSets)"] = str(cmdsetstr)
|
||||
|
||||
# scripts
|
||||
if hasattr(obj, "scripts") and hasattr(obj.scripts, "all") and obj.scripts.all():
|
||||
output["Scripts"] = "\n " + f"{obj.scripts}"
|
||||
# add the attributes
|
||||
output.update(self.format_attributes(obj))
|
||||
# Tags
|
||||
tags = obj.tags.all(return_key_and_category=True)
|
||||
tags_string = "\n" + utils.fill(
|
||||
", ".join(sorted(f"{tag}[{category}]" for tag, category in tags)), indent=2,
|
||||
)
|
||||
if tags:
|
||||
output["Tags[category]"] = tags_string
|
||||
# Contents of object
|
||||
exits = []
|
||||
pobjs = []
|
||||
things = []
|
||||
if hasattr(obj, "contents"):
|
||||
for content in obj.contents:
|
||||
if content.destination:
|
||||
exits.append(content)
|
||||
elif content.account:
|
||||
pobjs.append(content)
|
||||
else:
|
||||
things.append(content)
|
||||
if exits:
|
||||
output["Exits (has .destination)"] = ", ".join(
|
||||
f"{exit.name}({exit.dbref})" for exit in exits
|
||||
)
|
||||
if pobjs:
|
||||
output["Characters"] = ", ".join(
|
||||
f"{dclr}{pobj.name}|n({pobj.dbref})" for pobj in pobjs
|
||||
)
|
||||
if things:
|
||||
output["Contents"] = ", ".join(
|
||||
f"{cont.name}({cont.dbref})"
|
||||
for cont in obj.contents
|
||||
if cont not in exits and cont not in pobjs
|
||||
)
|
||||
# format output
|
||||
main_str = []
|
||||
max_width = -1
|
||||
for block in output.values():
|
||||
max_width = max(max_width, max(display_len(line) for line in block.split("\n")))
|
||||
max_width = max(0, min(self.client_width(), max_width))
|
||||
for header, block in objdata.items():
|
||||
if block is not None:
|
||||
blockstr = f"{self.header_color}{header}|n: {block}"
|
||||
max_width = max(max_width, max(display_len(line) for line in blockstr.split("\n")))
|
||||
main_str.append(blockstr)
|
||||
main_str = "\n".join(main_str)
|
||||
|
||||
max_width = max(0, min(self.client_width(), max_width))
|
||||
sep = self.separator * max_width
|
||||
mainstr = "\n".join(f"{hclr}{header}|n: {block}" for (header, block) in output.items())
|
||||
return f"{sep}\n{mainstr}\n{sep}"
|
||||
|
||||
return f"{sep}\n{main_str}\n{sep}"
|
||||
|
||||
def parse(self):
|
||||
super().parse()
|
||||
|
||||
self.examine_objs = []
|
||||
|
||||
if not self.args:
|
||||
# If no arguments are provided, examine the invoker's location.
|
||||
if hasattr(self.caller, "location"):
|
||||
self.examine_objs.append((self.caller.location, None))
|
||||
else:
|
||||
self.msg("You need to supply a target to examine.")
|
||||
raise InterruptCommand
|
||||
else:
|
||||
for objdef in self.lhs_objattr:
|
||||
obj = None
|
||||
obj_name = objdef["name"] # name
|
||||
obj_attrs = objdef["attrs"] # /attrs
|
||||
|
||||
self.account_mode = (
|
||||
utils.inherits_from(self.caller, "evennia.accounts.accounts.DefaultAccount")
|
||||
or "account" in self.switches
|
||||
or obj_name.startswith("*")
|
||||
)
|
||||
if self.account_mode:
|
||||
try:
|
||||
obj = self.caller.search_account(obj_name.lstrip("*"))
|
||||
except AttributeError:
|
||||
# this means we are calling examine from an account object
|
||||
obj = self.caller.search(
|
||||
obj_name.lstrip("*"), search_object="object" in self.switches
|
||||
)
|
||||
else:
|
||||
obj = self.caller.search(obj_name)
|
||||
|
||||
if obj:
|
||||
self.examine_objs.append((obj, obj_attrs))
|
||||
|
||||
def func(self):
|
||||
"""Process command"""
|
||||
caller = self.caller
|
||||
|
||||
def get_cmdset_callback(cmdset):
|
||||
def get_cmdset_callback(current_cmdset):
|
||||
"""
|
||||
We make use of the cmdhandeler.get_and_merge_cmdsets below. This
|
||||
We make use of the cmdhandler.get_and_merge_cmdsets below. This
|
||||
is an asynchronous function, returning a Twisted deferred.
|
||||
So in order to properly use this we need use this callback;
|
||||
it is called with the result of get_and_merge_cmdsets, whenever
|
||||
that function finishes. Taking the resulting cmdset, we continue
|
||||
to format and output the result.
|
||||
"""
|
||||
self.msg(self.format_output(obj, cmdset).strip())
|
||||
self.msg(self.format_output(obj, current_cmdset).strip())
|
||||
|
||||
if not self.args:
|
||||
# If no arguments are provided, examine the invoker's location.
|
||||
if hasattr(caller, "location"):
|
||||
obj = caller.location
|
||||
if not obj.access(caller, "examine"):
|
||||
# If we don't have special info access, just look at the object instead.
|
||||
self.msg(caller.at_look(obj))
|
||||
return
|
||||
obj_session = obj.sessions.get()[0] if obj.sessions.count() else None
|
||||
for obj, obj_attrs in self.examine_objs:
|
||||
# these are parsed out in .parse already
|
||||
|
||||
# using callback for printing result whenever function returns.
|
||||
get_and_merge_cmdsets(
|
||||
obj, obj_session, self.account, obj, "object", self.raw_string
|
||||
).addCallback(get_cmdset_callback)
|
||||
else:
|
||||
self.msg("You need to supply a target to examine.")
|
||||
return
|
||||
|
||||
# we have given a specific target object
|
||||
for objdef in self.lhs_objattr:
|
||||
|
||||
obj = None
|
||||
obj_name = objdef["name"]
|
||||
obj_attrs = objdef["attrs"]
|
||||
|
||||
self.account_mode = (
|
||||
utils.inherits_from(caller, "evennia.accounts.accounts.DefaultAccount")
|
||||
or "account" in self.switches
|
||||
or obj_name.startswith("*")
|
||||
)
|
||||
if self.account_mode:
|
||||
try:
|
||||
obj = caller.search_account(obj_name.lstrip("*"))
|
||||
except AttributeError:
|
||||
# this means we are calling examine from an account object
|
||||
obj = caller.search(
|
||||
obj_name.lstrip("*"), search_object="object" in self.switches
|
||||
)
|
||||
else:
|
||||
obj = caller.search(obj_name)
|
||||
if not obj:
|
||||
continue
|
||||
|
||||
if not obj.access(caller, "examine"):
|
||||
if not obj.access(self.caller, "examine"):
|
||||
# If we don't have special info access, just look
|
||||
# at the object instead.
|
||||
self.msg(caller.at_look(obj))
|
||||
self.msg(self.caller.at_look(obj))
|
||||
continue
|
||||
|
||||
if obj_attrs:
|
||||
for attrname in obj_attrs:
|
||||
# we are only interested in specific attributes
|
||||
ret = "\n".join(
|
||||
f"{self.header_color}{header}|n:{value}"
|
||||
for header, value in self.format_attributes(
|
||||
obj, attrname, crop=False
|
||||
).items()
|
||||
)
|
||||
self.caller.msg(ret)
|
||||
# we are only interested in specific attributes
|
||||
attrs = [attr for attr in obj.db_attributes.all() if attr.db_key in obj_attrs]
|
||||
if not attrs:
|
||||
self.msg("No attributes found on {obj.name}.")
|
||||
else:
|
||||
out_strings = []
|
||||
for attr in attrs:
|
||||
out_strings.append(self.format_single_attribute_detail(obj, attr))
|
||||
out_str = "\n".join(out_strings)
|
||||
max_width = max(display_len(line) for line in out_strings)
|
||||
max_width = max(0, min(max_width, self.client_width()))
|
||||
sep = self.separator * max_width
|
||||
self.msg(f"{sep}\n{out_str}")
|
||||
return
|
||||
|
||||
# examine the obj itself
|
||||
|
||||
# get the cmdset status
|
||||
session = None
|
||||
if obj.sessions.count():
|
||||
mergemode = "session"
|
||||
session = obj.sessions.get()[0]
|
||||
elif self.account_mode:
|
||||
mergemode = "account"
|
||||
else:
|
||||
session = None
|
||||
if obj.sessions.count():
|
||||
mergemode = "session"
|
||||
session = obj.sessions.get()[0]
|
||||
elif self.account_mode:
|
||||
mergemode = "account"
|
||||
else:
|
||||
mergemode = "object"
|
||||
mergemode = "object"
|
||||
|
||||
account = None
|
||||
objct = None
|
||||
if self.account_mode:
|
||||
account = obj
|
||||
else:
|
||||
account = obj.account
|
||||
objct = obj
|
||||
account = None
|
||||
objct = None
|
||||
if self.account_mode:
|
||||
account = obj
|
||||
else:
|
||||
account = obj.account
|
||||
objct = obj
|
||||
|
||||
# this is usually handled when a command runs, but when we examine
|
||||
# we may have leftover inherited cmdsets directly after a move etc.
|
||||
obj.cmdset.update()
|
||||
# using callback to print results whenever function returns.
|
||||
get_and_merge_cmdsets(
|
||||
obj, session, account, objct, mergemode, self.raw_string
|
||||
).addCallback(get_cmdset_callback)
|
||||
# this is usually handled when a command runs, but when we examine
|
||||
# we may have leftover inherited cmdsets directly after a move etc.
|
||||
obj.cmdset.update()
|
||||
# using callback to print results whenever function returns.
|
||||
get_and_merge_cmdsets(
|
||||
obj, session, account, objct, mergemode, self.raw_string
|
||||
).addCallback(get_cmdset_callback)
|
||||
|
||||
|
||||
class CmdFind(COMMAND_DEFAULT_CLASS):
|
||||
|
|
@ -3312,7 +3341,7 @@ class CmdObjects(COMMAND_DEFAULT_CLASS):
|
|||
)
|
||||
|
||||
# last N table
|
||||
objs = ObjectDB.objects.all().order_by("db_date_created")[max(0, nobjs - nlim) :]
|
||||
objs = ObjectDB.objects.all().order_by("db_date_created")[max(0, nobjs - nlim): ]
|
||||
latesttable = self.styled_table(
|
||||
"|wcreated|n", "|wdbref|n", "|wname|n", "|wtypeclass|n", align="l", border="table"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -918,7 +918,7 @@ class TestBuilding(CommandTest):
|
|||
self.call(building.CmdExamine(), "*TestAccount", "Name/key: TestAccount")
|
||||
|
||||
self.char1.db.test = "testval"
|
||||
self.call(building.CmdExamine(), "self/test", "Persistent attribute(s):\n test = testval")
|
||||
self.call(building.CmdExamine(), "self/test", "Attribute Char/test [category=None]:\n\ntestval")
|
||||
self.call(building.CmdExamine(), "NotFound", "Could not find 'NotFound'.")
|
||||
self.call(building.CmdExamine(), "out", "Name/key: out")
|
||||
|
||||
|
|
@ -927,7 +927,7 @@ class TestBuilding(CommandTest):
|
|||
self.call(
|
||||
building.CmdExamine(),
|
||||
"self/test2",
|
||||
"Persistent attribute(s):\n test2 = this is a \$random() value.",
|
||||
"Attribute Char/test2 [category=None]:\n\nthis is a \$random() value."
|
||||
)
|
||||
|
||||
self.room1.scripts.add(self.script.__class__)
|
||||
|
|
@ -1864,6 +1864,7 @@ class TestBuilding(CommandTest):
|
|||
)
|
||||
|
||||
|
||||
|
||||
from evennia.utils.create import create_channel # noqa
|
||||
|
||||
class TestCommsChannel(CommandTest):
|
||||
|
|
@ -2085,6 +2086,21 @@ class TestCommsChannel(CommandTest):
|
|||
)
|
||||
|
||||
|
||||
from evennia.comms import comms # noqa
|
||||
|
||||
|
||||
class TestComms(CommandTest):
|
||||
|
||||
def test_page(self):
|
||||
self.call(
|
||||
comms.CmdPage(),
|
||||
"TestAccount2 = Test",
|
||||
"TestAccount2 is offline. They will see your message if they list their pages later."
|
||||
"|You paged TestAccount2 with: 'Test'.",
|
||||
receiver=self.account,
|
||||
)
|
||||
|
||||
|
||||
class TestBatchProcess(CommandTest):
|
||||
|
||||
@patch("evennia.contrib.tutorial_examples.red_button.repeat")
|
||||
|
|
|
|||
|
|
@ -3418,15 +3418,6 @@ class TestLegacyMuxComms(CommandTest):
|
|||
receiver=self.account,
|
||||
)
|
||||
|
||||
def test_page(self):
|
||||
self.call(
|
||||
comms.CmdPage(),
|
||||
"TestAccount2 = Test",
|
||||
"TestAccount2 is offline. They will see your message if they list their pages later."
|
||||
"|You paged TestAccount2 with: 'Test'.",
|
||||
receiver=self.account,
|
||||
)
|
||||
|
||||
def test_cboot(self):
|
||||
# No one else connected to boot
|
||||
self.call(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue