diff --git a/docs/pylib/api_rst2md.py b/docs/pylib/api_rst2md.py index 5bb3bc00f6..31bd96bb3a 100755 --- a/docs/pylib/api_rst2md.py +++ b/docs/pylib/api_rst2md.py @@ -11,15 +11,15 @@ from os import rename def _rst2md(filename_rst): - with open(filename_rst, 'r') as fil: + with open(filename_rst, "r") as fil: # read rst file, reformat and save txt = fil.read() - with open(filename_rst, 'w') as fil: + with open(filename_rst, "w") as fil: txt = "```{eval-rst}\n" + txt + "\n```" fil.write(txt) # rename .rst file to .md file - filename, _ = filename_rst.rsplit('.', 1) + filename, _ = filename_rst.rsplit(".", 1) filename_md = filename + ".md" rename(filename_rst, filename_md) diff --git a/docs/pylib/auto_link_remapper.py b/docs/pylib/auto_link_remapper.py index 0e410a10f2..bb8ffbad65 100644 --- a/docs/pylib/auto_link_remapper.py +++ b/docs/pylib/auto_link_remapper.py @@ -149,7 +149,7 @@ def auto_link_remapper(no_autodoc=False): for strip_prefix in _STRIP_PREFIX: if url.startswith(strip_prefix): - url = url[len(strip_prefix):] + url = url[len(strip_prefix) :] if any(url.startswith(noremap) for noremap in _NO_REMAP_STARTSWITH): # skip regular http/s urls etc @@ -157,10 +157,10 @@ def auto_link_remapper(no_autodoc=False): if url.startswith("evennia."): # api link - we want to remove legacy #reference and remove .md - if '#' in url: - _, url = url.rsplit('#', 1) + if "#" in url: + _, url = url.rsplit("#", 1) if url.endswith(".md"): - url, _ = url.rsplit('.', 1) + url, _ = url.rsplit(".", 1) return f"[{txt}]({url})" fname, *part = url.rsplit("/", 1) @@ -174,7 +174,9 @@ def auto_link_remapper(no_autodoc=False): if _CURRFILE in docref_map and fname in docref_map[_CURRFILE]: cfilename = _CURRFILE.rsplit("/", 1)[-1] - urlout = docref_map[_CURRFILE][fname] + ".md" + ("#" + anchor[0].lower() if anchor else "") + urlout = ( + docref_map[_CURRFILE][fname] + ".md" + ("#" + anchor[0].lower() if anchor else "") + ) if urlout != url: print(f" {cfilename}: [{txt}]({url}) -> [{txt}]({urlout})") else: @@ -193,7 +195,7 @@ def auto_link_remapper(no_autodoc=False): for strip_prefix in _STRIP_PREFIX: if url.startswith(strip_prefix): - url = url[len(strip_prefix):] + url = url[len(strip_prefix) :] if any(url.startswith(noremap) for noremap in _NO_REMAP_STARTSWITH): return f"[{txt}]: {url}" @@ -202,8 +204,8 @@ def auto_link_remapper(no_autodoc=False): urlout = url elif url.startswith("evennia."): # api link - we want to remove legacy #reference - if '#' in url: - _, urlout = url.rsplit('#', 1) + if "#" in url: + _, urlout = url.rsplit("#", 1) else: fname, *part = url.rsplit("/", 1) fname = part[0] if part else fname diff --git a/docs/pylib/contrib_readmes2docs.py b/docs/pylib/contrib_readmes2docs.py index aa2886c02e..a63ecae504 100644 --- a/docs/pylib/contrib_readmes2docs.py +++ b/docs/pylib/contrib_readmes2docs.py @@ -50,15 +50,11 @@ tutorials are found here. Also the home of the Tutorial World demo adventure. "utils": """ Miscellaneous, optional tools for manipulating text, auditing connections and more. -""" +""", } -_FILENAME_MAP = { - "rpsystem": "RPSystem", - "xyzgrid": "XYZGrid", - "awsstorage": "AWSStorage" -} +_FILENAME_MAP = {"rpsystem": "RPSystem", "xyzgrid": "XYZGrid", "awsstorage": "AWSStorage"} HEADER = """# Contribs @@ -145,10 +141,14 @@ def readmes2docs(directory=_SOURCE_DIR): pypath = f"evennia.contrib.{category}.{name}" - filename = "Contrib-" + "-".join( - _FILENAME_MAP.get( - part, part.capitalize() if part[0].islower() else part) - for part in name.split("_")) + ".md" + filename = ( + "Contrib-" + + "-".join( + _FILENAME_MAP.get(part, part.capitalize() if part[0].islower() else part) + for part in name.split("_") + ) + + ".md" + ) outfile = pathjoin(_OUT_DIR, filename) with open(file_path) as fil: @@ -163,7 +163,7 @@ def readmes2docs(directory=_SOURCE_DIR): except IndexError: blurb = name - with open(outfile, 'w') as fil: + with open(outfile, "w") as fil: fil.write(data) categories[category].append((name, credits, blurb, filename, pypath)) @@ -179,11 +179,7 @@ def readmes2docs(directory=_SOURCE_DIR): for tup in sorted(contrib_tups, key=lambda tup: tup[0].lower()): catlines.append( BLURB.format( - name=tup[0], - credits=tup[1], - blurb=tup[2], - filename=tup[3], - code_location=tup[4] + name=tup[0], credits=tup[1], blurb=tup[2], filename=tup[3], code_location=tup[4] ) ) filenames.append(f"{tup[3]}") @@ -193,17 +189,15 @@ def readmes2docs(directory=_SOURCE_DIR): category=category, category_desc=_CATEGORY_DESCS[category].strip(), blurbs="\n".join(catlines), - toctree=toctree + toctree=toctree, ) ) text = _FILE_STRUCTURE.format( - header=HEADER, - categories="\n".join(category_sections), - footer=INDEX_FOOTER + header=HEADER, categories="\n".join(category_sections), footer=INDEX_FOOTER ) - with open(_OUT_INDEX_FILE, 'w') as fil: + with open(_OUT_INDEX_FILE, "w") as fil: fil.write(text) print(f" -- Converted Contrib READMEs to {ncount} doc pages + index.") diff --git a/docs/pylib/fmtwidth.py b/docs/pylib/fmtwidth.py index 4c30c5a185..c38a2b81f6 100644 --- a/docs/pylib/fmtwidth.py +++ b/docs/pylib/fmtwidth.py @@ -26,7 +26,11 @@ if __name__ == "__main__": filepaths = glob.glob(args.files, recursive=True) width = args.width - wrapper = textwrap.TextWrapper(width=width, break_long_words=False, expand_tabs=True,) + wrapper = textwrap.TextWrapper( + width=width, + break_long_words=False, + expand_tabs=True, + ) count = 0 for filepath in filepaths: diff --git a/docs/pylib/update_default_cmd_index.py b/docs/pylib/update_default_cmd_index.py index d9f4297e7c..7005999876 100644 --- a/docs/pylib/update_default_cmd_index.py +++ b/docs/pylib/update_default_cmd_index.py @@ -6,11 +6,9 @@ # from os.path import dirname, abspath, join as pathjoin -from evennia.utils.utils import ( - mod_import, variable_from_module, callables_from_module -) +from evennia.utils.utils import mod_import, variable_from_module, callables_from_module -__all__ = ("run_update") +__all__ = "run_update" PAGE = """ @@ -33,6 +31,7 @@ with [EvEditor](EvEditor), flipping pages in [EvMore](EvMore) or using the """ + def run_update(no_autodoc=False): if no_autodoc: @@ -71,7 +70,8 @@ def run_update(no_autodoc=False): for modname in cmd_modules: module = mod_import(modname) cmds_per_module[module] = [ - cmd for cmd in callables_from_module(module).values() if cmd.__name__.startswith("Cmd")] + cmd for cmd in callables_from_module(module).values() if cmd.__name__.startswith("Cmd") + ] for cmd in cmds_per_module[module]: cmd_to_module_map[cmd] = module cmds_alphabetically.append(cmd) @@ -79,8 +79,9 @@ def run_update(no_autodoc=False): cmd_infos = [] for cmd in cmds_alphabetically: - aliases = [alias[1:] if alias and alias[0] == "@" else alias - for alias in sorted(cmd.aliases)] + aliases = [ + alias[1:] if alias and alias[0] == "@" else alias for alias in sorted(cmd.aliases) + ] aliases = f" [{', '.join(sorted(cmd.aliases))}]" if aliases else "" cmdlink = f"[**{cmd.key}**{aliases}]({cmd.__module__}.{cmd.__name__})" category = f"help-category: _{cmd.help_category.capitalize()}_" @@ -98,12 +99,13 @@ def run_update(no_autodoc=False): txt = PAGE.format( ncommands=len(cmd_to_cmdset_map), nfiles=len(cmds_per_module), - alphabetical="\n".join(f"- {info}" for info in cmd_infos)) + alphabetical="\n".join(f"- {info}" for info in cmd_infos), + ) outdir = pathjoin(dirname(dirname(abspath(__file__))), "source", "Components") fname = pathjoin(outdir, "Default-Commands.md") - with open(fname, 'w') as fil: + with open(fname, "w") as fil: fil.write(txt) print(" -- Updated Default Command index.") diff --git a/docs/pylib/update_dynamic_pages.py b/docs/pylib/update_dynamic_pages.py index e9f84a37d7..05fa5bd157 100644 --- a/docs/pylib/update_dynamic_pages.py +++ b/docs/pylib/update_dynamic_pages.py @@ -10,6 +10,7 @@ DOCDIR = pathjoin(ROOTDIR, "docs") DOCSRCDIR = pathjoin(DOCDIR, "source") EVENNIADIR = pathjoin(ROOTDIR, "evennia") + def update_changelog(): """ Plain CHANGELOG copy @@ -22,7 +23,7 @@ def update_changelog(): with open(sourcefile) as fil: txt = fil.read() - with open(targetfile, 'w') as fil: + with open(targetfile, "w") as fil: fil.write(txt) print(" -- Updated Changelog.md") @@ -62,7 +63,7 @@ if settings.SERVERNAME == "Evennia": {txt} ``` """ - with open(targetfile, 'w') as fil: + with open(targetfile, "w") as fil: fil.write(txt) print(" -- Updated Settings-Default.md") diff --git a/docs/source/conf.py b/docs/source/conf.py index 1d93ec24dc..3e62351533 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -7,6 +7,7 @@ import os import sys import re + # from recommonmark.transform import AutoStructify from sphinx.util.osutil import cd @@ -31,7 +32,7 @@ extensions = [ "sphinx.ext.viewcode", "sphinx.ext.todo", "sphinx.ext.githubpages", - "myst_parser" + "myst_parser", ] source_suffix = [".md", ".rst"] @@ -145,9 +146,11 @@ _github_code_root = "https://github.com/evennia/evennia/blob/" _github_doc_root = "https://github.com/evennia/tree/master/docs/sources/" _github_issue_choose = "https://github.com/evennia/evennia/issues/new/choose" _ref_regex = re.compile( # normal reference-links [txt](url) - r"\[(?P[\w -\[\]\`\n]+?)\]\((?P.+?)\)", re.I + re.S + re.U + re.M) + r"\[(?P[\w -\[\]\`\n]+?)\]\((?P.+?)\)", re.I + re.S + re.U + re.M +) _ref_doc_regex = re.compile( # in-document bottom references [txt]: url - r"\[(?P[\w -\`]+?)\\n]:\s+?(?P.+?)(?=$|\n)", re.I + re.S + re.U + re.M) + r"\[(?P[\w -\`]+?)\\n]:\s+?(?P.+?)(?=$|\n)", re.I + re.S + re.U + re.M +) def url_resolver(app, docname, source): @@ -165,10 +168,11 @@ def url_resolver(app, docname, source): """ + def _url_remap(url): # determine depth in tree of current document - docdepth = docname.count('/') + 1 + docdepth = docname.count("/") + 1 relative_path = "../".join("" for _ in range(docdepth)) if url.endswith(_choose_issue): @@ -176,14 +180,14 @@ def url_resolver(app, docname, source): return _github_issue_choose elif _githubstart in url: # github:develop/... shortcut - urlpath = url[url.index(_githubstart) + len(_githubstart):] + urlpath = url[url.index(_githubstart) + len(_githubstart) :] if not (urlpath.startswith("develop/") or urlpath.startswith("master")): urlpath = "master/" + urlpath return _github_code_root + urlpath elif _sourcestart in url: ind = url.index(_sourcestart) - modpath, *inmodule = url[ind + len(_sourcestart):].rsplit("#", 1) + modpath, *inmodule = url[ind + len(_sourcestart) :].rsplit("#", 1) modpath = "/".join(modpath.split(".")) inmodule = "#" + inmodule[0] if inmodule else "" modpath = modpath + ".html" + inmodule @@ -194,13 +198,13 @@ def url_resolver(app, docname, source): return url def _re_ref_sub(match): - txt = match.group('txt') - url = _url_remap(match.group('url')) + txt = match.group("txt") + url = _url_remap(match.group("url")) return f"[{txt}]({url})" def _re_docref_sub(match): - txt = match.group('txt') - url = _url_remap(match.group('url')) + txt = match.group("txt") + url = _url_remap(match.group("url")) return f"[{txt}]: {url}" src = source[0] @@ -248,7 +252,7 @@ autodoc_default_options = { "show-inheritance": True, "special-members": "__init__", "enable_eval_rst": True, - "inherited_members": True + "inherited_members": True, } autodoc_member_order = "bysource" @@ -345,8 +349,12 @@ def setup(app): # build toctree file sys.path.insert(1, os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) - from docs.pylib import (auto_link_remapper, update_default_cmd_index, - contrib_readmes2docs, update_dynamic_pages) + from docs.pylib import ( + auto_link_remapper, + update_default_cmd_index, + contrib_readmes2docs, + update_dynamic_pages, + ) _no_autodoc = os.environ.get("NOAUTODOC") update_default_cmd_index.run_update(no_autodoc=_no_autodoc) diff --git a/evennia/__init__.py b/evennia/__init__.py index a75101be31..1ae50a98ca 100644 --- a/evennia/__init__.py +++ b/evennia/__init__.py @@ -100,6 +100,7 @@ MONITOR_HANDLER = None GLOBAL_SCRIPTS = None OPTION_CLASSES = None + def _create_version(): """ Helper function for building the version string diff --git a/evennia/accounts/accounts.py b/evennia/accounts/accounts.py index 0edf186225..ed1443de09 100644 --- a/evennia/accounts/accounts.py +++ b/evennia/accounts/accounts.py @@ -54,11 +54,12 @@ _CMDHANDLER = None # Create throttles for too many account-creations and login attempts CREATION_THROTTLE = Throttle( - name='creation', limit=settings.CREATION_THROTTLE_LIMIT, - timeout=settings.CREATION_THROTTLE_TIMEOUT + name="creation", + limit=settings.CREATION_THROTTLE_LIMIT, + timeout=settings.CREATION_THROTTLE_TIMEOUT, ) LOGIN_THROTTLE = Throttle( - name='login', limit=settings.LOGIN_THROTTLE_LIMIT, timeout=settings.LOGIN_THROTTLE_TIMEOUT + name="login", limit=settings.LOGIN_THROTTLE_LIMIT, timeout=settings.LOGIN_THROTTLE_TIMEOUT ) @@ -802,8 +803,11 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): except Exception: errors.append( - _("There was an error creating the Account. " - "If this problem persists, contact an admin.")) + _( + "There was an error creating the Account. " + "If this problem persists, contact an admin." + ) + ) logger.log_trace() return None, errors @@ -879,7 +883,6 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): super().delete(*args, **kwargs) return True - # methods inherited from database model def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs): @@ -968,9 +971,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): sessions = self.sessions.get() session = sessions[0] if sessions else None - return _CMDHANDLER( - self, raw_string, callertype="account", session=session, **kwargs - ) + return _CMDHANDLER(self, raw_string, callertype="account", session=session, **kwargs) # channel receive hooks @@ -1000,11 +1001,11 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): """ if senders: - sender_string = ', '.join(sender.get_display_name(self) for sender in senders) + sender_string = ", ".join(sender.get_display_name(self) for sender in senders) message_lstrip = message.lstrip() - if message_lstrip.startswith((':', ';')): + if message_lstrip.startswith((":", ";")): # this is a pose, should show as e.g. "User1 smiles to channel" - spacing = "" if message_lstrip[1:].startswith((':', '\'', ',')) else " " + spacing = "" if message_lstrip[1:].startswith((":", "'", ",")) else " " message = f"{sender_string}{spacing}{message_lstrip[1:]}" else: # normal message @@ -1035,8 +1036,11 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): to customize the message for the receiver on the channel-level. """ - self.msg(text=(message, {"from_channel": channel.id}), - from_obj=senders, options={"from_channel": channel.id}) + self.msg( + text=(message, {"from_channel": channel.id}), + from_obj=senders, + options={"from_channel": channel.id}, + ) def at_post_channel_msg(self, message, channel, senders=None, **kwargs): """ @@ -1373,8 +1377,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): if _MUDINFO_CHANNEL is None: if settings.CHANNEL_MUDINFO: try: - _MUDINFO_CHANNEL = ChannelDB.objects.get( - db_key=settings.CHANNEL_MUDINFO["key"]) + _MUDINFO_CHANNEL = ChannelDB.objects.get(db_key=settings.CHANNEL_MUDINFO["key"]) except ChannelDB.DoesNotExist: logger.log_trace() else: @@ -1383,7 +1386,8 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): if settings.CHANNEL_CONNECTINFO: try: _CONNECT_CHANNEL = ChannelDB.objects.get( - db_key=settings.CHANNEL_CONNECTINFO["key"]) + db_key=settings.CHANNEL_CONNECTINFO["key"] + ) except ChannelDB.DoesNotExist: logger.log_trace() else: @@ -1661,7 +1665,8 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): if sess and sid: result.append( f"\n - |G{char.key}|n [{', '.join(char.permissions.all())}] " - f"(played by you in session {sid})") + f"(played by you in session {sid})" + ) else: result.append( f"\n - |R{char.key}|n [{', '.join(char.permissions.all())}] " diff --git a/evennia/accounts/bots.py b/evennia/accounts/bots.py index ea22af230d..659998de56 100644 --- a/evennia/accounts/bots.py +++ b/evennia/accounts/bots.py @@ -329,9 +329,7 @@ class IRCBot(Bot): chstr = f"{self.db.irc_channel} ({self.db.irc_network}:{self.db.irc_port})" nicklist = ", ".join(sorted(kwargs["nicklist"], key=lambda n: n.lower())) for obj in self._nicklist_callers: - obj.msg( - "Nicks at {chstr}:\n {nicklist}".format(chstr=chstr, nicklist=nicklist) - ) + obj.msg("Nicks at {chstr}:\n {nicklist}".format(chstr=chstr, nicklist=nicklist)) self._nicklist_callers = [] return diff --git a/evennia/accounts/manager.py b/evennia/accounts/manager.py index abe9143287..3c60c2e98c 100644 --- a/evennia/accounts/manager.py +++ b/evennia/accounts/manager.py @@ -276,8 +276,11 @@ class AccountDBManager(TypedObjectManager, UserManager): new_account.set_password(password) new_account._createdict = dict( - locks=locks, permissions=permissions, - report_to=report_to, tags=tags, attributes=attributes + locks=locks, + permissions=permissions, + report_to=report_to, + tags=tags, + attributes=attributes, ) # saving will trigger the signal that calls the # at_first_save hook on the typeclass, where the _createdict diff --git a/evennia/commands/cmdhandler.py b/evennia/commands/cmdhandler.py index 762f808d96..ca18957e48 100644 --- a/evennia/commands/cmdhandler.py +++ b/evennia/commands/cmdhandler.py @@ -80,47 +80,63 @@ _SEARCH_AT_RESULT = utils.variable_from_module(*settings.SEARCH_AT_RESULT.rsplit # is the normal "production message to echo to the account. _ERROR_UNTRAPPED = ( - _(""" + _( + """ An untrapped error occurred. -"""), - _(""" +""" + ), + _( + """ An untrapped error occurred. Please file a bug report detailing the steps to reproduce. -"""), +""" + ), ) _ERROR_CMDSETS = ( - _(""" + _( + """ A cmdset merger-error occurred. This is often due to a syntax error in one of the cmdsets to merge. -"""), - _(""" +""" + ), + _( + """ A cmdset merger-error occurred. Please file a bug report detailing the steps to reproduce. -"""), +""" + ), ) _ERROR_NOCMDSETS = ( - _(""" + _( + """ No command sets found! This is a critical bug that can have multiple causes. -"""), - _(""" +""" + ), + _( + """ No command sets found! This is a sign of a critical bug. If disconnecting/reconnecting doesn't" solve the problem, try to contact the server admin through" some other means for assistance. -"""), +""" + ), ) _ERROR_CMDHANDLER = ( - _(""" + _( + """ A command handler bug occurred. If this is not due to a local change, please file a bug report with the Evennia project, including the traceback and steps to reproduce. -"""), - _(""" +""" + ), + _( + """ A command handler bug occurred. Please notify staff - they should likely file a bug report with the Evennia project. -"""), +""" + ), ) _ERROR_RECURSION_LIMIT = _( diff --git a/evennia/commands/cmdparser.py b/evennia/commands/cmdparser.py index 8e993bbbfb..e2c470b456 100644 --- a/evennia/commands/cmdparser.py +++ b/evennia/commands/cmdparser.py @@ -71,7 +71,7 @@ def build_matches(raw_string, cmdset, include_prefixes=False): for cmdname in [cmd.key] + cmd.aliases if cmdname and l_raw_string.startswith(cmdname.lower()) - and (not cmd.arg_regex or cmd.arg_regex.match(l_raw_string[len(cmdname):])) + and (not cmd.arg_regex or cmd.arg_regex.match(l_raw_string[len(cmdname) :])) ] ) else: @@ -90,7 +90,7 @@ def build_matches(raw_string, cmdset, include_prefixes=False): if ( cmdname and l_raw_string.startswith(cmdname.lower()) - and (not cmd.arg_regex or cmd.arg_regex.match(l_raw_string[len(cmdname):])) + and (not cmd.arg_regex or cmd.arg_regex.match(l_raw_string[len(cmdname) :])) ): matches.append(create_match(cmdname, raw_string, cmd, raw_cmdname)) except Exception: @@ -125,7 +125,10 @@ def try_num_differentiators(raw_string): # the user might be trying to identify the command # with a #num-command style syntax. We expect the regex to # contain the groups "number" and "name". - mindex, new_raw_string = (num_ref_match.group("number"), num_ref_match.group("name") + num_ref_match.group("args")) + mindex, new_raw_string = ( + num_ref_match.group("number"), + num_ref_match.group("name") + num_ref_match.group("args"), + ) return int(mindex), new_raw_string else: return None, None @@ -182,9 +185,7 @@ def cmdparser(raw_string, cmdset, caller, match_index=None): if not matches and _CMD_IGNORE_PREFIXES: # still no match. Try to strip prefixes - raw_string = ( - raw_string.lstrip(_CMD_IGNORE_PREFIXES) if len(raw_string) > 1 else raw_string - ) + raw_string = raw_string.lstrip(_CMD_IGNORE_PREFIXES) if len(raw_string) > 1 else raw_string matches = build_matches(raw_string, cmdset, include_prefixes=False) # only select command matches we are actually allowed to call. diff --git a/evennia/commands/cmdset.py b/evennia/commands/cmdset.py index e23143c563..1a0aa952b0 100644 --- a/evennia/commands/cmdset.py +++ b/evennia/commands/cmdset.py @@ -358,14 +358,18 @@ class CmdSet(object, metaclass=_CmdSetMeta): """ perm = "perm" if self.persistent else "non-perm" - options = ", ".join([ - "{}:{}".format(opt, "T" if getattr(self, opt) else "F") - for opt in ("no_exits", "no_objs", "no_channels", "duplicates") - if getattr(self, opt) is not None - ]) + options = ", ".join( + [ + "{}:{}".format(opt, "T" if getattr(self, opt) else "F") + for opt in ("no_exits", "no_objs", "no_channels", "duplicates") + if getattr(self, opt) is not None + ] + ) options = (", " + options) if options else "" - return (f": " - + ", ".join([str(cmd) for cmd in sorted(self.commands, key=lambda o: o.key)])) + return ( + f": " + + ", ".join([str(cmd) for cmd in sorted(self.commands, key=lambda o: o.key)]) + ) def __iter__(self): """ @@ -519,10 +523,12 @@ class CmdSet(object, metaclass=_CmdSetMeta): try: cmdset = self._instantiate(cmdset) except RuntimeError: - err = ("Adding cmdset {cmdset} to {cls} lead to an " - "infinite loop. When adding a cmdset to another, " - "make sure they are not themself cyclically added to " - "the new cmdset somewhere in the chain.") + err = ( + "Adding cmdset {cmdset} to {cls} lead to an " + "infinite loop. When adding a cmdset to another, " + "make sure they are not themself cyclically added to " + "the new cmdset somewhere in the chain." + ) raise RuntimeError(_(err.format(cmdset=cmdset, cls=self.__class__))) cmds = cmdset.commands elif is_iter(cmd): diff --git a/evennia/commands/cmdsethandler.py b/evennia/commands/cmdsethandler.py index 6f76a74842..49611e54d2 100644 --- a/evennia/commands/cmdsethandler.py +++ b/evennia/commands/cmdsethandler.py @@ -423,8 +423,7 @@ class CmdSetHandler(object): self.mergetype_stack.append(new_current.actual_mergetype) self.current = new_current - def add(self, cmdset, emit_to_obj=None, persistent=False, default_cmdset=False, - **kwargs): + def add(self, cmdset, emit_to_obj=None, persistent=False, default_cmdset=False, **kwargs): """ Add a cmdset to the handler, on top of the old ones, unless it is set as the default one (it will then end up at the bottom of the stack) @@ -451,9 +450,11 @@ class CmdSetHandler(object): """ if "permanent" in kwargs: - logger.log_dep("obj.cmdset.add() kwarg 'permanent' has changed name to " - "'persistent' and now defaults to True.") - persistent = kwargs['permanent'] if persistent is None else persistent + logger.log_dep( + "obj.cmdset.add() kwarg 'permanent' has changed name to " + "'persistent' and now defaults to True." + ) + persistent = kwargs["permanent"] if persistent is None else persistent if not (isinstance(cmdset, str) or utils.inherits_from(cmdset, CmdSet)): string = _("Only CmdSets can be added to the cmdsethandler!") @@ -491,9 +492,10 @@ class CmdSetHandler(object): """ if "permanent" in kwargs: - logger.log_dep("obj.cmdset.add_default() kwarg 'permanent' has changed name to " - "'persistent'.") - persistent = kwargs['permanent'] if persistent is None else persistent + logger.log_dep( + "obj.cmdset.add_default() kwarg 'permanent' has changed name to 'persistent'." + ) + persistent = kwargs["permanent"] if persistent is None else persistent self.add(cmdset, emit_to_obj=emit_to_obj, persistent=persistent, default_cmdset=True) def remove(self, cmdset=None, default_cmdset=False): diff --git a/evennia/commands/command.py b/evennia/commands/command.py index ad7aa8746a..4c9c24d403 100644 --- a/evennia/commands/command.py +++ b/evennia/commands/command.py @@ -102,16 +102,16 @@ def _init_command(cls, **kwargs): # pre-prepare a help index entry for quicker lookup # strip the @- etc to allow help to be agnostic stripped_key = cls.key[1:] if cls.key and cls.key[0] in CMD_IGNORE_PREFIXES else "" - stripped_aliases = ( - " ".join(al[1:] if al and al[0] in CMD_IGNORE_PREFIXES else al - for al in cls.aliases)) + stripped_aliases = " ".join( + al[1:] if al and al[0] in CMD_IGNORE_PREFIXES else al for al in cls.aliases + ) cls.search_index_entry = { "key": cls.key, "aliases": " ".join(cls.aliases), "no_prefix": f"{stripped_key} {stripped_aliases}", "category": cls.help_category, "text": cls.__doc__, - "tags": "" + "tags": "", } @@ -562,7 +562,7 @@ Command {self} has no defined `func()` - showing on-command variables: """ try: return reverse( - 'help-entry-detail', + "help-entry-detail", kwargs={"category": slugify(self.help_category), "topic": slugify(self.key)}, ) except Exception as e: diff --git a/evennia/commands/default/admin.py b/evennia/commands/default/admin.py index 1002c0b30d..cfe70bd5cc 100644 --- a/evennia/commands/default/admin.py +++ b/evennia/commands/default/admin.py @@ -217,7 +217,7 @@ class CmdBan(COMMAND_DEFAULT_CLASS): ipregex = re.compile(r"%s" % ipregex) bantup = ("", ban, ipregex, now, reason) - ret = yield(f"Are you sure you want to {typ}-ban '|w{ban}|n' [Y]/N?") + ret = yield (f"Are you sure you want to {typ}-ban '|w{ban}|n' [Y]/N?") if str(ret).lower() in ("no", "n"): self.caller.msg("Aborted.") return @@ -273,7 +273,7 @@ class CmdUnban(COMMAND_DEFAULT_CLASS): ban = banlist[num - 1] value = (" ".join([s for s in ban[:2]])).strip() - ret = yield(f"Are you sure you want to unban {num}: '|w{value}|n' [Y]/N?") + ret = yield (f"Are you sure you want to unban {num}: '|w{value}|n' [Y]/N?") if str(ret).lower() in ("n", "no"): self.caller.msg("Aborted.") return diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index 6372c469e0..e42d33ae5d 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -17,7 +17,8 @@ from evennia.utils.utils import ( class_from_module, get_all_typeclasses, variable_from_module, - dbref, crop, + dbref, + crop, interactive, list_to_string, display_len, @@ -1498,9 +1499,11 @@ class CmdOpen(ObjManipCommand): super().parse() self.location = self.caller.location if not self.args or not self.rhs: - self.caller.msg("Usage: open [;alias...][:typeclass]" - "[,[;alias..][:typeclass]]] " - "= ") + self.caller.msg( + "Usage: open [;alias...][:typeclass]" + "[,[;alias..][:typeclass]]] " + "= " + ) raise InterruptCommand if not self.location: self.caller.msg("You cannot create an exit from a None-location.") @@ -1519,8 +1522,9 @@ class CmdOpen(ObjManipCommand): as well as the self.create_exit() method. """ # Create exit - ok = self.create_exit(self.exit_name, self.location, self.destination, - self.exit_aliases, self.exit_typeclass) + ok = self.create_exit( + self.exit_name, self.location, self.destination, self.exit_aliases, self.exit_typeclass + ) if not ok: # an error; the exit was not created, so we quit. return @@ -1529,8 +1533,13 @@ class CmdOpen(ObjManipCommand): back_exit_name = self.lhs_objs[1]["name"] back_exit_aliases = self.lhs_objs[1]["aliases"] back_exit_typeclass = self.lhs_objs[1]["option"] - self.create_exit(back_exit_name, self.destination, self.location, back_exit_aliases, - back_exit_typeclass) + self.create_exit( + back_exit_name, + self.destination, + self.location, + back_exit_aliases, + back_exit_typeclass, + ) def _convert_from_string(cmd, strobj): @@ -1740,8 +1749,10 @@ class CmdSetAttribute(ObjManipCommand): obj.attributes.remove(attr, category=category) return f"\nDeleted attribute {obj.name}/|w{attr}|n [category:{category}]." else: - return (f"\nNo attribute {obj.name}/|w{attr}|n [category: {category}] " - "was found to delete.") + return ( + f"\nNo attribute {obj.name}/|w{attr}|n [category: {category}] " + "was found to delete." + ) error = f"\nNo attribute {obj.name}/|w{attr}|n [category: {category}] was found to delete." if nested: error += " (Nested lookups attempted)" @@ -1813,7 +1824,7 @@ class CmdSetAttribute(ObjManipCommand): except AttributeError: # we set empty buffer on nonexisting Attribute because otherwise # we'd always have the string "None" in the buffer to start with - old_value = '' + old_value = "" return str(old_value) # we already confirmed we are ok with this def save(caller, buf): @@ -1825,11 +1836,12 @@ class CmdSetAttribute(ObjManipCommand): try: old_value = obj.attributes.get(attr, raise_exception=True) if not isinstance(old_value, str): - answer = yield( + answer = yield ( f"|rWarning: Attribute |w{attr}|r is of type |w{type(old_value).__name__}|r. " "\nTo continue editing, it must be converted to (and saved as) a string. " - "Continue? [Y]/N?") - if answer.lower() in ('n', 'no'): + "Continue? [Y]/N?" + ) + if answer.lower() in ("n", "no"): self.caller.msg("Aborted edit.") return except AttributeError: @@ -1903,9 +1915,11 @@ class CmdSetAttribute(ObjManipCommand): caller.msg("The Line editor can only be applied " "to one attribute at a time.") return if not attrs: - caller.msg("Use `set/edit /` to define the Attribute to edit.\nTo " - "edit the current room description, use `set/edit here/desc` (or " - "use the `desc` command).") + caller.msg( + "Use `set/edit /` to define the Attribute to edit.\nTo " + "edit the current room description, use `set/edit here/desc` (or " + "use the `desc` command)." + ) return self.edit_handler(obj, attrs[0], caller) return @@ -1936,8 +1950,10 @@ class CmdSetAttribute(ObjManipCommand): global _ATTRFUNCPARSER if not _ATTRFUNCPARSER: _ATTRFUNCPARSER = funcparser.FuncParser( - {"dbref": funcparser.funcparser_callable_search, - "search": funcparser.funcparser_callable_search} + { + "dbref": funcparser.funcparser_callable_search, + "search": funcparser.funcparser_callable_search, + } ) if not (obj.access(self.caller, "control") or obj.access(self.caller, "edit")): @@ -1951,10 +1967,13 @@ class CmdSetAttribute(ObjManipCommand): if hasattr(parsed_value, "access"): # if this is an object we must have the right to read it, if so, # we will not convert it to a string - if not (parsed_value.access(caller, "control") - or parsed_value.access(self.caller, "edit")): - caller.msg("You don't have permission to set " - f"object with identifier '{value}'.") + if not ( + parsed_value.access(caller, "control") + or parsed_value.access(self.caller, "edit") + ): + caller.msg( + "You don't have permission to set " f"object with identifier '{value}'." + ) continue value = parsed_value else: @@ -2038,7 +2057,7 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS): obj = caller.search(query) if not obj: return - elif (self.account and self.account.__dbclass__ == dbclass): + elif self.account and self.account.__dbclass__ == dbclass: # applying account while caller is object caller.msg(f"Trying to search {new_typeclass} with query '{self.lhs}'.") obj = self.account.search(query) @@ -2071,7 +2090,7 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS): caller = self.caller - if "list" in self.switches or self.cmdname in ('typeclasses', '@typeclasses'): + if "list" in self.switches or self.cmdname in ("typeclasses", "@typeclasses"): tclasses = get_all_typeclasses() contribs = [key for key in sorted(tclasses) if key.startswith("evennia.contrib")] or [ "" @@ -2188,8 +2207,10 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS): is_same = obj.is_typeclass(new_typeclass, exact=True) if is_same and "force" not in self.switches: - string = (f"{obj.name} already has the typeclass '{new_typeclass}'. " - "Use /force to override.") + string = ( + f"{obj.name} already has the typeclass '{new_typeclass}'. " + "Use /force to override." + ) else: update = "update" in self.switches reset = "reset" in self.switches @@ -2220,7 +2241,8 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS): if "prototype" in self.switches: modified = spawner.batch_update_objects_with_prototype( - prototype, objects=[obj], caller=self.caller) + prototype, objects=[obj], caller=self.caller + ) prototype_success = modified > 0 if not prototype_success: caller.msg("Prototype %s failed to apply." % prototype["key"]) @@ -2543,9 +2565,7 @@ class CmdExamine(ObjManipCommand): def format_locks(self, obj): locks = str(obj.locks) if locks: - return utils.fill( - "; ".join([lock for lock in locks.split(";")]), indent=2 - ) + return utils.fill("; ".join([lock for lock in locks.split(";")]), indent=2) return "Default" def format_scripts(self, obj): @@ -2572,6 +2592,7 @@ class CmdExamine(ObjManipCommand): if value: return f"{string}: T" return f"{string}: F" + txt = ", ".join( _truefalse(opt, getattr(cmdset, opt)) for opt in ("no_exits", "no_objs", "no_channels", "duplicates") @@ -2607,13 +2628,18 @@ class CmdExamine(ObjManipCommand): # 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()]) + 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()]) + 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 @@ -2659,8 +2685,10 @@ class CmdExamine(ObjManipCommand): typ = f" |B[type: {typ}]|n" if typ else "" value = utils.to_str(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}") + 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 @@ -2680,8 +2708,7 @@ class CmdExamine(ObjManipCommand): def format_attributes(self, obj): output = "\n " + "\n ".join( - sorted(self.format_single_attribute(attr) - for attr in obj.db_attributes.all()) + sorted(self.format_single_attribute(attr) for attr in obj.db_attributes.all()) ) if output.strip(): # we don't want just an empty line @@ -2695,8 +2722,7 @@ class CmdExamine(ObjManipCommand): if ndb_attr and ndb_attr[0]: return "\n " + " \n".join( - sorted(self.format_single_attribute(attr) - for attr, value in ndb_attr) + sorted(self.format_single_attribute(attr) for attr, value in ndb_attr) ) def format_exits(self, obj): @@ -2706,14 +2732,16 @@ class CmdExamine(ObjManipCommand): def format_chars(self, obj): if hasattr(obj, "contents"): - chars = ", ".join(f"{obj.name}({obj.dbref})" for obj in obj.contents - if obj.account) + 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) + 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 format_script_desc(self, obj): @@ -2736,8 +2764,10 @@ class CmdExamine(ObjManipCommand): remaining_repeats = obj.remaining_repeats() remaining_repeats = 0 if remaining_repeats is None else remaining_repeats repeats = f" - {remaining_repeats}/{obj.db_repeats} remain" - return (f"{active} - interval: {interval}s " - f"(next: {next_repeat}{repeats}, start_delay: {start_delay})") + return ( + f"{active} - interval: {interval}s " + f"(next: {next_repeat}{repeats}, start_delay: {start_delay})" + ) def format_channel_sub_totals(self, obj): if hasattr(obj, "db_account_subscriptions"): @@ -2752,14 +2782,16 @@ class CmdExamine(ObjManipCommand): account_subs = obj.db_account_subscriptions.all() if account_subs: return "\n " + "\n ".join( - format_grid([sub.key for sub in account_subs], sep=' ', width=_DEFAULT_WIDTH)) + format_grid([sub.key for sub in account_subs], sep=" ", width=_DEFAULT_WIDTH) + ) def format_channel_object_subs(self, obj): if hasattr(obj, "db_object_subscriptions"): object_subs = obj.db_object_subscriptions.all() if object_subs: return "\n " + "\n ".join( - format_grid([sub.key for sub in object_subs], sep=' ', width=_DEFAULT_WIDTH)) + format_grid([sub.key for sub in object_subs], sep=" ", width=_DEFAULT_WIDTH) + ) def get_formatted_obj_data(self, obj, current_cmdset): """ @@ -2781,13 +2813,14 @@ class CmdExamine(ObjManipCommand): 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")): + 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[ + f"Commands vailable to {obj.key} (result of Merged Cmdset(s))" + ] = self.format_current_cmds(obj, current_cmdset) if self.object_type == "script": objdata["Description"] = self.format_script_desc(obj) objdata["Persistent"] = self.format_script_is_persistent(obj) @@ -2859,10 +2892,11 @@ class CmdExamine(ObjManipCommand): obj = None elif len(obj) > 1: err = "Multiple {objtype} found with key {obj_name}:\n{matches}" - self.caller.msg(err.format( - obj_name=obj_name, - matches=", ".join(f"{ob.key}(#{ob.id})" for ob in obj) - )) + self.caller.msg( + err.format( + obj_name=obj_name, matches=", ".join(f"{ob.key}(#{ob.id})" for ob in obj) + ) + ) obj = None else: obj = obj[0] @@ -2887,13 +2921,16 @@ class CmdExamine(ObjManipCommand): # is not so common anyway. obj = None - obj_name = objdef["name"] # name + obj_name = objdef["name"] # name obj_attrs = objdef["attrs"] # /attrs # identify object type, in prio account - script - channel object_type = "object" - if (utils.inherits_from(self.caller, "evennia.accounts.accounts.DefaultAccount") - or "account" in self.switches or obj_name.startswith("*")): + if ( + utils.inherits_from(self.caller, "evennia.accounts.accounts.DefaultAccount") + or "account" in self.switches + or obj_name.startswith("*") + ): object_type = "account" elif "script" in self.switches: object_type = "script" @@ -3293,7 +3330,7 @@ class CmdScripts(COMMAND_DEFAULT_CLASS): "start": "|gStarted|n", "stop": "|RStopped|n", "pause": "|Paused|n", - "delete": "|rDeleted|n" + "delete": "|rDeleted|n", } def _search_script(self, args): @@ -3307,7 +3344,7 @@ class CmdScripts(COMMAND_DEFAULT_CLASS): return scripts if "-" in args: # may be a dbref-range - val1, val2 = (dbref(part.strip()) for part in args.split('-', 1)) + val1, val2 = (dbref(part.strip()) for part in args.split("-", 1)) if val1 and val2: scripts = ScriptDB.objects.filter(id__in=(range(val1, val2 + 1))) if scripts: @@ -3348,11 +3385,14 @@ class CmdScripts(COMMAND_DEFAULT_CLASS): if obj.scripts.add(self.rhs, autostart=True): caller.msg( f"Script |w{self.rhs}|n successfully added and " - f"started on {obj.get_display_name(caller)}.") + f"started on {obj.get_display_name(caller)}." + ) else: - caller.msg(f"Script {self.rhs} could not be added and/or started " - f"on {obj.get_display_name(caller)} (or it started and " - "immediately shut down).") + caller.msg( + f"Script {self.rhs} could not be added and/or started " + f"on {obj.get_display_name(caller)} (or it started and " + "immediately shut down)." + ) else: # just show all scripts on object scripts = ScriptDB.objects.filter(db_obj=obj) @@ -3374,12 +3414,15 @@ class CmdScripts(COMMAND_DEFAULT_CLASS): new_script = None if new_script: - caller.msg(f"Global Script Created - " - f"{new_script.key} ({new_script.typeclass_path})") + caller.msg( + f"Global Script Created - " + f"{new_script.key} ({new_script.typeclass_path})" + ) ScriptEvMore(caller, [new_script], session=self.session) else: - caller.msg(f"Global Script |rNOT|n Created |r(see log)|n - " - f"arguments: {self.args}") + caller.msg( + f"Global Script |rNOT|n Created |r(see log)|n - " f"arguments: {self.args}" + ) elif scripts or obj: # modification switches - must operate on existing scripts @@ -3388,9 +3431,11 @@ class CmdScripts(COMMAND_DEFAULT_CLASS): scripts = ScriptDB.objects.filter(db_obj=obj) if scripts.count() > 1: - ret = yield(f"Multiple scripts found: {scripts}. Are you sure you want to " - "operate on all of them? [Y]/N? ") - if ret.lower() in ('n', 'no'): + ret = yield ( + f"Multiple scripts found: {scripts}. Are you sure you want to " + "operate on all of them? [Y]/N? " + ) + if ret.lower() in ("n", "no"): caller.msg("Aborted.") return @@ -3406,11 +3451,14 @@ class CmdScripts(COMMAND_DEFAULT_CLASS): getattr(script, switch)() except Exception: logger.log_trace() - msgs.append(f"{scripttype} |rNOT|n {verb} |r(see log)|n - " - f"{script_key} ({script_typeclass_path})|n") + msgs.append( + f"{scripttype} |rNOT|n {verb} |r(see log)|n - " + f"{script_key} ({script_typeclass_path})|n" + ) else: - msgs.append(f"{scripttype} {verb} - " - f"{script_key} ({script_typeclass_path})") + msgs.append( + f"{scripttype} {verb} - " f"{script_key} ({script_typeclass_path})" + ) caller.msg("\n".join(msgs)) if "delete" not in self.switches: ScriptEvMore(caller, [script], session=self.session) @@ -3488,7 +3536,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" ) @@ -3620,14 +3668,18 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS): # check any locks if not (caller.permissions.check("Admin") or obj_to_teleport.access(caller, "teleport")): - caller.msg(f"{obj_to_teleport} 'teleport'-lock blocks you from teleporting " - "it anywhere.") + caller.msg( + f"{obj_to_teleport} 'teleport'-lock blocks you from teleporting " "it anywhere." + ) return - if not (caller.permissions.check("Admin") - or destination.access(obj_to_teleport, "teleport_here")): - caller.msg(f"{destination} 'teleport_here'-lock blocks {obj_to_teleport} from " - "moving there.") + if not ( + caller.permissions.check("Admin") + or destination.access(obj_to_teleport, "teleport_here") + ): + caller.msg( + f"{destination} 'teleport_here'-lock blocks {obj_to_teleport} from " "moving there." + ) return # try the teleport @@ -3636,8 +3688,11 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS): obj_to_teleport.location = destination caller.msg(f"Teleported {obj_to_teleport} None -> {destination}") elif obj_to_teleport.move_to( - destination, quiet="quiet" in self.switches, - emit_to_obj=caller, use_destination="intoexit" not in self.switches): + destination, + quiet="quiet" in self.switches, + emit_to_obj=caller, + use_destination="intoexit" not in self.switches, + ): if obj_to_teleport == caller: caller.msg(f"Teleported to {destination}.") @@ -3995,7 +4050,7 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS): self.caller.msg("No prototypes found.") def _list_prototypes(self, key=None, tags=None): - """Display prototypes as a list, optionally limited by key/tags. """ + """Display prototypes as a list, optionally limited by key/tags.""" protlib.list_prototypes(self.caller, key=key, tags=tags, session=self.session) @interactive @@ -4039,7 +4094,9 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS): return try: n_updated = spawner.batch_update_objects_with_prototype( - prototype, objects=existing_objects, caller=caller, + prototype, + objects=existing_objects, + caller=caller, ) except Exception: logger.log_trace() diff --git a/evennia/commands/default/comms.py b/evennia/commands/default/comms.py index ee4138a80c..d1219c1a4e 100644 --- a/evennia/commands/default/comms.py +++ b/evennia/commands/default/comms.py @@ -20,16 +20,15 @@ from evennia.utils.evmenu import ask_yes_no COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS) CHANNEL_DEFAULT_TYPECLASS = class_from_module( - settings.BASE_CHANNEL_TYPECLASS, fallback=settings.FALLBACK_CHANNEL_TYPECLASS) + settings.BASE_CHANNEL_TYPECLASS, fallback=settings.FALLBACK_CHANNEL_TYPECLASS +) # limit symbol import for API __all__ = ( "CmdChannel", "CmdObjectChannel", - "CmdPage", - "CmdIRC2Chan", "CmdIRCStatus", "CmdRSS2Chan", @@ -207,6 +206,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): ban mychannel1,mychannel2= EvilUser : Was banned for spamming. """ + key = "@channel" aliases = ["@chan", "@channels"] help_category = "Comms" @@ -215,8 +215,25 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): # the manage: lock controls access to /create/destroy/desc/lock/unlock switches locks = "cmd:not pperm(channel_banned);admin:all();manage:all();changelocks:perm(Admin)" switch_options = ( - "list", "all", "history", "sub", "unsub", "mute", "unmute", "alias", "unalias", - "create", "destroy", "desc", "lock", "unlock", "boot", "ban", "unban", "who",) + "list", + "all", + "history", + "sub", + "unsub", + "mute", + "unmute", + "alias", + "unalias", + "create", + "destroy", + "desc", + "lock", + "unlock", + "boot", + "ban", + "unban", + "who", + ) # disable this in child command classes if wanting on-character channels account_caller = True @@ -253,17 +270,24 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): channels = CHANNEL_DEFAULT_TYPECLASS.objects.channel_search(channelname, exact=exact) # check permissions - channels = [channel for channel in channels - if channel.access(caller, 'listen') or channel.access(caller, 'control')] + channels = [ + channel + for channel in channels + if channel.access(caller, "listen") or channel.access(caller, "control") + ] if handle_errors: if not channels: - self.msg(f"No channel found matching '{channelname}' " - "(could also be due to missing access).") + self.msg( + f"No channel found matching '{channelname}' " + "(could also be due to missing access)." + ) return None elif len(channels) > 1: - self.msg("Multiple possible channel matches/alias for " - "'{channelname}':\n" + ", ".join(chan.key for chan in channels)) + self.msg( + "Multiple possible channel matches/alias for " + "'{channelname}':\n" + ", ".join(chan.key for chan in channels) + ) return None return channels[0] else: @@ -312,6 +336,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): return self.msg( "".join(line.split("[-]", 1)[1] if "[-]" in line else line for line in lines) ) + # asynchronously tail the log file tail_log_file(log_file, start_index, 20, callback=send_msg) @@ -491,7 +516,8 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): lockstring = "send:all();listen:all();control:id(%s)" % caller.id new_chan = create.create_channel( - name, aliases=aliases, desc=description, locks=lockstring, typeclass=typeclass) + name, aliases=aliases, desc=description, locks=lockstring, typeclass=typeclass + ) self.sub_to_channel(new_chan) return new_chan, "" @@ -514,14 +540,14 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): channel_key = channel.key if message is None: - message = (f"|rChannel {channel_key} is being destroyed. " - "Make sure to clean any channel aliases.|n") + message = ( + f"|rChannel {channel_key} is being destroyed. " + "Make sure to clean any channel aliases.|n" + ) if message: channel.msg(message, senders=caller, bypass_mute=True) channel.delete() - logger.log_sec( - "Channel {} was deleted by {}".format(channel_key, caller) - ) + logger.log_sec("Channel {} was deleted by {}".format(channel_key, caller)) def set_lock(self, channel, lockstring): """ @@ -610,8 +636,10 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): if not quiet: channel.msg(f"{target.key} was booted from channel by {self.caller.key}.{reason}") - logger.log_sec(f"Channel Boot: {target} (Channel: {channel}, " - f"Reason: {reason.strip()}, Caller: {self.caller}") + logger.log_sec( + f"Channel Boot: {target} (Channel: {channel}, " + f"Reason: {reason.strip()}, Caller: {self.caller}" + ) return True, "" def ban_user(self, channel, target, quiet=False, reason=""): @@ -684,7 +712,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): caller = self.caller mute_list = list(channel.mutelist) online_list = channel.subscriptions.online() - if channel.access(caller, 'control'): + if channel.access(caller, "control"): # for those with channel control, show also offline users all_subs = list(channel.subscriptions.all()) else: @@ -694,8 +722,10 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): who_list = [] for subscriber in all_subs: name = subscriber.get_display_name(caller) - conditions = ("muting" if subscriber in mute_list else "", - "offline" if subscriber not in online_list else "") + conditions = ( + "muting" if subscriber in mute_list else "", + "offline" if subscriber not in online_list else "", + ) conditions = [cond for cond in conditions if cond] cond_text = "(" + ", ".join(conditions) + ")" if conditions else "" who_list.append(f"{name}{cond_text}") @@ -743,7 +773,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): "locks", "description", align="l", - maxwidth=_DEFAULT_WIDTH + maxwidth=_DEFAULT_WIDTH, ) for chan in subscribed: @@ -756,14 +786,14 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): my_aliases = ", ".join(self.get_channel_aliases(chan)) comtable.add_row( *( - chanid, - "{key}{aliases}".format( - key=chan.key, - aliases=";"+ ";".join(chan.aliases.all()) if chan.aliases.all() else "" - ), - my_aliases, - locks, - chan.db.desc + chanid, + "{key}{aliases}".format( + key=chan.key, + aliases=";" + ";".join(chan.aliases.all()) if chan.aliases.all() else "", + ), + my_aliases, + locks, + chan.db.desc, ) ) return comtable @@ -799,11 +829,14 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): substatus = "|gYes|n" my_aliases = ", ".join(self.get_channel_aliases(chan)) comtable.add_row( - *(substatus, - chan.key, - ",".join(chan.aliases.all()) if chan.aliases.all() else "", - my_aliases, - chan.db.desc)) + *( + substatus, + chan.key, + ",".join(chan.aliases.all()) if chan.aliases.all() else "", + my_aliases, + chan.db.desc, + ) + ) comtable.reformat_column(0, width=8) return comtable @@ -818,16 +851,17 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): switches = self.switches channel_names = [name for name in self.lhslist if name] - #from evennia import set_trace;set_trace() + # from evennia import set_trace;set_trace() - if 'all' in switches: + if "all" in switches: # show all available channels subscribed, available = self.list_channels() table = self.display_all_channels(subscribed, available) self.msg( "\n|wAvailable channels|n (use no argument to " - f"only show your subscriptions)\n{table}") + f"only show your subscriptions)\n{table}" + ) return if not channel_names: @@ -835,15 +869,16 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): subscribed, _ = self.list_channels() table = self.display_subbed_channels(subscribed) - self.msg("\n|wChannel subscriptions|n " - f"(use |w/all|n to see all available):\n{table}") + self.msg( + "\n|wChannel subscriptions|n " f"(use |w/all|n to see all available):\n{table}" + ) return if not self.switches and not self.args: self.msg("Usage[/switches]: channel [= message]") return - if 'create' in switches: + if "create" in switches: # create a new channel if not self.access(caller, "manage"): @@ -865,7 +900,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): self.msg(err) return - if 'unalias' in switches: + if "unalias" in switches: # remove a personal alias (no channel needed) alias = self.args.strip() if not alias: @@ -884,12 +919,11 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): # channels without a space in their name), we need to check if the # first 'channel name' is in fact 'channelname text' no_rhs_channel_name = self.args.split(" ", 1)[0] - possible_lhs_message = self.args[len(no_rhs_channel_name):] - if possible_lhs_message.strip() == '=': + possible_lhs_message = self.args[len(no_rhs_channel_name) :] + if possible_lhs_message.strip() == "=": possible_lhs_message = "" channel_names.append(no_rhs_channel_name) - channels = [] errors = [] for channel_name in channel_names: @@ -897,16 +931,20 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): # 'listen/control' perms. found_channels = self.search_channel(channel_name, exact=False, handle_errors=False) if not found_channels: - errors.append(f"No channel found matching '{channel_name}' " - "(could also be due to missing access).") + errors.append( + f"No channel found matching '{channel_name}' " + "(could also be due to missing access)." + ) elif len(found_channels) > 1: - errors.append("Multiple possible channel matches/alias for " - "'{channel_name}':\n" + ", ".join(chan.key for chan in found_channels)) + errors.append( + "Multiple possible channel matches/alias for " + "'{channel_name}':\n" + ", ".join(chan.key for chan in found_channels) + ) else: channels.append(found_channels[0]) if not channels: - self.msg('\n'.join(errors)) + self.msg("\n".join(errors)) return # we have at least one channel at this point @@ -925,30 +963,35 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): if channel in subscribed: table = self.display_subbed_channels([channel]) header = f"Channel |w{channel.key}|n" - self.msg(f"{header}\n(use |w{channel.key} |n (or a channel-alias) " - f"to chat and the 'channel' command " - f"to customize)\n{table}") + self.msg( + f"{header}\n(use |w{channel.key} |n (or a channel-alias) " + f"to chat and the 'channel' command " + f"to customize)\n{table}" + ) elif channel in available: table = self.display_all_channels([], [channel]) self.msg( "\n|wNot subscribed to this channel|n (use /list to " - f"show all subscriptions)\n{table}") + f"show all subscriptions)\n{table}" + ) return - if 'history' in switches or 'hist' in switches: + if "history" in switches or "hist" in switches: # view channel history index = self.rhs or 0 try: index = max(0, int(index)) except ValueError: - self.msg("The history index (describing how many lines to go back) " - "must be an integer >= 0.") + self.msg( + "The history index (describing how many lines to go back) " + "must be an integer >= 0." + ) return self.get_channel_history(channel, start_index=index) return - if 'sub' in switches: + if "sub" in switches: # subscribe to a channel aliases = [] if self.rhs: @@ -957,26 +1000,29 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): if success: for alias in aliases: self.add_alias(channel, alias) - alias_txt = ', '.join(aliases) - alias_txt = f" using alias(es) {alias_txt}" if aliases else '' - self.msg("You are now subscribed " - f"to the channel {channel.key}{alias_txt}. Use /alias to " - "add additional aliases for referring to the channel.") + alias_txt = ", ".join(aliases) + alias_txt = f" using alias(es) {alias_txt}" if aliases else "" + self.msg( + "You are now subscribed " + f"to the channel {channel.key}{alias_txt}. Use /alias to " + "add additional aliases for referring to the channel." + ) else: self.msg(err) return - if 'unsub' in switches: + if "unsub" in switches: # un-subscribe from a channel success, err = self.unsub_from_channel(channel) if success: - self.msg(f"You un-subscribed from channel {channel.key}. " - "All aliases were cleared.") + self.msg( + f"You un-subscribed from channel {channel.key}. " "All aliases were cleared." + ) else: self.msg(err) return - if 'alias' in switches: + if "alias" in switches: # create a new personal alias for a channel alias = self.rhs if not alias: @@ -986,7 +1032,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): self.msg(f"Added/updated your alias '{alias}' for channel {channel.key}.") return - if 'mute' in switches: + if "mute" in switches: # mute a given channel success, err = self.mute_channel(channel) if success: @@ -995,7 +1041,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): self.msg(err) return - if 'unmute' in switches: + if "unmute" in switches: # unmute a given channel success, err = self.unmute_channel(channel) if success: @@ -1004,7 +1050,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): self.msg(err) return - if 'destroy' in switches or 'delete' in switches: + if "destroy" in switches or "delete" in switches: # destroy a channel we control if not self.access(caller, "manage"): @@ -1028,10 +1074,10 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): "remove all users' aliases. {options}?", yes_action=_perform_delete, no_action="Aborted.", - default="N" + default="N", ) - if 'desc' in switches: + if "desc" in switches: # set channel description if not self.access(caller, "manage"): @@ -1051,7 +1097,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): self.set_desc(channel, desc) self.msg("Updated channel description.") - if 'lock' in switches: + if "lock" in switches: # add a lockstring to channel if not self.access(caller, "changelocks"): @@ -1075,7 +1121,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): self.msg(f"Could not add/update lock: {err}") return - if 'unlock' in switches: + if "unlock" in switches: # remove/update lockstring from channel if not self.access(caller, "changelocks"): @@ -1099,7 +1145,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): self.msg(f"Could not remove lock: {err}") return - if 'boot' in switches: + if "boot" in switches: # boot a user from channel(s) if not self.access(caller, "admin"): @@ -1134,8 +1180,9 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): self.msg(f"Cannot boot {target.key} from channel {chan.key}: {err}") channames = ", ".join(chan.key for chan in channels) - reasonwarn = (". Also note that your reason will be echoed to the channel" - if reason else '') + reasonwarn = ( + ". Also note that your reason will be echoed to the channel" if reason else "" + ) ask_yes_no( caller, prompt=f"Are you sure you want to boot user {target.key} from " @@ -1143,11 +1190,11 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): "{options}?", yes_action=_boot_user, no_action="Aborted.", - default="Y" + default="Y", ) return - if 'ban' in switches: + if "ban" in switches: # ban a user from channel(s) if not self.access(caller, "admin"): @@ -1161,8 +1208,10 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): self.msg(f"You need 'control'-access to view bans on channel {channel.key}") return - bans = ["Channel bans " - "(to ban, use channel/ban channel[,channel,...] = username [:reason]"] + bans = [ + "Channel bans " + "(to ban, use channel/ban channel[,channel,...] = username [:reason]" + ] bans.extend(self.channel_list_bans(channel)) self.msg("\n".join(bans)) return @@ -1191,8 +1240,9 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): self.msg(f"Cannot boot {target.key} from channel {chan.key}: {err}") channames = ", ".join(chan.key for chan in channels) - reasonwarn = (". Also note that your reason will be echoed to the channel" - if reason else '') + reasonwarn = ( + ". Also note that your reason will be echoed to the channel" if reason else "" + ) ask_yes_no( caller, f"Are you sure you want to ban user {target.key} from " @@ -1203,7 +1253,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): ) return - if 'unban' in switches: + if "unban" in switches: # unban a previously banned user from channel if not self.access(caller, "admin"): @@ -1414,7 +1464,6 @@ class CmdPage(COMMAND_DEFAULT_CLASS): receiver=receiver, message=page.message, ) - ) lastpages = "\n ".join(listing) @@ -1465,6 +1514,7 @@ def _list_bots(cmd): else: return "No irc bots found." + class CmdIRC2Chan(COMMAND_DEFAULT_CLASS): """ Link an evennia channel to an external IRC channel diff --git a/evennia/commands/default/general.py b/evennia/commands/default/general.py index 939d4d753f..ca66727d94 100644 --- a/evennia/commands/default/general.py +++ b/evennia/commands/default/general.py @@ -382,10 +382,13 @@ class CmdInventory(COMMAND_DEFAULT_CLASS): string = "You are not carrying anything." else: from evennia.utils.ansi import raw as raw_ansi + table = self.styled_table(border="header") for item in items: - table.add_row(f"|C{item.name}|n", - "{}|n".format(utils.crop(raw_ansi(item.db.desc or ""), width=50) or "")) + table.add_row( + f"|C{item.name}|n", + "{}|n".format(utils.crop(raw_ansi(item.db.desc or ""), width=50) or ""), + ) string = f"|wYou are carrying:\n{table}" self.caller.msg(string) diff --git a/evennia/commands/default/help.py b/evennia/commands/default/help.py index e121122abc..3ac6c4924a 100644 --- a/evennia/commands/default/help.py +++ b/evennia/commands/default/help.py @@ -19,11 +19,7 @@ from evennia.utils import create, evmore from evennia.utils.ansi import ANSIString from evennia.help.filehelp import FILE_HELP_ENTRIES from evennia.utils.eveditor import EvEditor -from evennia.utils.utils import ( - class_from_module, - inherits_from, - format_grid, pad -) +from evennia.utils.utils import class_from_module, inherits_from, format_grid, pad from evennia.help.utils import help_search_with_index, parse_entry_for_subcategories CMD_IGNORE_PREFIXES = settings.CMD_IGNORE_PREFIXES @@ -35,12 +31,14 @@ HELP_CLICKABLE_TOPICS = settings.HELP_CLICKABLE_TOPICS # limit symbol import for API __all__ = ("CmdHelp", "CmdSetHelp") + @dataclass class HelpCategory: """ Mock 'help entry' to search categories with the same code. """ + key: str @property @@ -113,7 +111,10 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): if type(self).help_more: usemore = True - if self.session and self.session.protocol_key in ("websocket", "ajax/comet",): + if self.session and self.session.protocol_key in ( + "websocket", + "ajax/comet", + ): try: options = self.account.db._saved_webclient_options if options and options["helppopup"]: @@ -127,8 +128,15 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): self.msg(text=(text, {"type": "help"})) - def format_help_entry(self, topic="", help_text="", aliases=None, suggested=None, - subtopics=None, click_topics=True): + def format_help_entry( + self, + topic="", + help_text="", + aliases=None, + suggested=None, + subtopics=None, + click_topics=True, + ): """This visually formats the help entry. This method can be overriden to customize the way a help entry is displayed. @@ -152,28 +160,24 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): title = f"|CHelp for |w{topic}|n" if topic else "|rNo help found|n" if aliases: - aliases = ( - " |C(aliases: {}|C)|n".format("|C,|n ".join(f"|w{ali}|n" for ali in aliases)) - ) + aliases = " |C(aliases: {}|C)|n".format("|C,|n ".join(f"|w{ali}|n" for ali in aliases)) else: - aliases = '' + aliases = "" - help_text = "\n" + dedent(help_text.strip('\n')) if help_text else "" + help_text = "\n" + dedent(help_text.strip("\n")) if help_text else "" if subtopics: if click_topics: subtopics = [ - f"|lchelp {topic}/{subtop}|lt|w{topic}/{subtop}|n|le" - for subtop in subtopics - ] + f"|lchelp {topic}/{subtop}|lt|w{topic}/{subtop}|n|le" for subtop in subtopics + ] else: subtopics = [f"|w{topic}/{subtop}|n" for subtop in subtopics] - subtopics = ( - "\n|CSubtopics:|n\n {}".format( - "\n ".join(format_grid(subtopics, width=self.client_width()))) + subtopics = "\n|CSubtopics:|n\n {}".format( + "\n ".join(format_grid(subtopics, width=self.client_width())) ) else: - subtopics = '' + subtopics = "" if suggested: suggested = sorted(suggested) @@ -181,12 +185,11 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): suggested = [f"|lchelp {sug}|lt|w{sug}|n|le" for sug in suggested] else: suggested = [f"|w{sug}|n" for sug in suggested] - suggested = ( - "\n|COther topic suggestions:|n\n{}".format( - "\n ".join(format_grid(suggested, width=self.client_width()))) + suggested = "\n|COther topic suggestions:|n\n{}".format( + "\n ".join(format_grid(suggested, width=self.client_width())) ) else: - suggested = '' + suggested = "" end = start @@ -194,8 +197,9 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): return "\n".join(part.rstrip() for part in partorder if part) - def format_help_index(self, cmd_help_dict=None, db_help_dict=None, title_lone_category=False, - click_topics=True): + def format_help_index( + self, cmd_help_dict=None, db_help_dict=None, title_lone_category=False, click_topics=True + ): """Output a category-ordered g for displaying the main help, grouped by category. @@ -219,6 +223,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): commands and topics. """ + def _group_by_category(help_dict): grid = [] verbatim_elements = [] @@ -231,9 +236,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): # make the help topics clickable if click_topics: - entries = [ - f'|lchelp {entry}|lt{entry}|le' for entry in entries - ] + entries = [f"|lchelp {entry}|lt{entry}|le" for entry in entries] # add the entries to the grid grid.extend(entries) @@ -243,7 +246,8 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): category_str = f"-- {category.title()} " grid.append( ANSIString( - self.index_category_clr + category_str + self.index_category_clr + + category_str + "-" * (width - len(category_str)) + self.index_topic_clr ) @@ -255,9 +259,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): # make the help topics clickable if click_topics: - entries = [ - f'|lchelp {entry}|lt{entry}|le' for entry in entries - ] + entries = [f"|lchelp {entry}|lt{entry}|le" for entry in entries] # add the entries to the grid grid.extend(entries) @@ -272,18 +274,22 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): if any(cmd_help_dict.values()): # get the command-help entries by-category - sep1 = (self.index_type_separator_clr - + pad("Commands", width=width, fillchar='-') - + self.index_topic_clr) + sep1 = ( + self.index_type_separator_clr + + pad("Commands", width=width, fillchar="-") + + self.index_topic_clr + ) grid, verbatim_elements = _group_by_category(cmd_help_dict) gridrows = format_grid(grid, width, sep=" ", verbatim_elements=verbatim_elements) cmd_grid = ANSIString("\n").join(gridrows) if gridrows else "" if any(db_help_dict.values()): # get db-based help entries by-category - sep2 = (self.index_type_separator_clr - + pad("Game & World", width=width, fillchar='-') - + self.index_topic_clr) + sep2 = ( + self.index_type_separator_clr + + pad("Game & World", width=width, fillchar="-") + + self.index_topic_clr + ) grid, verbatim_elements = _group_by_category(db_help_dict) gridrows = format_grid(grid, width, sep=" ", verbatim_elements=verbatim_elements) db_grid = ANSIString("\n").join(gridrows) if gridrows else "" @@ -316,9 +322,9 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): """ if inherits_from(cmd_or_topic, "evennia.commands.command.Command"): - return cmd_or_topic.auto_help and cmd_or_topic.access(caller, 'read', default=True) + return cmd_or_topic.auto_help and cmd_or_topic.access(caller, "read", default=True) else: - return cmd_or_topic.access(caller, 'read', default=True) + return cmd_or_topic.access(caller, "read", default=True) def can_list_topic(self, cmd_or_topic, caller): """ @@ -355,12 +361,12 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): ) if has_view: - return cmd_or_topic.access(caller, 'view', default=True) + return cmd_or_topic.access(caller, "view", default=True) else: # no explicit 'view' lock - use the 'read' lock - return cmd_or_topic.access(caller, 'read', default=True) + return cmd_or_topic.access(caller, "read", default=True) - def collect_topics(self, caller, mode='list'): + def collect_topics(self, caller, mode="list"): """ Collect help topics from all sources (cmd/db/file). @@ -383,43 +389,45 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): cmdset.make_unique(caller) # retrieve all available commands and database / file-help topics. # also check the 'cmd:' lock here - cmd_help_topics = [cmd for cmd in cmdset if cmd and cmd.access(caller, 'cmd')] + cmd_help_topics = [cmd for cmd in cmdset if cmd and cmd.access(caller, "cmd")] # get all file-based help entries, checking perms - file_help_topics = { - topic.key.lower().strip(): topic - for topic in FILE_HELP_ENTRIES.all() - } + file_help_topics = {topic.key.lower().strip(): topic for topic in FILE_HELP_ENTRIES.all()} # get db-based help entries, checking perms - db_help_topics = { - topic.key.lower().strip(): topic - for topic in HelpEntry.objects.all() - } - if mode == 'list': + db_help_topics = {topic.key.lower().strip(): topic for topic in HelpEntry.objects.all()} + if mode == "list": # check the view lock for all help entries/commands and determine key cmd_help_topics = { - cmd.auto_help_display_key - if hasattr(cmd, "auto_help_display_key") else cmd.key: cmd - for cmd in cmd_help_topics if self.can_list_topic(cmd, caller)} + cmd.auto_help_display_key if hasattr(cmd, "auto_help_display_key") else cmd.key: cmd + for cmd in cmd_help_topics + if self.can_list_topic(cmd, caller) + } db_help_topics = { - key: entry for key, entry in db_help_topics.items() + key: entry + for key, entry in db_help_topics.items() if self.can_list_topic(entry, caller) } file_help_topics = { - key: entry for key, entry in file_help_topics.items() - if self.can_list_topic(entry, caller)} + key: entry + for key, entry in file_help_topics.items() + if self.can_list_topic(entry, caller) + } else: # query - check the read lock on entries cmd_help_topics = { - cmd.auto_help_display_key - if hasattr(cmd, "auto_help_display_key") else cmd.key: cmd - for cmd in cmd_help_topics if self.can_read_topic(cmd, caller)} + cmd.auto_help_display_key if hasattr(cmd, "auto_help_display_key") else cmd.key: cmd + for cmd in cmd_help_topics + if self.can_read_topic(cmd, caller) + } db_help_topics = { - key: entry for key, entry in db_help_topics.items() + key: entry + for key, entry in db_help_topics.items() if self.can_read_topic(entry, caller) } file_help_topics = { - key: entry for key, entry in file_help_topics.items() - if self.can_read_topic(entry, caller)} + key: entry + for key, entry in file_help_topics.items() + if self.can_read_topic(entry, caller) + } return cmd_help_topics, db_help_topics, file_help_topics @@ -452,9 +460,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): # return of this will either be a HelpCategory, a Command or a # HelpEntry/FileHelpEntry. matches, suggestions = help_search_with_index( - match_query, entries, - suggestion_maxnum=self.suggestion_maxnum, - fields=search_fields + match_query, entries, suggestion_maxnum=self.suggestion_maxnum, fields=search_fields ) if matches: match = matches[0] @@ -478,8 +484,9 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): # parse the query if self.args: - self.subtopics = [part.strip().lower() - for part in self.args.split(self.subtopic_separator_char)] + self.subtopics = [ + part.strip().lower() for part in self.args.split(self.subtopic_separator_char) + ] self.topic = self.subtopics.pop(0) else: self.topic = "" @@ -505,7 +512,6 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): return key[1:] return key - def func(self): """ Run the dynamic help entry creator. @@ -518,8 +524,9 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): # list all available help entries, grouped by category. We want to # build dictionaries {category: [topic, topic, ...], ...} - cmd_help_topics, db_help_topics, file_help_topics = \ - self.collect_topics(caller, mode='list') + cmd_help_topics, db_help_topics, file_help_topics = self.collect_topics( + caller, mode="list" + ) # db-topics override file-based ones file_db_help_topics = {**file_help_topics, **db_help_topics} @@ -538,21 +545,21 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): file_db_help_by_category[entry.help_category].append(key) # generate the index and display - output = self.format_help_index(cmd_help_by_category, - file_db_help_by_category, - click_topics=clickable_topics) + output = self.format_help_index( + cmd_help_by_category, file_db_help_by_category, click_topics=clickable_topics + ) self.msg_help(output) return # search for a specific entry. We need to check for 'read' access here before # building the set of possibilities. - cmd_help_topics, db_help_topics, file_help_topics = \ - self.collect_topics(caller, mode='query') + cmd_help_topics, db_help_topics, file_help_topics = self.collect_topics( + caller, mode="query" + ) # get a collection of all keys + aliases to be able to strip prefixes like @ - key_and_aliases = set( - chain(*(cmd._keyaliases for cmd in cmd_help_topics.values()))) + key_and_aliases = set(chain(*(cmd._keyaliases for cmd in cmd_help_topics.values()))) # db-help topics takes priority over file-help file_db_help_topics = {**file_help_topics, **db_help_topics} @@ -561,8 +568,9 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): all_topics = {**file_db_help_topics, **cmd_help_topics} # get all categories - all_categories = list(set( - HelpCategory(topic.help_category) for topic in all_topics.values())) + all_categories = list( + set(HelpCategory(topic.help_category) for topic in all_topics.values()) + ) # all available help options - will be searched in order. We also check # the # read-permission here. @@ -586,23 +594,26 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): for match_query in [query, f"{query}*", f"*{query}"]: _, suggestions = help_search_with_index( - match_query, entries, + match_query, + entries, suggestion_maxnum=self.suggestion_maxnum, - fields=search_fields + fields=search_fields, ) if suggestions: help_text += ( "\n... But matches where found within the help " - "texts of the suggestions below.") - suggestions = [self.strip_cmd_prefix(sugg, key_and_aliases) - for sugg in suggestions] + "texts of the suggestions below." + ) + suggestions = [ + self.strip_cmd_prefix(sugg, key_and_aliases) for sugg in suggestions + ] break output = self.format_help_entry( topic=None, # this will give a no-match style title help_text=help_text, suggested=suggestions, - click_topics=clickable_topics + click_topics=clickable_topics, ) self.msg_help(output) @@ -612,14 +623,20 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): # no subtopics for categories - these are just lists of topics category = match.key category_lower = category.lower() - cmds_in_category = [key for key, cmd in cmd_help_topics.items() - if category_lower == cmd.help_category] - topics_in_category = [key for key, topic in file_db_help_topics.items() - if category_lower == topic.help_category] - output = self.format_help_index({category: cmds_in_category}, - {category: topics_in_category}, - title_lone_category=True, - click_topics=clickable_topics) + cmds_in_category = [ + key for key, cmd in cmd_help_topics.items() if category_lower == cmd.help_category + ] + topics_in_category = [ + key + for key, topic in file_db_help_topics.items() + if category_lower == topic.help_category + ] + output = self.format_help_index( + {category: cmds_in_category}, + {category: topics_in_category}, + title_lone_category=True, + click_topics=clickable_topics, + ) self.msg_help(output) return @@ -674,7 +691,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): topic=topic, help_text=f"No help entry found for '{checked_topic}'", subtopics=subtopic_index, - click_topics=clickable_topics + click_topics=clickable_topics, ) self.msg_help(output) return @@ -702,7 +719,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): aliases=aliases, subtopics=subtopic_index, suggested=suggested, - click_topics=clickable_topics + click_topics=clickable_topics, ) self.msg_help(output) @@ -829,15 +846,17 @@ class CmdSetHelp(CmdHelp): # check if we have an old entry with the same name - cmd_help_topics, db_help_topics, file_help_topics = \ - self.collect_topics(self.caller, mode='query') + cmd_help_topics, db_help_topics, file_help_topics = self.collect_topics( + self.caller, mode="query" + ) # db-help topics takes priority over file-help file_db_help_topics = {**file_help_topics, **db_help_topics} # commands take priority over the other types all_topics = {**file_db_help_topics, **cmd_help_topics} # get all categories - all_categories = list(set( - HelpCategory(topic.help_category) for topic in all_topics.values())) + all_categories = list( + set(HelpCategory(topic.help_category) for topic in all_topics.values()) + ) # all available help options - will be searched in order. We also check # the # read-permission here. entries = list(all_topics.values()) + all_categories @@ -853,29 +872,35 @@ class CmdSetHelp(CmdHelp): if match: warning = None if isinstance(match, HelpCategory): - warning = (f"'{querystr}' matches (or partially matches) the name of " - "help-category '{match.key}'. If you continue, your help entry will " - "take precedence and the category (or part of its name) *may* not " - "be usable for grouping help entries anymore.") + warning = ( + f"'{querystr}' matches (or partially matches) the name of " + "help-category '{match.key}'. If you continue, your help entry will " + "take precedence and the category (or part of its name) *may* not " + "be usable for grouping help entries anymore." + ) elif inherits_from(match, "evennia.commands.command.Command"): - warning = (f"'{querystr}' matches (or partially matches) the key/alias of " - "Command '{match.key}'. Command-help take precedence over other " - "help entries so your help *may* be impossible to reach for those " - "with access to that command.") + warning = ( + f"'{querystr}' matches (or partially matches) the key/alias of " + "Command '{match.key}'. Command-help take precedence over other " + "help entries so your help *may* be impossible to reach for those " + "with access to that command." + ) elif inherits_from(match, "evennia.help.filehelp.FileHelpEntry"): - warning = (f"'{querystr}' matches (or partially matches) the name/alias of the " - f"file-based help topic '{match.key}'. File-help entries cannot be " - "modified from in-game (they are files on-disk). If you continue, " - "your help entry may shadow the file-based one's name partly or " - "completely.") + warning = ( + f"'{querystr}' matches (or partially matches) the name/alias of the " + f"file-based help topic '{match.key}'. File-help entries cannot be " + "modified from in-game (they are files on-disk). If you continue, " + "your help entry may shadow the file-based one's name partly or " + "completely." + ) if warning: # show a warning for a clashing help-entry type. Even if user accepts this # we don't break here since we may need to show warnings for other inputs. # We don't count this as an old-entry hit because we can't edit these # types of entries. self.msg(f"|rWarning:\n|r{warning}|n") - repl = yield("|wDo you still want to continue? Y/[N]?|n") - if repl.lower() not in ('y', 'yes'): + repl = yield ("|wDo you still want to continue? Y/[N]?|n") + if repl.lower() not in ("y", "yes"): self.msg("Aborted.") return else: @@ -897,7 +922,11 @@ class CmdSetHelp(CmdHelp): helpentry = old_entry else: helpentry = create.create_help_entry( - topicstr, self.rhs, category=category, locks=lockstring, aliases=aliases, + topicstr, + self.rhs, + category=category, + locks=lockstring, + aliases=aliases, ) self.caller.db._editing_help = helpentry @@ -976,6 +1005,4 @@ class CmdSetHelp(CmdHelp): ) return else: - self.msg( - f"Error when creating topic '{topicstr}'{aliastxt}! Contact an admin." - ) + self.msg(f"Error when creating topic '{topicstr}'{aliastxt}! Contact an admin.") diff --git a/evennia/commands/default/system.py b/evennia/commands/default/system.py index a97f6bed6a..c993c71103 100644 --- a/evennia/commands/default/system.py +++ b/evennia/commands/default/system.py @@ -593,13 +593,15 @@ class CmdService(COMMAND_DEFAULT_CLASS): if delmode: caller.msg("You cannot remove a core Evennia service (named 'Evennia*').") return - string = ("|RYou seem to be shutting down a core Evennia " - "service (named 'Evennia*').\nNote that stopping " - "some TCP port services will *not* disconnect users " - "*already* connected on those ports, but *may* " - "instead cause spurious errors for them.\nTo safely " - "and permanently remove ports, change settings file " - "and restart the server.|n\n") + string = ( + "|RYou seem to be shutting down a core Evennia " + "service (named 'Evennia*').\nNote that stopping " + "some TCP port services will *not* disconnect users " + "*already* connected on those ports, but *may* " + "instead cause spurious errors for them.\nTo safely " + "and permanently remove ports, change settings file " + "and restart the server.|n\n" + ) caller.msg(string) if delmode: @@ -611,9 +613,11 @@ class CmdService(COMMAND_DEFAULT_CLASS): try: service.stopService() except Exception as err: - caller.msg(f"|rErrors were reported when stopping this service{err}.\n" - "If there are remaining problems, try reloading " - "or rebooting the server.") + caller.msg( + f"|rErrors were reported when stopping this service{err}.\n" + "If there are remaining problems, try reloading " + "or rebooting the server." + ) caller.msg("|g... Stopped service '%s'.|n" % self.args) return @@ -626,9 +630,11 @@ class CmdService(COMMAND_DEFAULT_CLASS): try: service.startService() except Exception as err: - caller.msg(f"|rErrors were reported when starting this service{err}.\n" - "If there are remaining problems, try reloading the server, changing the " - "settings if it's a non-standard service.|n") + caller.msg( + f"|rErrors were reported when starting this service{err}.\n" + "If there are remaining problems, try reloading the server, changing the " + "settings if it's a non-standard service.|n" + ) caller.msg("|gService started.|n") @@ -973,8 +979,8 @@ class CmdTasks(COMMAND_DEFAULT_CLASS): @staticmethod def coll_date_func(task): """Replace regex characters in date string and collect deferred function name.""" - t_comp_date = str(task[0]).replace('-', '/') - t_func_name = str(task[1]).split(' ') + t_comp_date = str(task[0]).replace("-", "/") + t_func_name = str(task[1]).split(" ") t_func_mem_ref = t_func_name[3] if len(t_func_name) >= 4 else None return t_comp_date, t_func_mem_ref @@ -994,19 +1000,19 @@ class CmdTasks(COMMAND_DEFAULT_CLASS): # verify manipulating the correct task task_args = _TASK_HANDLER.tasks.get(task_id, False) if not task_args: # check if the task is still active - self.msg('Task completed while waiting for input.') + self.msg("Task completed while waiting for input.") return else: # make certain a task with matching IDs has not been created t_comp_date, t_func_mem_ref = self.coll_date_func(task_args) if self.t_comp_date != t_comp_date or self.t_func_mem_ref != t_func_mem_ref: - self.msg('Task completed while waiting for input.') + self.msg("Task completed while waiting for input.") return # Do the action requested by command caller action_return = self.task_action() - self.msg(f'{self.action_request} request completed.') - self.msg(f'The task function {self.action_request} returned: {action_return}') + self.msg(f"{self.action_request} request completed.") + self.msg(f"The task function {self.action_request} returned: {action_return}") def func(self): # get a reference of the global task handler @@ -1015,9 +1021,9 @@ class CmdTasks(COMMAND_DEFAULT_CLASS): from evennia.scripts.taskhandler import TASK_HANDLER as _TASK_HANDLER # handle no tasks active. if not _TASK_HANDLER.tasks: - self.msg('There are no active tasks.') + self.msg("There are no active tasks.") if self.switches or self.args: - self.msg('Likely the task has completed and been removed.') + self.msg("Likely the task has completed and been removed.") return # handle caller's request to manipulate a task(s) @@ -1033,8 +1039,8 @@ class CmdTasks(COMMAND_DEFAULT_CLASS): # if the argument is a task id, proccess the action on a single task if arg_is_id: - err_arg_msg = 'Switch and task ID are required when manipulating a task.' - task_comp_msg = 'Task completed while processing request.' + err_arg_msg = "Switch and task ID are required when manipulating a task." + task_comp_msg = "Task completed while processing request." # handle missing arguments or switches if not self.switches and self.lhs: @@ -1047,14 +1053,16 @@ class CmdTasks(COMMAND_DEFAULT_CLASS): # handle task no longer existing if not task.exists(): - self.msg(f'Task {task_id} does not exist.') + self.msg(f"Task {task_id} does not exist.") return # get a reference of the function caller requested switch_action = getattr(task, action_request, False) if not switch_action: - self.msg(f'{self.switches[0]}, is not an acceptable task action or ' \ - f'{task_comp_msg.lower()}') + self.msg( + f"{self.switches[0]}, is not an acceptable task action or " + f"{task_comp_msg.lower()}" + ) # verify manipulating the correct task if task_id in _TASK_HANDLER.tasks: @@ -1064,25 +1072,29 @@ class CmdTasks(COMMAND_DEFAULT_CLASS): return else: t_comp_date, t_func_mem_ref = self.coll_date_func(task_args) - t_func_name = str(task_args[1]).split(' ') + t_func_name = str(task_args[1]).split(" ") t_func_name = t_func_name[1] if len(t_func_name) >= 2 else None if task.exists(): # make certain the task has not been called yet. - prompt = (f'{action_request.capitalize()} task {task_id} with completion date ' - f'{t_comp_date} ({t_func_name}) {{options}}?') - no_msg = f'No {action_request} processed.' + prompt = ( + f"{action_request.capitalize()} task {task_id} with completion date " + f"{t_comp_date} ({t_func_name}) {{options}}?" + ) + no_msg = f"No {action_request} processed." # record variables for use in do_task_action method self.task_id = task_id self.t_comp_date = t_comp_date self.t_func_mem_ref = t_func_mem_ref self.task_action = switch_action self.action_request = action_request - ask_yes_no(self.caller, - prompt=prompt, - yes_action=self.do_task_action, - no_action=no_msg, - default="Y", - allow_abort=True) + ask_yes_no( + self.caller, + prompt=prompt, + yes_action=self.do_task_action, + no_action=no_msg, + default="Y", + allow_abort=True, + ) return True else: self.msg(task_comp_msg) @@ -1102,7 +1114,7 @@ class CmdTasks(COMMAND_DEFAULT_CLASS): # call requested action on all tasks with the function name for task_id, task_args in current_tasks.items(): - t_func_name = str(task_args[1]).split(' ') + t_func_name = str(task_args[1]).split(" ") t_func_name = t_func_name[1] if len(t_func_name) >= 2 else None # skip this task if it is not for the function desired if arg_func_name != t_func_name: @@ -1112,33 +1124,39 @@ class CmdTasks(COMMAND_DEFAULT_CLASS): switch_action = getattr(task, action_request, False) if switch_action: action_return = switch_action() - self.msg(f'Task action {action_request} completed on task ID {task_id}.') - self.msg(f'The task function {action_request} returned: {action_return}') + self.msg(f"Task action {action_request} completed on task ID {task_id}.") + self.msg(f"The task function {action_request} returned: {action_return}") # provide a message if not tasks of the function name was found if not name_match_found: - self.msg(f'No tasks deferring function name {arg_func_name} found.') + self.msg(f"No tasks deferring function name {arg_func_name} found.") return return True # check if an maleformed request was created elif self.switches or self.lhs: - self.msg('Task command misformed.') - self.msg('Proper format tasks[/switch] [function name or task id]') + self.msg("Task command misformed.") + self.msg("Proper format tasks[/switch] [function name or task id]") return # No task manupilation requested, build a table of tasks and display it # get the width of screen in characters width = self.client_width() # create table header and list to hold tasks data and actions - tasks_header = ('Task ID', 'Completion Date', 'Function', 'Arguments', 'KWARGS', - 'persistent') + tasks_header = ( + "Task ID", + "Completion Date", + "Function", + "Arguments", + "KWARGS", + "persistent", + ) # empty list of lists, the size of the header tasks_list = [list() for i in range(len(tasks_header))] for task_id, task in _TASK_HANDLER.tasks.items(): # collect data from the task t_comp_date, t_func_mem_ref = self.coll_date_func(task) - t_func_name = str(task[1]).split(' ') + t_func_name = str(task[1]).split(" ") t_func_name = t_func_name[1] if len(t_func_name) >= 2 else None t_args = str(task[2]) t_kwargs = str(task[3]) @@ -1148,8 +1166,9 @@ class CmdTasks(COMMAND_DEFAULT_CLASS): for i in range(len(tasks_header)): tasks_list[i].append(task_data[i]) # create and display the table - tasks_table = EvTable(*tasks_header, table=tasks_list, maxwidth=width, border='cells', - align='center') - actions = (f'/{switch}' for switch in self.switch_options) + tasks_table = EvTable( + *tasks_header, table=tasks_list, maxwidth=width, border="cells", align="center" + ) + actions = (f"/{switch}" for switch in self.switch_options) helptxt = f"\nActions: {iter_to_str(actions)}" self.msg(str(tasks_table) + helptxt) diff --git a/evennia/commands/default/tests.py b/evennia/commands/default/tests.py index 2ab0e19ea5..b21b504e60 100644 --- a/evennia/commands/default/tests.py +++ b/evennia/commands/default/tests.py @@ -21,7 +21,11 @@ from unittest.mock import patch, Mock, MagicMock from evennia import DefaultRoom, DefaultExit, ObjectDB from evennia.commands.default.cmdset_character import CharacterCmdSet -from evennia.utils.test_resources import BaseEvenniaTest, BaseEvenniaCommandTest, EvenniaCommandTest # noqa +from evennia.utils.test_resources import ( + BaseEvenniaTest, + BaseEvenniaCommandTest, + EvenniaCommandTest, +) # noqa from evennia.commands.default import ( help as help_module, general, @@ -170,83 +174,90 @@ class TestHelp(BaseEvenniaCommandTest): help_module.CmdSetHelp(), "testhelp, General = This is a test", "Topic 'testhelp' was successfully created.", - cmdset=CharacterCmdSet() + cmdset=CharacterCmdSet(), ) self.call(help_module.CmdHelp(), "testhelp", "Help for testhelp", cmdset=CharacterCmdSet()) - @parameterized.expand([ - ("test", # main help entry - "Help for test\n\n" - "Main help text\n\n" - "Subtopics:\n" - " test/creating extra stuff" - " test/something else" - " test/more" - ), - ("test/creating extra stuff", # subtopic, full match - "Help for test/creating extra stuff\n\n" - "Help on creating extra stuff.\n\n" - "Subtopics:\n" - " test/creating extra stuff/subsubtopic\n" - ), - ("test/creating", # startswith-match - "Help for test/creating extra stuff\n\n" - "Help on creating extra stuff.\n\n" - "Subtopics:\n" - " test/creating extra stuff/subsubtopic\n" - ), - ("test/extra", # partial match - "Help for test/creating extra stuff\n\n" - "Help on creating extra stuff.\n\n" - "Subtopics:\n" - " test/creating extra stuff/subsubtopic\n" - ), - ("test/extra/subsubtopic", # partial subsub-match - "Help for test/creating extra stuff/subsubtopic\n\n" - "A subsubtopic text" - ), - ("test/creating extra/subsub", # partial subsub-match - "Help for test/creating extra stuff/subsubtopic\n\n" - "A subsubtopic text" - ), - ("test/Something else", # case - "Help for test/something else\n\n" - "Something else" - ), - ("test/More", # case - "Help for test/more\n\n" - "Another text\n\n" - "Subtopics:\n" - " test/more/second-more" - ), - ("test/More/Second-more", - "Help for test/more/second-more\n\n" - "The Second More text.\n\n" - "Subtopics:\n" - " test/more/second-more/more again" - " test/more/second-more/third more" - ), - ("test/More/-more", # partial match - "Help for test/more/second-more\n\n" - "The Second More text.\n\n" - "Subtopics:\n" - " test/more/second-more/more again" - " test/more/second-more/third more" - ), - ("test/more/second/more again", - "Help for test/more/second-more/more again\n\n" - "Even more text.\n" - ), - ("test/more/second/third", - "Help for test/more/second-more/third more\n\n" - "Third more text\n" - ), - ]) + @parameterized.expand( + [ + ( + "test", # main help entry + "Help for test\n\n" + "Main help text\n\n" + "Subtopics:\n" + " test/creating extra stuff" + " test/something else" + " test/more", + ), + ( + "test/creating extra stuff", # subtopic, full match + "Help for test/creating extra stuff\n\n" + "Help on creating extra stuff.\n\n" + "Subtopics:\n" + " test/creating extra stuff/subsubtopic\n", + ), + ( + "test/creating", # startswith-match + "Help for test/creating extra stuff\n\n" + "Help on creating extra stuff.\n\n" + "Subtopics:\n" + " test/creating extra stuff/subsubtopic\n", + ), + ( + "test/extra", # partial match + "Help for test/creating extra stuff\n\n" + "Help on creating extra stuff.\n\n" + "Subtopics:\n" + " test/creating extra stuff/subsubtopic\n", + ), + ( + "test/extra/subsubtopic", # partial subsub-match + "Help for test/creating extra stuff/subsubtopic\n\n" "A subsubtopic text", + ), + ( + "test/creating extra/subsub", # partial subsub-match + "Help for test/creating extra stuff/subsubtopic\n\n" "A subsubtopic text", + ), + ("test/Something else", "Help for test/something else\n\n" "Something else"), # case + ( + "test/More", # case + "Help for test/more\n\n" + "Another text\n\n" + "Subtopics:\n" + " test/more/second-more", + ), + ( + "test/More/Second-more", + "Help for test/more/second-more\n\n" + "The Second More text.\n\n" + "Subtopics:\n" + " test/more/second-more/more again" + " test/more/second-more/third more", + ), + ( + "test/More/-more", # partial match + "Help for test/more/second-more\n\n" + "The Second More text.\n\n" + "Subtopics:\n" + " test/more/second-more/more again" + " test/more/second-more/third more", + ), + ( + "test/more/second/more again", + "Help for test/more/second-more/more again\n\n" "Even more text.\n", + ), + ( + "test/more/second/third", + "Help for test/more/second-more/third more\n\n" "Third more text\n", + ), + ] + ) def test_subtopic_fetch(self, helparg, expected): """ Check retrieval of subtopics. """ + class TestCmd(Command): """ Main help text @@ -282,6 +293,7 @@ class TestHelp(BaseEvenniaCommandTest): Third more text """ + key = "test" class TestCmdSet(CmdSet): @@ -289,10 +301,7 @@ class TestHelp(BaseEvenniaCommandTest): self.add(TestCmd()) self.add(help_module.CmdHelp()) - self.call(help_module.CmdHelp(), - helparg, - expected, - cmdset=TestCmdSet()) + self.call(help_module.CmdHelp(), helparg, expected, cmdset=TestCmdSet()) class TestSystem(BaseEvenniaCommandTest): @@ -314,13 +323,15 @@ class TestSystem(BaseEvenniaCommandTest): def test_server_load(self): self.call(system.CmdServerLoad(), "", "Server CPU and Memory load:") + _TASK_HANDLER = None + def func_test_cmd_tasks(): - return 'success' + return "success" + class TestCmdTasks(BaseEvenniaCommandTest): - def setUp(self): super().setUp() # get a reference of TASK_HANDLER @@ -334,70 +345,82 @@ class TestCmdTasks(BaseEvenniaCommandTest): self.task = self.task_handler.add(self.timedelay, func_test_cmd_tasks) task_args = self.task_handler.tasks.get(self.task.get_id(), False) - def tearDown(self): super().tearDown() self.task_handler.clear() def test_no_tasks(self): self.task_handler.clear() - self.call(system.CmdTasks(), '', 'There are no active tasks.') + self.call(system.CmdTasks(), "", "There are no active tasks.") def test_active_task(self): - cmd_result = self.call(system.CmdTasks(), '') - for ptrn in ('Task ID', 'Completion', 'Date', 'Function', 'KWARGS', 'persisten', - '1', r'\d+/\d+/\d+', r'\d+\:', r'\d+\:\d+', r'\:\d+', 'func_test', '{}', - 'False'): + cmd_result = self.call(system.CmdTasks(), "") + for ptrn in ( + "Task ID", + "Completion", + "Date", + "Function", + "KWARGS", + "persisten", + "1", + r"\d+/\d+/\d+", + r"\d+\:", + r"\d+\:\d+", + r"\:\d+", + "func_test", + "{}", + "False", + ): self.assertRegex(cmd_result, ptrn) def test_persistent_task(self): self.task_handler.clear() self.task_handler.add(self.timedelay, func_test_cmd_tasks, persistent=True) - cmd_result = self.call(system.CmdTasks(), '') - self.assertRegex(cmd_result, 'True') + cmd_result = self.call(system.CmdTasks(), "") + self.assertRegex(cmd_result, "True") def test_pause_unpause(self): # test pause - args = f'/pause {self.task.get_id()}' - wanted_msg = 'Pause task 1 with completion date' + args = f"/pause {self.task.get_id()}" + wanted_msg = "Pause task 1 with completion date" cmd_result = self.call(system.CmdTasks(), args, wanted_msg) - self.assertRegex(cmd_result, ' \(func_test_cmd_tasks\) ') - self.char1.execute_cmd('y') + self.assertRegex(cmd_result, " \(func_test_cmd_tasks\) ") + self.char1.execute_cmd("y") self.assertTrue(self.task.paused) self.task_handler.clock.advance(self.timedelay + 1) # test unpause - args = f'/unpause {self.task.get_id()}' + args = f"/unpause {self.task.get_id()}" self.assertTrue(self.task.exists()) - wanted_msg = 'Unpause task 1 with completion date' + wanted_msg = "Unpause task 1 with completion date" cmd_result = self.call(system.CmdTasks(), args, wanted_msg) - self.assertRegex(cmd_result, ' \(func_test_cmd_tasks\) ') - self.char1.execute_cmd('y') + self.assertRegex(cmd_result, " \(func_test_cmd_tasks\) ") + self.char1.execute_cmd("y") # verify task continues after unpause self.task_handler.clock.advance(1) self.assertFalse(self.task.exists()) def test_do_task(self): - args = f'/do_task {self.task.get_id()}' - wanted_msg = 'Do_task task 1 with completion date' + args = f"/do_task {self.task.get_id()}" + wanted_msg = "Do_task task 1 with completion date" cmd_result = self.call(system.CmdTasks(), args, wanted_msg) - self.assertRegex(cmd_result, ' \(func_test_cmd_tasks\) ') - self.char1.execute_cmd('y') + self.assertRegex(cmd_result, " \(func_test_cmd_tasks\) ") + self.char1.execute_cmd("y") self.assertFalse(self.task.exists()) def test_remove(self): - args = f'/remove {self.task.get_id()}' - wanted_msg = 'Remove task 1 with completion date' + args = f"/remove {self.task.get_id()}" + wanted_msg = "Remove task 1 with completion date" cmd_result = self.call(system.CmdTasks(), args, wanted_msg) - self.assertRegex(cmd_result, ' \(func_test_cmd_tasks\) ') - self.char1.execute_cmd('y') + self.assertRegex(cmd_result, " \(func_test_cmd_tasks\) ") + self.char1.execute_cmd("y") self.assertFalse(self.task.exists()) def test_call(self): - args = f'/call {self.task.get_id()}' - wanted_msg = 'Call task 1 with completion date' + args = f"/call {self.task.get_id()}" + wanted_msg = "Call task 1 with completion date" cmd_result = self.call(system.CmdTasks(), args, wanted_msg) - self.assertRegex(cmd_result, ' \(func_test_cmd_tasks\) ') - self.char1.execute_cmd('y') + self.assertRegex(cmd_result, " \(func_test_cmd_tasks\) ") + self.char1.execute_cmd("y") # make certain the task is still active self.assertTrue(self.task.active()) # go past delay time, the task should call do_task and remove itself after calling. @@ -405,55 +428,57 @@ class TestCmdTasks(BaseEvenniaCommandTest): self.assertFalse(self.task.exists()) def test_cancel(self): - args = f'/cancel {self.task.get_id()}' - wanted_msg = 'Cancel task 1 with completion date' + args = f"/cancel {self.task.get_id()}" + wanted_msg = "Cancel task 1 with completion date" cmd_result = self.call(system.CmdTasks(), args, wanted_msg) - self.assertRegex(cmd_result, ' \(func_test_cmd_tasks\) ') - self.char1.execute_cmd('y') + self.assertRegex(cmd_result, " \(func_test_cmd_tasks\) ") + self.char1.execute_cmd("y") self.assertTrue(self.task.exists()) self.assertFalse(self.task.active()) def test_func_name_manipulation(self): self.task_handler.add(self.timedelay, func_test_cmd_tasks) # add an extra task - args = f'/remove func_test_cmd_tasks' - wanted_msg = 'Task action remove completed on task ID 1.|The task function remove returned: True|' \ - 'Task action remove completed on task ID 2.|The task function remove returned: True' + args = f"/remove func_test_cmd_tasks" + wanted_msg = ( + "Task action remove completed on task ID 1.|The task function remove returned: True|" + "Task action remove completed on task ID 2.|The task function remove returned: True" + ) self.call(system.CmdTasks(), args, wanted_msg) self.assertFalse(self.task_handler.tasks) # no tasks should exist. def test_wrong_func_name(self): - args = f'/remove intentional_fail' - wanted_msg = 'No tasks deferring function name intentional_fail found.' + args = f"/remove intentional_fail" + wanted_msg = "No tasks deferring function name intentional_fail found." self.call(system.CmdTasks(), args, wanted_msg) self.assertTrue(self.task.active()) def test_no_input(self): - args = f'/cancel {self.task.get_id()}' + args = f"/cancel {self.task.get_id()}" self.call(system.CmdTasks(), args) # task should complete since no input was received self.task_handler.clock.advance(self.timedelay + 1) self.assertFalse(self.task.exists()) def test_responce_of_yes(self): - self.call(system.CmdTasks(), f'/cancel {self.task.get_id()}') + self.call(system.CmdTasks(), f"/cancel {self.task.get_id()}") self.char1.msg = Mock() - self.char1.execute_cmd('y') - text = '' + self.char1.execute_cmd("y") + text = "" for _, _, kwargs in self.char1.msg.mock_calls: - text += kwargs.get('text', '') - self.assertEqual(text, 'cancel request completed.The task function cancel returned: True') + text += kwargs.get("text", "") + self.assertEqual(text, "cancel request completed.The task function cancel returned: True") self.assertTrue(self.task.exists()) def test_task_complete_waiting_input(self): """Test for task completing while waiting for input.""" - self.call(system.CmdTasks(), f'/cancel {self.task.get_id()}') + self.call(system.CmdTasks(), f"/cancel {self.task.get_id()}") self.task_handler.clock.advance(self.timedelay + 1) self.char1.msg = Mock() - self.char1.execute_cmd('y') - text = '' + self.char1.execute_cmd("y") + text = "" for _, _, kwargs in self.char1.msg.mock_calls: - text += kwargs.get('text', '') - self.assertEqual(text, 'Task completed while waiting for input.') + text += kwargs.get("text", "") + self.assertEqual(text, "Task completed while waiting for input.") self.assertFalse(self.task.exists()) def test_new_task_waiting_input(self): @@ -461,22 +486,23 @@ class TestCmdTasks(BaseEvenniaCommandTest): Test task completing than a new task with the same ID being made while waitinf for input. """ self.assertTrue(self.task.get_id(), 1) - self.call(system.CmdTasks(), f'/cancel {self.task.get_id()}') + self.call(system.CmdTasks(), f"/cancel {self.task.get_id()}") self.task_handler.clock.advance(self.timedelay + 1) self.assertFalse(self.task.exists()) self.task = self.task_handler.add(self.timedelay, func_test_cmd_tasks) self.assertTrue(self.task.get_id(), 1) self.char1.msg = Mock() - self.char1.execute_cmd('y') - text = '' + self.char1.execute_cmd("y") + text = "" for _, _, kwargs in self.char1.msg.mock_calls: - text += kwargs.get('text', '') - self.assertEqual(text, 'Task completed while waiting for input.') + text += kwargs.get("text", "") + self.assertEqual(text, "Task completed while waiting for input.") def test_misformed_command(self): - wanted_msg = 'Task command misformed.|Proper format tasks[/switch] ' \ - '[function name or task id]' - self.call(system.CmdTasks(), f'/cancel', wanted_msg) + wanted_msg = ( + "Task command misformed.|Proper format tasks[/switch] " "[function name or task id]" + ) + self.call(system.CmdTasks(), f"/cancel", wanted_msg) class TestAdmin(BaseEvenniaCommandTest): @@ -656,7 +682,9 @@ class TestBuilding(BaseEvenniaCommandTest): self.call(building.CmdExamine(), "*TestAccount", "Name/key: TestAccount") self.char1.db.test = "testval" - self.call(building.CmdExamine(), "self/test", "Attribute Char/test [category=None]:\n\ntestval") + 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") @@ -665,7 +693,7 @@ class TestBuilding(BaseEvenniaCommandTest): self.call( building.CmdExamine(), "self/test2", - "Attribute Char/test2 [category=None]:\n\nthis is a \$random() value." + "Attribute Char/test2 [category=None]:\n\nthis is a \$random() value.", ) self.room1.scripts.add(self.script.__class__) @@ -710,10 +738,16 @@ class TestBuilding(BaseEvenniaCommandTest): 'Obj2/test2="value2"', "Created attribute Obj2/test2 [category:None] = value2", ) - self.call(building.CmdSetAttribute(), - "Obj2/test2", "Attribute Obj2/test2 [category:None] = value2") - self.call(building.CmdSetAttribute(), - "Obj2/NotFound", "Attribute Obj2/notfound [category:None] does not exist.") + self.call( + building.CmdSetAttribute(), + "Obj2/test2", + "Attribute Obj2/test2 [category:None] = value2", + ) + self.call( + building.CmdSetAttribute(), + "Obj2/NotFound", + "Attribute Obj2/notfound [category:None] does not exist.", + ) with patch("evennia.commands.default.building.EvEditor") as mock_ed: self.call(building.CmdSetAttribute(), "/edit Obj2/test3") @@ -753,17 +787,18 @@ class TestBuilding(BaseEvenniaCommandTest): # list - adding white space proves real parsing self.call( building.CmdSetAttribute(), - "Obj/test1=[1,2]", "Created attribute Obj/test1 [category:None] = [1, 2]" + "Obj/test1=[1,2]", + "Created attribute Obj/test1 [category:None] = [1, 2]", + ) + self.call( + building.CmdSetAttribute(), "Obj/test1", "Attribute Obj/test1 [category:None] = [1, 2]" + ) + self.call( + building.CmdSetAttribute(), "Obj/test1[0]", "Attribute Obj/test1[0] [category:None] = 1" + ) + self.call( + building.CmdSetAttribute(), "Obj/test1[1]", "Attribute Obj/test1[1] [category:None] = 2" ) - self.call(building.CmdSetAttribute(), - "Obj/test1", - "Attribute Obj/test1 [category:None] = [1, 2]") - self.call(building.CmdSetAttribute(), - "Obj/test1[0]", - "Attribute Obj/test1[0] [category:None] = 1") - self.call(building.CmdSetAttribute(), - "Obj/test1[1]", - "Attribute Obj/test1[1] [category:None] = 2") self.call( building.CmdSetAttribute(), "Obj/test1[0] = 99", @@ -772,7 +807,7 @@ class TestBuilding(BaseEvenniaCommandTest): self.call( building.CmdSetAttribute(), "Obj/test1[0]", - "Attribute Obj/test1[0] [category:None] = 99" + "Attribute Obj/test1[0] [category:None] = 99", ) # list delete self.call( @@ -781,9 +816,7 @@ class TestBuilding(BaseEvenniaCommandTest): "Deleted attribute Obj/test1[0] [category:None].", ) self.call( - building.CmdSetAttribute(), - "Obj/test1[0]", - "Attribute Obj/test1[0] [category:None] = 2" + building.CmdSetAttribute(), "Obj/test1[0]", "Attribute Obj/test1[0] [category:None] = 2" ) self.call( building.CmdSetAttribute(), @@ -795,7 +828,7 @@ class TestBuilding(BaseEvenniaCommandTest): building.CmdSetAttribute(), "Obj/test1[5] =", "No attribute Obj/test1[5] [category: None] was found to " - "delete. (Nested lookups attempted)" + "delete. (Nested lookups attempted)", ) # Append self.call( @@ -817,17 +850,18 @@ class TestBuilding(BaseEvenniaCommandTest): ) self.call( building.CmdSetAttribute(), - "Obj/test2", "Attribute Obj/test2 [category:None] = {'one': 1, 'two': 2}" + "Obj/test2", + "Attribute Obj/test2 [category:None] = {'one': 1, 'two': 2}", ) self.call( building.CmdSetAttribute(), "Obj/test2['one']", - "Attribute Obj/test2['one'] [category:None] = 1" + "Attribute Obj/test2['one'] [category:None] = 1", ) self.call( building.CmdSetAttribute(), "Obj/test2['one]", - "Attribute Obj/test2['one] [category:None] = 1" + "Attribute Obj/test2['one] [category:None] = 1", ) self.call( building.CmdSetAttribute(), @@ -837,17 +871,17 @@ class TestBuilding(BaseEvenniaCommandTest): self.call( building.CmdSetAttribute(), "Obj/test2['one']", - "Attribute Obj/test2['one'] [category:None] = 99" + "Attribute Obj/test2['one'] [category:None] = 99", ) self.call( building.CmdSetAttribute(), "Obj/test2['two']", - "Attribute Obj/test2['two'] [category:None] = 2" + "Attribute Obj/test2['two'] [category:None] = 2", ) self.call( building.CmdSetAttribute(), "Obj/test2[+'three']", - "Attribute Obj/test2[+'three'] [category:None] does not exist. (Nested lookups attempted)" + "Attribute Obj/test2[+'three'] [category:None] does not exist. (Nested lookups attempted)", ) self.call( building.CmdSetAttribute(), @@ -857,7 +891,7 @@ class TestBuilding(BaseEvenniaCommandTest): self.call( building.CmdSetAttribute(), "Obj/test2[+'three'] =", - "Deleted attribute Obj/test2[+'three'] [category:None]." + "Deleted attribute Obj/test2[+'three'] [category:None].", ) self.call( building.CmdSetAttribute(), @@ -873,23 +907,23 @@ class TestBuilding(BaseEvenniaCommandTest): self.call( building.CmdSetAttribute(), "Obj/test2['two']", - "Attribute Obj/test2['two'] [category:None] does not exist. (Nested lookups attempted)" + "Attribute Obj/test2['two'] [category:None] does not exist. (Nested lookups attempted)", ) self.call( building.CmdSetAttribute(), "Obj/test2", - "Attribute Obj/test2 [category:None] = {'one': 99, 'three': 3}" + "Attribute Obj/test2 [category:None] = {'one': 99, 'three': 3}", ) self.call( building.CmdSetAttribute(), "Obj/test2[0]", - "Attribute Obj/test2[0] [category:None] does not exist. (Nested lookups attempted)" + "Attribute Obj/test2[0] [category:None] does not exist. (Nested lookups attempted)", ) self.call( building.CmdSetAttribute(), "Obj/test2['five'] =", "No attribute Obj/test2['five'] [category: None] " - "was found to delete. (Nested lookups attempted)" + "was found to delete. (Nested lookups attempted)", ) self.call( building.CmdSetAttribute(), @@ -907,7 +941,7 @@ class TestBuilding(BaseEvenniaCommandTest): self.call( building.CmdSetAttribute(), "Obj/tup = (1,2)", - "Created attribute Obj/tup [category:None] = (1, 2)" + "Created attribute Obj/tup [category:None] = (1, 2)", ) self.call( building.CmdSetAttribute(), @@ -929,7 +963,7 @@ class TestBuilding(BaseEvenniaCommandTest): # Special case for tuple, could have a better message "Obj/tup[1] = ", "No attribute Obj/tup[1] [category: None] " - "was found to delete. (Nested lookups attempted)" + "was found to delete. (Nested lookups attempted)", ) # Deaper nesting @@ -941,26 +975,25 @@ class TestBuilding(BaseEvenniaCommandTest): self.call( building.CmdSetAttribute(), "Obj/test3[0]['one']", - "Attribute Obj/test3[0]['one'] [category:None] = 1" + "Attribute Obj/test3[0]['one'] [category:None] = 1", ) self.call( building.CmdSetAttribute(), "Obj/test3[0]", - "Attribute Obj/test3[0] [category:None] = {'one': 1}" + "Attribute Obj/test3[0] [category:None] = {'one': 1}", ) self.call( building.CmdSetAttribute(), "Obj/test3[0]['one'] =", - "Deleted attribute Obj/test3[0]['one'] [category:None]." + "Deleted attribute Obj/test3[0]['one'] [category:None].", ) self.call( building.CmdSetAttribute(), "Obj/test3[0]", - "Attribute Obj/test3[0] [category:None] = {}") + "Attribute Obj/test3[0] [category:None] = {}", + ) self.call( - building.CmdSetAttribute(), - "Obj/test3", - "Attribute Obj/test3 [category:None] = [{}]" + building.CmdSetAttribute(), "Obj/test3", "Attribute Obj/test3 [category:None] = [{}]" ) # Naughty keys @@ -972,7 +1005,8 @@ class TestBuilding(BaseEvenniaCommandTest): self.call( building.CmdSetAttribute(), "Obj/test4[0]", - "Attribute Obj/test4[0] [category:None] = foo") + "Attribute Obj/test4[0] [category:None] = foo", + ) self.call( building.CmdSetAttribute(), "Obj/test4=[{'one': 1}]", @@ -981,32 +1015,30 @@ class TestBuilding(BaseEvenniaCommandTest): self.call( building.CmdSetAttribute(), "Obj/test4[0]['one']", - "Attribute Obj/test4[0]['one'] [category:None] = 1" + "Attribute Obj/test4[0]['one'] [category:None] = 1", ) # Prefer nested items self.call( building.CmdSetAttribute(), "Obj/test4[0]", - "Attribute Obj/test4[0] [category:None] = {'one': 1}" + "Attribute Obj/test4[0] [category:None] = {'one': 1}", ) self.call( building.CmdSetAttribute(), "Obj/test4[0]['one']", - "Attribute Obj/test4[0]['one'] [category:None] = 1" + "Attribute Obj/test4[0]['one'] [category:None] = 1", ) # Restored access - self.call( - building.CmdWipe(), - "Obj/test4", - "Wiped attributes test4 on Obj.") + self.call(building.CmdWipe(), "Obj/test4", "Wiped attributes test4 on Obj.") self.call( building.CmdSetAttribute(), "Obj/test4[0]", - "Attribute Obj/test4[0] [category:None] = foo") + "Attribute Obj/test4[0] [category:None] = foo", + ) self.call( building.CmdSetAttribute(), "Obj/test4[0]['one']", - "Attribute Obj/test4[0]['one'] [category:None] does not exist. (Nested lookups attempted)" + "Attribute Obj/test4[0]['one'] [category:None] does not exist. (Nested lookups attempted)", ) def test_split_nested_attr(self): @@ -1222,8 +1254,11 @@ class TestBuilding(BaseEvenniaCommandTest): self.call(building.CmdSetHome(), "Obj = Room2", "Home location of Obj was set to Room") def test_list_cmdsets(self): - self.call(building.CmdListCmdSets(), "", - " stack:\n :") + self.call( + building.CmdListCmdSets(), + "", + " stack:\n :", + ) self.call(building.CmdListCmdSets(), "NotFound", "Could not find 'NotFound'") def test_typeclass(self): @@ -1392,17 +1427,13 @@ class TestBuilding(BaseEvenniaCommandTest): self.call( building.CmdScripts(), "Obj = scripts.scripts.DefaultScript", - "Script scripts.scripts.DefaultScript successfully added" - ) - self.call( - building.CmdScripts(), - "evennia.Dummy", - "Global Script NOT Created " + "Script scripts.scripts.DefaultScript successfully added", ) + self.call(building.CmdScripts(), "evennia.Dummy", "Global Script NOT Created ") self.call( building.CmdScripts(), "evennia.scripts.scripts.DoNothing", - "Global Script Created - sys_do_nothing " + "Global Script Created - sys_do_nothing ", ) self.call(building.CmdScripts(), "Obj ", "dbref ") @@ -1412,32 +1443,33 @@ class TestBuilding(BaseEvenniaCommandTest): self.call(building.CmdScripts(), "/stop Obj", "Script on Obj Stopped - ") self.call( - building.CmdScripts(), "Obj = scripts.scripts.DefaultScript", + building.CmdScripts(), + "Obj = scripts.scripts.DefaultScript", "Script scripts.scripts.DefaultScript successfully added", - inputs=["Y"] + inputs=["Y"], ) self.call( building.CmdScripts(), "/start Obj = scripts.scripts.DefaultScript", "Script on Obj Started ", - inputs=["Y"] + inputs=["Y"], ) self.call( building.CmdScripts(), "/stop Obj = scripts.scripts.DefaultScript", "Script on Obj Stopped ", - inputs=["Y"] + inputs=["Y"], ) self.call( building.CmdScripts(), "/delete Obj = scripts.scripts.DefaultScript", "Script on Obj Deleted ", - inputs=["Y"] + inputs=["Y"], ) self.call( building.CmdScripts(), "/delete evennia.scripts.scripts.DoNothing", - "Global Script Deleted -" + "Global Script Deleted -", ) def test_script_multi_delete(self): @@ -1452,7 +1484,7 @@ class TestBuilding(BaseEvenniaCommandTest): f"Global Script Deleted - #{script1.id} (evennia.scripts.scripts.DefaultScript)|" f"Global Script Deleted - #{script2.id} (evennia.scripts.scripts.DefaultScript)|" f"Global Script Deleted - #{script3.id} (evennia.scripts.scripts.DefaultScript)", - inputs=["y"] + inputs=["y"], ) self.assertFalse(script1.pk) self.assertFalse(script2.pk) @@ -1707,11 +1739,10 @@ class TestCommsChannel(BaseEvenniaCommandTest): Test the central `channel` command. """ + def setUp(self): super().setUp() - self.channel = create_channel( - key="testchannel", - desc="A test channel") + self.channel = create_channel(key="testchannel", desc="A test channel") self.channel.connect(self.char1) self.cmdchannel = cmd_comms.CmdChannel self.cmdchannel.account_caller = False @@ -1722,61 +1753,35 @@ class TestCommsChannel(BaseEvenniaCommandTest): # test channel command def test_channel__noarg(self): - self.call( - self.cmdchannel(), - "", - "Channel subscriptions" - ) + self.call(self.cmdchannel(), "", "Channel subscriptions") def test_channel__msg(self): self.channel.msg = Mock() - self.call( - self.cmdchannel(), - "testchannel = Test message", - "" - ) + self.call(self.cmdchannel(), "testchannel = Test message", "") self.channel.msg.assert_called_with("Test message", senders=self.char1) def test_channel__list(self): - self.call( - self.cmdchannel(), - "/list", - "Channel subscriptions" - ) + self.call(self.cmdchannel(), "/list", "Channel subscriptions") def test_channel__all(self): - self.call( - self.cmdchannel(), - "/all", - "Available channels" - ) + self.call(self.cmdchannel(), "/all", "Available channels") def test_channel__history(self): with patch("evennia.commands.default.comms.tail_log_file") as mock_tail: - self.call( - self.cmdchannel(), - "/history testchannel", - "" - ) + self.call(self.cmdchannel(), "/history testchannel", "") mock_tail.assert_called() def test_channel__sub(self): self.channel.disconnect(self.char1) - self.call( - self.cmdchannel(), - "/sub testchannel", - "You are now subscribed" - ) + self.call(self.cmdchannel(), "/sub testchannel", "You are now subscribed") self.assertTrue(self.char1 in self.channel.subscriptions.all()) - self.assertEqual(self.char1.nicks.nickreplace("testchannel Hello"), "channel testchannel = Hello") + self.assertEqual( + self.char1.nicks.nickreplace("testchannel Hello"), "channel testchannel = Hello" + ) def test_channel__unsub(self): - self.call( - self.cmdchannel(), - "/unsub testchannel", - "You un-subscribed" - ) + self.call(self.cmdchannel(), "/unsub testchannel", "You un-subscribed") self.assertFalse(self.char1 in self.channel.subscriptions.all()) def test_channel__alias__unalias(self): @@ -1786,51 +1791,31 @@ class TestCommsChannel(BaseEvenniaCommandTest): self.call( self.cmdchannel(), "/alias testchannel = foo", - "Added/updated your alias 'foo' for channel testchannel." + "Added/updated your alias 'foo' for channel testchannel.", ) - self.assertEqual( - self.char1.nicks.nickreplace('foo Hello'), "channel testchannel = Hello") + self.assertEqual(self.char1.nicks.nickreplace("foo Hello"), "channel testchannel = Hello") # use alias self.channel.msg = Mock() - self.call( - self.cmdchannel(), - "foo = test message", - "") + self.call(self.cmdchannel(), "foo = test message", "") self.channel.msg.assert_called_with("test message", senders=self.char1) # remove alias - self.call( - self.cmdchannel(), - "/unalias foo", - "Removed your channel alias 'foo'" - ) - self.assertEqual(self.char1.nicks.get('foo $1', category="channel"), None) + self.call(self.cmdchannel(), "/unalias foo", "Removed your channel alias 'foo'") + self.assertEqual(self.char1.nicks.get("foo $1", category="channel"), None) def test_channel__mute(self): - self.call( - self.cmdchannel(), - "/mute testchannel", - "Muted channel testchannel" - ) + self.call(self.cmdchannel(), "/mute testchannel", "Muted channel testchannel") self.assertTrue(self.char1 in self.channel.mutelist) def test_channel__unmute(self): self.channel.mute(self.char1) - self.call( - self.cmdchannel(), - "/unmute testchannel = Char1", - "Un-muted channel testchannel" - ) + self.call(self.cmdchannel(), "/unmute testchannel = Char1", "Un-muted channel testchannel") self.assertFalse(self.char1 in self.channel.mutelist) def test_channel__create(self): - self.call( - self.cmdchannel(), - "/create testchannel2", - "Created (and joined) new channel" - ) + self.call(self.cmdchannel(), "/create testchannel2", "Created (and joined) new channel") def test_channel__destroy(self): self.channel.msg = Mock() @@ -1838,34 +1823,27 @@ class TestCommsChannel(BaseEvenniaCommandTest): self.cmdchannel(), "/destroy testchannel = delete reason", "Are you sure you want to delete channel ", - inputs=['Yes'] + inputs=["Yes"], ) - self.channel.msg.assert_called_with( - "delete reason", bypass_mute=True, senders=self.char1) + self.channel.msg.assert_called_with("delete reason", bypass_mute=True, senders=self.char1) def test_channel__desc(self): self.call( self.cmdchannel(), "/desc testchannel = Another description", - "Updated channel description." + "Updated channel description.", ) def test_channel__lock(self): self.call( - self.cmdchannel(), - "/lock testchannel = foo:false()", - "Added/updated lock on channel" + self.cmdchannel(), "/lock testchannel = foo:false()", "Added/updated lock on channel" ) - self.assertEqual(self.channel.locks.get('foo'), 'foo:false()') + self.assertEqual(self.channel.locks.get("foo"), "foo:false()") def test_channel__unlock(self): self.channel.locks.add("foo:true()") - self.call( - self.cmdchannel(), - "/unlock testchannel = foo", - "Removed lock from channel" - ) - self.assertEqual(self.channel.locks.get('foo'), '') + self.call(self.cmdchannel(), "/unlock testchannel = foo", "Removed lock from channel") + self.assertEqual(self.channel.locks.get("foo"), "") def test_channel__boot(self): self.channel.connect(self.char2) @@ -1877,12 +1855,14 @@ class TestCommsChannel(BaseEvenniaCommandTest): self.cmdchannel(), "/boot testchannel = Char2 : Booting from channel!", "Are you sure ", - inputs=["Yes"] + inputs=["Yes"], ) self.channel.msg.assert_called_with( - "Char2 was booted from channel by Char. Reason: Booting from channel!") + "Char2 was booted from channel by Char. Reason: Booting from channel!" + ) self.char2.msg.assert_called_with( - "You were booted from channel testchannel by Char. Reason: Booting from channel!") + "You were booted from channel testchannel by Char. Reason: Booting from channel!" + ) def test_channel__ban__unban(self): """Test first ban and then unban""" @@ -1897,12 +1877,14 @@ class TestCommsChannel(BaseEvenniaCommandTest): self.cmdchannel(), "/ban testchannel = Char2 : Banning from channel!", "Are you sure ", - inputs=["Yes"] + inputs=["Yes"], ) self.channel.msg.assert_called_with( - "Char2 was booted from channel by Char. Reason: Banning from channel!") + "Char2 was booted from channel by Char. Reason: Banning from channel!" + ) self.char2.msg.assert_called_with( - "You were booted from channel testchannel by Char. Reason: Banning from channel!") + "You were booted from channel testchannel by Char. Reason: Banning from channel!" + ) self.assertTrue(self.char2 in self.channel.banlist) # unban @@ -1910,23 +1892,18 @@ class TestCommsChannel(BaseEvenniaCommandTest): self.call( self.cmdchannel(), "/unban testchannel = Char2", - "Un-banned Char2 from channel testchannel" + "Un-banned Char2 from channel testchannel", ) self.assertFalse(self.char2 in self.channel.banlist) def test_channel__who(self): - self.call( - self.cmdchannel(), - "/who testchannel", - "Subscribed to testchannel:\nChar" - ) + self.call(self.cmdchannel(), "/who testchannel", "Subscribed to testchannel:\nChar") from evennia.commands.default import comms # noqa class TestComms(BaseEvenniaCommandTest): - def test_page(self): self.call( comms.CmdPage(), @@ -1942,6 +1919,7 @@ class TestBatchProcess(BaseEvenniaCommandTest): Test the batch processor. """ + # there is some sort of issue with the mock; it needs to loaded once to work from evennia.contrib.tutorials.red_button import red_button # noqa diff --git a/evennia/commands/default/unloggedin.py b/evennia/commands/default/unloggedin.py index e6837551ec..7bd6cc7ffc 100644 --- a/evennia/commands/default/unloggedin.py +++ b/evennia/commands/default/unloggedin.py @@ -201,13 +201,17 @@ class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS): non_normalized_username = username username = Account.normalize_username(username) if non_normalized_username != username: - session.msg("Note: your username was normalized to strip spaces and remove characters " - "that could be visually confusing.") + session.msg( + "Note: your username was normalized to strip spaces and remove characters " + "that could be visually confusing." + ) # have the user verify their new account was what they intended - answer = yield(f"You want to create an account '{username}' with password '{password}'." - "\nIs this what you intended? [Y]/N?") - if answer.lower() in ('n', 'no'): + answer = yield ( + f"You want to create an account '{username}' with password '{password}'." + "\nIs this what you intended? [Y]/N?" + ) + if answer.lower() in ("n", "no"): session.msg("Aborted. If your user name contains spaces, surround it by quotes.") return @@ -344,7 +348,7 @@ class CmdUnconnectedEncoding(COMMAND_DEFAULT_CLASS): If you don't submit an encoding, the current encoding will be displayed instead. - """ + """ key = "encoding" aliases = "encode" diff --git a/evennia/commands/tests.py b/evennia/commands/tests.py index 7698aba781..f0ad139f01 100644 --- a/evennia/commands/tests.py +++ b/evennia/commands/tests.py @@ -339,7 +339,7 @@ class TestOptionTransferTrue(TestCase): b.no_objs = False d.duplicates = False # higher-prio sets will change the option up the chain - cmdset_f = d + c + b + a # reverse, high prio + cmdset_f = d + c + b + a # reverse, high prio self.assertTrue(cmdset_f.no_exits) self.assertTrue(cmdset_f.no_objs) self.assertTrue(cmdset_f.no_channels) @@ -407,7 +407,7 @@ class TestOptionTransferTrue(TestCase): c.priority = 1 d.priority = 2 c.no_exits = False - c.no_channels = None # passthrough + c.no_channels = None # passthrough b.no_objs = False d.duplicates = False # higher-prio sets will change the option up the chain @@ -639,7 +639,7 @@ class TestOptionTransferFalse(TestCase): b.no_objs = True d.duplicates = True # higher-prio sets will change the option up the chain - cmdset_f = d + c + b + a # reverse, high prio + cmdset_f = d + c + b + a # reverse, high prio self.assertFalse(cmdset_f.no_exits) self.assertFalse(cmdset_f.no_objs) self.assertFalse(cmdset_f.no_channels) @@ -663,7 +663,7 @@ class TestOptionTransferFalse(TestCase): b.no_objs = True d.duplicates = True # higher-prio sets will change the option up the chain - cmdset_f = a + b + c + d # forward, high prio, never happens + cmdset_f = a + b + c + d # forward, high prio, never happens self.assertFalse(cmdset_f.no_exits) self.assertFalse(cmdset_f.no_objs) self.assertFalse(cmdset_f.no_channels) @@ -707,7 +707,7 @@ class TestOptionTransferFalse(TestCase): c.priority = 1 d.priority = 2 c.no_exits = True - c.no_channels = None # passthrough + c.no_channels = None # passthrough b.no_objs = True d.duplicates = True # higher-prio sets will change the option up the chain @@ -908,6 +908,7 @@ class TestOptionTransferReplace(TestCase): """ Test option transfer through more complex merge types. """ + def setUp(self): super().setUp() self.cmdset_a = _CmdSetA() @@ -1182,7 +1183,6 @@ class TestCmdSetNesting(BaseEvenniaTest): """ def test_nest(self): - class CmdA(Command): key = "a" diff --git a/evennia/comms/comms.py b/evennia/comms/comms.py index 68d61864b7..990cd2b5ee 100644 --- a/evennia/comms/comms.py +++ b/evennia/comms/comms.py @@ -107,7 +107,8 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase): """ if not self._log_file: self._log_file = self.attributes.get( - "log_file", self.log_file.format(channelname=self.key.lower())) + "log_file", self.log_file.format(channelname=self.key.lower()) + ) return self._log_file def set_log_filename(self, filename): @@ -455,8 +456,13 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase): # needing to use the `channel` command explicitly. msg_nick_pattern = self.channel_msg_nick_pattern.format(alias=alias) msg_nick_replacement = self.channel_msg_nick_replacement.format(channelname=chan_key) - user.nicks.add(msg_nick_pattern, msg_nick_replacement, category="inputline", - pattern_is_regex=True, **kwargs) + user.nicks.add( + msg_nick_pattern, + msg_nick_replacement, + category="inputline", + pattern_is_regex=True, + **kwargs, + ) if chan_key != alias: # this allows for using the alias for general channel lookups @@ -546,7 +552,7 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase): if not bypass_mute: receivers = [receiver for receiver in receivers if receiver not in self.mutelist] - send_kwargs = {'senders': senders, 'bypass_mute': bypass_mute, **kwargs} + send_kwargs = {"senders": senders, "bypass_mute": bypass_mute, **kwargs} # pre-send hook message = self.at_pre_msg(message, **send_kwargs) @@ -826,27 +832,37 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase): # TODO Evennia 1.0+ removed hooks. Remove in 1.1. def message_transform(self, *args, **kwargs): - raise RuntimeError("Channel.message_transform is no longer used in 1.0+. " - "Use Account/Object.at_pre_channel_msg instead.") + raise RuntimeError( + "Channel.message_transform is no longer used in 1.0+. " + "Use Account/Object.at_pre_channel_msg instead." + ) def distribute_message(self, msgobj, online=False, **kwargs): raise RuntimeError("Channel.distribute_message is no longer used in 1.0+.") def format_senders(self, senders=None, **kwargs): - raise RuntimeError("Channel.format_senders is no longer used in 1.0+. " - "Use Account/Object.at_pre_channel_msg instead.") + raise RuntimeError( + "Channel.format_senders is no longer used in 1.0+. " + "Use Account/Object.at_pre_channel_msg instead." + ) def pose_transform(self, msgobj, sender_string, **kwargs): - raise RuntimeError("Channel.pose_transform is no longer used in 1.0+. " - "Use Account/Object.at_pre_channel_msg instead.") + raise RuntimeError( + "Channel.pose_transform is no longer used in 1.0+. " + "Use Account/Object.at_pre_channel_msg instead." + ) def format_external(self, msgobj, senders, emit=False, **kwargs): - raise RuntimeError("Channel.format_external is no longer used in 1.0+. " - "Use Account/Object.at_pre_channel_msg instead.") + raise RuntimeError( + "Channel.format_external is no longer used in 1.0+. " + "Use Account/Object.at_pre_channel_msg instead." + ) def format_message(self, msgobj, emit=False, **kwargs): - raise RuntimeError("Channel.format_message is no longer used in 1.0+. " - "Use Account/Object.at_pre_channel_msg instead.") + raise RuntimeError( + "Channel.format_message is no longer used in 1.0+. " + "Use Account/Object.at_pre_channel_msg instead." + ) def pre_send_message(self, msg, **kwargs): raise RuntimeError("Channel.pre_send_message was renamed to Channel.at_pre_msg.") diff --git a/evennia/comms/managers.py b/evennia/comms/managers.py index a267d0ace9..f9160b332f 100644 --- a/evennia/comms/managers.py +++ b/evennia/comms/managers.py @@ -215,7 +215,7 @@ class MsgManager(TypedObjectManager): return self.filter(db_receivers_accounts=obj).exclude(db_hide_from_accounts=obj) elif typ == "object": return self.filter(db_receivers_objects=obj).exclude(db_hide_from_objects=obj) - elif typ == 'script': + elif typ == "script": return self.filter(db_receivers_scripts=obj) else: raise CommError @@ -257,7 +257,7 @@ class MsgManager(TypedObjectManager): sender_restrict = Q(db_sender_accounts__pk=spk) & ~Q(db_hide_from_accounts__pk=spk) elif styp == "object": sender_restrict = Q(db_sender_objects__pk=spk) & ~Q(db_hide_from_objects__pk=spk) - elif styp == 'script': + elif styp == "script": sender_restrict = Q(db_sender_scripts__pk=spk) else: sender_restrict = Q() @@ -266,16 +266,16 @@ class MsgManager(TypedObjectManager): if receiver: rpk = receiver.pk if rtyp == "account": - receiver_restrict = ( - Q(db_receivers_accounts__pk=rpk) & ~Q(db_hide_from_accounts__pk=rpk)) + receiver_restrict = Q(db_receivers_accounts__pk=rpk) & ~Q(db_hide_from_accounts__pk=rpk) elif rtyp == "object": receiver_restrict = Q(db_receivers_objects__pk=rpk) & ~Q(db_hide_from_objects__pk=rpk) - elif rtyp == 'script': + elif rtyp == "script": receiver_restrict = Q(db_receivers_scripts__pk=rpk) elif rtyp == "channel": raise DeprecationWarning( "Msg.objects.search don't accept channel recipients since " - "Channels no longer accepts Msg objects.") + "Channels no longer accepts Msg objects." + ) else: receiver_restrict = Q() # filter by full text @@ -289,8 +289,9 @@ class MsgManager(TypedObjectManager): # back-compatibility alias message_search = search_message - def create_message(self, senderobj, message, receivers=None, locks=None, tags=None, - header=None, **kwargs): + def create_message( + self, senderobj, message, receivers=None, locks=None, tags=None, header=None, **kwargs + ): """ Create a new communication Msg. Msgs represent a unit of database-persistent communication between entites. @@ -315,7 +316,7 @@ class MsgManager(TypedObjectManager): it's up to the command definitions to limit this as desired. """ - if 'channels' in kwargs: + if "channels" in kwargs: raise DeprecationWarning( "create_message() does not accept 'channel' kwarg anymore " "- channels no longer accept Msg objects." @@ -339,6 +340,7 @@ class MsgManager(TypedObjectManager): new_message.save() return new_message + # # Channel manager # diff --git a/evennia/comms/models.py b/evennia/comms/models.py index ca1ae702ff..23fdd8a3aa 100644 --- a/evennia/comms/models.py +++ b/evennia/comms/models.py @@ -104,7 +104,7 @@ class Msg(SharedMemoryModel): blank=True, db_index=True, help_text="Identifier for single external sender, for use with senders " - "not represented by a regular database model." + "not represented by a regular database model.", ) db_receivers_accounts = models.ManyToManyField( @@ -137,7 +137,7 @@ class Msg(SharedMemoryModel): blank=True, db_index=True, help_text="Identifier for single external receiver, for use with recievers " - "not represented by a regular database model." + "not represented by a regular database model.", ) # header could be used for meta-info about the message if your system needs @@ -286,7 +286,7 @@ class Msg(SharedMemoryModel): """ if isinstance(receivers, str): self.db_receiver_external = receivers - self.save(update_fields=['db_receiver_external']) + self.save(update_fields=["db_receiver_external"]) return for receiver in make_iter(receivers): diff --git a/evennia/comms/tests.py b/evennia/comms/tests.py index d0959e3194..2795ccf48a 100644 --- a/evennia/comms/tests.py +++ b/evennia/comms/tests.py @@ -21,7 +21,9 @@ class ObjectCreationTest(BaseEvenniaTest): class ChannelWholistTests(BaseEvenniaTest): def setUp(self): super().setUp() - self.default_channel, _ = DefaultChannel.create("coffeetalk", description="A place to talk about coffee.") + self.default_channel, _ = DefaultChannel.create( + "coffeetalk", description="A place to talk about coffee." + ) self.default_channel.connect(self.obj1) def test_wholist_shows_subscribed_objects(self): @@ -31,7 +33,9 @@ class ChannelWholistTests(BaseEvenniaTest): def test_wholist_shows_none_when_empty(self): # No one hates dogs - empty_channel, _ = DefaultChannel.create("doghaters", description="A place where dog haters unite.") + empty_channel, _ = DefaultChannel.create( + "doghaters", description="A place where dog haters unite." + ) expected = "" result = empty_channel.wholist self.assertEqual(expected, result) diff --git a/evennia/contrib/base_systems/awsstorage/aws_s3_cdn.py b/evennia/contrib/base_systems/awsstorage/aws_s3_cdn.py index e47c6e2723..2bf626ed8c 100644 --- a/evennia/contrib/base_systems/awsstorage/aws_s3_cdn.py +++ b/evennia/contrib/base_systems/awsstorage/aws_s3_cdn.py @@ -165,7 +165,9 @@ def check_location(storage): correct = storage.location.lstrip("/") raise ImproperlyConfigured( "{}.location cannot begin with a leading slash. Found '{}'. Use '{}' instead.".format( - storage.__class__.__name__, storage.location, correct, + storage.__class__.__name__, + storage.location, + correct, ) ) diff --git a/evennia/contrib/base_systems/awsstorage/tests.py b/evennia/contrib/base_systems/awsstorage/tests.py index a9d180d461..400bfda0df 100644 --- a/evennia/contrib/base_systems/awsstorage/tests.py +++ b/evennia/contrib/base_systems/awsstorage/tests.py @@ -121,7 +121,11 @@ class S3Boto3StorageTests(S3Boto3TestCase): obj = self.storage.bucket.Object.return_value obj.upload_fileobj.assert_called_with( - content, ExtraArgs={"ContentType": "text/plain", "ACL": self.storage.default_acl,} + content, + ExtraArgs={ + "ContentType": "text/plain", + "ACL": self.storage.default_acl, + }, ) def test_storage_save_with_acl(self): @@ -136,7 +140,11 @@ class S3Boto3StorageTests(S3Boto3TestCase): obj = self.storage.bucket.Object.return_value obj.upload_fileobj.assert_called_with( - content, ExtraArgs={"ContentType": "text/plain", "ACL": "private",} + content, + ExtraArgs={ + "ContentType": "text/plain", + "ACL": "private", + }, ) def test_content_type(self): @@ -151,7 +159,11 @@ class S3Boto3StorageTests(S3Boto3TestCase): obj = self.storage.bucket.Object.return_value obj.upload_fileobj.assert_called_with( - content, ExtraArgs={"ContentType": "image/jpeg", "ACL": self.storage.default_acl,} + content, + ExtraArgs={ + "ContentType": "image/jpeg", + "ACL": self.storage.default_acl, + }, ) def test_storage_save_gzipped(self): @@ -379,7 +391,10 @@ class S3Boto3StorageTests(S3Boto3TestCase): self.assertEqual(uploaded_content, written_content) multipart.complete.assert_called_once_with( MultipartUpload={ - "Parts": [{"ETag": "123", "PartNumber": 1}, {"ETag": "456", "PartNumber": 2},] + "Parts": [ + {"ETag": "123", "PartNumber": 1}, + {"ETag": "456", "PartNumber": 2}, + ] } ) @@ -394,7 +409,10 @@ class S3Boto3StorageTests(S3Boto3TestCase): ) self.storage._get_or_create_bucket("testbucketname") Bucket.create.assert_called_once_with( - ACL="public-read", CreateBucketConfiguration={"LocationConstraint": "sa-east-1",} + ACL="public-read", + CreateBucketConfiguration={ + "LocationConstraint": "sa-east-1", + }, ) def test_auto_creating_bucket_with_acl(self): @@ -409,22 +427,28 @@ class S3Boto3StorageTests(S3Boto3TestCase): ) self.storage._get_or_create_bucket("testbucketname") Bucket.create.assert_called_once_with( - ACL="public-read", CreateBucketConfiguration={"LocationConstraint": "sa-east-1",} + ACL="public-read", + CreateBucketConfiguration={ + "LocationConstraint": "sa-east-1", + }, ) def test_storage_exists(self): self.assertTrue(self.storage.exists("file.txt")) self.storage.connection.meta.client.head_object.assert_called_with( - Bucket=self.storage.bucket_name, Key="file.txt", + Bucket=self.storage.bucket_name, + Key="file.txt", ) def test_storage_exists_false(self): self.storage.connection.meta.client.head_object.side_effect = ClientError( - {"Error": {"Code": "404", "Message": "Not Found"}}, "HeadObject", + {"Error": {"Code": "404", "Message": "Not Found"}}, + "HeadObject", ) self.assertFalse(self.storage.exists("file.txt")) self.storage.connection.meta.client.head_object.assert_called_with( - Bucket=self.storage.bucket_name, Key="file.txt", + Bucket=self.storage.bucket_name, + Key="file.txt", ) def test_storage_exists_doesnt_create_bucket(self): @@ -445,8 +469,14 @@ class S3Boto3StorageTests(S3Boto3TestCase): # 4.txt pages = [ { - "CommonPrefixes": [{"Prefix": "some"}, {"Prefix": "other"},], - "Contents": [{"Key": "2.txt"}, {"Key": "4.txt"},], + "CommonPrefixes": [ + {"Prefix": "some"}, + {"Prefix": "other"}, + ], + "Contents": [ + {"Key": "2.txt"}, + {"Key": "4.txt"}, + ], }, ] @@ -465,7 +495,14 @@ class S3Boto3StorageTests(S3Boto3TestCase): # some/path/1.txt # some/2.txt pages = [ - {"CommonPrefixes": [{"Prefix": "some/path"},], "Contents": [{"Key": "some/2.txt"},],}, + { + "CommonPrefixes": [ + {"Prefix": "some/path"}, + ], + "Contents": [ + {"Key": "some/2.txt"}, + ], + }, ] paginator = mock.MagicMock() diff --git a/evennia/contrib/base_systems/building_menu/tests.py b/evennia/contrib/base_systems/building_menu/tests.py index 648f614f27..f03360b7b6 100644 --- a/evennia/contrib/base_systems/building_menu/tests.py +++ b/evennia/contrib/base_systems/building_menu/tests.py @@ -4,7 +4,7 @@ Building menu tests. """ from evennia.commands.default.tests import BaseEvenniaCommandTest -from . building_menu import BuildingMenu, CmdNoMatch +from .building_menu import BuildingMenu, CmdNoMatch class Submenu(BuildingMenu): diff --git a/evennia/contrib/base_systems/custom_gametime/custom_gametime.py b/evennia/contrib/base_systems/custom_gametime/custom_gametime.py index 619985ceb7..5611580187 100644 --- a/evennia/contrib/base_systems/custom_gametime/custom_gametime.py +++ b/evennia/contrib/base_systems/custom_gametime/custom_gametime.py @@ -151,8 +151,9 @@ def realtime_to_gametime(secs=0, mins=0, hrs=0, days=1, weeks=1, months=1, yrs=0 """ if days <= 0 or weeks <= 0 or months <= 0: - raise ValueError("realtime_to_gametime: days/weeks/months cannot be set <= 0, " - "they start from 1.") + raise ValueError( + "realtime_to_gametime: days/weeks/months cannot be set <= 0, " "they start from 1." + ) # days/weeks/months start from 1, we need to adjust them to work mathematically. days, weeks, months = days - 1, weeks - 1, months - 1 diff --git a/evennia/contrib/base_systems/ingame_python/tests.py b/evennia/contrib/base_systems/ingame_python/tests.py index ddcee13d1d..e4727f0f1e 100644 --- a/evennia/contrib/base_systems/ingame_python/tests.py +++ b/evennia/contrib/base_systems/ingame_python/tests.py @@ -31,17 +31,27 @@ class TestEventHandler(BaseEvenniaTest): def setUp(self): """Create the event handler.""" super().setUp() - self.handler = create_script("evennia.contrib.base_systems.ingame_python.scripts.EventHandler") + self.handler = create_script( + "evennia.contrib.base_systems.ingame_python.scripts.EventHandler" + ) # Copy old events if necessary if OLD_EVENTS: self.handler.ndb.events = dict(OLD_EVENTS) # Alter typeclasses - self.char1.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter") - self.char2.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter") - self.room1.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom") - self.room2.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom") + self.char1.swap_typeclass( + "evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter" + ) + self.char2.swap_typeclass( + "evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter" + ) + self.room1.swap_typeclass( + "evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom" + ) + self.room2.swap_typeclass( + "evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom" + ) self.exit.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventExit") def tearDown(self): @@ -253,17 +263,27 @@ class TestCmdCallback(BaseEvenniaCommandTest): def setUp(self): """Create the callback handler.""" super().setUp() - self.handler = create_script("evennia.contrib.base_systems.ingame_python.scripts.EventHandler") + self.handler = create_script( + "evennia.contrib.base_systems.ingame_python.scripts.EventHandler" + ) # Copy old events if necessary if OLD_EVENTS: self.handler.ndb.events = dict(OLD_EVENTS) # Alter typeclasses - self.char1.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter") - self.char2.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter") - self.room1.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom") - self.room2.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom") + self.char1.swap_typeclass( + "evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter" + ) + self.char2.swap_typeclass( + "evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter" + ) + self.room1.swap_typeclass( + "evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom" + ) + self.room2.swap_typeclass( + "evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom" + ) self.exit.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventExit") def tearDown(self): @@ -432,17 +452,27 @@ class TestDefaultCallbacks(BaseEvenniaCommandTest): def setUp(self): """Create the callback handler.""" super().setUp() - self.handler = create_script("evennia.contrib.base_systems.ingame_python.scripts.EventHandler") + self.handler = create_script( + "evennia.contrib.base_systems.ingame_python.scripts.EventHandler" + ) # Copy old events if necessary if OLD_EVENTS: self.handler.ndb.events = dict(OLD_EVENTS) # Alter typeclasses - self.char1.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter") - self.char2.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter") - self.room1.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom") - self.room2.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom") + self.char1.swap_typeclass( + "evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter" + ) + self.char2.swap_typeclass( + "evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter" + ) + self.room1.swap_typeclass( + "evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom" + ) + self.room2.swap_typeclass( + "evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom" + ) self.exit.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventExit") def tearDown(self): diff --git a/evennia/contrib/base_systems/ingame_python/typeclasses.py b/evennia/contrib/base_systems/ingame_python/typeclasses.py index e2b42daf79..de70631218 100644 --- a/evennia/contrib/base_systems/ingame_python/typeclasses.py +++ b/evennia/contrib/base_systems/ingame_python/typeclasses.py @@ -11,7 +11,11 @@ from evennia import DefaultCharacter, DefaultExit, DefaultObject, DefaultRoom from evennia import ScriptDB from evennia.utils.utils import delay, inherits_from, lazy_property from evennia.contrib.base_systems.ingame_python.callbackhandler import CallbackHandler -from evennia.contrib.base_systems.ingame_python.utils import register_events, time_event, phrase_event +from evennia.contrib.base_systems.ingame_python.utils import ( + register_events, + time_event, + phrase_event, +) # Character help CHARACTER_CAN_DELETE = """ diff --git a/evennia/contrib/base_systems/ingame_python/utils.py b/evennia/contrib/base_systems/ingame_python/utils.py index 4860625736..48ecab1ab0 100644 --- a/evennia/contrib/base_systems/ingame_python/utils.py +++ b/evennia/contrib/base_systems/ingame_python/utils.py @@ -173,7 +173,9 @@ def time_event(obj, event_name, number, parameters): """ seconds, usual, key = get_next_wait(parameters) script = create_script( - "evennia.contrib.base_systems.ingame_python.scripts.TimeEventScript", interval=seconds, obj=obj + "evennia.contrib.base_systems.ingame_python.scripts.TimeEventScript", + interval=seconds, + obj=obj, ) script.key = key script.desc = "event on {}".format(key) diff --git a/evennia/contrib/base_systems/menu_login/menu_login.py b/evennia/contrib/base_systems/menu_login/menu_login.py index 59207a4270..4654faf2a4 100644 --- a/evennia/contrib/base_systems/menu_login/menu_login.py +++ b/evennia/contrib/base_systems/menu_login/menu_login.py @@ -31,8 +31,7 @@ _GUEST_ENABLED = settings.GUEST_ENABLED _ACCOUNT = class_from_module(settings.BASE_ACCOUNT_TYPECLASS) _GUEST = class_from_module(settings.BASE_GUEST_TYPECLASS) -_ACCOUNT_HELP = ( - "Enter a new or existing login name.") +_ACCOUNT_HELP = "Enter a new or existing login name." _PASSWORD_HELP = ( "Password should be a minimum of 8 characters (preferably longer) and " "can contain a mix of letters, spaces, digits and @/./+/-/_/'/, only." diff --git a/evennia/contrib/base_systems/mux_comms_cmds/mux_comms_cmds.py b/evennia/contrib/base_systems/mux_comms_cmds/mux_comms_cmds.py index 2e22d2b025..2736f7f914 100644 --- a/evennia/contrib/base_systems/mux_comms_cmds/mux_comms_cmds.py +++ b/evennia/contrib/base_systems/mux_comms_cmds/mux_comms_cmds.py @@ -140,7 +140,7 @@ class CmdDelCom(CmdChannel): account_caller = True def func(self): - """Implementing the command. """ + """Implementing the command.""" caller = self.caller @@ -207,8 +207,7 @@ class CmdAllCom(CmdChannel): args = self.args if not args: subscribed, available = self.list_channels() - self.msg( - "\n|wAvailable channels:\n{table}") + self.msg("\n|wAvailable channels:\n{table}") return return @@ -353,7 +352,7 @@ class CmdCBoot(CmdChannel): self.msg(string) return - success, err = self.boot_user(target, quiet='quiet' in self.switches) + success, err = self.boot_user(target, quiet="quiet" in self.switches) if success: self.msg(f"Booted {target.key} from {channel.key}") logger.log_sec( diff --git a/evennia/contrib/full_systems/evscaperoom/objects.py b/evennia/contrib/full_systems/evscaperoom/objects.py index a7201c1389..7f9be4a78f 100644 --- a/evennia/contrib/full_systems/evscaperoom/objects.py +++ b/evennia/contrib/full_systems/evscaperoom/objects.py @@ -343,7 +343,7 @@ class EvscaperoomObject(DefaultObject): # Evennia hooks def return_appearance(self, looker, **kwargs): - """ Could be modified per state. We generally don't worry about the + """Could be modified per state. We generally don't worry about the contents of the object by default. """ diff --git a/evennia/contrib/game_systems/crafting/crafting.py b/evennia/contrib/game_systems/crafting/crafting.py index 2b31121273..9e3bbeb009 100644 --- a/evennia/contrib/game_systems/crafting/crafting.py +++ b/evennia/contrib/game_systems/crafting/crafting.py @@ -667,8 +667,8 @@ class CraftingRecipe(CraftingRecipeBase): consumable_kwargs = {} if location: - tool_kwargs['location'] = location - consumable_kwargs['location'] = location + tool_kwargs["location"] = location + consumable_kwargs["location"] = location tool_key = tool_kwargs.pop("key", None) cons_key = consumable_kwargs.pop("key", None) @@ -966,6 +966,7 @@ class CmdCraft(Command): things in the current location, like a furnace, windmill or anvil. """ + key = "craft" locks = "cmd:all()" help_category = "General" diff --git a/evennia/contrib/game_systems/crafting/example_recipes.py b/evennia/contrib/game_systems/crafting/example_recipes.py index 3c307ff25b..0f0feb5c70 100644 --- a/evennia/contrib/game_systems/crafting/example_recipes.py +++ b/evennia/contrib/game_systems/crafting/example_recipes.py @@ -78,6 +78,7 @@ from .crafting import craft, CraftingRecipe, CraftingValidationError # Sword recipe # ------------------------------------------------------------ + class PigIronRecipe(CraftingRecipe): """ Pig iron is a high-carbon result of melting iron in a blast furnace. @@ -331,9 +332,9 @@ class SwordRecipe(_SwordSmithingBaseRecipe): exact_consumable_order = True -#------------------------------------------------------------ +# ------------------------------------------------------------ # Recipes for spell casting -#------------------------------------------------------------ +# ------------------------------------------------------------ class _MagicRecipe(CraftingRecipe): @@ -348,6 +349,7 @@ class _MagicRecipe(CraftingRecipe): We also assume that the crafter has skills set on itself as plain Attributes. """ + name = "" # all spells require a spellbook and a wand (so there!) tool_tags = ["spellbook", "wand"] @@ -390,15 +392,15 @@ class _MagicRecipe(CraftingRecipe): skill_value = crafter.attributes.get(skill_name) if skill_value is None or skill_value < min_value: - self.msg(self.error_too_low_skill_level.format(skill_name=skill_name, - spell=self.name)) + self.msg( + self.error_too_low_skill_level.format(skill_name=skill_name, spell=self.name) + ) raise CraftingValidationError # get the value of the skill to roll self.skill_roll_value = self.crafter.attributes.get(self.skill_roll) if self.skill_roll_value is None: - self.msg(self.error_no_skill_roll.format(skill_name=self.skill_roll, - spell=self.name)) + self.msg(self.error_no_skill_roll.format(skill_name=self.skill_roll, spell=self.name)) raise CraftingValidationError def do_craft(self, **kwargs): @@ -446,12 +448,13 @@ class FireballRecipe(_MagicRecipe): need to be created to understand what they mean when used. """ + name = "fireball" - skill_requirements = [('firemagic', 10)] # skill 'firemagic' lvl 10 or higher + skill_requirements = [("firemagic", 10)] # skill 'firemagic' lvl 10 or higher skill_roll = "firemagic" success_message = "A ball of flame appears!" - desired_effects = [('target_fire_damage', 25), ('ranged_attack', -2), ('mana_cost', 12)] - failure_effects = [('self_fire_damage', 5), ('mana_cost', 5)] + desired_effects = [("target_fire_damage", 25), ("ranged_attack", -2), ("mana_cost", 12)] + failure_effects = [("self_fire_damage", 5), ("mana_cost", 5)] class HealingRecipe(_MagicRecipe): @@ -462,11 +465,12 @@ class HealingRecipe(_MagicRecipe): need to be created to understand what they mean. """ + name = "heal" - skill_requirements = [('bodymagic', 5), ("empathy", 10)] + skill_requirements = [("bodymagic", 5), ("empathy", 10)] skill_roll = "bodymagic" success_message = "You successfully extend your healing aura." - desired_effects = [('healing', 15), ('mana_cost', 5)] + desired_effects = [("healing", 15), ("mana_cost", 5)] failure_effects = [] @@ -478,7 +482,8 @@ class CmdCast(Command): cast """ - key = 'cast' + + key = "cast" def parse(self): """ @@ -488,8 +493,8 @@ class CmdCast(Command): """ args = self.args.strip().lower() target = None - if ' ' in args: - self.spellname, *target = args.split(' ', 1) + if " " in args: + self.spellname, *target = args.split(" ", 1) else: self.spellname = args @@ -512,8 +517,9 @@ class CmdCast(Command): try: # if this completes without an exception, the caster will have # a new magic_effect set on themselves, ready to use or apply in some way. - success, effects = craft(self.caller, self.spellname, *possible_tools, - raise_exception=True) + success, effects = craft( + self.caller, self.spellname, *possible_tools, raise_exception=True + ) except CraftingValidationError: return except KeyError: @@ -527,5 +533,7 @@ class CmdCast(Command): # (which could be yourself) by a number of health points given by the recipe. effect_txt = ", ".join(f"{eff[0]}({eff[1]})" for eff in effects) success_txt = "|gsucceeded|n" if success else "|rfailed|n" - self.caller.msg(f"Casting the spell {self.spellname} on {self.target} {success_txt}, " - f"causing the following effects: {effect_txt}.") + self.caller.msg( + f"Casting the spell {self.spellname} on {self.target} {success_txt}, " + f"causing the following effects: {effect_txt}." + ) diff --git a/evennia/contrib/game_systems/crafting/tests.py b/evennia/contrib/game_systems/crafting/tests.py index b28faaba78..285f7a9ae9 100644 --- a/evennia/contrib/game_systems/crafting/tests.py +++ b/evennia/contrib/game_systems/crafting/tests.py @@ -165,7 +165,7 @@ class TestCraftingRecipe(BaseEvenniaTestCase): pass def test_error_format(self): - """Test the automatic error formatter """ + """Test the automatic error formatter""" recipe = _MockRecipe( self.crafter, self.tool1, self.tool2, self.cons1, self.cons2, self.cons3 ) @@ -428,7 +428,7 @@ class TestCraftingRecipe(BaseEvenniaTestCase): self.assertIsNotNone(self.tool2.pk) def test_craft_tool_order__fail(self): - """Strict tool-order recipe fail """ + """Strict tool-order recipe fail""" recipe = _MockRecipe( self.crafter, self.tool2, self.tool1, self.cons1, self.cons2, self.cons3 ) @@ -451,7 +451,7 @@ class TestCraftingRecipe(BaseEvenniaTestCase): self.assertIsNotNone(self.tool2.pk) def test_craft_cons_order__fail(self): - """Strict tool-order recipe fail """ + """Strict tool-order recipe fail""" recipe = _MockRecipe( self.crafter, self.tool1, self.tool2, self.cons3, self.cons2, self.cons1 ) @@ -653,7 +653,10 @@ class TestCraftSword(BaseEvenniaTestCase): @mock.patch("evennia.contrib.game_systems.crafting.crafting._load_recipes", new=mock.MagicMock()) -@mock.patch("evennia.contrib.game_systems.crafting.crafting._RECIPE_CLASSES", new={"testrecipe": _MockRecipe}) +@mock.patch( + "evennia.contrib.game_systems.crafting.crafting._RECIPE_CLASSES", + new={"testrecipe": _MockRecipe}, +) @override_settings(CRAFT_RECIPE_MODULES=[]) class TestCraftCommand(BaseEvenniaCommandTest): """Test the crafting command""" diff --git a/evennia/contrib/game_systems/gendersub/tests.py b/evennia/contrib/game_systems/gendersub/tests.py index b01bab163a..3e8a2780d6 100644 --- a/evennia/contrib/game_systems/gendersub/tests.py +++ b/evennia/contrib/game_systems/gendersub/tests.py @@ -22,7 +22,9 @@ class TestGenderSub(BaseEvenniaCommandTest): self.assertEqual( gendersub._RE_GENDER_PRONOUN.sub(char._get_pronoun, txt), "Test their gender" ) - with patch("evennia.contrib.game_systems.gendersub.gendersub.DefaultCharacter.msg") as mock_msg: + with patch( + "evennia.contrib.game_systems.gendersub.gendersub.DefaultCharacter.msg" + ) as mock_msg: char.db.gender = "female" char.msg("Test |p gender") mock_msg.assert_called_with("Test her gender", from_obj=None, session=None) diff --git a/evennia/contrib/game_systems/turnbattle/tb_basic.py b/evennia/contrib/game_systems/turnbattle/tb_basic.py index 240758e357..ba5e5bf679 100644 --- a/evennia/contrib/game_systems/turnbattle/tb_basic.py +++ b/evennia/contrib/game_systems/turnbattle/tb_basic.py @@ -304,6 +304,7 @@ class TBBasicCharacter(DefaultCharacter): A character able to participate in turn-based combat. Has attributes for current and maximum HP, and access to combat commands. """ + rules = COMBAT_RULES def at_object_creation(self): diff --git a/evennia/contrib/game_systems/turnbattle/tb_equip.py b/evennia/contrib/game_systems/turnbattle/tb_equip.py index 62d6bc790f..7ccca4a926 100644 --- a/evennia/contrib/game_systems/turnbattle/tb_equip.py +++ b/evennia/contrib/game_systems/turnbattle/tb_equip.py @@ -243,6 +243,7 @@ class TBEquipTurnHandler(tb_basic.TBBasicTurnHandler): Fights persist until only one participant is left with any HP or all remaining participants choose to end the combat with the 'disengage' command. """ + rules = COMBAT_RULES @@ -258,6 +259,7 @@ class TBEWeapon(DefaultObject): A weapon which can be wielded in combat with the 'wield' command. """ + rules = COMBAT_RULES def at_object_creation(self): @@ -513,8 +515,9 @@ class CmdWield(Command): weapon = self.caller.search(self.args, candidates=self.caller.contents) if not weapon: return - if not weapon.is_typeclass("evennia.contrib.game_systems.turnbattle.tb_equip.TBEWeapon", - exact=True): + if not weapon.is_typeclass( + "evennia.contrib.game_systems.turnbattle.tb_equip.TBEWeapon", exact=True + ): self.caller.msg("That's not a weapon!") # Remember to update the path to the weapon typeclass if you move this module! return @@ -597,8 +600,9 @@ class CmdDon(Command): armor = self.caller.search(self.args, candidates=self.caller.contents) if not armor: return - if not armor.is_typeclass("evennia.contrib.game_systems.turnbattle.tb_equip.TBEArmor", - exact=True): + if not armor.is_typeclass( + "evennia.contrib.game_systems.turnbattle.tb_equip.TBEArmor", exact=True + ): self.caller.msg("That's not armor!") # Remember to update the path to the armor typeclass if you move this module! return diff --git a/evennia/contrib/game_systems/turnbattle/tb_items.py b/evennia/contrib/game_systems/turnbattle/tb_items.py index 74230754dc..d0f52d94e9 100644 --- a/evennia/contrib/game_systems/turnbattle/tb_items.py +++ b/evennia/contrib/game_systems/turnbattle/tb_items.py @@ -104,7 +104,6 @@ COMBAT FUNCTIONS START HERE class ItemCombatRules(tb_basic.BasicCombatRules): - def get_attack(self, attacker, defender): """ Returns a value for an attack roll. @@ -699,7 +698,7 @@ AMULET_OF_MIGHT = { AMULET_OF_WEAKNESS = { "key": "The Amulet of Weakness", "desc": "The one who holds this amulet can call upon its power to gain great weakness. " - "It's not a terribly useful artifact.", + "It's not a terribly useful artifact.", "item_func": "add_condition", "item_selfonly": True, "item_kwargs": {"conditions": [("Damage Down", 3), ("Accuracy Down", 3), ("Defense Down", 3)]}, diff --git a/evennia/contrib/game_systems/turnbattle/tb_magic.py b/evennia/contrib/game_systems/turnbattle/tb_magic.py index 08d87745b4..1be372b81a 100644 --- a/evennia/contrib/game_systems/turnbattle/tb_magic.py +++ b/evennia/contrib/game_systems/turnbattle/tb_magic.py @@ -93,8 +93,8 @@ These functions also all accept **kwargs, and how these are used is specified in the docstring for each function. """ -class MagicCombatRules(tb_basic.BasicCombatRules): +class MagicCombatRules(tb_basic.BasicCombatRules): def spell_healing(self, caster, spell_name, targets, cost, **kwargs): """ Spell that restores HP to a target or targets. @@ -325,11 +325,7 @@ SPELLS = { "attack_name": ("A jet of flame", "jets of flame"), "damage_range": (25, 35), }, - "cure wounds": { - "spellfunc": COMBAT_RULES.spell_healing, - "target": "anychar", - "cost": 5 - }, + "cure wounds": {"spellfunc": COMBAT_RULES.spell_healing, "target": "anychar", "cost": 5}, "mass cure wounds": { "spellfunc": COMBAT_RULES.spell_healing, "target": "anychar", @@ -376,6 +372,7 @@ class TBMagicCharacter(tb_basic.TBBasicCharacter): and maximum HP, access to combat commands and magic. """ + rules = COMBAT_RULES def at_object_creation(self): diff --git a/evennia/contrib/game_systems/turnbattle/tb_range.py b/evennia/contrib/game_systems/turnbattle/tb_range.py index 660598d5de..cc6041c95c 100644 --- a/evennia/contrib/game_systems/turnbattle/tb_range.py +++ b/evennia/contrib/game_systems/turnbattle/tb_range.py @@ -122,7 +122,6 @@ COMBAT FUNCTIONS START HERE class RangedCombatRules(tb_basic.BasicCombatRules): - def get_attack(self, attacker, defender, attack_type): """ Returns a value for an attack roll. @@ -154,7 +153,7 @@ class RangedCombatRules(tb_basic.BasicCombatRules): attack_value -= 15 return attack_value - def get_defense(self, attacker, defender, attack_type='melee'): + def get_defense(self, attacker, defender, attack_type="melee"): """ Returns a value for defense, which an attack roll must equal or exceed in order for an attack to hit. @@ -284,8 +283,9 @@ class RangedCombatRules(tb_basic.BasicCombatRules): if thing != mover and thing != target: # Move away from each object closer to the target than you, if it's also closer to # you than you are to the target. - if (self.get_range(mover, thing) >= self.get_range(target, thing) - and self.get_range(mover, thing) < self.get_range(mover, target)): + if self.get_range(mover, thing) >= self.get_range(target, thing) and self.get_range( + mover, thing + ) < self.get_range(mover, target): self.distance_inc(mover, thing) # Move away from anything your target is engaged with if self.get_range(target, thing) == 0: @@ -296,8 +296,9 @@ class RangedCombatRules(tb_basic.BasicCombatRules): # Then, move away from your target. self.distance_inc(mover, target) - def resolve_attack(self, attacker, defender, attack_value=None, defense_value=None, - attack_type='melee'): + def resolve_attack( + self, attacker, defender, attack_value=None, defense_value=None, attack_type="melee" + ): """ Resolves an attack and outputs the result. @@ -496,6 +497,7 @@ class TBRangeCharacter(tb_basic.TBBasicCharacter): A character able to participate in turn-based combat. Has attributes for current and maximum HP, and access to combat commands. """ + rules = COMBAT_RULES @@ -797,15 +799,17 @@ class CmdShoot(Command): in_melee = [] for target in attacker.db.combat_range: # Object is engaged and has HP - if (self.rules.get_range(attacker, defender) == 0 - and target.db.hp and target != self.caller): + if ( + self.rules.get_range(attacker, defender) == 0 + and target.db.hp + and target != self.caller + ): in_melee.append(target) # Add to list of targets in melee if len(in_melee) > 0: self.caller.msg( "You can't shoot because there are fighters engaged with you (%s) - you need " - "to retreat! (see: help withdraw)" - % ", ".join(obj.key for obj in in_melee) + "to retreat! (see: help withdraw)" % ", ".join(obj.key for obj in in_melee) ) return diff --git a/evennia/contrib/game_systems/turnbattle/tests.py b/evennia/contrib/game_systems/turnbattle/tests.py index 57989965f6..b40289ba9b 100644 --- a/evennia/contrib/game_systems/turnbattle/tests.py +++ b/evennia/contrib/game_systems/turnbattle/tests.py @@ -133,8 +133,9 @@ class TestTurnBattleBasicFunc(BaseEvenniaTest): self.assertTrue(self.defender.db.hp == 7) # Resolve attack self.defender.db.hp = 40 - tb_basic.COMBAT_RULES.resolve_attack(self.attacker, self.defender, - attack_value=20, defense_value=10) + tb_basic.COMBAT_RULES.resolve_attack( + self.attacker, self.defender, attack_value=20, defense_value=10 + ) self.assertTrue(self.defender.db.hp < 40) # Combat cleanup self.attacker.db.Combat_attribute = True @@ -227,7 +228,9 @@ class TestTurnBattleEquipFunc(BaseEvenniaTest): self.assertTrue(self.defender.db.hp == 7) # Resolve attack self.defender.db.hp = 40 - tb_equip.COMBAT_RULES.resolve_attack(self.attacker, self.defender, attack_value=20, defense_value=10) + tb_equip.COMBAT_RULES.resolve_attack( + self.attacker, self.defender, attack_value=20, defense_value=10 + ) self.assertTrue(self.defender.db.hp < 40) # Combat cleanup self.attacker.db.Combat_attribute = True @@ -305,12 +308,14 @@ class TestTurnBattleRangeFunc(BaseEvenniaTest): initiative = tb_range.COMBAT_RULES.roll_init(self.attacker) self.assertTrue(initiative >= 0 and initiative <= 1000) # Attack roll - attack_roll = tb_range.COMBAT_RULES.get_attack(self.attacker, self.defender, - attack_type="test") + attack_roll = tb_range.COMBAT_RULES.get_attack( + self.attacker, self.defender, attack_type="test" + ) self.assertTrue(attack_roll >= 0 and attack_roll <= 100) # Defense roll - defense_roll = tb_range.COMBAT_RULES.get_defense(self.attacker, self.defender, - attack_type="test") + defense_roll = tb_range.COMBAT_RULES.get_defense( + self.attacker, self.defender, attack_type="test" + ) self.assertTrue(defense_roll == 50) # Damage roll damage_roll = tb_range.COMBAT_RULES.get_damage(self.attacker, self.defender) @@ -436,8 +441,9 @@ class TestTurnBattleItemsFunc(BaseEvenniaTest): self.assertTrue(self.defender.db.hp == 7) # Resolve attack self.defender.db.hp = 40 - tb_items.COMBAT_RULES.resolve_attack(self.attacker, self.defender, attack_value=20, - defense_value=10) + tb_items.COMBAT_RULES.resolve_attack( + self.attacker, self.defender, attack_value=20, defense_value=10 + ) self.assertTrue(self.defender.db.hp < 40) # Combat cleanup self.attacker.db.Combat_attribute = True @@ -555,8 +561,9 @@ class TestTurnBattleMagicFunc(BaseEvenniaTest): self.assertTrue(self.defender.db.hp == 7) # Resolve attack self.defender.db.hp = 40 - tb_magic.COMBAT_RULES.resolve_attack(self.attacker, self.defender, attack_value=20, - defense_value=10) + tb_magic.COMBAT_RULES.resolve_attack( + self.attacker, self.defender, attack_value=20, defense_value=10 + ) self.assertTrue(self.defender.db.hp < 40) # Combat cleanup self.attacker.db.Combat_attribute = True diff --git a/evennia/contrib/grid/__init__.py b/evennia/contrib/grid/__init__.py index 46473517bc..8eeb7423d5 100644 --- a/evennia/contrib/grid/__init__.py +++ b/evennia/contrib/grid/__init__.py @@ -2,4 +2,3 @@ Contribs related to moving in and manipulating the game world and grid. """ - diff --git a/evennia/contrib/grid/mapbuilder/tests.py b/evennia/contrib/grid/mapbuilder/tests.py index 2a62072749..fcd6412b92 100644 --- a/evennia/contrib/grid/mapbuilder/tests.py +++ b/evennia/contrib/grid/mapbuilder/tests.py @@ -18,17 +18,18 @@ import random # A map with a temple (â–²) amongst mountains (n,∩) in a forest (♣,â™ ) on an # island surrounded by water (≈). By giving no instructions for the water # characters we effectively skip it and create no rooms for those squares. -EXAMPLE1_MAP = '''\ +EXAMPLE1_MAP = """\ ≈≈≈≈≈ ≈♣n♣≈ ≈∩▲∩≈ ≈♠n♠≈ ≈≈≈≈≈ -''' +""" + def example1_build_forest(x, y, **kwargs): - '''A basic example of build instructions. Make sure to include **kwargs - in the arguments and return an instance of the room for exit generation.''' + """A basic example of build instructions. Make sure to include **kwargs + in the arguments and return an instance of the room for exit generation.""" # Create a room and provide a basic description. room = create_object(rooms.Room, key="forest" + str(x) + str(y)) @@ -42,7 +43,7 @@ def example1_build_forest(x, y, **kwargs): def example1_build_mountains(x, y, **kwargs): - '''A room that is a little more advanced''' + """A room that is a little more advanced""" # Create the room. room = create_object(rooms.Room, key="mountains" + str(x) + str(y)) @@ -68,7 +69,7 @@ def example1_build_mountains(x, y, **kwargs): def example1_build_temple(x, y, **kwargs): - '''A unique room that does not need to be as general''' + """A unique room that does not need to be as general""" # Create the room. room = create_object(rooms.Room, key="temple" + str(x) + str(y)) @@ -115,7 +116,7 @@ EXAMPLE1_LEGEND = { # This is the same layout as Example 1 but included are characters for exits. # We can use these characters to determine which rooms should be connected. -EXAMPLE2_MAP = '''\ +EXAMPLE2_MAP = """\ ≈ ≈ ≈ ≈ ≈ ≈ ♣-♣-♣ ≈ @@ -125,11 +126,11 @@ EXAMPLE2_MAP = '''\ ≈ ♣-♣-♣ ≈ ≈ ≈ ≈ ≈ ≈ -''' +""" def example2_build_forest(x, y, **kwargs): - '''A basic room''' + """A basic room""" # If on anything other than the first iteration - Do nothing. if kwargs["iteration"] > 0: return None @@ -143,7 +144,7 @@ def example2_build_forest(x, y, **kwargs): def example2_build_verticle_exit(x, y, **kwargs): - '''Creates two exits to and from the two rooms north and south.''' + """Creates two exits to and from the two rooms north and south.""" # If on the first iteration - Do nothing. if kwargs["iteration"] == 0: return @@ -164,7 +165,7 @@ def example2_build_verticle_exit(x, y, **kwargs): def example2_build_horizontal_exit(x, y, **kwargs): - '''Creates two exits to and from the two rooms east and west.''' + """Creates two exits to and from the two rooms east and west.""" # If on the first iteration - Do nothing. if kwargs["iteration"] == 0: return diff --git a/evennia/contrib/grid/wilderness/__init__.py b/evennia/contrib/grid/wilderness/__init__.py index ccb0877d88..585c452f17 100644 --- a/evennia/contrib/grid/wilderness/__init__.py +++ b/evennia/contrib/grid/wilderness/__init__.py @@ -5,7 +5,7 @@ Wilderness contrib - titeuf87, 2017 from .wilderness import create_wilderness # noqa from .wilderness import enter_wilderness # noqa -from .wilderness import get_new_coordinates # noqa +from .wilderness import get_new_coordinates # noqa from .wilderness import WildernessScript # noqa from .wilderness import WildernessExit # noqa from .wilderness import WildernessRoom # noqa diff --git a/evennia/contrib/grid/xyzgrid/commands.py b/evennia/contrib/grid/xyzgrid/commands.py index 7a23231076..a670c5ce67 100644 --- a/evennia/contrib/grid/xyzgrid/commands.py +++ b/evennia/contrib/grid/xyzgrid/commands.py @@ -58,6 +58,7 @@ class CmdXYZTeleport(building.CmdTeleport): are given, the target is a location on the XYZGrid. """ + def _search_by_xyz(self, inp): inp = inp.strip("()") X, Y, *Z = inp.split(",", 2) @@ -69,8 +70,10 @@ class CmdXYZTeleport(building.CmdTeleport): try: xyz = self.caller.xyz except AttributeError: - self.caller.msg("Z-coordinate is also required since you are not currently " - "in a room with a Z coordinate of its own.") + self.caller.msg( + "Z-coordinate is also required since you are not currently " + "in a room with a Z coordinate of its own." + ) raise InterruptCommand else: Z = xyz[2] @@ -134,9 +137,11 @@ class CmdXYZOpen(building.CmdOpen): self.location = self.caller.location if not self.args or not self.rhs: - self.caller.msg("Usage: open [;alias...][:typeclass]" - "[,[;alias..][:typeclass]]] " - "= ") + self.caller.msg( + "Usage: open [;alias...][:typeclass]" + "[,[;alias..][:typeclass]]] " + "= " + ) raise InterruptCommand if not self.location: self.caller.msg("You cannot create an exit from a None-location.") @@ -184,6 +189,7 @@ class CmdGoto(COMMAND_DEFAULT_CLASS): Builders can optionally specify a specific grid coordinate (X,Y) to go to. """ + key = "goto" aliases = "path" help_category = "General" @@ -207,11 +213,19 @@ class CmdGoto(COMMAND_DEFAULT_CLASS): def _search_by_key_and_alias(self, inp, xyz_start): Z = xyz_start[2] - candidates = list(XYZRoom.objects.filter_xyz(xyz=('*', '*', Z))) + candidates = list(XYZRoom.objects.filter_xyz(xyz=("*", "*", Z))) return self.caller.search(inp, candidates=candidates) - def _auto_step(self, caller, session, target=None, - xymap=None, directions=None, step_sequence=None, step=True): + def _auto_step( + self, + caller, + session, + target=None, + xymap=None, + directions=None, + step_sequence=None, + step=True, + ): path_data = caller.ndb.xy_path_data @@ -221,8 +235,12 @@ class CmdGoto(COMMAND_DEFAULT_CLASS): # stop any old task in its tracks path_data.task.cancel() path_data = caller.ndb.xy_path_data = PathData( - target=target, xymap=xymap, directions=directions, - step_sequence=step_sequence, task=None) + target=target, + xymap=xymap, + directions=directions, + step_sequence=step_sequence, + task=None, + ) if step and path_data: @@ -285,7 +303,7 @@ class CmdGoto(COMMAND_DEFAULT_CLASS): xymap=path_data.xymap, directions=directions, step_sequence=step_sequence, - task=None + task=None, ) # the map can itself tell the stepper to stop the auto-step prematurely interrupt_node_or_link = None @@ -301,7 +319,8 @@ class CmdGoto(COMMAND_DEFAULT_CLASS): # the exit name does not need to be the same as the cardinal direction! exit_name, *_ = first_link.spawn_aliases.get( - direction, current_node.direction_spawn_defaults.get(direction, ('unknown', ))) + direction, current_node.direction_spawn_defaults.get(direction, ("unknown",)) + ) exit_obj = caller.search(exit_name) if not exit_obj: @@ -315,13 +334,15 @@ class CmdGoto(COMMAND_DEFAULT_CLASS): # premature stop of pathfind-step because of map node/link of interrupt type if hasattr(interrupt_node_or_link, "node_index"): message = exit_obj.destination.attributes.get( - "xyz_path_interrupt_msg", default=self.default_xyz_path_interrupt_msg) + "xyz_path_interrupt_msg", default=self.default_xyz_path_interrupt_msg + ) # we move into the node/room and then stop caller.execute_cmd(exit_name, session=session) else: # if the link is interrupted we don't cross it at all message = exit_obj.attributes.get( - "xyz_path_interrupt_msg", default=self.default_xyz_path_interrupt_msg) + "xyz_path_interrupt_msg", default=self.default_xyz_path_interrupt_msg + ) caller.msg(message) return @@ -335,7 +356,7 @@ class CmdGoto(COMMAND_DEFAULT_CLASS): xymap=path_data.xymap, directions=path_data.directions, step_sequence=path_data.step_sequence, - task=delay(self.auto_step_delay, self._auto_step, caller, session) + task=delay(self.auto_step_delay, self._auto_step, caller, session), ) def func(self): @@ -344,7 +365,7 @@ class CmdGoto(COMMAND_DEFAULT_CLASS): """ caller = self.caller - goto_mode = self.cmdname == 'goto' + goto_mode = self.cmdname == "goto" # check if we have an existing path path_data = caller.ndb.xy_path_data @@ -359,8 +380,7 @@ class CmdGoto(COMMAND_DEFAULT_CLASS): caller.msg(f"Aborted auto-walking to {target_name}.") return # goto/path-command will show current path - current_path = list_to_string( - [f"|w{step}|n" for step in path_data.directions]) + current_path = list_to_string([f"|w{step}|n" for step in path_data.directions]) moving = "(moving)" if task and task.active() else "" caller.msg(f"Path to {target_name}{moving}: {current_path}") else: @@ -405,12 +425,21 @@ class CmdGoto(COMMAND_DEFAULT_CLASS): xy_end = xyz_end[:2] directions, step_sequence = xymap.get_shortest_path(xy_start, xy_end) - caller.msg(f"There are {len(directions)} steps to {target.get_display_name(caller)}: " - f"|w{list_to_string(directions, endsep='|n, and finally|w')}|n") + caller.msg( + f"There are {len(directions)} steps to {target.get_display_name(caller)}: " + f"|w{list_to_string(directions, endsep='|n, and finally|w')}|n" + ) # create data for display and start stepping if we used goto - self._auto_step(caller, self.session, target=target, xymap=xymap, - directions=directions, step_sequence=step_sequence, step=goto_mode) + self._auto_step( + caller, + self.session, + target=target, + xymap=xymap, + directions=directions, + step_sequence=step_sequence, + step=goto_mode, + ) class CmdMap(COMMAND_DEFAULT_CLASS): @@ -424,6 +453,7 @@ class CmdMap(COMMAND_DEFAULT_CLASS): This is a builder-command. """ + key = "map" locks = "cmd:perm(Builders)" @@ -453,8 +483,10 @@ class CmdMap(COMMAND_DEFAULT_CLASS): xymap = xyzgrid.get_map(Z) if not xymap: - self.caller.msg(f"XYMap '{Z}' is not found on the grid. Try 'map list' to see " - "available maps/Zcoords.") + self.caller.msg( + f"XYMap '{Z}' is not found on the grid. Try 'map list' to see " + "available maps/Zcoords." + ) return self.caller.msg(ansi.raw(xymap.mapstring)) @@ -465,6 +497,7 @@ class XYZGridCmdSet(CmdSet): Cmdset for easily adding the above cmds to the character cmdset. """ + key = "xyzgrid_cmdset" def at_cmdset_creation(self): diff --git a/evennia/contrib/grid/xyzgrid/example.py b/evennia/contrib/grid/xyzgrid/example.py index bd9cb8eaf5..f0dbc5cb8d 100644 --- a/evennia/contrib/grid/xyzgrid/example.py +++ b/evennia/contrib/grid/xyzgrid/example.py @@ -81,14 +81,13 @@ class TransitionToCave(xymap_legend.TransitionMapNode): into a room but only acts as a target for finding the exit's destination. """ - symbol = 'T' - target_map_xyz = (1, 0, 'the small cave') + + symbol = "T" + target_map_xyz = (1, 0, "the small cave") # extends the default legend -LEGEND_MAP1 = { - 'T': TransitionToCave -} +LEGEND_MAP1 = {"T": TransitionToCave} # link coordinates to rooms @@ -96,70 +95,62 @@ PROTOTYPES_MAP1 = { # node/room prototypes (3, 0): { "key": "Dungeon Entrance", - "desc": "To the east, a narrow opening leads into darkness." + "desc": "To the east, a narrow opening leads into darkness.", }, (4, 1): { "key": "Under the foilage of a giant tree", "desc": "High above the branches of a giant tree blocks out the sunlight. A slide " - "leading down from the upper branches ends here." + "leading down from the upper branches ends here.", }, (4, 4): { "key": "The slide", - "desc": "A slide leads down to the ground from here. It looks like a one-way trip." + "desc": "A slide leads down to the ground from here. It looks like a one-way trip.", }, (6, 1): { "key": "Thorny path", "desc": "To the east is a pathway of thorns. If you get through, you don't think you'll be " - "able to get back here the same way." - }, - (8, 1): { - "key": "By a large tree", - "desc": "You are standing at the root of a great tree." - }, - (8, 3): { - "key": "At the top of the tree", - "desc": "You are at the top of the tree." + "able to get back here the same way.", }, + (8, 1): {"key": "By a large tree", "desc": "You are standing at the root of a great tree."}, + (8, 3): {"key": "At the top of the tree", "desc": "You are at the top of the tree."}, (3, 7): { "key": "Dense foilage", - "desc": "The foilage to the east is extra dense. It will take forever to get through it." + "desc": "The foilage to the east is extra dense. It will take forever to get through it.", }, (5, 6): { "key": "On a huge branch", - "desc": "To the east is a glowing light, may be a teleporter to a higher branch." + "desc": "To the east is a glowing light, may be a teleporter to a higher branch.", }, (9, 7): { "key": "On an enormous branch", - "desc": "To the west is a glowing light. It may be a teleporter to a lower branch." + "desc": "To the west is a glowing light. It may be a teleporter to a lower branch.", }, (10, 8): { "key": "A gorgeous view", - "desc": "The view from here is breathtaking, showing the forest stretching far and wide." + "desc": "The view from here is breathtaking, showing the forest stretching far and wide.", }, # default rooms - ('*', '*'): { + ("*", "*"): { "key": "Among the branches of a giant tree", - "desc": "These branches are wide enough to easily walk on. There's green all around." + "desc": "These branches are wide enough to easily walk on. There's green all around.", }, # directional prototypes - (3, 0, 'e'): { - "desc": "A dark passage into the underworld." - }, + (3, 0, "e"): {"desc": "A dark passage into the underworld."}, } for key, prot in PROTOTYPES_MAP1.items(): if len(key) == 2: # we don't want to give exits the room typeclass! - prot['prototype_parent'] = ROOM_PARENT + prot["prototype_parent"] = ROOM_PARENT else: - prot['prototype_parent'] = EXIT_PARENT + prot["prototype_parent"] = EXIT_PARENT XYMAP_DATA_MAP1 = { "zcoord": "the large tree", "map": MAP1, "legend": LEGEND_MAP1, - "prototypes": PROTOTYPES_MAP1 + "prototypes": PROTOTYPES_MAP1, } # -------------------------------------- map2 @@ -188,14 +179,13 @@ class TransitionToLargeTree(xymap_legend.TransitionMapNode): into a room by only acts as a target for finding the exit's destination. """ - symbol = 'T' - target_map_xyz = (3, 0, 'the large tree') + + symbol = "T" + target_map_xyz = (3, 0, "the large tree") # this extends the default legend (that defines #,-+ etc) -LEGEND_MAP2 = { - "T": TransitionToLargeTree -} +LEGEND_MAP2 = {"T": TransitionToLargeTree} # prototypes for specific locations PROTOTYPES_MAP2 = { @@ -203,64 +193,54 @@ PROTOTYPES_MAP2 = { (1, 0): { "key": "The entrance", "desc": "This is the entrance to a small cave leading into the ground. " - "Light sifts in from the outside, while cavernous passages disappear " - "into darkness." + "Light sifts in from the outside, while cavernous passages disappear " + "into darkness.", }, (2, 0): { "key": "A gruesome sight.", - "desc": "Something was killed here recently. The smell is unbearable." + "desc": "Something was killed here recently. The smell is unbearable.", }, (1, 1): { "key": "A dark pathway", - "desc": "The path splits three ways here. To the north a faint light can be seen." + "desc": "The path splits three ways here. To the north a faint light can be seen.", }, (3, 2): { "key": "Stagnant water", "desc": "A pool of stagnant, black water dominates this small chamber. To the nortwest " - "a faint light can be seen." - }, - (0, 2): { - "key": "A dark alcove", - "desc": "This alcove is empty." + "a faint light can be seen.", }, + (0, 2): {"key": "A dark alcove", "desc": "This alcove is empty."}, (1, 2): { "key": "South-west corner of the atrium", "desc": "Sunlight sifts down into a large underground chamber. Weeds and grass sprout " - "between the stones." + "between the stones.", }, (2, 2): { "key": "South-east corner of the atrium", "desc": "Sunlight sifts down into a large underground chamber. Weeds and grass sprout " - "between the stones." + "between the stones.", }, (1, 3): { "key": "North-west corner of the atrium", "desc": "Sunlight sifts down into a large underground chamber. Weeds and grass sprout " - "between the stones." + "between the stones.", }, (2, 3): { "key": "North-east corner of the atrium", "desc": "Sunlight sifts down into a large underground chamber. Weeds and grass sprout " - "between the stones. To the east is a dark passage." + "between the stones. To the east is a dark passage.", }, (3, 3): { "key": "Craggy crevice", "desc": "This is the deepest part of the dungeon. The path shrinks away and there " - "is no way to continue deeper." + "is no way to continue deeper.", }, # default fallback for undefined nodes - ('*', '*'): { - "key": "A dark room", - "desc": "A dark, but empty, room." - }, + ("*", "*"): {"key": "A dark room", "desc": "A dark, but empty, room."}, # directional prototypes - (1, 0, 'w'): { - "desc": "A narrow path to the fresh air of the outside world." - }, + (1, 0, "w"): {"desc": "A narrow path to the fresh air of the outside world."}, # directional fallbacks for unset directions - ('*', '*', '*'): { - "desc": "A dark passage" - } + ("*", "*", "*"): {"desc": "A dark passage"}, } # this is required by the prototypes, but we add it all at once so we don't @@ -268,9 +248,9 @@ PROTOTYPES_MAP2 = { for key, prot in PROTOTYPES_MAP2.items(): if len(key) == 2: # we don't want to give exits the room typeclass! - prot['prototype_parent'] = ROOM_PARENT + prot["prototype_parent"] = ROOM_PARENT else: - prot['prototype_parent'] = EXIT_PARENT + prot["prototype_parent"] = EXIT_PARENT XYMAP_DATA_MAP2 = { @@ -278,14 +258,8 @@ XYMAP_DATA_MAP2 = { "zcoord": "the small cave", "legend": LEGEND_MAP2, "prototypes": PROTOTYPES_MAP2, - "options": { - "map_visual_range": 1, - "map_mode": 'scan' - } + "options": {"map_visual_range": 1, "map_mode": "scan"}, } # This is read by the parser -XYMAP_DATA_LIST = [ - XYMAP_DATA_MAP1, - XYMAP_DATA_MAP2 -] +XYMAP_DATA_LIST = [XYMAP_DATA_MAP1, XYMAP_DATA_MAP2] diff --git a/evennia/contrib/grid/xyzgrid/launchcmd.py b/evennia/contrib/grid/xyzgrid/launchcmd.py index 2a5a4df92f..efac48942a 100644 --- a/evennia/contrib/grid/xyzgrid/launchcmd.py +++ b/evennia/contrib/grid/xyzgrid/launchcmd.py @@ -160,11 +160,12 @@ _TOPICS_MAP = { "add": _HELP_ADD, "spawn": _HELP_SPAWN, "initpath": _HELP_INITPATH, - "delete": _HELP_DELETE + "delete": _HELP_DELETE, } evennia._init() + def _option_help(*suboptions): """ Show help aid. @@ -188,6 +189,7 @@ def _option_list(*suboptions): # override grid's logger to echo directly to console def _log(msg): print(msg) + xyzgrid.log = _log xymap_data = xyzgrid.grid @@ -210,7 +212,7 @@ def _option_list(*suboptions): if not xymap: print(f"No XYMap with Z='{zcoord}' was found on grid.") else: - nrooms = xyzgrid.get_room(('*', '*', zcoord)).count() + nrooms = xyzgrid.get_room(("*", "*", zcoord)).count() nnodes = len(xymap.node_index_map) print("\n" + str(repr(xymap)) + ":\n") checkwarning = True @@ -218,22 +220,29 @@ def _option_list(*suboptions): print(f"{nrooms} / {nnodes} rooms are spawned.") checkwarning = False elif nrooms < nnodes: - print(f"{nrooms} / {nnodes} rooms are spawned\n" - "Note: Transitional nodes are *not* spawned (they just point \n" - "to another map), so the 'missing room(s)' may just be from such nodes.") + print( + f"{nrooms} / {nnodes} rooms are spawned\n" + "Note: Transitional nodes are *not* spawned (they just point \n" + "to another map), so the 'missing room(s)' may just be from such nodes." + ) elif nrooms > nnodes: - print(f"{nrooms} / {nnodes} rooms are spawned\n" - "Note: Maybe some rooms were removed from map. Run 'spawn' to re-sync.") + print( + f"{nrooms} / {nnodes} rooms are spawned\n" + "Note: Maybe some rooms were removed from map. Run 'spawn' to re-sync." + ) else: print(f"{nrooms} / {nnodes} rooms are spawned\n") if checkwarning: - print("Note: This check is not complete; it does not consider changed map " - "topology\nlike relocated nodes/rooms and new/removed links/exits - this " - "is calculated only during a spawn.") + print( + "Note: This check is not complete; it does not consider changed map " + "topology\nlike relocated nodes/rooms and new/removed links/exits - this " + "is calculated only during a spawn." + ) print("\nDisplayed map (as appearing in-game):\n\n" + ansi.parse_ansi(str(xymap))) - print("\nRaw map string (including axes and invisible nodes/links):\n" - + str(xymap.mapstring)) + print( + "\nRaw map string (including axes and invisible nodes/links):\n" + str(xymap.mapstring) + ) print(f"\nCustom map options: {xymap.options}\n") legend = [] for key, node_or_link in xymap.legend.items(): @@ -260,6 +269,7 @@ def _option_add(*suboptions): # override grid's logger to echo directly to console def _log(msg): print(msg) + grid.log = _log xymap_data_list = [] @@ -290,35 +300,44 @@ def _option_spawn(*suboptions): # override grid's logger to echo directly to console def _log(msg): print(msg) + grid.log = _log if suboptions: - opts = ''.join(suboptions).strip('()') + opts = "".join(suboptions).strip("()") # coordinate tuple try: x, y, z = (part.strip() for part in opts.split(",")) except ValueError: - print("spawn coordinate must be given as (X, Y, Z) tuple, where '*' act " - "wild cards and Z is the mapname/z-coord of the map to load.") + print( + "spawn coordinate must be given as (X, Y, Z) tuple, where '*' act " + "wild cards and Z is the mapname/z-coord of the map to load." + ) return else: - x, y, z = '*', '*', '*' + x, y, z = "*", "*", "*" - if x == y == z == '*': - inp = input("This will (re)spawn the entire grid. If it was built before, it may spawn \n" - "new rooms or delete rooms that no longer matches the grid.\nDo you want to " - "continue? [Y]/N? ") + if x == y == z == "*": + inp = input( + "This will (re)spawn the entire grid. If it was built before, it may spawn \n" + "new rooms or delete rooms that no longer matches the grid.\nDo you want to " + "continue? [Y]/N? " + ) else: - inp = input("This will spawn/delete objects in the database matching grid coordinates \n" - f"({x},{y},{z}) (where '*' is a wildcard).\nDo you want to continue? [Y]/N? ") - if inp.lower() in ('no', 'n'): + inp = input( + "This will spawn/delete objects in the database matching grid coordinates \n" + f"({x},{y},{z}) (where '*' is a wildcard).\nDo you want to continue? [Y]/N? " + ) + if inp.lower() in ("no", "n"): print("Aborted.") return print("Beginner-Tutorial spawn ...") grid.spawn(xyz=(x, y, z)) - print("... spawn complete!\nIt's recommended to reload the server to refresh caches if this " - "modified an existing grid.") + print( + "... spawn complete!\nIt's recommended to reload the server to refresh caches if this " + "modified an existing grid." + ) def _option_initpath(*suboptions): @@ -331,6 +350,7 @@ def _option_initpath(*suboptions): # override grid's logger to echo directly to console def _log(msg): print(msg) + grid.log = _log xymaps = grid.all_maps() @@ -354,19 +374,24 @@ def _option_delete(*suboptions): # override grid's logger to echo directly to console def _log(msg): print(msg) + grid.log = _log if not suboptions: - repl = input("WARNING: This will delete the ENTIRE Grid and wipe all rooms/exits!" - "\nObjects/Chars inside deleted rooms will be moved to their home locations." - "\nThis can't be undone. Are you sure you want to continue? Y/[N]? ") - if repl.lower() not in ('yes', 'y'): + repl = input( + "WARNING: This will delete the ENTIRE Grid and wipe all rooms/exits!" + "\nObjects/Chars inside deleted rooms will be moved to their home locations." + "\nThis can't be undone. Are you sure you want to continue? Y/[N]? " + ) + if repl.lower() not in ("yes", "y"): print("Aborted.") return print("Deleting grid ...") grid.delete() - print("... done.\nPlease reload the server now; otherwise " - "removed rooms may linger in cache.") + print( + "... done.\nPlease reload the server now; otherwise " + "removed rooms may linger in cache." + ) return zcoords = (part.strip() for part in suboptions) @@ -376,21 +401,24 @@ def _option_delete(*suboptions): print(f"Mapname/zcoord {zcoord} is not a part of the grid.") err = True if err: - print("Valid mapnames/zcoords are\n:", "\n ".join( - xymap.Z for xymap in grid.all_rooms())) + print("Valid mapnames/zcoords are\n:", "\n ".join(xymap.Z for xymap in grid.all_rooms())) return - repl = input("This will delete map(s) {', '.join(zcoords)} and wipe all corresponding\n" - "rooms/exits!" - "\nObjects/Chars inside deleted rooms will be moved to their home locations." - "\nThis can't be undone. Are you sure you want to continue? Y/[N]? ") - if repl.lower() not in ('yes', 'y'): + repl = input( + "This will delete map(s) {', '.join(zcoords)} and wipe all corresponding\n" + "rooms/exits!" + "\nObjects/Chars inside deleted rooms will be moved to their home locations." + "\nThis can't be undone. Are you sure you want to continue? Y/[N]? " + ) + if repl.lower() not in ("yes", "y"): print("Aborted.") return print("Deleting selected xymaps ...") grid.remove_map(*zcoords, remove_objects=True) - print("... done.\nPlease reload the server to refresh room caches." - "\nAlso remember to remove any links from remaining maps pointing to deleted maps.") + print( + "... done.\nPlease reload the server to refresh room caches." + "\nAlso remember to remove any links from remaining maps pointing to deleted maps." + ) def xyzcommand(*args): @@ -405,20 +433,19 @@ def xyzcommand(*args): option, *suboptions = args - if option in ('help', 'h'): + if option in ("help", "h"): _option_help(*suboptions) - if option in ('list', 'show'): + if option in ("list", "show"): _option_list(*suboptions) - elif option == 'init': + elif option == "init": _option_init(*suboptions) - elif option == 'add': + elif option == "add": _option_add(*suboptions) - elif option == 'spawn': + elif option == "spawn": _option_spawn(*suboptions) - elif option == 'initpath': + elif option == "initpath": _option_initpath(*suboptions) - elif option == 'delete': + elif option == "delete": _option_delete(*suboptions) else: print(f"Unknown option '{option}'. Use 'evennia xyzgrid help' for valid arguments.") - diff --git a/evennia/contrib/grid/xyzgrid/prototypes.py b/evennia/contrib/grid/xyzgrid/prototypes.py index 52dc182909..2c0341dc24 100644 --- a/evennia/contrib/grid/xyzgrid/prototypes.py +++ b/evennia/contrib/grid/xyzgrid/prototypes.py @@ -27,19 +27,19 @@ except AttributeError: exit_override = {} room_prototype = { - 'prototype_key': 'xyz_room', - 'typeclass': 'evennia.contrib.grid.xyzgrid.xyzroom.XYZRoom', - 'prototype_tags': ("xyzroom", ), - 'key': "A room", - 'desc': "An empty room." + "prototype_key": "xyz_room", + "typeclass": "evennia.contrib.grid.xyzgrid.xyzroom.XYZRoom", + "prototype_tags": ("xyzroom",), + "key": "A room", + "desc": "An empty room.", } room_prototype.update(room_override) exit_prototype = { - 'prototype_key': 'xyz_exit', - 'typeclass': 'evennia.contrib.grid.xyzgrid.xyzroom.XYZExit', - 'prototype_tags': ("xyzexit", ), - 'desc': "An exit." + "prototype_key": "xyz_exit", + "typeclass": "evennia.contrib.grid.xyzgrid.xyzroom.XYZExit", + "prototype_tags": ("xyzexit",), + "desc": "An exit.", } exit_prototype.update(exit_override) diff --git a/evennia/contrib/grid/xyzgrid/tests.py b/evennia/contrib/grid/xyzgrid/tests.py index 23074b78a3..8e3d727625 100644 --- a/evennia/contrib/grid/xyzgrid/tests.py +++ b/evennia/contrib/grid/xyzgrid/tests.py @@ -345,7 +345,8 @@ class _MapTest(BaseEvenniaTest): Parent for map tests """ - map_data = {'map': MAP1, 'zcoord': "map1"} + + map_data = {"map": MAP1, "zcoord": "map1"} map_display = MAP1_DISPLAY def setUp(self): @@ -353,7 +354,7 @@ class _MapTest(BaseEvenniaTest): super().setUp() self.grid, err = xyzgrid.XYZGrid.create("testgrid") self.grid.add_maps(self.map_data) - self.map = self.grid.get_map(self.map_data['zcoord']) + self.map = self.grid.get_map(self.map_data["zcoord"]) # output to console # def _log(msg): @@ -371,6 +372,7 @@ class TestMap1(_MapTest): Test the Map class with a simple 4-node map """ + def test_str_output(self): """Check the display_map""" self.assertEqual(str(self.map).replace("||", "|").strip(), MAP1_DISPLAY) @@ -384,69 +386,76 @@ class TestMap1(_MapTest): def test_get_shortest_path(self): directions, path = self.map.get_shortest_path((0, 0), (1, 1)) - self.assertEqual(directions, ['e', 'n']) + self.assertEqual(directions, ["e", "n"]) self.assertEqual( [str(node) for node in path], - [str(self.map.node_index_map[0]), - "", - str(self.map.node_index_map[1]), - "", - str(self.map.node_index_map[3])] + [ + str(self.map.node_index_map[0]), + "", + str(self.map.node_index_map[1]), + "", + str(self.map.node_index_map[3]), + ], ) - @parameterized.expand([ - ((0, 0), "| \n#-", [["|", " "], ["#", "-"]]), - ((1, 0), " |\n-#", [[" ", "|"], ["-", "#"]]), - ((0, 1), "#-\n| ", [["#", "-"], ["|", " "]]), - ((1, 1), "-#\n |", [["-", "#"], [" ", "|"]]), - - ]) + @parameterized.expand( + [ + ((0, 0), "| \n#-", [["|", " "], ["#", "-"]]), + ((1, 0), " |\n-#", [[" ", "|"], ["-", "#"]]), + ((0, 1), "#-\n| ", [["#", "-"], ["|", " "]]), + ((1, 1), "-#\n |", [["-", "#"], [" ", "|"]]), + ] + ) def test_get_visual_range__scan(self, coord, expectstr, expectlst): """ Test displaying a part of the map around a central point. """ - mapstr = self.map.get_visual_range(coord, dist=1, mode='scan', character=None) - maplst = self.map.get_visual_range(coord, dist=1, mode='scan', return_str=False, - character=None) + mapstr = self.map.get_visual_range(coord, dist=1, mode="scan", character=None) + maplst = self.map.get_visual_range( + coord, dist=1, mode="scan", return_str=False, character=None + ) maplst = [[part.replace("||", "|") for part in partlst] for partlst in maplst] self.assertEqual(expectstr, mapstr.replace("||", "|")) self.assertEqual(expectlst, maplst[::-1]) - @parameterized.expand([ - ((0, 0), "| \n@-", [["|", " "], ["@", "-"]]), - ((1, 0), " |\n-@", [[" ", "|"], ["-", "@"]]), - ((0, 1), "@-\n| ", [["@", "-"], ["|", " "]]), - ((1, 1), "-@\n |", [["-", "@"], [" ", "|"]]), - - ]) + @parameterized.expand( + [ + ((0, 0), "| \n@-", [["|", " "], ["@", "-"]]), + ((1, 0), " |\n-@", [[" ", "|"], ["-", "@"]]), + ((0, 1), "@-\n| ", [["@", "-"], ["|", " "]]), + ((1, 1), "-@\n |", [["-", "@"], [" ", "|"]]), + ] + ) def test_get_visual_range__scan__character(self, coord, expectstr, expectlst): """ Test displaying a part of the map around a central point, showing the character @-symbol in that spot. """ - mapstr = self.map.get_visual_range(coord, dist=1, mode='scan', character='@') - maplst = self.map.get_visual_range(coord, dist=1, mode='scan', return_str=False, - character='@') + mapstr = self.map.get_visual_range(coord, dist=1, mode="scan", character="@") + maplst = self.map.get_visual_range( + coord, dist=1, mode="scan", return_str=False, character="@" + ) maplst = [[part.replace("||", "|") for part in partlst] for partlst in maplst] self.assertEqual(expectstr, mapstr.replace("||", "|")) self.assertEqual(expectlst, maplst[::-1]) # flip y-axis for print - @parameterized.expand([ - ((0, 0), 1, '# \n| \n@-#'), - ((0, 1), 1, '@-#\n| \n# '), - ((1, 0), 1, ' #\n |\n#-@'), - ((1, 1), 1, '#-@\n |\n #'), - ((0, 0), 2, '#-#\n| |\n@-#'), - - ]) + @parameterized.expand( + [ + ((0, 0), 1, "# \n| \n@-#"), + ((0, 1), 1, "@-#\n| \n# "), + ((1, 0), 1, " #\n |\n#-@"), + ((1, 1), 1, "#-@\n |\n #"), + ((0, 0), 2, "#-#\n| |\n@-#"), + ] + ) def test_get_visual_range__nodes__character(self, coord, dist, expected): """ Get sub-part of map with node-mode. """ - mapstr = self.map.get_visual_range(coord, dist=dist, mode='nodes', character='@') + mapstr = self.map.get_visual_range(coord, dist=dist, mode="nodes", character="@") self.assertEqual(expected, mapstr.replace("||", "|")) def test_spawn(self): @@ -464,14 +473,15 @@ class TestMap2(_MapTest): Test with Map2 - a bigger map with multi-step links """ - map_data = {'map': MAP2, 'zcoord': "map2"} + + map_data = {"map": MAP2, "zcoord": "map2"} map_display = MAP2_DISPLAY def test_str_output(self): """Check the display_map""" # strip the leftover spaces on the right to better # work with text editor stripping this automatically ... - stripped_map = "\n".join(line.rstrip() for line in str(self.map).split('\n')) + stripped_map = "\n".join(line.rstrip() for line in str(self.map).split("\n")) self.assertEqual(stripped_map.replace("||", "|"), MAP2_DISPLAY) def test_node_from_coord(self): @@ -481,15 +491,17 @@ class TestMap2(_MapTest): self.assertEqual(node.x // 2, node.X) self.assertEqual(node.y // 2, node.Y) - @parameterized.expand([ - ((1, 0), (4, 0), ('e', 'e', 'e')), # straight path - ((1, 0), (5, 1), ('n', 'e', 'e', 'e')), # shortcut over long link - ((2, 2), (2, 5), ('n', 'n')), # shortcut over long link (vertical) - ((4, 4), (0, 5), ('w', 'n', 'w', 'w')), # shortcut over long link (vertical) - ((4, 0), (0, 5), ('n', 'w', 'n', 'n', 'n', 'w', 'w')), # across entire grid - ((4, 0), (0, 5), ('n', 'w', 'n', 'n', 'n', 'w', 'w')), # across entire grid - ((5, 3), (0, 3), ('s', 'w', 'w', 'w', 'w', 'n')), # down and back - ]) + @parameterized.expand( + [ + ((1, 0), (4, 0), ("e", "e", "e")), # straight path + ((1, 0), (5, 1), ("n", "e", "e", "e")), # shortcut over long link + ((2, 2), (2, 5), ("n", "n")), # shortcut over long link (vertical) + ((4, 4), (0, 5), ("w", "n", "w", "w")), # shortcut over long link (vertical) + ((4, 0), (0, 5), ("n", "w", "n", "n", "n", "w", "w")), # across entire grid + ((4, 0), (0, 5), ("n", "w", "n", "n", "n", "w", "w")), # across entire grid + ((5, 3), (0, 3), ("s", "w", "w", "w", "w", "n")), # down and back + ] + ) def test_shortest_path(self, startcoord, endcoord, expected_directions): """ Test shortest-path calculations throughout the grid. @@ -498,19 +510,24 @@ class TestMap2(_MapTest): directions, _ = self.map.get_shortest_path(startcoord, endcoord) self.assertEqual(expected_directions, tuple(directions)) - @parameterized.expand([ - ((1, 0), '#-#-#-#\n| | \n#-#-#--\n | \n @-#-#'), - ((2, 2), ' #---#\n | |\n# | #\n| | \n#-#-@-#--\n| ' - '| \n#-#-#---#\n | |\n #-#-#-#'), - ((4, 5), '#-#-@ \n| | \n#---# \n| | \n| #-#'), - ((5, 2), '--# \n | \n #-#\n |\n#---@\n \n--#-#\n | \n#-# '), - ]) + @parameterized.expand( + [ + ((1, 0), "#-#-#-#\n| | \n#-#-#--\n | \n @-#-#"), + ( + (2, 2), + " #---#\n | |\n# | #\n| | \n#-#-@-#--\n| " + "| \n#-#-#---#\n | |\n #-#-#-#", + ), + ((4, 5), "#-#-@ \n| | \n#---# \n| | \n| #-#"), + ((5, 2), "--# \n | \n #-#\n |\n#---@\n \n--#-#\n | \n#-# "), + ] + ) def test_get_visual_range__scan__character(self, coord, expected): """ Test showing smaller part of grid, showing @-character in the middle. """ - mapstr = self.map.get_visual_range(coord, dist=4, mode='scan', character='@') + mapstr = self.map.get_visual_range(coord, dist=4, mode="scan", character="@") self.assertEqual(expected, mapstr.replace("||", "|")) def test_extended_path_tracking__horizontal(self): @@ -520,11 +537,11 @@ class TestMap2(_MapTest): """ node = self.map.get_node_from_coord((4, 1)) self.assertEqual( - {direction: [step.symbol for step in steps] - for direction, steps in node.xy_steps_to_node.items()}, - {'e': ['-'], - 's': ['|'], - 'w': ['-', '-', '-']} + { + direction: [step.symbol for step in steps] + for direction, steps in node.xy_steps_to_node.items() + }, + {"e": ["-"], "s": ["|"], "w": ["-", "-", "-"]}, ) def test_extended_path_tracking__vertical(self): @@ -534,38 +551,46 @@ class TestMap2(_MapTest): """ node = self.map.get_node_from_coord((2, 2)) self.assertEqual( - {direction: [step.symbol for step in steps] - for direction, steps in node.xy_steps_to_node.items()}, - {'n': ['|', '|', '|'], - 'e': ['-'], - 's': ['|'], - 'w': ['-']} + { + direction: [step.symbol for step in steps] + for direction, steps in node.xy_steps_to_node.items() + }, + {"n": ["|", "|", "|"], "e": ["-"], "s": ["|"], "w": ["-"]}, ) - @parameterized.expand([ - ((0, 0), 2, None, '@'), # outside of any known node - ((4, 5), 0, None, '@'), # 0 distance - ((1, 0), 2, None, - '#-#-# \n | \n @-#-#'), - ((0, 5), 1, None, '@-#'), - ((0, 5), 4, None, - '@-#-#-#-#\n | \n #---#\n | \n | \n | \n # '), - ((5, 1), 3, None, ' # \n | \n#-#---#-@\n | \n #-# '), - ((2, 2), 2, None, - ' # \n | \n #---# \n | \n | \n | \n' - '#-#-@-#---#\n | \n #-#---# '), - ((2, 2), 2, (5, 5), # limit display size - ' | \n | \n#-@-#\n | \n#-#--'), - ((2, 2), 4, (3, 3), ' | \n-@-\n | '), - ((2, 2), 4, (1, 1), '@') - ]) + @parameterized.expand( + [ + ((0, 0), 2, None, "@"), # outside of any known node + ((4, 5), 0, None, "@"), # 0 distance + ((1, 0), 2, None, "#-#-# \n | \n @-#-#"), + ((0, 5), 1, None, "@-#"), + ( + (0, 5), + 4, + None, + "@-#-#-#-#\n | \n #---#\n | \n | \n | \n # ", + ), + ((5, 1), 3, None, " # \n | \n#-#---#-@\n | \n #-# "), + ( + (2, 2), + 2, + None, + " # \n | \n #---# \n | \n | \n | \n" + "#-#-@-#---#\n | \n #-#---# ", + ), + ((2, 2), 2, (5, 5), " | \n | \n#-@-#\n | \n#-#--"), # limit display size + ((2, 2), 4, (3, 3), " | \n-@-\n | "), + ((2, 2), 4, (1, 1), "@"), + ] + ) def test_get_visual_range__nodes__character(self, coord, dist, max_size, expected): """ Get sub-part of map with node-mode. """ - mapstr = self.map.get_visual_range(coord, dist=dist, mode='nodes', character='@', - max_size=max_size) + mapstr = self.map.get_visual_range( + coord, dist=dist, mode="nodes", character="@", max_size=max_size + ) self.assertEqual(expected, mapstr.replace("||", "|")) def test_spawn(self): @@ -583,27 +608,30 @@ class TestMap3(_MapTest): Test Map3 - Map with diagonal links """ - map_data = {'map': MAP3, 'zcoord': "map3"} + + map_data = {"map": MAP3, "zcoord": "map3"} map_display = MAP3_DISPLAY def test_str_output(self): """Check the display_map""" - stripped_map = "\n".join(line.rstrip() for line in str(self.map).split('\n')) + stripped_map = "\n".join(line.rstrip() for line in str(self.map).split("\n")) self.assertEqual(MAP3_DISPLAY, stripped_map.replace("||", "|")) - @parameterized.expand([ - ((0, 0), (1, 0), ()), # no node at (1, 0)! - ((2, 0), (5, 0), ('e', 'e')), # straight path - ((0, 0), (1, 1), ('ne', )), - ((4, 1), (4, 3), ('nw', 'ne')), - ((4, 1), (4, 3), ('nw', 'ne')), - ((2, 2), (3, 5), ('nw', 'ne')), - ((2, 2), (1, 5), ('nw', 'n', 'n')), - ((5, 5), (0, 0), ('sw', 's', 'sw', 'w', 'sw', 'sw')), - ((5, 5), (0, 0), ('sw', 's', 'sw', 'w', 'sw', 'sw')), - ((5, 2), (1, 2), ('sw', 'nw', 'w', 'nw', 's')), - ((4, 1), (1, 1), ('s', 'w', 'nw')) - ]) + @parameterized.expand( + [ + ((0, 0), (1, 0), ()), # no node at (1, 0)! + ((2, 0), (5, 0), ("e", "e")), # straight path + ((0, 0), (1, 1), ("ne",)), + ((4, 1), (4, 3), ("nw", "ne")), + ((4, 1), (4, 3), ("nw", "ne")), + ((2, 2), (3, 5), ("nw", "ne")), + ((2, 2), (1, 5), ("nw", "n", "n")), + ((5, 5), (0, 0), ("sw", "s", "sw", "w", "sw", "sw")), + ((5, 5), (0, 0), ("sw", "s", "sw", "w", "sw", "sw")), + ((5, 2), (1, 2), ("sw", "nw", "w", "nw", "s")), + ((4, 1), (1, 1), ("s", "w", "nw")), + ] + ) def test_shortest_path(self, startcoord, endcoord, expected_directions): """ Test shortest-path calculations throughout the grid. @@ -612,19 +640,26 @@ class TestMap3(_MapTest): directions, _ = self.map.get_shortest_path(startcoord, endcoord) self.assertEqual(expected_directions, tuple(directions)) - @parameterized.expand([ - ((2, 2), 2, None, - ' # \n / \n # / \n |/ \n # #\n |\\ / \n # @-# \n ' - '|/ \\ \n # #\n / \\ \n# # '), - ((5, 2), 2, None, ' # \n | \n # \n / \\ \n# @\n \\ / \n # \n | \n # ') - ]) + @parameterized.expand( + [ + ( + (2, 2), + 2, + None, + " # \n / \n # / \n |/ \n # #\n |\\ / \n # @-# \n " + "|/ \\ \n # #\n / \\ \n# # ", + ), + ((5, 2), 2, None, " # \n | \n # \n / \\ \n# @\n \\ / \n # \n | \n # "), + ] + ) def test_get_visual_range__nodes__character(self, coord, dist, max_size, expected): """ Get sub-part of map with node-mode. """ - mapstr = self.map.get_visual_range(coord, dist=dist, mode='nodes', character='@', - max_size=max_size) + mapstr = self.map.get_visual_range( + coord, dist=dist, mode="nodes", character="@", max_size=max_size + ) self.assertEqual(expected, mapstr.replace("||", "|")) def test_spawn(self): @@ -642,22 +677,25 @@ class TestMap4(_MapTest): Test Map4 - Map with + and x crossing links """ - map_data = {'map': MAP4, 'zcoord': "map4"} + + map_data = {"map": MAP4, "zcoord": "map4"} map_display = MAP4_DISPLAY def test_str_output(self): """Check the display_map""" - stripped_map = "\n".join(line.rstrip() for line in str(self.map).split('\n')) + stripped_map = "\n".join(line.rstrip() for line in str(self.map).split("\n")) self.assertEqual(MAP4_DISPLAY, stripped_map.replace("||", "|")) - @parameterized.expand([ - ((1, 0), (1, 2), ('n',)), # cross + vertically - ((0, 1), (2, 1), ('e',)), # cross + horizontally - ((4, 1), (1, 0), ('w', 'w', 'n', 'e', 's')), - ((1, 2), (2, 3), ('ne', )), # cross x - ((1, 2), (2, 3), ('ne', )), - ((2, 2), (0, 4), ('w', 'ne', 'nw', 'w')), - ]) + @parameterized.expand( + [ + ((1, 0), (1, 2), ("n",)), # cross + vertically + ((0, 1), (2, 1), ("e",)), # cross + horizontally + ((4, 1), (1, 0), ("w", "w", "n", "e", "s")), + ((1, 2), (2, 3), ("ne",)), # cross x + ((1, 2), (2, 3), ("ne",)), + ((2, 2), (0, 4), ("w", "ne", "nw", "w")), + ] + ) def test_shortest_path(self, startcoord, endcoord, expected_directions): """ Test shortest-path calculations throughout the grid. @@ -681,20 +719,23 @@ class TestMap5(_MapTest): Test Map5 - Small map with one-way links """ - map_data = {'map': MAP5, 'zcoord': "map5"} + + map_data = {"map": MAP5, "zcoord": "map5"} map_display = MAP5_DISPLAY def test_str_output(self): """Check the display_map""" - stripped_map = "\n".join(line.rstrip() for line in str(self.map).split('\n')) + stripped_map = "\n".join(line.rstrip() for line in str(self.map).split("\n")) self.assertEqual(MAP5_DISPLAY, stripped_map.replace("||", "|")) - @parameterized.expand([ - ((0, 0), (1, 0), ('e',)), # cross one-way - ((1, 0), (0, 0), ()), # blocked - ((0, 1), (1, 1), ('e',)), # should still take shortest - ((1, 1), (0, 1), ('n', 'w', 's')), # take long way around - ]) + @parameterized.expand( + [ + ((0, 0), (1, 0), ("e",)), # cross one-way + ((1, 0), (0, 0), ()), # blocked + ((0, 1), (1, 1), ("e",)), # should still take shortest + ((1, 1), (0, 1), ("n", "w", "s")), # take long way around + ] + ) def test_shortest_path(self, startcoord, endcoord, expected_directions): """ Test shortest-path calculations throughout the grid. @@ -718,24 +759,27 @@ class TestMap6(_MapTest): Test Map6 - Bigger map with one-way links in different directions """ - map_data = {'map': MAP6, 'zcoord': "map6"} + + map_data = {"map": MAP6, "zcoord": "map6"} map_display = MAP6_DISPLAY def test_str_output(self): """Check the display_map""" - stripped_map = "\n".join(line.rstrip() for line in str(self.map).split('\n')) + stripped_map = "\n".join(line.rstrip() for line in str(self.map).split("\n")) self.assertEqual(MAP6_DISPLAY, stripped_map.replace("||", "|")) - @parameterized.expand([ - ((0, 0), (2, 0), ('e', 'e')), # cross one-way - ((2, 0), (0, 0), ('e', 'n', 'w', 's', 'w')), # blocked, long way around - ((4, 0), (3, 0), ('w',)), - ((3, 0), (4, 0), ('n', 'e', 's')), - ((1, 1), (1, 2), ('n',)), - ((1, 2), (1, 1), ('e', 'e', 's', 'w')), - ((3, 1), (1, 4), ('w', 'n', 'n')), - ((0, 4), (0, 0), ('e', 'e', 'e', 's', 's', 's', 'w', 's', 'w')), - ]) + @parameterized.expand( + [ + ((0, 0), (2, 0), ("e", "e")), # cross one-way + ((2, 0), (0, 0), ("e", "n", "w", "s", "w")), # blocked, long way around + ((4, 0), (3, 0), ("w",)), + ((3, 0), (4, 0), ("n", "e", "s")), + ((1, 1), (1, 2), ("n",)), + ((1, 2), (1, 1), ("e", "e", "s", "w")), + ((3, 1), (1, 4), ("w", "n", "n")), + ((0, 4), (0, 0), ("e", "e", "e", "s", "s", "s", "w", "s", "w")), + ] + ) def test_shortest_path(self, startcoord, endcoord, expected_directions): """ Test shortest-path calculations throughout the grid. @@ -759,20 +803,23 @@ class TestMap7(_MapTest): Test Map7 - Small test of dynamic link node """ - map_data = {'map': MAP7, 'zcoord': "map7"} + + map_data = {"map": MAP7, "zcoord": "map7"} map_display = MAP7_DISPLAY def test_str_output(self): """Check the display_map""" - stripped_map = "\n".join(line.rstrip() for line in str(self.map).split('\n')) + stripped_map = "\n".join(line.rstrip() for line in str(self.map).split("\n")) self.assertEqual(MAP7_DISPLAY, stripped_map.replace("||", "|")) - @parameterized.expand([ - ((1, 0), (1, 2), ('n', )), - ((1, 2), (1, 0), ('s', )), - ((0, 1), (2, 1), ('e', )), - ((2, 1), (0, 1), ('w', )), - ]) + @parameterized.expand( + [ + ((1, 0), (1, 2), ("n",)), + ((1, 2), (1, 0), ("s",)), + ((0, 1), (2, 1), ("e",)), + ((2, 1), (0, 1), ("w",)), + ] + ) def test_shortest_path(self, startcoord, endcoord, expected_directions): """ test shortest-path calculations throughout the grid. @@ -796,23 +843,26 @@ class TestMap8(_MapTest): Test Map8 - Small test of dynamic link node """ - map_data = {'map': MAP8, 'zcoord': "map8"} + + map_data = {"map": MAP8, "zcoord": "map8"} map_display = MAP8_DISPLAY def test_str_output(self): """Check the display_map""" - stripped_map = "\n".join(line.rstrip() for line in str(self.map).split('\n')) + stripped_map = "\n".join(line.rstrip() for line in str(self.map).split("\n")) self.assertEqual(MAP8_DISPLAY, stripped_map.replace("||", "|")) - @parameterized.expand([ - ((2, 0), (2, 2), ('n',)), - ((0, 0), (5, 3), ('e', 'e')), - ((5, 1), (0, 3), ('w', 'w', 'n', 'w')), - ((1, 1), (2, 2), ('n', 'w', 's')), - ((5, 3), (5, 3), ()), - ((5, 3), (0, 4), ('s', 'n', 'w', 'n')), - ((1, 4), (3, 3), ('e', 'w', 'e')), - ]) + @parameterized.expand( + [ + ((2, 0), (2, 2), ("n",)), + ((0, 0), (5, 3), ("e", "e")), + ((5, 1), (0, 3), ("w", "w", "n", "w")), + ((1, 1), (2, 2), ("n", "w", "s")), + ((5, 3), (5, 3), ()), + ((5, 3), (0, 4), ("s", "n", "w", "n")), + ((1, 4), (3, 3), ("e", "w", "e")), + ] + ) def test_shortest_path(self, startcoord, endcoord, expected_directions): """ test shortest-path calculations throughout the grid. @@ -821,39 +871,78 @@ class TestMap8(_MapTest): directions, _ = self.map.get_shortest_path(startcoord, endcoord) self.assertEqual(expected_directions, tuple(directions)) - @parameterized.expand([ - ((2, 2), 1, None, ' #-o \n | \n# o \n| | \no-o-@-#\n ' - '| \n o \n | \n # '), - ]) + @parameterized.expand( + [ + ( + (2, 2), + 1, + None, + " #-o \n | \n# o \n| | \no-o-@-#\n " + "| \n o \n | \n # ", + ), + ] + ) def test_get_visual_range__nodes__character(self, coord, dist, max_size, expected): """ Get sub-part of map with node-mode. """ - mapstr = self.map.get_visual_range(coord, dist=dist, mode='nodes', character='@', - max_size=max_size) + mapstr = self.map.get_visual_range( + coord, dist=dist, mode="nodes", character="@", max_size=max_size + ) self.assertEqual(expected, mapstr.replace("||", "|")) - @parameterized.expand([ - ((2, 2), (3, 2), 1, None, ' #-o \n | \n# o \n| | \no-o-@..\n | \n o ' - '\n | \n # '), - ((2, 2), (5, 3), 1, None, ' #-o \n | \n# o \n| | \no-o-@-#\n . \n . ' - '\n . \n ...'), - ((2, 2), (5, 3), 2, None, '#-#-o \n| \\| \n#-o-o-# .\n| |\\ .\no-o-@-' - '# .\n . . \n . . \n . . \n#---... '), - ((5, 3), (2, 2), 2, (13, 7), ' o-o\n | |\n o-@\n .\n. .\n. . '), - ((5, 3), (1, 1), 2, None, ' o-o\n | |\n o-@\n. .\n..... ' - '.\n . . \n . . \n . . \n#---... ') - ]) + @parameterized.expand( + [ + ( + (2, 2), + (3, 2), + 1, + None, + " #-o \n | \n# o \n| | \no-o-@..\n | \n o " + "\n | \n # ", + ), + ( + (2, 2), + (5, 3), + 1, + None, + " #-o \n | \n# o \n| | \no-o-@-#\n . \n . " + "\n . \n ...", + ), + ( + (2, 2), + (5, 3), + 2, + None, + "#-#-o \n| \\| \n#-o-o-# .\n| |\\ .\no-o-@-" + "# .\n . . \n . . \n . . \n#---... ", + ), + ((5, 3), (2, 2), 2, (13, 7), " o-o\n | |\n o-@\n .\n. .\n. . "), + ( + (5, 3), + (1, 1), + 2, + None, + " o-o\n | |\n o-@\n. .\n..... " + ".\n . . \n . . \n . . \n#---... ", + ), + ] + ) def test_get_visual_range_with_path(self, coord, target, dist, max_size, expected): """ Get visual range with a path-to-target marked. """ - mapstr = self.map.get_visual_range(coord, dist=dist, mode='nodes', - target=target, target_path_style=".", - character='@', - max_size=max_size) + mapstr = self.map.get_visual_range( + coord, + dist=dist, + mode="nodes", + target=target, + target_path_style=".", + character="@", + max_size=max_size, + ) self.assertEqual(expected, mapstr.replace("||", "|")) def test_spawn(self): @@ -871,20 +960,23 @@ class TestMap9(_MapTest): Test Map9 - a map with up/down links. """ - map_data = {'map': MAP9, 'zcoord': "map9"} + + map_data = {"map": MAP9, "zcoord": "map9"} map_display = MAP9_DISPLAY def test_str_output(self): """Check the display_map""" - stripped_map = "\n".join(line.rstrip() for line in str(self.map).split('\n')) + stripped_map = "\n".join(line.rstrip() for line in str(self.map).split("\n")) self.assertEqual(MAP9_DISPLAY, stripped_map.replace("||", "|")) - @parameterized.expand([ - ((0, 0), (0, 1), ('u',)), - ((0, 0), (1, 0), ('d',)), - ((1, 0), (2, 1), ('d', 'u', 'e', 'u', 'e', 'd')), - ((2, 1), (0, 1), ('u', 'w', 'd', 'w')), - ]) + @parameterized.expand( + [ + ((0, 0), (0, 1), ("u",)), + ((0, 0), (1, 0), ("d",)), + ((1, 0), (2, 1), ("d", "u", "e", "u", "e", "d")), + ((2, 1), (0, 1), ("u", "w", "d", "w")), + ] + ) def test_shortest_path(self, startcoord, endcoord, expected_directions): """ test shortest-path calculations throughout the grid. @@ -909,27 +1001,30 @@ class TestMap10(_MapTest): 'invisible' nodes and won't show up in the map display. """ - map_data = {'map': MAP10, 'zcoord': "map10"} + + map_data = {"map": MAP10, "zcoord": "map10"} map_display = MAP10_DISPLAY def test_str_output(self): """Check the display_map""" - stripped_map = "\n".join(line.rstrip() for line in str(self.map).split('\n')) + stripped_map = "\n".join(line.rstrip() for line in str(self.map).split("\n")) self.assertEqual(MAP10_DISPLAY, stripped_map.replace("||", "|")) # interrupts are only relevant to the auto-stepper - @parameterized.expand([ - ((0, 0), (1, 0), ('n', 'e', 's')), - ((3, 0), (3, 1), ()), # the blockage hinders this - ((1, 3), (0, 4), ('e', 'n', 'w', 'w')), - ((0, 1), (3, 2), ('e', 'n', 'e', 'e')), - ((0, 1), (0, 3), ('e', 'n', 'n', 'w')), - ((1, 3), (0, 3), ('w',)), - ((3, 2), (2, 2), ('w',)), - ((3, 2), (1, 2), ('w', 'w')), - ((3, 3), (0, 3), ('w', 'w')), - ((2, 2), (3, 2), ('e',)), - ]) + @parameterized.expand( + [ + ((0, 0), (1, 0), ("n", "e", "s")), + ((3, 0), (3, 1), ()), # the blockage hinders this + ((1, 3), (0, 4), ("e", "n", "w", "w")), + ((0, 1), (3, 2), ("e", "n", "e", "e")), + ((0, 1), (0, 3), ("e", "n", "n", "w")), + ((1, 3), (0, 3), ("w",)), + ((3, 2), (2, 2), ("w",)), + ((3, 2), (1, 2), ("w", "w")), + ((3, 3), (0, 3), ("w", "w")), + ((2, 2), (3, 2), ("e",)), + ] + ) def test_shortest_path(self, startcoord, endcoord, expected_directions): """ test shortest-path calculations throughout the grid. @@ -938,11 +1033,17 @@ class TestMap10(_MapTest): directions, _ = self.map.get_shortest_path(startcoord, endcoord) self.assertEqual(expected_directions, tuple(directions)) - @parameterized.expand([ - ((2, 2), (3, 2), ('e', ), ((2, 2), (2.5, 2), (3, 2))), - ((3, 3), (0, 3), ('w', 'w'), ((3, 3), (2.5, 3.0), (2.0, 3.0), - (1.5, 3.0), (1, 3), (0.5, 3), (0, 3))), - ]) + @parameterized.expand( + [ + ((2, 2), (3, 2), ("e",), ((2, 2), (2.5, 2), (3, 2))), + ( + (3, 3), + (0, 3), + ("w", "w"), + ((3, 3), (2.5, 3.0), (2.0, 3.0), (1.5, 3.0), (1, 3), (0.5, 3), (0, 3)), + ), + ] + ) def test_paths(self, startcoord, endcoord, expected_directions, expected_path): """ Test path locations. @@ -968,18 +1069,21 @@ class TestMap11(_MapTest): Test Map11 - a map teleporter links. """ - map_data = {'map': MAP11, 'zcoord': "map11"} + + map_data = {"map": MAP11, "zcoord": "map11"} map_display = MAP11_DISPLAY def test_str_output(self): """Check the display_map""" - stripped_map = "\n".join(line.rstrip() for line in str(self.map).split('\n')) + stripped_map = "\n".join(line.rstrip() for line in str(self.map).split("\n")) self.assertEqual(MAP11_DISPLAY, stripped_map.replace("||", "|")) - @parameterized.expand([ - ((2, 0), (1, 2), ('e', 'nw', 'e')), - ((1, 2), (2, 0), ('w', 'se', 'w')), - ]) + @parameterized.expand( + [ + ((2, 0), (1, 2), ("e", "nw", "e")), + ((1, 2), (2, 0), ("w", "se", "w")), + ] + ) def test_shortest_path(self, startcoord, endcoord, expected_directions): """ test shortest-path calculations throughout the grid. @@ -988,12 +1092,22 @@ class TestMap11(_MapTest): directions, _ = self.map.get_shortest_path(startcoord, endcoord) self.assertEqual(expected_directions, tuple(directions)) - @parameterized.expand([ - ((3, 0), (0, 2), ('nw', ), - ((3, 0), (2.5, 0.5), (2.0, 1.0), (1.0, 1.0), (0.5, 1.5), (0, 2))), - ((0, 2), (3, 0), ('se', ), - ((0, 2), (0.5, 1.5), (1.0, 1.0), (2.0, 1.0), (2.5, 0.5), (3, 0))), - ]) + @parameterized.expand( + [ + ( + (3, 0), + (0, 2), + ("nw",), + ((3, 0), (2.5, 0.5), (2.0, 1.0), (1.0, 1.0), (0.5, 1.5), (0, 2)), + ), + ( + (0, 2), + (3, 0), + ("se",), + ((0, 2), (0.5, 1.5), (1.0, 1.0), (2.0, 1.0), (2.5, 0.5), (3, 0)), + ), + ] + ) def test_paths(self, startcoord, endcoord, expected_directions, expected_path): """ Test path locations. @@ -1004,20 +1118,26 @@ class TestMap11(_MapTest): strpositions = [(step.X, step.Y) for step in path] self.assertEqual(expected_path, tuple(strpositions)) - @parameterized.expand([ - ((2, 0), (1, 2), 3, None, '... \n . \n . . \n . \n @..'), - ((1, 2), (2, 0), 3, None, '..@ \n . \n . . \n . \n ...'), - - ]) + @parameterized.expand( + [ + ((2, 0), (1, 2), 3, None, "... \n . \n . . \n . \n @.."), + ((1, 2), (2, 0), 3, None, "..@ \n . \n . . \n . \n ..."), + ] + ) def test_get_visual_range_with_path(self, coord, target, dist, max_size, expected): """ Get visual range with a path-to-target marked. """ - mapstr = self.map.get_visual_range(coord, dist=dist, mode='nodes', - target=target, target_path_style=".", - character='@', - max_size=max_size) + mapstr = self.map.get_visual_range( + coord, + dist=dist, + mode="nodes", + target=target, + target_path_style=".", + character="@", + max_size=max_size, + ) self.assertEqual(expected, mapstr) @@ -1056,10 +1176,12 @@ class TestMapStressTest(TestCase): return f"{edge}\n{(l1 + l2) * Ysize}{l1}\n\n{edge}" - @parameterized.expand([ - ((10, 10), 0.03), - ((100, 100), 5), - ]) + @parameterized.expand( + [ + ((10, 10), 0.03), + ((100, 100), 5), + ] + ) def test_grid_creation(self, gridsize, max_time): """ Test of grid-creataion performance for Nx, Ny grid. @@ -1068,7 +1190,7 @@ class TestMapStressTest(TestCase): # import cProfile Xmax, Ymax = gridsize grid = self._get_grid(Xmax, Ymax) - mapobj = xymap.XYMap({'map': grid}, Z="testmap") + mapobj = xymap.XYMap({"map": grid}, Z="testmap") # t0 = time() mapobj.parse() # cProfile.runctx('mapobj.parse()', globals(), locals()) @@ -1077,10 +1199,12 @@ class TestMapStressTest(TestCase): # print(f"Map creation of ({Xmax}x{Ymax}) grid slower " # f"than expected {max_time}s.") - @parameterized.expand([ - ((10, 10), 10**-3), - ((20, 20), 10**-3), - ]) + @parameterized.expand( + [ + ((10, 10), 10**-3), + ((20, 20), 10**-3), + ] + ) def test_grid_pathfind(self, gridsize, max_time): """ Test pathfinding performance for Nx, Ny grid. @@ -1088,7 +1212,7 @@ class TestMapStressTest(TestCase): """ Xmax, Ymax = gridsize grid = self._get_grid(Xmax, Ymax) - mapobj = xymap.XYMap({'map': grid}, Z="testmap") + mapobj = xymap.XYMap({"map": grid}, Z="testmap") mapobj.parse() # t0 = time() @@ -1097,10 +1221,11 @@ class TestMapStressTest(TestCase): # print(f"pathfinder matrix for grid {Xmax}x{Ymax}: {t1 - t0}s") # get the maximum distance and 9 other random points in the grid - start_end_points = [((0, 0), (Xmax-1, Ymax-1))] + start_end_points = [((0, 0), (Xmax - 1, Ymax - 1))] for _ in range(9): - start_end_points.append(((randint(0, Xmax), randint(0, Ymax)), - (randint(0, Xmax), randint(0, Ymax)))) + start_end_points.append( + ((randint(0, Xmax), randint(0, Ymax)), (randint(0, Xmax), randint(0, Ymax))) + ) # t0 = time() for startcoord, endcoord in start_end_points: @@ -1110,10 +1235,12 @@ class TestMapStressTest(TestCase): # print(f"Pathfinding for ({Xmax}x{Ymax}) grid slower " # f"than expected {max_time}s.") - @parameterized.expand([ - ((10, 10), 4, 0.01), - ((20, 20), 4, 0.01), - ]) + @parameterized.expand( + [ + ((10, 10), 4, 0.01), + ((20, 20), 4, 0.01), + ] + ) def test_grid_visibility(self, gridsize, dist, max_time): """ Test grid visualization performance for Nx, Ny grid for @@ -1122,7 +1249,7 @@ class TestMapStressTest(TestCase): """ Xmax, Ymax = gridsize grid = self._get_grid(Xmax, Ymax) - mapobj = xymap.XYMap({'map': grid}, Z="testmap") + mapobj = xymap.XYMap({"map": grid}, Z="testmap") mapobj.parse() # t0 = time() @@ -1132,15 +1259,15 @@ class TestMapStressTest(TestCase): # get random center points in grid and a range of targets to visualize the # path to - start_end_points = [((0, 0), (Xmax-1, Ymax-1))] # include max distance + start_end_points = [((0, 0), (Xmax - 1, Ymax - 1))] # include max distance for _ in range(9): - start_end_points.append(((randint(0, Xmax), randint(0, Ymax)), - (randint(0, Xmax), randint(0, Ymax)))) + start_end_points.append( + ((randint(0, Xmax), randint(0, Ymax)), (randint(0, Xmax), randint(0, Ymax))) + ) # t0 = time() for coord, target in start_end_points: - mapobj.get_visual_range(coord, dist=dist, mode='nodes', - character='@', target=target) + mapobj.get_visual_range(coord, dist=dist, mode="nodes", character="@", target=target) # t1 = time() # if (t1 - t0) / 10 > max_time: # print(f"Visual Range calculation for ({Xmax}x{Ymax}) grid " @@ -1158,10 +1285,7 @@ class TestXYZGrid(BaseEvenniaTest): def setUp(self): self.grid, err = xyzgrid.XYZGrid.create("testgrid") - self.map_data1 = { - "map": MAP1, - "zcoord": self.zcoord - } + self.map_data1 = {"map": MAP1, "zcoord": self.zcoord} self.grid.add_maps(self.map_data1) @@ -1171,7 +1295,7 @@ class TestXYZGrid(BaseEvenniaTest): def test_str_output(self): """Check the display_map""" xymap = self.grid.get_map(self.zcoord) - stripped_map = "\n".join(line.rstrip() for line in str(xymap).split('\n')) + stripped_map = "\n".join(line.rstrip() for line in str(xymap).split("\n")) self.assertEqual(MAP1_DISPLAY, stripped_map.replace("||", "|")) def test_spawn(self): @@ -1201,37 +1325,31 @@ class TestXYZGridTransition(BaseEvenniaTest): Test the XYZGrid class and transitions between maps. """ + def setUp(self): super().setUp() self.grid, err = xyzgrid.XYZGrid.create("testgrid") - self.map_data12a = { - "map": MAP12a, - "zcoord": "map12a", - "legend": {"T": Map12aTransition} - } - self.map_data12b = { - "map": MAP12b, - "zcoord": "map12b", - "legend": {"T": Map12bTransition} - - } + self.map_data12a = {"map": MAP12a, "zcoord": "map12a", "legend": {"T": Map12aTransition}} + self.map_data12b = {"map": MAP12b, "zcoord": "map12b", "legend": {"T": Map12bTransition}} self.grid.add_maps(self.map_data12a, self.map_data12b) def tearDown(self): self.grid.delete() - @parameterized.expand([ - ((1, 0), (1, 1), ('w', 'n', 'e')), - ((1, 1), (1, 0), ('w', 's', 'e')), - ]) + @parameterized.expand( + [ + ((1, 0), (1, 1), ("w", "n", "e")), + ((1, 1), (1, 0), ("w", "s", "e")), + ] + ) def test_shortest_path(self, startcoord, endcoord, expected_directions): """ test shortest-path calculations throughout the grid. """ - directions, _ = self.grid.get_map('map12a').get_shortest_path(startcoord, endcoord) + directions, _ = self.grid.get_map("map12a").get_shortest_path(startcoord, endcoord) self.assertEqual(expected_directions, tuple(directions)) def test_spawn(self): @@ -1244,10 +1362,10 @@ class TestXYZGridTransition(BaseEvenniaTest): self.assertEqual(xyzroom.XYZRoom.objects.all().count(), 6) self.assertEqual(xyzroom.XYZExit.objects.all().count(), 10) - room1 = xyzroom.XYZRoom.objects.get_xyz(xyz=(0, 1, 'map12a')) - room2 = xyzroom.XYZRoom.objects.get_xyz(xyz=(1, 0, 'map12b')) - east_exit = [exi for exi in room1.exits if exi.db_key == 'east'][0] - west_exit = [exi for exi in room2.exits if exi.db_key == 'west'][0] + room1 = xyzroom.XYZRoom.objects.get_xyz(xyz=(0, 1, "map12a")) + room2 = xyzroom.XYZRoom.objects.get_xyz(xyz=(1, 0, "map12b")) + east_exit = [exi for exi in room1.exits if exi.db_key == "east"][0] + west_exit = [exi for exi in room2.exits if exi.db_key == "west"][0] # make sure exits traverse the maps self.assertEqual(east_exit.db_destination, room2) @@ -1259,6 +1377,7 @@ class TestBuildExampleGrid(BaseEvenniaTest): Test building the map-example (this takes about 30s) """ + def setUp(self): # build and populate grid super().setUp() @@ -1283,10 +1402,10 @@ class TestBuildExampleGrid(BaseEvenniaTest): self.grid.spawn() # testing - room1a = xyzroom.XYZRoom.objects.get_xyz(xyz=(3, 0, 'the large tree')) - room1b = xyzroom.XYZRoom.objects.get_xyz(xyz=(10, 8, 'the large tree')) - room2a = xyzroom.XYZRoom.objects.get_xyz(xyz=(1, 0, 'the small cave')) - room2b = xyzroom.XYZRoom.objects.get_xyz(xyz=(1, 3, 'the small cave')) + room1a = xyzroom.XYZRoom.objects.get_xyz(xyz=(3, 0, "the large tree")) + room1b = xyzroom.XYZRoom.objects.get_xyz(xyz=(10, 8, "the large tree")) + room2a = xyzroom.XYZRoom.objects.get_xyz(xyz=(1, 0, "the small cave")) + room2b = xyzroom.XYZRoom.objects.get_xyz(xyz=(1, 3, "the small cave")) self.assertEqual(room1a.key, "Dungeon Entrance") self.assertTrue(room1a.db.desc.startswith("To the east")) diff --git a/evennia/contrib/grid/xyzgrid/utils.py b/evennia/contrib/grid/xyzgrid/utils.py index 04b6fe7b71..b8dc405101 100644 --- a/evennia/contrib/grid/xyzgrid/utils.py +++ b/evennia/contrib/grid/xyzgrid/utils.py @@ -30,13 +30,15 @@ MAPSCAN = { # errors for Map system -class MapError(RuntimeError): +class MapError(RuntimeError): def __init__(self, error="", node_or_link=None): prefix = "" if node_or_link: - prefix = (f"{node_or_link.__class__.__name__} '{node_or_link.symbol}' " - f"at XYZ=({node_or_link.X:g},{node_or_link.Y:g},{node_or_link.Z}) ") + prefix = ( + f"{node_or_link.__class__.__name__} '{node_or_link.symbol}' " + f"at XYZ=({node_or_link.X:g},{node_or_link.Y:g},{node_or_link.Z}) " + ) self.node_or_link = node_or_link self.message = f"{prefix}{error}" super().__init__(self.message) @@ -52,4 +54,5 @@ class MapTransition(RuntimeWarning): leads to another map. """ + pass diff --git a/evennia/contrib/grid/xyzgrid/xymap.py b/evennia/contrib/grid/xyzgrid/xymap.py index 13825a564b..2583e0aec3 100644 --- a/evennia/contrib/grid/xyzgrid/xymap.py +++ b/evennia/contrib/grid/xyzgrid/xymap.py @@ -104,7 +104,8 @@ try: except ImportError as err: raise ImportError( f"{err}\nThe XYZgrid contrib requires " - "the SciPy package. Install with `pip install scipy'.") + "the SciPy package. Install with `pip install scipy'." + ) from django.conf import settings from evennia.utils.utils import variable_from_module, mod_import, is_iter from evennia.utils import logger @@ -122,9 +123,7 @@ _CACHE_DIR = settings.CACHE_DIR _LOADED_PROTOTYPES = None _XYZROOMCLASS = None -MAP_DATA_KEYS = [ - "zcoord", "map", "legend", "prototypes", "options", "module_path" -] +MAP_DATA_KEYS = ["zcoord", "map", "legend", "prototypes", "options", "module_path"] DEFAULT_LEGEND = xymap_legend.LEGEND @@ -172,11 +171,11 @@ class XYMap: but recommended for readability! """ - mapcorner_symbol = '+' + mapcorner_symbol = "+" max_pathfinding_length = 500 - empty_symbol = ' ' + empty_symbol = " " # we normally only accept one single character for the legend key - legend_key_exceptions = ("\\") + legend_key_exceptions = "\\" def __init__(self, map_module_or_dict, Z="map", xyzgrid=None): """ @@ -210,7 +209,9 @@ class XYMap: if not _LOADED_PROTOTYPES: # inject default prototypes, but don't override prototype-keys loaded from # settings, if they exist (that means the user wants to replace the defaults) - protlib.load_module_prototypes("evennia.contrib.grid.xyzgrid.prototypes", override=False) + protlib.load_module_prototypes( + "evennia.contrib.grid.xyzgrid.prototypes", override=False + ) _LOADED_PROTOTYPES = True self.Z = Z @@ -264,7 +265,7 @@ class XYMap: nnodes = 0 if self.node_index_map: nnodes = len(self.node_index_map) - return (f"") + return f"" def log(self, msg): if self.xyzgrid: @@ -317,34 +318,41 @@ class XYMap: mapdata = variable_from_module(mod, "XYMAP_DATA") if not mapdata: - raise MapError("No valid XYMAP_DATA or XYMAP_DATA_LIST could be found from " - f"{map_module_or_dict}.") + raise MapError( + "No valid XYMAP_DATA or XYMAP_DATA_LIST could be found from " + f"{map_module_or_dict}." + ) # validate if any(key for key in mapdata if key not in MAP_DATA_KEYS): - raise MapError(f"Mapdata has keys {list(mapdata)}, but only " - f"keys {MAP_DATA_KEYS} are allowed.") + raise MapError( + f"Mapdata has keys {list(mapdata)}, but only " f"keys {MAP_DATA_KEYS} are allowed." + ) - for key in mapdata.get('legend', DEFAULT_LEGEND): + for key in mapdata.get("legend", DEFAULT_LEGEND): if not key or len(key) > 1: if key not in self.legend_key_exceptions: - raise MapError(f"Map-legend key '{key}' is invalid: All keys must " - "be exactly one character long. Use the node/link's " - "`.display_symbol` property to change how it is " - "displayed.") - if 'map' not in mapdata or not mapdata['map']: + raise MapError( + f"Map-legend key '{key}' is invalid: All keys must " + "be exactly one character long. Use the node/link's " + "`.display_symbol` property to change how it is " + "displayed." + ) + if "map" not in mapdata or not mapdata["map"]: raise MapError("No map found. Add 'map' key to map-data dict.") - for key, prototype in mapdata.get('prototypes', {}).items(): + for key, prototype in mapdata.get("prototypes", {}).items(): if not (is_iter(key) and (2 <= len(key) <= 3)): - raise MapError(f"Prototype override key {key} is malformed: It must be a " - "coordinate (X, Y) for nodes or (X, Y, direction) for links; " - "where direction is a supported direction string ('n', 'ne', etc).") + raise MapError( + f"Prototype override key {key} is malformed: It must be a " + "coordinate (X, Y) for nodes or (X, Y, direction) for links; " + "where direction is a supported direction string ('n', 'ne', etc)." + ) # store/update result - self.Z = mapdata.get('zcoord', self.Z) - self.mapstring = mapdata['map'] - self.prototypes = mapdata.get('prototypes', {}) - self.options = mapdata.get('options', {}) + self.Z = mapdata.get("zcoord", self.Z) + self.mapstring = mapdata["map"] + self.prototypes = mapdata.get("prototypes", {}) + self.options = mapdata.get("options", {}) # merge the custom legend onto the default legend to allow easily # overriding only parts of it @@ -357,8 +365,9 @@ class XYMap: # nothing more to do continue # we need to load the prototype dict onto each for ease of access. Note that - proto = protlib.search_prototype(prototype, require_single=True, - no_db=_NO_DB_PROTOTYPES)[0] + proto = protlib.search_prototype( + prototype, require_single=True, no_db=_NO_DB_PROTOTYPES + )[0] node_or_link_class.prototype = proto def parse(self): @@ -391,7 +400,8 @@ class XYMap: raise MapParserError( f"The mapstring must have at least two '{mapcorner_symbol}' " "symbols marking the upper- and bottom-left corners of the " - "grid area.") + "grid area." + ) # find the the position (in the string as a whole) of the top-left corner-marker maplines = mapstring.split("\n") @@ -406,13 +416,15 @@ class XYMap: # find the position (in the string as a whole) of the bottom-left corner-marker # this is always in a stright line down from the first marker botleft_marker_x, botleft_marker_y = topleft_marker_x, -1 - for botleft_marker_y, line in enumerate(maplines[topleft_marker_y + 1:]): + for botleft_marker_y, line in enumerate(maplines[topleft_marker_y + 1 :]): if line.find(mapcorner_symbol) == topleft_marker_x: break if botleft_marker_y == -1: - raise MapParserError(f"No bottom-left corner-marker ({mapcorner_symbol}) found! " - "Make sure it lines up with the top-left corner-marker " - f"(found at column {topleft_marker_x} of the string).") + raise MapParserError( + f"No bottom-left corner-marker ({mapcorner_symbol}) found! " + "Make sure it lines up with the top-left corner-marker " + f"(found at column {topleft_marker_x} of the string)." + ) # the actual coordinate is dy below the topleft marker so we need to shift botleft_marker_y += topleft_marker_y + 1 @@ -443,8 +455,7 @@ class XYMap: mapnode_or_link_class = self.legend.get(char) if not mapnode_or_link_class: raise MapParserError( - f"Symbol '{char}' on XY=({ix / 2:g},{iy / 2:g}) " - "is not found in LEGEND." + f"Symbol '{char}' on XY=({ix / 2:g},{iy / 2:g}) " "is not found in LEGEND." ) if hasattr(mapnode_or_link_class, "node_index"): # A mapnode. Mapnodes can only be placed on even grid positions, where @@ -454,7 +465,8 @@ class XYMap: raise MapParserError( f"Symbol '{char}' on XY=({ix / 2:g},{iy / 2:g}) marks a " "MapNode but is located between integer (X,Y) positions (only " - "Links can be placed between coordinates)!") + "Links can be placed between coordinates)!" + ) # save the node to several different maps for different uses # in both coordinate systems @@ -462,14 +474,17 @@ class XYMap: max_X, max_Y = max(max_X, iX), max(max_Y, iY) node_index += 1 - xygrid[ix][iy] = XYgrid[iX][iY] = node_index_map[node_index] = \ - mapnode_or_link_class(x=ix, y=iy, Z=self.Z, - node_index=node_index, symbol=char, xymap=self) + xygrid[ix][iy] = XYgrid[iX][iY] = node_index_map[ + node_index + ] = mapnode_or_link_class( + x=ix, y=iy, Z=self.Z, node_index=node_index, symbol=char, xymap=self + ) else: # we have a link at this xygrid position (this is ok everywhere) - xygrid[ix][iy] = mapnode_or_link_class(x=ix, y=iy, Z=self.Z, symbol=char, - xymap=self) + xygrid[ix][iy] = mapnode_or_link_class( + x=ix, y=iy, Z=self.Z, symbol=char, xymap=self + ) # store the symbol mapping for transition lookups symbol_map[char].append(xygrid[ix][iy]) @@ -499,20 +514,23 @@ class XYMap: node_coord = (node.X, node.Y) # load prototype from override, or use default try: - node.prototype = flatten_prototype(self.prototypes.get( - node_coord, - self.prototypes.get(('*', '*'), node.prototype)), - no_db=_NO_DB_PROTOTYPES + node.prototype = flatten_prototype( + self.prototypes.get( + node_coord, self.prototypes.get(("*", "*"), node.prototype) + ), + no_db=_NO_DB_PROTOTYPES, ) except Exception as err: raise MapParserError(f"Room prototype malformed: {err}", node) # do the same for links (x, y, direction) coords for direction, maplink in node.first_links.items(): try: - maplink.prototype = flatten_prototype(self.prototypes.get( - node_coord + (direction,), - self.prototypes.get(('*', '*', '*'), maplink.prototype)), - no_db=_NO_DB_PROTOTYPES + maplink.prototype = flatten_prototype( + self.prototypes.get( + node_coord + (direction,), + self.prototypes.get(("*", "*", "*"), maplink.prototype), + ), + no_db=_NO_DB_PROTOTYPES, ) except Exception as err: raise MapParserError(f"Exit prototype malformed: {err}", maplink) @@ -539,8 +557,10 @@ class XYMap: This performs a depth-first pass down the the given dist. """ - def _scan_neighbors(start_node, points, dist=2, - xmin=BIGVAL, ymin=BIGVAL, xmax=0, ymax=0, depth=0): + + def _scan_neighbors( + start_node, points, dist=2, xmin=BIGVAL, ymin=BIGVAL, xmax=0, ymax=0, depth=0 + ): x0, y0 = start_node.x, start_node.y points.append((x0, y0)) @@ -558,9 +578,15 @@ class XYMap: ymin, ymax = min(ymin, y), max(ymax, y) points, xmin, xmax, ymin, ymax = _scan_neighbors( - end_node, points, dist=dist, - xmin=xmin, ymin=ymin, xmax=xmax, ymax=ymax, - depth=depth + 1) + end_node, + points, + dist=dist, + xmin=xmin, + ymin=ymin, + xmax=xmax, + ymax=ymax, + depth=depth + 1, + ) return points, xmin, xmax, ymin, ymax @@ -581,14 +607,16 @@ class XYMap: # check if the solution for this grid was already solved previously. mapstr, dist_matrix, pathfinding_routes = "", None, None - with open(self.pathfinder_baked_filename, 'rb') as fil: + with open(self.pathfinder_baked_filename, "rb") as fil: try: mapstr, dist_matrix, pathfinding_routes = pickle.load(fil) except Exception: logger.log_trace() - if (mapstr == self.mapstring - and dist_matrix is not None - and pathfinding_routes is not None): + if ( + mapstr == self.mapstring + and dist_matrix is not None + and pathfinding_routes is not None + ): # this is important - it means the map hasn't changed so # we can re-use the stored data! self.dist_matrix = dist_matrix @@ -606,16 +634,20 @@ class XYMap: # solve using Dijkstra's algorithm self.dist_matrix, self.pathfinding_routes = dijkstra( - pathfinding_matrix, directed=True, - return_predecessors=True, limit=self.max_pathfinding_length) + pathfinding_matrix, + directed=True, + return_predecessors=True, + limit=self.max_pathfinding_length, + ) if self.pathfinder_baked_filename: # try to cache the results - with open(self.pathfinder_baked_filename, 'wb') as fil: - pickle.dump((self.mapstring, self.dist_matrix, self.pathfinding_routes), - fil, protocol=4) + with open(self.pathfinder_baked_filename, "wb") as fil: + pickle.dump( + (self.mapstring, self.dist_matrix, self.pathfinding_routes), fil, protocol=4 + ) - def spawn_nodes(self, xy=('*', '*')): + def spawn_nodes(self, xy=("*", "*")): """ Convert the nodes of this XYMap into actual in-world rooms by spawning their related prototypes in the correct coordinate positions. This must be done *first* @@ -638,12 +670,14 @@ class XYMap: if not _XYZROOMCLASS: from evennia.contrib.grid.xyzgrid.xyzroom import XYZRoom as _XYZROOMCLASS x, y = xy - wildcard = '*' + wildcard = "*" spawned = [] # find existing nodes, in case some rooms need to be removed - map_coords = [(node.X, node.Y) for node in - sorted(self.node_index_map.values(), key=lambda n: (n.Y, n.X))] + map_coords = [ + (node.X, node.Y) + for node in sorted(self.node_index_map.values(), key=lambda n: (n.Y, n.X)) + ] for existing_room in _XYZROOMCLASS.objects.filter_xyz(xyz=(x, y, self.Z)): roomX, roomY, _ = existing_room.xyz if (roomX, roomY) not in map_coords: @@ -657,7 +691,7 @@ class XYMap: spawned.append(node) return spawned - def spawn_links(self, xy=('*', '*'), nodes=None, directions=None): + def spawn_links(self, xy=("*", "*"), nodes=None, directions=None): """ Convert links of this XYMap into actual in-game exits by spawning their related prototypes. It's possible to only spawn a specic exit by specifying the node and @@ -676,7 +710,7 @@ class XYMap: """ x, y = xy - wildcard = '*' + wildcard = "*" if not nodes: nodes = sorted(self.node_index_map.values(), key=lambda n: (n.Z, n.Y, n.X)) @@ -706,8 +740,10 @@ class XYMap: iX, iY = xy if not ((0 <= iX <= self.max_X) and (0 <= iY <= self.max_Y)): - raise MapError(f"get_node_from_coord got coordinate {xy} which is " - f"outside the grid size of (0,0) - ({self.max_X}, {self.max_Y}).") + raise MapError( + f"get_node_from_coord got coordinate {xy} which is " + f"outside the grid size of (0,0) - ({self.max_X}, {self.max_Y})." + ) try: return self.XYgrid[iX][iY] except KeyError: @@ -753,8 +789,10 @@ class XYMap: istartnode = startnode.node_index inextnode = endnode.node_index except AttributeError: - raise MapError(f"Map.get_shortest_path received start/end nodes {startnode} and " - f"{endnode}. They must both be MapNodes (not Links)") + raise MapError( + f"Map.get_shortest_path received start/end nodes {startnode} and " + f"{endnode}. They must both be MapNodes (not Links)" + ) if self.pathfinding_routes is None: self.calculate_path_matrix() @@ -782,13 +820,18 @@ class XYMap: return directions, path - def get_visual_range(self, xy, dist=2, mode='nodes', - character='@', - target=None, - target_path_style="|y{display_symbol}|n", - max_size=None, - indent=0, - return_str=True): + def get_visual_range( + self, + xy, + dist=2, + mode="nodes", + character="@", + target=None, + target_path_style="|y{display_symbol}|n", + max_size=None, + indent=0, + return_str=True, + ): """ Get a part of the grid centered on a specific point and extended a certain number of nodes or grid points in every direction. @@ -876,7 +919,7 @@ class XYMap: # nothing but ourselves or emptiness return character if character else self.empty_symbol - elif mode == 'nodes': + elif mode == "nodes": # dist measures only full, reachable nodes. points, xmin, xmax, ymin, ymax = self._get_topology_around_coord(xy, dist=dist) @@ -888,7 +931,7 @@ class XYMap: for (ix0, iy0) in points: gridmap[iy0 - ymin][ix0 - xmin] = display_map[iy0][ix0] - elif mode == 'scan': + elif mode == "scan": # scan-mode - dist measures individual grid points xmin, xmax = max(0, ix - dist), min(width, ix + dist + 1) @@ -897,8 +940,10 @@ class XYMap: gridmap = [line[xmin:xmax] for line in display_map[ymin:ymax]] else: - raise MapError(f"Map.get_visual_range 'mode' was '{mode}' " - "- it must be either 'scan' or 'nodes'.") + raise MapError( + f"Map.get_visual_range 'mode' was '{mode}' " + "- it must be either 'scan' or 'nodes'." + ) if character: gridmap[iyc][ixc] = character # correct indexing; it's a list of lines @@ -906,8 +951,7 @@ class XYMap: # stylize path to target def _default_callable(node): - return target_path_style.format( - display_symbol=node.get_display_symbol()) + return target_path_style.format(display_symbol=node.get_display_symbol()) if callable(target_path_style): _target_path_style = target_path_style @@ -916,7 +960,7 @@ class XYMap: _, path = self.get_shortest_path(xy, target) - maxstep = dist if mode == 'nodes' else dist / 2 + maxstep = dist if mode == "nodes" else dist / 2 nsteps = 0 for node_or_link in path[1:]: if hasattr(node_or_link, "node_index"): diff --git a/evennia/contrib/grid/xyzgrid/xymap_legend.py b/evennia/contrib/grid/xyzgrid/xymap_legend.py index 2912e02998..ff5ea6b0b7 100644 --- a/evennia/contrib/grid/xyzgrid/xymap_legend.py +++ b/evennia/contrib/grid/xyzgrid/xymap_legend.py @@ -14,7 +14,8 @@ try: except ImportError as err: raise ImportError( f"{err}\nThe XYZgrid contrib requires " - "the SciPy package. Install with `pip install scipy'.") + "the SciPy package. Install with `pip install scipy'." + ) import uuid from collections import defaultdict @@ -33,6 +34,7 @@ UUID_XYZ_NAMESPACE = uuid.uuid5(uuid.UUID(int=0), "xyzgrid") # Nodes/Links + class MapNode: """ This represents a 'room' node on the map. Note that the map system deals with two grids, the @@ -62,8 +64,9 @@ class MapNode: for various reasons, mostly map-transitions). """ + # symbol used to identify this link on the map - symbol = '#' + symbol = "#" # if printing this node should show another symbol. If set # to the empty string, use `symbol`. display_symbol = None @@ -79,16 +82,16 @@ class MapNode: multilink = True # default values to use if the exit doesn't have a 'spawn_aliases' iterable direction_spawn_defaults = { - 'n': ('north', 'n'), - 'ne': ('northeast', 'ne', 'north-east'), - 'e': ('east', 'e'), - 'se': ('southeast', 'se', 'south-east'), - 's': ('south', 's'), - 'sw': ('southwest', 'sw', 'south-west'), - 'w': ('west', 'w'), - 'nw': ('northwest', 'nw', 'north-west'), - 'd': ('down', 'd', 'do'), - 'u': ('up', 'u'), + "n": ("north", "n"), + "ne": ("northeast", "ne", "north-east"), + "e": ("east", "e"), + "se": ("southeast", "se", "south-east"), + "s": ("south", "s"), + "sw": ("southwest", "sw", "south-west"), + "w": ("west", "w"), + "nw": ("northwest", "nw", "north-west"), + "d": ("down", "d", "do"), + "u": ("up", "u"), } def __init__(self, x, y, Z, node_index=0, symbol=None, xymap=None): @@ -202,7 +205,9 @@ class MapNode: if first_step_name in self.closest_neighbor_names: raise MapParserError( f"has more than one outgoing direction '{first_step_name}'. " - "All directions out of a node must be unique.", self) + "All directions out of a node must be unique.", + self, + ) self.closest_neighbor_names[first_step_name] = direction node_index = end_node.node_index @@ -215,8 +220,9 @@ class MapNode: # used for building the shortest path. Note that we store the # aliased link directions here, for quick display by the # shortest-route solver - shortest_route = self.shortest_route_to_node.get( - node_index, ("", [], BIGVAL))[2] + shortest_route = self.shortest_route_to_node.get(node_index, ("", [], BIGVAL))[ + 2 + ] if weight < shortest_route: self.shortest_route_to_node[node_index] = (first_step_name, steps, weight) @@ -280,11 +286,9 @@ class MapNode: str or tuple: The key of the spawned exit, or a tuple (key, alias, alias, ...) """ - key, *aliases = ( - self.first_links[direction] - .spawn_aliases.get( - direction, self.direction_spawn_defaults.get( - direction, ('unknown', )))) + key, *aliases = self.first_links[direction].spawn_aliases.get( + direction, self.direction_spawn_defaults.get(direction, ("unknown",)) + ) if return_aliases: return (key, *aliases) return key @@ -313,28 +317,24 @@ class MapNode: nodeobj = NodeTypeclass.objects.get_xyz(xyz=xyz) except django_exceptions.ObjectDoesNotExist: # create a new entity with proper coordinates etc - tclass = self.prototype['typeclass'] - tclass = (f' ({tclass})' - if tclass != 'evennia.contrib.grid.xyzgrid.xyzroom.XYZRoom' - else '') - self.log(f" spawning room at xyz={xyz}{tclass}") - nodeobj, err = NodeTypeclass.create( - self.prototype.get('key', 'An empty room'), - xyz=xyz + tclass = self.prototype["typeclass"] + tclass = ( + f" ({tclass})" if tclass != "evennia.contrib.grid.xyzgrid.xyzroom.XYZRoom" else "" ) + self.log(f" spawning room at xyz={xyz}{tclass}") + nodeobj, err = NodeTypeclass.create(self.prototype.get("key", "An empty room"), xyz=xyz) if err: raise RuntimeError(err) else: self.log(f" updating existing room (if changed) at xyz={xyz}") - if not self.prototype.get('prototype_key'): + if not self.prototype.get("prototype_key"): # make sure there is a prototype_key in prototype - self.prototype['prototype_key'] = self.generate_prototype_key() + self.prototype["prototype_key"] = self.generate_prototype_key() # apply prototype to node. This will not override the XYZ tags since # these are not in the prototype and exact=False - spawner.batch_update_objects_with_prototype( - self.prototype, objects=[nodeobj], exact=False) + spawner.batch_update_objects_with_prototype(self.prototype, objects=[nodeobj], exact=False) def spawn_links(self, directions=None): """ @@ -364,9 +364,9 @@ class MapNode: for direction, link in self.first_links.items(): key, *aliases = self.get_exit_spawn_name(direction) - if not link.prototype.get('prototype_key'): + if not link.prototype.get("prototype_key"): # generate a deterministic prototype_key if it doesn't exist - link.prototype['prototype_key'] = self.generate_prototype_key() + link.prototype["prototype_key"] = self.generate_prototype_key() maplinks[key.lower()] = (key, aliases, direction, link) # remove duplicates @@ -380,8 +380,7 @@ class MapNode: # we need to search for exits in all directions since some # may have been removed since last sync - linkobjs = {exi.db_key.lower(): exi - for exi in ExitTypeclass.objects.filter_xyz(xyz=xyz)} + linkobjs = {exi.db_key.lower(): exi for exi in ExitTypeclass.objects.filter_xyz(xyz=xyz)} # figure out if the topology changed between grid and map (will always # build all exits first run) @@ -411,16 +410,19 @@ class MapNode: raise RuntimeError(err) linkobjs[key.lower()] = exi prot = maplinks[key.lower()][3].prototype - tclass = prot['typeclass'] - tclass = (f' ({tclass})' - if tclass != 'evennia.contrib.grid.xyzgrid.xyzroom.XYZExit' - else '') + tclass = prot["typeclass"] + tclass = ( + f" ({tclass})" + if tclass != "evennia.contrib.grid.xyzgrid.xyzroom.XYZExit" + else "" + ) self.log(f" spawning/updating exit xyz={xyz}, direction={key}{tclass}") # apply prototypes to catch any changes for key, linkobj in linkobjs.items(): spawner.batch_update_objects_with_prototype( - maplinks[key.lower()][3].prototype, objects=[linkobj], exact=False) + maplinks[key.lower()][3].prototype, objects=[linkobj], exact=False + ) def unspawn(self): """ @@ -466,8 +468,9 @@ class TransitionMapNode(MapNode): actual rooms (`#`) on the other map (NOT to the `T`s)! """ - symbol = 'T' - display_symbol = ' ' + + symbol = "T" + display_symbol = " " # X,Y,Z coordinates of target node taget_map_xyz = (None, None, None) @@ -477,10 +480,13 @@ class TransitionMapNode(MapNode): the exit to this node (since the prototype is None, this node itself will not be built). """ - if any(True for coord in self.target_map_xyz if coord in (None, 'unset')): - raise MapParserError(f"(Z={self.xymap.Z}) has not defined its " - "`.target_map_xyz` property. It must point " - "to another valid xymap (Z coordinate).", self) + if any(True for coord in self.target_map_xyz if coord in (None, "unset")): + raise MapParserError( + f"(Z={self.xymap.Z}) has not defined its " + "`.target_map_xyz` property. It must point " + "to another valid xymap (Z coordinate).", + self, + ) return self.target_map_xyz @@ -548,6 +554,7 @@ class MapLink: `node.get_exit_spawn_name(direction)` """ + # symbol for identifying this link on the map symbol = "" # if `None`, use .symbol @@ -661,7 +668,9 @@ class MapLink: return None, 0, None raise MapParserError( f"was connected to from the direction {start_direction}, but " - "is not set up to link in that direction.", self) + "is not set up to link in that direction.", + self, + ) # note that if `get_direction` returns an unknown direction, this will be equivalent # to pointing to an empty location, which makes sense @@ -674,8 +683,7 @@ class MapLink: next_target = self.at_empty_target(start_direction, end_direction) if not next_target: - raise MapParserError( - f"points to empty space in the direction {end_direction}!", self) + raise MapParserError(f"points to empty space in the direction {end_direction}!", self) _weight += self.get_weight(start_direction, _weight) if _steps is None: @@ -688,13 +696,16 @@ class MapLink: return ( next_target, _weight / max(1, _linklen) if self.average_long_link_weights else _weight, - _steps + _steps, ) else: # we hit another link. Progress recursively. return next_target.traverse( REVERSE_DIRECTIONS.get(end_direction, end_direction), - _weight=_weight, _linklen=_linklen + 1, _steps=_steps) + _weight=_weight, + _linklen=_linklen + 1, + _steps=_steps, + ) def get_linked_neighbors(self, directions=None): """ @@ -720,8 +731,7 @@ class MapLink: # there is is something there, we need to check if it is either # a map node or a link connecting in our direction node_or_link = xygrid[end_x][end_y] - if (node_or_link.multilink - or node_or_link.get_direction(direction)): + if node_or_link.multilink or node_or_link.get_direction(direction): links[direction] = node_or_link return links @@ -845,7 +855,8 @@ class SmartRerouterMapLink(MapLink): for direction in unhandled_links_copy: if REVERSE_DIRECTIONS[direction] in unhandled_links_copy: directions[direction] = REVERSE_DIRECTIONS[ - unhandled_links.pop(unhandled_links.index(direction))] + unhandled_links.pop(unhandled_links.index(direction)) + ] # check if we have any non-cross-through paths left to handle n_unhandled = len(unhandled_links) @@ -856,7 +867,8 @@ class SmartRerouterMapLink(MapLink): if n_unhandled != 2: links = ", ".join(unhandled_links) raise MapParserError( - f"cannot determine how to connect in/out directions {links}.", self) + f"cannot determine how to connect in/out directions {links}.", self + ) directions[unhandled_links[0]] = unhandled_links[1] directions[unhandled_links[1]] = unhandled_links[0] @@ -865,6 +877,7 @@ class SmartRerouterMapLink(MapLink): return self.directions.get(start_direction) + class SmartTeleporterMapLink(MapLink): """ The teleport link works by connecting to nowhere - and will then continue @@ -889,10 +902,11 @@ class SmartTeleporterMapLink(MapLink): -#-t-# - invalid, only one connected link is allowed. """ - symbol = 't' + + symbol = "t" # usually invisible - display_symbol = ' ' - direction_name = 'teleport' + display_symbol = " " + direction_name = "teleport" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -932,7 +946,9 @@ class SmartTeleporterMapLink(MapLink): if len(found_teleporters) > 1: raise MapParserError( "found too many matching teleporters (must be exactly one more): " - f"{found_teleporters}", self) + f"{found_teleporters}", + self, + ) other_teleporter = found_teleporters[0] # link the two so we don't need to scan again for the other one @@ -952,9 +968,10 @@ class SmartTeleporterMapLink(MapLink): if len(neighbors) != 1: raise MapParserError("must have exactly one link connected to it.", self) direction, link = next(iter(neighbors.items())) - if hasattr(link, 'node_index'): - raise MapParserError("can only connect to a Link. Found {link} in " - "direction {direction}.", self) + if hasattr(link, "node_index"): + raise MapParserError( + "can only connect to a Link. Found {link} in " "direction {direction}.", self + ) # the string 'teleport' will not be understood by the traverser, leading to # this being interpreted as an empty target and the `at_empty_target` # hook firing when trying to traverse this link. @@ -962,12 +979,10 @@ class SmartTeleporterMapLink(MapLink): if start_direction == direction_name: # called while traversing another teleport # - we must make sure we can always access/leave the teleport. - self.directions = {direction_name: direction, - direction: direction_name} + self.directions = {direction_name: direction, direction: direction_name} else: # called while traversing a normal link - self.directions = {start_direction: direction_name, - direction_name: direction} + self.directions = {start_direction: direction_name, direction_name: direction} return self.directions.get(start_direction) @@ -1016,6 +1031,7 @@ class SmartMapLink(MapLink): # | """ + multilink = True def get_direction(self, start_direction): @@ -1027,8 +1043,11 @@ class SmartMapLink(MapLink): if not self.directions: directions = {} neighbors = self.get_linked_neighbors() - nodes = [direction for direction, neighbor in neighbors.items() - if hasattr(neighbor, 'node_index')] + nodes = [ + direction + for direction, neighbor in neighbors.items() + if hasattr(neighbor, "node_index") + ] if len(nodes) == 2: # prefer link to these two nodes @@ -1042,7 +1061,9 @@ class SmartMapLink(MapLink): "must have exactly two connections - either directly to " "two nodes or connecting directly to one node and with exactly one other " f"link direction. The neighbor(s) in directions {list(neighbors.keys())} do " - "not fulfill these criteria.", self) + "not fulfill these criteria.", + self, + ) self.directions = directions return self.directions.get(start_direction) @@ -1071,20 +1092,26 @@ class InvisibleSmartMapLink(SmartMapLink): # this allows for normal movement directions even if the invisible-node # is marked with a different symbol. direction_aliases = { - 'n': 'n', 'ne': 'ne', 'e': 'e', 'se': 'se', - 's': 's', 'sw': 'sw', 'w': 'w', 'nw': 'nw' + "n": "n", + "ne": "ne", + "e": "e", + "se": "se", + "s": "s", + "sw": "sw", + "w": "w", + "nw": "nw", } # replace current link position with what the smart links "should" look like display_symbol_aliases = { - (('n', 's'), ('s', 'n')): '|', - (('n', 's'),): 'v', - (('s', 'n')): '^', - (('e', 'w'), ('w', 'e')): '-', - (('e', 'w'),): '>', - (('w', 'e'),): '<', - (('nw', 'se'), ('sw', 'ne')): '\\', - (('ne', 'sw'), ('sw', 'ne')): '/', + (("n", "s"), ("s", "n")): "|", + (("n", "s"),): "v", + (("s", "n")): "^", + (("e", "w"), ("w", "e")): "-", + (("e", "w"),): ">", + (("w", "e"),): "<", + (("nw", "se"), ("sw", "ne")): "\\", + (("ne", "sw"), ("sw", "ne")): "/", } def get_display_symbol(self): @@ -1098,12 +1125,10 @@ class InvisibleSmartMapLink(SmartMapLink): """ if not hasattr(self, "_cached_display_symbol"): legend = self.xymap.legend - default_symbol = ( - self.symbol if self.display_symbol is None else self.display_symbol) + default_symbol = self.symbol if self.display_symbol is None else self.display_symbol self._cached_display_symbol = default_symbol - dirtuple = tuple((key, self.directions[key]) - for key in sorted(self.directions.keys())) + dirtuple = tuple((key, self.directions[key]) for key in sorted(self.directions.keys())) replacement_symbol = self.display_symbol_aliases.get(dirtuple, default_symbol) @@ -1112,16 +1137,19 @@ class InvisibleSmartMapLink(SmartMapLink): if node_or_link_class: # initiate class in the current location and run get_display_symbol # to get what it would show. - self._cached_display_symbol = ( - node_or_link_class(self.x, self.y, self.Z).get_display_symbol()) + self._cached_display_symbol = node_or_link_class( + self.x, self.y, self.Z + ).get_display_symbol() return self._cached_display_symbol # ---------------------------------- # Default nodes and link classes + class BasicMapNode(MapNode): """A map node/room""" + symbol = "#" prototype = "xyz_room" @@ -1129,20 +1157,25 @@ class BasicMapNode(MapNode): class InterruptMapNode(MapNode): """A point of interest node/room. Pathfinder will ignore but auto-stepper will stop here if passing through. Beginner-Tutorial from here is fine.""" + symbol = "I" display_symbol = "#" interrupt_path = True prototype = "xyz_room" + class MapTransitionNode(TransitionMapNode): """Transition-target node to other map. This is not actually spawned in-game.""" + symbol = "T" display_symbol = " " prototype = None # important to leave None! target_map_xyz = (None, None, None) # must be set manually + class NSMapLink(MapLink): """Two-way, North-South link""" + symbol = "|" display_symbol = "||" directions = {"n": "s", "s": "n"} @@ -1151,6 +1184,7 @@ class NSMapLink(MapLink): class EWMapLink(MapLink): """Two-way, East-West link""" + symbol = "-" directions = {"e": "w", "w": "e"} prototype = "xyz_exit" @@ -1158,6 +1192,7 @@ class EWMapLink(MapLink): class NESWMapLink(MapLink): """Two-way, NorthWest-SouthWest link""" + symbol = "/" directions = {"ne": "sw", "sw": "ne"} prototype = "xyz_exit" @@ -1165,6 +1200,7 @@ class NESWMapLink(MapLink): class SENWMapLink(MapLink): """Two-way, SouthEast-NorthWest link""" + symbol = "\\" directions = {"se": "nw", "nw": "se"} prototype = "xyz_exit" @@ -1172,22 +1208,23 @@ class SENWMapLink(MapLink): class PlusMapLink(MapLink): """Two-way, crossing North-South and East-West links""" + symbol = "+" - directions = {"s": "n", "n": "s", - "e": "w", "w": "e"} + directions = {"s": "n", "n": "s", "e": "w", "w": "e"} prototype = "xyz_exit" class CrossMapLink(MapLink): """Two-way, crossing NorthEast-SouthWest and SouthEast-NorthWest links""" + symbol = "x" - directions = {"ne": "sw", "sw": "ne", - "se": "nw", "nw": "se"} + directions = {"ne": "sw", "sw": "ne", "se": "nw", "nw": "se"} prototype = "xyz_exit" class NSOneWayMapLink(MapLink): """One-way North-South link""" + symbol = "v" directions = {"n": "s"} prototype = "xyz_exit" @@ -1195,6 +1232,7 @@ class NSOneWayMapLink(MapLink): class SNOneWayMapLink(MapLink): """One-way South-North link""" + symbol = "^" directions = {"s": "n"} prototype = "xyz_exit" @@ -1202,6 +1240,7 @@ class SNOneWayMapLink(MapLink): class EWOneWayMapLink(MapLink): """One-way East-West link""" + symbol = "<" directions = {"e": "w"} prototype = "xyz_exit" @@ -1209,6 +1248,7 @@ class EWOneWayMapLink(MapLink): class WEOneWayMapLink(MapLink): """One-way West-East link""" + symbol = ">" directions = {"w": "e"} prototype = "xyz_exit" @@ -1216,21 +1256,39 @@ class WEOneWayMapLink(MapLink): class UpMapLink(SmartMapLink): """Up direction. Note that this stays on the same z-coord so it's a 'fake' up.""" - symbol = 'u' + + symbol = "u" # all movement over this link is 'up', regardless of where on the xygrid we move. - direction_aliases = {'n': symbol, 'ne': symbol, 'e': symbol, 'se': symbol, - 's': symbol, 'sw': symbol, 'w': symbol, 'nw': symbol} + direction_aliases = { + "n": symbol, + "ne": symbol, + "e": symbol, + "se": symbol, + "s": symbol, + "sw": symbol, + "w": symbol, + "nw": symbol, + } spawn_aliases = {direction: ("up", "u") for direction in direction_aliases} prototype = "xyz_exit" class DownMapLink(UpMapLink): """Down direction. Note that this stays on the same z-coord, so it's a 'fake' down.""" - symbol = 'd' + + symbol = "d" # all movement over this link is 'down', regardless of where on the xygrid we move. - direction_aliases = {'n': symbol, 'ne': symbol, 'e': symbol, 'se': symbol, - 's': symbol, 'sw': symbol, 'w': symbol, 'nw': symbol} + direction_aliases = { + "n": symbol, + "ne": symbol, + "e": symbol, + "se": symbol, + "s": symbol, + "sw": symbol, + "w": symbol, + "nw": symbol, + } spawn_aliases = {direction: ("down", "d") for direction in direction_aliases} prototype = "xyz_exit" @@ -1238,6 +1296,7 @@ class DownMapLink(UpMapLink): class InterruptMapLink(InvisibleSmartMapLink): """A (still passable) link. Pathfinder will treat this as any link, but auto-stepper will always abort before crossing this link - so this must be crossed manually.""" + symbol = "i" interrupt_path = True prototype = "xyz_exit" @@ -1250,14 +1309,24 @@ class BlockedMapLink(InvisibleSmartMapLink): link in any paths. """ - symbol = 'b' - weights = {'n': BIGVAL, 'ne': BIGVAL, 'e': BIGVAL, 'se': BIGVAL, - 's': BIGVAL, 'sw': BIGVAL, 'w': BIGVAL, 'nw': BIGVAL} + + symbol = "b" + weights = { + "n": BIGVAL, + "ne": BIGVAL, + "e": BIGVAL, + "se": BIGVAL, + "s": BIGVAL, + "sw": BIGVAL, + "w": BIGVAL, + "nw": BIGVAL, + } prototype = "xyz_exit" class RouterMapLink(SmartRerouterMapLink): """A link that connects other links to build 'knees', pass-throughs etc.""" + symbol = "o" @@ -1266,7 +1335,8 @@ class TeleporterMapLink(SmartTeleporterMapLink): Teleporter links. Must appear in pairs on the same xy map. To make it one-way, add additional one-way link out of the teleporter on one side. """ - symbol = 't' + + symbol = "t" # all map components; used as base if not overridden @@ -1291,5 +1361,5 @@ LEGEND = { "d": DownMapLink, "b": BlockedMapLink, "i": InterruptMapLink, - 't': TeleporterMapLink, + "t": TeleporterMapLink, } diff --git a/evennia/contrib/grid/xyzgrid/xyzgrid.py b/evennia/contrib/grid/xyzgrid/xyzgrid.py index 77eb797bf7..1e8b538ab2 100644 --- a/evennia/contrib/grid/xyzgrid/xyzgrid.py +++ b/evennia/contrib/grid/xyzgrid/xyzgrid.py @@ -28,6 +28,7 @@ class XYZGrid(DefaultScript): Main grid class. This organizes the Maps based on their name/Z-coordinate. """ + def at_script_creation(self): """ What we store persistently is data used to create each map (the legends, names etc) @@ -88,7 +89,7 @@ class XYZGrid(DefaultScript): """ return XYZRoom.objects.filter_xyz(xyz=xyz, **kwargs) - def get_exit(self, xyz, name='north', **kwargs): + def get_exit(self, xyz, name="north", **kwargs): """ Get one or more exit object at coordinate. @@ -102,7 +103,7 @@ class XYZGrid(DefaultScript): Queryset: A queryset of XYZExit(s) found. """ - kwargs['db_key'] = name + kwargs["db_key"] = name return XYZExit.objects.filter_xyz_exit(xyz=xyz, **kwargs) def maps_from_module(self, module_path): @@ -127,7 +128,7 @@ class XYZGrid(DefaultScript): if not mapdata: self.log(f"Could not find or load map from {module_path}.") return - mapdata['module_path'] = module_path + mapdata["module_path"] = module_path return map_data_list def reload(self): @@ -154,9 +155,9 @@ class XYZGrid(DefaultScript): # we reload the map from module new_mapdata = loaded_mapdata.get(zcoord) if not new_mapdata: - if 'module_path' in old_mapdata: - for mapdata in self.maps_from_module(old_mapdata['module_path']): - loaded_mapdata[mapdata['zcoord']] = mapdata + if "module_path" in old_mapdata: + for mapdata in self.maps_from_module(old_mapdata["module_path"]): + loaded_mapdata[mapdata["zcoord"]] = mapdata else: # nowhere to reload from - use what we have loaded_mapdata[zcoord] = old_mapdata @@ -198,7 +199,7 @@ class XYZGrid(DefaultScript): """ for mapdata in mapdatas: - zcoord = mapdata.get('zcoord') + zcoord = mapdata.get("zcoord") if not zcoord: raise RuntimeError("XYZGrid.add_map data must contain 'zcoord'.") @@ -220,7 +221,7 @@ class XYZGrid(DefaultScript): if remove_objects: # we can't batch-delete because we want to run the .delete # method that also wipes exits and moves content to save locations - for xyzroom in XYZRoom.objects.filter_xyz(xyz=('*', '*', zcoord)): + for xyzroom in XYZRoom.objects.filter_xyz(xyz=("*", "*", zcoord)): xyzroom.delete() self.reload() @@ -234,7 +235,7 @@ class XYZGrid(DefaultScript): self.remove_map(*(zcoord for zcoord in self.db.map_data), remove_objects=True) super().delete() - def spawn(self, xyz=('*', '*', '*'), directions=None): + def spawn(self, xyz=("*", "*", "*"), directions=None): """ Create/recreate/update the in-game grid based on the stored Maps or for a specific Map or coordinate. @@ -255,7 +256,7 @@ class XYZGrid(DefaultScript): """ x, y, z = xyz - wildcard = '*' + wildcard = "*" if z == wildcard: xymaps = self.grid @@ -293,8 +294,10 @@ def get_xyzgrid(print_errors=True): xyzgrid.reload() return xyzgrid elif len(xyzgrid) > 1: - ("Warning: More than one XYZGrid instances were found. This is an error and " - "only the first one will be used. Delete the other one(s) manually.") + ( + "Warning: More than one XYZGrid instances were found. This is an error and " + "only the first one will be used. Delete the other one(s) manually." + ) xyzgrid = xyzgrid[0] try: if not xyzgrid.ndb.loaded: diff --git a/evennia/contrib/grid/xyzgrid/xyzroom.py b/evennia/contrib/grid/xyzgrid/xyzroom.py index 2b384dc326..581ca2688c 100644 --- a/evennia/contrib/grid/xyzgrid/xyzroom.py +++ b/evennia/contrib/grid/xyzgrid/xyzroom.py @@ -31,7 +31,8 @@ class XYZManager(ObjectManager): efficiently querying the room in the database based on XY coordinates. """ - def filter_xyz(self, xyz=('*', '*', '*'), **kwargs): + + def filter_xyz(self, xyz=("*", "*", "*"), **kwargs): """ Filter queryset based on XYZ position on the grid. The Z-position is the name of the XYMap Set a coordinate to `'*'` to act as a wildcard (setting all coords to `*` will thus find @@ -49,23 +50,28 @@ class XYZManager(ObjectManager): """ x, y, z = xyz - wildcard = '*' + wildcard = "*" return ( - self - .filter_family(**kwargs) + self.filter_family(**kwargs) .filter( - Q() if x == wildcard - else Q(db_tags__db_key=str(x), db_tags__db_category=MAP_X_TAG_CATEGORY)) + Q() + if x == wildcard + else Q(db_tags__db_key=str(x), db_tags__db_category=MAP_X_TAG_CATEGORY) + ) .filter( - Q() if y == wildcard - else Q(db_tags__db_key=str(y), db_tags__db_category=MAP_Y_TAG_CATEGORY)) + Q() + if y == wildcard + else Q(db_tags__db_key=str(y), db_tags__db_category=MAP_Y_TAG_CATEGORY) + ) .filter( - Q() if z == wildcard - else Q(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY)) + Q() + if z == wildcard + else Q(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY) + ) ) - def get_xyz(self, xyz=(0, 0, 'map'), **kwargs): + def get_xyz(self, xyz=(0, 0, "map"), **kwargs): """ Always return a single matched entity directly. This accepts no `*`-wildcards. This will also find children of XYZRooms on the given coordinates. @@ -93,8 +99,9 @@ class XYZManager(ObjectManager): # error - mimic default get() behavior but with a little more info x, y, z = xyz - inp = (f"Query: xyz=({x},{y},{z}), " + - ",".join(f"{key}={val}" for key, val in kwargs.items())) + inp = f"Query: xyz=({x},{y},{z}), " + ",".join( + f"{key}={val}" for key, val in kwargs.items() + ) if ncount > 1: raise self.model.MultipleObjectsReturned(inp) else: @@ -108,8 +115,7 @@ class XYZExitManager(XYZManager): """ - def filter_xyz_exit(self, xyz=('*', '*', '*'), - xyz_destination=('*', '*', '*'), **kwargs): + def filter_xyz_exit(self, xyz=("*", "*", "*"), xyz_destination=("*", "*", "*"), **kwargs): """ Used by exits (objects with a source and -destination property). Find all exits out of a source or to a particular destination. This will also find @@ -138,32 +144,43 @@ class XYZExitManager(XYZManager): """ x, y, z = xyz xdest, ydest, zdest = xyz_destination - wildcard = '*' + wildcard = "*" return ( - self - .filter_family(**kwargs) + self.filter_family(**kwargs) .filter( - Q() if x == wildcard - else Q(db_tags__db_key=str(x), db_tags__db_category=MAP_X_TAG_CATEGORY)) + Q() + if x == wildcard + else Q(db_tags__db_key=str(x), db_tags__db_category=MAP_X_TAG_CATEGORY) + ) .filter( - Q() if y == wildcard - else Q(db_tags__db_key=str(y), db_tags__db_category=MAP_Y_TAG_CATEGORY)) + Q() + if y == wildcard + else Q(db_tags__db_key=str(y), db_tags__db_category=MAP_Y_TAG_CATEGORY) + ) .filter( - Q() if z == wildcard - else Q(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY)) + Q() + if z == wildcard + else Q(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY) + ) .filter( - Q() if xdest == wildcard - else Q(db_tags__db_key=str(xdest), db_tags__db_category=MAP_XDEST_TAG_CATEGORY)) + Q() + if xdest == wildcard + else Q(db_tags__db_key=str(xdest), db_tags__db_category=MAP_XDEST_TAG_CATEGORY) + ) .filter( - Q() if ydest == wildcard - else Q(db_tags__db_key=str(ydest), db_tags__db_category=MAP_YDEST_TAG_CATEGORY)) + Q() + if ydest == wildcard + else Q(db_tags__db_key=str(ydest), db_tags__db_category=MAP_YDEST_TAG_CATEGORY) + ) .filter( - Q() if zdest == wildcard - else Q(db_tags__db_key=str(zdest), db_tags__db_category=MAP_ZDEST_TAG_CATEGORY)) + Q() + if zdest == wildcard + else Q(db_tags__db_key=str(zdest), db_tags__db_category=MAP_ZDEST_TAG_CATEGORY) + ) ) - def get_xyz_exit(self, xyz=(0, 0, 'map'), xyz_destination=(0, 0, 'map'), **kwargs): + def get_xyz_exit(self, xyz=(0, 0, "map"), xyz_destination=(0, 0, "map"), **kwargs): """ Used by exits (objects with a source and -destination property). Get a single exit. All source/destination coordinates (as well as the map's name) are required. @@ -199,8 +216,7 @@ class XYZExitManager(XYZManager): try: return ( - self - .filter(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY) + self.filter(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY) .filter(db_tags__db_key=str(x), db_tags__db_category=MAP_X_TAG_CATEGORY) .filter(db_tags__db_key=str(y), db_tags__db_category=MAP_Y_TAG_CATEGORY) .filter(db_tags__db_key=str(xdest), db_tags__db_category=MAP_XDEST_TAG_CATEGORY) @@ -209,10 +225,12 @@ class XYZExitManager(XYZManager): .get(**kwargs) ) except self.model.DoesNotExist: - inp = (f"xyz=({x},{y},{z}),xyz_destination=({xdest},{ydest},{zdest})," + - ",".join(f"{key}={val}" for key, val in kwargs.items())) - raise self.model.DoesNotExist(f"{self.model.__name__} " - f"matching query {inp} does not exist.") + inp = f"xyz=({x},{y},{z}),xyz_destination=({xdest},{ydest},{zdest})," + ",".join( + f"{key}={val}" for key, val in kwargs.items() + ) + raise self.model.DoesNotExist( + f"{self.model.__name__} " f"matching query {inp} does not exist." + ) class XYZRoom(DefaultRoom): @@ -244,10 +262,10 @@ class XYZRoom(DefaultRoom): # default settings for map visualization map_display = True - map_mode = 'nodes' # or 'scan' + map_mode = "nodes" # or 'scan' map_visual_range = 2 map_character_symbol = "|g@|n" - map_align = 'c' + map_align = "c" map_target_path_style = "|y{display_symbol}|n" map_fill_all = True map_separator_char = "|x~|n" @@ -267,8 +285,10 @@ class XYZRoom(DefaultRoom): z = self.tags.get(category=MAP_Z_TAG_CATEGORY, return_list=False) if x is None or y is None or z is None: # don't cache unfinished coordinate (probably tags have not finished saving) - return tuple(int(coord) if coord is not None and coord.isdigit() else coord - for coord in (x, y, z)) + return tuple( + int(coord) if coord is not None and coord.isdigit() else coord + for coord in (x, y, z) + ) # cache result, convert to correct types (tags are strings) self._xyz = tuple(int(coord) if coord.isdigit() else coord for coord in (x, y, z)) @@ -290,7 +310,7 @@ class XYZRoom(DefaultRoom): return self._xymap @classmethod - def create(cls, key, account=None, xyz=(0, 0, 'map'), **kwargs): + def create(cls, key, account=None, xyz=(0, 0, "map"), **kwargs): """ Creation method aware of XYZ coordinates. @@ -316,14 +336,18 @@ class XYZRoom(DefaultRoom): try: x, y, z = xyz except ValueError: - return None, [f"XYRroom.create got `xyz={xyz}` - needs a valid (X,Y,Z) " - "coordinate of ints/strings."] + return None, [ + f"XYRroom.create got `xyz={xyz}` - needs a valid (X,Y,Z) " + "coordinate of ints/strings." + ] existing_query = cls.objects.filter_xyz(xyz=(x, y, z)) if existing_query.exists(): existing_room = existing_query.first() - return None, [f"XYRoom XYZ=({x},{y},{z}) already exists " - f"(existing room is named '{existing_room.db_key}')!"] + return None, [ + f"XYRoom XYZ=({x},{y},{z}) already exists " + f"(existing room is named '{existing_room.db_key}')!" + ] tags = ( (str(x), MAP_X_TAG_CATEGORY), @@ -410,26 +434,29 @@ class XYZRoom(DefaultRoom): xyz = self.xyz xymap = self.xyzgrid.get_map(xyz[2]) - if xymap and kwargs.get('map_display', xymap.options.get("map_display", self.map_display)): + if xymap and kwargs.get("map_display", xymap.options.get("map_display", self.map_display)): # show the near-area map. map_character_symbol = kwargs.get( - 'map_character_symbol', - xymap.options.get("map_character_symbol", self.map_character_symbol)) + "map_character_symbol", + xymap.options.get("map_character_symbol", self.map_character_symbol), + ) map_visual_range = kwargs.get( - "map_visual_range", xymap.options.get("map_visual_range", self.map_visual_range)) - map_mode = kwargs.get( - "map_mode", xymap.options.get("map_mode", self.map_mode)) - map_align = kwargs.get( - "map_align", xymap.options.get("map_align", self.map_align)) + "map_visual_range", xymap.options.get("map_visual_range", self.map_visual_range) + ) + map_mode = kwargs.get("map_mode", xymap.options.get("map_mode", self.map_mode)) + map_align = kwargs.get("map_align", xymap.options.get("map_align", self.map_align)) map_target_path_style = kwargs.get( "map_target_path_style", - xymap.options.get("map_target_path_style", self.map_target_path_style)) + xymap.options.get("map_target_path_style", self.map_target_path_style), + ) map_area_client = kwargs.get( - "map_fill_all", xymap.options.get("map_fill_all", self.map_fill_all)) + "map_fill_all", xymap.options.get("map_fill_all", self.map_fill_all) + ) map_separator_char = kwargs.get( "map_separator_char", - xymap.options.get("map_separator_char", self.map_separator_char)) + xymap.options.get("map_separator_char", self.map_separator_char), + ) client_width, _ = looker.sessions.get()[0].get_client_size() @@ -438,15 +465,14 @@ class XYZRoom(DefaultRoom): if map_area_client: display_width = client_width else: - display_width = max(map_width, - max(len(line) for line in room_desc.split("\n"))) + display_width = max(map_width, max(len(line) for line in room_desc.split("\n"))) # align map map_indent = 0 sep_width = display_width - if map_align == 'r': + if map_align == "r": map_indent = max(0, display_width - map_width) - elif map_align == 'c': + elif map_align == "c": map_indent = max(0, (display_width - map_width) // 2) # data set by the goto/path-command, for displaying the shortest path @@ -462,7 +488,7 @@ class XYZRoom(DefaultRoom): target_path_style=map_target_path_style, character=map_character_symbol, max_size=(display_width, None), - indent=map_indent + indent=map_indent, ) sep = map_separator_char * sep_width map_display = f"{sep}|n\n{map_display}\n{sep}" @@ -523,8 +549,16 @@ class XYZExit(DefaultExit): return self._xyz_destination @classmethod - def create(cls, key, account=None, xyz=(0, 0, 'map'), xyz_destination=(0, 0, 'map'), - location=None, destination=None, **kwargs): + def create( + cls, + key, + account=None, + xyz=(0, 0, "map"), + xyz_destination=(0, 0, "map"), + location=None, + destination=None, + **kwargs, + ): """ Creation method aware of coordinates. @@ -559,23 +593,33 @@ class XYZExit(DefaultExit): return None, ["XYExit.create need either `xyz=(X,Y,Z)` coordinate or a `location`."] else: source = XYZRoom.objects.get_xyz(xyz=(x, y, z)) - tags.extend(((str(x), MAP_X_TAG_CATEGORY), - (str(y), MAP_Y_TAG_CATEGORY), - (str(z), MAP_Z_TAG_CATEGORY))) + tags.extend( + ( + (str(x), MAP_X_TAG_CATEGORY), + (str(y), MAP_Y_TAG_CATEGORY), + (str(z), MAP_Z_TAG_CATEGORY), + ) + ) if destination: dest = destination else: try: xdest, ydest, zdest = xyz_destination except ValueError: - return None, ["XYExit.create need either `xyz_destination=(X,Y,Z)` coordinate " - "or a `destination`."] + return None, [ + "XYExit.create need either `xyz_destination=(X,Y,Z)` coordinate " + "or a `destination`." + ] else: dest = XYZRoom.objects.get_xyz(xyz=(xdest, ydest, zdest)) - tags.extend(((str(xdest), MAP_XDEST_TAG_CATEGORY), - (str(ydest), MAP_YDEST_TAG_CATEGORY), - (str(zdest), MAP_ZDEST_TAG_CATEGORY))) + tags.extend( + ( + (str(xdest), MAP_XDEST_TAG_CATEGORY), + (str(ydest), MAP_YDEST_TAG_CATEGORY), + (str(zdest), MAP_ZDEST_TAG_CATEGORY), + ) + ) return DefaultExit.create( - key, source, dest, - account=account, tags=tags, typeclass=cls, **kwargs) + key, source, dest, account=account, tags=tags, typeclass=cls, **kwargs + ) diff --git a/evennia/contrib/rpg/dice/__init__.py b/evennia/contrib/rpg/dice/__init__.py index 1c196bb27f..924c6611fe 100644 --- a/evennia/contrib/rpg/dice/__init__.py +++ b/evennia/contrib/rpg/dice/__init__.py @@ -6,4 +6,4 @@ Rolling dice - Griatch, 2012 from .dice import roll # noqa from .dice import roll_dice # noqa from .dice import CmdDice # noqa -from .dice import DiceCmdSet # noqa +from .dice import DiceCmdSet # noqa diff --git a/evennia/contrib/rpg/dice/dice.py b/evennia/contrib/rpg/dice/dice.py index 7b7078684d..7e6776e433 100644 --- a/evennia/contrib/rpg/dice/dice.py +++ b/evennia/contrib/rpg/dice/dice.py @@ -58,8 +58,7 @@ from random import randint from evennia import default_cmds, CmdSet -def roll(dicenum, dicetype, modifier=None, - conditional=None, return_tuple=False): +def roll(dicenum, dicetype, modifier=None, conditional=None, return_tuple=False): """ This is a standard dice roller. @@ -141,6 +140,7 @@ def roll(dicenum, dicetype, modifier=None, else: return result + # legacy alias roll_dice = roll diff --git a/evennia/contrib/rpg/rpsystem/__init__.py b/evennia/contrib/rpg/rpsystem/__init__.py index d78217ea7f..9affd32487 100644 --- a/evennia/contrib/rpg/rpsystem/__init__.py +++ b/evennia/contrib/rpg/rpsystem/__init__.py @@ -7,7 +7,7 @@ from .rpsystem import EmoteError, SdescError, RecogError, LanguageError # noqa from .rpsystem import ordered_permutation_regex, regex_tuple_from_key_alias # noqa from .rpsystem import parse_language, parse_sdescs_and_recogs, send_emote # noqa from .rpsystem import SdescHandler, RecogHandler # noqa -from .rpsystem import RPCommand, CmdEmote, CmdSay, CmdSdesc, CmdPose, CmdRecog, CmdMask # noqa +from .rpsystem import RPCommand, CmdEmote, CmdSay, CmdSdesc, CmdPose, CmdRecog, CmdMask # noqa from .rpsystem import RPSystemCmdSet # noqa from .rpsystem import ContribRPObject # noqa from .rpsystem import ContribRPRoom # noqa diff --git a/evennia/contrib/rpg/rpsystem/rplanguage.py b/evennia/contrib/rpg/rpsystem/rplanguage.py index d3d561c85e..4c47fc63b7 100644 --- a/evennia/contrib/rpg/rpsystem/rplanguage.py +++ b/evennia/contrib/rpg/rpsystem/rplanguage.py @@ -315,7 +315,8 @@ class LanguageHandler(DefaultScript): raise IndexError( "Could not find a matching phoneme for the grammar " f"'{match.group()}'. Make there is at least one phoneme matching this " - "combination of consonants and vowels.") + "combination of consonants and vowels." + ) translation[word.lower()] = new_word.lower() if manual_translations: diff --git a/evennia/contrib/rpg/rpsystem/rpsystem.py b/evennia/contrib/rpg/rpsystem/rpsystem.py index f96adad6b6..6a34d39baf 100644 --- a/evennia/contrib/rpg/rpsystem/rpsystem.py +++ b/evennia/contrib/rpg/rpsystem/rpsystem.py @@ -513,7 +513,7 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ errors.append(_EMOTE_NOMATCH_ERROR.format(ref=marker_match.group())) elif nmatches == 1: # a unique match - parse into intermediary representation - case = '~' # retain original case of sdesc + case = "~" # retain original case of sdesc if case_sensitive: # case sensitive mode # internal flags for the case used for the original /query @@ -526,14 +526,14 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ # self-refs are kept as-is, others are parsed by case matchtext = marker_match.group().lstrip(_PREFIX) if matchtext.istitle(): - case = 't' + case = "t" elif matchtext.isupper(): - case = '^' + case = "^" elif matchtext.islower(): - case = 'v' + case = "v" key = "#%i%s" % (obj.id, case) - string = string[:istart0] + "{%s}" % key + string[istart + maxscore:] + string = string[:istart0] + "{%s}" % key + string[istart + maxscore :] mapping[key] = obj else: @@ -601,8 +601,9 @@ def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs): """ case_sensitive = kwargs.pop("case_sensitive", True) try: - emote, obj_mapping = parse_sdescs_and_recogs(sender, receivers, emote, - case_sensitive=case_sensitive) + emote, obj_mapping = parse_sdescs_and_recogs( + sender, receivers, emote, case_sensitive=case_sensitive + ) emote, language_mapping = parse_language(sender, emote) except (EmoteError, LanguageError) as err: # handle all error messages, don't hide actual coding errors @@ -615,8 +616,8 @@ def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs): # (the text could have nested object mappings). emote = _RE_REF.sub(r"{{#\1}}", emote) # if anonymous_add is passed as a kwarg, collect and remove it from kwargs - if 'anonymous_add' in kwargs: - anonymous_add = kwargs.pop('anonymous_add') + if "anonymous_add" in kwargs: + anonymous_add = kwargs.pop("anonymous_add") if anonymous_add and not any(1 for tag in obj_mapping if tag.startswith(skey)): # no self-reference in the emote - add to the end obj_mapping[skey] = sender @@ -670,12 +671,13 @@ def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs): ) # make sure receiver always sees their real name rkey_start = "#%i" % receiver.id - rkey_keep_case = rkey_start + '~' # signifies keeping the case + rkey_keep_case = rkey_start + "~" # signifies keeping the case for rkey in (key for key in receiver_sdesc_mapping if key.startswith(rkey_start)): # we could have #%i^, #%it etc depending on input case - we want the # self-reference to retain case. receiver_sdesc_mapping[rkey] = process_sdesc( - receiver.key, receiver, ref=rkey_keep_case, **kwargs) + receiver.key, receiver, ref=rkey_keep_case, **kwargs + ) # do the template replacement of the sdesc/recog {#num} markers receiver.msg(sendemote.format(**receiver_sdesc_mapping), from_obj=sender, **kwargs) @@ -1709,14 +1711,14 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject): if not sdesc: return "" - ref = kwargs.get('ref', '~') # ~ to keep sdesc unchanged - if 't' in ref: + ref = kwargs.get("ref", "~") # ~ to keep sdesc unchanged + if "t" in ref: # we only want to capitalize the first letter if there are many words sdesc = sdesc.lower() sdesc = sdesc[0].upper() + sdesc[1:] if len(sdesc) > 1 else sdesc.upper() - elif '^' in ref: + elif "^" in ref: sdesc = sdesc.upper() - elif 'v' in ref: + elif "v" in ref: sdesc = sdesc.lower() return "|b%s|n" % sdesc diff --git a/evennia/contrib/rpg/rpsystem/tests.py b/evennia/contrib/rpg/rpsystem/tests.py index 292d9b0469..cd96ac43b1 100644 --- a/evennia/contrib/rpg/rpsystem/tests.py +++ b/evennia/contrib/rpg/rpsystem/tests.py @@ -274,7 +274,7 @@ class TestRPSystem(BaseEvenniaTest): result = rpsystem.regex_tuple_from_key_alias(self.speaker) t2 = time.time() # print(f"t1: {t1 - t0}, t2: {t2 - t1}") - self.assertLess(t2 - t1, 10 ** -4) + self.assertLess(t2 - t1, 10**-4) self.assertEqual(result, (Anything, self.speaker, self.speaker.key)) diff --git a/evennia/contrib/rpg/traits/tests.py b/evennia/contrib/rpg/traits/tests.py index bb31bbfc13..ddc746d2c4 100644 --- a/evennia/contrib/rpg/traits/tests.py +++ b/evennia/contrib/rpg/traits/tests.py @@ -59,16 +59,28 @@ class TraitHandlerTest(_TraitHandlerBase): super().setUp() self.traithandler.add("test1", name="Test1", trait_type="trait") self.traithandler.add( - "test2", name="Test2", trait_type="trait", value=["foo", {"1": [1, 2, 3]}, 4], + "test2", + name="Test2", + trait_type="trait", + value=["foo", {"1": [1, 2, 3]}, 4], ) def test_add_trait(self): self.assertEqual( - self._get_dbstore("test1"), {"name": "Test1", "trait_type": "trait", "value": None,} + self._get_dbstore("test1"), + { + "name": "Test1", + "trait_type": "trait", + "value": None, + }, ) self.assertEqual( self._get_dbstore("test2"), - {"name": "Test2", "trait_type": "trait", "value": ["foo", {"1": [1, 2, 3]}, 4],}, + { + "name": "Test2", + "trait_type": "trait", + "value": ["foo", {"1": [1, 2, 3]}, 4], + }, ) self.assertEqual(len(self.traithandler), 2) @@ -328,7 +340,12 @@ class TestTraitCounter(_TraitHandlerBase): max=10, extra_val1="xvalue1", extra_val2="xvalue2", - descs={0: "range0", 2: "range1", 5: "range2", 7: "range3",}, + descs={ + 0: "range0", + 2: "range1", + 5: "range2", + 7: "range3", + }, ) self.trait = self.traithandler.get("test1") @@ -348,7 +365,12 @@ class TestTraitCounter(_TraitHandlerBase): "max": 10, "extra_val1": "xvalue1", "extra_val2": "xvalue2", - "descs": {0: "range0", 2: "range1", 5: "range2", 7: "range3",}, + "descs": { + 0: "range0", + 2: "range1", + 5: "range2", + 7: "range3", + }, "rate": 0, "ratetarget": None, "last_update": None, @@ -507,7 +529,12 @@ class TestTraitCounterTimed(_TraitHandlerBase): max=100, extra_val1="xvalue1", extra_val2="xvalue2", - descs={0: "range0", 2: "range1", 5: "range2", 7: "range3",}, + descs={ + 0: "range0", + 2: "range1", + 5: "range2", + 7: "range3", + }, rate=1, ratetarget=None, ) @@ -579,7 +606,12 @@ class TestTraitGauge(_TraitHandlerBase): mod=2, extra_val1="xvalue1", extra_val2="xvalue2", - descs={0: "range0", 2: "range1", 5: "range2", 7: "range3",}, + descs={ + 0: "range0", + 2: "range1", + 5: "range2", + 7: "range3", + }, ) self.trait = self.traithandler.get("test1") @@ -598,7 +630,12 @@ class TestTraitGauge(_TraitHandlerBase): "min": 0, "extra_val1": "xvalue1", "extra_val2": "xvalue2", - "descs": {0: "range0", 2: "range1", 5: "range2", 7: "range3",}, + "descs": { + 0: "range0", + 2: "range1", + 5: "range2", + 7: "range3", + }, "rate": 0, "ratetarget": None, "last_update": None, @@ -763,7 +800,12 @@ class TestTraitGaugeTimed(_TraitHandlerBase): min=0, extra_val1="xvalue1", extra_val2="xvalue2", - descs={0: "range0", 2: "range1", 5: "range2", 7: "range3",}, + descs={ + 0: "range0", + 2: "range1", + 5: "range2", + 7: "range3", + }, rate=1, ratetarget=None, ) @@ -831,8 +873,20 @@ class TestNumericTraitOperators(BaseEvenniaTestCase): def setUp(self): # direct instantiation for testing only; use TraitHandler in production - self.st = traits.Trait({"name": "Strength", "trait_type": "trait", "value": 8,}) - self.at = traits.Trait({"name": "Attack", "trait_type": "trait", "value": 4,}) + self.st = traits.Trait( + { + "name": "Strength", + "trait_type": "trait", + "value": 8, + } + ) + self.at = traits.Trait( + { + "name": "Attack", + "trait_type": "trait", + "value": 4, + } + ) def tearDown(self): self.st, self.at = None, None diff --git a/evennia/contrib/rpg/traits/traits.py b/evennia/contrib/rpg/traits/traits.py index f178c68e80..fbc4f664bc 100644 --- a/evennia/contrib/rpg/traits/traits.py +++ b/evennia/contrib/rpg/traits/traits.py @@ -526,6 +526,7 @@ class MandatoryTraitKey: """ + class TraitHandler: """ Factory class that instantiates Trait objects. Must be assigned as a property @@ -794,10 +795,7 @@ class TraitProperty: trait = traithandler.get(self._trait_key) if trait is None: # initialize the trait - traithandler.add( - self._trait_key, - **self._trait_properties - ) + traithandler.add(self._trait_key, **self._trait_properties) trait = traithandler.get(self._trait_key) # caches it in the traithandler self._cache[instance] = trait return self._cache[instance] @@ -809,6 +807,7 @@ class TraitProperty: """ pass + # Parent Trait class diff --git a/evennia/contrib/tutorials/red_button/red_button.py b/evennia/contrib/tutorials/red_button/red_button.py index 0e3484ab3d..28689fa015 100644 --- a/evennia/contrib/tutorials/red_button/red_button.py +++ b/evennia/contrib/tutorials/red_button/red_button.py @@ -42,6 +42,7 @@ from evennia.utils.utils import delay, repeat, interactive # Commands for the state when the lid covering the button is closed. + class CmdPushLidClosed(Command): """ Push the red button (lid closed) @@ -121,23 +122,27 @@ class CmdSmashGlass(Command): """ rand = random.random() self.caller.location.msg_contents( - f"{self.caller.name} tries to smash the glass of the button.", - exclude=self.caller) + f"{self.caller.name} tries to smash the glass of the button.", exclude=self.caller + ) if rand < 0.2: - string = ("You smash your hand against the glass" - " with all your might. The lid won't budge" - " but you cause quite the tremor through the button's mount." - "\nIt looks like the button's lamp stopped working for the time being, " - "but the lid is still as closed as ever.") + string = ( + "You smash your hand against the glass" + " with all your might. The lid won't budge" + " but you cause quite the tremor through the button's mount." + "\nIt looks like the button's lamp stopped working for the time being, " + "but the lid is still as closed as ever." + ) # self.obj is the button itself self.obj.break_lamp() elif rand < 0.6: string = "You hit the lid hard. It doesn't move an inch." else: - string = ("You place a well-aimed fist against the glass of the lid." - " Unfortunately all you get is a pain in your hand. Maybe" - " you should just try to just ... open the lid instead?") + string = ( + "You place a well-aimed fist against the glass of the lid." + " Unfortunately all you get is a pain in your hand. Maybe" + " you should just try to just ... open the lid instead?" + ) self.caller.msg(string) @@ -165,8 +170,8 @@ class CmdOpenLid(Command): string += "the lid will soon close again." self.caller.msg(string) self.caller.location.msg_contents( - f"{self.caller.name} opens the lid of the button.", - exclude=self.caller) + f"{self.caller.name} opens the lid of the button.", exclude=self.caller + ) self.obj.to_open_state() @@ -200,6 +205,7 @@ class LidClosedCmdSet(CmdSet): # Commands for the state when the button's protective cover is open - now the # push command will work. You can also close the lid again. + class CmdPushLidOpen(Command): """ Push the red button @@ -225,15 +231,15 @@ class CmdPushLidOpen(Command): """ # pause a little between each message. self.caller.msg("You reach out to press the big red button ...") - yield(2) # pause 2s before next message + yield (2) # pause 2s before next message self.caller.msg("\n\n|wBOOOOM! A bright light blinds you!|n") - yield(1) # pause 1s before next message + yield (1) # pause 1s before next message self.caller.msg("\n\n|xThe world goes dark ...|n") name = self.caller.name self.caller.location.msg_contents( - f"{name} presses the button. BOOM! {name} is blinded by a flash!", - exclude=self.caller) + f"{name} presses the button. BOOM! {name} is blinded by a flash!", exclude=self.caller + ) self.obj.blind_target(self.caller) @@ -259,8 +265,8 @@ class CmdCloseLid(Command): # this will clean out scripts dependent on lid being open. self.caller.msg("You close the button's lid. It clicks back into place.") self.caller.location.msg_contents( - f"{self.caller.name} closes the button's lid.", - exclude=self.caller) + f"{self.caller.name} closes the button's lid.", exclude=self.caller + ) class LidOpenCmdSet(CmdSet): @@ -286,6 +292,7 @@ class LidOpenCmdSet(CmdSet): # Commands for when the button has been pushed and the player is blinded. This # replaces commands on the player making them 'blind' for a while. + class CmdBlindLook(Command): """ Looking around in darkness @@ -317,12 +324,14 @@ class CmdBlindLook(Command): string = "You fumble around, hands outstretched. You bump your knee." else: # trying to look - string = ("You are temporarily blinded by the flash. " - "Until it wears off, all you can do is feel around blindly.") + string = ( + "You are temporarily blinded by the flash. " + "Until it wears off, all you can do is feel around blindly." + ) self.caller.msg(string) self.caller.location.msg_contents( - f"{self.caller.name} stumbles around, blinded.", - exclude=self.caller) + f"{self.caller.name} stumbles around, blinded.", exclude=self.caller + ) class CmdBlindHelp(Command): @@ -420,20 +429,26 @@ class RedButton(DefaultObject): `button = create_object(RedButton, ..., attributes=[('desc', 'my desc')])`. """ + # these are the pre-set descriptions. Setting attributes will override # these on the fly. - desc_closed_lid = ("This is a large red button, inviting yet evil-looking. " - "A closed glass lid protects it.") - desc_open_lid = ("This is a large red button, inviting yet evil-looking. " - "Its glass cover is open and the button exposed.") + desc_closed_lid = ( + "This is a large red button, inviting yet evil-looking. " "A closed glass lid protects it." + ) + desc_open_lid = ( + "This is a large red button, inviting yet evil-looking. " + "Its glass cover is open and the button exposed." + ) auto_close_msg = "The button's glass lid silently slides back in place." lamp_breaks_msg = "The lamp flickers, the button going dark." desc_add_lamp_broken = "\nThe big red button has stopped blinking for the time being." # note that this is a list. A random message will display each time - blink_msgs = ["The red button flashes briefly.", - "The red button blinks invitingly.", - "The red button flashes. You know you wanna push it!"] + blink_msgs = [ + "The red button flashes briefly.", + "The red button blinks invitingly.", + "The red button flashes. You know you wanna push it!", + ] def at_object_creation(self): """ @@ -523,9 +538,9 @@ class RedButton(DefaultObject): self.cmdset.add(LidOpenCmdSet) # wait 20s then call self.to_closed_state with a message as argument - delay(35, self.to_closed_state, - self.db.auto_close_msg or self.auto_close_msg, - persistent=True) + delay( + 35, self.to_closed_state, self.db.auto_close_msg or self.auto_close_msg, persistent=True + ) def _unblind_target(self, caller): """ @@ -536,7 +551,8 @@ class RedButton(DefaultObject): caller.msg("You blink feverishly as your eyesight slowly returns.") self.location.msg_contents( f"{caller.name} seems to be recovering their eyesight, blinking feverishly.", - exclude=caller) + exclude=caller, + ) def blind_target(self, caller): """ @@ -554,8 +570,7 @@ class RedButton(DefaultObject): # wait 20s then call self._unblind to remove blindness effect. The # persistent=True means the delay should survive a server reload. - delay(20, self._unblind_target, caller, - persistent=True) + delay(20, self._unblind_target, caller, persistent=True) def _unbreak_lamp(self): """ diff --git a/evennia/contrib/tutorials/talking_npc/talking_npc.py b/evennia/contrib/tutorials/talking_npc/talking_npc.py index e66483c3b5..b7c90bf1e8 100644 --- a/evennia/contrib/tutorials/talking_npc/talking_npc.py +++ b/evennia/contrib/tutorials/talking_npc/talking_npc.py @@ -65,8 +65,10 @@ def info2(caller): def info3(caller): - text = ("'Well ... I'm sort of busy so, have to go. NPC business. " - "Important stuff. You wouldn't understand.'") + text = ( + "'Well ... I'm sort of busy so, have to go. NPC business. " + "Important stuff. You wouldn't understand.'" + ) options = ( {"desc": "Oookay ... I won't keep you. Bye.", "goto": "END"}, @@ -91,15 +93,15 @@ def END(caller): class CmdTalk(default_cmds.MuxCommand): """ - Talks to an npc + Talks to an npc - Usage: - talk + Usage: + talk - This command is only available if a talkative non-player-character - (NPC) is actually present. It will strike up a conversation with - that NPC and give you options on what to talk about. - """ + This command is only available if a talkative non-player-character + (NPC) is actually present. It will strike up a conversation with + that NPC and give you options on what to talk about. + """ key = "talk" locks = "cmd:all()" @@ -113,8 +115,11 @@ class CmdTalk(default_cmds.MuxCommand): # Initiate the menu. Change this if you are putting this on # some other custom NPC class. - EvMenu(self.caller, "evennia.contrib.tutorials.talking_npc.talking_npc", - startnode="menu_start_node") + EvMenu( + self.caller, + "evennia.contrib.tutorials.talking_npc.talking_npc", + startnode="menu_start_node", + ) class TalkingCmdSet(CmdSet): diff --git a/evennia/contrib/tutorials/tutorial_world/objects.py b/evennia/contrib/tutorials/tutorial_world/objects.py index 8fce833bb0..f8722dd7fc 100644 --- a/evennia/contrib/tutorials/tutorial_world/objects.py +++ b/evennia/contrib/tutorials/tutorial_world/objects.py @@ -1158,7 +1158,8 @@ class TutorialWeaponRack(TutorialObject): |wstab/thrust/pierce |n - poke at the enemy. More damage but harder to hit. |wslash/chop/bash |n - swipe at the enemy. Less damage but easier to hit. |wdefend/parry|n - protect yourself and make yourself harder to hit.) - """).strip() + """ + ).strip() self.db.no_more_weapons_msg = "you find nothing else of use." self.db.available_weapons = ["knife", "dagger", "sword", "club"] diff --git a/evennia/contrib/tutorials/tutorial_world/rooms.py b/evennia/contrib/tutorials/tutorial_world/rooms.py index 50c9bacb3f..8f23363134 100644 --- a/evennia/contrib/tutorials/tutorial_world/rooms.py +++ b/evennia/contrib/tutorials/tutorial_world/rooms.py @@ -78,6 +78,7 @@ class CmdTutorial(Command): helptext += "\n\n (Write 'give up' if you want to abandon your quest.)" caller.msg(helptext) + # for the @detail command we inherit from MuxCommand, since # we want to make use of MuxCommand's pre-parsing of '=' in the # argument. @@ -202,22 +203,26 @@ class CmdTutorialLook(default_cmds.CmdLook): looking_at_obj.at_desc(looker=caller) return + class CmdTutorialGiveUp(default_cmds.MuxCommand): """ Give up the tutorial-world quest and return to Limbo, the start room of the server. """ + key = "give up" - aliases = ['abort'] + aliases = ["abort"] def func(self): outro_room = OutroRoom.objects.all() if outro_room: outro_room = outro_room[0] else: - self.caller.msg("That didn't work (seems like a bug). " - "Try to use the |wteleport|n command instead.") + self.caller.msg( + "That didn't work (seems like a bug). " + "Try to use the |wteleport|n command instead." + ) return self.caller.move_to(outro_room) @@ -312,6 +317,7 @@ class TutorialStartExit(DefaultExit): will also clean up the intro command. """ + def at_object_creation(self): self.cmdset.add(CmdSetEvenniaIntro, persistent=True) @@ -397,6 +403,7 @@ SUPERUSER_WARNING = ( # # ------------------------------------------------------------- + class CmdEvenniaIntro(Command): """ Start the Evennia intro wizard. @@ -405,10 +412,12 @@ class CmdEvenniaIntro(Command): intro """ + key = "intro" def func(self): from .intro_menu import init_menu + # quell also superusers if self.caller.account: self.caller.msg("Auto-quelling permissions while in intro ...") @@ -463,6 +472,7 @@ class IntroRoom(TutorialRoom): character.account.execute_cmd("quell") character.msg("(Auto-quelling while in tutorial-world)") + # ------------------------------------------------------------- # # Bridge - unique room @@ -1176,4 +1186,3 @@ class OutroRoom(TutorialRoom): def at_object_leave(self, character, destination): if character.account: character.account.execute_cmd("unquell") - diff --git a/evennia/contrib/utils/auditing/tests.py b/evennia/contrib/utils/auditing/tests.py index 626e7e291d..bdd288315c 100644 --- a/evennia/contrib/utils/auditing/tests.py +++ b/evennia/contrib/utils/auditing/tests.py @@ -12,10 +12,8 @@ from .server import AuditedServerSession from evennia.server.sessionhandler import SESSIONS -@override_settings( - AUDIT_MASKS=[]) +@override_settings(AUDIT_MASKS=[]) class AuditingTest(BaseEvenniaTest): - @patch("evennia.server.sessionhandler._ServerSession", AuditedServerSession) def setup_session(self): """Overrides default one in EvenniaTest""" @@ -29,8 +27,10 @@ class AuditingTest(BaseEvenniaTest): SESSIONS.login(session, self.account, testmode=True) self.session = session - @patch("evennia.contrib.utils.auditing.server.AUDIT_CALLBACK", - "evennia.contrib.utils.auditing.outputs.to_syslog") + @patch( + "evennia.contrib.utils.auditing.server.AUDIT_CALLBACK", + "evennia.contrib.utils.auditing.outputs.to_syslog", + ) @patch("evennia.contrib.utils.auditing.server.AUDIT_IN", True) @patch("evennia.contrib.utils.auditing.server.AUDIT_OUT", True) def test_mask(self): @@ -100,8 +100,10 @@ class AuditingTest(BaseEvenniaTest): for secret in secrets: self.assertEqual(self.session.mask(secret), secret) - @patch("evennia.contrib.utils.auditing.server.AUDIT_CALLBACK", - "evennia.contrib.utils.auditing.outputs.to_syslog") + @patch( + "evennia.contrib.utils.auditing.server.AUDIT_CALLBACK", + "evennia.contrib.utils.auditing.outputs.to_syslog", + ) @patch("evennia.contrib.utils.auditing.server.AUDIT_IN", True) @patch("evennia.contrib.utils.auditing.server.AUDIT_OUT", True) def test_audit(self): diff --git a/evennia/contrib/utils/fieldfill/__init__.py b/evennia/contrib/utils/fieldfill/__init__.py index ff8ce2b500..1f214b2c8d 100644 --- a/evennia/contrib/utils/fieldfill/__init__.py +++ b/evennia/contrib/utils/fieldfill/__init__.py @@ -5,6 +5,6 @@ FieldFill contrib - Tim Ashley Jenkins 2018 from .fieldfill import FieldEvMenu # noqa from .fieldfill import CmdTestMenu # noqa -from .fieldfill import init_fill_field # noqa -from .fieldfill import form_template_to_dict # noqa -from .fieldfill import display_formdata # noqa +from .fieldfill import init_fill_field # noqa +from .fieldfill import form_template_to_dict # noqa +from .fieldfill import display_formdata # noqa diff --git a/evennia/contrib/utils/random_string_generator/__init__.py b/evennia/contrib/utils/random_string_generator/__init__.py index 8c64d91367..67b1030955 100644 --- a/evennia/contrib/utils/random_string_generator/__init__.py +++ b/evennia/contrib/utils/random_string_generator/__init__.py @@ -5,4 +5,4 @@ Pseudo-random generator - vlgeoff 2017 from .random_string_generator import RandomStringGenerator # noqa from .random_string_generator import RandomStringGeneratorScript # noqa -from .random_string_generator import RejectedRegex, ExhaustedGenerator # noqa +from .random_string_generator import RejectedRegex, ExhaustedGenerator # noqa diff --git a/evennia/contrib/utils/random_string_generator/random_string_generator.py b/evennia/contrib/utils/random_string_generator/random_string_generator.py index a639f407f4..633bfcf189 100644 --- a/evennia/contrib/utils/random_string_generator/random_string_generator.py +++ b/evennia/contrib/utils/random_string_generator/random_string_generator.py @@ -156,8 +156,10 @@ class RandomStringGenerator: self._find_elements(regex) def __repr__(self): - return "".format( - self.name + return ( + "".format( + self.name + ) ) def _get_script(self): @@ -169,7 +171,8 @@ class RandomStringGenerator: script = ScriptDB.objects.get(db_key="generator_script") except ScriptDB.DoesNotExist: script = create_script( - "evennia.contrib.utils.random_string_generator.RandomStringGeneratorScript") + "evennia.contrib.utils.random_string_generator.RandomStringGeneratorScript" + ) type(self).script = script return script diff --git a/evennia/game_template/commands/command.py b/evennia/game_template/commands/command.py index de804eaf5a..f7b4c15b7e 100644 --- a/evennia/game_template/commands/command.py +++ b/evennia/game_template/commands/command.py @@ -19,6 +19,7 @@ class Command(BaseCommand): here. Without setting one, the parent's docstring will show (like now). """ + # Each Command class implements the following methods, called in this order # (only func() is actually required): # diff --git a/evennia/game_template/typeclasses/objects.py b/evennia/game_template/typeclasses/objects.py index 3fa0c7e721..aae01e508f 100644 --- a/evennia/game_template/typeclasses/objects.py +++ b/evennia/game_template/typeclasses/objects.py @@ -157,6 +157,6 @@ class Object(DefaultObject): at_say(speaker, message) - by default, called if an object inside this object speaks - """ + """ pass diff --git a/evennia/game_template/web/urls.py b/evennia/game_template/web/urls.py index 2bbb61dbbe..e38eb472c9 100644 --- a/evennia/game_template/web/urls.py +++ b/evennia/game_template/web/urls.py @@ -13,6 +13,7 @@ Search the Django documentation for "URL dispatcher" for more help. """ from django.urls import path, include + # default evennia patterns from evennia.web.urls import urlpatterns as evennia_default_urlpatterns @@ -24,7 +25,6 @@ urlpatterns = [ path("webclient/", include("web.webclient.urls")), # web admin path("admin/", include("web.admin.urls")), - # add any extra urls here: # path("mypath/", include("path.to.my.urls.file")), ] diff --git a/evennia/game_template/world/help_entries.py b/evennia/game_template/world/help_entries.py index f68af62797..58c3af442f 100644 --- a/evennia/game_template/world/help_entries.py +++ b/evennia/game_template/world/help_entries.py @@ -27,7 +27,7 @@ Each dict is on the form HELP_ENTRY_DICTS = [ { "key": "evennia", - "aliases": ['ev'], + "aliases": ["ev"], "category": "General", "locks": "read:perm(Developer)", "text": """ @@ -51,7 +51,7 @@ HELP_ENTRY_DICTS = [ There is also a discord channel you can find from the sidebard on evennia.com. - """ + """, }, { "key": "building", @@ -60,6 +60,6 @@ HELP_ENTRY_DICTS = [ Evennia comes with a bunch of default building commands. You can find a building tutorial in the evennia documentation. - """ - } + """, + }, ] diff --git a/evennia/help/filehelp.py b/evennia/help/filehelp.py index bd54469973..3825dd1358 100644 --- a/evennia/help/filehelp.py +++ b/evennia/help/filehelp.py @@ -69,8 +69,7 @@ from dataclasses import dataclass from django.conf import settings from django.urls import reverse from django.utils.text import slugify -from evennia.utils.utils import ( - variable_from_module, make_iter, all_from_module) +from evennia.utils.utils import variable_from_module, make_iter, all_from_module from evennia.utils import logger from evennia.utils.utils import lazy_property from evennia.locks.lockhandler import LockHandler @@ -86,6 +85,7 @@ class FileHelpEntry: help command. """ + key: str aliases: list help_category: str @@ -147,7 +147,7 @@ class FileHelpEntry: """ try: return reverse( - 'help-entry-detail', + "help-entry-detail", kwargs={"category": slugify(self.help_category), "topic": slugify(self.key)}, ) except Exception: @@ -192,8 +192,7 @@ class FileHelpStorageHandler: """ Initialize the storage. """ - self.help_file_modules = [str(part).strip() - for part in make_iter(help_file_modules)] + self.help_file_modules = [str(part).strip() for part in make_iter(help_file_modules)] self.help_entries = [] self.help_entries_dict = {} self.load() @@ -206,13 +205,11 @@ class FileHelpStorageHandler: loaded_help_dicts = [] for module_or_path in self.help_file_modules: - help_dict_list = variable_from_module( - module_or_path, variable="HELP_ENTRY_DICTS" - ) + help_dict_list = variable_from_module(module_or_path, variable="HELP_ENTRY_DICTS") if not help_dict_list: help_dict_list = [ - dct for dct in all_from_module(module_or_path).values() - if isinstance(dct, dict)] + dct for dct in all_from_module(module_or_path).values() if isinstance(dct, dict) + ] if help_dict_list: loaded_help_dicts.extend(help_dict_list) else: @@ -223,19 +220,23 @@ class FileHelpStorageHandler: unique_help_entries = {} for dct in loaded_help_dicts: - key = dct.get('key').lower().strip() - category = dct.get('category', _DEFAULT_HELP_CATEGORY).strip() - aliases = list(dct.get('aliases', [])) - entrytext = dct.get('text', '') - locks = dct.get('locks', '') + key = dct.get("key").lower().strip() + category = dct.get("category", _DEFAULT_HELP_CATEGORY).strip() + aliases = list(dct.get("aliases", [])) + entrytext = dct.get("text", "") + locks = dct.get("locks", "") if not key and entrytext: logger.error(f"Cannot load file-help-entry (missing key or text): {dct}") continue unique_help_entries[key] = FileHelpEntry( - key=key, help_category=category, aliases=aliases, lock_storage=locks, - entrytext=entrytext) + key=key, + help_category=category, + aliases=aliases, + lock_storage=locks, + entrytext=entrytext, + ) self.help_entries_dict = unique_help_entries self.help_entries = list(unique_help_entries.values()) diff --git a/evennia/help/tests.py b/evennia/help/tests.py index 5f39821779..ce0442ac74 100644 --- a/evennia/help/tests.py +++ b/evennia/help/tests.py @@ -23,7 +23,8 @@ class TestParseSubtopics(TestCase): """ self.maxDiff = None - entry = dedent(""" + entry = dedent( + """ Main topic text # subtopics ## foo @@ -36,7 +37,9 @@ class TestParseSubtopics(TestCase): Bar subcategory ### moo Bar/Moo subcategory - """, indent=0) + """, + indent=0, + ) expected = { None: "Main topic text", "foo": { @@ -45,15 +48,10 @@ class TestParseSubtopics(TestCase): None: "\nFoo/Moo subsub-category\n", "dum": { None: "\nFoo/Moo/Dum subsubsub-category\n", - } + }, }, }, - "bar": { - None: "\nBar subcategory\n", - "moo": { - None: "\nBar/Moo subcategory" - } - } + "bar": {None: "\nBar subcategory\n", "moo": {None: "\nBar/Moo subcategory"}}, } actual_result = help_utils.parse_entry_for_subcategories(entry) @@ -65,28 +63,30 @@ class TestParseSubtopics(TestCase): """ - entry = dedent(""" + entry = dedent( + """ Main topic text # SUBTOPICS ## creating extra stuff Help on creating extra stuff. - """, indent=0) + """, + indent=0, + ) expected = { None: "Main topic text", - "creating extra stuff": { - None: "\nHelp on creating extra stuff." - } + "creating extra stuff": {None: "\nHelp on creating extra stuff."}, } actual_result = help_utils.parse_entry_for_subcategories(entry) self.assertEqual(expected, actual_result) + # test filehelp system HELP_ENTRY_DICTS = [ { "key": "evennia", - "aliases": ['ev'], + "aliases": ["ev"], "category": "General", "text": """ Evennia is a MUD game server in Python. @@ -105,7 +105,7 @@ HELP_ENTRY_DICTS = [ There is also a discord channel you can find from the sidebard on evennia.com. - """ + """, }, { "key": "building", @@ -114,12 +114,11 @@ HELP_ENTRY_DICTS = [ Evennia comes with a bunch of default building commands. You can find a building tutorial in the evennia documentation. - """ - } + """, + }, ] - class TestFileHelp(TestCase): """ Test the File-help system @@ -135,7 +134,7 @@ class TestFileHelp(TestCase): result = storage.all() for inum, helpentry in enumerate(result): - self.assertEqual(HELP_ENTRY_DICTS[inum]['key'], helpentry.key) - self.assertEqual(HELP_ENTRY_DICTS[inum].get('aliases', []), helpentry.aliases) - self.assertEqual(HELP_ENTRY_DICTS[inum]['category'], helpentry.help_category) - self.assertEqual(HELP_ENTRY_DICTS[inum]['text'], helpentry.entrytext) + self.assertEqual(HELP_ENTRY_DICTS[inum]["key"], helpentry.key) + self.assertEqual(HELP_ENTRY_DICTS[inum].get("aliases", []), helpentry.aliases) + self.assertEqual(HELP_ENTRY_DICTS[inum]["category"], helpentry.help_category) + self.assertEqual(HELP_ENTRY_DICTS[inum]["text"], helpentry.entrytext) diff --git a/evennia/help/utils.py b/evennia/help/utils.py index 52ef5ce5de..0fc3fafce1 100644 --- a/evennia/help/utils.py +++ b/evennia/help/utils.py @@ -19,11 +19,9 @@ _LUNR_EXCEPTION = None _LUNR_GET_BUILDER = None _LUNR_BUILDER_PIPELINE = None -_RE_HELP_SUBTOPICS_START = re.compile( - r"^\s*?#\s*?subtopics\s*?$", re.I + re.M) +_RE_HELP_SUBTOPICS_START = re.compile(r"^\s*?#\s*?subtopics\s*?$", re.I + re.M) _RE_HELP_SUBTOPIC_SPLIT = re.compile(r"^\s*?(\#{2,6}\s*?\w+?[a-z0-9 \-\?!,\.]*?)$", re.M + re.I) -_RE_HELP_SUBTOPIC_PARSE = re.compile( - r"^(?P\#{2,6})\s*?(?P.*?)$", re.I + re.M) +_RE_HELP_SUBTOPIC_PARSE = re.compile(r"^(?P\#{2,6})\s*?(?P.*?)$", re.I + re.M) MAX_SUBTOPIC_NESTING = 5 @@ -57,6 +55,7 @@ def help_search_with_index(query, candidate_entries, suggestion_maxnum=5, fields from lunr import get_default_builder as _LUNR_GET_BUILDER from lunr import stop_word_filter from lunr.stemmer import stemmer + # from lunr.trimmer import trimmer # pre-create a lunr index-builder pipeline where we've removed some of @@ -90,12 +89,7 @@ def help_search_with_index(query, candidate_entries, suggestion_maxnum=5, fields builder.pipeline.reset() builder.pipeline.add(*_LUNR_BUILDER_PIPELINE) - search_index = _LUNR( - ref="key", - fields=fields, - documents=indx, - builder=builder - ) + search_index = _LUNR(ref="key", fields=fields, documents=indx, builder=builder) try: matches = search_index.search(query)[:suggestion_maxnum] @@ -175,7 +169,7 @@ def parse_entry_for_subcategories(entry): """ topic, *subtopics = _RE_HELP_SUBTOPICS_START.split(entry, maxsplit=1) - structure = {None: topic.strip('\n')} + structure = {None: topic.strip("\n")} if subtopics: subtopics = subtopics[0] @@ -193,12 +187,13 @@ def parse_entry_for_subcategories(entry): if subtopic_match: # a new sub(-sub..) category starts. mdict = subtopic_match.groupdict() - subtopic = mdict['name'].lower().strip() - new_nesting = len(mdict['nesting']) - 1 + subtopic = mdict["name"].lower().strip() + new_nesting = len(mdict["nesting"]) - 1 if new_nesting > MAX_SUBTOPIC_NESTING: raise RuntimeError( - f"Can have max {MAX_SUBTOPIC_NESTING} levels of nested help subtopics.") + f"Can have max {MAX_SUBTOPIC_NESTING} levels of nested help subtopics." + ) nestdiff = new_nesting - current_nesting if nestdiff < 0: @@ -226,7 +221,5 @@ def parse_entry_for_subcategories(entry): if key in dct: dct = dct[key] else: - dct[key] = { - None: part - } + dct[key] = {None: part} return structure diff --git a/evennia/locks/lockfuncs.py b/evennia/locks/lockfuncs.py index b599d9d49d..34e06aa714 100644 --- a/evennia/locks/lockfuncs.py +++ b/evennia/locks/lockfuncs.py @@ -43,6 +43,7 @@ def true(*args, **kwargs): """ return True + def all(*args, **kwargs): return True diff --git a/evennia/locks/lockhandler.py b/evennia/locks/lockhandler.py index 2e6b817e9d..27fbc7de0a 100644 --- a/evennia/locks/lockhandler.py +++ b/evennia/locks/lockhandler.py @@ -235,8 +235,11 @@ class LockHandler: funcname, rest = (part.strip().strip(")") for part in funcstring.split("(", 1)) func = _LOCKFUNCS.get(funcname, None) if not callable(func): - elist.append(_("Lock: lock-function '{lockfunc}' is not available.").format( - lockfunc=funcstring)) + elist.append( + _("Lock: lock-function '{lockfunc}' is not available.").format( + lockfunc=funcstring + ) + ) continue args = list(arg.strip() for arg in rest.split(",") if arg and "=" not in arg) kwargs = dict( @@ -263,13 +266,16 @@ class LockHandler: continue if access_type in locks: duplicates += 1 - wlist.append(_( - "LockHandler on {obj}: access type '{access_type}' " - "changed from '{source}' to '{goal}' ".format( - obj=self.obj, - access_type=access_type, - source=locks[access_type][2], - goal=raw_lockstring)) + wlist.append( + _( + "LockHandler on {obj}: access type '{access_type}' " + "changed from '{source}' to '{goal}' ".format( + obj=self.obj, + access_type=access_type, + source=locks[access_type][2], + goal=raw_lockstring, + ) + ) ) locks[access_type] = (evalstring, tuple(lock_funcs), raw_lockstring) if wlist and WARNING_LOG: @@ -695,6 +701,7 @@ def check_lockstring( access_type=access_type, ) + def check_perm(obj, permission, no_superuser_bypass=False): """ Shortcut for checking if an object has the given `permission`. If the @@ -713,6 +720,7 @@ def check_perm(obj, permission, no_superuser_bypass=False): """ from evennia.locks.lockfuncs import perm + if not no_superuser_bypass and obj.is_superuser: return True return perm(obj, None, permission) diff --git a/evennia/locks/tests.py b/evennia/locks/tests.py index 1ab20b9dd6..482012d238 100644 --- a/evennia/locks/tests.py +++ b/evennia/locks/tests.py @@ -215,12 +215,11 @@ class TestPermissionCheck(BaseEvenniaTest): Test the PermissionHandler.check method """ + def test_check__success(self): """Test combinations that should pass the check""" self.assertEqual( - [perm for perm in self.char1.account.permissions.all()], - ['developer', 'player'] - + [perm for perm in self.char1.account.permissions.all()], ["developer", "player"] ) self.assertTrue(self.char1.permissions.check("Builder")) self.assertTrue(self.char1.permissions.check("Builder", "Player")) @@ -234,12 +233,11 @@ class TestPermissionCheck(BaseEvenniaTest): self.assertFalse(self.char1.permissions.check("Builder", "dummy", require_all=True)) self.assertFalse(self.char1.permissions.check("Developer", "foobar", require_all=True)) - self.char1.account.permissions.remove('developer') + self.char1.account.permissions.remove("developer") self.char1.account.permissions.add("Builder") self.assertEqual( - [perm for perm in self.char1.account.permissions.all()], - ['builder', 'player'] + [perm for perm in self.char1.account.permissions.all()], ["builder", "player"] ) self.assertFalse(self.char1.permissions.check("Developer")) self.assertFalse(self.char1.permissions.check("Developer", "Player", require_all=True)) diff --git a/evennia/objects/manager.py b/evennia/objects/manager.py index c93c2ddac8..aa5cb8e749 100644 --- a/evennia/objects/manager.py +++ b/evennia/objects/manager.py @@ -237,8 +237,9 @@ class ObjectDBManager(TypedObjectManager): ) type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q() try: - return self.filter( - cand_restriction & type_restriction & Q(**querykwargs)).order_by("id") + return self.filter(cand_restriction & type_restriction & Q(**querykwargs)).order_by( + "id" + ) except exceptions.FieldError: return self.none() except ValueError: @@ -335,8 +336,9 @@ class ObjectDBManager(TypedObjectManager): index_matches = string_partial_matching(key_strings, ostring, ret_index=True) if index_matches: # a match by key - match_ids = [obj.id for ind, obj in enumerate(search_candidates) - if ind in index_matches] + match_ids = [ + obj.id for ind, obj in enumerate(search_candidates) if ind in index_matches + ] else: # match by alias rather than by key search_candidates = search_candidates.filter( diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index c7402822c0..4efba09107 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -22,9 +22,15 @@ from evennia.scripts.scripthandler import ScriptHandler from evennia.typeclasses.attributes import ModelAttributeBackend, NickHandler from evennia.typeclasses.models import TypeclassBase from evennia.utils import ansi, create, funcparser, logger, search -from evennia.utils.utils import (class_from_module, is_iter, lazy_property, - list_to_string, make_iter, to_str, - variable_from_module) +from evennia.utils.utils import ( + class_from_module, + is_iter, + lazy_property, + list_to_string, + make_iter, + to_str, + variable_from_module, +) _INFLECT = inflect.engine() _MULTISESSION_MODE = settings.MULTISESSION_MODE @@ -212,13 +218,13 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): objects = ObjectManager() # populated by `return_apperance` - appearance_template = ''' + appearance_template = """ {header} |c{name}|n {desc} {exits}{characters}{things} {footer} - ''' + """ # on-object properties @@ -532,12 +538,14 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): # we re-run exact match agains one of the matches to # make sure we were not catching partial matches not belonging # to the stack - nstack = len(ObjectDB.objects.get_objs_with_key_or_alias( - results[0].key, - exact=True, - candidates=list(results), - typeclasses=[typeclass] if typeclass else None - )) + nstack = len( + ObjectDB.objects.get_objs_with_key_or_alias( + results[0].key, + exact=True, + candidates=list(results), + typeclasses=[typeclass] if typeclass else None, + ) + ) if nstack == nresults: # a valid stack, return multiple results return list(results)[:stacked] @@ -630,9 +638,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): raw_string = self.nicks.nickreplace( raw_string, categories=("inputline", "channel"), include_account=True ) - return _CMDHANDLER( - self, raw_string, callertype="object", session=session, **kwargs - ) + return _CMDHANDLER(self, raw_string, callertype="object", session=session, **kwargs) def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs): """ @@ -790,7 +796,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): mapping = mapping or {} you = from_obj or self - if 'you' not in mapping: + if "you" not in mapping: mapping[you] = you contents = self.contents @@ -802,14 +808,23 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): # actor-stance replacements inmessage = _MSG_CONTENTS_PARSER.parse( - inmessage, raise_errors=True, return_string=True, - caller=you, receiver=receiver, mapping=mapping) + inmessage, + raise_errors=True, + return_string=True, + caller=you, + receiver=receiver, + mapping=mapping, + ) # director-stance replacements outmessage = inmessage.format( - **{key: obj.get_display_name(looker=receiver) - if hasattr(obj, "get_display_name") else str(obj) - for key, obj in mapping.items()}) + **{ + key: obj.get_display_name(looker=receiver) + if hasattr(obj, "get_display_name") + else str(obj) + for key, obj in mapping.items() + } + ) receiver.msg(text=(outmessage, outkwargs), from_obj=from_obj, **kwargs) @@ -866,6 +881,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): 7. `self.at_post_move(source_location)` """ + def logerr(string="", err=None): """Simple log helper method""" logger.log_trace() @@ -989,8 +1005,10 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): if not home: obj.location = None obj.msg(_("Something went wrong! You are dumped into nowhere. Contact an admin.")) - logger.log_err("Missing default home - '{name}(#{dbid})' now " - "has a null location.".format(name=obj.name, dbid=obj.dbid)) + logger.log_err( + "Missing default home - '{name}(#{dbid})' now " + "has a null location.".format(name=obj.name, dbid=obj.dbid) + ) return if obj.has_account: @@ -1550,7 +1568,8 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): # This was created from nowhere and added to an account's # inventory; it's probably the result of a create command. string = _("You now have {name} in your possession.").format( - name=self.get_display_name(self.location)) + name=self.get_display_name(self.location) + ) self.location.msg(string) return @@ -1754,13 +1773,14 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): lists are the actual objects. """ + def filter_visible(obj_list): return [obj for obj in obj_list if obj != looker and obj.access(looker, "view")] return { "exits": filter_visible(self.contents_get(content_type="exit")), "characters": filter_visible(self.contents_get(content_type="character")), - "things": filter_visible(self.contents_get(content_type="object")) + "things": filter_visible(self.contents_get(content_type="object")), } def get_content_names(self, looker, **kwargs): @@ -1789,13 +1809,14 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): # a mapping {'exits': [...], 'characters': [...], 'things': [...]} contents_map = self.get_visible_contents(looker, **kwargs) - character_names = [char.get_display_name(looker, **kwargs) - for char in contents_map['characters']] - exit_names = [exi.get_display_name(looker, **kwargs) for exi in contents_map['exits']] + character_names = [ + char.get_display_name(looker, **kwargs) for char in contents_map["characters"] + ] + exit_names = [exi.get_display_name(looker, **kwargs) for exi in contents_map["exits"]] # group all same-named things under one name things = defaultdict(list) - for thing in contents_map['things']: + for thing in contents_map["things"]: things[thing.get_display_name(looker, **kwargs)].append(thing) # pluralize same-named things @@ -1806,11 +1827,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): singular, plural = thing.get_numbered_name(nthings, looker, key=thingname) thing_names.append(singular if nthings == 1 else plural) - return { - "exits": exit_names, - "characters": character_names, - "things": thing_names - } + return {"exits": exit_names, "characters": character_names, "things": thing_names} def return_appearance(self, looker, **kwargs): """ @@ -1840,7 +1857,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): """ if not looker: - return '' + return "" # ourselves name = self.get_display_name(looker, **kwargs) @@ -1848,20 +1865,20 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): # contents content_names_map = self.get_content_names(looker, **kwargs) - exits = list_to_string(content_names_map['exits']) - characters = list_to_string(content_names_map['characters']) - things = list_to_string(content_names_map['things']) + exits = list_to_string(content_names_map["exits"]) + characters = list_to_string(content_names_map["characters"]) + things = list_to_string(content_names_map["things"]) # populate the appearance_template string. It's a good idea to strip it and # let the client add any extra spaces instead. return self.appearance_template.format( - header='', + header="", name=name, desc=desc, - exits=f"|wExits:|n {exits}" if exits else '', - characters=f"\n|wCharacters:|n {characters}" if characters else '', - things=f"\n|wYou see:|n {things}" if things else '', - footer='' + exits=f"|wExits:|n {exits}" if exits else "", + characters=f"\n|wCharacters:|n {characters}" if characters else "", + things=f"\n|wYou see:|n {things}" if things else "", + footer="", ).strip() def at_look(self, target, **kwargs): @@ -2124,7 +2141,8 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): msg_type = "whisper" msg_self = ( '{self} whisper to {all_receivers}, "|n{speech}|n"' - if msg_self is True else msg_self + if msg_self is True + else msg_self ) msg_receivers = msg_receivers or '{object} whispers: "|n{speech}|n"' msg_location = None @@ -2332,7 +2350,7 @@ class DefaultCharacter(DefaultObject): @classmethod def validate_name(cls, name): - """ Validate the character name prior to creating. Overload this function to add custom validators + """Validate the character name prior to creating. Overload this function to add custom validators Args: name (str) : The name of the character @@ -2391,8 +2409,7 @@ class DefaultCharacter(DefaultObject): self.db.prelogout_location = self.location # save location again to be sure. else: account.msg( - _("|r{obj} has no location and no home is set.|n").format(obj=self), - session=session + _("|r{obj} has no location and no home is set.|n").format(obj=self), session=session ) # Note to set home. def at_post_puppet(self, **kwargs): @@ -2414,8 +2431,10 @@ class DefaultCharacter(DefaultObject): self.msg((self.at_look(self.location), {"type": "look"}), options=None) def message(obj, from_obj): - obj.msg(_("{name} has entered the game.").format(name=self.get_display_name(obj)), - from_obj=from_obj) + obj.msg( + _("{name} has entered the game.").format(name=self.get_display_name(obj)), + from_obj=from_obj, + ) self.location.for_contents(message, exclude=[self], from_obj=self) @@ -2438,8 +2457,10 @@ class DefaultCharacter(DefaultObject): if self.location: def message(obj, from_obj): - obj.msg(_("{name} has left the game.").format(name=self.get_display_name(obj)), - from_obj=from_obj) + obj.msg( + _("{name} has left the game.").format(name=self.get_display_name(obj)), + from_obj=from_obj, + ) self.location.for_contents(message, exclude=[self], from_obj=self) self.db.prelogout_location = self.location @@ -2582,6 +2603,7 @@ class DefaultRoom(DefaultObject): # Default Exit command, used by the base exit object # + class ExitCommand(_COMMAND_DEFAULT_CLASS): """ This is a command that simply cause the caller to traverse diff --git a/evennia/objects/tests.py b/evennia/objects/tests.py index db409b78ea..33a666b1a8 100644 --- a/evennia/objects/tests.py +++ b/evennia/objects/tests.py @@ -202,18 +202,19 @@ class TestContentHandler(BaseEvenniaTest): def test_contents_order(self): """Move object from room to room in various ways""" - self.assertEqual(self.room1.contents, [ - self.exit, self.obj1, self.obj2, self.char1, self.char2]) + self.assertEqual( + self.room1.contents, [self.exit, self.obj1, self.obj2, self.char1, self.char2] + ) self.assertEqual(self.room2.contents, []) # use move_to hook to move obj1 self.obj1.move_to(self.room2) - self.assertEqual(self.room1.contents, [self.exit, self.obj2, self.char1, self.char2 ]) + self.assertEqual(self.room1.contents, [self.exit, self.obj2, self.char1, self.char2]) self.assertEqual(self.room2.contents, [self.obj1]) # move obj2 self.obj2.move_to(self.room2) - self.assertEqual(self.room1.contents, [self.exit, self.char1, self.char2 ]) + self.assertEqual(self.room1.contents, [self.exit, self.char1, self.char2]) self.assertEqual(self.room2.contents, [self.obj1, self.obj2]) # move back and forth - it should diff --git a/evennia/prototypes/menus.py b/evennia/prototypes/menus.py index 2858c31123..d47cfd5129 100644 --- a/evennia/prototypes/menus.py +++ b/evennia/prototypes/menus.py @@ -262,7 +262,8 @@ def _validate_prototype(prototype): def _format_protfuncs(): out = [] sorted_funcs = [ - (key, func) for key, func in sorted(protlib.FUNC_PARSER.callables.items(), key=lambda tup: tup[0]) + (key, func) + for key, func in sorted(protlib.FUNC_PARSER.callables.items(), key=lambda tup: tup[0]) ] for protfunc_name, protfunc in sorted_funcs: out.append( @@ -2113,8 +2114,9 @@ def _apply_diff(caller, **kwargs): objects = kwargs["objects"] back_node = kwargs["back_node"] diff = kwargs.get("diff", None) - num_changed = spawner.batch_update_objects_with_prototype(prototype, diff=diff, objects=objects, - caller=caller) + num_changed = spawner.batch_update_objects_with_prototype( + prototype, diff=diff, objects=objects, caller=caller + ) caller.msg("|g{num} objects were updated successfully.|n".format(num=num_changed)) return back_node @@ -2354,7 +2356,7 @@ def node_apply_diff(caller, **kwargs): def node_prototype_save(caller, **kwargs): - """Save prototype to disk """ + """Save prototype to disk""" # these are only set if we selected 'yes' to save on a previous pass prototype = kwargs.get("prototype", None) # set to True/False if answered, None if first pass diff --git a/evennia/prototypes/protfuncs.py b/evennia/prototypes/protfuncs.py index b93edc0d1b..ded1c03f97 100644 --- a/evennia/prototypes/protfuncs.py +++ b/evennia/prototypes/protfuncs.py @@ -50,16 +50,20 @@ def protfunc_callable_protkey(*args, **kwargs): prot_value = prototype[fieldname] else: # check if it's an attribute - for attrtuple in prototype.get('attrs', []): + for attrtuple in prototype.get("attrs", []): if attrtuple[0] == fieldname: prot_value = attrtuple[1] break else: - raise AttributeError(f"{fieldname} not found in prototype\n{prototype}\n" - "(neither as prototype-field or as an Attribute") + raise AttributeError( + f"{fieldname} not found in prototype\n{prototype}\n" + "(neither as prototype-field or as an Attribute" + ) if callable(prot_value): - raise RuntimeError(f"Error in prototype\n{prototype}\n$protkey can only reference static " - f"values/attributes (found {prot_value})") + raise RuntimeError( + f"Error in prototype\n{prototype}\n$protkey can only reference static " + f"values/attributes (found {prot_value})" + ) try: return funcparser.funcparser_callable_eval(prot_value, **kwargs) except funcparser.ParsingError: diff --git a/evennia/prototypes/prototypes.py b/evennia/prototypes/prototypes.py index a8a6e6bdaf..89422c1ff7 100644 --- a/evennia/prototypes/prototypes.py +++ b/evennia/prototypes/prototypes.py @@ -131,8 +131,9 @@ def homogenize_prototype(prototype, custom_keys=None): for attr in attrs: # attrs must be on form [(key, value, category, lockstr)] if not is_iter(attr): - logger.log_error("Prototype's 'attr' field must " - f"be a list of tuples: {prototype}") + logger.log_error( + "Prototype's 'attr' field must " f"be a list of tuples: {prototype}" + ) elif attr: nattr = len(attr) if nattr == 1: @@ -147,14 +148,15 @@ def homogenize_prototype(prototype, custom_keys=None): elif key == "prototype_parent": # homogenize any prototype-parents embedded directly as dicts - protparents = prototype.get('prototype_parent', []) + protparents = prototype.get("prototype_parent", []) if isinstance(protparents, dict): protparents = [protparents] for parent in make_iter(protparents): if isinstance(parent, dict): # recursively homogenize directly embedded prototype parents homogenized_parents.append( - homogenize_prototype(parent, custom_keys=custom_keys)) + homogenize_prototype(parent, custom_keys=custom_keys) + ) else: # normal prototype-parent names are added as-is homogenized_parents.append(parent) @@ -170,7 +172,7 @@ def homogenize_prototype(prototype, custom_keys=None): if homogenized_tags: homogenized["tags"] = homogenized_tags if homogenized_parents: - homogenized['prototype_parent'] = homogenized_parents + homogenized["prototype_parent"] = homogenized_parents # add required missing parts that had defaults before @@ -190,6 +192,7 @@ def homogenize_prototype(prototype, custom_keys=None): # module/dict-based prototypes + def load_module_prototypes(*mod_or_prototypes, override=True): """ Load module prototypes. Also prototype-dicts passed directly to this function are considered @@ -233,12 +236,16 @@ def load_module_prototypes(*mod_or_prototypes, override=True): # prototype dicts that must have 'prototype_key' set. for prot in prototype_list: if not isinstance(prot, dict): - logger.log_err(f"Prototype read from {mod}.PROTOTYPE_LIST " - f"is not a dict (skipping): {prot}") + logger.log_err( + f"Prototype read from {mod}.PROTOTYPE_LIST " + f"is not a dict (skipping): {prot}" + ) continue elif "prototype_key" not in prot: - logger.log_err(f"Prototype read from {mod}.PROTOTYPE_LIST " - f"is missing the 'prototype_key' (skipping): {prot}") + logger.log_err( + f"Prototype read from {mod}.PROTOTYPE_LIST " + f"is missing the 'prototype_key' (skipping): {prot}" + ) continue prots.append((prot["prototype_key"], homogenize_prototype(prot))) else: @@ -270,7 +277,8 @@ def load_module_prototypes(*mod_or_prototypes, override=True): { "prototype_key": actual_prot_key, "prototype_desc": ( - prototype["prototype_desc"] if "prototype_desc" in prototype else (mod or "N/A")), + prototype["prototype_desc"] if "prototype_desc" in prototype else (mod or "N/A") + ), "prototype_locks": ( prototype["prototype_locks"] if "prototype_locks" in prototype @@ -292,9 +300,11 @@ def load_module_prototypes(*mod_or_prototypes, override=True): if isinstance(mod_or_dict, dict): # a single prototype; we must make sure it has its key - prototype_key = mod_or_dict.get('prototype_key') + prototype_key = mod_or_dict.get("prototype_key") if not prototype_key: - raise ValidationError(f"The prototype {mod_or_prototype} does not contain a 'prototype_key'") + raise ValidationError( + f"The prototype {mod_or_prototype} does not contain a 'prototype_key'" + ) prots = [(prototype_key, mod_or_dict)] mod = None else: @@ -307,7 +317,7 @@ def load_module_prototypes(*mod_or_prototypes, override=True): for prototype_key, prot in prots: prototype = _cleanup_prototype(prototype_key, prot, mod=mod) # the key can change since in-proto key is given prio over variable-name-based keys - actual_prototype_key = prototype['prototype_key'] + actual_prototype_key = prototype["prototype_key"] if actual_prototype_key in _MODULE_PROTOTYPES and not override: # don't override - useful to still let settings replace dynamic inserts @@ -462,23 +472,26 @@ def delete_prototype(prototype_key, caller=None): stored_prototype = DbPrototype.objects.filter(db_key__iexact=prototype_key) if not stored_prototype: - raise PermissionError(_("Prototype {prototype_key} was not found.").format( - prototype_key=prototype_key)) + raise PermissionError( + _("Prototype {prototype_key} was not found.").format(prototype_key=prototype_key) + ) stored_prototype = stored_prototype[0] if caller: if not stored_prototype.access(caller, "edit"): raise PermissionError( - _("{caller} needs explicit 'edit' permissions to " - "delete prototype {prototype_key}.").format( - caller=caller, prototype_key=prototype_key) + _( + "{caller} needs explicit 'edit' permissions to " + "delete prototype {prototype_key}." + ).format(caller=caller, prototype_key=prototype_key) ) stored_prototype.delete() return True -def search_prototype(key=None, tags=None, require_single=False, return_iterators=False, - no_db=False): +def search_prototype( + key=None, tags=None, require_single=False, return_iterators=False, no_db=False +): """ Find prototypes based on key and/or tags, or all prototypes. @@ -520,7 +533,7 @@ def search_prototype(key=None, tags=None, require_single=False, return_iterators # prototype keys are always in lowecase if key: - key = key.lower() + key = key.lower() # search module prototypes @@ -587,10 +600,10 @@ def search_prototype(key=None, tags=None, require_single=False, return_iterators nmodules = len(module_prototypes) ndbprots = db_matches.count() if db_matches else 0 if nmodules + ndbprots != 1: - raise KeyError(_( - "Found {num} matching prototypes among {module_prototypes}.").format( - num=nmodules + ndbprots, - module_prototypes=module_prototypes) + raise KeyError( + _("Found {num} matching prototypes among {module_prototypes}.").format( + num=nmodules + ndbprots, module_prototypes=module_prototypes + ) ) if return_iterators: @@ -670,7 +683,7 @@ class PrototypeEvMore(EvMore): else: # get the correct slice, adjusted for the db-prototypes pageno = max(0, pageno - self._npages_db) - return modprot_list[pageno * self.height: pageno * self.height + self.height] + return modprot_list[pageno * self.height : pageno * self.height + self.height] def page_formatter(self, page): """ @@ -809,12 +822,15 @@ def validate_prototype( if is_prototype_base: _flags["errors"].append( _("Prototype {protkey} requires `typeclass` " "or 'prototype_parent'.").format( - protkey=protkey) + protkey=protkey + ) ) else: _flags["warnings"].append( - _("Prototype {protkey} can only be used as a mixin since it lacks " - "'typeclass' or 'prototype_parent' keys.").format(protkey=protkey) + _( + "Prototype {protkey} can only be used as a mixin since it lacks " + "'typeclass' or 'prototype_parent' keys." + ).format(protkey=protkey) ) if strict and typeclass: @@ -822,9 +838,10 @@ def validate_prototype( class_from_module(typeclass) except ImportError as err: _flags["errors"].append( - _("{err}: Prototype {protkey} is based on typeclass {typeclass}, " - "which could not be imported!").format( - err=err, protkey=protkey, typeclass=typeclass) + _( + "{err}: Prototype {protkey} is based on typeclass {typeclass}, " + "which could not be imported!" + ).format(err=err, protkey=protkey, typeclass=typeclass) ) if prototype_parent and isinstance(prototype_parent, dict): @@ -840,20 +857,24 @@ def validate_prototype( else: protstring = protstring.lower() if protkey is not None and protstring == protkey: - _flags["errors"].append(_("Prototype {protkey} tries to parent itself.").format( - protkey=protkey)) + _flags["errors"].append( + _("Prototype {protkey} tries to parent itself.").format(protkey=protkey) + ) protparent = protparents.get(protstring) if not protparent: _flags["errors"].append( - _("Prototype {protkey}'s `prototype_parent` (named '{parent}') " - "was not found.").format(protkey=protkey, parent=protstring) + _( + "Prototype {protkey}'s `prototype_parent` (named '{parent}') " + "was not found." + ).format(protkey=protkey, parent=protstring) ) # check for infinite recursion if id(prototype) in _flags["visited"]: _flags["errors"].append( _("{protkey} has infinite nesting of prototypes.").format( - protkey=protkey or prototype) + protkey=protkey or prototype + ) ) if _flags["errors"]: @@ -875,9 +896,11 @@ def validate_prototype( # if we get back to the current level without a typeclass it's an error. if strict and is_prototype_base and _flags["depth"] <= 0 and not _flags["typeclass"]: _flags["errors"].append( - _("Prototype {protkey} has no `typeclass` defined anywhere in its parent\n " - "chain. Add `typeclass`, or a `prototype_parent` pointing to a " - "prototype with a typeclass.").format(protkey=protkey) + _( + "Prototype {protkey} has no `typeclass` defined anywhere in its parent\n " + "chain. Add `typeclass`, or a `prototype_parent` pointing to a " + "prototype with a typeclass." + ).format(protkey=protkey) ) if _flags["depth"] <= 0: @@ -901,8 +924,9 @@ def validate_prototype( prototype["prototype_locks"] = prototype_locks -def protfunc_parser(value, available_functions=None, testing=False, stacktrace=False, - caller=None, **kwargs): +def protfunc_parser( + value, available_functions=None, testing=False, stacktrace=False, caller=None, **kwargs +): """ Parse a prototype value string for a protfunc and process it. @@ -1129,8 +1153,9 @@ def value_to_obj(value, force=True): stype = type(value) if is_iter(value): if stype == dict: - return {value_to_obj_or_any(key): value_to_obj_or_any(val) - for key, val in value.items()} + return { + value_to_obj_or_any(key): value_to_obj_or_any(val) for key, val in value.items() + } else: return stype([value_to_obj_or_any(val) for val in value]) return dbid_to_obj(value, ObjectDB) diff --git a/evennia/prototypes/spawner.py b/evennia/prototypes/spawner.py index 7fa641c357..e609e81554 100644 --- a/evennia/prototypes/spawner.py +++ b/evennia/prototypes/spawner.py @@ -154,8 +154,13 @@ from evennia.prototypes.prototypes import ( _CREATE_OBJECT_KWARGS = ("key", "location", "home", "destination") -_PROTOTYPE_META_NAMES = ("prototype_key", "prototype_desc", "prototype_tags", - "prototype_locks", "prototype_parent") +_PROTOTYPE_META_NAMES = ( + "prototype_key", + "prototype_desc", + "prototype_tags", + "prototype_locks", + "prototype_parent", +) _PROTOTYPE_ROOT_NAMES = ( "typeclass", "key", @@ -235,9 +240,7 @@ def _get_prototype(inprot, protparents, uninherited=None, _workprot=None): parent_prototype = protparents.get(prototype.lower(), {}) # Build the prot dictionary in reverse order, overloading - new_prot = _get_prototype( - parent_prototype, protparents, _workprot=_workprot - ) + new_prot = _get_prototype(parent_prototype, protparents, _workprot=_workprot) # attrs, tags have internal structure that should be inherited separately new_prot["attrs"] = _inherit_attrs( @@ -276,8 +279,9 @@ def flatten_prototype(prototype, validate=False, no_db=False): if prototype: prototype = protlib.homogenize_prototype(prototype) - protparents = {prot["prototype_key"].lower(): prot - for prot in protlib.search_prototype(no_db=no_db)} + protparents = { + prot["prototype_key"].lower(): prot for prot in protlib.search_prototype(no_db=no_db) + } protlib.validate_prototype( prototype, None, protparents, is_prototype_base=validate, strict=validate ) @@ -342,7 +346,7 @@ def prototype_from_object(obj): prot["aliases"] = aliases tags = sorted( [(tag.db_key, tag.db_category, tag.db_data) for tag in obj.tags.all(return_objs=True)], - key=lambda tup: (str(tup[0]), tup[1] or '', tup[2] or '') + key=lambda tup: (str(tup[0]), tup[1] or "", tup[2] or ""), ) if tags: prot["tags"] = tags @@ -351,7 +355,7 @@ def prototype_from_object(obj): (attr.key, attr.value, attr.category, ";".join(attr.locks.all())) for attr in obj.attributes.all() ], - key=lambda tup: (str(tup[0]), tup[1] or '', tup[2] or '', tup[3]) + key=lambda tup: (str(tup[0]), tup[1] or "", tup[2] or "", tup[3]), ) if attrs: prot["attrs"] = attrs @@ -489,8 +493,10 @@ def flatten_diff(diff): out.extend(_get_all_nested_diff_instructions(val)) else: raise RuntimeError( - _("Diff contains non-dicts that are not on the " - "form (old, new, action_to_take): {diffpart}").format(diffpart) + _( + "Diff contains non-dicts that are not on the " + "form (old, new, action_to_take): {diffpart}" + ).format(diffpart) ) return out @@ -627,8 +633,9 @@ def format_diff(diff, minimal=True): return "\n ".join(line for line in texts if line) -def batch_update_objects_with_prototype(prototype, diff=None, objects=None, - exact=False, caller=None): +def batch_update_objects_with_prototype( + prototype, diff=None, objects=None, exact=False, caller=None +): """ Update existing objects with the latest version of the prototype. @@ -941,27 +948,32 @@ def spawn(*prototypes, caller=None, **kwargs): val = prot.pop("location", None) create_kwargs["db_location"] = init_spawn_value( - val, value_to_obj, caller=caller, prototype=prototype) + val, value_to_obj, caller=caller, prototype=prototype + ) val = prot.pop("home", None) if val: - create_kwargs["db_home"] = init_spawn_value(val, value_to_obj, caller=caller, - prototype=prototype) + create_kwargs["db_home"] = init_spawn_value( + val, value_to_obj, caller=caller, prototype=prototype + ) else: try: create_kwargs["db_home"] = init_spawn_value( - settings.DEFAULT_HOME, value_to_obj, caller=caller, prototype=prototype) + settings.DEFAULT_HOME, value_to_obj, caller=caller, prototype=prototype + ) except ObjectDB.DoesNotExist: # settings.DEFAULT_HOME not existing is common for unittests pass val = prot.pop("destination", None) - create_kwargs["db_destination"] = init_spawn_value(val, value_to_obj, caller=caller, - prototype=prototype) + create_kwargs["db_destination"] = init_spawn_value( + val, value_to_obj, caller=caller, prototype=prototype + ) val = prot.pop("typeclass", settings.BASE_OBJECT_TYPECLASS) - create_kwargs["db_typeclass_path"] = init_spawn_value(val, str, caller=caller, - prototype=prototype) + create_kwargs["db_typeclass_path"] = init_spawn_value( + val, str, caller=caller, prototype=prototype + ) # extract calls to handlers val = prot.pop("permissions", []) @@ -974,8 +986,13 @@ def spawn(*prototypes, caller=None, **kwargs): val = prot.pop("tags", []) tags = [] for (tag, category, *data) in val: - tags.append((init_spawn_value(tag, str, caller=caller, prototype=prototype), - category, data[0] if data else None)) + tags.append( + ( + init_spawn_value(tag, str, caller=caller, prototype=prototype), + category, + data[0] if data else None, + ) + ) prototype_key = prototype.get("prototype_key", None) if prototype_key: @@ -987,8 +1004,10 @@ def spawn(*prototypes, caller=None, **kwargs): # extract ndb assignments nattributes = dict( - (key.split("_", 1)[1], init_spawn_value(val, value_to_obj, caller=caller, - prototype=prototype)) + ( + key.split("_", 1)[1], + init_spawn_value(val, value_to_obj, caller=caller, prototype=prototype), + ) for key, val in prot.items() if key.startswith("ndb_") ) @@ -998,8 +1017,13 @@ def spawn(*prototypes, caller=None, **kwargs): attributes = [] for (attrname, value, *rest) in val: attributes.append( - (attrname, init_spawn_value(value, caller=caller, prototype=prototype), - rest[0] if rest else None, rest[1] if len(rest) > 1 else None)) + ( + attrname, + init_spawn_value(value, caller=caller, prototype=prototype), + rest[0] if rest else None, + rest[1] if len(rest) > 1 else None, + ) + ) simple_attributes = [] for key, value in ( @@ -1010,8 +1034,14 @@ def spawn(*prototypes, caller=None, **kwargs): continue else: simple_attributes.append( - (key, init_spawn_value(value, value_to_obj_or_any, caller=caller, - prototype=prototype), None, None) + ( + key, + init_spawn_value( + value, value_to_obj_or_any, caller=caller, prototype=prototype + ), + None, + None, + ) ) attributes = attributes + simple_attributes diff --git a/evennia/prototypes/tests.py b/evennia/prototypes/tests.py index f3a05aa831..d9d9ffd818 100644 --- a/evennia/prototypes/tests.py +++ b/evennia/prototypes/tests.py @@ -45,6 +45,7 @@ _PROTPARENTS = { }, } + class TestSpawner(BaseEvenniaTest): def setUp(self): super().setUp() @@ -340,7 +341,6 @@ class TestProtLib(BaseEvenniaTest): class TestProtFuncs(BaseEvenniaTest): - @override_settings(PROT_FUNC_MODULES=["evennia.prototypes.protfuncs"]) def test_protkey_protfunc(self): test_prot = {"key1": "value1", "key2": 2} @@ -350,8 +350,7 @@ class TestProtFuncs(BaseEvenniaTest): "value1", ) self.assertEqual( - protlib.protfunc_parser("$protkey(key2)", testing=True, prototype=test_prot), - 2 + protlib.protfunc_parser("$protkey(key2)", testing=True, prototype=test_prot), 2 ) @@ -908,6 +907,7 @@ class Test2474(BaseEvenniaTest): that of its prototype_parent. """ + prototypes = { "WEAPON": { "typeclass": "evennia.objects.objects.DefaultObject", @@ -951,14 +951,14 @@ class TestPartialTagAttributes(BaseEvenniaTest): def setUp(self): super().setUp() self.prot = { - 'prototype_key': 'rock', - 'typeclass': 'evennia.objects.objects.DefaultObject', - 'key': 'a rock', - 'tags': [('quantity', 'groupable')], # missing data field - 'attrs': [('quantity', 1)], # missing category and lock fields - 'desc': 'A good way to get stoned.' + "prototype_key": "rock", + "typeclass": "evennia.objects.objects.DefaultObject", + "key": "a rock", + "tags": [("quantity", "groupable")], # missing data field + "attrs": [("quantity", 1)], # missing category and lock fields + "desc": "A good way to get stoned.", } def test_partial_spawn(self): obj = spawner.spawn(self.prot) - self.assertEqual(obj[0].key, self.prot['key']) + self.assertEqual(obj[0].key, self.prot["key"]) diff --git a/evennia/scripts/monitorhandler.py b/evennia/scripts/monitorhandler.py index 21c5969b8b..91c50b90a0 100644 --- a/evennia/scripts/monitorhandler.py +++ b/evennia/scripts/monitorhandler.py @@ -108,8 +108,8 @@ class MonitorHandler(object): """ # if this an Attribute with a category we should differentiate fieldname = self._attr_category_fieldname( - fieldname, obj.db_category - if fieldname == "db_value" and hasattr(obj, "db_category") else None + fieldname, + obj.db_category if fieldname == "db_value" and hasattr(obj, "db_category") else None, ) to_delete = [] @@ -124,8 +124,7 @@ class MonitorHandler(object): for (obj, fieldname, idstring) in to_delete: del self.monitors[obj][fieldname][idstring] - def add(self, obj, fieldname, callback, idstring="", persistent=False, - category=None, **kwargs): + def add(self, obj, fieldname, callback, idstring="", persistent=False, category=None, **kwargs): """ Add monitoring to a given field or Attribute. A field must be specified with the full db_* name or it will be assumed diff --git a/evennia/scripts/scripthandler.py b/evennia/scripts/scripthandler.py index 8fe5f34eb1..e5be10fdfd 100644 --- a/evennia/scripts/scripthandler.py +++ b/evennia/scripts/scripthandler.py @@ -49,10 +49,12 @@ class ScriptHandler(object): except Exception: next_repeat = "?" string += _("\n '{key}' ({next_repeat}/{interval}, {repeats} repeats): {desc}").format( - key=script.key, next_repeat=next_repeat, + key=script.key, + next_repeat=next_repeat, interval=interval, repeats=repeats, - desc=script.desc) + desc=script.desc, + ) return string.strip() def add(self, scriptclass, key=None, autostart=True): diff --git a/evennia/scripts/tests.py b/evennia/scripts/tests.py index d639c5ca1d..3d0e7d1308 100644 --- a/evennia/scripts/tests.py +++ b/evennia/scripts/tests.py @@ -85,5 +85,5 @@ class TestExtendedLoopingCall(TestCase): loopcall.start(20, now=False, start_delay=10, count_start=1) loopcall.__call__.assert_not_called() - self.assertEqual(loopcall.interval , 20) + self.assertEqual(loopcall.interval, 20) loopcall._scheduleFrom.assert_called_with(121) diff --git a/evennia/scripts/tickerhandler.py b/evennia/scripts/tickerhandler.py index fa51e342d1..5af87f1445 100644 --- a/evennia/scripts/tickerhandler.py +++ b/evennia/scripts/tickerhandler.py @@ -90,6 +90,7 @@ a text-game, and if you want to update some property, consider doing so on-demand rather than using a ticker. """ + class Ticker(object): """ Represents a repeatedly running task that calls diff --git a/evennia/server/deprecations.py b/evennia/server/deprecations.py index 2ca5723636..06b0ec0817 100644 --- a/evennia/server/deprecations.py +++ b/evennia/server/deprecations.py @@ -6,6 +6,7 @@ These all print to the terminal. """ import os + def check_errors(settings): """ Check for deprecations that are critical errors and should stop @@ -61,19 +62,26 @@ def check_errors(settings): ) depstring = ( "settings.{} was renamed to {}. Update your settings file (the FuncParser " - "replaces and generalizes that which inlinefuncs used to do).") + "replaces and generalizes that which inlinefuncs used to do)." + ) if hasattr(settings, "INLINEFUNC_ENABLED"): - raise DeprecationWarning(depstring.format( - "settings.INLINEFUNC_ENABLED", "FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED")) + raise DeprecationWarning( + depstring.format( + "settings.INLINEFUNC_ENABLED", "FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED" + ) + ) if hasattr(settings, "INLINEFUNC_STACK_MAXSIZE"): - raise DeprecationWarning(depstring.format( - "settings.INLINEFUNC_STACK_MAXSIZE", "FUNCPARSER_MAX_NESTING")) + raise DeprecationWarning( + depstring.format("settings.INLINEFUNC_STACK_MAXSIZE", "FUNCPARSER_MAX_NESTING") + ) if hasattr(settings, "INLINEFUNC_MODULES"): - raise DeprecationWarning(depstring.format( - "settings.INLINEFUNC_MODULES", "FUNCPARSER_OUTGOING_MESSAGES_MODULES")) + raise DeprecationWarning( + depstring.format("settings.INLINEFUNC_MODULES", "FUNCPARSER_OUTGOING_MESSAGES_MODULES") + ) if hasattr(settings, "PROTFUNC_MODULES"): - raise DeprecationWarning(depstring.format( - "settings.PROTFUNC_MODULES", "FUNCPARSER_PROTOTYPE_VALUE_MODULES")) + raise DeprecationWarning( + depstring.format("settings.PROTFUNC_MODULES", "FUNCPARSER_PROTOTYPE_VALUE_MODULES") + ) gametime_deprecation = ( "The settings TIME_SEC_PER_MIN, TIME_MIN_PER_HOUR," @@ -121,7 +129,8 @@ def check_errors(settings): raise DeprecationWarning( "settings.CHANNEL_HANDLER_CLASS and CHANNEL COMMAND_CLASS are " "unused and should be removed. The ChannelHandler is no more; " - "channels are now handled by aliasing the default 'channel' command.") + "channels are now handled by aliasing the default 'channel' command." + ) template_overrides_dir = os.path.join(settings.GAME_DIR, "web", "template_overrides") static_overrides_dir = os.path.join(settings.GAME_DIR, "web", "static_overrides") @@ -151,8 +160,10 @@ def check_warnings(settings): if settings.ALLOWED_HOSTS == ["*"]: print(" [Devel: settings.ALLOWED_HOSTS set to '*' (all). Limit in production.]") if settings.SERVER_HOSTNAME == "localhost": - print(" [Devel: settings.SERVER_HOSTNAME is set to 'localhost'. " - "Update to the actual hostname in production.]") + print( + " [Devel: settings.SERVER_HOSTNAME is set to 'localhost'. " + "Update to the actual hostname in production.]" + ) for dbentry in settings.DATABASES.values(): if "psycopg" in dbentry.get("ENGINE", ""): diff --git a/evennia/server/evennia_launcher.py b/evennia/server/evennia_launcher.py index 84d3df38f3..e1b8e69af3 100644 --- a/evennia/server/evennia_launcher.py +++ b/evennia/server/evennia_launcher.py @@ -1431,6 +1431,7 @@ def create_superuser(): if (username is not None) and (password is not None) and len(password) > 0: from evennia.accounts.models import AccountDB + superuser = AccountDB.objects.create_superuser(username, email, password) superuser.save() else: @@ -1947,7 +1948,7 @@ def run_custom_commands(option, *args): return False cmdpath = custom_commands.get(option) if cmdpath: - modpath, *cmdname = cmdpath.rsplit('.', 1) + modpath, *cmdname = cmdpath.rsplit(".", 1) if cmdname: cmdname = cmdname[0] mod = importlib.import_module(modpath) diff --git a/evennia/server/initial_setup.py b/evennia/server/initial_setup.py index 26fdb3b7fb..4e1ebf6644 100644 --- a/evennia/server/initial_setup.py +++ b/evennia/server/initial_setup.py @@ -25,14 +25,16 @@ ERROR_NO_SUPERUSER = """ """ -LIMBO_DESC = _(""" +LIMBO_DESC = _( + """ Welcome to your new |wEvennia|n-based game! Visit https://www.evennia.com if you need help, want to contribute, report issues or just join the community. As a privileged user, write |wbatchcommand tutorial_world.build|n to build tutorial content. Once built, try |wintro|n for starting help and |wtutorial|n to play the demo game. -""") +""" +) WARNING_POSTGRESQL_FIX = """ @@ -99,7 +101,8 @@ def create_objects(): superuser_character = ObjectDB.objects.get(id=1) except ObjectDB.DoesNotExist: superuser_character = create.create_object( - character_typeclass, key=superuser.username, nohome=True) + character_typeclass, key=superuser.username, nohome=True + ) superuser_character.db_typeclass_path = character_typeclass superuser_character.db.desc = _("This is User #1.") @@ -135,6 +138,7 @@ def create_objects(): if not superuser_character.home: superuser_character.home = limbo_obj + def at_initial_setup(): """ Custom hook for users to overload some or all parts of the initial @@ -192,7 +196,7 @@ def handle_setup(last_step=None): the function will exit immediately. """ - if last_step in('done', -1): + if last_step in ("done", -1): # this means we don't need to handle setup since # it already ran sucessfully once. -1 is the legacy # value for existing databases. @@ -200,15 +204,15 @@ def handle_setup(last_step=None): # setup sequence setup_sequence = { - 'create_objects': create_objects, - 'at_initial_setup': at_initial_setup, - 'collectstatic': collectstatic, - 'done': reset_server, + "create_objects": create_objects, + "at_initial_setup": at_initial_setup, + "collectstatic": collectstatic, + "done": reset_server, } # determine the sequence so we can skip ahead steps = list(setup_sequence) - steps = steps[steps.index(last_step) + 1 if last_step is not None else 0:] + steps = steps[steps.index(last_step) + 1 if last_step is not None else 0 :] # step through queue from last completed function. Once completed, # the 'done' key should be set. @@ -221,6 +225,6 @@ def handle_setup(last_step=None): else: # save the step ServerConfig.objects.conf("last_initial_setup_step", stepname) - if stepname == 'done': + if stepname == "done": # always exit on 'done' break diff --git a/evennia/server/inputfuncs.py b/evennia/server/inputfuncs.py index 0f9c791554..5bab84bdf8 100644 --- a/evennia/server/inputfuncs.py +++ b/evennia/server/inputfuncs.py @@ -42,7 +42,6 @@ _STRIP_INCOMING_MXP = settings.MXP_ENABLED and settings.MXP_OUTGOING_ONLY _STRIP_MXP = None - def _NA(o): return "N/A" @@ -95,9 +94,7 @@ def text(session, *args, **kwargs): # nick replacement puppet = session.puppet if puppet: - txt = puppet.nicks.nickreplace( - txt, categories=("inputline"), include_account=True - ) + txt = puppet.nicks.nickreplace(txt, categories=("inputline"), include_account=True) else: txt = session.account.nicks.nickreplace( txt, categories=("inputline"), include_account=False diff --git a/evennia/server/portal/amp.py b/evennia/server/portal/amp.py index 574fcd2313..ca568384f6 100644 --- a/evennia/server/portal/amp.py +++ b/evennia/server/portal/amp.py @@ -47,12 +47,12 @@ NULNUL = b"\x00\x00" AMP_MAXLEN = amp.MAX_VALUE_LENGTH # max allowed data length in AMP protocol (cannot be changed) # amp internal -ASK = b'_ask' -ANSWER = b'_answer' -ERROR = b'_error' -ERROR_CODE = b'_error_code' -ERROR_DESCRIPTION = b'_error_description' -UNKNOWN_ERROR_CODE = b'UNKNOWN' +ASK = b"_ask" +ANSWER = b"_answer" +ERROR = b"_error" +ERROR_CODE = b"_error_code" +ERROR_DESCRIPTION = b"_error_description" +UNKNOWN_ERROR_CODE = b"UNKNOWN" # buffers _SENDBATCH = defaultdict(list) @@ -322,6 +322,7 @@ class AMPMultiConnectionProtocol(amp.AMP): add a specific log of the problem on the erroring side. """ + def formatAnswer(answerBox): answerBox[ANSWER] = box[ASK] return answerBox @@ -350,6 +351,7 @@ class AMPMultiConnectionProtocol(amp.AMP): errorBox[ERROR_DESCRIPTION] = desc errorBox[ERROR_CODE] = code return errorBox + deferred = self.dispatchCommand(box) if ASK in box: deferred.addCallbacks(formatAnswer, formatError) diff --git a/evennia/server/portal/irc.py b/evennia/server/portal/irc.py index a0636a321e..002cb767d9 100644 --- a/evennia/server/portal/irc.py +++ b/evennia/server/portal/irc.py @@ -244,7 +244,7 @@ class IRCBot(irc.IRCClient, Session): self.sendLine("NAMES %s" % self.channel) def irc_RPL_NAMREPLY(self, prefix, params): - """"Handles IRC NAME request returns (nicklist)""" + """ "Handles IRC NAME request returns (nicklist)""" channel = params[2].lower() if channel != self.channel.lower(): return diff --git a/evennia/server/portal/portal.py b/evennia/server/portal/portal.py index 82dc6ed6d4..67b2b96c8d 100644 --- a/evennia/server/portal/portal.py +++ b/evennia/server/portal/portal.py @@ -358,8 +358,7 @@ if SSH_ENABLED: for port in SSH_PORTS: pstring = "%s:%s" % (ifacestr, port) factory = ssh.makeFactory( - {"protocolFactory": _ssh_protocol, - "protocolArgs": (), "sessions": PORTAL_SESSIONS} + {"protocolFactory": _ssh_protocol, "protocolArgs": (), "sessions": PORTAL_SESSIONS} ) factory.noisy = False ssh_service = internet.TCPServer(port, factory, interface=interface) diff --git a/evennia/server/portal/portalsessionhandler.py b/evennia/server/portal/portalsessionhandler.py index d0921c5109..3b9e5fca55 100644 --- a/evennia/server/portal/portalsessionhandler.py +++ b/evennia/server/portal/portalsessionhandler.py @@ -37,8 +37,9 @@ DUMMYSESSION = namedtuple("DummySession", ["sessid"])(0) # Portal-SessionHandler class # ------------------------------------------------------------- -DOS_PROTECTION_MSG = _("{servername} DoS protection is active." - "You are queued to connect in {num} seconds ...") +DOS_PROTECTION_MSG = _( + "{servername} DoS protection is active." "You are queued to connect in {num} seconds ..." +) class PortalSessionHandler(SessionHandler): @@ -117,9 +118,12 @@ class PortalSessionHandler(SessionHandler): if len(_CONNECTION_QUEUE) > 1: session.data_out( text=( - (DOS_PROTECTION_MSG.format( - servername=settings.SERVERNAME, - num=len(_CONNECTION_QUEUE) * _MIN_TIME_BETWEEN_CONNECTS),), + ( + DOS_PROTECTION_MSG.format( + servername=settings.SERVERNAME, + num=len(_CONNECTION_QUEUE) * _MIN_TIME_BETWEEN_CONNECTS, + ), + ), {}, ) ) @@ -442,8 +446,8 @@ class PortalSessionHandler(SessionHandler): self.portal.amp_protocol.send_MsgPortal2Server(session, **kwargs) # eventual local echo (text input only) - if 'text' in kwargs and session.protocol_flags.get('LOCALECHO', False): - self.data_out(session, text=kwargs['text']) + if "text" in kwargs and session.protocol_flags.get("LOCALECHO", False): + self.data_out(session, text=kwargs["text"]) def data_out(self, session, **kwargs): """ diff --git a/evennia/server/portal/ssh.py b/evennia/server/portal/ssh.py index fd039ddeae..abd8d2a0b7 100644 --- a/evennia/server/portal/ssh.py +++ b/evennia/server/portal/ssh.py @@ -80,6 +80,7 @@ class SSHServerFactory(protocol.ServerFactory): This is only to name this better in logs """ + noisy = False def logPrefix(self): diff --git a/evennia/server/portal/telnet.py b/evennia/server/portal/telnet.py index 87fa126d0e..4d25696c50 100644 --- a/evennia/server/portal/telnet.py +++ b/evennia/server/portal/telnet.py @@ -32,8 +32,8 @@ from evennia.utils import ansi from evennia.utils.utils import to_bytes, class_from_module _RE_N = re.compile(r"\|n$") -_RE_LEND = re.compile(br"\n$|\r$|\r\n$|\r\x00$|", re.MULTILINE) -_RE_LINEBREAK = re.compile(br"\n\r|\r\n|\n|\r", re.DOTALL + re.MULTILINE) +_RE_LEND = re.compile(rb"\n$|\r$|\r\n$|\r\x00$|", re.MULTILINE) +_RE_LINEBREAK = re.compile(rb"\n\r|\r\n|\n|\r", re.DOTALL + re.MULTILINE) _RE_SCREENREADER_REGEX = re.compile( r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE ) @@ -63,6 +63,7 @@ class TelnetServerFactory(protocol.ServerFactory): This exists only to name this better in logs. """ + noisy = False def logPrefix(self): @@ -92,6 +93,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS): super().dataReceived(data) except ValueError as err: from evennia.utils import logger + logger.log_err(f"Malformed telnet input: {err}") def connectionMade(self): @@ -458,8 +460,9 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS): prompt = mxp_parse(prompt) prompt = to_bytes(prompt, self) prompt = prompt.replace(IAC, IAC + IAC).replace(b"\n", b"\r\n") - if not self.protocol_flags.get("NOPROMPTGOAHEAD", - self.protocol_flags.get("NOGOAHEAD", True)): + if not self.protocol_flags.get( + "NOPROMPTGOAHEAD", self.protocol_flags.get("NOGOAHEAD", True) + ): prompt += IAC + GA self.transport.write(mccp_compress(self, prompt)) else: diff --git a/evennia/server/portal/telnet_oob.py b/evennia/server/portal/telnet_oob.py index 7b709d6348..3fee37719c 100644 --- a/evennia/server/portal/telnet_oob.py +++ b/evennia/server/portal/telnet_oob.py @@ -47,14 +47,14 @@ GMCP = bytes([201]) # pre-compiled regexes # returns 2-tuple msdp_regex_table = re.compile( - br"%s\s*(\w*?)\s*%s\s*%s(.*?)%s" % (MSDP_VAR, MSDP_VAL, MSDP_TABLE_OPEN, MSDP_TABLE_CLOSE) + rb"%s\s*(\w*?)\s*%s\s*%s(.*?)%s" % (MSDP_VAR, MSDP_VAL, MSDP_TABLE_OPEN, MSDP_TABLE_CLOSE) ) # returns 2-tuple msdp_regex_array = re.compile( - br"%s\s*(\w*?)\s*%s\s*%s(.*?)%s" % (MSDP_VAR, MSDP_VAL, MSDP_ARRAY_OPEN, MSDP_ARRAY_CLOSE) + rb"%s\s*(\w*?)\s*%s\s*%s(.*?)%s" % (MSDP_VAR, MSDP_VAL, MSDP_ARRAY_OPEN, MSDP_ARRAY_CLOSE) ) -msdp_regex_var = re.compile(br"%s" % MSDP_VAR) -msdp_regex_val = re.compile(br"%s" % MSDP_VAL) +msdp_regex_var = re.compile(rb"%s" % MSDP_VAR) +msdp_regex_val = re.compile(rb"%s" % MSDP_VAL) EVENNIA_TO_GMCP = { "client_options": "Core.Supports.Get", diff --git a/evennia/server/profiling/dummyrunner.py b/evennia/server/profiling/dummyrunner.py index 50f65872fb..f7e6a1d67a 100644 --- a/evennia/server/profiling/dummyrunner.py +++ b/evennia/server/profiling/dummyrunner.py @@ -41,8 +41,10 @@ from twisted.internet import reactor, protocol from twisted.internet.task import LoopingCall import django + django.setup() import evennia # noqa + evennia._init() from django.conf import settings # noqa @@ -214,6 +216,7 @@ class CmdDummyRunnerEchoResponse(Command): with the receive time on both ends. """ + key = "dummyrunner_echo_response" def func(self): @@ -228,9 +231,11 @@ class DummyRunnerCmdSet(CmdSet): Dummyrunner injected cmdset. """ + def at_cmdset_creation(self): self.add(CmdDummyRunnerEchoResponse()) + # ------------------------------------------------------------ # Helper functions # ------------------------------------------------------------ @@ -285,6 +290,7 @@ def makeiter(obj): # Client classes # ------------------------------------------------------------ + class DummyClient(telnet.StatefulTelnetProtocol): """ Handles connection to a running Evennia server, @@ -296,11 +302,13 @@ class DummyClient(telnet.StatefulTelnetProtocol): def report(self, text, clientkey): pad = " " * (25 - len(text)) tim = round(time.time() - self.connection_timestamp) - print(f"{text} {clientkey}{pad}\t" - f"conn: {NCONNECTED} -> " - f"welcome screen: {NLOGIN_SCREEN} -> " - f"authing: {NLOGGING_IN} -> " - f"loggedin/tot: {NLOGGED_IN}/{NCLIENTS} (after {tim}s)") + print( + f"{text} {clientkey}{pad}\t" + f"conn: {NCONNECTED} -> " + f"welcome screen: {NLOGIN_SCREEN} -> " + f"authing: {NLOGGING_IN} -> " + f"loggedin/tot: {NLOGGED_IN}/{NCLIENTS} (after {tim}s)" + ) def connectionMade(self): """ @@ -342,7 +350,7 @@ class DummyClient(telnet.StatefulTelnetProtocol): # (unclear why this would be - overload?) # try sending a look to get something to start with self.report("?? retrying welcome screen", self.key) - self.sendLine(bytes("look", 'utf-8')) + self.sendLine(bytes("look", "utf-8")) # make sure to check again later reactor.callLater(30, self._retry_welcome_screen) @@ -363,8 +371,10 @@ class DummyClient(telnet.StatefulTelnetProtocol): TOTAL_LAG_MEASURES = 0 TIME_ALL_LOGIN = time.time() - print(f".. running 30s average: ~{avgrate} actions/s " - f"lag: {lag:.2}s (in: {lag_in:.2}s, out: {lag_out:.2}s)") + print( + f".. running 30s average: ~{avgrate} actions/s " + f"lag: {lag:.2}s (in: {lag_in:.2}s, out: {lag_out:.2}s)" + ) reactor.callLater(30, self._print_statistics) @@ -393,7 +403,7 @@ class DummyClient(telnet.StatefulTelnetProtocol): # negotiation) # start client tick d = LoopingCall(self.step) - df = max(abs(TIMESTEP * 0.001), min(TIMESTEP/10, 0.5)) + df = max(abs(TIMESTEP * 0.001), min(TIMESTEP / 10, 0.5)) # dither next attempt with random time timestep = TIMESTEP + (-df + (random.random() * df)) d.start(timestep, now=True).addErrback(self.error) @@ -477,7 +487,7 @@ class DummyClient(telnet.StatefulTelnetProtocol): self._logging_out = True cmd = self._logout(self)[0] self.report(f"-> logout/disconnect ({self.istep} actions)", self.key) - self.sendLine(bytes(cmd, 'utf-8')) + self.sendLine(bytes(cmd, "utf-8")) def step(self): """ @@ -521,7 +531,7 @@ class DummyClient(telnet.StatefulTelnetProtocol): # the send as possible cmd = cmd.format(timestamp=time.time()) - self.sendLine(bytes(cmd, 'utf-8')) + self.sendLine(bytes(cmd, "utf-8")) self.action_started = time.time() self.istep += 1 @@ -605,17 +615,19 @@ if __name__ == "__main__": args = parser.parse_args() nclients = int(args.nclients[0]) - print(INFO_STARTING.format( - nclients=nclients, - port=TELNET_PORT, - idmapper_cache_size=IDMAPPER_CACHE_MAXSIZE, - timestep=TIMESTEP, - rate=1/TIMESTEP, - chance_of_login=CHANCE_OF_LOGIN * 100, - chance_of_action=CHANCE_OF_ACTION * 100, - avg_rate=(1 / TIMESTEP) * CHANCE_OF_ACTION, - avg_rate_total=(1 / TIMESTEP) * CHANCE_OF_ACTION * nclients - )) + print( + INFO_STARTING.format( + nclients=nclients, + port=TELNET_PORT, + idmapper_cache_size=IDMAPPER_CACHE_MAXSIZE, + timestep=TIMESTEP, + rate=1 / TIMESTEP, + chance_of_login=CHANCE_OF_LOGIN * 100, + chance_of_action=CHANCE_OF_ACTION * 100, + avg_rate=(1 / TIMESTEP) * CHANCE_OF_ACTION, + avg_rate_total=(1 / TIMESTEP) * CHANCE_OF_ACTION * nclients, + ) + ) # run the dummyrunner TIME_START = t0 = time.time() diff --git a/evennia/server/profiling/dummyrunner_settings.py b/evennia/server/profiling/dummyrunner_settings.py index b4bb7090bb..1dc63b0410 100644 --- a/evennia/server/profiling/dummyrunner_settings.py +++ b/evennia/server/profiling/dummyrunner_settings.py @@ -86,8 +86,9 @@ TELNET_PORT = None # some convenient templates DUMMY_NAME = "Dummy_{gid}" -DUMMY_PWD = (''.join(random.choice(string.ascii_letters + string.digits) - for _ in range(20)) + "-{gid}") +DUMMY_PWD = ( + "".join(random.choice(string.ascii_letters + string.digits) for _ in range(20)) + "-{gid}" +) START_ROOM = "testing_room_start_{gid}" ROOM_TEMPLATE = "testing_room_%s" EXIT_TEMPLATE = "exit_%s" @@ -101,6 +102,7 @@ TOBJ_TYPECLASS = "contrib.tutorial_examples.red_button.RedButton" # login/logout + def c_login(client): "logins to the game" # we always use a new client name @@ -131,10 +133,7 @@ def c_login_nodig(client): "logins, don't dig its own room" cname = DUMMY_NAME.format(gid=client.gid) cpwd = DUMMY_PWD.format(gid=client.gid) - cmds = ( - f"create {cname} {cpwd}", - f"connect {cname} {cpwd}" - ) + cmds = (f"create {cname} {cpwd}", f"connect {cname} {cpwd}") return cmds @@ -174,7 +173,10 @@ def c_idles(client): def c_help(client): "reads help files" - cmds = ("help", "dummyrunner_echo_response",) + cmds = ( + "help", + "dummyrunner_echo_response", + ) return cmds @@ -262,21 +264,16 @@ def c_measure_lag(client): PROFILE = "looker" -if PROFILE == 'idler': +if PROFILE == "idler": ACTIONS = ( c_login, c_logout, (0.9, c_idles), (0.1, c_measure_lag), ) -elif PROFILE == 'looker': - ACTIONS = ( - c_login, - c_logout, - (0.8, c_looks), - (0.2, c_measure_lag) - ) -elif PROFILE == 'normal_player': +elif PROFILE == "looker": + ACTIONS = (c_login, c_logout, (0.8, c_looks), (0.2, c_measure_lag)) +elif PROFILE == "normal_player": ACTIONS = ( c_login, c_logout, @@ -285,9 +282,9 @@ elif PROFILE == 'normal_player': (0.2, c_help), (0.3, c_moves), (0.05, c_socialize), - (0.1, c_measure_lag) + (0.1, c_measure_lag), ) -elif PROFILE == 'normal_builder': +elif PROFILE == "normal_builder": ACTIONS = ( c_login, c_logout, @@ -297,9 +294,9 @@ elif PROFILE == 'normal_builder': (0.01, c_digs), (0.01, c_creates_obj), (0.2, c_moves), - (0.1, c_measure_lag) + (0.1, c_measure_lag), ) -elif PROFILE == 'heavy_builder': +elif PROFILE == "heavy_builder": ACTIONS = ( c_login, c_logout, @@ -309,9 +306,9 @@ elif PROFILE == 'heavy_builder': (0.1, c_digs), (0.1, c_creates_obj), (0.2, c_moves), - (0.1, c_measure_lag) + (0.1, c_measure_lag), ) -elif PROFILE == 'socializing_builder': +elif PROFILE == "socializing_builder": ACTIONS = ( c_login, c_logout, @@ -321,17 +318,13 @@ elif PROFILE == 'socializing_builder': (0.1, c_creates_obj), (0.2, c_digs), (0.3, c_moves), - (0.1, c_measure_lag) - ) -elif PROFILE == 'only_digger': - ACTIONS = ( - c_login, - c_logout, - (0.9, c_digs), - (0.1, c_measure_lag) + (0.1, c_measure_lag), ) +elif PROFILE == "only_digger": + ACTIONS = (c_login, c_logout, (0.9, c_digs), (0.1, c_measure_lag)) else: print("No dummyrunner ACTION profile defined.") import sys + sys.exit() diff --git a/evennia/server/profiling/settings_mixin.py b/evennia/server/profiling/settings_mixin.py index b1e20618dd..5b21b686b2 100644 --- a/evennia/server/profiling/settings_mixin.py +++ b/evennia/server/profiling/settings_mixin.py @@ -27,7 +27,8 @@ MAX_COMMAND_RATE = 100000 MAX_CONNECTION_RATE = 100000 MAX_CHAR_LIMIT = 100000 -print(""" +print( + """ Dummyrunner settings_mixin added (ONLY FOR PROFILING, NOT FOR PRODUCTION!) PASSWORD_HASHERS = ("django.contrib.auth.hashers.MD5PasswordHasher",) @@ -39,4 +40,5 @@ print(""" MAX_COMMAND_RATE = 100000 MAX_CONNECTION_RATE = 100000 MAX_CHAR_LIMIT = 100000 -""") +""" +) diff --git a/evennia/server/profiling/tests.py b/evennia/server/profiling/tests.py index 8e6cfd7ed7..2301c77776 100644 --- a/evennia/server/profiling/tests.py +++ b/evennia/server/profiling/tests.py @@ -31,7 +31,7 @@ class TestDummyrunnerSettings(TestCase): self.client.counter = Mock(return_value=1) self.client.gid = "20171025161153-1" self.client.name = "Dummy_%s" % self.client.gid - self.client.password = Something, + self.client.password = (Something,) self.client.start_room = "testing_room_start_%s" % self.client.gid self.client.objs = [] self.client.exits = [] @@ -45,12 +45,12 @@ class TestDummyrunnerSettings(TestCase): c_login(self.client), ( Something, # create - 'yes', # confirm creation + "yes", # confirm creation Something, # connect "dig %s" % self.client.start_room, "teleport %s" % self.client.start_room, "py from evennia.server.profiling.dummyrunner import DummyRunnerCmdSet;" - "self.cmdset.add(DummyRunnerCmdSet, persistent=False)" + "self.cmdset.add(DummyRunnerCmdSet, persistent=False)", ), ) @@ -86,7 +86,7 @@ class TestDummyrunnerSettings(TestCase): ) def test_c_digs(self): - self.assertEqual(c_digs(self.client), ("dig/tel testing_room_1 = exit_1, exit_1", )) + self.assertEqual(c_digs(self.client), ("dig/tel testing_room_1 = exit_1, exit_1",)) self.assertEqual(self.client.exits, ["exit_1", "exit_1"]) self.clear_client_lists() diff --git a/evennia/server/server.py b/evennia/server/server.py index af7aeea343..771ded506a 100644 --- a/evennia/server/server.py +++ b/evennia/server/server.py @@ -284,9 +284,12 @@ class Evennia: (i, tup[0], tup[1]) for i, tup in enumerate(settings_compare) if i in mismatches ): # update the database - INFO_DICT["info"] = ( - " %s:\n '%s' changed to '%s'. Updating unchanged entries in database ..." - % (settings_names[i], prev, curr) + INFO_DICT[ + "info" + ] = " %s:\n '%s' changed to '%s'. Updating unchanged entries in database ..." % ( + settings_names[i], + prev, + curr, ) if i == 0: ObjectDB.objects.filter(db_cmdset_storage__exact=prev).update( @@ -344,7 +347,7 @@ class Evennia: # i.e. this is an empty DB that needs populating. INFO_DICT["info"] = " Server started for the first time. Setting defaults." initial_setup.handle_setup() - elif last_initial_setup_step not in ('done', -1): + elif last_initial_setup_step not in ("done", -1): # last step crashed, so we weill resume from this step. # modules and setup will resume from this step, retrying # the last failed module. When all are finished, the step @@ -405,9 +408,10 @@ class Evennia: self.update_defaults() # run at_init() on all cached entities on reconnect - [[entity.at_init() - for entity in typeclass_db.get_all_cached_instances()] - for typeclass_db in TypedObject.__subclasses__()] + [ + [entity.at_init() for entity in typeclass_db.get_all_cached_instances()] + for typeclass_db in TypedObject.__subclasses__() + ] # call correct server hook based on start file value if mode == "reload": @@ -418,6 +422,7 @@ class Evennia: self.at_server_cold_start() logger.log_msg("Evennia Server successfully restarted in 'reset' mode.") elif mode == "shutdown": + from evennia.objects.models import ObjectDB self.at_server_cold_start() # clear eventual lingering session storages ObjectDB.objects.clear_all_sessids() diff --git a/evennia/server/sessionhandler.py b/evennia/server/sessionhandler.py index 977cdad6c3..7ccb302720 100644 --- a/evennia/server/sessionhandler.py +++ b/evennia/server/sessionhandler.py @@ -185,8 +185,10 @@ class SessionHandler(dict): global _FUNCPARSER if not _FUNCPARSER: from evennia.utils.funcparser import FuncParser - _FUNCPARSER = FuncParser(settings.FUNCPARSER_OUTGOING_MESSAGES_MODULES, - raise_errors=True) + + _FUNCPARSER = FuncParser( + settings.FUNCPARSER_OUTGOING_MESSAGES_MODULES, raise_errors=True + ) options = kwargs.pop("options", None) or {} raw = options.get("raw", False) @@ -222,8 +224,11 @@ class SessionHandler(dict): elif isinstance(data, (str, bytes)): data = _utf8(data) - if (_FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED - and not raw and isinstance(self, ServerSessionHandler)): + if ( + _FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED + and not raw + and isinstance(self, ServerSessionHandler) + ): # only apply funcparser on the outgoing path (sessionhandler->) # data = parse_inlinefunc(data, strip=strip_inlinefunc, session=session) data = _FUNCPARSER.parse(data, strip=strip_inlinefunc, session=session) @@ -638,8 +643,7 @@ class ServerSessionHandler(SessionHandler): # mean connecting from the same host would not catch duplicates sid = id(curr_session) doublet_sessions = [ - sess for sess in self.values() - if sess.logged_in and sess.uid == uid and id(sess) != sid + sess for sess in self.values() if sess.logged_in and sess.uid == uid and id(sess) != sid ] for session in doublet_sessions: diff --git a/evennia/server/tests/test_misc.py b/evennia/server/tests/test_misc.py index d768e742f7..e0b91625d1 100644 --- a/evennia/server/tests/test_misc.py +++ b/evennia/server/tests/test_misc.py @@ -124,14 +124,14 @@ class ThrottleTest(BaseEvenniaTest): # There should only be (cache_size * num_ips) total in the Throttle cache self.assertEqual(sum([len(cache[x]) for x in cache.keys()]), throttle.cache_size * len(ips)) - + # Make sure the cache is populated self.assertTrue(throttle.get()) - - # Remove the test IPs from the throttle cache + + # Remove the test IPs from the throttle cache # (in case persistent storage was configured by the user) for ip in ips: self.assertTrue(throttle.remove(ip)) - + # Make sure the cache is empty - self.assertFalse(throttle.get()) \ No newline at end of file + self.assertFalse(throttle.get()) diff --git a/evennia/server/tests/testrunner.py b/evennia/server/tests/testrunner.py index 5eaa1daa31..99148a6ea8 100644 --- a/evennia/server/tests/testrunner.py +++ b/evennia/server/tests/testrunner.py @@ -32,6 +32,4 @@ class EvenniaTestSuiteRunner(DiscoverRunner): import evennia evennia._init() - return super().build_suite( - test_labels, extra_tests=extra_tests, **kwargs - ) + return super().build_suite(test_labels, extra_tests=extra_tests, **kwargs) diff --git a/evennia/server/throttle.py b/evennia/server/throttle.py index 737198a53b..17b18a6b71 100644 --- a/evennia/server/throttle.py +++ b/evennia/server/throttle.py @@ -35,14 +35,14 @@ class Throttle: the throttle is imposed! """ try: - self.storage = caches['throttle'] + self.storage = caches["throttle"] except Exception: logger.log_trace("Throttle: Errors encountered; using default cache.") - self.storage = caches['default'] + self.storage = caches["default"] - self.name = kwargs.get('name', 'undefined-throttle') + self.name = kwargs.get("name", "undefined-throttle") self.limit = kwargs.get("limit", 5) - self.cache_size = kwargs.get('cache_size', self.limit) + self.cache_size = kwargs.get("cache_size", self.limit) self.timeout = kwargs.get("timeout", 5 * 60) def get_cache_key(self, *args, **kwargs): @@ -51,7 +51,7 @@ class Throttle: collisions in the same namespace. """ - return '-'.join((self.name, *args)) + return "-".join((self.name, *args)) def touch(self, key, *args, **kwargs): """ @@ -85,7 +85,7 @@ class Throttle: cache_key = self.get_cache_key(str(ip)) return self.storage.get(cache_key, deque(maxlen=self.cache_size)) else: - keys_key = self.get_cache_key('keys') + keys_key = self.get_cache_key("keys") keys = self.storage.get_or_set(keys_key, set(), self.timeout) data = self.storage.get_many((self.get_cache_key(x) for x in keys)) @@ -161,7 +161,7 @@ class Throttle: IP, not the cache-prefixed key. """ - keys_key = self.get_cache_key('keys') + keys_key = self.get_cache_key("keys") keys = self.storage.get(keys_key, set()) keys.add(ip) self.storage.set(keys_key, keys, self.timeout) @@ -175,7 +175,7 @@ class Throttle: ip(str): IP to remove from list of keys. """ - keys_key = self.get_cache_key('keys') + keys_key = self.get_cache_key("keys") keys = self.storage.get(keys_key, set()) try: keys.remove(ip) diff --git a/evennia/settings_default.py b/evennia/settings_default.py index 914d5c0704..ecfd723405 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -301,7 +301,7 @@ ATTRIBUTE_STORED_MODEL_RENAME = [ (("typeclasses", "defaultplayer"), ("typeclasses", "defaultaccount")), ] # Default type of autofield (required by Django) -DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' +DEFAULT_AUTO_FIELD = "django.db.models.AutoField" ###################################################################### # Evennia webclient options @@ -316,7 +316,7 @@ WEBCLIENT_OPTIONS = { # Shows notifications of new messages as popup windows "notification_popup": False, # Plays a sound for notifications of new messages - "notification_sound": False + "notification_sound": False, } ###################################################################### @@ -487,7 +487,7 @@ COMMAND_DEFAULT_CLASS = "evennia.commands.default.muxcommand.MuxCommand" # input. By default the command-name should end with a space or / (since the # default commands uses MuxCommand and /switches). Note that the extra \n # is necessary for use with batchprocessor. -COMMAND_DEFAULT_ARG_REGEX = r'^[ /]|\n|$' +COMMAND_DEFAULT_ARG_REGEX = r"^[ /]|\n|$" # By default, Command.msg will only send data to the Session calling # the Command in the first place. If set, Command.msg will instead return # data to all Sessions connected to the Account/Character associated with @@ -661,9 +661,9 @@ HELP_CLICKABLE_TOPICS = True # This changes the start-symbol for the funcparser callable. Note that # this will make a lot of documentation invalid and there may also be # other unexpected side effects, so change with caution. -FUNCPARSER_START_CHAR = '$' +FUNCPARSER_START_CHAR = "$" # The symbol to use to escape Func -FUNCPARSER_ESCAPE_CHAR = '\\' +FUNCPARSER_ESCAPE_CHAR = "\\" # This is the global max nesting-level for nesting functions in # the funcparser. This protects against infinite loops. FUNCPARSER_MAX_NESTING = 20 @@ -676,8 +676,10 @@ FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED = False FUNCPARSER_OUTGOING_MESSAGES_MODULES = ["evennia.utils.funcparser", "server.conf.inlinefuncs"] # Prototype values are also parsed with FuncParser. These modules # define which $func callables are available to use in prototypes. -FUNCPARSER_PROTOTYPE_PARSING_MODULES = ["evennia.prototypes.protfuncs", - "server.conf.prototypefuncs"] +FUNCPARSER_PROTOTYPE_PARSING_MODULES = [ + "evennia.prototypes.protfuncs", + "server.conf.prototypefuncs", +] ###################################################################### # Global Scripts @@ -745,7 +747,7 @@ LOGIN_THROTTLE_TIMEOUT = 5 * 60 # since they can be exploitative. This list defines Account-level permissions # (and higher) that bypass this stripping. It is used as a fallback if a # specific list of perms are not given to the helper function. -INPUT_CLEANUP_BYPASS_PERMISSIONS = ['Builder'] +INPUT_CLEANUP_BYPASS_PERMISSIONS = ["Builder"] ###################################################################### @@ -898,8 +900,8 @@ USE_I18N = False # Where to find locales (no need to change this, most likely) LOCALE_PATHS = [os.path.join(EVENNIA_DIR, "locale/")] # How to display time stamps in e.g. the admin -SHORT_DATETIME_FORMAT = 'Y-m-d H:i:s.u' -DATETIME_FORMAT = 'Y-m-d H:i:s' # ISO 8601 but without T and timezone +SHORT_DATETIME_FORMAT = "Y-m-d H:i:s.u" +DATETIME_FORMAT = "Y-m-d H:i:s" # ISO 8601 but without T and timezone # This should be turned off unless you want to do tests with Django's # development webserver (normally Evennia runs its own server) SERVE_MEDIA = False @@ -965,16 +967,14 @@ TEMPLATES = [ # Django cache settings # https://docs.djangoproject.com/en/dev/topics/cache/#setting-up-the-cache CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + "default": { + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", + }, + "throttle": { + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", + "TIMEOUT": 60 * 5, + "OPTIONS": {"MAX_ENTRIES": 2000}, }, - 'throttle': { - 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', - 'TIMEOUT': 60 * 5, - 'OPTIONS': { - 'MAX_ENTRIES': 2000 - } - } } # MiddleWare are semi-transparent extensions to Django's functionality. # see http://www.djangoproject.com/documentation/middleware/ for a more detailed @@ -1039,8 +1039,14 @@ AUTH_PASSWORD_VALIDATORS = [ # Username validation plugins AUTH_USERNAME_VALIDATORS = [ {"NAME": "django.contrib.auth.validators.ASCIIUsernameValidator"}, - {"NAME": "django.core.validators.MinLengthValidator", "OPTIONS": {"limit_value": 3},}, - {"NAME": "django.core.validators.MaxLengthValidator", "OPTIONS": {"limit_value": 30},}, + { + "NAME": "django.core.validators.MinLengthValidator", + "OPTIONS": {"limit_value": 3}, + }, + { + "NAME": "django.core.validators.MaxLengthValidator", + "OPTIONS": {"limit_value": 30}, + }, {"NAME": "evennia.server.validators.EvenniaUsernameAvailabilityValidator"}, ] @@ -1059,7 +1065,9 @@ REST_FRAMEWORK = { "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination", "PAGE_SIZE": 25, # require logged in users to call API so that access checks can work on them - "DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated",], + "DEFAULT_PERMISSION_CLASSES": [ + "rest_framework.permissions.IsAuthenticated", + ], # These are the different ways people can authenticate for API requests - via # session or with user/password. Other ways are possible, such as via tokens # or oauth, but require additional dependencies. diff --git a/evennia/typeclasses/attributes.py b/evennia/typeclasses/attributes.py index 257af1fc8e..67522d068d 100644 --- a/evennia/typeclasses/attributes.py +++ b/evennia/typeclasses/attributes.py @@ -173,10 +173,10 @@ class AttributeProperty: foo = AttributeProperty(default="Bar") """ + attrhandler_name = "attributes" - def __init__(self, default=None, category=None, strattr=False, lockstring="", - autocreate=False): + def __init__(self, default=None, category=None, strattr=False, lockstring="", autocreate=False): """ Initialize an Attribute as a property descriptor. @@ -218,13 +218,12 @@ class AttributeProperty: """ value = self._default try: - value = ( - getattr(instance, self.attrhandler_name) - .get(key=self._key, - default=self._default, - category=self._category, - strattr=self._strattr, - raise_exception=self._autocreate) + value = getattr(instance, self.attrhandler_name).get( + key=self._key, + default=self._default, + category=self._category, + strattr=self._strattr, + raise_exception=self._autocreate, ) except AttributeError: if self._autocreate: @@ -241,12 +240,13 @@ class AttributeProperty: """ ( - getattr(instance, self.attrhandler_name) - .add(self._key, - value, - category=self._category, - lockstring=self._lockstring, - strattr=self._strattr) + getattr(instance, self.attrhandler_name).add( + self._key, + value, + category=self._category, + lockstring=self._lockstring, + strattr=self._strattr, + ) ) def __delete__(self, instance): @@ -254,11 +254,7 @@ class AttributeProperty: Called when running `del` on the field. Will remove/clear the Attribute. """ - ( - getattr(instance, self.attrhandler_name) - .remove(key=self._key, - category=self._category) - ) + (getattr(instance, self.attrhandler_name).remove(key=self._key, category=self._category)) class NAttributeProperty(AttributeProperty): @@ -273,6 +269,7 @@ class NAttributeProperty(AttributeProperty): foo = NAttributeProperty(default="Bar") """ + attrhandler_name = "nattributes" @@ -357,7 +354,7 @@ class Attribute(IAttribute, SharedMemoryModel): self.save(update_fields=["db_lock_storage"]) def __lock_storage_del(self): - self.db_lock_storage = '' + self.db_lock_storage = "" self.save(update_fields=["db_lock_storage"]) lock_storage = property(__lock_storage_get, __lock_storage_set, __lock_storage_del) @@ -387,6 +384,7 @@ class Attribute(IAttribute, SharedMemoryModel): """Deleter. Allows for del attr.value. This removes the entire attribute.""" self.delete() + # # Handlers making use of the Attribute model # @@ -1465,15 +1463,14 @@ def initialize_nick_templates(pattern, replacement, pattern_is_regex=False): # groups. we need to split out any | - separated parts so we can # attach the line-break/ending extras all regexes require. pattern_regex_string = r"|".join( - or_part + r"(?:[\n\r]*?)\Z" - for or_part in _RE_OR.split(pattern)) + or_part + r"(?:[\n\r]*?)\Z" for or_part in _RE_OR.split(pattern) + ) else: # Shell pattern syntax - convert $N to argN groups # for the shell pattern we make sure we have matching $N on both sides pattern_args = [match.group(1) for match in _RE_NICK_RAW_ARG.finditer(pattern)] - replacement_args = [ - match.group(1) for match in _RE_NICK_RAW_ARG.finditer(replacement)] + replacement_args = [match.group(1) for match in _RE_NICK_RAW_ARG.finditer(replacement)] if set(pattern_args) != set(replacement_args): # We don't have the same amount of argN/$N tags in input/output. raise NickTemplateInvalid("Nicks: Both in/out-templates must contain the same $N tags.") @@ -1482,7 +1479,8 @@ def initialize_nick_templates(pattern, replacement, pattern_is_regex=False): pattern_regex_string = fnmatch.translate(pattern) pattern_regex_string = _RE_NICK_SPACE.sub(r"\\s+", pattern_regex_string) pattern_regex_string = _RE_NICK_ARG.sub( - lambda m: "(?P.+?)" % m.group(2), pattern_regex_string) + lambda m: "(?P.+?)" % m.group(2), pattern_regex_string + ) # we must account for a possible line break coming over the wire pattern_regex_string = pattern_regex_string[:-2] + r"(?:[\n\r]*?)\Z" @@ -1509,8 +1507,9 @@ def parse_nick_template(string, template_regex, outtemplate): """ match = template_regex.match(string) if match: - matchdict = {key: value if value is not None else "" - for key, value in match.groupdict().items()} + matchdict = { + key: value if value is not None else "" for key, value in match.groupdict().items() + } return True, outtemplate.format(**matchdict) return False, string @@ -1613,9 +1612,11 @@ class NickHandler(AttributeHandler): """ nick_regex, nick_template = initialize_nick_templates( - pattern, replacement, pattern_is_regex=pattern_is_regex) - super().add(pattern, (nick_regex, nick_template, pattern, replacement), - category=category, **kwargs) + pattern, replacement, pattern_is_regex=pattern_is_regex + ) + super().add( + pattern, (nick_regex, nick_template, pattern, replacement), category=category, **kwargs + ) def remove(self, key, category="inputline", **kwargs): """ diff --git a/evennia/typeclasses/managers.py b/evennia/typeclasses/managers.py index 1136c52762..c8ad666e23 100644 --- a/evennia/typeclasses/managers.py +++ b/evennia/typeclasses/managers.py @@ -514,7 +514,8 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): typeclass=F("db_typeclass_path"), # Calculate this class' percentage of total composition percent=ExpressionWrapper( - ((F("count") / float(self.count())) * 100.0), output_field=FloatField(), + ((F("count") / float(self.count())) * 100.0), + output_field=FloatField(), ), ) .values("typeclass", "count", "percent") @@ -750,11 +751,7 @@ class TypeclassManager(TypedObjectManager): Returns: Annotated queryset. """ - return ( - super() - .filter(db_typeclass_path=self.model.path) - .annotate(*args, **kwargs) - ) + return super().filter(db_typeclass_path=self.model.path).annotate(*args, **kwargs) def values(self, *args, **kwargs): """ @@ -766,11 +763,7 @@ class TypeclassManager(TypedObjectManager): Returns: Queryset of values dictionaries, just filtered by typeclass first. """ - return ( - super() - .filter(db_typeclass_path=self.model.path) - .values(*args, **kwargs) - ) + return super().filter(db_typeclass_path=self.model.path).values(*args, **kwargs) def values_list(self, *args, **kwargs): """ @@ -782,11 +775,7 @@ class TypeclassManager(TypedObjectManager): Returns: Queryset of value_list tuples, just filtered by typeclass first. """ - return ( - super() - .filter(db_typeclass_path=self.model.path) - .values_list(*args, **kwargs) - ) + return super().filter(db_typeclass_path=self.model.path).values_list(*args, **kwargs) def _get_subclasses(self, cls): """ diff --git a/evennia/typeclasses/models.py b/evennia/typeclasses/models.py index 6512d30527..23acf4e714 100644 --- a/evennia/typeclasses/models.py +++ b/evennia/typeclasses/models.py @@ -213,7 +213,7 @@ class TypedObject(SharedMemoryModel): max_length=255, null=True, help_text="this defines what 'type' of entity this is. This variable holds " - "a Python path to a module with a valid Evennia Typeclass.", + "a Python path to a module with a valid Evennia Typeclass.", db_index=True, ) # Creation date. This is not changed once the object is created. @@ -223,19 +223,19 @@ class TypedObject(SharedMemoryModel): "locks", blank=True, help_text="locks limit access to an entity. A lock is defined as a 'lock string' " - "on the form 'type:lockfunctions', defining what functionality is locked and " - "how to determine access. Not defining a lock means no access is granted.", + "on the form 'type:lockfunctions', defining what functionality is locked and " + "how to determine access. Not defining a lock means no access is granted.", ) # many2many relationships db_attributes = models.ManyToManyField( Attribute, help_text="attributes on this object. An attribute can hold any pickle-able " - "python object (see docs for special cases).", + "python object (see docs for special cases).", ) db_tags = models.ManyToManyField( Tag, help_text="tags on this object. Tags are simple string markers to identify, " - "group and alias objects.", + "group and alias objects.", ) # Database manager diff --git a/evennia/typeclasses/tests.py b/evennia/typeclasses/tests.py index 4e26a6161d..6a9f6bdbcc 100644 --- a/evennia/typeclasses/tests.py +++ b/evennia/typeclasses/tests.py @@ -102,7 +102,8 @@ class TestTypedObjectManager(BaseEvenniaTest): [self.obj1, self.obj2], ) self.assertEqual( - self._manager("get_by_tag", ["tag5", "tag7"], "category1"), [self.obj1, self.obj2], + self._manager("get_by_tag", ["tag5", "tag7"], "category1"), + [self.obj1, self.obj2], ) self.assertEqual(self._manager("get_by_tag", category="category1"), [self.obj1, self.obj2]) self.assertEqual(self._manager("get_by_tag", category="category2"), [self.obj2]) @@ -180,45 +181,96 @@ class TestNickHandler(BaseEvenniaTest): Test the nick handler replacement. """ - @parameterized.expand([ - # shell syntax - ("gr $1 $2 at $3", "emote with a $1 smile, $2 grins at $3.", False, - "gr happy Foo at Bar", "emote with a happy smile, Foo grins at Bar."), - # regex syntax - ("gr (?P.+?) (?P.+?) at (?P.+?)", - "emote with a $1 smile, $2 grins at $3.", True, - "gr happy Foo at Bar", "emote with a happy smile, Foo grins at Bar."), - # channel-style syntax - ("groo $1", "channel groo = $1", False, - "groo Hello world", "channel groo = Hello world"), - (r"groo\s*?|groo\s+?(?P.+?)", "channel groo = $1", True, - "groo Hello world", "channel groo = Hello world"), - (r"groo\s*?|groo\s+?(?P.+?)", "channel groo = $1", True, - "groo ", "channel groo = "), - (r"groo\s*?|groo\s*?(?P.+?)", "channel groo = $1", True, - "groo", "channel groo = "), - (r"groo\s*?|groo\s+?(?P.+?)", "channel groo = $1", True, - "grooHello world", "grooHello world"), # not matched - this is correct! - # optional, space-separated arguments - (r"groo\s*?|groo\s+?(?P.+?)(?:\s+?(?P.+?)){0,1}", - "channel groo = $1 and $2", True, - "groo Hello World", "channel groo = Hello and World"), - (r"groo\s*?|groo\s+?(?P.+?)(?:\s+?(?P.+?)){0,1}", - "channel groo = $1 and $2", True, - "groo Hello", "channel groo = Hello and "), - (r"groo\s*?|groo\s+?(?P.+?)(?:\s+?(?P.+?)){0,1}", - "channel groo = $1 and $2", True, - "groo", "channel groo = and "), # $1/$2 replaced by '' - ]) - def test_nick_parsing(self, pattern, replacement, pattern_is_regex, - inp_string, expected_replaced): + + @parameterized.expand( + [ + # shell syntax + ( + "gr $1 $2 at $3", + "emote with a $1 smile, $2 grins at $3.", + False, + "gr happy Foo at Bar", + "emote with a happy smile, Foo grins at Bar.", + ), + # regex syntax + ( + "gr (?P.+?) (?P.+?) at (?P.+?)", + "emote with a $1 smile, $2 grins at $3.", + True, + "gr happy Foo at Bar", + "emote with a happy smile, Foo grins at Bar.", + ), + # channel-style syntax + ( + "groo $1", + "channel groo = $1", + False, + "groo Hello world", + "channel groo = Hello world", + ), + ( + r"groo\s*?|groo\s+?(?P.+?)", + "channel groo = $1", + True, + "groo Hello world", + "channel groo = Hello world", + ), + ( + r"groo\s*?|groo\s+?(?P.+?)", + "channel groo = $1", + True, + "groo ", + "channel groo = ", + ), + ( + r"groo\s*?|groo\s*?(?P.+?)", + "channel groo = $1", + True, + "groo", + "channel groo = ", + ), + ( + r"groo\s*?|groo\s+?(?P.+?)", + "channel groo = $1", + True, + "grooHello world", + "grooHello world", + ), # not matched - this is correct! + # optional, space-separated arguments + ( + r"groo\s*?|groo\s+?(?P.+?)(?:\s+?(?P.+?)){0,1}", + "channel groo = $1 and $2", + True, + "groo Hello World", + "channel groo = Hello and World", + ), + ( + r"groo\s*?|groo\s+?(?P.+?)(?:\s+?(?P.+?)){0,1}", + "channel groo = $1 and $2", + True, + "groo Hello", + "channel groo = Hello and ", + ), + ( + r"groo\s*?|groo\s+?(?P.+?)(?:\s+?(?P.+?)){0,1}", + "channel groo = $1 and $2", + True, + "groo", + "channel groo = and ", + ), # $1/$2 replaced by '' + ] + ) + def test_nick_parsing( + self, pattern, replacement, pattern_is_regex, inp_string, expected_replaced + ): """ Setting up nick patterns and make sure they replace as expected. """ # from evennia import set_trace;set_trace() - self.char1.nicks.add(pattern, replacement, - category="inputline", pattern_is_regex=pattern_is_regex) + self.char1.nicks.add( + pattern, replacement, category="inputline", pattern_is_regex=pattern_is_regex + ) actual_replaced = self.char1.nicks.nickreplace(inp_string) self.assertEqual(expected_replaced, actual_replaced) diff --git a/evennia/utils/ansi.py b/evennia/utils/ansi.py index fe2f791c0f..2f1868fc9d 100644 --- a/evennia/utils/ansi.py +++ b/evennia/utils/ansi.py @@ -440,7 +440,7 @@ class ANSIParser(object): Strip explicitly ansi line breaks and tabs. """ - return self.unsafe_tokens.sub('', string) + return self.unsafe_tokens.sub("", string) def parse_ansi(self, string, strip_ansi=False, xterm256=False, mxp=False): """ @@ -998,10 +998,10 @@ class ANSIString(str, metaclass=ANSIMeta): return ANSIString(self._raw_string[slc]) if slc.start is None: # this is a [:x] slice - return ANSIString(self._raw_string[:char_indexes[0]]) + return ANSIString(self._raw_string[: char_indexes[0]]) if slc.stop is None: # a [x:] slice - return ANSIString(self._raw_string[char_indexes[-1] + 1:]) + return ANSIString(self._raw_string[char_indexes[-1] + 1 :]) return ANSIString("") try: string = self[slc.start or 0]._raw_string @@ -1104,7 +1104,7 @@ class ANSIString(str, metaclass=ANSIMeta): current_index = 0 result = tuple() for section in parent_result: - result += (self[current_index: current_index + len(section)],) + result += (self[current_index : current_index + len(section)],) current_index += len(section) return result @@ -1224,7 +1224,7 @@ class ANSIString(str, metaclass=ANSIMeta): start = next + bylen maxsplit -= 1 # NB. if it's already < 0, it stays < 0 - res.append(self[start: len(self)]) + res.append(self[start : len(self)]) if drop_spaces: return [part for part in res if part != ""] return res @@ -1267,7 +1267,7 @@ class ANSIString(str, metaclass=ANSIMeta): if next < 0: break # Get character codes after the index as well. - res.append(self[next + bylen: end]) + res.append(self[next + bylen : end]) end = next maxsplit -= 1 # NB. if it's already < 0, it stays < 0 @@ -1321,7 +1321,7 @@ class ANSIString(str, metaclass=ANSIMeta): ic -= 1 ir2 -= 1 rstripped = rstripped[::-1] - return ANSIString(lstripped + raw[ir1: ir2 + 1] + rstripped) + return ANSIString(lstripped + raw[ir1 : ir2 + 1] + rstripped) def lstrip(self, chars=None): """ @@ -1440,7 +1440,7 @@ class ANSIString(str, metaclass=ANSIMeta): start = None end = char._char_indexes[0] prefix = char._raw_string[start:end] - postfix = char._raw_string[end + 1:] + postfix = char._raw_string[end + 1 :] line = char._clean_string * amount code_indexes = [i for i in range(0, len(prefix))] length = len(prefix) + len(line) diff --git a/evennia/utils/containers.py b/evennia/utils/containers.py index 1b91cc0e8a..6b709fbf75 100644 --- a/evennia/utils/containers.py +++ b/evennia/utils/containers.py @@ -134,11 +134,12 @@ class GlobalScriptContainer(Container): typeclass = self.typeclass_storage[key] script = typeclass.objects.filter( - db_key=key, db_account__isnull=True, db_obj__isnull=True).first() + db_key=key, db_account__isnull=True, db_obj__isnull=True + ).first() kwargs = {**self.loaded_data[key]} - kwargs['key'] = key - kwargs['persistent'] = kwargs.get('persistent', True) + kwargs["key"] = key + kwargs["persistent"] = kwargs.get("persistent", True) compare_hash = str(dumps(kwargs, protocol=4)) @@ -146,8 +147,9 @@ class GlobalScriptContainer(Container): script_hash = script.attributes.get("global_script_settings", category="settings_hash") if script_hash is None: # legacy - store the hash anew and assume no change - script.attributes.add("global_script_settings", compare_hash, - category="settings_hash") + script.attributes.add( + "global_script_settings", compare_hash, category="settings_hash" + ) elif script_hash != compare_hash: # wipe the old version and create anew logger.log_info(f"GLOBAL_SCRIPTS: Settings changed for {key} ({typeclass}).") @@ -164,8 +166,7 @@ class GlobalScriptContainer(Container): return None # store a hash representation of the setup - script.attributes.add("_global_script_settings", - compare_hash, category="settings_hash") + script.attributes.add("_global_script_settings", compare_hash, category="settings_hash") script.start() return script @@ -200,7 +201,8 @@ class GlobalScriptContainer(Container): self.typeclass_storage[key] = class_from_module(typeclass) except Exception: logger.log_trace( - f"GlobalScriptContainer could not start import global script {key}.") + f"GlobalScriptContainer could not start import global script {key}." + ) def get(self, key, default=None): """ diff --git a/evennia/utils/dbserialize.py b/evennia/utils/dbserialize.py index 02d6e9fa28..438dd5eee2 100644 --- a/evennia/utils/dbserialize.py +++ b/evennia/utils/dbserialize.py @@ -391,7 +391,7 @@ class _SaverDeque(_SaverMutable): @_save def rotate(self, *args): self._data.rotate(*args) - + @_save def remove(self, *args): self._data.remove(*args) diff --git a/evennia/utils/eveditor.py b/evennia/utils/eveditor.py index 6f1dba6fb5..c51ff8bc4b 100644 --- a/evennia/utils/eveditor.py +++ b/evennia/utils/eveditor.py @@ -64,7 +64,8 @@ _DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH # # ------------------------------------------------------------- -_HELP_TEXT = _(""" +_HELP_TEXT = _( + """ - any non-command is appended to the end of the buffer. : - view buffer or only line(s) :: - raw-view buffer or only line(s) @@ -100,54 +101,67 @@ _HELP_TEXT = _(""" :fd - de-indent entire buffer or line :echo - turn echoing of the input on/off (helpful for some clients) -""") +""" +) -_HELP_LEGEND = _(""" +_HELP_LEGEND = _( + """ Legend: - line number, like '5' or range, like '3:7'. - a single word, or multiple words with quotes around them. - longer string, usually not needing quotes. -""") +""" +) -_HELP_CODE = _(""" +_HELP_CODE = _( + """ :! - Execute code buffer without saving :< - Decrease the level of automatic indentation for the next lines :> - Increase the level of automatic indentation for the next lines := - Switch automatic indentation on/off """.lstrip( - "\n" -)) + "\n" + ) +) -_ERROR_LOADFUNC = _(""" +_ERROR_LOADFUNC = _( + """ {error} |rBuffer load function error. Could not load initial data.|n -""") +""" +) -_ERROR_SAVEFUNC = _(""" +_ERROR_SAVEFUNC = _( + """ {error} |rSave function returned an error. Buffer not saved.|n -""") +""" +) _ERROR_NO_SAVEFUNC = _("|rNo save function defined. Buffer cannot be saved.|n") _MSG_SAVE_NO_CHANGE = _("No changes need saving") _DEFAULT_NO_QUITFUNC = _("Exited editor.") -_ERROR_QUITFUNC = _(""" +_ERROR_QUITFUNC = _( + """ {error} |rQuit function gave an error. Skipping.|n -""") +""" +) -_ERROR_PERSISTENT_SAVING = _(""" +_ERROR_PERSISTENT_SAVING = _( + """ {error} |rThe editor state could not be saved for persistent mode. Switching to non-persistent mode (which means the editor session won't survive an eventual server reload - so save often!)|n -""") +""" +) _TRACE_PERSISTENT_SAVING = _( "EvEditor persistent-mode error. Commonly, this is because one or " @@ -522,11 +536,15 @@ class CmdEditorGroup(CmdEditorBase): if not self.linerange: lstart = 0 lend = self.cline + 1 - caller.msg(_("Removed {arg1} for lines {l1}-{l2}.").format( - arg1=self.arg1, l1=lstart + 1, l2=lend + 1)) + caller.msg( + _("Removed {arg1} for lines {l1}-{l2}.").format( + arg1=self.arg1, l1=lstart + 1, l2=lend + 1 + ) + ) else: - caller.msg(_("Removed {arg1} for {line}.").format( - arg1=self.arg1, line=self.lstr)) + caller.msg( + _("Removed {arg1} for {line}.").format(arg1=self.arg1, line=self.lstr) + ) sarea = "\n".join(linebuffer[lstart:lend]) sarea = re.sub(r"%s" % self.arg1.strip("'").strip('"'), "", sarea, re.MULTILINE) buf = linebuffer[:lstart] + sarea.split("\n") + linebuffer[lend:] @@ -561,8 +579,11 @@ class CmdEditorGroup(CmdEditorBase): else: buf = linebuffer[:lstart] + editor._copy_buffer + linebuffer[lstart:] editor.update_buffer(buf) - caller.msg(_("Pasted buffer {cbuf} to {line}.").format( - cbuf=editor._copy_buffer, line=self.lstr)) + caller.msg( + _("Pasted buffer {cbuf} to {line}.").format( + cbuf=editor._copy_buffer, line=self.lstr + ) + ) elif cmd == ":i": # :i - insert new line new_lines = self.args.split("\n") @@ -571,8 +592,11 @@ class CmdEditorGroup(CmdEditorBase): else: buf = linebuffer[:lstart] + new_lines + linebuffer[lstart:] editor.update_buffer(buf) - caller.msg(_("Inserted {num} new line(s) at {line}.").format( - num=len(new_lines), line=self.lstr)) + caller.msg( + _("Inserted {num} new line(s) at {line}.").format( + num=len(new_lines), line=self.lstr + ) + ) elif cmd == ":r": # :r - replace lines new_lines = self.args.split("\n") @@ -581,8 +605,11 @@ class CmdEditorGroup(CmdEditorBase): else: buf = linebuffer[:lstart] + new_lines + linebuffer[lend:] editor.update_buffer(buf) - caller.msg(_("Replaced {num} line(s) at {line}.").format( - num=len(new_lines), line=self.lstr)) + caller.msg( + _("Replaced {num} line(s) at {line}.").format( + num=len(new_lines), line=self.lstr + ) + ) elif cmd == ":I": # :I - insert text at beginning of line(s) if not self.raw_string and not editor._codefunc: @@ -618,12 +645,14 @@ class CmdEditorGroup(CmdEditorBase): lend = self.cline + 1 caller.msg( _("Search-replaced {arg1} -> {arg2} for lines {l1}-{l2}.").format( - arg1=self.arg1, arg2=self.arg2, l1=lstart + 1, l2=lend) + arg1=self.arg1, arg2=self.arg2, l1=lstart + 1, l2=lend + ) ) else: caller.msg( _("Search-replaced {arg1} -> {arg2} for {line}.").format( - arg1=self.arg1, arg2=self.arg2, line=self.lstr) + arg1=self.arg1, arg2=self.arg2, line=self.lstr + ) ) sarea = "\n".join(linebuffer[lstart:lend]) @@ -645,8 +674,7 @@ class CmdEditorGroup(CmdEditorBase): if not self.linerange: lstart = 0 lend = self.cline + 1 - caller.msg(_("Flood filled lines {l1}-{l2}.").format( - l1=lstart + 1, l2=lend)) + caller.msg(_("Flood filled lines {l1}-{l2}.").format(l1=lstart + 1, l2=lend)) else: caller.msg(_("Flood filled {line}.").format(line=self.lstr)) fbuf = "\n".join(linebuffer[lstart:lend]) @@ -678,11 +706,15 @@ class CmdEditorGroup(CmdEditorBase): if not self.linerange: lstart = 0 lend = self.cline + 1 - self.caller.msg(_("{align}-justified lines {l1}-{l2}.").format( - align=align_name[align], l1=lstart + 1, l2=lend)) + self.caller.msg( + _("{align}-justified lines {l1}-{l2}.").format( + align=align_name[align], l1=lstart + 1, l2=lend + ) + ) else: - self.caller.msg(_("{align}-justified {line}.").format( - align=align_name[align], line=self.lstr)) + self.caller.msg( + _("{align}-justified {line}.").format(align=align_name[align], line=self.lstr) + ) jbuf = "\n".join(linebuffer[lstart:lend]) jbuf = justify(jbuf, width=width, align=align) buf = linebuffer[:lstart] + jbuf.split("\n") + linebuffer[lend:] @@ -704,8 +736,11 @@ class CmdEditorGroup(CmdEditorBase): if not self.linerange: lstart = 0 lend = self.cline + 1 - caller.msg(_("Removed left margin (dedented) lines {l1}-{l2}.").format( - l1=lstart + 1, l2=lend)) + caller.msg( + _("Removed left margin (dedented) lines {l1}-{l2}.").format( + l1=lstart + 1, l2=lend + ) + ) else: caller.msg(_("Removed left margin (dedented) {line}.").format(line=self.lstr)) fbuf = "\n".join(linebuffer[lstart:lend]) @@ -727,9 +762,11 @@ class CmdEditorGroup(CmdEditorBase): editor.decrease_indent() indent = editor._indent if indent >= 0: - caller.msg(_( - "Decreased indentation: new indentation is {indent}.").format( - indent=indent)) + caller.msg( + _("Decreased indentation: new indentation is {indent}.").format( + indent=indent + ) + ) else: caller.msg(_("|rManual indentation is OFF.|n Use := to turn it on.")) else: @@ -740,9 +777,11 @@ class CmdEditorGroup(CmdEditorBase): editor.increase_indent() indent = editor._indent if indent >= 0: - caller.msg(_( - "Increased indentation: new indentation is {indent}.").format( - indent=indent)) + caller.msg( + _("Increased indentation: new indentation is {indent}.").format( + indent=indent + ) + ) else: caller.msg(_("|rManual indentation is OFF.|n Use := to turn it on.")) else: @@ -909,9 +948,11 @@ class EvEditor: try: self._buffer = self._loadfunc(self._caller) if not isinstance(self._buffer, str): - self._caller.msg(f"|rBuffer is of type |w{type(self._buffer)})|r. " - "Continuing, it is converted to a string " - "(and will be saved as such)!|n") + self._caller.msg( + f"|rBuffer is of type |w{type(self._buffer)})|r. " + "Continuing, it is converted to a string " + "(and will be saved as such)!|n" + ) self._buffer = to_str(self._buffer) except Exception as e: from evennia.utils import logger diff --git a/evennia/utils/evmenu.py b/evennia/utils/evmenu.py index 0081e70148..a3f34f47b2 100644 --- a/evennia/utils/evmenu.py +++ b/evennia/utils/evmenu.py @@ -1264,7 +1264,7 @@ class EvMenu: table.extend([" " for i in range(nrows - nlastcol)]) # build the actual table grid - table = [table[icol * nrows: (icol * nrows) + nrows] for icol in range(0, ncols)] + table = [table[icol * nrows : (icol * nrows) + nrows] for icol in range(0, ncols)] # adjust the width of each column for icol in range(len(table)): @@ -1390,14 +1390,17 @@ def list_node(option_generator, select=None, pagesize=10): try: if bool(getargspec(select).keywords): return select( - caller, selection, available_choices=available_choices, **kwargs) + caller, selection, available_choices=available_choices, **kwargs + ) else: return select(caller, selection, **kwargs) except Exception: - logger.log_trace("Error in EvMenu.list_node decorator:\n " - f"select-callable: {select}\n with args: ({caller}" - f"{selection}, {available_choices}, {kwargs}) raised " - "exception.") + logger.log_trace( + "Error in EvMenu.list_node decorator:\n " + f"select-callable: {select}\n with args: ({caller}" + f"{selection}, {available_choices}, {kwargs}) raised " + "exception." + ) elif select: # we assume a string was given, we inject the result into the kwargs # to pass on to the next node @@ -1420,7 +1423,7 @@ def list_node(option_generator, select=None, pagesize=10): if option_list: nall_options = len(option_list) pages = [ - option_list[ind: ind + pagesize] for ind in range(0, nall_options, pagesize) + option_list[ind : ind + pagesize] for ind in range(0, nall_options, pagesize) ] npages = len(pages) @@ -1661,7 +1664,7 @@ class CmdYesNoQuestion(Command): """ key = _CMD_NOINPUT - aliases = [_CMD_NOMATCH, "yes", "no", 'y', 'n', 'a', 'abort'] + aliases = [_CMD_NOMATCH, "yes", "no", "y", "n", "a", "abort"] arg_regex = r"^$" def _clean(self, caller): @@ -1694,7 +1697,7 @@ class CmdYesNoQuestion(Command): else: inp = raw - if inp in ('a', 'abort') and yes_no_question.allow_abort: + if inp in ("a", "abort") and yes_no_question.allow_abort: caller.msg(_("Aborted.")) self._clean(caller) return @@ -1703,11 +1706,11 @@ class CmdYesNoQuestion(Command): args = yes_no_question.args kwargs = yes_no_question.kwargs - kwargs['caller_session'] = self.session + kwargs["caller_session"] = self.session - if inp in ('yes', 'y'): + if inp in ("yes", "y"): yes_no_question.yes_callable(caller, *args, **kwargs) - elif inp in ('no', 'n'): + elif inp in ("no", "n"): yes_no_question.no_callable(caller, *args, **kwargs) else: # invalid input. Resend prompt without cleaning @@ -1741,8 +1744,17 @@ class YesNoQuestionCmdSet(CmdSet): self.add(CmdYesNoQuestion()) -def ask_yes_no(caller, prompt="Yes or No {options}?", yes_action="Yes", no_action="No", - default=None, allow_abort=False, session=None, *args, **kwargs): +def ask_yes_no( + caller, + prompt="Yes or No {options}?", + yes_action="Yes", + no_action="No", + default=None, + allow_abort=False, + session=None, + *args, + **kwargs, +): """ A helper question for asking a simple yes/no question. This will cause the system to pause and wait for input from the player. @@ -1786,22 +1798,23 @@ def ask_yes_no(caller, prompt="Yes or No {options}?", yes_action="Yes", no_actio _callable_yes, _callable_no, allow_abort=True) """ + def _callable_yes_txt(caller, *args, **kwargs): - yes_txt = kwargs['yes_txt'] - session = kwargs['caller_session'] + yes_txt = kwargs["yes_txt"] + session = kwargs["caller_session"] caller.msg(yes_txt, session=session) def _callable_no_txt(caller, *args, **kwargs): - no_txt = kwargs['no_txt'] - session = kwargs['caller_session'] + no_txt = kwargs["no_txt"] + session = kwargs["caller_session"] caller.msg(no_txt, session=session) if not callable(yes_action): - kwargs['yes_txt'] = str(yes_action) + kwargs["yes_txt"] = str(yes_action) yes_action = _callable_yes_txt if not callable(no_action): - kwargs['no_txt'] = str(no_action) + kwargs["no_txt"] = str(no_action) no_action = _callable_no_txt # prepare the prompt with options @@ -1843,9 +1856,7 @@ def ask_yes_no(caller, prompt="Yes or No {options}?", yes_action="Yes", no_actio _RE_NODE = re.compile(r"##\s*?NODE\s+?(?P\S[\S\s]*?)$", re.I + re.M) _RE_OPTIONS_SEP = re.compile(r"##\s*?OPTIONS\s*?$", re.I + re.M) _RE_CALLABLE = re.compile(r"\S+?\(\)", re.I + re.M) -_RE_CALLABLE = re.compile( - r"(?P\S+?)(?:\((?P[\S\s]+?)\)|\(\))", re.I + re.M -) +_RE_CALLABLE = re.compile(r"(?P\S+?)(?:\((?P[\S\s]+?)\)|\(\))", re.I + re.M) _HELP_NO_OPTION_MATCH = _("Choose an option or try 'help'.") @@ -1859,8 +1870,8 @@ _OPTION_COMMENT_START = "#" # Input/option/goto handler functions that allows for dynamically generated # nodes read from the menu template. -def _process_callable(caller, goto, goto_callables, raw_string, - current_nodename, kwargs): + +def _process_callable(caller, goto, goto_callables, raw_string, current_nodename, kwargs): """ Central helper for parsing a goto-callable (`funcname(**kwargs)`) out of the right-hand-side of the template options and map this to an actual @@ -1876,12 +1887,18 @@ def _process_callable(caller, goto, goto_callables, raw_string, for kwarg in gotokwargs.split(","): if kwarg and "=" in kwarg: key, value = [part.strip() for part in kwarg.split("=", 1)] - if key in ("evmenu_goto", "evmenu_gotomap", "_current_nodename", - "evmenu_current_nodename", "evmenu_goto_callables"): + if key in ( + "evmenu_goto", + "evmenu_gotomap", + "_current_nodename", + "evmenu_current_nodename", + "evmenu_goto_callables", + ): raise RuntimeError( f"EvMenu template error: goto-callable '{goto}' uses a " f"kwarg ({kwarg}) that is reserved for the EvMenu templating " - "system. Rename the kwarg.") + "system. Rename the kwarg." + ) try: key = literal_eval(key) except ValueError: @@ -1908,8 +1925,7 @@ def _generated_goto_func(caller, raw_string, **kwargs): goto = kwargs["evmenu_goto"] goto_callables = kwargs["evmenu_goto_callables"] current_nodename = kwargs["evmenu_current_nodename"] - return _process_callable(caller, goto, goto_callables, raw_string, - current_nodename, kwargs) + return _process_callable(caller, goto, goto_callables, raw_string, current_nodename, kwargs) def _generated_input_goto_func(caller, raw_string, **kwargs): @@ -1929,13 +1945,15 @@ def _generated_input_goto_func(caller, raw_string, **kwargs): # start with glob patterns for pattern, goto in gotomap.items(): if fnmatch(raw_string.lower(), pattern): - return _process_callable(caller, goto, goto_callables, raw_string, - current_nodename, kwargs) + return _process_callable( + caller, goto, goto_callables, raw_string, current_nodename, kwargs + ) # no glob pattern match; try regex for pattern, goto in gotomap.items(): if pattern and re.match(pattern, raw_string.lower(), flags=re.I + re.M): - return _process_callable(caller, goto, goto_callables, raw_string, - current_nodename, kwargs) + return _process_callable( + caller, goto, goto_callables, raw_string, current_nodename, kwargs + ) # no match, show error raise EvMenuGotoAbortMessage(_HELP_NO_OPTION_MATCH) @@ -1966,6 +1984,7 @@ def parse_menu_template(caller, menu_template, goto_callables=None): dict: A `{"node": nodefunc}` menutree suitable to pass into EvMenu. """ + def _validate_kwarg(goto, kwarg): """ Validate goto-callable kwarg is on correct form. @@ -1975,14 +1994,21 @@ def parse_menu_template(caller, menu_template, goto_callables=None): f"EvMenu template error: goto-callable '{goto}' has a " f"non-kwarg argument ({kwarg}). All callables in the " "template must have only keyword-arguments, or no " - "args at all.") + "args at all." + ) key, _ = [part.strip() for part in kwarg.split("=", 1)] - if key in ("evmenu_goto", "evmenu_gotomap", "_current_nodename", - "evmenu_current_nodename", "evmenu_goto_callables"): + if key in ( + "evmenu_goto", + "evmenu_gotomap", + "_current_nodename", + "evmenu_current_nodename", + "evmenu_goto_callables", + ): raise RuntimeError( f"EvMenu template error: goto-callable '{goto}' uses a " f"kwarg ({kwarg}) that is reserved for the EvMenu templating " - "system. Rename the kwarg.") + "system. Rename the kwarg." + ) def _parse_options(nodename, optiontxt, goto_callables): """ @@ -2013,7 +2039,7 @@ def parse_menu_template(caller, menu_template, goto_callables=None): if match: kwargs = match.group("kwargs") if kwargs: - for kwarg in kwargs.split(','): + for kwarg in kwargs.split(","): _validate_kwarg(goto, kwarg) # parse key [;aliases|pattern] @@ -2025,7 +2051,7 @@ def parse_menu_template(caller, menu_template, goto_callables=None): if main_key.startswith(_OPTION_INPUT_MARKER): # if we have a pattern, build the arguments for _default later - pattern = main_key[len(_OPTION_INPUT_MARKER):].strip() + pattern = main_key[len(_OPTION_INPUT_MARKER) :].strip() inputparsemap[pattern] = goto else: # a regular goto string/callable target diff --git a/evennia/utils/evmore.py b/evennia/utils/evmore.py index 5f5ce212f0..9129db1298 100644 --- a/evennia/utils/evmore.py +++ b/evennia/utils/evmore.py @@ -103,6 +103,7 @@ class CmdMoreExit(Command): Any non-more command will exit the pager. """ + key = _CMD_NOMATCH def func(self): @@ -356,7 +357,7 @@ class EvMore(object): querysets); to avoid fetching all objects at the same time. """ - return self._data[pageno * self.height: pageno * self.height + self.height] + return self._data[pageno * self.height : pageno * self.height + self.height] def paginator_django(self, pageno): """ @@ -434,7 +435,7 @@ class EvMore(object): lines = text.split("\n") self._data = [ - _LBR.join(lines[i: i + self.height]) for i in range(0, len(lines), self.height) + _LBR.join(lines[i : i + self.height]) for i in range(0, len(lines), self.height) ] self._npages = len(self._data) diff --git a/evennia/utils/funcparser.py b/evennia/utils/funcparser.py index 033a06c350..e0ae1d3eb6 100644 --- a/evennia/utils/funcparser.py +++ b/evennia/utils/funcparser.py @@ -49,8 +49,14 @@ import random from django.conf import settings from evennia.utils import logger from evennia.utils.utils import ( - make_iter, callables_from_module, variable_from_module, pad, crop, justify, - safe_convert_to_types) + make_iter, + callables_from_module, + variable_from_module, + pad, + crop, + justify, + safe_convert_to_types, +) from evennia.utils import search from evennia.utils.verb_conjugation.conjugate import verb_actor_stance_components from evennia.utils.verb_conjugation.pronouns import pronoun_to_viewpoints @@ -69,6 +75,7 @@ class _ParsedFunc: Represents a function parsed from the string """ + prefix: str = _START_CHAR funcname: str = "" args: list = dataclasses.field(default_factory=list) @@ -96,6 +103,7 @@ class ParsingError(RuntimeError): """ Failed to parse for some reason. """ + pass @@ -106,12 +114,14 @@ class FuncParser: """ - def __init__(self, - callables, - start_char=_START_CHAR, - escape_char=_ESCAPE_CHAR, - max_nesting=_MAX_NESTING, - **default_kwargs): + def __init__( + self, + callables, + start_char=_START_CHAR, + escape_char=_ESCAPE_CHAR, + max_nesting=_MAX_NESTING, + **default_kwargs, + ): """ Initialize the parser. @@ -145,7 +155,8 @@ class FuncParser: loaded_callables = {} for module_or_path in make_iter(callables): callables_mapping = variable_from_module( - module_or_path, variable="FUNCPARSER_CALLABLES") + module_or_path, variable="FUNCPARSER_CALLABLES" + ) if callables_mapping: try: # mapping supplied in variable @@ -153,7 +164,8 @@ class FuncParser: except ValueError: raise ParsingError( f"Failure to parse - {module_or_path}.FUNCPARSER_CALLABLES " - "(must be a dict {'funcname': callable, ...})") + "(must be a dict {'funcname': callable, ...})" + ) else: # use all top-level variables # (handles both paths and module instances @@ -229,13 +241,18 @@ class FuncParser: if not func: if raise_errors: available = ", ".join(f"'{key}'" for key in self.callables) - raise ParsingError(f"Unknown parsed function '{str(parsedfunc)}' " - f"(available: {available})") + raise ParsingError( + f"Unknown parsed function '{str(parsedfunc)}' " f"(available: {available})" + ) return str(parsedfunc) # build kwargs in the proper priority order - kwargs = {**self.default_kwargs, **kwargs, **reserved_kwargs, - **{'funcparser': self, "raise_errors": raise_errors}} + kwargs = { + **self.default_kwargs, + **kwargs, + **reserved_kwargs, + **{"funcparser": self, "raise_errors": raise_errors}, + } try: ret = func(*args, **kwargs) @@ -250,8 +267,15 @@ class FuncParser: raise return str(parsedfunc) - def parse(self, string, raise_errors=False, escape=False, - strip=False, return_str=True, **reserved_kwargs): + def parse( + self, + string, + raise_errors=False, + escape=False, + strip=False, + return_str=True, + **reserved_kwargs, + ): """ Use parser to parse a string that may or may not have `$funcname(*args, **kwargs)` - style tokens in it. Only the callables @@ -298,14 +322,14 @@ class FuncParser: double_quoted = False open_lparens = 0 # open ( open_lsquare = 0 # open [ - open_lcurly = 0 # open { + open_lcurly = 0 # open { escaped = False current_kwarg = "" exec_return = "" curr_func = None - fullstr = '' # final string - infuncstr = '' # string parts inside the current level of $funcdef (including $) + fullstr = "" # final string + infuncstr = "" # string parts inside the current level of $funcdef (including $) for char in string: @@ -332,8 +356,10 @@ class FuncParser: if len(callstack) > _MAX_NESTING: # stack full - ignore this function if raise_errors: - raise ParsingError("Only allows for parsing nesting function defs " - f"to a max depth of {_MAX_NESTING}.") + raise ParsingError( + "Only allows for parsing nesting function defs " + f"to a max depth of {_MAX_NESTING}." + ) infuncstr += char continue else: @@ -368,12 +394,12 @@ class FuncParser: # in a function def (can be nested) - if exec_return != '' and char not in (",=)"): + if exec_return != "" and char not in (",=)"): # if exec_return is followed by any other character # than one demarking an arg,kwarg or function-end # it must immediately merge as a string infuncstr += str(exec_return) - exec_return = '' + exec_return = "" if char == "'": # note that this is the same as "\'" # a single quote - flip status @@ -393,12 +419,12 @@ class FuncParser: continue # special characters detected inside function def - if char == '(': + if char == "(": if not curr_func.funcname: # end of a funcdef name curr_func.funcname = infuncstr curr_func.fullstr += infuncstr + char - infuncstr = '' + infuncstr = "" else: # just a random left-parenthesis infuncstr += char @@ -406,29 +432,29 @@ class FuncParser: open_lparens += 1 continue - if char in '[]': + if char in "[]": # a square bracket - start/end of a list? infuncstr += char - open_lsquare += -1 if char == ']' else 1 + open_lsquare += -1 if char == "]" else 1 continue - if char in '{}': + if char in "{}": # a curly bracket - start/end of dict/set? infuncstr += char - open_lcurly += -1 if char == '}' else 1 + open_lcurly += -1 if char == "}" else 1 continue - if char == '=': + if char == "=": # beginning of a keyword argument - if exec_return != '': + if exec_return != "": infuncstr = exec_return current_kwarg = infuncstr.strip() curr_func.kwargs[current_kwarg] = "" curr_func.fullstr += infuncstr + char - infuncstr = '' + infuncstr = "" continue - if char in (',)'): + if char in (",)"): # commas and right-parens may indicate arguments ending if open_lparens > 1: @@ -436,7 +462,7 @@ class FuncParser: # indicate we are inside an unclosed, nested (, so # we need to not count this as a new arg or end of funcdef. infuncstr += char - open_lparens -= 1 if char == ')' else 0 + open_lparens -= 1 if char == ")" else 0 continue if open_lcurly > 0 or open_lsquare > 0: @@ -444,7 +470,7 @@ class FuncParser: infuncstr += char continue - if exec_return != '': + if exec_return != "": # store the execution return as-received if current_kwarg: curr_func.kwargs[current_kwarg] = exec_return @@ -465,17 +491,17 @@ class FuncParser: curr_func.fullstr += str(exec_return) + infuncstr + char current_kwarg = "" - exec_return = '' - infuncstr = '' + exec_return = "" + infuncstr = "" - if char == ')': + if char == ")": # closing the function list - this means we have a # ready function-def to run. open_lparens = 0 if strip: # remove function as if it returned empty - exec_return = '' + exec_return = "" elif escape: # get function and set it as escaped exec_return = escape_char + curr_func.fullstr @@ -483,7 +509,8 @@ class FuncParser: # execute the function - the result may be a string or # something else exec_return = self.execute( - curr_func, raise_errors=raise_errors, **reserved_kwargs) + curr_func, raise_errors=raise_errors, **reserved_kwargs + ) if callstack: # unnest the higher-level funcdef from stack @@ -494,8 +521,8 @@ class FuncParser: # if we have an ongoing string, we must merge the # exec into this as a part of that string infuncstr = curr_func.infuncstr + str(exec_return) - exec_return = '' - curr_func.infuncstr = '' + exec_return = "" + curr_func.infuncstr = "" single_quoted = curr_func.single_quoted double_quoted = curr_func.double_quoted open_lparens = curr_func.open_lparens @@ -507,8 +534,8 @@ class FuncParser: curr_func = None fullstr += str(exec_return) if return_str: - exec_return = '' - infuncstr = '' + exec_return = "" + infuncstr = "" continue infuncstr += char @@ -521,7 +548,7 @@ class FuncParser: for _ in range(len(callstack)): infuncstr = str(callstack.pop()) + infuncstr - if not return_str and exec_return != '': + if not return_str and exec_return != "": # return explicit return return exec_return @@ -576,8 +603,14 @@ class FuncParser: assert parser.parse_to_any("$ret1() and text" == '1 and text' """ - return self.parse(string, raise_errors=False, escape=False, strip=False, - return_str=False, **reserved_kwargs) + return self.parse( + string, + raise_errors=False, + escape=False, + strip=False, + return_str=False, + **reserved_kwargs, + ) # @@ -585,6 +618,7 @@ class FuncParser: # FUNCPARSER_CALLABLES. # + def funcparser_callable_eval(*args, **kwargs): """ Funcparser callable. This will combine safe evaluations to try to parse the @@ -604,7 +638,7 @@ def funcparser_callable_eval(*args, **kwargs): """ args, kwargs = safe_convert_to_types(("py", {}), *args, **kwargs) - return args[0] if args else '' + return args[0] if args else "" def funcparser_callable_toint(*args, **kwargs): @@ -631,9 +665,9 @@ def _apply_operation_two_elements(*args, operator="+", **kwargs): better for non-list arithmetic. """ - args, kwargs = safe_convert_to_types((('py', 'py'), {}), *args, **kwargs) + args, kwargs = safe_convert_to_types((("py", "py"), {}), *args, **kwargs) if not len(args) > 1: - return '' + return "" val1, val2 = args[0], args[1] try: if operator == "+": @@ -645,29 +679,29 @@ def _apply_operation_two_elements(*args, operator="+", **kwargs): elif operator == "/": return val1 / val2 except Exception: - if kwargs.get('raise_errors'): + if kwargs.get("raise_errors"): raise - return '' + return "" def funcparser_callable_add(*args, **kwargs): """Usage: `$add(val1, val2) -> val1 + val2`""" - return _apply_operation_two_elements(*args, operator='+', **kwargs) + return _apply_operation_two_elements(*args, operator="+", **kwargs) def funcparser_callable_sub(*args, **kwargs): """Usage: ``$sub(val1, val2) -> val1 - val2`""" - return _apply_operation_two_elements(*args, operator='-', **kwargs) + return _apply_operation_two_elements(*args, operator="-", **kwargs) def funcparser_callable_mult(*args, **kwargs): """Usage: `$mult(val1, val2) -> val1 * val2`""" - return _apply_operation_two_elements(*args, operator='*', **kwargs) + return _apply_operation_two_elements(*args, operator="*", **kwargs) def funcparser_callable_div(*args, **kwargs): """Usage: `$mult(val1, val2) -> val1 / val2`""" - return _apply_operation_two_elements(*args, operator='/', **kwargs) + return _apply_operation_two_elements(*args, operator="/", **kwargs) def funcparser_callable_round(*args, **kwargs): @@ -690,7 +724,7 @@ def funcparser_callable_round(*args, **kwargs): """ if not args: - return '' + return "" args, _ = safe_convert_to_types(((float, int), {}), *args, **kwargs) num, *significant = args @@ -698,9 +732,10 @@ def funcparser_callable_round(*args, **kwargs): try: round(num, significant) except Exception: - if kwargs.get('raise_errors'): + if kwargs.get("raise_errors"): raise - return '' + return "" + def funcparser_callable_random(*args, **kwargs): """ @@ -724,7 +759,7 @@ def funcparser_callable_random(*args, **kwargs): - `$random(5, 10.0)` - random value [5..10] (float) """ - args, _ = safe_convert_to_types((('py', 'py'), {}), *args, **kwargs) + args, _ = safe_convert_to_types((("py", "py"), {}), *args, **kwargs) nargs = len(args) if nargs == 1: @@ -741,9 +776,10 @@ def funcparser_callable_random(*args, **kwargs): else: return random.randint(minval, maxval) except Exception: - if kwargs.get('raise_errors'): + if kwargs.get("raise_errors"): raise - return '' + return "" + def funcparser_callable_randint(*args, **kwargs): """ @@ -772,14 +808,14 @@ def funcparser_callable_choice(*args, **kwargs): """ if not args: - return '' - args, _ = safe_convert_to_types(('py', {}), *args, **kwargs) + return "" + args, _ = safe_convert_to_types(("py", {}), *args, **kwargs) try: return random.choice(args[0]) except Exception: - if kwargs.get('raise_errors'): + if kwargs.get("raise_errors"): raise - return '' + return "" def funcparser_callable_pad(*args, **kwargs): @@ -798,9 +834,10 @@ def funcparser_callable_pad(*args, **kwargs): """ if not args: - return '' + return "" args, kwargs = safe_convert_to_types( - ((str, int, str, str), {'width': int, 'align': str, 'fillchar': str}), *args, **kwargs) + ((str, int, str, str), {"width": int, "align": str, "fillchar": str}), *args, **kwargs + ) text, *rest = args nrest = len(rest) @@ -809,10 +846,10 @@ def funcparser_callable_pad(*args, **kwargs): except TypeError: width = _CLIENT_DEFAULT_WIDTH - align = kwargs.get("align", rest[1] if nrest > 1 else 'c') - fillchar = kwargs.get("fillchar", rest[2] if nrest > 2 else ' ') - if align not in ('c', 'l', 'r'): - align = 'c' + align = kwargs.get("align", rest[1] if nrest > 1 else "c") + fillchar = kwargs.get("fillchar", rest[2] if nrest > 2 else " ") + if align not in ("c", "l", "r"): + align = "c" return pad(str(text), width=width, align=align, fillchar=fillchar) @@ -833,14 +870,14 @@ def funcparser_callable_crop(*args, **kwargs): """ if not args: - return '' + return "" text, *rest = args nrest = len(rest) try: width = int(kwargs.get("width", rest[0] if nrest > 0 else _CLIENT_DEFAULT_WIDTH)) except TypeError: width = _CLIENT_DEFAULT_WIDTH - suffix = kwargs.get('suffix', rest[1] if nrest > 1 else "[...]") + suffix = kwargs.get("suffix", rest[1] if nrest > 1 else "[...]") return crop(str(text), width=width, suffix=str(suffix)) @@ -852,7 +889,7 @@ def funcparser_callable_space(*args, **kwarg): """ if not args: - return '' + return "" try: width = int(args[0]) except TypeError: @@ -879,14 +916,14 @@ def funcparser_callable_justify(*args, **kwargs): """ if not args: - return '' + return "" text, *rest = args lrest = len(rest) try: width = int(kwargs.get("width", rest[0] if lrest > 0 else _CLIENT_DEFAULT_WIDTH)) except TypeError: width = _CLIENT_DEFAULT_WIDTH - align = str(kwargs.get("align", rest[1] if lrest > 1 else 'f')) + align = str(kwargs.get("align", rest[1] if lrest > 1 else "f")) try: indent = int(kwargs.get("indent", rest[2] if lrest > 2 else 0)) except TypeError: @@ -897,17 +934,17 @@ def funcparser_callable_justify(*args, **kwargs): # legacy for backwards compatibility def funcparser_callable_left_justify(*args, **kwargs): "Usage: $ljust(text)" - return funcparser_callable_justify(*args, align='l', **kwargs) + return funcparser_callable_justify(*args, align="l", **kwargs) def funcparser_callable_right_justify(*args, **kwargs): "Usage: $rjust(text)" - return funcparser_callable_justify(*args, align='r', **kwargs) + return funcparser_callable_justify(*args, align="r", **kwargs) def funcparser_callable_center_justify(*args, **kwargs): "Usage: $cjust(text)" - return funcparser_callable_justify(*args, align='c', **kwargs) + return funcparser_callable_justify(*args, align="c", **kwargs) def funcparser_callable_clr(*args, **kwargs): @@ -930,9 +967,9 @@ def funcparser_callable_clr(*args, **kwargs): """ if not args: - return '' + return "" - startclr, text, endclr = '', '', '' + startclr, text, endclr = "", "", "" if len(args) > 1: # $clr(pre, text, post)) startclr, *rest = args @@ -943,11 +980,11 @@ def funcparser_callable_clr(*args, **kwargs): else: # $clr(text, start=pre, end=post) text = args[0] - startclr = kwargs.get("start", '') - endclr = kwargs.get("end", '') + startclr = kwargs.get("start", "") + endclr = kwargs.get("end", "") startclr = "|" + startclr if startclr else "" - endclr = "|" + endclr if endclr else ("|n" if startclr else '') + endclr = "|" + endclr if endclr else ("|n" if startclr else "") return f"{startclr}{text}{endclr}" @@ -1007,13 +1044,14 @@ def funcparser_callable_search(*args, caller=None, access="control", **kwargs): raise ParsingError(f"$search: Query '{query}' gave no matches.") if len(targets) > 1 and not return_list: - raise ParsingError("$search: Query '{query}' found {num} matches. " - "Set return_list=True to accept a list".format( - query=query, num=len(targets))) + raise ParsingError( + "$search: Query '{query}' found {num} matches. " + "Set return_list=True to accept a list".format(query=query, num=len(targets)) + ) for target in targets: if not target.access(caller, target, access): - raise ParsingError('$search Cannot add found entity - access failure.') + raise ParsingError("$search Cannot add found entity - access failure.") return list(targets) if return_list else targets[0] @@ -1025,12 +1063,14 @@ def funcparser_callable_search_list(*args, caller=None, access="control", **kwar Legacy alias for search with a return_list=True kwarg preset. """ - return funcparser_callable_search(*args, caller=caller, access=access, - return_list=True, **kwargs) + return funcparser_callable_search( + *args, caller=caller, access=access, return_list=True, **kwargs + ) -def funcparser_callable_you(*args, caller=None, receiver=None, mapping=None, capitalize=False, - **kwargs): +def funcparser_callable_you( + *args, caller=None, receiver=None, mapping=None, capitalize=False, **kwargs +): """ Usage: $you() or $you(key) @@ -1079,18 +1119,23 @@ def funcparser_callable_you(*args, caller=None, receiver=None, mapping=None, cap capitalize = bool(capitalize) if caller == receiver: return "You" if capitalize else "you" - return (caller.get_display_name(looker=receiver) - if hasattr(caller, "get_display_name") else str(caller)) + return ( + caller.get_display_name(looker=receiver) + if hasattr(caller, "get_display_name") + else str(caller) + ) def funcparser_callable_you_capitalize( - *args, you=None, receiver=None, mapping=None, capitalize=True, **kwargs): + *args, you=None, receiver=None, mapping=None, capitalize=True, **kwargs +): """ Usage: $You() - capitalizes the 'you' output. """ return funcparser_callable_you( - *args, you=you, receiver=receiver, mapping=mapping, capitalize=capitalize, **kwargs) + *args, you=you, receiver=receiver, mapping=mapping, capitalize=capitalize, **kwargs + ) def funcparser_callable_conjugate(*args, caller=None, receiver=None, **kwargs): @@ -1125,7 +1170,7 @@ def funcparser_callable_conjugate(*args, caller=None, receiver=None, **kwargs): """ if not args: - return '' + return "" if not (caller and receiver): raise ParsingError("No caller/receiver supplied to $conj callable") @@ -1229,7 +1274,7 @@ def funcparser_callable_pronoun(*args, caller=None, receiver=None, capitalize=Fa """ if not args: - return '' + return "" pronoun, *options = args # options is either multiple args or a space-separated string @@ -1252,8 +1297,12 @@ def funcparser_callable_pronoun(*args, caller=None, receiver=None, capitalize=Fa default_viewpoint = kwargs["viewpoint"] pronoun_1st_or_2nd_person, pronoun_3rd_person = pronoun_to_viewpoints( - pronoun, options, - pronoun_type=default_pronoun_type, gender=default_gender, viewpoint=default_viewpoint) + pronoun, + options, + pronoun_type=default_pronoun_type, + gender=default_gender, + viewpoint=default_viewpoint, + ) if capitalize: pronoun_1st_or_2nd_person = pronoun_1st_or_2nd_person.capitalize() @@ -1263,14 +1312,15 @@ def funcparser_callable_pronoun(*args, caller=None, receiver=None, capitalize=Fa def funcparser_callable_pronoun_capitalize( - *args, caller=None, receiver=None, capitalize=True, **kwargs): + *args, caller=None, receiver=None, capitalize=True, **kwargs +): """ Usage: $Pron(word) - always maps to a capitalized word. """ return funcparser_callable_pronoun( - *args, caller=caller, receiver=receiver, capitalize=capitalize, **kwargs) - + *args, caller=caller, receiver=receiver, capitalize=capitalize, **kwargs + ) # these are made available as callables by adding 'evennia.utils.funcparser' as @@ -1278,7 +1328,6 @@ def funcparser_callable_pronoun_capitalize( FUNCPARSER_CALLABLES = { # 'standard' callables - # eval and arithmetic "eval": funcparser_callable_eval, "add": funcparser_callable_add, @@ -1287,12 +1336,10 @@ FUNCPARSER_CALLABLES = { "div": funcparser_callable_div, "round": funcparser_callable_round, "toint": funcparser_callable_toint, - # randomizers "random": funcparser_callable_random, "randint": funcparser_callable_randint, "choice": funcparser_callable_choice, - # string manip "pad": funcparser_callable_pad, "crop": funcparser_callable_crop, diff --git a/evennia/utils/gametime.py b/evennia/utils/gametime.py index 59dfc7ec8b..0e6358285c 100644 --- a/evennia/utils/gametime.py +++ b/evennia/utils/gametime.py @@ -219,8 +219,16 @@ def real_seconds_until(sec=None, min=None, hour=None, day=None, month=None, year def schedule( - callback, repeat=False, sec=None, min=None, hour=None, day=None, month=None, year=None, - *args, **kwargs + callback, + repeat=False, + sec=None, + min=None, + hour=None, + day=None, + month=None, + year=None, + *args, + **kwargs, ): """ Call a callback at a given in-game time. diff --git a/evennia/utils/logger.py b/evennia/utils/logger.py index b010045dbf..5c40c33d5b 100644 --- a/evennia/utils/logger.py +++ b/evennia/utils/logger.py @@ -372,9 +372,9 @@ class EvenniaLogFile(logfile.LogFile): the previous log to the start of the new one. """ - append_tail = (num_lines_to_append - if num_lines_to_append is not None - else self.num_lines_to_append) + append_tail = ( + num_lines_to_append if num_lines_to_append is not None else self.num_lines_to_append + ) if not append_tail: logfile.LogFile.rotate(self) return @@ -511,6 +511,7 @@ def log_file_exists(filename="game.log"): global _LOGDIR if not _LOGDIR: from django.conf import settings + _LOGDIR = settings.LOG_DIR filename = os.path.join(_LOGDIR, filename) @@ -573,7 +574,7 @@ def tail_log_file(filename, offset, nlines, callback=None): lines_found = filehandle.readlines() block_count -= 1 # return the right number of lines - lines_found = lines_found[-nlines - offset: -offset if offset else None] + lines_found = lines_found[-nlines - offset : -offset if offset else None] if callback: callback(lines_found) return None diff --git a/evennia/utils/optionhandler.py b/evennia/utils/optionhandler.py index fbac1c1282..5b562498de 100644 --- a/evennia/utils/optionhandler.py +++ b/evennia/utils/optionhandler.py @@ -161,9 +161,9 @@ class OptionHandler: if not match: raise ValueError(_("Option not found!")) if len(match) > 1: - raise ValueError(_("Multiple matches:") - + f"{', '.join(match)}. " - + _("Please be more specific.")) + raise ValueError( + _("Multiple matches:") + f"{', '.join(match)}. " + _("Please be more specific.") + ) match = match[0] op = self.get(match, return_obj=True) op.set(value, **kwargs) diff --git a/evennia/utils/test_resources.py b/evennia/utils/test_resources.py index c4907d44a5..58beb138c9 100644 --- a/evennia/utils/test_resources.py +++ b/evennia/utils/test_resources.py @@ -55,8 +55,10 @@ DEFAULT_SETTING_RESETS = dict( MSSP_META_MODULE="evennia.game_template.server.conf.mssp", WEB_PLUGINS_MODULE="server.conf.web_plugins", LOCK_FUNC_MODULES=("evennia.locks.lockfuncs", "evennia.game_template.server.conf.lockfuncs"), - INPUT_FUNC_MODULES=["evennia.server.inputfuncs", - "evennia.game_template.server.conf.inputfuncs"], + INPUT_FUNC_MODULES=[ + "evennia.server.inputfuncs", + "evennia.game_template.server.conf.inputfuncs", + ], PROTOTYPE_MODULES=["evennia.game_template.world.prototypes"], CMDSET_UNLOGGEDIN="evennia.game_template.commands.default_cmdsets.UnloggedinCmdSet", CMDSET_SESSION="evennia.game_template.commands.default_cmdsets.SessionCmdSet", @@ -70,7 +72,8 @@ DEFAULT_SETTING_RESETS = dict( "evennia.contrib.base_systems", "evennia.contrib.full_systems", "evennia.contrib.tutorials", - "evennia.contrib.utils"], + "evennia.contrib.utils", + ], BASE_ACCOUNT_TYPECLASS="evennia.accounts.accounts.DefaultAccount", BASE_OBJECT_TYPECLASS="evennia.objects.objects.DefaultObject", BASE_CHARACTER_TYPECLASS="evennia.objects.objects.DefaultCharacter", @@ -78,22 +81,26 @@ DEFAULT_SETTING_RESETS = dict( BASE_EXIT_TYPECLASS="evennia.objects.objects.DefaultExit", BASE_CHANNEL_TYPECLASS="evennia.comms.comms.DefaultChannel", BASE_SCRIPT_TYPECLASS="evennia.scripts.scripts.DefaultScript", - BASE_BATCHPROCESS_PATHS=["evennia.game_template.world", - "evennia.contrib", "evennia.contrib.tutorials"], + BASE_BATCHPROCESS_PATHS=[ + "evennia.game_template.world", + "evennia.contrib", + "evennia.contrib.tutorials", + ], FILE_HELP_ENTRY_MODULES=["evennia.game_template.world.help_entries"], - FUNCPARSER_OUTGOING_MESSAGES_MODULES=["evennia.utils.funcparser", - "evennia.game_template.server.conf.inlinefuncs"], - FUNCPARSER_PROTOTYPE_PARSING_MODULES=["evennia.prototypes.protfuncs", - "evennia.game_template.server.conf.prototypefuncs"], + FUNCPARSER_OUTGOING_MESSAGES_MODULES=[ + "evennia.utils.funcparser", + "evennia.game_template.server.conf.inlinefuncs", + ], + FUNCPARSER_PROTOTYPE_PARSING_MODULES=[ + "evennia.prototypes.protfuncs", + "evennia.game_template.server.conf.prototypefuncs", + ], BASE_GUEST_TYPECLASS="evennia.accounts.accounts.DefaultGuest", # a special flag; test with settings._TEST_ENVIRONMENT to see if code runs in a test _TEST_ENVIRONMENT=True, ) -DEFAULT_SETTINGS = { - **all_from_module(settings_default), - **DEFAULT_SETTING_RESETS -} +DEFAULT_SETTINGS = {**all_from_module(settings_default), **DEFAULT_SETTING_RESETS} DEFAULT_SETTINGS.pop("DATABASES") # we want different dbs tested in CI @@ -154,6 +161,7 @@ class EvenniaTestMixin: """ Evennia test environment mixin """ + account_typeclass = DefaultAccount object_typeclass = DefaultObject character_typeclass = DefaultCharacter @@ -183,8 +191,9 @@ class EvenniaTestMixin: self.account2.delete() # Set up fake prototype module for allowing tests to use named prototypes. - @override_settings(PROTOTYPE_MODULES=["evennia.utils.tests.data.prototypes_example"], - DEFAULT_HOME="#1") + @override_settings( + PROTOTYPE_MODULES=["evennia.utils.tests.data.prototypes_example"], DEFAULT_HOME="#1" + ) def create_rooms(self): self.room1 = create.create_object(self.room_typeclass, key="Room", nohome=True) self.room1.db.desc = "room_desc" @@ -262,8 +271,10 @@ class EvenniaTestMixin: settings.DEFAULT_HOME = self.backups[2] settings.PROTOTYPE_MODULES = self.backups[3] except AttributeError as err: - raise AttributeError(f"{err}: Teardown error. If you overrode the `setUp()` method " - "in your test, make sure you also added `super().setUp()`!") + raise AttributeError( + f"{err}: Teardown error. If you overrode the `setUp()` method " + "in your test, make sure you also added `super().setUp()`!" + ) del SESSIONS[self.session.sessid] self.teardown_accounts() @@ -415,8 +426,9 @@ class EvenniaCommandTestMixin: receiver_mapping = {} if isinstance(msg, dict): # a mapping {receiver: msg, ...} - receiver_mapping = {recv: str(msg).strip() if msg else None - for recv, msg in msg.items()} + receiver_mapping = { + recv: str(msg).strip() if msg else None for recv, msg in msg.items() + } else: # a single expected string and thus a single receiver (defaults to caller) receiver = receiver if receiver else caller @@ -487,8 +499,9 @@ class EvenniaCommandTestMixin: receiver.msg = unmocked_msg_methods[receiver] # Get the first element of a tuple if msg received a tuple instead of a string - stored_msg = [str(smsg[0]) - if isinstance(smsg, tuple) else str(smsg) for smsg in stored_msg] + stored_msg = [ + str(smsg[0]) if isinstance(smsg, tuple) else str(smsg) for smsg in stored_msg + ] if expected_msg is None: # no expected_msg; just build the returned_msgs dict @@ -504,16 +517,17 @@ class EvenniaCommandTestMixin: # to write the comparison string. We also strip ansi before this # comparison since otherwise it would mess with the regex. returned_msg = msg_sep.join( - _RE_STRIP_EVMENU.sub( - "", ansi.parse_ansi(mess, strip_ansi=noansi)) - for mess in stored_msg).strip() + _RE_STRIP_EVMENU.sub("", ansi.parse_ansi(mess, strip_ansi=noansi)) + for mess in stored_msg + ).strip() # this is the actual test if expected_msg == "" and returned_msg or not returned_msg.startswith(expected_msg): # failed the test raise AssertionError( self._ERROR_FORMAT.format( - expected_msg=expected_msg, returned_msg=returned_msg) + expected_msg=expected_msg, returned_msg=returned_msg + ) ) # passed! returned_msgs[receiver] = returned_msg @@ -525,6 +539,7 @@ class EvenniaCommandTestMixin: # Base testing classes + @override_settings(**DEFAULT_SETTINGS) class BaseEvenniaTestCase(TestCase): """ @@ -532,11 +547,13 @@ class BaseEvenniaTestCase(TestCase): """ + class EvenniaTestCase(TestCase): """ For use with gamedir settings; Just like the normal test case, only for naming consistency. """ + pass @@ -547,6 +564,7 @@ class BaseEvenniaTest(EvenniaTestMixin, TestCase): """ + class EvenniaTest(EvenniaTestMixin, TestCase): """ This test class is intended for inheriting in mygame tests. diff --git a/evennia/utils/tests/test_create_functions.py b/evennia/utils/tests/test_create_functions.py index 5bdc30b57d..1e22913633 100644 --- a/evennia/utils/tests/test_create_functions.py +++ b/evennia/utils/tests/test_create_functions.py @@ -50,7 +50,6 @@ class TestCreateScript(BaseEvenniaTest): assert script assert not script.is_active - def test_create_script_w_repeats_equal_2(self): class TestScriptC(DefaultScript): def at_script_creation(self): diff --git a/evennia/utils/tests/test_dbserialize.py b/evennia/utils/tests/test_dbserialize.py index 896769aa38..31b059d753 100644 --- a/evennia/utils/tests/test_dbserialize.py +++ b/evennia/utils/tests/test_dbserialize.py @@ -13,7 +13,9 @@ class TestDbSerialize(TestCase): """ def setUp(self): - self.obj = DefaultObject(db_key="Tester",) + self.obj = DefaultObject( + db_key="Tester", + ) self.obj.save() def test_constants(self): @@ -59,6 +61,6 @@ class TestDbSerialize(TestCase): self.assertEqual(self.obj.db.test, [{0: 1}, {1: 0}]) def test_dict(self): - self.obj.db.test = {'a': True} - self.obj.db.test.update({'b': False}) - self.assertEqual(self.obj.db.test, {'a': True, 'b': False}) + self.obj.db.test = {"a": True} + self.obj.db.test.update({"b": False}) + self.assertEqual(self.obj.db.test, {"a": True, "b": False}) diff --git a/evennia/utils/tests/test_evform.py b/evennia/utils/tests/test_evform.py index eb63afce30..5a1b0a7a43 100644 --- a/evennia/utils/tests/test_evform.py +++ b/evennia/utils/tests/test_evform.py @@ -30,9 +30,9 @@ class TestEvForm(TestCase): } ) # create the EvTables - tableA = evtable.EvTable("HP", "MV", "MP", - table=[["**"], ["*****"], ["***"]], - border="incols") + tableA = evtable.EvTable( + "HP", "MV", "MP", table=[["**"], ["*****"], ["***"]], border="incols" + ) tableB = evtable.EvTable( "Skill", "Value", @@ -50,7 +50,7 @@ class TestEvForm(TestCase): def _simple_form(self, form): cellsdict = {1: "Apple", 2: "Banana", 3: "Citrus", 4: "Durian"} - formdict = {"FORMCHAR": 'x', "TABLECHAR": 'c', "FORM": form} + formdict = {"FORMCHAR": "x", "TABLECHAR": "c", "FORM": form} form = evform.EvForm(form=formdict) form.map(cellsdict) form = ansi.strip_ansi(str(form)) @@ -226,25 +226,16 @@ class TestEvFormParallelTables(TestCase): def setUp(self): self.text1 = "Test text" - self.table2 = evtable.EvTable( - "Ab", "Sc", - table=[ - ["|ySTR", "|yCON", "|yDEX"], - [10, 10, 10] - ] - ) + self.table2 = evtable.EvTable("Ab", "Sc", table=[["|ySTR", "|yCON", "|yDEX"], [10, 10, 10]]) self.table3 = evtable.EvTable( - "|RSkill", "|RLevel", + "|RSkill", + "|RLevel", table=[ ["|yAcro", "|yAnim", "|yArca", "|yAth", "|yDec", "|yHis"], - [10, 10, 10, 10, 10, 10] - ] + [10, 10, 10, 10, 10, 10], + ], ) - self.formdict = { - "FORM": _SHEET, - "FORMCHAR": 'x', - "TABLECHAR": 'c' - } + self.formdict = {"FORM": _SHEET, "FORMCHAR": "x", "TABLECHAR": "c"} def test_parallel_tables(self): """ @@ -253,14 +244,8 @@ class TestEvFormParallelTables(TestCase): form = evform.EvForm(form=self.formdict) form.map( cells={ - '1': self.text1, + "1": self.text1, }, - tables={ - '2': self.table2, - '3': self.table3 - } - ) - self.assertEqual( - ansi.strip_ansi(str(form).strip()), - _EXPECTED.strip() + tables={"2": self.table2, "3": self.table3}, ) + self.assertEqual(ansi.strip_ansi(str(form).strip()), _EXPECTED.strip()) diff --git a/evennia/utils/tests/test_evmenu.py b/evennia/utils/tests/test_evmenu.py index dcc1a5255c..4c1529f4fb 100644 --- a/evennia/utils/tests/test_evmenu.py +++ b/evennia/utils/tests/test_evmenu.py @@ -321,8 +321,7 @@ class TestMenuTemplateParse(BaseEvenniaTest): def test_parse_menu_template(self): """EvMenu template testing""" - menutree = evmenu.parse_menu_template(self.char1, self.menu_template, - self.goto_callables) + menutree = evmenu.parse_menu_template(self.char1, self.menu_template, self.goto_callables) self.assertEqual(menutree, {"start": Anything, "node1": Anything, "node2": Anything}) def test_template2menu(self): diff --git a/evennia/utils/tests/test_funcparser.py b/evennia/utils/tests/test_funcparser.py index 6447d8a975..c62928ff53 100644 --- a/evennia/utils/tests/test_funcparser.py +++ b/evennia/utils/tests/test_funcparser.py @@ -15,37 +15,43 @@ from evennia.utils import funcparser, test_resources def _test_callable(*args, **kwargs): - kwargs.pop('funcparser', None) - kwargs.pop('raise_errors', None) + kwargs.pop("funcparser", None) + kwargs.pop("raise_errors", None) argstr = ", ".join(args) kwargstr = "" if kwargs: kwargstr = (", " if args else "") + ( - ", ".join(f"{key}={val}" for key, val in kwargs.items())) + ", ".join(f"{key}={val}" for key, val in kwargs.items()) + ) return f"_test({argstr}{kwargstr})" + def _repl_callable(*args, **kwargs): if args: return f"r{args[0]}r" return "rr" + def _double_callable(*args, **kwargs): if args: try: return int(args[0]) * 2 except ValueError: pass - return 'N/A' + return "N/A" + def _eval_callable(*args, **kwargs): if args: return simple_eval(args[0]) - return '' + return "" + def _clr_callable(*args, **kwargs): clr, string, *rest = args return f"|{clr}{string}|n" + def _typ_callable(*args, **kwargs): try: if isinstance(args[0], str): @@ -55,18 +61,22 @@ def _typ_callable(*args, **kwargs): except (SyntaxError, ValueError): return type("") + def _add_callable(*args, **kwargs): if len(args) > 1: return literal_eval(args[0]) + literal_eval(args[1]) - return '' + return "" + def _lit_callable(*args, **kwargs): return literal_eval(args[0]) + def _lsum_callable(*args, **kwargs): if isinstance(args[0], (list, tuple)): return sum(val for val in args[0]) - return '' + return "" + _test_callables = { "foo": _test_callable, @@ -82,93 +92,112 @@ _test_callables = { "sum": _lsum_callable, } + class TestFuncParser(TestCase): """ Test the FuncParser class """ + def setUp(self): - self.parser = funcparser.FuncParser( - _test_callables - ) + self.parser = funcparser.FuncParser(_test_callables) - @parameterized.expand([ - ("Test normal string", "Test normal string"), - ("Test noargs1 $foo()", "Test noargs1 _test()"), - ("Test noargs2 $bar() etc.", "Test noargs2 _test() etc."), - ("Test noargs3 $with spaces() etc.", "Test noargs3 _test() etc."), - ("Test noargs4 $foo(), $bar() and $foo", "Test noargs4 _test(), _test() and $foo"), - ("$foo() Test noargs5", "_test() Test noargs5"), - ("Test args1 $foo(a,b,c)", "Test args1 _test(a, b, c)"), - ("Test args2 $bar(foo, bar, too)", "Test args2 _test(foo, bar, too)"), - ("Test args3 $bar(foo, bar, ' too')", "Test args3 _test(foo, bar, ' too')"), - ("Test args4 $foo('')", "Test args4 _test('')"), - ("Test args4 $foo(\"\")", "Test args4 _test(\"\")"), - ("Test args5 $foo(\(\))", "Test args5 _test(())"), - ("Test args6 $foo(\()", "Test args6 _test(()"), - ("Test args7 $foo(())", "Test args7 _test(())"), - ("Test args8 $foo())", "Test args8 _test())"), - ("Test args9 $foo(=)", "Test args9 _test(=)"), - ("Test args10 $foo(\,)", "Test args10 _test(,)"), - ("Test args10 $foo(',')", "Test args10 _test(',')"), - ("Test args11 $foo(()", "Test args11 $foo(()"), # invalid syntax - ("Test kwarg1 $bar(foo=1, bar='foo', too=ere)", - "Test kwarg1 _test(foo=1, bar='foo', too=ere)"), - ("Test kwarg2 $bar(foo,bar,too=ere)", - "Test kwarg2 _test(foo, bar, too=ere)"), - ("test kwarg3 $foo(foo = bar, bar = ere )", - "test kwarg3 _test(foo=bar, bar=ere)"), - ("test kwarg4 $foo(foo =' bar ',\" bar \"= ere )", - "test kwarg4 _test(foo=' bar ', \" bar \"=ere)"), - ("Test nest1 $foo($bar(foo,bar,too=ere))", - "Test nest1 _test(_test(foo, bar, too=ere))"), - ("Test nest2 $foo(bar,$repl(a),$repl()=$repl(),a=b) etc", - "Test nest2 _test(bar, rar, rr=rr, a=b) etc"), - ("Test nest3 $foo(bar,$repl($repl($repl(c))))", - "Test nest3 _test(bar, rrrcrrr)"), - ("Test nest4 $foo($bar(a,b),$bar(a,$repl()),$bar())", - "Test nest4 _test(_test(a, b), _test(a, rr), _test())"), - ("Test escape1 \\$repl(foo)", "Test escape1 $repl(foo)"), - ("Test escape2 \"This is $foo() and $bar($bar())\", $repl()", - "Test escape2 \"This is _test() and _test(_test())\", rr"), - ("Test escape3 'This is $foo() and $bar($bar())', $repl()", - "Test escape3 'This is _test() and _test(_test())', rr"), - ("Test escape4 $$foo() and $$bar(a,b), $repl()", - "Test escape4 $foo() and $bar(a,b), rr"), - ("Test with color |r$foo(a,b)|n is ok", - "Test with color |r_test(a, b)|n is ok"), - ("Test malformed1 This is $foo( and $bar(", - "Test malformed1 This is $foo( and $bar("), - ("Test malformed2 This is $foo( and $bar()", - "Test malformed2 This is $foo( and _test()"), - ("Test malformed3 $", "Test malformed3 $"), - ("Test malformed4 This is $foo(a=b and $bar(", - "Test malformed4 This is $foo(a=b and $bar("), - ("Test malformed5 This is $foo(a=b, and $repl()", - "Test malformed5 This is $foo(a=b, and rr"), - ("Test nonstr 4x2 = $double(4)", "Test nonstr 4x2 = 8"), - ("Test nonstr 4x2 = $double(foo)", "Test nonstr 4x2 = N/A"), - ("Test clr $clr(r, This is a red string!)", "Test clr |rThis is a red string!|n"), - ("Test eval1 $eval(21 + 21 - 10)", "Test eval1 32"), - ("Test eval2 $eval((21 + 21) / 2)", "Test eval2 21.0"), - ("Test eval3 $eval('21' + 'foo' + 'bar')", "Test eval3 21foobar"), - ("Test eval4 $eval('21' + '$repl()' + '' + str(10 // 2))", "Test eval4 21rr5"), - ("Test eval5 $eval('21' + '\$repl()' + '' + str(10 // 2))", "Test eval5 21$repl()5"), - ("Test eval6 $eval('$repl(a)' + '$repl(b)')", "Test eval6 rarrbr"), - ("Test type1 $typ([1,2,3,4])", "Test type1 "), - ("Test type2 $typ((1,2,3,4))", "Test type2 "), - ("Test type3 $typ({1,2,3,4})", "Test type3 "), - ("Test type4 $typ({1:2,3:4})", "Test type4 "), - ("Test type5 $typ(1), $typ(1.0)", "Test type5 , "), - ("Test type6 $typ('1'), $typ(\"1.0\")", "Test type6 , "), - ("Test add1 $add(1, 2)", "Test add1 3"), - ("Test add2 $add([1,2,3,4], [5,6])", "Test add2 [1, 2, 3, 4, 5, 6]"), - ("Test literal1 $sum($lit([1,2,3,4,5,6]))", "Test literal1 21"), - ("Test literal2 $typ($lit(1))", "Test literal2 "), - ("Test literal3 $typ($lit(1)aaa)", "Test literal3 "), - ("Test literal4 $typ(aaa$lit(1))", "Test literal4 "), - ]) + @parameterized.expand( + [ + ("Test normal string", "Test normal string"), + ("Test noargs1 $foo()", "Test noargs1 _test()"), + ("Test noargs2 $bar() etc.", "Test noargs2 _test() etc."), + ("Test noargs3 $with spaces() etc.", "Test noargs3 _test() etc."), + ("Test noargs4 $foo(), $bar() and $foo", "Test noargs4 _test(), _test() and $foo"), + ("$foo() Test noargs5", "_test() Test noargs5"), + ("Test args1 $foo(a,b,c)", "Test args1 _test(a, b, c)"), + ("Test args2 $bar(foo, bar, too)", "Test args2 _test(foo, bar, too)"), + ("Test args3 $bar(foo, bar, ' too')", "Test args3 _test(foo, bar, ' too')"), + ("Test args4 $foo('')", "Test args4 _test('')"), + ('Test args4 $foo("")', 'Test args4 _test("")'), + ("Test args5 $foo(\(\))", "Test args5 _test(())"), + ("Test args6 $foo(\()", "Test args6 _test(()"), + ("Test args7 $foo(())", "Test args7 _test(())"), + ("Test args8 $foo())", "Test args8 _test())"), + ("Test args9 $foo(=)", "Test args9 _test(=)"), + ("Test args10 $foo(\,)", "Test args10 _test(,)"), + ("Test args10 $foo(',')", "Test args10 _test(',')"), + ("Test args11 $foo(()", "Test args11 $foo(()"), # invalid syntax + ( + "Test kwarg1 $bar(foo=1, bar='foo', too=ere)", + "Test kwarg1 _test(foo=1, bar='foo', too=ere)", + ), + ("Test kwarg2 $bar(foo,bar,too=ere)", "Test kwarg2 _test(foo, bar, too=ere)"), + ("test kwarg3 $foo(foo = bar, bar = ere )", "test kwarg3 _test(foo=bar, bar=ere)"), + ( + "test kwarg4 $foo(foo =' bar ',\" bar \"= ere )", + "test kwarg4 _test(foo=' bar ', \" bar \"=ere)", + ), + ( + "Test nest1 $foo($bar(foo,bar,too=ere))", + "Test nest1 _test(_test(foo, bar, too=ere))", + ), + ( + "Test nest2 $foo(bar,$repl(a),$repl()=$repl(),a=b) etc", + "Test nest2 _test(bar, rar, rr=rr, a=b) etc", + ), + ("Test nest3 $foo(bar,$repl($repl($repl(c))))", "Test nest3 _test(bar, rrrcrrr)"), + ( + "Test nest4 $foo($bar(a,b),$bar(a,$repl()),$bar())", + "Test nest4 _test(_test(a, b), _test(a, rr), _test())", + ), + ("Test escape1 \\$repl(foo)", "Test escape1 $repl(foo)"), + ( + 'Test escape2 "This is $foo() and $bar($bar())", $repl()', + 'Test escape2 "This is _test() and _test(_test())", rr', + ), + ( + "Test escape3 'This is $foo() and $bar($bar())', $repl()", + "Test escape3 'This is _test() and _test(_test())', rr", + ), + ( + "Test escape4 $$foo() and $$bar(a,b), $repl()", + "Test escape4 $foo() and $bar(a,b), rr", + ), + ("Test with color |r$foo(a,b)|n is ok", "Test with color |r_test(a, b)|n is ok"), + ("Test malformed1 This is $foo( and $bar(", "Test malformed1 This is $foo( and $bar("), + ( + "Test malformed2 This is $foo( and $bar()", + "Test malformed2 This is $foo( and _test()", + ), + ("Test malformed3 $", "Test malformed3 $"), + ( + "Test malformed4 This is $foo(a=b and $bar(", + "Test malformed4 This is $foo(a=b and $bar(", + ), + ( + "Test malformed5 This is $foo(a=b, and $repl()", + "Test malformed5 This is $foo(a=b, and rr", + ), + ("Test nonstr 4x2 = $double(4)", "Test nonstr 4x2 = 8"), + ("Test nonstr 4x2 = $double(foo)", "Test nonstr 4x2 = N/A"), + ("Test clr $clr(r, This is a red string!)", "Test clr |rThis is a red string!|n"), + ("Test eval1 $eval(21 + 21 - 10)", "Test eval1 32"), + ("Test eval2 $eval((21 + 21) / 2)", "Test eval2 21.0"), + ("Test eval3 $eval('21' + 'foo' + 'bar')", "Test eval3 21foobar"), + ("Test eval4 $eval('21' + '$repl()' + '' + str(10 // 2))", "Test eval4 21rr5"), + ("Test eval5 $eval('21' + '\$repl()' + '' + str(10 // 2))", "Test eval5 21$repl()5"), + ("Test eval6 $eval('$repl(a)' + '$repl(b)')", "Test eval6 rarrbr"), + ("Test type1 $typ([1,2,3,4])", "Test type1 "), + ("Test type2 $typ((1,2,3,4))", "Test type2 "), + ("Test type3 $typ({1,2,3,4})", "Test type3 "), + ("Test type4 $typ({1:2,3:4})", "Test type4 "), + ("Test type5 $typ(1), $typ(1.0)", "Test type5 , "), + ("Test type6 $typ('1'), $typ(\"1.0\")", "Test type6 , "), + ("Test add1 $add(1, 2)", "Test add1 3"), + ("Test add2 $add([1,2,3,4], [5,6])", "Test add2 [1, 2, 3, 4, 5, 6]"), + ("Test literal1 $sum($lit([1,2,3,4,5,6]))", "Test literal1 21"), + ("Test literal2 $typ($lit(1))", "Test literal2 "), + ("Test literal3 $typ($lit(1)aaa)", "Test literal3 "), + ("Test literal4 $typ(aaa$lit(1))", "Test literal4 "), + ] + ) def test_parse(self, string, expected): """ Test parsing of string. @@ -217,7 +246,7 @@ class TestFuncParser(TestCase): # normal parse ret = self.parser.parse(string) - self.assertEqual('123', ret) + self.assertEqual("123", ret) self.assertTrue(isinstance(ret, str)) # parse lit @@ -235,11 +264,11 @@ class TestFuncParser(TestCase): # mixing a literal with other chars always make a string ret = self.parser.parse_to_any(string + "aa") - self.assertEqual('123aa', ret) + self.assertEqual("123aa", ret) self.assertTrue(isinstance(ret, str)) ret = self.parser.parse_to_any("test") - self.assertEqual('test', ret) + self.assertEqual("test", ret) self.assertTrue(isinstance(ret, str)) def test_kwargs_overrides(self): @@ -248,10 +277,7 @@ class TestFuncParser(TestCase): """ # default kwargs passed on initializations - parser = funcparser.FuncParser( - _test_callables, - test='foo' - ) + parser = funcparser.FuncParser(_test_callables, test="foo") ret = parser.parse("This is a $foo() string") self.assertEqual("This is a _test(test=foo) string", ret) @@ -270,6 +296,7 @@ class TestFuncParser(TestCase): ret = parser.parse("This is a $foo(foo=moo) string", foo="bar") self.assertEqual("This is a _test(test=foo, foo=bar) string", ret) + class _DummyObj: def __init__(self, name): self.name = name @@ -283,17 +310,22 @@ class TestDefaultCallables(TestCase): Test default callables. """ + def setUp(self): from django.conf import settings - self.parser = funcparser.FuncParser({**funcparser.FUNCPARSER_CALLABLES, - **funcparser.ACTOR_STANCE_CALLABLES}) + + self.parser = funcparser.FuncParser( + {**funcparser.FUNCPARSER_CALLABLES, **funcparser.ACTOR_STANCE_CALLABLES} + ) self.obj1 = _DummyObj("Char1") self.obj2 = _DummyObj("Char2") - @parameterized.expand([ - ("Test py1 $eval('')", "Test py1 "), - ]) + @parameterized.expand( + [ + ("Test py1 $eval('')", "Test py1 "), + ] + ) def test_callable(self, string, expected): """ Test callables with various input strings @@ -302,47 +334,65 @@ class TestDefaultCallables(TestCase): ret = self.parser.parse(string, raise_errors=True) self.assertEqual(expected, ret) - @parameterized.expand([ - ("$You() $conj(smile) at him.", "You smile at him.", "Char1 smiles at him."), - ("$You() $conj(smile) at $You(char1).", "You smile at You.", "Char1 smiles at Char1."), - ("$You() $conj(smile) at $You(char2).", "You smile at Char2.", "Char1 smiles at You."), - ("$You(char2) $conj(smile) at $you(char1).", "Char2 smile at you.", "You smiles at Char1."), - ("$You() $conj(smile) to $pron(yourself,m).", "You smile to yourself.", - "Char1 smiles to himself."), - ("$You() $conj(smile) to $pron(herself).", "You smile to yourself.", - "Char1 smiles to herself.") # reverse reference - ]) + @parameterized.expand( + [ + ("$You() $conj(smile) at him.", "You smile at him.", "Char1 smiles at him."), + ("$You() $conj(smile) at $You(char1).", "You smile at You.", "Char1 smiles at Char1."), + ("$You() $conj(smile) at $You(char2).", "You smile at Char2.", "Char1 smiles at You."), + ( + "$You(char2) $conj(smile) at $you(char1).", + "Char2 smile at you.", + "You smiles at Char1.", + ), + ( + "$You() $conj(smile) to $pron(yourself,m).", + "You smile to yourself.", + "Char1 smiles to himself.", + ), + ( + "$You() $conj(smile) to $pron(herself).", + "You smile to yourself.", + "Char1 smiles to herself.", + ), # reverse reference + ] + ) def test_conjugate(self, string, expected_you, expected_them): """ Test callables with various input strings """ mapping = {"char1": self.obj1, "char2": self.obj2} - ret = self.parser.parse(string, caller=self.obj1, receiver=self.obj1, mapping=mapping, - raise_errors=True) + ret = self.parser.parse( + string, caller=self.obj1, receiver=self.obj1, mapping=mapping, raise_errors=True + ) self.assertEqual(expected_you, ret) - ret = self.parser.parse(string, caller=self.obj1, receiver=self.obj2, mapping=mapping, - raise_errors=True) + ret = self.parser.parse( + string, caller=self.obj1, receiver=self.obj2, mapping=mapping, raise_errors=True + ) self.assertEqual(expected_them, ret) - @parameterized.expand([ - ("Test $pad(Hello, 20, c, -) there", "Test -------Hello-------- there"), - ("Test $pad(Hello, width=20, align=c, fillchar=-) there", - "Test -------Hello-------- there"), - ("Test $crop(This is a long test, 12)", "Test This is[...]"), - ("Some $space(10) here", "Some here"), - ("Some $clr(b, blue color) now", "Some |bblue color|n now"), - ("Some $add(1, 2) things", "Some 3 things"), - ("Some $sub(10, 2) things", "Some 8 things"), - ("Some $mult(3, 2) things", "Some 6 things"), - ("Some $div(6, 2) things", "Some 3.0 things"), - ("Some $toint(6) things", "Some 6 things"), - ("Some $ljust(Hello, 30)", "Some Hello "), - ("Some $rjust(Hello, 30)", "Some Hello"), - ("Some $rjust(Hello, width=30)", "Some Hello"), - ("Some $cjust(Hello, 30)", "Some Hello "), - ("Some $eval('-'*20)Hello", "Some --------------------Hello"), - ]) + @parameterized.expand( + [ + ("Test $pad(Hello, 20, c, -) there", "Test -------Hello-------- there"), + ( + "Test $pad(Hello, width=20, align=c, fillchar=-) there", + "Test -------Hello-------- there", + ), + ("Test $crop(This is a long test, 12)", "Test This is[...]"), + ("Some $space(10) here", "Some here"), + ("Some $clr(b, blue color) now", "Some |bblue color|n now"), + ("Some $add(1, 2) things", "Some 3 things"), + ("Some $sub(10, 2) things", "Some 8 things"), + ("Some $mult(3, 2) things", "Some 6 things"), + ("Some $div(6, 2) things", "Some 3.0 things"), + ("Some $toint(6) things", "Some 6 things"), + ("Some $ljust(Hello, 30)", "Some Hello "), + ("Some $rjust(Hello, 30)", "Some Hello"), + ("Some $rjust(Hello, width=30)", "Some Hello"), + ("Some $cjust(Hello, 30)", "Some Hello "), + ("Some $eval('-'*20)Hello", "Some --------------------Hello"), + ] + ) def test_other_callables(self, string, expected): """ Test default callables. @@ -413,7 +463,7 @@ class TestDefaultCallables(TestCase): self.parser.parse( 'this should be $pad("""escaped,""" and """instead,""" cropped $crop(with a long,5) text., 80)' ), - "this should be \"\"\"escaped,\"\"\" and \"\"\"instead,\"\"\" cropped with text. ", + 'this should be """escaped,""" and """instead,""" cropped with text. ', ) @@ -422,6 +472,7 @@ class TestCallableSearch(test_resources.BaseEvenniaTest): Test the $search(query) callable """ + def setUp(self): super().setUp() self.parser = funcparser.FuncParser(funcparser.SEARCHING_CALLABLES) @@ -469,5 +520,3 @@ class TestCallableSearch(test_resources.BaseEvenniaTest): ret = self.parser.parse(string, caller=self.char1, return_str=False, raise_errors=True) self.assertEqual(expected, ret) - - diff --git a/evennia/utils/tests/test_tagparsing.py b/evennia/utils/tests/test_tagparsing.py index ae78c8fd0d..c684ab9e3b 100644 --- a/evennia/utils/tests/test_tagparsing.py +++ b/evennia/utils/tests/test_tagparsing.py @@ -224,10 +224,15 @@ class ANSIStringTestCase(TestCase): # from evennia import set_trace;set_trace() split_string = string[:9] + "Test" + string[13:] self.assertEqual( - repr((ANSIString("A bigger ") - + ANSIString("|rTest") # note that the |r|n is replayed together on next line - + ANSIString("|r|n of things |bwith more color|n")).raw()), - repr(split_string.raw())) + repr( + ( + ANSIString("A bigger ") + + ANSIString("|rTest") # note that the |r|n is replayed together on next line + + ANSIString("|r|n of things |bwith more color|n") + ).raw() + ), + repr(split_string.raw()), + ) def test_slice_full(self): string = ANSIString("A bigger |rTest|n of things |bwith more color|n") diff --git a/evennia/utils/tests/test_text2html.py b/evennia/utils/tests/test_text2html.py index 0bb151f22b..a1c45c06f4 100644 --- a/evennia/utils/tests/test_text2html.py +++ b/evennia/utils/tests/test_text2html.py @@ -165,8 +165,7 @@ class TestText2Html(TestCase): "space": " ", "spacestart": " ", } - self.assertEqual("    ", - parser.sub_text(mocked_match)) + self.assertEqual("    ", parser.sub_text(mocked_match)) mocked_match.groupdict.return_value = { "htmlchars": "", @@ -182,30 +181,24 @@ class TestText2Html(TestCase): parser = text2html.HTML_PARSER parser.tabstop = 4 # single tab - self.assertEqual(parser.parse("foo|>foo"), - "foo    foo") + self.assertEqual(parser.parse("foo|>foo"), "foo    foo") # space and tab - self.assertEqual(parser.parse("foo |>foo"), - "foo     foo") + self.assertEqual(parser.parse("foo |>foo"), "foo     foo") # space, tab, space - self.assertEqual(parser.parse("foo |> foo"), - "foo      foo") + self.assertEqual(parser.parse("foo |> foo"), "foo      foo") def test_parse_space_to_html(self): """test space parsing - a single space should be kept, two or more should get  """ parser = text2html.HTML_PARSER # single space - self.assertEqual(parser.parse("foo foo"), - "foo foo") + self.assertEqual(parser.parse("foo foo"), "foo foo") # double space - self.assertEqual(parser.parse("foo foo"), - "foo  foo") + self.assertEqual(parser.parse("foo foo"), "foo  foo") # triple space - self.assertEqual(parser.parse("foo foo"), - "foo   foo") + self.assertEqual(parser.parse("foo foo"), "foo   foo") def test_parse_html(self): self.assertEqual("foo", text2html.parse_html("foo")) @@ -217,16 +210,16 @@ class TestText2Html(TestCase): # blink back-cyan normal underline red green yellow blue magenta cyan back-green text2html.parse_html("|^|[CHello|n|u|rW|go|yr|bl|md|c!|[G!"), '' - 'Hello' # noqa - '' - 'W' # noqa - 'o' - 'r' - 'l' - 'd' - '!' - '!' # noqa - '' - '' - '' + 'Hello' # noqa + '' + 'W' # noqa + 'o' + 'r' + 'l' + 'd' + '!' + '!' # noqa + "" + "" + "", ) diff --git a/evennia/utils/tests/test_utils.py b/evennia/utils/tests/test_utils.py index b930f2fda2..10d23a9176 100644 --- a/evennia/utils/tests/test_utils.py +++ b/evennia/utils/tests/test_utils.py @@ -397,21 +397,32 @@ class TestSafeConvert(TestCase): """ - @parameterized.expand([ - (('1', '2', 3, 4, '5'), {'a': 1, 'b': '2', 'c': 3}, - ((int, float, str, int), {'a': int, 'b': float}), # " - (1, 2.0, '3', 4, '5'), {'a': 1, 'b': 2.0, 'c': 3}), - (('1 + 2', '[1, 2, 3]', [3, 4, 5]), {'a': '3 + 4', 'b': 5}, - (('py', 'py', 'py'), {'a': 'py', 'b': 'py'}), - (3, [1, 2, 3], [3, 4, 5]), {'a': 7, 'b': 5}), - ]) + @parameterized.expand( + [ + ( + ("1", "2", 3, 4, "5"), + {"a": 1, "b": "2", "c": 3}, + ((int, float, str, int), {"a": int, "b": float}), # " + (1, 2.0, "3", 4, "5"), + {"a": 1, "b": 2.0, "c": 3}, + ), + ( + ("1 + 2", "[1, 2, 3]", [3, 4, 5]), + {"a": "3 + 4", "b": 5}, + (("py", "py", "py"), {"a": "py", "b": "py"}), + (3, [1, 2, 3], [3, 4, 5]), + {"a": 7, "b": 5}, + ), + ] + ) def test_conversion(self, args, kwargs, converters, expected_args, expected_kwargs): """ Test the converter with different inputs """ result_args, result_kwargs = utils.safe_convert_to_types( - converters, *args, raise_errors=True, **kwargs) + converters, *args, raise_errors=True, **kwargs + ) self.assertEqual(expected_args, result_args) self.assertEqual(expected_kwargs, result_kwargs) @@ -423,12 +434,10 @@ class TestSafeConvert(TestCase): from evennia.utils.funcparser import ParsingError with self.assertRaises(ValueError): - utils.safe_convert_to_types( - (int, ), *('foo', ), raise_errors=True) + utils.safe_convert_to_types((int,), *("foo",), raise_errors=True) with self.assertRaises(ParsingError) as err: - utils.safe_convert_to_types( - ('py', {}), *('foo', ), raise_errors=True) + utils.safe_convert_to_types(("py", {}), *("foo",), raise_errors=True) _TASK_HANDLER = None @@ -445,10 +454,11 @@ def dummy_func(obj): """ # get a reference of object from evennia.objects.models import ObjectDB + obj = ObjectDB.objects.object_search(obj) obj = obj[0] # make changes to object - obj.ndb.dummy_var = 'dummy_func ran' + obj.ndb.dummy_var = "dummy_func ran" return True @@ -477,7 +487,7 @@ class TestDelay(BaseEvenniaTest): t = utils.delay(self.timedelay, dummy_func, self.char1.dbref, persistent=pers) result = t.call() self.assertTrue(result) - self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') + self.assertEqual(self.char1.ndb.dummy_var, "dummy_func ran") self.assertTrue(t.exists()) self.assertTrue(t.active()) self.char1.ndb.dummy_var = False @@ -489,7 +499,7 @@ class TestDelay(BaseEvenniaTest): # call the task early to test Task.call and TaskHandler.call_task result = t.do_task() self.assertTrue(result) - self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') + self.assertEqual(self.char1.ndb.dummy_var, "dummy_func ran") self.assertFalse(t.exists()) self.char1.ndb.dummy_var = False @@ -500,18 +510,18 @@ class TestDelay(BaseEvenniaTest): t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=pers) self.assertTrue(t.active()) _TASK_HANDLER.clock.advance(timedelay) # make time pass - self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') + self.assertEqual(self.char1.ndb.dummy_var, "dummy_func ran") self.assertFalse(t.exists()) self.char1.ndb.dummy_var = False def test_short_deferred_call(self): # wait for deferred to call with a very short time - timedelay = .1 + timedelay = 0.1 for pers in (False, True): t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=pers) self.assertTrue(t.active()) _TASK_HANDLER.clock.advance(timedelay) # make time pass - self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') + self.assertEqual(self.char1.ndb.dummy_var, "dummy_func ran") self.assertFalse(t.exists()) self.char1.ndb.dummy_var = False @@ -586,7 +596,7 @@ class TestDelay(BaseEvenniaTest): _TASK_HANDLER.clock.advance(timedelay) # make time pass self.assertEqual(self.char1.ndb.dummy_var, False) t.unpause() - self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') + self.assertEqual(self.char1.ndb.dummy_var, "dummy_func ran") self.char1.ndb.dummy_var = False def test_auto_stale_task_removal(self): @@ -601,7 +611,9 @@ class TestDelay(BaseEvenniaTest): self.assertTrue(t.get_id() in _TASK_HANDLER.to_save) self.assertTrue(t.get_id() in _TASK_HANDLER.tasks) # Make task handler's now time, after the stale timeout - _TASK_HANDLER._now = datetime.now() + timedelta(seconds=_TASK_HANDLER.stale_timeout + timedelay + 1) + _TASK_HANDLER._now = datetime.now() + timedelta( + seconds=_TASK_HANDLER.stale_timeout + timedelay + 1 + ) # add a task to test automatic removal t2 = utils.delay(timedelay, dummy_func, self.char1.dbref) if pers: @@ -622,7 +634,9 @@ class TestDelay(BaseEvenniaTest): self.assertTrue(t.get_id() in _TASK_HANDLER.to_save) self.assertTrue(t.get_id() in _TASK_HANDLER.tasks) # Make task handler's now time, after the stale timeout - _TASK_HANDLER._now = datetime.now() + timedelta(seconds=_TASK_HANDLER.stale_timeout + timedelay + 1) + _TASK_HANDLER._now = datetime.now() + timedelta( + seconds=_TASK_HANDLER.stale_timeout + timedelay + 1 + ) _TASK_HANDLER.clean_stale_tasks() # cleanup of stale tasks in in the save method if pers: self.assertFalse(t.get_id() in _TASK_HANDLER.to_save) @@ -643,7 +657,9 @@ class TestDelay(BaseEvenniaTest): self.assertTrue(t.get_id() in _TASK_HANDLER.to_save) self.assertTrue(t.get_id() in _TASK_HANDLER.tasks) # Make task handler's now time, after the stale timeout - _TASK_HANDLER._now = datetime.now() + timedelta(seconds=_TASK_HANDLER.stale_timeout + timedelay + 1) + _TASK_HANDLER._now = datetime.now() + timedelta( + seconds=_TASK_HANDLER.stale_timeout + timedelay + 1 + ) t2 = utils.delay(timedelay, dummy_func, self.char1.dbref) if pers: self.assertTrue(t.get_id() in _TASK_HANDLER.to_save) @@ -665,5 +681,7 @@ class TestDelay(BaseEvenniaTest): self.assertEqual(self.char1.ndb.dummy_var, False) # task has not run _TASK_HANDLER.load() # load persistent tasks from database. _TASK_HANDLER.create_delays() # create new deffered instances from persistent tasks - _TASK_HANDLER.clock.advance(timedelay) # Clock must advance to trigger, even if past timedelay - self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') + _TASK_HANDLER.clock.advance( + timedelay + ) # Clock must advance to trigger, even if past timedelay + self.assertEqual(self.char1.ndb.dummy_var, "dummy_func ran") diff --git a/evennia/utils/text2html.py b/evennia/utils/text2html.py index bb4251caf7..7adfdfdd8e 100644 --- a/evennia/utils/text2html.py +++ b/evennia/utils/text2html.py @@ -301,8 +301,8 @@ class TextToHTMLparser(object): text (str): Processed text. """ url, text = [grp.replace('"', "\\"") for grp in match.groups()] - val = ( - r"""{text}""".format(url=url, text=text) + val = r"""{text}""".format( + url=url, text=text ) return val diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index 753b617b9d..e88ed3ba69 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -206,7 +206,7 @@ def dedent(text, baseline_index=None, indent=None): baseline = lines[baseline_index] spaceremove = len(baseline) - len(baseline.lstrip(" ")) return "\n".join( - line[min(spaceremove, len(line) - len(line.lstrip(" "))):] for line in lines + line[min(spaceremove, len(line) - len(line.lstrip(" "))) :] for line in lines ) @@ -345,7 +345,7 @@ def columnize(string, columns=2, spacing=4, align="l", width=None): cols = [] istart = 0 for irows in nrows: - cols.append(onecol[istart: istart + irows]) + cols.append(onecol[istart : istart + irows]) istart = istart + irows for col in cols: if len(col) < height: @@ -411,7 +411,7 @@ def iter_to_str(iterable, endsep=", and", addquote=False): endsep = " " + str(endsep).strip() else: # no separator given - use comma - endsep = ',' + endsep = "," if len_iter == 1: return str(iterable[0]) @@ -1087,8 +1087,9 @@ def delay(timedelay, callback, *args, **kwargs): return _TASK_HANDLER.add(timedelay, callback, *args, **kwargs) -def repeat(interval, callback, persistent=True, idstring="", stop=False, - store_key=None, *args, **kwargs): +def repeat( + interval, callback, persistent=True, idstring="", stop=False, store_key=None, *args, **kwargs +): """ Start a repeating task using the TickerHandler. @@ -1124,16 +1125,18 @@ def repeat(interval, callback, persistent=True, idstring="", stop=False, if stop: # we pass all args, but only store_key matters if given - _TICKER_HANDLER.remove(interval=interval, - callback=callback, - idstring=idstring, - persistent=persistent, - store_key=store_key) + _TICKER_HANDLER.remove( + interval=interval, + callback=callback, + idstring=idstring, + persistent=persistent, + store_key=store_key, + ) else: - return _TICKER_HANDLER.add(interval=interval, - callback=callback, - idstring=idstring, - persistent=persistent) + return _TICKER_HANDLER.add( + interval=interval, callback=callback, idstring=idstring, persistent=persistent + ) + def unrepeat(store_key): """ @@ -1626,7 +1629,7 @@ def string_similarity(string1, string2): vec2 = [string2.count(v) for v in vocabulary] try: return float(sum(vec1[i] * vec2[i] for i in range(len(vocabulary)))) / ( - math.sqrt(sum(v1 ** 2 for v1 in vec1)) * math.sqrt(sum(v2 ** 2 for v2 in vec2)) + math.sqrt(sum(v1**2 for v1 in vec1)) * math.sqrt(sum(v2**2 for v2 in vec2)) ) except ZeroDivisionError: # can happen if empty-string cmdnames appear for some reason. @@ -1914,8 +1917,8 @@ def format_grid(elements, width=78, sep=" ", verbatim_elements=None): # one line per row, output directly since this is trivial # we use rstrip here to remove extra spaces added by sep return [ - crop(element.rstrip(), width) + " " \ - * max(0, width - display_len((element.rstrip()))) + crop(element.rstrip(), width) + + " " * max(0, width - display_len((element.rstrip()))) for iel, element in enumerate(elements) ] @@ -2105,18 +2108,20 @@ class lazy_property: """Protect against setting""" handlername = self.__name__ raise AttributeError( - _("{obj}.{handlername} is a handler and can't be set directly. " - "To add values, use `{obj}.{handlername}.add()` instead.").format( - obj=obj, handlername=handlername) + _( + "{obj}.{handlername} is a handler and can't be set directly. " + "To add values, use `{obj}.{handlername}.add()` instead." + ).format(obj=obj, handlername=handlername) ) def __delete__(self, obj): """Protect against deleting""" handlername = self.__name__ raise AttributeError( - _("{obj}.{handlername} is a handler and can't be deleted directly. " - "To remove values, use `{obj}.{handlername}.remove()` instead.").format( - obj=obj, handlername=handlername) + _( + "{obj}.{handlername} is a handler and can't be deleted directly. " + "To remove values, use `{obj}.{handlername}.remove()` instead." + ).format(obj=obj, handlername=handlername) ) @@ -2549,9 +2554,10 @@ def safe_convert_to_types(converters, *args, raise_errors=True, **kwargs): # ... """ + def _safe_eval(inp): if not inp: - return '' + return "" if not isinstance(inp, str): # already converted return inp @@ -2568,9 +2574,12 @@ def safe_convert_to_types(converters, *args, raise_errors=True, **kwargs): if raise_errors: from evennia.utils.funcparser import ParsingError - err = (f"Errors converting '{inp}' to python:\n" - f"literal_eval raised {literal_err}\n" - f"simple_eval raised {simple_err}") + + err = ( + f"Errors converting '{inp}' to python:\n" + f"literal_eval raised {literal_err}\n" + f"simple_eval raised {simple_err}" + ) raise ParsingError(err) # handle an incomplete/mixed set of input converters @@ -2584,9 +2593,9 @@ def safe_convert_to_types(converters, *args, raise_errors=True, **kwargs): if args and arg_converters: args = list(args) arg_converters = make_iter(arg_converters) - for iarg, arg in enumerate(args[:len(arg_converters)]): + for iarg, arg in enumerate(args[: len(arg_converters)]): converter = arg_converters[iarg] - converter = _safe_eval if converter in ('py', 'python') else converter + converter = _safe_eval if converter in ("py", "python") else converter try: args[iarg] = converter(arg) except Exception: @@ -2595,7 +2604,7 @@ def safe_convert_to_types(converters, *args, raise_errors=True, **kwargs): args = tuple(args) if kwarg_converters and isinstance(kwarg_converters, dict): for key, converter in kwarg_converters.items(): - converter = _safe_eval if converter in ('py', 'python') else converter + converter = _safe_eval if converter in ("py", "python") else converter if key in {**kwargs}: try: kwargs[key] = converter(kwargs[key]) @@ -2674,7 +2683,11 @@ def copy_word_case(base_word, new_word): else: # WorD - a mix. Handle each character maxlen = len(base_word) - shared, excess = new_word[:maxlen], new_word[maxlen - 1:] - return "".join(char.upper() if base_word[ic].isupper() else char.lower() - for ic, char in enumerate(new_word)) + excess - + shared, excess = new_word[:maxlen], new_word[maxlen - 1 :] + return ( + "".join( + char.upper() if base_word[ic].isupper() else char.lower() + for ic, char in enumerate(new_word) + ) + + excess + ) diff --git a/evennia/utils/validatorfuncs.py b/evennia/utils/validatorfuncs.py index 3084b91b93..a21144a59b 100644 --- a/evennia/utils/validatorfuncs.py +++ b/evennia/utils/validatorfuncs.py @@ -34,8 +34,9 @@ def color(entry, option_key="Color", **kwargs): raise ValueError(_("Nothing entered for a {option_key}!").format(option_key=option_key)) test_str = strip_ansi(f"|{entry}|n") if test_str: - raise ValueError(_("'{entry}' is not a valid {option_key}.").format( - entry=entry, option_key=option_key)) + raise ValueError( + _("'{entry}' is not a valid {option_key}.").format(entry=entry, option_key=option_key) + ) return entry @@ -86,16 +87,16 @@ def datetime(entry, option_key="Datetime", account=None, from_tz=None, **kwargs) else: raise ValueError( _("{option_key} must be entered in a 24-hour format such as: {timeformat}").format( - option_key=option_key, - timeformat=now.strftime('%b %d %H:%M')) + option_key=option_key, timeformat=now.strftime("%b %d %H:%M") + ) ) try: local = _dt.datetime.strptime(entry, "%b %d %H:%M %Y") except ValueError: raise ValueError( _("{option_key} must be entered in a 24-hour format such as: {timeformat}").format( - option_key=option_key, - timeformat=now.strftime('%b %d %H:%M')) + option_key=option_key, timeformat=now.strftime("%b %d %H:%M") + ) ) local_tz = from_tz.localize(local) return local_tz.astimezone(utc) @@ -138,7 +139,8 @@ def duration(entry, option_key="Duration", **kwargs): else: raise ValueError( _("Could not convert section '{interval}' to a {option_key}.").format( - interval=interval, option_key=option_key) + interval=interval, option_key=option_key + ) ) return _dt.timedelta(days, seconds, 0, 0, minutes, hours, weeks) @@ -147,38 +149,49 @@ def duration(entry, option_key="Duration", **kwargs): def future(entry, option_key="Future Datetime", from_tz=None, **kwargs): time = datetime(entry, option_key, from_tz=from_tz) if time < _dt.datetime.utcnow().replace(tzinfo=_dt.timezone.utc): - raise ValueError(_("That {option_key} is in the past! Must give a Future datetime!").format( - option_key=option_key)) + raise ValueError( + _("That {option_key} is in the past! Must give a Future datetime!").format( + option_key=option_key + ) + ) return time def signed_integer(entry, option_key="Signed Integer", **kwargs): if not entry: - raise ValueError(_("Must enter a whole number for {option_key}!").format( - option_key=option_key)) + raise ValueError( + _("Must enter a whole number for {option_key}!").format(option_key=option_key) + ) try: num = int(entry) except ValueError: - raise ValueError(_("Could not convert '{entry}' to a whole " - "number for {option_key}!").format( - entry=entry, option_key=option_key)) + raise ValueError( + _("Could not convert '{entry}' to a whole " "number for {option_key}!").format( + entry=entry, option_key=option_key + ) + ) return num def positive_integer(entry, option_key="Positive Integer", **kwargs): num = signed_integer(entry, option_key) if not num >= 1: - raise ValueError(_("Must enter a whole number greater than 0 for {option_key}!").format( - option_key=option_key)) + raise ValueError( + _("Must enter a whole number greater than 0 for {option_key}!").format( + option_key=option_key + ) + ) return num def unsigned_integer(entry, option_key="Unsigned Integer", **kwargs): num = signed_integer(entry, option_key) if not num >= 0: - raise ValueError(_("{option_key} must be a whole number greater than " - "or equal to 0!").format( - option_key=option_key)) + raise ValueError( + _("{option_key} must be a whole number greater than " "or equal to 0!").format( + option_key=option_key + ) + ) return num @@ -194,9 +207,9 @@ def boolean(entry, option_key="True/False", **kwargs): Boolean """ - error = (_("Must enter a true/false input for {option_key}. Accepts {alternatives}.").format( - option_key=option_key, - alternatives="0/1, True/False, On/Off, Yes/No, Enabled/Disabled")) + error = _("Must enter a true/false input for {option_key}. Accepts {alternatives}.").format( + option_key=option_key, alternatives="0/1, True/False, On/Off, Yes/No, Enabled/Disabled" + ) if not isinstance(entry, str): raise ValueError(error) entry = entry.upper() @@ -225,11 +238,16 @@ def timezone(entry, option_key="Timezone", **kwargs): if len(found) > 1: raise ValueError( _("That matched: {matches}. Please be more specific!").format( - matches=', '.join(str(t) for t in found))) + matches=", ".join(str(t) for t in found) + ) + ) if found: return _TZ_DICT[found[0]] - raise ValueError(_("Could not find timezone '{entry}' for {option_key}!").format( - entry=entry, option_key=option_key)) + raise ValueError( + _("Could not find timezone '{entry}' for {option_key}!").format( + entry=entry, option_key=option_key + ) + ) def email(entry, option_key="Email Address", **kwargs): @@ -251,8 +269,11 @@ def lock(entry, option_key="locks", access_options=None, **kwargs): raise ValueError(_("Must enter an access type!")) if access_options: if access_type not in access_options: - raise ValueError(_("Access type must be one of: {alternatives}").format( - alternatives=', '.join(access_options))) + raise ValueError( + _("Access type must be one of: {alternatives}").format( + alternatives=", ".join(access_options) + ) + ) if not lockfunc: raise ValueError(_("Lock func not entered.")) return entry diff --git a/evennia/utils/verb_conjugation/conjugate.py b/evennia/utils/verb_conjugation/conjugate.py index 48cba4f184..393347af6c 100644 --- a/evennia/utils/verb_conjugation/conjugate.py +++ b/evennia/utils/verb_conjugation/conjugate.py @@ -89,7 +89,7 @@ def verb_infinitive(verb): """ - return verb_lemmas.get(verb, '') + return verb_lemmas.get(verb, "") def verb_conjugate(verb, tense="infinitive", negate=False): diff --git a/evennia/utils/verb_conjugation/pronouns.py b/evennia/utils/verb_conjugation/pronouns.py index f1fdd2c86f..49e606fa36 100644 --- a/evennia/utils/verb_conjugation/pronouns.py +++ b/evennia/utils/verb_conjugation/pronouns.py @@ -36,32 +36,10 @@ DEFAULT_GENDER = "neutral" PRONOUN_MAPPING = { # 1st/2nd person -> 3rd person mappings - "I": { - "subject pronoun": { - "3rd person": { - "male": "he", - "female": "she", - "neutral": "it" - } - } - }, - "me": { - "object pronoun": { - "3rd person": { - "male": "him", - "female": "her", - "neutral": "it" - } - } - }, + "I": {"subject pronoun": {"3rd person": {"male": "he", "female": "she", "neutral": "it"}}}, + "me": {"object pronoun": {"3rd person": {"male": "him", "female": "her", "neutral": "it"}}}, "my": { - "possessive adjective": { - "3rd person": { - "male": "his", - "female": "her", - "neutral": "its" - } - } + "possessive adjective": {"3rd person": {"male": "his", "female": "her", "neutral": "its"}} }, "mine": { "possessive pronoun": { @@ -98,7 +76,7 @@ PRONOUN_MAPPING = { "neutral": "it", "plural": "them", } - } + }, }, "your": { "possessive adjective": { @@ -116,7 +94,7 @@ PRONOUN_MAPPING = { "male": "his", "female": "hers", "neutral": "theirs", # colloqial - "plural": "theirs" + "plural": "theirs", } } }, @@ -129,235 +107,97 @@ PRONOUN_MAPPING = { } } }, - "we": { - "subject pronoun": { - "3rd person": { - "plural": "they" - } - } - }, - "us": { - "object pronoun": { - "3rd person": { - "plural": "them" - } - } - }, - "our": { - "possessive adjective": { - "3rd person": { - "plural": "their" - } - } - }, - "ours": { - "possessive pronoun": { - "3rd person": { - "plural": "theirs" - } - } - }, - "ourselves": { - "reflexive pronoun": { - "3rd person": { - "plural": "themselves" - } - } - }, - "ours": { - "possessive pronoun": { - "3rd person": { - "plural": "theirs" - } - } - }, - "ourselves": { - "reflexive pronoun": { - "3rd person": { - "plural": "themselves" - } - } - }, - "yourselves": { - "reflexive_pronoun": { - "3rd person": { - "plural": "themselves" - } - } - }, + "we": {"subject pronoun": {"3rd person": {"plural": "they"}}}, + "us": {"object pronoun": {"3rd person": {"plural": "them"}}}, + "our": {"possessive adjective": {"3rd person": {"plural": "their"}}}, + "ours": {"possessive pronoun": {"3rd person": {"plural": "theirs"}}}, + "ourselves": {"reflexive pronoun": {"3rd person": {"plural": "themselves"}}}, + "ours": {"possessive pronoun": {"3rd person": {"plural": "theirs"}}}, + "ourselves": {"reflexive pronoun": {"3rd person": {"plural": "themselves"}}}, + "yourselves": {"reflexive_pronoun": {"3rd person": {"plural": "themselves"}}}, # 3rd person to 1st/second person mappings "he": { "subject pronoun": { - "1st person": { - "neutral": "I", - "plural": "we" # pluralis majestatis - }, - "2nd person": { - "neutral": "you", - "plural": "you" # pluralis majestatis - } + "1st person": {"neutral": "I", "plural": "we"}, # pluralis majestatis + "2nd person": {"neutral": "you", "plural": "you"}, # pluralis majestatis } }, "him": { "object pronoun": { - "1st person": { - "neutral": "me", - "plural": "us" # pluralis majestatis - }, - "2nd person": { - "neutral": "you", - "plural": "you" # pluralis majestatis - }, + "1st person": {"neutral": "me", "plural": "us"}, # pluralis majestatis + "2nd person": {"neutral": "you", "plural": "you"}, # pluralis majestatis } }, "his": { "possessive adjective": { - "1st person": { - "neutral": "my", - "plural": "our" # pluralis majestatis - }, - "2nd person": { - "neutral": "your", - "plural": "your" # pluralis majestatis - } + "1st person": {"neutral": "my", "plural": "our"}, # pluralis majestatis + "2nd person": {"neutral": "your", "plural": "your"}, # pluralis majestatis }, "possessive pronoun": { - "1st person": { - "neutral": "mine", - "plural": "ours" # pluralis majestatis - }, - "2nd person": { - "neutral": "yours", - "plural": "yours" # pluralis majestatis - } - } + "1st person": {"neutral": "mine", "plural": "ours"}, # pluralis majestatis + "2nd person": {"neutral": "yours", "plural": "yours"}, # pluralis majestatis + }, }, "himself": { "reflexive pronoun": { - "1st person": { - "neutral": "myself", - "plural": "ourselves" # pluralis majestatis - }, - "2nd person": { - "neutral": "yours", - "plural": "yours" # pluralis majestatis - } + "1st person": {"neutral": "myself", "plural": "ourselves"}, # pluralis majestatis + "2nd person": {"neutral": "yours", "plural": "yours"}, # pluralis majestatis }, }, "she": { "subject pronoun": { - "1st person": { - "neutral": "I", - "plural": "you" # pluralis majestatis - }, - "2nd person": { - "neutral": "you", - "plural": "we" # pluralis majestatis - } + "1st person": {"neutral": "I", "plural": "you"}, # pluralis majestatis + "2nd person": {"neutral": "you", "plural": "we"}, # pluralis majestatis } }, "her": { "object pronoun": { - "1st person": { - "neutral": "me", - "plural": "us" # pluralis majestatis - }, - "2nd person": { - "neutral": "you", - "plural": "you" # pluralis majestatis - } + "1st person": {"neutral": "me", "plural": "us"}, # pluralis majestatis + "2nd person": {"neutral": "you", "plural": "you"}, # pluralis majestatis }, "possessive adjective": { - "1st person": { - "neutral": "my", - "plural": "our" # pluralis majestatis - }, - "2nd person": { - "neutral": "your", - "plural": "your" # pluralis majestatis - } + "1st person": {"neutral": "my", "plural": "our"}, # pluralis majestatis + "2nd person": {"neutral": "your", "plural": "your"}, # pluralis majestatis }, }, "hers": { "possessive pronoun": { - "1st person": { - "neutral": "mine", - "plural": "ours" # pluralis majestatis - }, - "2nd person": { - "neutral": "yours", - "plural": "yours" # pluralis majestatis - } + "1st person": {"neutral": "mine", "plural": "ours"}, # pluralis majestatis + "2nd person": {"neutral": "yours", "plural": "yours"}, # pluralis majestatis } }, "herself": { "reflexive pronoun": { - "1st person": { - "neutral": "myself", - "plural": "ourselves" # pluralis majestatis - }, - "2nd person": { - "neutral": "yourself", - "plural": "yourselves" # pluralis majestatis - } + "1st person": {"neutral": "myself", "plural": "ourselves"}, # pluralis majestatis + "2nd person": {"neutral": "yourself", "plural": "yourselves"}, # pluralis majestatis }, }, "it": { "subject pronoun": { - "1st person": { - "neutral": "I", - "plural": "we" # pluralis majestatis - }, - "2nd person": { - "neutral": "you", - "plural": "you" # pluralis majestatis - } + "1st person": {"neutral": "I", "plural": "we"}, # pluralis majestatis + "2nd person": {"neutral": "you", "plural": "you"}, # pluralis majestatis }, "object pronoun": { - "1st person": { - "neutral": "me", - "plural": "us" # pluralis majestatis - }, - "2nd person": { - "neutral": "you", - "plural": "you" # pluralis majestatis - } - } + "1st person": {"neutral": "me", "plural": "us"}, # pluralis majestatis + "2nd person": {"neutral": "you", "plural": "you"}, # pluralis majestatis + }, }, "its": { "possessive adjective": { - "1st person": { - "neutral": "my", - "plural": "our" # pluralis majestatis - }, - "2nd person": { - "neutral": "your", - "plural": "your" # pluralis majestatis - } + "1st person": {"neutral": "my", "plural": "our"}, # pluralis majestatis + "2nd person": {"neutral": "your", "plural": "your"}, # pluralis majestatis } }, "theirs": { "possessive pronoun": { - "1st person": { - "neutral": "mine", - "plural": "ours" # pluralis majestatis - }, - "2nd person": { - "neutral": "yours", - "plural": "yours" # pluralis majestatis - } + "1st person": {"neutral": "mine", "plural": "ours"}, # pluralis majestatis + "2nd person": {"neutral": "yours", "plural": "yours"}, # pluralis majestatis } }, "itself": { "reflexive pronoun": { - "1st person": { - "neutral": "myself", - "plural": "ourselves" # pluralis majestatis - }, - "2nd person": { - "neutral": "yourself", - "plural": "yourselves" # pluralis majestatis - } + "1st person": {"neutral": "myself", "plural": "ourselves"}, # pluralis majestatis + "2nd person": {"neutral": "yourself", "plural": "yourselves"}, # pluralis majestatis }, }, "they": { @@ -367,7 +207,7 @@ PRONOUN_MAPPING = { }, "2nd person": { "plural": "you", - } + }, } }, "them": { @@ -377,7 +217,7 @@ PRONOUN_MAPPING = { }, "2nd person": { "plural": "you", - } + }, } }, "their": { @@ -387,7 +227,7 @@ PRONOUN_MAPPING = { }, "2nd person": { "plural": "your", - } + }, } }, "themselves": { @@ -397,9 +237,9 @@ PRONOUN_MAPPING = { }, "2nd person": { "plural": "yourselves", - } + }, } - } + }, } @@ -423,15 +263,20 @@ ALIASES = { "pp": "possessive pronoun", } -PRONOUN_TYPES = ["subject pronoun", "object pronoun", "possessive adjective", - "possessive pronoun", "reflexive pronoun"] +PRONOUN_TYPES = [ + "subject pronoun", + "object pronoun", + "possessive adjective", + "possessive pronoun", + "reflexive pronoun", +] VIEWPOINTS = ["1st person", "2nd person", "3rd person"] GENDERS = ["male", "female", "neutral", "plural"] # including plural as a gender for simplicity -def pronoun_to_viewpoints(pronoun, - options=None, pronoun_type="object_pronoun", - gender="neutral", viewpoint="2nd person"): +def pronoun_to_viewpoints( + pronoun, options=None, pronoun_type="object_pronoun", gender="neutral", viewpoint="2nd person" +): """ Access function for determining the forms of a pronount from different viewpoints. diff --git a/evennia/utils/verb_conjugation/tests.py b/evennia/utils/verb_conjugation/tests.py index 39d10a977c..92e918c3e5 100644 --- a/evennia/utils/verb_conjugation/tests.py +++ b/evennia/utils/verb_conjugation/tests.py @@ -13,32 +13,37 @@ class TestVerbConjugate(TestCase): Test the conjugation. """ - @parameterized.expand([ - ("have", "have"), - ("swim", "swim"), - ("give", "give"), - ("given", "give"), - ("am", "be"), - ("doing", "do"), - ("are", "be"), - ]) + + @parameterized.expand( + [ + ("have", "have"), + ("swim", "swim"), + ("give", "give"), + ("given", "give"), + ("am", "be"), + ("doing", "do"), + ("are", "be"), + ] + ) def test_verb_infinitive(self, verb, expected): """ Test the infinite-getter. """ self.assertEqual(expected, conjugate.verb_infinitive(verb)) - @parameterized.expand([ - ("inf", "have", "have"), - ("inf", "swim", "swim"), - ("inf", "give", "give"), - ("inf", "given", "give"), - ("inf", "am", "be"), - ("inf", "doing", "do"), - ("inf", "are", "be"), - ("2sgpres", "am", "are"), - ("3sgpres", "am", "is"), - ]) + @parameterized.expand( + [ + ("inf", "have", "have"), + ("inf", "swim", "swim"), + ("inf", "give", "give"), + ("inf", "given", "give"), + ("inf", "am", "be"), + ("inf", "doing", "do"), + ("inf", "are", "be"), + ("2sgpres", "am", "are"), + ("3sgpres", "am", "is"), + ] + ) def test_verb_conjugate(self, tense, verb, expected): """ Test conjugation for different tenses. @@ -46,17 +51,19 @@ class TestVerbConjugate(TestCase): """ self.assertEqual(expected, conjugate.verb_conjugate(verb, tense=tense)) - @parameterized.expand([ - ("1st", "have", "have"), - ("1st", "swim", "swim"), - ("1st", "give", "give"), - ("1st", "given", "give"), - ("1st", "am", "am"), - ("1st", "doing", "do"), - ("1st", "are", "am"), - ("2nd", "were", "are"), - ("3rd", "am", "is"), - ]) + @parameterized.expand( + [ + ("1st", "have", "have"), + ("1st", "swim", "swim"), + ("1st", "give", "give"), + ("1st", "given", "give"), + ("1st", "am", "am"), + ("1st", "doing", "do"), + ("1st", "are", "am"), + ("2nd", "were", "are"), + ("3rd", "am", "is"), + ] + ) def test_verb_present(self, person, verb, expected): """ Test the present. @@ -64,15 +71,17 @@ class TestVerbConjugate(TestCase): """ self.assertEqual(expected, conjugate.verb_present(verb, person=person)) - @parameterized.expand([ - ("have", "having"), - ("swim", "swimming"), - ("give", "giving"), - ("given", "giving"), - ("am", "being"), - ("doing", "doing"), - ("are", "being"), - ]) + @parameterized.expand( + [ + ("have", "having"), + ("swim", "swimming"), + ("give", "giving"), + ("given", "giving"), + ("am", "being"), + ("doing", "doing"), + ("are", "being"), + ] + ) def test_verb_present_participle(self, verb, expected): """ Test the present_participle @@ -80,16 +89,18 @@ class TestVerbConjugate(TestCase): """ self.assertEqual(expected, conjugate.verb_present_participle(verb)) - @parameterized.expand([ - ("1st", "have", "had"), - ("1st", "swim", "swam"), - ("1st", "give", "gave"), - ("1st", "given", "gave"), - ("1st", "am", "was"), - ("1st", "doing", "did"), - ("1st", "are", "was"), - ("2nd", "were", "were"), - ]) + @parameterized.expand( + [ + ("1st", "have", "had"), + ("1st", "swim", "swam"), + ("1st", "give", "gave"), + ("1st", "given", "gave"), + ("1st", "am", "was"), + ("1st", "doing", "did"), + ("1st", "are", "was"), + ("2nd", "were", "were"), + ] + ) def test_verb_past(self, person, verb, expected): """ Test the past getter. @@ -97,15 +108,17 @@ class TestVerbConjugate(TestCase): """ self.assertEqual(expected, conjugate.verb_past(verb, person=person)) - @parameterized.expand([ - ("have", "had"), - ("swim", "swum"), - ("give", "given"), - ("given", "given"), - ("am", "been"), - ("doing", "done"), - ("are", "been"), - ]) + @parameterized.expand( + [ + ("have", "had"), + ("swim", "swum"), + ("give", "given"), + ("given", "given"), + ("am", "been"), + ("doing", "done"), + ("are", "been"), + ] + ) def test_verb_past_participle(self, verb, expected): """ Test the past participle. @@ -120,15 +133,17 @@ class TestVerbConjugate(TestCase): """ self.assertEqual(list(conjugate.verb_tenses_keys.keys()), conjugate.verb_all_tenses()) - @parameterized.expand([ - ("have", "infinitive"), - ("swim", "infinitive"), - ("give", "infinitive"), - ("given", "past participle"), - ("am", "1st singular present"), - ("doing", "present participle"), - ("are", "2nd singular present"), - ]) + @parameterized.expand( + [ + ("have", "infinitive"), + ("swim", "infinitive"), + ("give", "infinitive"), + ("given", "past participle"), + ("am", "1st singular present"), + ("doing", "present participle"), + ("are", "2nd singular present"), + ] + ) def test_verb_tense(self, verb, expected): """ Test the tense retriever. @@ -136,15 +151,17 @@ class TestVerbConjugate(TestCase): """ self.assertEqual(expected, conjugate.verb_tense(verb)) - @parameterized.expand([ - ("inf", "have", True), - ("inf", "swim", True), - ("inf", "give", True), - ("inf", "given", False), - ("inf", "am", False), - ("inf", "doing", False), - ("inf", "are", False), - ]) + @parameterized.expand( + [ + ("inf", "have", True), + ("inf", "swim", True), + ("inf", "give", True), + ("inf", "given", False), + ("inf", "am", False), + ("inf", "doing", False), + ("inf", "are", False), + ] + ) def test_verb_is_tense(self, tense, verb, expected): """ Test the tense-checker @@ -152,16 +169,18 @@ class TestVerbConjugate(TestCase): """ self.assertEqual(expected, conjugate.verb_is_tense(verb, tense)) - @parameterized.expand([ - ("1st", "have", False), - ("1st", "swim", False), - ("1st", "give", False), - ("1st", "given", False), - ("1st", "am", True), - ("1st", "doing", False), - ("1st", "are", False), - ("1st", "had", False), - ]) + @parameterized.expand( + [ + ("1st", "have", False), + ("1st", "swim", False), + ("1st", "give", False), + ("1st", "given", False), + ("1st", "am", True), + ("1st", "doing", False), + ("1st", "are", False), + ("1st", "had", False), + ] + ) def test_verb_is_present(self, person, verb, expected): """ Test the tense-checker @@ -169,15 +188,17 @@ class TestVerbConjugate(TestCase): """ self.assertEqual(expected, conjugate.verb_is_present(verb, person=person)) - @parameterized.expand([ - ("have", False), - ("swim", False), - ("give", False), - ("given", False), - ("am", False), - ("doing", True), - ("are", False), - ]) + @parameterized.expand( + [ + ("have", False), + ("swim", False), + ("give", False), + ("given", False), + ("am", False), + ("doing", True), + ("are", False), + ] + ) def test_verb_is_present_participle(self, verb, expected): """ Test the tense-checker @@ -185,16 +206,18 @@ class TestVerbConjugate(TestCase): """ self.assertEqual(expected, conjugate.verb_is_present_participle(verb)) - @parameterized.expand([ - ("1st", "have", False), - ("1st", "swim", False), - ("1st", "give", False), - ("1st", "given", False), - ("1st", "am", False), - ("1st", "doing", False), - ("1st", "are", False), - ("2nd", "were", True), - ]) + @parameterized.expand( + [ + ("1st", "have", False), + ("1st", "swim", False), + ("1st", "give", False), + ("1st", "given", False), + ("1st", "am", False), + ("1st", "doing", False), + ("1st", "are", False), + ("2nd", "were", True), + ] + ) def test_verb_is_past(self, person, verb, expected): """ Test the tense-checker @@ -202,16 +225,18 @@ class TestVerbConjugate(TestCase): """ self.assertEqual(expected, conjugate.verb_is_past(verb, person=person)) - @parameterized.expand([ - ("have", False), - ("swimming", False), - ("give", False), - ("given", True), - ("am", False), - ("doing", False), - ("are", False), - ("had", False), - ]) + @parameterized.expand( + [ + ("have", False), + ("swimming", False), + ("give", False), + ("given", True), + ("am", False), + ("doing", False), + ("are", False), + ("had", False), + ] + ) def test_verb_is_past_participle(self, verb, expected): """ Test the tense-checker @@ -219,20 +244,22 @@ class TestVerbConjugate(TestCase): """ self.assertEqual(expected, conjugate.verb_is_past_participle(verb)) - @parameterized.expand([ - ("have", ("have", "has")), - ("swimming", ("swimming", "swimming")), - ("give", ("give", "gives")), - ("given", ("given", "given")), - ("am", ("are", "is")), - ("doing", ("doing", "doing")), - ("are", ("are", "is")), - ("had", ("had", "had")), - ("grin", ("grin", "grins")), - ("smile", ("smile", "smiles")), - ("vex", ("vex", "vexes")), - ("thrust", ("thrust", "thrusts")), - ]) + @parameterized.expand( + [ + ("have", ("have", "has")), + ("swimming", ("swimming", "swimming")), + ("give", ("give", "gives")), + ("given", ("given", "given")), + ("am", ("are", "is")), + ("doing", ("doing", "doing")), + ("are", ("are", "is")), + ("had", ("had", "had")), + ("grin", ("grin", "grins")), + ("smile", ("smile", "smiles")), + ("vex", ("vex", "vexes")), + ("thrust", ("thrust", "thrusts")), + ] + ) def test_verb_actor_stance_components(self, verb, expected): """ Test the tense-checker @@ -247,40 +274,42 @@ class TestPronounMapping(TestCase): """ - @parameterized.expand([ - ("you", "m", "you", "he"), - ("you", "f op", "you", "her"), - ("I", "", "I", "it"), - ("I", "p", "I", "it"), # plural is invalid - ("I", "m", "I", "he"), - ("Me", "n", "Me", "It"), - ("your", "p", "your", "their"), - ("ours", "", "ours", "theirs"), - ("yourself", "", "yourself", "itself"), - ("yourself", "m", "yourself", "himself"), - ("yourself", "f", "yourself", "herself"), - ("yourself", "p", "yourself", "itself"), # plural is invalid - ("yourselves", "", "yourselves", "themselves"), - ("he", "", "you", "he"), # assume 2nd person - ("he", "1", "I", "he"), - ("he", "1 p", "we", "he"), - ("her", "p", "you", "her"), - ("her", "pa", "your", "her"), - ("their", "pa", "your", "their"), - ("their", "pa", "your", "their"), - ("itself", "", "yourself", "itself"), - ("themselves", "", "yourselves", "themselves"), - ("herself", "", "yourself", "herself"), - ]) - def test_mapping_with_options(self, pronoun, options, - expected_1st_or_2nd_person, - expected_3rd_person): + @parameterized.expand( + [ + ("you", "m", "you", "he"), + ("you", "f op", "you", "her"), + ("I", "", "I", "it"), + ("I", "p", "I", "it"), # plural is invalid + ("I", "m", "I", "he"), + ("Me", "n", "Me", "It"), + ("your", "p", "your", "their"), + ("ours", "", "ours", "theirs"), + ("yourself", "", "yourself", "itself"), + ("yourself", "m", "yourself", "himself"), + ("yourself", "f", "yourself", "herself"), + ("yourself", "p", "yourself", "itself"), # plural is invalid + ("yourselves", "", "yourselves", "themselves"), + ("he", "", "you", "he"), # assume 2nd person + ("he", "1", "I", "he"), + ("he", "1 p", "we", "he"), + ("her", "p", "you", "her"), + ("her", "pa", "your", "her"), + ("their", "pa", "your", "their"), + ("their", "pa", "your", "their"), + ("itself", "", "yourself", "itself"), + ("themselves", "", "yourselves", "themselves"), + ("herself", "", "yourself", "herself"), + ] + ) + def test_mapping_with_options( + self, pronoun, options, expected_1st_or_2nd_person, expected_3rd_person + ): """ Test the pronoun mapper. """ - received_1st_or_2nd_person, received_3rd_person = ( - pronouns.pronoun_to_viewpoints(pronoun, options) + received_1st_or_2nd_person, received_3rd_person = pronouns.pronoun_to_viewpoints( + pronoun, options ) self.assertEqual(expected_1st_or_2nd_person, received_1st_or_2nd_person) diff --git a/evennia/web/admin/accounts.py b/evennia/web/admin/accounts.py index 03c5486ec8..4054997d01 100644 --- a/evennia/web/admin/accounts.py +++ b/evennia/web/admin/accounts.py @@ -78,13 +78,13 @@ class AccountChangeForm(UserChangeForm): ) is_superuser = forms.BooleanField( - label = "Superuser status", + label="Superuser status", required=False, help_text="Superusers bypass all in-game locks and has all " - "permissions without explicitly assigning them. Usually " - "only one superuser (user #1) is needed and only a superuser " - "can create another superuser.
" - "Only Superusers can change the user/group permissions below." + "permissions without explicitly assigning them. Usually " + "only one superuser (user #1) is needed and only a superuser " + "can create another superuser.
" + "Only Superusers can change the user/group permissions below.", ) def clean_username(self): @@ -165,11 +165,13 @@ class AccountAttributeInline(AttributeInline): model = AccountDB.db_attributes.through related_field = "accountdb" + class ObjectPuppetInline(admin.StackedInline): """ Inline creation of puppet-Object in Account. """ + from .objects import ObjectCreateForm verbose_name = "Puppeted Object" @@ -185,19 +187,26 @@ class ObjectPuppetInline(admin.StackedInline): "fields": ( ("db_key", "db_typeclass_path"), ("db_location", "db_home", "db_destination"), - "db_cmdset_storage", - "db_lock_storage", + "db_cmdset_storage", + "db_lock_storage", ), "description": "Object currently puppeted by the account (note that this " - "will go away if account logs out or unpuppets)", + "will go away if account logs out or unpuppets)", }, ), ) extra = 0 - readonly_fields = ("db_key", "db_typeclass_path", "db_destination", - "db_location", "db_home", "db_account", - "db_cmdset_storage", "db_lock_storage") + readonly_fields = ( + "db_key", + "db_typeclass_path", + "db_destination", + "db_location", + "db_home", + "db_account", + "db_cmdset_storage", + "db_lock_storage", + ) # disable adding/deleting this inline - read-only! def has_add_permission(self, request, obj=None): @@ -213,7 +222,15 @@ class AccountAdmin(BaseUserAdmin): This is the main creation screen for Users/accounts """ - list_display = ("id", "username", "is_staff", "is_superuser", "db_typeclass_path", "db_date_created") + + list_display = ( + "id", + "username", + "is_staff", + "is_superuser", + "db_typeclass_path", + "db_date_created", + ) list_display_links = ("id", "username") form = AccountChangeForm add_form = AccountCreationForm @@ -277,6 +294,7 @@ class AccountAdmin(BaseUserAdmin): from evennia.utils import dbserialize return str(dbserialize.pack_dbobj(obj)) + serialized_string.help_text = ( "Copy & paste this string into an Attribute's `value` field to store this account there." ) @@ -289,8 +307,8 @@ class AccountAdmin(BaseUserAdmin): return mark_safe( ", ".join( '{name}'.format( - url=reverse("admin:objects_objectdb_change", args=[obj.id]), - name=obj.db_key) + url=reverse("admin:objects_objectdb_change", args=[obj.id]), name=obj.db_key + ) for obj in ObjectDB.objects.filter(db_account=obj) ) ) @@ -316,9 +334,7 @@ class AccountAdmin(BaseUserAdmin): form = super().get_form(request, obj, **kwargs) disabled_fields = set() if not request.user.is_superuser: - disabled_fields |= { - 'is_superuser', 'user_permissions', 'user_groups' - } + disabled_fields |= {"is_superuser", "user_permissions", "user_groups"} for field_name in disabled_fields: if field_name in form.base_fields: form.base_fields[field_name].disabled = True diff --git a/evennia/web/admin/attributes.py b/evennia/web/admin/attributes.py index e983a2fcc2..7abe8094dc 100644 --- a/evennia/web/admin/attributes.py +++ b/evennia/web/admin/attributes.py @@ -27,28 +27,30 @@ class AttributeForm(forms.ModelForm): """ attr_key = forms.CharField( - label="Attribute Name", required=False, - help_text="The main identifier of the Attribute. For Nicks, this is the pattern-matching string." + label="Attribute Name", + required=False, + help_text="The main identifier of the Attribute. For Nicks, this is the pattern-matching string.", ) attr_category = forms.CharField( label="Category", help_text="Categorization. Unset (default) gives a category of `None`, which is " - "is what is searched with e.g. `obj.db.attrname`. For 'nick'-type attributes, this is usually " - "'inputline' or 'channel'.", - required=False, max_length=128 + "is what is searched with e.g. `obj.db.attrname`. For 'nick'-type attributes, this is usually " + "'inputline' or 'channel'.", + required=False, + max_length=128, ) attr_value = PickledFormField( label="Value", help_text="Value to pickle/save. Db-objects are serialized as a list " "containing `__packed_dbobj__` (they can't easily be added from here). Nicks " "store their pattern-replacement here.", - required=False + required=False, ) attr_type = forms.ChoiceField( label="Type", choices=[(None, "-"), ("nick", "nick")], help_text="Unset for regular Attributes, 'nick' for Nick-replacement usage.", - required=False + required=False, ) attr_lockstring = forms.CharField( label="Locks", @@ -62,10 +64,10 @@ class AttributeForm(forms.ModelForm): def __init__(self, *args, **kwargs): """ - If we have an Attribute, then we'll prepopulate our instance with the fields we'd expect it - to have based on the Attribute. attr_key, attr_category, attr_value, attr_type, - and attr_lockstring all refer to the corresponding Attribute fields. The initial data of the form fields will - similarly be populated. + If we have an Attribute, then we'll prepopulate our instance with the fields we'd expect it + to have based on the Attribute. attr_key, attr_category, attr_value, attr_type, + and attr_lockstring all refer to the corresponding Attribute fields. The initial data of the form fields will + similarly be populated. """ super().__init__(*args, **kwargs) diff --git a/evennia/web/admin/comms.py b/evennia/web/admin/comms.py index 7b95cab7e0..ddb853fad6 100644 --- a/evennia/web/admin/comms.py +++ b/evennia/web/admin/comms.py @@ -17,9 +17,11 @@ class MsgTagInline(TagInline): Inline display for Msg-tags. """ + model = Msg.db_tags.through related_field = "msg" + class MsgForm(forms.ModelForm): """ Custom Msg form. @@ -35,7 +37,7 @@ class MsgForm(forms.ModelForm): required=False, widget=forms.Textarea(attrs={"cols": "100", "rows": "2"}), help_text="Optional header for the message; it could be a title or " - "metadata depending on msg-use." + "metadata depending on msg-use.", ) db_lock_storage = forms.CharField( @@ -48,7 +50,6 @@ class MsgForm(forms.ModelForm): ) - @admin.register(Msg) class MsgAdmin(admin.ModelAdmin): """ @@ -67,11 +68,19 @@ class MsgAdmin(admin.ModelAdmin): ) list_display_links = ("id", "db_date_created", "start_of_message") ordering = ["-db_date_created", "-id"] - search_fields = ["=id", "^db_date_created", "^db_message", - "^db_sender_accounts__db_key", "^db_sender_objects__db_key", - "^db_sender_scripts__db_key", "^db_sender_external", - "^db_receivers_accounts__db_key", "^db_receivers_objects__db_key", - "^db_receivers_scripts__db_key", "^db_receiver_external"] + search_fields = [ + "=id", + "^db_date_created", + "^db_message", + "^db_sender_accounts__db_key", + "^db_sender_objects__db_key", + "^db_sender_scripts__db_key", + "^db_sender_external", + "^db_receivers_accounts__db_key", + "^db_receivers_objects__db_key", + "^db_receivers_scripts__db_key", + "^db_receiver_external", + ] readonly_fields = ["db_date_created", "serialized_string"] save_as = True save_on_top = True @@ -80,21 +89,36 @@ class MsgAdmin(admin.ModelAdmin): raw_id_fields = ( "db_sender_accounts", - "db_sender_objects", "db_sender_scripts", - "db_receivers_accounts", "db_receivers_objects", - "db_receivers_scripts", "db_hide_from_accounts", - "db_hide_from_objects") + "db_sender_objects", + "db_sender_scripts", + "db_receivers_accounts", + "db_receivers_objects", + "db_receivers_scripts", + "db_hide_from_accounts", + "db_hide_from_objects", + ) fieldsets = ( ( None, { "fields": ( - ("db_sender_accounts", "db_sender_objects", "db_sender_scripts", "db_sender_external"), - ("db_receivers_accounts", "db_receivers_objects", "db_receivers_scripts", "db_receiver_external"), + ( + "db_sender_accounts", + "db_sender_objects", + "db_sender_scripts", + "db_sender_external", + ), + ( + "db_receivers_accounts", + "db_receivers_objects", + "db_receivers_scripts", + "db_receiver_external", + ), ("db_hide_from_accounts", "db_hide_from_objects"), "db_header", - "db_message", "serialized_string" + "db_message", + "serialized_string", ) }, ), @@ -104,12 +128,14 @@ class MsgAdmin(admin.ModelAdmin): senders = [o for o in obj.senders if o] if senders: return senders[0] + sender.help_text = "If multiple, only the first is shown." def receiver(self, obj): receivers = [o for o in obj.receivers if o] if receivers: return receivers[0] + receiver.help_text = "If multiple, only the first is shown." def start_of_message(self, obj): @@ -126,6 +152,7 @@ class MsgAdmin(admin.ModelAdmin): """ from evennia.utils import dbserialize + return str(dbserialize.pack_dbobj(obj)) serialized_string.help_text = ( @@ -144,7 +171,6 @@ class MsgAdmin(admin.ModelAdmin): return super().get_form(request, obj, **kwargs) - class ChannelAttributeInline(AttributeInline): """ Inline display of Channel Attribute - experimental @@ -170,6 +196,7 @@ class ChannelForm(forms.ModelForm): Form for accessing channels. """ + class Meta: model = ChannelDB fields = "__all__" @@ -193,8 +220,14 @@ class ChannelAdmin(admin.ModelAdmin): inlines = [ChannelTagInline, ChannelAttributeInline] form = ChannelForm - list_display = ("id", "db_key", "no_of_subscribers", "db_lock_storage", "db_typeclass_path", - "db_date_created") + list_display = ( + "id", + "db_key", + "no_of_subscribers", + "db_lock_storage", + "db_typeclass_path", + "db_date_created", + ) list_display_links = ("id", "db_key") ordering = ["-db_date_created", "-id", "-db_key"] search_fields = ["id", "db_key", "db_tags__db_key"] @@ -212,7 +245,7 @@ class ChannelAdmin(admin.ModelAdmin): "db_lock_storage", "db_account_subscriptions", "db_object_subscriptions", - "serialized_string" + "serialized_string", ) }, ), @@ -244,6 +277,7 @@ class ChannelAdmin(admin.ModelAdmin): """ from evennia.utils import dbserialize + return str(dbserialize.pack_dbobj(obj)) serialized_string.help_text = ( diff --git a/evennia/web/admin/help.py b/evennia/web/admin/help.py index 6010595987..c7f8a87b7e 100644 --- a/evennia/web/admin/help.py +++ b/evennia/web/admin/help.py @@ -30,7 +30,8 @@ class HelpEntryForm(forms.ModelForm): widget=forms.Textarea(attrs={"cols": "100", "rows": "2"}), help_text="Set lock to view:all() unless you want it to only show to certain users." "
Use the `edit:` limit if wanting to limit who can edit from in-game. By default it's " - "only limited to who can use the `sethelp` command (Builders).") + "only limited to who can use the `sethelp` command (Builders).", + ) @admin.register(HelpEntry) diff --git a/evennia/web/admin/objects.py b/evennia/web/admin/objects.py index b1d7cecbe4..356164e19d 100644 --- a/evennia/web/admin/objects.py +++ b/evennia/web/admin/objects.py @@ -63,9 +63,11 @@ class ObjectCreateForm(forms.ModelForm): f"
If you are creating a Character you usually need {settings.BASE_CHARACTER_TYPECLASS} " "or a subclass of that.
If your custom class is not found in the list, it may not be imported " "into Evennia yet.", - choices=lambda: adminutils.get_and_load_typeclasses(parent=ObjectDB)) + choices=lambda: adminutils.get_and_load_typeclasses(parent=ObjectDB), + ) - db_lock_storage = forms.CharField( label="Locks", + db_lock_storage = forms.CharField( + label="Locks", required=False, widget=forms.Textarea(attrs={"cols": "100", "rows": "2"}), help_text="In-game lock definition string. If not given, defaults will be used. " @@ -92,29 +94,32 @@ class ObjectCreateForm(forms.ModelForm): label="Location", required=False, widget=ForeignKeyRawIdWidget( - ObjectDB._meta.get_field('db_location').remote_field, admin.site), + ObjectDB._meta.get_field("db_location").remote_field, admin.site + ), help_text="The (current) in-game location.
" - "Usually a Room but can be
" - "empty for un-puppeted Characters." + "Usually a Room but can be
" + "empty for un-puppeted Characters.", ) db_home = forms.ModelChoiceField( ObjectDB.objects.all(), label="Home", required=False, widget=ForeignKeyRawIdWidget( - ObjectDB._meta.get_field('db_location').remote_field, admin.site), + ObjectDB._meta.get_field("db_location").remote_field, admin.site + ), help_text="Fallback in-game location.
" - "All objects should usually have
" - "a home location." - ) + "All objects should usually have
" + "a home location.", + ) db_destination = forms.ModelChoiceField( ObjectDB.objects.all(), label="Destination", required=False, widget=ForeignKeyRawIdWidget( - ObjectDB._meta.get_field('db_destination').remote_field, admin.site), - help_text="Only used by Exits." - ) + ObjectDB._meta.get_field("db_destination").remote_field, admin.site + ), + help_text="Only used by Exits.", + ) def __init__(self, *args, **kwargs): """ @@ -157,9 +162,10 @@ class ObjectEditForm(ObjectCreateForm): label="Puppeting Account", required=False, widget=ForeignKeyRawIdWidget( - ObjectDB._meta.get_field('db_account').remote_field, admin.site), + ObjectDB._meta.get_field("db_account").remote_field, admin.site + ), help_text="An Account puppeting this Object (if any).
Note that when a user logs " - "off/unpuppets, this
field will be empty again. This is normal." + "off/unpuppets, this
field will be empty again. This is normal.", ) @@ -171,10 +177,24 @@ class ObjectAdmin(admin.ModelAdmin): """ inlines = [ObjectTagInline, ObjectAttributeInline] - list_display = ("id", "db_key", "db_typeclass_path", "db_location", "db_destination", "db_account", "db_date_created") + list_display = ( + "id", + "db_key", + "db_typeclass_path", + "db_location", + "db_destination", + "db_account", + "db_date_created", + ) list_display_links = ("id", "db_key") ordering = ["-db_date_created", "-id"] - search_fields = ["=id", "^db_key", "db_typeclass_path", "^db_account__db_key", "^db_location__db_key"] + search_fields = [ + "=id", + "^db_key", + "db_typeclass_path", + "^db_account__db_key", + "^db_location__db_key", + ] raw_id_fields = ("db_destination", "db_location", "db_home", "db_account") readonly_fields = ("serialized_string", "link_button") @@ -197,7 +217,7 @@ class ObjectAdmin(admin.ModelAdmin): ("db_account", "link_button"), "db_cmdset_storage", "db_lock_storage", - "serialized_string" + "serialized_string", ) }, ), @@ -223,6 +243,7 @@ class ObjectAdmin(admin.ModelAdmin): """ from evennia.utils import dbserialize + return str(dbserialize.pack_dbobj(obj)) serialized_string.help_text = ( @@ -268,7 +289,7 @@ class ObjectAdmin(admin.ModelAdmin): path( "account-object-link/", self.admin_site.admin_view(self.link_object_to_account), - name="object-account-link" + name="object-account-link", ) ] return custom_urls + urls @@ -276,8 +297,9 @@ class ObjectAdmin(admin.ModelAdmin): def link_button(self, obj): return format_html( 'Link to Account ', - reverse("admin:object-account-link", args=[obj.pk]) + reverse("admin:object-account-link", args=[obj.pk]), ) + link_button.short_description = "Create attrs/locks for puppeting" link_button.allow_tags = True @@ -305,21 +327,24 @@ class ObjectAdmin(admin.ModelAdmin): lock = obj.locks.get("puppet") lock += f" or pid({account.id})" obj.locks.add(lock) - self.message_user(request, - "Did the following (where possible): " - f"Set Account.db._last_puppet = {obj}, " - f"Added {obj} to Account.db._playable_characters list, " - f"Added 'puppet:pid({account.id})' lock to {obj}.") + self.message_user( + request, + "Did the following (where possible): " + f"Set Account.db._last_puppet = {obj}, " + f"Added {obj} to Account.db._playable_characters list, " + f"Added 'puppet:pid({account.id})' lock to {obj}.", + ) else: - self.message_user(request, "Account must be connected for this action " - "(set Puppeting Account and save this page first).", - level=messages.ERROR) + self.message_user( + request, + "Account must be connected for this action " + "(set Puppeting Account and save this page first).", + level=messages.ERROR, + ) # stay on the same page return HttpResponseRedirect(reverse("admin:objects_objectdb_change", args=[obj.pk])) - - def save_model(self, request, obj, form, change): """ Model-save hook. diff --git a/evennia/web/admin/scripts.py b/evennia/web/admin/scripts.py index 891eaa12f9..428a0a2adb 100644 --- a/evennia/web/admin/scripts.py +++ b/evennia/web/admin/scripts.py @@ -15,8 +15,7 @@ from . import utils as adminutils class ScriptForm(forms.ModelForm): db_key = forms.CharField( - label = "Name/Key", - help_text="Script identifier, shown in listings etc." + label="Name/Key", help_text="Script identifier, shown in listings etc." ) db_typeclass_path = forms.ChoiceField( @@ -24,10 +23,12 @@ class ScriptForm(forms.ModelForm): help_text="This is the Python-path to the class implementing the actual script functionality. " "
If your custom class is not found here, it may not be imported into Evennia yet.", choices=lambda: adminutils.get_and_load_typeclasses( - parent=ScriptDB, excluded_parents=["evennia.prototypes.prototypes.DbPrototype"]) + parent=ScriptDB, excluded_parents=["evennia.prototypes.prototypes.DbPrototype"] + ), ) - db_lock_storage = forms.CharField( label="Locks", + db_lock_storage = forms.CharField( + label="Locks", required=False, widget=forms.Textarea(attrs={"cols": "100", "rows": "2"}), help_text="In-game lock definition string. If not given, defaults will be used. " @@ -38,18 +39,14 @@ class ScriptForm(forms.ModelForm): db_interval = forms.IntegerField( label="Repeat Interval", help_text="Optional timer component.
How often to call the Script's
`at_repeat` hook, in seconds." - "
Set to 0 to disable." + "
Set to 0 to disable.", ) db_repeats = forms.IntegerField( - help_text="Only repeat this many times." - "
Set to 0 to run indefinitely." - ) - db_start_delay = forms.BooleanField( - help_text="Wait Interval seconds before first call." + help_text="Only repeat this many times." "
Set to 0 to run indefinitely." ) + db_start_delay = forms.BooleanField(help_text="Wait Interval seconds before first call.") db_persistent = forms.BooleanField( - label = "Survives reboot", - help_text="If unset, a server reboot will remove the timer." + label="Survives reboot", help_text="If unset, a server reboot will remove the timer." ) @@ -68,6 +65,7 @@ class ScriptAttributeInline(AttributeInline): Inline attribute tags. """ + model = ScriptDB.db_attributes.through related_field = "scriptdb" @@ -108,8 +106,8 @@ class ScriptAdmin(admin.ModelAdmin): ("db_key", "db_typeclass_path"), ("db_interval", "db_repeats", "db_start_delay", "db_persistent"), "db_obj", - "db_lock_storage", - "serialized_string" + "db_lock_storage", + "serialized_string", ) }, ), @@ -122,13 +120,13 @@ class ScriptAdmin(admin.ModelAdmin): """ from evennia.utils import dbserialize + return str(dbserialize.pack_dbobj(obj)) serialized_string.help_text = ( "Copy & paste this string into an Attribute's `value` field to store this script there." ) - def get_form(self, request, obj=None, **kwargs): """ Overrides help texts. diff --git a/evennia/web/admin/tags.py b/evennia/web/admin/tags.py index 522fcbf498..7f760e6dca 100644 --- a/evennia/web/admin/tags.py +++ b/evennia/web/admin/tags.py @@ -20,9 +20,7 @@ class TagForm(forms.ModelForm): """ - db_key = forms.CharField( - label="Key/Name", required=True, help_text="The main key identifier" - ) + db_key = forms.CharField(label="Key/Name", required=True, help_text="The main key identifier") db_category = forms.CharField( label="Category", help_text="Used for grouping tags. Unset (default) gives a category of None", @@ -32,15 +30,22 @@ class TagForm(forms.ModelForm): label="Type", choices=[(None, "-"), ("alias", "alias"), ("permission", "permission")], help_text="Tags are used for different things. Unset for regular tags.", - required=False + required=False, ) db_model = forms.ChoiceField( - label="Model" , + label="Model", required=False, - help_text = "Each Tag can only 'attach' to one type of entity.", - choices=([("objectdb", "objectdb"), ("accountdb", "accountdb"), - ("scriptdb", "scriptdb"), ("channeldb", "channeldb"), - ("helpentry", "helpentry"), ("msg", "msg")]) + help_text="Each Tag can only 'attach' to one type of entity.", + choices=( + [ + ("objectdb", "objectdb"), + ("accountdb", "accountdb"), + ("scriptdb", "scriptdb"), + ("channeldb", "channeldb"), + ("helpentry", "helpentry"), + ("msg", "msg"), + ] + ), ) db_data = forms.CharField( label="Data", @@ -77,7 +82,7 @@ class InlineTagForm(forms.ModelForm): label="Type", choices=[(None, "-"), ("alias", "alias"), ("permission", "permission")], help_text="Tags are used for different things. Unset for regular tags.", - required=False + required=False, ) tag_data = forms.CharField( label="Data", @@ -91,10 +96,10 @@ class InlineTagForm(forms.ModelForm): def __init__(self, *args, **kwargs): """ - If we have a tag, then we'll prepopulate our instance with the fields we'd expect it - to have based on the tag. tag_key, tag_category, tag_type, and tag_data all refer to - the corresponding tag fields. The initial data of the form fields will similarly be - populated. + If we have a tag, then we'll prepopulate our instance with the fields we'd expect it + to have based on the tag. tag_key, tag_category, tag_type, and tag_data all refer to + the corresponding tag fields. The initial data of the form fields will similarly be + populated. """ super().__init__(*args, **kwargs) tagkey = None @@ -142,6 +147,7 @@ class TagFormSet(forms.BaseInlineFormSet): Object, where the handler is an AliasHandler, PermissionsHandler, or TagHandler, based on the type of tag. """ + verbose_name = "Tag" verbose_name_plural = "Tags" @@ -222,13 +228,6 @@ class TagAdmin(admin.ModelAdmin): fieldsets = ( ( None, - { - "fields": ( - ("db_key", "db_category"), - ("db_tagtype", "db_model"), - "db_data" - ) - }, + {"fields": (("db_key", "db_category"), ("db_tagtype", "db_model"), "db_data")}, ), ) - diff --git a/evennia/web/admin/urls.py b/evennia/web/admin/urls.py index dc5c19bd26..a5723be5c9 100644 --- a/evennia/web/admin/urls.py +++ b/evennia/web/admin/urls.py @@ -28,6 +28,4 @@ if settings.EVENNIA_ADMIN: ] else: # Just include the normal Django admin. - urlpatterns += [ - path("", admin.site.urls) - ] + urlpatterns += [path("", admin.site.urls)] diff --git a/evennia/web/admin/utils.py b/evennia/web/admin/utils.py index 21482bcdc5..c93bc03a77 100644 --- a/evennia/web/admin/utils.py +++ b/evennia/web/admin/utils.py @@ -26,6 +26,7 @@ def get_and_load_typeclasses(parent=None, excluded_parents=None): # this is necessary in order to have typeclasses imported and accessible # in the inheritance tree. import evennia + evennia._init() # this return a dict (path: class} @@ -33,18 +34,27 @@ def get_and_load_typeclasses(parent=None, excluded_parents=None): # filter out any excludes excluded_parents = excluded_parents or [] - tpaths = [path for path, tclass in tmap.items() - if not any(inherits_from(tclass, excl) for excl in excluded_parents)] + tpaths = [ + path + for path, tclass in tmap.items() + if not any(inherits_from(tclass, excl) for excl in excluded_parents) + ] # sort so we get custom paths (not in evennia repo) first tpaths = sorted(tpaths, key=lambda k: (1 if k.startswith("evennia.") else 0, k)) # the base models are not typeclasses so we filter them out - tpaths = [path for path in tpaths if path not in - ("evennia.objects.models.ObjectDB", - "evennia.accounts.models.AccountDB", - "evennia.scripts.models.ScriptDB", - "evennia.comms.models.ChannelDB",)] + tpaths = [ + path + for path in tpaths + if path + not in ( + "evennia.objects.models.ObjectDB", + "evennia.accounts.models.AccountDB", + "evennia.scripts.models.ScriptDB", + "evennia.comms.models.ChannelDB", + ) + ] # return on form excepted by ChoiceField return [(path, path) for path in tpaths if path] @@ -66,6 +76,7 @@ def get_and_load_cmdsets(parent=None, excluded_parents=None): """ # we must do this to have cmdsets imported and accessible in the inheritance tree. import evennia + evennia._init() cmap = get_all_cmdsets(parent) diff --git a/evennia/web/api/filters.py b/evennia/web/api/filters.py index d74932956b..38bd3d1c64 100644 --- a/evennia/web/api/filters.py +++ b/evennia/web/api/filters.py @@ -112,9 +112,11 @@ class AccountDBFilterSet(BaseTypeclassFilterSet): class Meta: model = AccountDB - fields = [fi for fi in (SHARED_FIELDS + ["username", "db_is_connected", "db_is_bot"]) - if fi != 'db_key'] - + fields = [ + fi + for fi in (SHARED_FIELDS + ["username", "db_is_connected", "db_is_bot"]) + if fi != "db_key" + ] class ScriptDBFilterSet(BaseTypeclassFilterSet): @@ -140,8 +142,7 @@ class HelpFilterSet(FilterSet): Filter for help entries """ + name = CharFilter(lookup_expr="iexact", method="filter_name", field_name="db_key") category = CharFilter(lookup_expr="iexact", method="filter_name", field_name="db_category") alias = AliasFilter(lookup_expr="iexact") - - diff --git a/evennia/web/api/root.py b/evennia/web/api/root.py index 4a89639435..fa918aa32a 100644 --- a/evennia/web/api/root.py +++ b/evennia/web/api/root.py @@ -11,7 +11,9 @@ class EvenniaAPIRoot(routers.APIRootView): Root of the Evennia API tree. """ + pass + class APIRootRouter(routers.DefaultRouter): APIRootView = EvenniaAPIRoot diff --git a/evennia/web/api/serializers.py b/evennia/web/api/serializers.py index 6ef9719908..6db2a16d09 100644 --- a/evennia/web/api/serializers.py +++ b/evennia/web/api/serializers.py @@ -25,6 +25,7 @@ class AttributeSerializer(serializers.ModelSerializer): Serialize Attribute views. """ + value_display = serializers.SerializerMethodField(source="value") db_value = serializers.CharField(write_only=True, required=False) @@ -151,6 +152,7 @@ class TypeclassListSerializerMixin: Shortened serializer for list views. """ + shared_fields = [ "id", "db_key", @@ -215,6 +217,7 @@ class ObjectListSerializer(TypeclassListSerializerMixin, serializers.ModelSerial Shortened representation for listings.] """ + class Meta: model = DefaultObject fields = [ @@ -261,10 +264,12 @@ class AccountListSerializer(TypeclassListSerializerMixin, serializers.ModelSeria A shortened form for listing. """ + class Meta: model = DefaultAccount fields = ["username"] + [ - fi for fi in TypeclassListSerializerMixin.shared_fields if fi != "db_key"] + fi for fi in TypeclassListSerializerMixin.shared_fields if fi != "db_key" + ] read_only_fields = ["id"] @@ -273,6 +278,7 @@ class ScriptDBSerializer(TypeclassSerializerMixin, serializers.ModelSerializer): Serializing Account. """ + attributes = serializers.SerializerMethodField() tags = serializers.SerializerMethodField() aliases = serializers.SerializerMethodField() @@ -295,6 +301,7 @@ class ScriptListSerializer(TypeclassListSerializerMixin, serializers.ModelSerial Shortened form for listing. """ + class Meta: model = ScriptDB fields = [ @@ -312,25 +319,36 @@ class HelpSerializer(TypeclassSerializerMixin, serializers.ModelSerializer): Serializers Help entries (not a typeclass). """ + tags = serializers.SerializerMethodField() aliases = serializers.SerializerMethodField() class Meta: model = HelpEntry fields = [ - "id", "db_key", "db_help_category", "db_entrytext", "db_date_created", - "tags", "aliases" + "id", + "db_key", + "db_help_category", + "db_entrytext", + "db_date_created", + "tags", + "aliases", ] read_only_fields = ["id"] + class HelpListSerializer(TypeclassListSerializerMixin, serializers.ModelSerializer): """ Shortened form for listings. """ + class Meta: model = HelpEntry fields = [ - "id", "db_key", "db_help_category", "db_date_created", + "id", + "db_key", + "db_help_category", + "db_date_created", ] read_only_fields = ["id"] diff --git a/evennia/web/api/tests.py b/evennia/web/api/tests.py index 2221ec4667..e82f9e8a6c 100644 --- a/evennia/web/api/tests.py +++ b/evennia/web/api/tests.py @@ -38,7 +38,16 @@ class TestEvenniaRESTApi(BaseEvenniaTest): def get_view_details(self, action): """Helper function for generating list of named tuples""" View = namedtuple( - "View", ["view_name", "obj", "list", "serializer", "list_serializer", "create_data", "retrieve_data"] + "View", + [ + "view_name", + "obj", + "list", + "serializer", + "list_serializer", + "create_data", + "retrieve_data", + ], ) views = [ View( diff --git a/evennia/web/api/urls.py b/evennia/web/api/urls.py index d4c7414762..bbf3d784ee 100644 --- a/evennia/web/api/urls.py +++ b/evennia/web/api/urls.py @@ -40,18 +40,17 @@ urlpatterns = router.urls urlpatterns += [ # openapi schema - path('openapi', - get_schema_view( - title="Evennia API", - description="Evennia OpenAPI Schema", - version="1.0"), - name='openapi', + path( + "openapi", + get_schema_view(title="Evennia API", description="Evennia OpenAPI Schema", version="1.0"), + name="openapi", ), # redoc auto-doc (based on openapi schema) - path('redoc/', - TemplateView.as_view( - template_name="rest_framework/redoc.html" , - extra_context={'schema_url': 'api:openapi'}), - name='redoc' - ) + path( + "redoc/", + TemplateView.as_view( + template_name="rest_framework/redoc.html", extra_context={"schema_url": "api:openapi"} + ), + name="redoc", + ), ] diff --git a/evennia/web/api/views.py b/evennia/web/api/views.py index 698616f2f2..3471b42c8a 100644 --- a/evennia/web/api/views.py +++ b/evennia/web/api/views.py @@ -26,12 +26,13 @@ class GeneralViewSetMixin: Mixin for both typeclass- and non-typeclass entities. """ + def get_serializer_class(self): """ Allow different serializers for certain actions. """ - if self.action == 'list': + if self.action == "list": if hasattr(self, "list_serializer_class"): return self.list_serializer_class return self.serializer_class @@ -93,6 +94,7 @@ class ObjectDBViewSet(TypeclassViewSetMixin, ModelViewSet): (rooms, exits, characters etc). """ + # An example of a basic viewset for all ObjectDB instances. It declares the # serializer to use for both retrieving and changing/creating/deleting # instances. Serializers are similar to django forms, used for the @@ -109,6 +111,7 @@ class CharacterViewSet(ObjectDBViewSet): Characters are a type of Object commonly used as player avatars in-game. """ + queryset = DefaultCharacter.objects.typeclass_search( DefaultCharacter.path, include_children=True ) @@ -167,6 +170,7 @@ class HelpViewSet(GeneralViewSetMixin, ModelViewSet): Note that command auto-help and file-based help entries are not accessible this way. """ + serializer_class = serializers.HelpSerializer queryset = HelpEntry.objects.all() filterset_class = filters.HelpFilterSet diff --git a/evennia/web/utils/adminsite.py b/evennia/web/utils/adminsite.py index 7b313cf2b6..6ef8fbe604 100644 --- a/evennia/web/utils/adminsite.py +++ b/evennia/web/utils/adminsite.py @@ -17,7 +17,8 @@ class EvenniaAdminApp(apps.AdminConfig): This is imported in INSTALLED_APPS instead of django.contrib.admin. """ - default_site = 'evennia.web.utils.adminsite.EvenniaAdminSite' + + default_site = "evennia.web.utils.adminsite.EvenniaAdminSite" class EvenniaAdminSite(admin.AdminSite): @@ -26,10 +27,21 @@ class EvenniaAdminSite(admin.AdminSite): admin.register in the admin/ folder, this is what is being registered to. """ + site_header = "Evennia web admin" - app_order = ["accounts", "objects", "scripts", "comms", "help", - "typeclasses", "server", "sites", "flatpages", "auth"] + app_order = [ + "accounts", + "objects", + "scripts", + "comms", + "help", + "typeclasses", + "server", + "sites", + "flatpages", + "auth", + ] def get_app_list(self, request): app_list = super().get_app_list(request) diff --git a/evennia/web/utils/tests.py b/evennia/web/utils/tests.py index 022cdb0d6e..b01df51d89 100644 --- a/evennia/web/utils/tests.py +++ b/evennia/web/utils/tests.py @@ -9,8 +9,10 @@ class TestGeneralContext(TestCase): @patch("evennia.web.utils.general_context.GAME_NAME", "test_name") @patch("evennia.web.utils.general_context.GAME_SLOGAN", "test_game_slogan") - @patch("evennia.web.utils.general_context.WEBSOCKET_CLIENT_ENABLED", - "websocket_client_enabled_testvalue") + @patch( + "evennia.web.utils.general_context.WEBSOCKET_CLIENT_ENABLED", + "websocket_client_enabled_testvalue", + ) @patch("evennia.web.utils.general_context.WEBCLIENT_ENABLED", "webclient_enabled_testvalue") @patch("evennia.web.utils.general_context.WEBSOCKET_PORT", "websocket_client_port_testvalue") @patch("evennia.web.utils.general_context.WEBSOCKET_URL", "websocket_client_url_testvalue") @@ -39,7 +41,7 @@ class TestGeneralContext(TestCase): "websocket_port": "websocket_client_port_testvalue", "websocket_url": "websocket_client_url_testvalue", "rest_api_enabled": True, - "server_hostname": 'localhost', + "server_hostname": "localhost", "ssh_enabled": False, "ssh_ports": False, "telnet_enabled": True, diff --git a/evennia/web/webclient/urls.py b/evennia/web/webclient/urls.py index 9ac0d6df50..f600edd4e8 100644 --- a/evennia/web/webclient/urls.py +++ b/evennia/web/webclient/urls.py @@ -7,6 +7,4 @@ from . import views app_name = "webclient" -urlpatterns = [ - path("", views.webclient, name="index") -] +urlpatterns = [path("", views.webclient, name="index")] diff --git a/evennia/web/website/forms.py b/evennia/web/website/forms.py index 93af41d0bf..c78d432428 100644 --- a/evennia/web/website/forms.py +++ b/evennia/web/website/forms.py @@ -52,8 +52,9 @@ class AccountForm(UserCreationForm): """ # The model/typeclass this form creates - model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS, - fallback=settings.FALLBACK_ACCOUNT_TYPECLASS) + model = class_from_module( + settings.BASE_ACCOUNT_TYPECLASS, fallback=settings.FALLBACK_ACCOUNT_TYPECLASS + ) # The fields to display on the form, in the given order fields = ("username", "email") @@ -88,8 +89,9 @@ class ObjectForm(EvenniaForm, ModelForm): """ # The model/typeclass this form creates - model = class_from_module(settings.BASE_OBJECT_TYPECLASS, - fallback=settings.FALLBACK_OBJECT_TYPECLASS) + model = class_from_module( + settings.BASE_OBJECT_TYPECLASS, fallback=settings.FALLBACK_OBJECT_TYPECLASS + ) # The fields to display on the form, in the given order fields = ("db_key",) @@ -142,8 +144,9 @@ class CharacterForm(ObjectForm): """ # Get the correct object model - model = class_from_module(settings.BASE_CHARACTER_TYPECLASS, - fallback=settings.FALLBACK_CHARACTER_TYPECLASS) + model = class_from_module( + settings.BASE_CHARACTER_TYPECLASS, fallback=settings.FALLBACK_CHARACTER_TYPECLASS + ) # Allow entry of the 'key' field fields = ("db_key",) diff --git a/evennia/web/website/tests.py b/evennia/web/website/tests.py index e8b9dcf21d..1ac5e1a9c5 100644 --- a/evennia/web/website/tests.py +++ b/evennia/web/website/tests.py @@ -129,8 +129,9 @@ class ChannelDetailTest(EvenniaWebTest): def setUp(self): super().setUp() - klass = class_from_module(self.channel_typeclass, - fallback=settings.FALLBACK_CHANNEL_TYPECLASS) + klass = class_from_module( + self.channel_typeclass, fallback=settings.FALLBACK_CHANNEL_TYPECLASS + ) # Create a channel klass.create("demo") @@ -144,13 +145,10 @@ class HelpListTest(EvenniaWebTest): HELP_ENTRY_DICTS = [ - { - "key": "unit test file entry", - "category": "General", - "text": "cache test file entry text" - } + {"key": "unit test file entry", "category": "General", "text": "cache test file entry text"} ] + class HelpDetailTest(EvenniaWebTest): url_name = "help-entry-detail" @@ -158,31 +156,30 @@ class HelpDetailTest(EvenniaWebTest): super().setUp() # create a db help entry - create_help_entry('unit test db entry', 'unit test db entry text', category="General") + create_help_entry("unit test db entry", "unit test db entry text", category="General") def get_kwargs(self): - return {"category": slugify("general"), - "topic": slugify('unit test db entry')} + return {"category": slugify("general"), "topic": slugify("unit test db entry")} def test_view(self): response = self.client.get(reverse(self.url_name, kwargs=self.get_kwargs()), follow=True) - self.assertEqual(response.context["entry_text"], 'unit test db entry text') + self.assertEqual(response.context["entry_text"], "unit test db entry text") def test_object_cache(self): # clear file help entries, use local HELP_ENTRY_DICTS to recreate new entries global _FILE_HELP_ENTRIES if _FILE_HELP_ENTRIES is None: from evennia.help.filehelp import FILE_HELP_ENTRIES as _FILE_HELP_ENTRIES - help_module = 'evennia.web.website.tests' + help_module = "evennia.web.website.tests" self.file_help_store = _FILE_HELP_ENTRIES.__init__(help_file_modules=[help_module]) # request access to an entry response = self.client.get(reverse(self.url_name, kwargs=self.get_kwargs()), follow=True) - self.assertEqual(response.context["entry_text"], 'unit test db entry text') + self.assertEqual(response.context["entry_text"], "unit test db entry text") # request a second entry, verifing the cached object is not provided on a new topic request - entry_two_args = {"category": slugify("general"), "topic": slugify('unit test file entry')} + entry_two_args = {"category": slugify("general"), "topic": slugify("unit test file entry")} response = self.client.get(reverse(self.url_name, kwargs=entry_two_args), follow=True) - self.assertEqual(response.context["entry_text"], 'cache test file entry text') + self.assertEqual(response.context["entry_text"], "cache test file entry text") class HelpLockedDetailTest(EvenniaWebTest): @@ -192,24 +189,27 @@ class HelpLockedDetailTest(EvenniaWebTest): super().setUp() # create a db entry with a lock - self.db_help_entry = create_help_entry('unit test locked topic', 'unit test locked entrytext', - category="General", locks='read:perm(Developer)') + self.db_help_entry = create_help_entry( + "unit test locked topic", + "unit test locked entrytext", + category="General", + locks="read:perm(Developer)", + ) def get_kwargs(self): - return {"category": slugify("general"), - "topic": slugify('unit test locked topic')} + return {"category": slugify("general"), "topic": slugify("unit test locked topic")} def test_locked_entry(self): # request access to an entry for permission the account does not have response = self.client.get(reverse(self.url_name, kwargs=self.get_kwargs()), follow=True) - self.assertEqual(response.context["entry_text"], 'Failed to find entry.') + self.assertEqual(response.context["entry_text"], "Failed to find entry.") def test_lock_with_perm(self): # log TestAccount in, grant permission required, read the entry self.login() self.account.permissions.add("Developer") response = self.client.get(reverse(self.url_name, kwargs=self.get_kwargs()), follow=True) - self.assertEqual(response.context["entry_text"], 'unit test locked entrytext') + self.assertEqual(response.context["entry_text"], "unit test locked entrytext") class CharacterCreateView(EvenniaWebTest): diff --git a/evennia/web/website/urls.py b/evennia/web/website/urls.py index 7e0f71a9f6..8b39ec4f11 100644 --- a/evennia/web/website/urls.py +++ b/evennia/web/website/urls.py @@ -5,53 +5,51 @@ This redirects to website sub-pages. from django.conf import settings from django.contrib import admin from django.urls import path, include -from .views import ( - index, errors, accounts, help as helpviews, channels, characters) +from .views import index, errors, accounts, help as helpviews, channels, characters urlpatterns = [ # website front page path("", index.EvenniaIndexView.as_view(), name="index"), - # errors path(r"tbi/", errors.to_be_implemented, name="to_be_implemented"), - # User Authentication (makes login/logout url names available) path("auth/register", accounts.AccountCreateView.as_view(), name="register"), path("auth/", include("django.contrib.auth.urls")), - # Help Topics path("help/", helpviews.HelpListView.as_view(), name="help"), - path(r"help///", - helpviews.HelpDetailView.as_view(), - name="help-entry-detail"), - + path( + r"help///", + helpviews.HelpDetailView.as_view(), + name="help-entry-detail", + ), # Channels path("channels/", channels.ChannelListView.as_view(), name="channels"), - path("channels//", - channels.ChannelDetailView.as_view(), - name="channel-detail"), - + path("channels//", channels.ChannelDetailView.as_view(), name="channel-detail"), # Character management path("characters/", characters.CharacterListView.as_view(), name="characters"), - path("characters/create/", - characters.CharacterCreateView.as_view(), - name="character-create"), - path("characters/manage/", - characters.CharacterManageView.as_view(), - name="character-manage"), - path("characters/detail///", - characters.CharacterDetailView.as_view(), - name="character-detail"), - path("characters/puppet///", - characters.CharacterPuppetView.as_view(), - name="character-puppet"), - path("characters/update///", - characters.CharacterUpdateView.as_view(), - name="character-update"), - path("characters/delete///", - characters.CharacterDeleteView.as_view(), - name="character-delete"), + path("characters/create/", characters.CharacterCreateView.as_view(), name="character-create"), + path("characters/manage/", characters.CharacterManageView.as_view(), name="character-manage"), + path( + "characters/detail///", + characters.CharacterDetailView.as_view(), + name="character-detail", + ), + path( + "characters/puppet///", + characters.CharacterPuppetView.as_view(), + name="character-puppet", + ), + path( + "characters/update///", + characters.CharacterUpdateView.as_view(), + name="character-update", + ), + path( + "characters/delete///", + characters.CharacterDeleteView.as_view(), + name="character-delete", + ), ] @@ -59,15 +57,18 @@ urlpatterns = [ # is not recommended and is usually unnecessary). if settings.SERVE_MEDIA: from django import views as django_views + urlpatterns.extend( [ - path("media/", - django_views.static.serve, - {"document_root": settings.MEDIA_ROOT}, + path( + "media/", + django_views.static.serve, + {"document_root": settings.MEDIA_ROOT}, ), - path("static/", - django_views.static.serve, - {"document_root": settings.STATIC_ROOT}, + path( + "static/", + django_views.static.serve, + {"document_root": settings.STATIC_ROOT}, ), ] ) diff --git a/evennia/web/website/views/accounts.py b/evennia/web/website/views/accounts.py index 9a81940677..75fe8f1e08 100644 --- a/evennia/web/website/views/accounts.py +++ b/evennia/web/website/views/accounts.py @@ -24,8 +24,9 @@ class AccountMixin(TypeclassMixin): """ # -- Django constructs -- - model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS, - fallback=settings.FALLBACK_ACCOUNT_TYPECLASS) + model = class_from_module( + settings.BASE_ACCOUNT_TYPECLASS, fallback=settings.FALLBACK_ACCOUNT_TYPECLASS + ) form_class = forms.AccountForm @@ -73,4 +74,3 @@ class AccountCreateView(AccountMixin, EvenniaCreateView): # Redirect the user to the login page return HttpResponseRedirect(self.success_url) - diff --git a/evennia/web/website/views/channels.py b/evennia/web/website/views/channels.py index a6f7dfeba8..cf5cf9278c 100644 --- a/evennia/web/website/views/channels.py +++ b/evennia/web/website/views/channels.py @@ -25,8 +25,9 @@ class ChannelMixin(TypeclassMixin): """ # -- Django constructs -- - model = class_from_module(settings.BASE_CHANNEL_TYPECLASS, - fallback=settings.FALLBACK_CHANNEL_TYPECLASS) + model = class_from_module( + settings.BASE_CHANNEL_TYPECLASS, fallback=settings.FALLBACK_CHANNEL_TYPECLASS + ) # -- Evennia constructs -- page_title = "Channels" @@ -175,5 +176,3 @@ class ChannelDetailView(ChannelMixin, ObjectDetailView): ) return obj - - diff --git a/evennia/web/website/views/characters.py b/evennia/web/website/views/characters.py index ebd2219634..34b335eda4 100644 --- a/evennia/web/website/views/characters.py +++ b/evennia/web/website/views/characters.py @@ -28,8 +28,9 @@ class CharacterMixin(TypeclassMixin): """ # -- Django constructs -- - model = class_from_module(settings.BASE_CHARACTER_TYPECLASS, - fallback=settings.FALLBACK_CHARACTER_TYPECLASS) + model = class_from_module( + settings.BASE_CHARACTER_TYPECLASS, fallback=settings.FALLBACK_CHARACTER_TYPECLASS + ) form_class = forms.CharacterForm success_url = reverse_lazy("character-manage") @@ -199,6 +200,7 @@ class CharacterDeleteView(CharacterMixin, ObjectDeleteView): ObjectDeleteView) can delete a character they own. """ + # using the character form fails there form_class = forms.EvenniaForm @@ -251,4 +253,3 @@ class CharacterCreateView(CharacterMixin, ObjectCreateView): # Call the Django "form failed" hook messages.error(self.request, "Your character could not be created.") return self.form_invalid(form) - diff --git a/evennia/web/website/views/errors.py b/evennia/web/website/views/errors.py index 0795308a98..75f08c5017 100644 --- a/evennia/web/website/views/errors.py +++ b/evennia/web/website/views/errors.py @@ -5,6 +5,7 @@ Error views. from django.shortcuts import render + def to_be_implemented(request): """ A notice letting the user know that this particular feature hasn't been diff --git a/evennia/web/website/views/help.py b/evennia/web/website/views/help.py index e92d6d2e1f..0ff1410e4f 100644 --- a/evennia/web/website/views/help.py +++ b/evennia/web/website/views/help.py @@ -33,12 +33,12 @@ def get_help_category(help_entry, slugify_cat=True): Returns: help_category (str): The category for the help entry. """ - help_category = getattr(help_entry, 'help_category', None) + help_category = getattr(help_entry, "help_category", None) if not help_category: - help_category = getattr(help_entry, 'db_help_category', DEFAULT_HELP_CATEGORY) + help_category = getattr(help_entry, "db_help_category", DEFAULT_HELP_CATEGORY) # if one does not exist, create a category for ease of use with web views html templates - if not hasattr(help_entry, 'web_help_category'): - setattr(help_entry, 'web_help_category', slugify(help_category)) + if not hasattr(help_entry, "web_help_category"): + setattr(help_entry, "web_help_category", slugify(help_category)) help_category = help_category.lower() return slugify(help_category) if slugify_cat else help_category @@ -52,13 +52,13 @@ def get_help_topic(help_entry): Returns: help_topic (str): The topic of the help entry. Default is 'unknown_topic'. """ - help_topic = getattr(help_entry, 'key', None) + help_topic = getattr(help_entry, "key", None) # if object has no key, assume it is a db help entry. if not help_topic: - help_topic = getattr(help_entry, 'db_key', 'unknown_topic') + help_topic = getattr(help_entry, "db_key", "unknown_topic") # if one does not exist, create a key for ease of use with web views html templates - if not hasattr(help_entry, 'web_help_key'): - setattr(help_entry, 'web_help_key', slugify(help_topic)) + if not hasattr(help_entry, "web_help_key"): + setattr(help_entry, "web_help_key", slugify(help_topic)) return help_topic.lower() @@ -79,9 +79,9 @@ def can_read_topic(cmd_or_topic, account): `can_list_topic` is also returning False. """ if inherits_from(cmd_or_topic, "evennia.commands.command.Command"): - return cmd_or_topic.auto_help and cmd_or_topic.access(account, 'read', default=True) + return cmd_or_topic.auto_help and cmd_or_topic.access(account, "read", default=True) else: - return cmd_or_topic.access(account, 'read', default=True) + return cmd_or_topic.access(account, "read", default=True) def collect_topics(account): @@ -99,7 +99,7 @@ def collect_topics(account): # collect commands of account and all puppets # skip a command if an entry is recorded with the same topics, category and help entry cmd_help_topics = [] - if not str(account) == 'AnonymousUser': + if not str(account) == "AnonymousUser": # create list of account and account's puppets puppets = account.db._playable_characters + [account] # add the account's and puppets' commands to cmd_help_topics list @@ -112,7 +112,7 @@ def collect_topics(account): # also check the 'cmd:' lock here for cmd in cmdset: # skip the command if the puppet does not have access - if not cmd.access(puppet, 'cmd'): + if not cmd.access(puppet, "cmd"): continue # skip the command if the puppet does not have read access if not can_read_topic(cmd, puppet): @@ -121,10 +121,11 @@ def collect_topics(account): entry_exists = False for verify_cmd in cmd_help_topics: if ( - verify_cmd.key and cmd.key and - verify_cmd.help_category == cmd.help_category and - verify_cmd.__doc__ == cmd.__doc__ - ): + verify_cmd.key + and cmd.key + and verify_cmd.help_category == cmd.help_category + and verify_cmd.__doc__ == cmd.__doc__ + ): entry_exists = True break if entry_exists: @@ -148,15 +149,14 @@ def collect_topics(account): # Collect commands into a dictionary, read access verified at puppet level cmd_help_topics = { - cmd.auto_help_display_key - if hasattr(cmd, "auto_help_display_key") else cmd.key: cmd + cmd.auto_help_display_key if hasattr(cmd, "auto_help_display_key") else cmd.key: cmd for cmd in cmd_help_topics } return cmd_help_topics, db_help_topics, file_help_topics -class HelpMixin(): +class HelpMixin: """ This is a "mixin", a modifier of sorts. @@ -224,7 +224,7 @@ class HelpDetailView(HelpMixin, DetailView): # Makes sure the page has a sensible title. obj = self.get_object() topic = get_help_topic(obj) - return f'{topic} detail' + return f"{topic} detail" def get_context_data(self, **kwargs): """ @@ -272,7 +272,7 @@ class HelpDetailView(HelpMixin, DetailView): context["topic_previous"] = None # Get the help entry text - text = 'Failed to find entry.' + text = "Failed to find entry." if inherits_from(obj, "evennia.commands.command.Command"): text = obj.__doc__ elif inherits_from(obj, "evennia.help.models.HelpEntry"): @@ -297,8 +297,8 @@ class HelpDetailView(HelpMixin, DetailView): """ - if hasattr(self, 'obj'): - return getattr(self, 'obj', None) + if hasattr(self, "obj"): + return getattr(self, "obj", None) # Get the queryset for the help entries the user can access if not queryset: @@ -323,9 +323,7 @@ class HelpDetailView(HelpMixin, DetailView): # Check if this object was requested in a valid manner if not obj: - return HttpResponseBadRequest( - f"No ({category}/{topic})s found matching the query." - ) + return HttpResponseBadRequest(f"No ({category}/{topic})s found matching the query.") else: # cache the object if one was found self.obj = obj diff --git a/evennia/web/website/views/index.py b/evennia/web/website/views/index.py index b70291265e..0337d17c09 100644 --- a/evennia/web/website/views/index.py +++ b/evennia/web/website/views/index.py @@ -29,14 +29,17 @@ def _gamestats(): nobjs = ObjectDB.objects.count() nobjs = nobjs or 1 # fix zero-div error with empty database - Character = class_from_module(settings.BASE_CHARACTER_TYPECLASS, - fallback=settings.FALLBACK_CHARACTER_TYPECLASS) + Character = class_from_module( + settings.BASE_CHARACTER_TYPECLASS, fallback=settings.FALLBACK_CHARACTER_TYPECLASS + ) nchars = Character.objects.all_family().count() - Room = class_from_module(settings.BASE_ROOM_TYPECLASS, - fallback=settings.FALLBACK_ROOM_TYPECLASS) + Room = class_from_module( + settings.BASE_ROOM_TYPECLASS, fallback=settings.FALLBACK_ROOM_TYPECLASS + ) nrooms = Room.objects.all_family().count() - Exit = class_from_module(settings.BASE_EXIT_TYPECLASS, - fallback=settings.FALLBACK_EXIT_TYPECLASS) + Exit = class_from_module( + settings.BASE_EXIT_TYPECLASS, fallback=settings.FALLBACK_EXIT_TYPECLASS + ) nexits = Exit.objects.all_family().count() nothers = nobjs - nchars - nrooms - nexits diff --git a/evennia/web/website/views/objects.py b/evennia/web/website/views/objects.py index 278bbd3837..09341ffd2d 100644 --- a/evennia/web/website/views/objects.py +++ b/evennia/web/website/views/objects.py @@ -11,8 +11,7 @@ from django.core.exceptions import PermissionDenied from django.contrib import messages from evennia.utils import class_from_module from django.utils.text import slugify -from .mixins import ( - EvenniaCreateView, EvenniaDeleteView, EvenniaUpdateView, EvenniaDetailView) +from .mixins import EvenniaCreateView, EvenniaDeleteView, EvenniaUpdateView, EvenniaDetailView class ObjectDetailView(EvenniaDetailView): @@ -35,8 +34,9 @@ class ObjectDetailView(EvenniaDetailView): # # So when you extend it, this line should look simple, like: # model = Object - model = class_from_module(settings.BASE_OBJECT_TYPECLASS, - fallback=settings.FALLBACK_OBJECT_TYPECLASS) + model = class_from_module( + settings.BASE_OBJECT_TYPECLASS, fallback=settings.FALLBACK_OBJECT_TYPECLASS + ) # What HTML template you wish to use to display this page. template_name = "website/object_detail.html" @@ -139,8 +139,9 @@ class ObjectCreateView(LoginRequiredMixin, EvenniaCreateView): """ - model = class_from_module(settings.BASE_OBJECT_TYPECLASS, - fallback=settings.FALLBACK_OBJECT_TYPECLASS) + model = class_from_module( + settings.BASE_OBJECT_TYPECLASS, fallback=settings.FALLBACK_OBJECT_TYPECLASS + ) class ObjectDeleteView(LoginRequiredMixin, ObjectDetailView, EvenniaDeleteView): @@ -155,8 +156,9 @@ class ObjectDeleteView(LoginRequiredMixin, ObjectDetailView, EvenniaDeleteView): """ # -- Django constructs -- - model = class_from_module(settings.BASE_OBJECT_TYPECLASS, - fallback=settings.FALLBACK_OBJECT_TYPECLASS) + model = class_from_module( + settings.BASE_OBJECT_TYPECLASS, fallback=settings.FALLBACK_OBJECT_TYPECLASS + ) template_name = "website/object_confirm_delete.html" # -- Evennia constructs -- @@ -178,8 +180,9 @@ class ObjectUpdateView(LoginRequiredMixin, ObjectDetailView, EvenniaUpdateView): """ # -- Django constructs -- - model = class_from_module(settings.BASE_OBJECT_TYPECLASS, - fallback=settings.FALLBACK_OBJECT_TYPECLASS) + model = class_from_module( + settings.BASE_OBJECT_TYPECLASS, fallback=settings.FALLBACK_OBJECT_TYPECLASS + ) # -- Evennia constructs -- access_type = "edit"