Ejemplo n.º 1
0
 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)
Ejemplo n.º 2
0
    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))
Ejemplo n.º 3
0
    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))
Ejemplo n.º 4
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)
Ejemplo n.º 5
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)
Ejemplo n.º 6
0
    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))
Ejemplo n.º 7
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)