Esempio n. 1
0
 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)
     )
Esempio n. 2
0
    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
Esempio n. 3
0
    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))))
Esempio n. 4
0
    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
Esempio n. 5
0
 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)
Esempio n. 6
0
    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")
Esempio n. 7
0
    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}"
Esempio n. 8
0
    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)
Esempio n. 9
0
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
Esempio n. 10
0
 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))
Esempio n. 11
0
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
Esempio n. 12
0
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)
Esempio n. 13
0
    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
Esempio n. 14
0
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)
Esempio n. 15
0
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)