def test_list_to_string(self): self.assertEqual("1, 2, 3", utils.list_to_string([1, 2, 3], endsep="")) self.assertEqual('"1", "2", "3"', utils.list_to_string([1, 2, 3], endsep="", addquote=True)) self.assertEqual("1, 2 and 3", utils.list_to_string([1, 2, 3])) self.assertEqual( '"1", "2" and "3"', utils.list_to_string([1, 2, 3], endsep="and", addquote=True) )
def return_appearance(self, looker, **kwargs): tennis_court = ObjectDB.objects.get(id=88) rec_message = "The nearby tennis courts are (always) empty." for x in tennis_court.contents: if x.has_account: rec_message = " You can see people milling about on the tennis court." """ This formats a description. It is the hook a 'look' command should call. Args: looker (Object): Object doing the looking. **kwargs (dict): Arbitrary, optional arguments for users overriding the call (unused by default). """ if not looker: return "" # get and identify all objects visible = (con for con in self.contents if con != looker and con.access(looker, "view")) exits, users, things = [], [], defaultdict(list) for con in visible: key = con.get_display_name(looker) if con.destination: exits.append(key) elif con.has_account: users.append("|c%s|n" % key) else: # things can be pluralized things[key].append(con) # get description, build string string = "|c%s|n\n" % self.get_display_name(looker) desc = self.db.desc if desc: string += "%s" % desc string += " " + rec_message if exits: string += "\n|wExits:|n " + list_to_string(exits) if users or things: # handle pluralization of things (never pluralize users) thing_strings = [] for key, itemlist in sorted(things.items()): nitem = len(itemlist) if nitem == 1: key, _ = itemlist[0].get_numbered_name(nitem, looker, key=key) else: key = [ item.get_numbered_name(nitem, looker, key=key)[1] for item in itemlist ][0] thing_strings.append(key) string += "\n|wYou see:|n " + list_to_string(users + thing_strings) return string
def ask_node(self, options, prompt="Enter choice: ", default=None): """ Retrieve options and jump to different menu nodes Args: options (dict): Node options on the form {key: (desc, callback), } prompt (str, optional): Question to ask default (str, optional): Default value to use if user hits return. """ opt_txt = "\n".join(f" {key}: {desc}" for key, (desc, _, _) in options.items()) self.display(opt_txt + "\n") while True: resp = input(prompt).strip() if not resp: if default: resp = str(default) if resp.lower() in options: # self.display(f" Selected '{resp}'.") desc, callback, kwargs = options[resp.lower()] callback(self, **kwargs) elif resp.lower() in ("quit", "q"): sys.exit() elif resp: # input, but nothing was recognized self.display(" Choose one of: {}".format( list_to_string(list(options))))
def return_appearance(self, looker, **kwargs): """ This formats a description. It is the hook a 'look' command should call. Args: looker (Object): Object doing the looking. **kwargs (dict): Arbitrary, optional arguments for users overriding the call (unused by default). """ if not looker: return "" # get and identify all objects visible = (con for con in self.contents if con != looker and con.access(looker, "view")) exits, users, things = [], [], defaultdict(list) for con in visible: key = con.get_display_name(looker) if con.destination: exits.append(key) elif con.has_account: users.append("|c%s|n" % key) else: # things can be pluralized things[key].append(con) # get description, build string string = "|c%s|n\n" % self.get_display_name(looker) desc = self.db.desc if desc: string += "%s" % desc if exits: string += "\n|wSorties :|n " + list_to_string(exits, "et") if users or things: # handle pluralization of things (never pluralize users) thing_strings = [] for key, itemlist in sorted(things.iteritems()): nitem = len(itemlist) if nitem == 1: key, _ = itemlist[0].get_numbered_name(nitem, looker, key=key) else: key = [item.get_numbered_name(nitem, looker, key=key)[1] for item in itemlist][0] thing_strings.append(key) string += "\n|wVous voyez :|n " + list_to_string(users + thing_strings, "et") return string
def func(self): """ Stores a vote for the player in the caller's player object, to allow for easier sorting from the AccountDB manager. Players are allowed 5 votes per week, each needing to be a distinct character with a different email address than the caller. Email addresses that are not set (having the '*****@*****.**' default, will be rejected as unsuitable. """ caller = self.caller if not caller.roster.current_account: raise ValueError("ERROR: No PlayerAccount set for this player!") if not self.args: votes = caller.db.votes or [] voted_for = list_to_string(votes) or "no one" remaining = self.max_votes - self.count_votes() caller.msg( "You have voted for %s, and have %s votes remaining." % (voted_for, remaining) ) return targ = caller.search(self.args) if not targ: caller.msg("Vote for who?") return if targ in self.caller_alts: caller.msg("You cannot vote for your alts.") return votes = caller.db.votes or [] if targ.roster.roster.name != "Active" and targ not in votes: caller.msg("You can only vote for an active character.") return if not targ.char_ob: caller.msg("%s doesn't have a character object assigned to them." % targ) return if targ in votes: if self.cmdstring == "unvote": caller.msg("Removing your vote for %s." % targ) votes.remove(targ) caller.db.votes = votes else: caller.msg( "You are already voting for %s. To remove them, use 'unvote'." % targ ) return else: if self.cmdstring == "unvote": caller.msg("You are not currently voting for %s." % targ) return num_votes = self.count_votes() if num_votes >= self.max_votes: caller.msg("You have voted %s times, which is the maximum." % num_votes) return votes.append(targ) caller.db.votes = votes caller.msg("Vote recorded for %s." % targ)
def wrapped_names(self, looker): """ Return a wrapped list of names. This method uses `list_to_string` to combine all names. Args: looker (Object): the looker. Returns: names (str): the list of names, wrapped for friendly display. """ return list_to_string(self.names(looker), endsep="and")
def return_appearance(self, looker, **kwargs): obj, pos = self.get_position(looker) pos = (f"\n|x[{self.position_prep_map[pos]} on " f"{obj.get_display_name(looker)}]|n" if obj else "") admin_only = "" if self.check_perm(looker, "Admin"): # only for admins objs = DefaultObject.objects.filter_family( db_location=self).exclude(id=looker.id) admin_only = "\n|xAdmin only: " + \ list_to_string([obj.get_display_name(looker) for obj in objs]) return f"{self.db.desc}{pos}{admin_only}"
def get_help(self, caller): """ Get help about this object. By default we return a listing of all actions you can do on this object. """ # custom-created signatures. We don't sort these command_signatures, helpstr = self.get_cmd_signatures() callsigns = list_to_string(["*" + sig for sig in command_signatures], endsep="or") # parse for *thing markers (use these as items) options = caller.attributes.get("options", category=self.room.tagcategory, default={}) style = options.get("things_style", 2) helpstr = helpstr.format(callsigns=callsigns) helpstr = parse_for_things(helpstr, style, clr="|w") return wrap(helpstr, width=80)
def name_special(caller, raw_input): # First, let's set the second effect and correct the case, if the answer 'no' wasn't given to get here. specialdict = rules.special_dictionary() if raw_input.lower() != "no": caller.ndb.special_effect_2 = raw_input for special in specialdict: if caller.ndb.special_effect_2.lower() == str(special).lower(): caller.ndb.special_effect_2 = special else: caller.ndb.special_effect_2 = "" effectlist = [caller.ndb.special_effect_1] if caller.ndb.special_effect_2: effectlist.append(caller.ndb.special_effect_2) effectstring = utils.list_to_string(effectlist, endsep="|255and|455", addquote=False) text = "The effects you chose are:\n|255[|455%s|255] (|455%i|255 SP)\n|nGive your special move a name! (30 characters maximum)\nExample: \"Burning Fury Punch\", \"Magic Bubble\", \"Psionic Blast\", etc." % (effectstring, rules.special_cost(effectlist)) options = ({"key": "_default", "goto": "verify_name"}) return text, options
def func(self): """Implements command""" caller = self.caller places = caller.location.places caller.msg("{wPlaces here:{n") caller.msg("{w------------{n") if not places: caller.msg("No places found.") return for num in range(len(places)): p_name = places[num].key max_spots = places[num].item_data.max_spots occupants = places[num].item_data.occupants spots = max_spots - len(occupants) caller.msg("%s (#%s) : %s empty spaces" % (p_name, num + 1, spots)) if occupants: # get names rather than keys so real names don't show up for masked characters names = [ob.name for ob in occupants if ob.access(caller, "view")] caller.msg("-Occupants: %s" % list_to_string(names))
def verify_desc(caller, raw_input): "Sets the special desc and asks the player to verify it." specialdesc = utils.wrap(raw_input, width=80) if len(specialdesc) > 300: specialdesc = specialdesc[:299] if specialdesc == "": specialdesc = "A special move called %s" % callder.ndb.special_name caller.ndb.special_desc = specialdesc effectlist = [caller.ndb.special_effect_1] if caller.ndb.special_effect_2: effectlist.append(caller.ndb.special_effect_2) effectstring = utils.list_to_string(effectlist, endsep="|255and|455", addquote=False) effectstringlength = len(effectstring) if "|255and|455" in effectstring: effectstringlength -= 8 paddinglength = max((80 - effectstringlength - len(caller.ndb.special_name) - 9), 0) padding = ('{:-^%i}' % paddinglength).format('') text = "Your special move:\n\n|455%s |255[|455%s|255] %s |455%i|255 SP|n\n%s\n\nIs this all right?" % (caller.ndb.special_name, effectstring, padding, rules.special_cost(effectlist), caller.ndb.special_desc) options = ({"key": "Yes", "desc":"Finalize special move", "goto": "finalize_special"}, {"key": "No", "desc":"Enter a new description", "goto": "desc_special"} ) return text, options
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 return_appearance(self, looker): """ This formats a description. It is the hook a 'look' command should call. Args: looker (Object): Object doing the looking. """ objects = ObjectSet() if not looker: return # Get and identify all objects visible = (con for con in self.contents if con != looker and con.access(looker, "view")) exits = [] for con in visible: key = con.get_display_name(looker) if con.destination: exits.append(key) else: objects.append(con) # Get description, build string string = "|c%s|n" % self.get_display_name(looker) desc = self.db.desc if desc: # Format the string description = desc # Process through the description keywords ($example) vars = {} if self.db.prototype: if self.db.prototype.db.desc: vars["parent"] = self.db.prototype.db.desc self.callbacks.call("describe", self, looker) match = RE_KEYWORD.search(description) while match: keyword = match.group()[1:] if keyword in vars: var = vars[keyword] else: var = self.callbacks.get_variable(keyword) start, end = match.span() description = description[:start] + var + description[end:] match = RE_KEYWORD.search(description) # Wrap the description final = "" for line in description.splitlines(): if len(line) >= 75: line = "\n".join(wrap(" " + line, 75)) final += "\n" + line description = final else: description = "" string += description if exits: string += "\n|wExits:|n " + ", ".join(exits) if objects: string += "\n|wYou see:|n " + list_to_string(objects.names(looker), endsep="and") return string
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 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)