def suggest_command(self): """Create default "command not available" error message.""" raw = self.raw_string.strip() # The raw command line text, minus surrounding whitespace char = self.character message = ["|wCommand |n'|y%s|n' |wis not available." % raw] suggestions = string_suggestions(raw, self.cmdset.get_all_cmd_keys_and_aliases(char), cutoff=0.72, maxnum=3) if suggestions: if len(suggestions) == 1: message.append('Maybe you meant |n"|g%s|n" |w?' % suggestions[0]) else: message.append('Maybe you meant %s' % '|w, '.join('|n"|g%s|n"' % each for each in suggestions[:-1])) message.append('|wor |n"|g%s|n" |w?' % suggestions[-1:][0]) else: message.append('Type |n"|ghelp|n"|w for help.') return ' '.join(message)
def func(self): """ Run the dynamic help entry creator. """ query, cmdset = self.args, self.cmdset caller = self.caller suggestion_cutoff = self.suggestion_cutoff suggestion_maxnum = self.suggestion_maxnum if not query: query = "all" # removing doublets in cmdset, caused by cmdhandler # having to allow doublet commands to manage exits etc. cmdset.make_unique(caller) # retrieve all available commands and database topics all_cmds = [cmd for cmd in cmdset if self.check_show_help(cmd, caller)] all_topics = [ topic for topic in HelpEntry.objects.all() if topic.access(caller, 'view', default=True) ] all_categories = list( set([cmd.help_category.lower() for cmd in all_cmds] + [topic.help_category.lower() for topic in all_topics])) if query in ("list", "all"): # we want to list all available help entries, grouped by category hdict_cmd = defaultdict(list) hdict_topic = defaultdict(list) # create the dictionaries {category:[topic, topic ...]} required by format_help_list # Filter commands that should be reached by the help # system, but not be displayed in the table. for cmd in all_cmds: if self.should_list_cmd(cmd, caller): hdict_cmd[cmd.help_category].append(cmd.key) [ hdict_topic[topic.help_category].append(topic.key) for topic in all_topics ] # report back self.msg_help(self.format_help_list(hdict_cmd, hdict_topic)) return # Try to access a particular command # build vocabulary of suggestions and rate them by string similarity. suggestions = None if suggestion_maxnum > 0: vocabulary = [cmd.key for cmd in all_cmds if cmd ] + [topic.key for topic in all_topics] + all_categories [vocabulary.extend(cmd.aliases) for cmd in all_cmds] suggestions = [ sugg for sugg in string_suggestions(query, set(vocabulary), cutoff=suggestion_cutoff, maxnum=suggestion_maxnum) if sugg != query ] if not suggestions: suggestions = [ sugg for sugg in vocabulary if sugg != query and sugg.startswith(query) ] # try an exact command auto-help match match = [cmd for cmd in all_cmds if cmd == query] if len(match) == 1: formatted = self.format_help_entry(match[0].key, match[0].get_help( caller, cmdset), aliases=match[0].aliases, suggested=suggestions) self.msg_help(formatted) return # try an exact database help entry match match = list(HelpEntry.objects.find_topicmatch(query, exact=True)) if len(match) == 1: formatted = self.format_help_entry(match[0].key, match[0].entrytext, aliases=match[0].aliases.all(), suggested=suggestions) self.msg_help(formatted) return # try to see if a category name was entered if query in all_categories: self.msg_help( self.format_help_list( { query: [ cmd.key for cmd in all_cmds if cmd.help_category == query ] }, { query: [ topic.key for topic in all_topics if topic.help_category == query ] })) return # no exact matches found. Just give suggestions. self.msg( self.format_help_entry("", "No help entry found for '%s'" % query, None, suggested=suggestions))
def func(self): """ Run the dynamic help entry creator. """ query, cmdset = self.args, self.cmdset caller = self.caller unavailable = False suggestion_cutoff = 0.6 suggestion_maxnum = 5 if not query: query = "all" # removing doublets in cmdset, caused by cmdhandler # having to allow doublet commands to manage exits etc. cmdset.make_unique(caller) # retrieve all available commands and database topics all_cmds = [ cmd for cmd in cmdset if cmd.auto_help and cmd.access(caller) ] player = caller if not hasattr(caller, 'character'): player = caller.player if not player.character: try: char_cmds = [ cmd for cmd in player.char_ob.cmdset.all()[0] if cmd.auto_help and cmd.access(caller) ] all_cmds += char_cmds except Exception: pass all_topics = [ topic for topic in HelpEntry.objects.all() if topic.access(caller, 'view', default=True) ] all_categories = list( set([cmd.help_category.lower() for cmd in all_cmds] + [topic.help_category.lower() for topic in all_topics])) if query in ("list", "all"): # we want to list all available help entries, grouped by category hdict_cmd = defaultdict(list) hdict_topic = defaultdict(list) # create the dictionaries {category:[topic, topic ...]} required by format_help_list [hdict_cmd[cmd.help_category].append(cmd.key) for cmd in all_cmds] [ hdict_topic[topic.help_category].append(topic.key) for topic in all_topics ] # report back self.msg(format_help_list(hdict_cmd, hdict_topic, brief=True)) return # Try to access a particular command # build vocabulary of suggestions and rate them by string similarity. vocabulary = [cmd.key for cmd in all_cmds if cmd ] + [topic.key for topic in all_topics] + all_categories [vocabulary.extend(cmd.aliases) for cmd in all_cmds] suggestions = [ sugg for sugg in string_suggestions(query, set(vocabulary), cutoff=suggestion_cutoff, maxnum=suggestion_maxnum) if sugg != query ] if not suggestions: suggestions = [ sugg for sugg in vocabulary if sugg != query and sugg.startswith(query) ] found_match = False # try an exact command auto-help match match = [cmd for cmd in all_cmds if cmd == query] # Account for prefixes if not match: if (query[0] == '+'): match = [ cmd for cmd in all_cmds if ((cmd == query[1:]) or (cmd == "@%s" % query[1:])) ] elif (query[0] == '@'): match = [ cmd for cmd in all_cmds if ((cmd == query[1:]) or (cmd == "+%s" % query[1:])) ] else: match = [ cmd for cmd in all_cmds if ((cmd == "@%s" % query) or (cmd == "+%s" % query)) ] if not match: match = [cmd for cmd in SituationalCmdSet() if cmd == query] unavailable = True if len(match) == 1: if not [ob for ob in self.cmdset if ob == match[0]]: unavailable = True doc_text = match[0].get_help(caller, cmdset) try: tags = match[0].help_entry_tags except AttributeError: tags = None self.msg( format_help_entry(match[0].key, doc_text, aliases=match[0].aliases, suggested=suggestions, unavailable=unavailable, related_tags=tags)) found_match = True # try an exact database help entry match match = list(HelpEntry.objects.find_topicmatch(query, exact=True)) match = [ topic for topic in match if topic.access(caller, 'view', default=True) ] if len(match) == 1: self.msg( format_help_entry(match[0].key, match[0].entrytext, suggested=suggestions)) found_match = True # try to see if a category name was entered if query in all_categories: # fixed bug - wouldn't match if category name was capitalized self.msg( format_help_list( { query: [ cmd.key for cmd in all_cmds if cmd.help_category.lower() == query ] }, { query: [ topic.key for topic in all_topics if topic.help_category.lower() == query ] })) return if found_match: return # no exact matches found. Just give suggestions. self.msg( format_help_entry("", "No help entry found for '%s'" % query, None, suggested=suggestions))
def cmdhandler(called_by, raw_string, _testing=False, callertype="session", session=None, cmdobj=None, cmdobj_key=None, **kwargs): """ This is the main mechanism that handles any string sent to the engine. Args: called_by (Session, Player or Object): Object from which this command was called. which this was called from. What this is depends on the game state. raw_string (str): The command string as given on the command line. _testing (bool, optional): Used for debug purposes and decides if we should actually execute the command or not. If True, the command instance will be returned. callertype (str, optional): One of "session", "player" or "object". These are treated in decending order, so when the Session is the caller, it will merge its own cmdset into cmdsets from both Player and eventual puppeted Object (and cmdsets in its room etc). A Player will only include its own cmdset and the Objects and so on. Merge order is the same order, so that Object cmdsets are merged in last, giving them precendence for same-name and same-prio commands. session (Session, optional): Relevant if callertype is "player" - the session will help retrieve the correct cmdsets from puppeted objects. cmdobj (Command, optional): If given a command instance, this will be executed using `called_by` as the caller, `raw_string` representing its arguments and (optionally) `cmdobj_key` as its input command name. No cmdset lookup will be performed but all other options apply as normal. This allows for running a specific Command within the command system mechanism. cmdobj_key (string, optional): Used together with `cmdobj` keyword to specify which cmdname should be assigned when calling the specified Command instance. This is made available as `self.cmdstring` when the Command runs. If not given, the command will be assumed to be called as `cmdobj.key`. Kwargs: kwargs (any): other keyword arguments will be assigned as named variables on the retrieved command object *before* it is executed. This is unused in default Evennia but may be used by code to set custom flags or special operating conditions for a command as it executes. Returns: deferred (Deferred): This deferred is fired with the return value of the command's `func` method. This is not used in default Evennia. """ @inlineCallbacks def _run_command(cmd, cmdname, args, raw_string, cmdset, session, player): """ Helper function: This initializes and runs the Command instance once the parser has identified it as either a normal command or one of the system commands. Args: cmd (Command): Command object. cmdname (str): Name of command. args (str): Extra text entered after the identified command. raw_string (str): Full input string. cmdset (CmdSet): Command sert the command belongs to (if any).. session (Session): Session of caller (if any). player (Player): Player of caller (if any). Returns: deferred (Deferred): this will fire with the return of the command's `func` method. Raises: RuntimeError: If command recursion limit was reached. """ global _COMMAND_NESTING try: # Assign useful variables to the instance cmd.caller = caller cmd.cmdstring = cmdname cmd.args = args cmd.cmdset = cmdset cmd.session = session cmd.player = player cmd.raw_string = raw_string #cmd.obj # set via on-object cmdset handler for each command, # since this may be different for every command when # merging multuple cmdsets if hasattr(cmd, 'obj') and hasattr(cmd.obj, 'scripts'): # cmd.obj is automatically made available by the cmdhandler. # we make sure to validate its scripts. yield cmd.obj.scripts.validate() if _testing: # only return the command instance returnValue(cmd) # assign custom kwargs to found cmd object for key, val in kwargs.items(): setattr(cmd, key, val) _COMMAND_NESTING[called_by] += 1 if _COMMAND_NESTING[called_by] > _COMMAND_RECURSION_LIMIT: err = _ERROR_RECURSION_LIMIT.format( recursion_limit=_COMMAND_RECURSION_LIMIT, raw_string=raw_string, cmdclass=cmd.__class__) raise RuntimeError(err) # pre-command hook abort = yield cmd.at_pre_cmd() if abort: # abort sequence returnValue(abort) # Parse and execute yield cmd.parse() # main command code # (return value is normally None) ret = cmd.func() if isinstance(ret, types.GeneratorType): # cmd.func() is a generator, execute progressively _progressive_cmd_run(cmd, ret) yield None else: ret = yield ret # post-command hook yield cmd.at_post_cmd() if cmd.save_for_next: # store a reference to this command, possibly # accessible by the next command. caller.ndb.last_cmd = yield copy(cmd) else: caller.ndb.last_cmd = None # return result to the deferred returnValue(ret) except Exception: _msg_err(caller, _ERROR_UNTRAPPED) raise ErrorReported(raw_string) finally: _COMMAND_NESTING[called_by] -= 1 raw_string = to_unicode(raw_string, force_string=True) session, player, obj = session, None, None if callertype == "session": session = called_by player = session.player obj = session.puppet elif callertype == "player": player = called_by if session: obj = yield session.puppet elif callertype == "object": obj = called_by else: raise RuntimeError("cmdhandler: callertype %s is not valid." % callertype) # the caller will be the one to receive messages and excert its permissions. # we assign the caller with preference 'bottom up' caller = obj or player or session # The error_to is the default recipient for errors. Tries to make sure a player # does not get spammed for errors while preserving character mirroring. error_to = obj or session or player try: # catch bugs in cmdhandler itself try: # catch special-type commands if cmdobj: # the command object is already given cmd = cmdobj() if callable(cmdobj) else cmdobj cmdname = cmdobj_key if cmdobj_key else cmd.key args = raw_string unformatted_raw_string = "%s%s" % (cmdname, args) cmdset = None session = session player = player else: # no explicit cmdobject given, figure it out cmdset = yield get_and_merge_cmdsets(caller, session, player, obj, callertype, raw_string) if not cmdset: # this is bad and shouldn't happen. raise NoCmdSets unformatted_raw_string = raw_string raw_string = raw_string.strip() if not raw_string: # Empty input. Test for system command instead. syscmd = yield cmdset.get(CMD_NOINPUT) sysarg = "" raise ExecSystemCommand(syscmd, sysarg) # Parse the input string and match to available cmdset. # This also checks for permissions, so all commands in match # are commands the caller is allowed to call. matches = yield _COMMAND_PARSER(raw_string, cmdset, caller) # Deal with matches if len(matches) > 1: # We have a multiple-match syscmd = yield cmdset.get(CMD_MULTIMATCH) sysarg = _("There were multiple matches.") if syscmd: # use custom CMD_MULTIMATCH syscmd.matches = matches else: # fall back to default error handling sysarg = yield _SEARCH_AT_RESULT( [match[2] for match in matches], caller, query=matches[0][0]) raise ExecSystemCommand(syscmd, sysarg) cmdname, args, cmd = "", "", None if len(matches) == 1: # We have a unique command match. But it may still be invalid. match = matches[0] cmdname, args, cmd = match[0], match[1], match[2] if not matches: # No commands match our entered command syscmd = yield cmdset.get(CMD_NOMATCH) if syscmd: # use custom CMD_NOMATCH command sysarg = raw_string else: # fallback to default error text sysarg = _( "Command '%s' is not available.") % raw_string suggestions = string_suggestions( raw_string, cmdset.get_all_cmd_keys_and_aliases(caller), cutoff=0.7, maxnum=3) if suggestions: sysarg += _( " Maybe you meant %s?") % utils.list_to_string( suggestions, _('or'), addquote=True) else: sysarg += _(" Type \"help\" for help.") raise ExecSystemCommand(syscmd, sysarg) # Check if this is a Channel-cmd match. if hasattr(cmd, 'is_channel') and cmd.is_channel: # even if a user-defined syscmd is not defined, the # found cmd is already a system command in its own right. syscmd = yield cmdset.get(CMD_CHANNEL) if syscmd: # replace system command with custom version cmd = syscmd cmd.session = session sysarg = "%s:%s" % (cmdname, args) raise ExecSystemCommand(cmd, sysarg) # A normal command. ret = yield _run_command(cmd, cmdname, args, unformatted_raw_string, cmdset, session, player) returnValue(ret) except ErrorReported as exc: # this error was already reported, so we # catch it here and don't pass it on. logger.log_err("User input was: '%s'." % exc.raw_string) except ExecSystemCommand as exc: # Not a normal command: run a system command, if available, # or fall back to a return string. syscmd = exc.syscmd sysarg = exc.sysarg if syscmd: ret = yield _run_command(syscmd, syscmd.key, sysarg, unformatted_raw_string, cmdset, session, player) returnValue(ret) elif sysarg: # return system arg error_to.msg(exc.sysarg) except NoCmdSets: # Critical error. logger.log_err("No cmdsets found: %s" % caller) error_to.msg(_ERROR_NOCMDSETS) except Exception: # We should not end up here. If we do, it's a programming bug. _msg_err(error_to, _ERROR_UNTRAPPED) except Exception: # This catches exceptions in cmdhandler exceptions themselves _msg_err(error_to, _ERROR_CMDHANDLER)
def cmdhandler(called_by, raw_string, _testing=False, callertype="session", session=None, **kwargs): """ This is the main mechanism that handles any string sent to the engine. Args: called_by (Session, Player or Object): Object from which this command was called. which this was called from. What this is depends on the game state. raw_string (str): The command string as given on the command line. _testing (bool, optional): Used for debug purposes and decides if we should actually execute the command or not. If True, the command instance will be returned. callertype (str, optional): One of "session", "player" or "object". These are treated in decending order, so when the Session is the caller, it will merge its own cmdset into cmdsets from both Player and eventual puppeted Object (and cmdsets in its room etc). A Player will only include its own cmdset and the Objects and so on. Merge order is the same order, so that Object cmdsets are merged in last, giving them precendence for same-name and same-prio commands. session (Session, optional): Relevant if callertype is "player" - the session will help retrieve the correct cmdsets from puppeted objects. Kwargs: kwargs (any): other keyword arguments will be assigned as named variables on the retrieved command object *before* it is executed. This is unused in default Evennia but may be used by code to set custom flags or special operating conditions for a command as it executes. Returns: deferred (Deferred): This deferred is fired with the return value of the command's `func` method. This is not used in default Evennia. """ @inlineCallbacks def _run_command(cmd, cmdname, args): """ Helper function: This initializes and runs the Command instance once the parser has identified it as either a normal command or one of the system commands. Args: cmd (Command): command object cmdname (str): name of command args (str): extra text entered after the identified command Returns: deferred (Deferred): this will fire with the return of the command's `func` method. Raises: RuntimeError: If command recursion limit was reached. """ global _COMMAND_NESTING try: # Assign useful variables to the instance cmd.caller = caller cmd.cmdstring = cmdname cmd.args = args cmd.cmdset = cmdset cmd.session = session cmd.player = player cmd.raw_string = unformatted_raw_string #cmd.obj # set via on-object cmdset handler for each command, # since this may be different for every command when # merging multuple cmdsets if hasattr(cmd, 'obj') and hasattr(cmd.obj, 'scripts'): # cmd.obj is automatically made available by the cmdhandler. # we make sure to validate its scripts. yield cmd.obj.scripts.validate() if _testing: # only return the command instance returnValue(cmd) # assign custom kwargs to found cmd object for key, val in kwargs.items(): setattr(cmd, key, val) _COMMAND_NESTING[called_by] += 1 if _COMMAND_NESTING[called_by] > _COMMAND_RECURSION_LIMIT: err = _ERROR_RECURSION_LIMIT.format(recursion_limit=_COMMAND_RECURSION_LIMIT, raw_string=unformatted_raw_string, cmdclass=cmd.__class__) raise RuntimeError(err) # pre-command hook abort = yield cmd.at_pre_cmd() if abort: # abort sequence returnValue(abort) # Parse and execute yield cmd.parse() # main command code # (return value is normally None) ret = yield cmd.func() # post-command hook yield cmd.at_post_cmd() if cmd.save_for_next: # store a reference to this command, possibly # accessible by the next command. caller.ndb.last_cmd = yield copy(cmd) else: caller.ndb.last_cmd = None # return result to the deferred returnValue(ret) except Exception: _msg_err(caller, _ERROR_UNTRAPPED) raise ErrorReported finally: _COMMAND_NESTING[called_by] -= 1 raw_string = to_unicode(raw_string, force_string=True) session, player, obj = session, None, None if callertype == "session": session = called_by player = session.player obj = session.puppet elif callertype == "player": player = called_by if session: obj = yield session.puppet elif callertype == "object": obj = called_by else: raise RuntimeError("cmdhandler: callertype %s is not valid." % callertype) # the caller will be the one to receive messages and excert its permissions. # we assign the caller with preference 'bottom up' caller = obj or player or session # The error_to is the default recipient for errors. Tries to make sure a player # does not get spammed for errors while preserving character mirroring. error_to = obj or session or player try: # catch bugs in cmdhandler itself try: # catch special-type commands cmdset = yield get_and_merge_cmdsets(caller, session, player, obj, callertype) if not cmdset: # this is bad and shouldn't happen. raise NoCmdSets unformatted_raw_string = raw_string raw_string = raw_string.strip() if not raw_string: # Empty input. Test for system command instead. syscmd = yield cmdset.get(CMD_NOINPUT) sysarg = "" raise ExecSystemCommand(syscmd, sysarg) # Parse the input string and match to available cmdset. # This also checks for permissions, so all commands in match # are commands the caller is allowed to call. matches = yield _COMMAND_PARSER(raw_string, cmdset, caller) # Deal with matches if len(matches) > 1: # We have a multiple-match syscmd = yield cmdset.get(CMD_MULTIMATCH) sysarg = _("There were multiple matches.") if syscmd: # use custom CMD_MULTIMATCH syscmd.matches = matches else: # fall back to default error handling sysarg = yield _SEARCH_AT_RESULT([match[2] for match in matches], caller, query=match[0]) raise ExecSystemCommand(syscmd, sysarg) if len(matches) == 1: # We have a unique command match. But it may still be invalid. match = matches[0] cmdname, args, cmd = match[0], match[1], match[2] if not matches: # No commands match our entered command syscmd = yield cmdset.get(CMD_NOMATCH) if syscmd: # use custom CMD_NOMATCH command sysarg = raw_string else: # fallback to default error text sysarg = _("Command '%s' is not available.") % raw_string suggestions = string_suggestions(raw_string, cmdset.get_all_cmd_keys_and_aliases(caller), cutoff=0.7, maxnum=3) if suggestions: sysarg += _(" Maybe you meant %s?") % utils.list_to_string(suggestions, _('or'), addquote=True) else: sysarg += _(" Type \"help\" for help.") raise ExecSystemCommand(syscmd, sysarg) # Check if this is a Channel-cmd match. if hasattr(cmd, 'is_channel') and cmd.is_channel: # even if a user-defined syscmd is not defined, the # found cmd is already a system command in its own right. syscmd = yield cmdset.get(CMD_CHANNEL) if syscmd: # replace system command with custom version cmd = syscmd cmd.session = session sysarg = "%s:%s" % (cmdname, args) raise ExecSystemCommand(cmd, sysarg) # A normal command. ret = yield _run_command(cmd, cmdname, args) returnValue(ret) except ErrorReported: # this error was already reported, so we # catch it here and don't pass it on. pass except ExecSystemCommand as exc: # Not a normal command: run a system command, if available, # or fall back to a return string. syscmd = exc.syscmd sysarg = exc.sysarg if syscmd: ret = yield _run_command(syscmd, syscmd.key, sysarg) returnValue(ret) elif sysarg: # return system arg error_to.msg(exc.sysarg) except NoCmdSets: # Critical error. logger.log_err("No cmdsets found: %s" % caller) error_to.msg(_ERROR_NOCMDSETS) except Exception: # We should not end up here. If we do, it's a programming bug. _msg_err(error_to, _ERROR_UNTRAPPED) except Exception: # This catches exceptions in cmdhandler exceptions themselves _msg_err(error_to, _ERROR_CMDHANDLER)
def func(self): """ Run the dynamic help entry creator. """ query, cmdset = self.args, self.cmdset caller = self.caller suggestion_cutoff = self.suggestion_cutoff suggestion_maxnum = self.suggestion_maxnum if not query: query = "all" # removing doublets in cmdset, caused by cmdhandler # having to allow doublet commands to manage exits etc. cmdset.make_unique(caller) # retrieve all available commands and database topics all_cmds = [cmd for cmd in cmdset if self.check_show_help(cmd, caller)] all_topics = [topic for topic in HelpEntry.objects.all() if topic.access(caller, 'view', default=True)] all_categories = list(set([cmd.help_category.lower() for cmd in all_cmds] + [topic.help_category.lower() for topic in all_topics])) if query in ("list", "all"): # we want to list all available help entries, grouped by category hdict_cmd = defaultdict(list) hdict_topic = defaultdict(list) # create the dictionaries {category:[topic, topic ...]} required by format_help_list # Filter commands that should be reached by the help # system, but not be displayed in the table. for cmd in all_cmds: if self.should_list_cmd(cmd, caller): hdict_cmd[cmd.help_category].append(cmd.key) [hdict_topic[topic.help_category].append(topic.key) for topic in all_topics] # report back self.msg_help(self.format_help_list(hdict_cmd, hdict_topic)) return # Try to access a particular command # build vocabulary of suggestions and rate them by string similarity. suggestions = None if suggestion_maxnum > 0: vocabulary = [cmd.key for cmd in all_cmds if cmd] + [topic.key for topic in all_topics] + all_categories [vocabulary.extend(cmd.aliases) for cmd in all_cmds] suggestions = [sugg for sugg in string_suggestions(query, set(vocabulary), cutoff=suggestion_cutoff, maxnum=suggestion_maxnum) if sugg != query] if not suggestions: suggestions = [sugg for sugg in vocabulary if sugg != query and sugg.startswith(query)] # try an exact command auto-help match match = [cmd for cmd in all_cmds if cmd == query] if len(match) == 1: formatted = self.format_help_entry(match[0].key, match[0].get_help(caller, cmdset), aliases=match[0].aliases, suggested=suggestions) self.msg_help(formatted) return # try an exact database help entry match match = list(HelpEntry.objects.find_topicmatch(query, exact=True)) if len(match) == 1: formatted = self.format_help_entry(match[0].key, match[0].entrytext, aliases=match[0].aliases.all(), suggested=suggestions) self.msg_help(formatted) return # try to see if a category name was entered if query in all_categories: self.msg_help(self.format_help_list({query:[cmd.key for cmd in all_cmds if cmd.help_category==query]}, {query:[topic.key for topic in all_topics if topic.help_category==query]})) return # no exact matches found. Just give suggestions. self.msg(self.format_help_entry("", "No help entry found for '%s'" % query, None, suggested=suggestions))
def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sessid=None, **kwargs): """ This is the main function to handle any string sent to the engine. called_by - object on which this was called from. This is either a Session, a Player or an Object. raw_string - the command string given on the command line _testing - if we should actually execute the command or not. if True, the command instance will be returned instead. callertype - this is one of "session", "player" or "object", in decending order. So when the Session is the caller, it will merge its own cmdset into cmdsets from both Player and eventual puppeted Object (and cmdsets in its room etc). A Player will only include its own cmdset and the Objects and so on. Merge order is the same order, so that Object cmdsets are merged in last, giving them precendence for same-name and same-prio commands. sessid - Relevant if callertype is "player" - the session id will help retrieve the correct cmdsets from puppeted objects. **kwargs - other keyword arguments will be assigned as named variables on the retrieved command object *before* it is executed. This is unuesed in default Evennia but may be used by code to set custom flags or special operating conditions for a command as it executes. Note that this function returns a deferred! """ @inlineCallbacks def _run_command(cmd, cmdname, args): """ This initializes and runs the Command instance once the parser has identified it as either a normal command or one of the system commands. Args: cmd (Command): command object cmdname (str): name of command args (str): extra text entered after the identified command Returns: deferred (Deferred): this will fire when the func() method returns. """ try: # Assign useful variables to the instance cmd.caller = caller cmd.cmdstring = cmdname cmd.args = args cmd.cmdset = cmdset cmd.sessid = session.sessid if session else sessid cmd.session = session cmd.player = player cmd.raw_string = unformatted_raw_string #cmd.obj # set via on-object cmdset handler for each command, # since this may be different for every command when # merging multuple cmdsets if hasattr(cmd, 'obj') and hasattr(cmd.obj, 'scripts'): # cmd.obj is automatically made available by the cmdhandler. # we make sure to validate its scripts. yield cmd.obj.scripts.validate() if _testing: # only return the command instance returnValue(cmd) # assign custom kwargs to found cmd object for key, val in kwargs.items(): setattr(cmd, key, val) # pre-command hook abort = yield cmd.at_pre_cmd() if abort: # abort sequence returnValue(abort) # Parse and execute yield cmd.parse() # main command code # (return value is normally None) ret = yield cmd.func() # post-command hook yield cmd.at_post_cmd() if cmd.save_for_next: # store a reference to this command, possibly # accessible by the next command. caller.ndb.last_cmd = yield copy(cmd) else: caller.ndb.last_cmd = None # return result to the deferred returnValue(ret) except Exception: logger.log_trace() _msg_err(caller, _ERROR_UNTRAPPED) raise ErrorReported raw_string = to_unicode(raw_string, force_string=True) session, player, obj = None, None, None if callertype == "session": session = called_by player = session.player if player: obj = yield player.get_puppet(session.sessid) elif callertype == "player": player = called_by if sessid: session = player.get_session(sessid) obj = yield player.get_puppet(sessid) elif callertype == "object": obj = called_by else: raise RuntimeError("cmdhandler: callertype %s is not valid." % callertype) # the caller will be the one to receive messages and excert its permissions. # we assign the caller with preference 'bottom up' caller = obj or player or session # The error_to is the default recipient for errors. Tries to make sure a player # does not get spammed for errors while preserving character mirroring. error_to = obj or session or player try: # catch bugs in cmdhandler itself try: # catch special-type commands cmdset = yield get_and_merge_cmdsets(caller, session, player, obj, callertype, sessid) if not cmdset: # this is bad and shouldn't happen. raise NoCmdSets unformatted_raw_string = raw_string raw_string = raw_string.strip() if not raw_string: # Empty input. Test for system command instead. syscmd = yield cmdset.get(CMD_NOINPUT) sysarg = "" raise ExecSystemCommand(syscmd, sysarg) # Parse the input string and match to available cmdset. # This also checks for permissions, so all commands in match # are commands the caller is allowed to call. matches = yield _COMMAND_PARSER(raw_string, cmdset, caller) # Deal with matches if len(matches) > 1: # We have a multiple-match syscmd = yield cmdset.get(CMD_MULTIMATCH) sysarg = _("There were multiple matches.") if syscmd: # use custom CMD_MULTIMATCH syscmd.matches = matches else: # fall back to default error handling sysarg = yield at_multimatch_cmd(caller, matches) raise ExecSystemCommand(syscmd, sysarg) if len(matches) == 1: # We have a unique command match. But it may still be invalid. match = matches[0] cmdname, args, cmd = match[0], match[1], match[2] # check if we allow this type of command if cmdset.no_channels and hasattr( cmd, "is_channel") and cmd.is_channel: matches = [] if cmdset.no_exits and hasattr(cmd, "is_exit") and cmd.is_exit: matches = [] if not matches: # No commands match our entered command syscmd = yield cmdset.get(CMD_NOMATCH) if syscmd: # use custom CMD_NOMATCH command sysarg = raw_string else: # fallback to default error text sysarg = _("Command '%s' is not available.") % raw_string suggestions = string_suggestions( raw_string, cmdset.get_all_cmd_keys_and_aliases(caller), cutoff=0.7, maxnum=3) if suggestions: sysarg += _( " Maybe you meant %s?") % utils.list_to_string( suggestions, _('or'), addquote=True) else: sysarg += _(" Type \"help\" for help.") raise ExecSystemCommand(syscmd, sysarg) # Check if this is a Channel-cmd match. if hasattr(cmd, 'is_channel') and cmd.is_channel: # even if a user-defined syscmd is not defined, the # found cmd is already a system command in its own right. syscmd = yield cmdset.get(CMD_CHANNEL) if syscmd: # replace system command with custom version cmd = syscmd cmd.sessid = session.sessid if session else None sysarg = "%s:%s" % (cmdname, args) raise ExecSystemCommand(cmd, sysarg) # A normal command. ret = yield _run_command(cmd, cmdname, args) returnValue(ret) except ErrorReported: # this error was already reported, so we # catch it here and don't pass it on. pass except ExecSystemCommand, exc: # Not a normal command: run a system command, if available, # or fall back to a return string. syscmd = exc.syscmd sysarg = exc.sysarg if syscmd: ret = yield _run_command(syscmd, syscmd.key, sysarg) returnValue(ret) elif sysarg: # return system arg error_to.msg(exc.sysarg, _nomulti=True) except NoCmdSets: # Critical error. logger.log_errmsg("No cmdsets found: %s" % caller) error_to.msg(_ERROR_NOCMDSETS, _nomulti=True) except Exception: # We should not end up here. If we do, it's a programming bug. logger.log_trace() _msg_err(error_to, _ERROR_UNTRAPPED)