示例#1
0
    def test_text(self):
        """Test to parse one argument as text."""
        # Try to enter a single word
        args = CommandArgs()
        args.add_argument("text", dest="simple")
        namespace = args.parse("something")
        self.assertEqual(namespace.simple, "something")

        # Try to enter several words
        args = CommandArgs()
        args.add_argument("text")
        namespace = args.parse("something else")
        self.assertEqual(namespace.text, "something else")
示例#2
0
    def test_options(self):
        """Test options arguments."""
        # Try to enter a single option
        args = CommandArgs()
        options = args.add_argument("options")
        options.add_option("t", "title", dest="title")
        namespace = args.parse("title=ok")
        self.assertEqual(namespace.title, "ok")

        # Try again, but with two words in the title
        namespace = args.parse("title=a title")
        self.assertEqual(namespace.title, "a title")

        # Try short options
        namespace = args.parse("t=ok")
        self.assertEqual(namespace.title, "ok")

        # Try again, but with two words in the title
        namespace = args.parse("t=a title")
        self.assertEqual(namespace.title, "a title")

        # Try with several options
        args = CommandArgs()
        options = args.add_argument("options")
        options.add_option("t", "title", optional=False, dest="title")
        options.add_option("d", "description", dest="description")
        namespace = args.parse("title=ok d=a description")
        self.assertEqual(namespace.title, "ok")
        self.assertEqual(namespace.description, "a description")

        # Try again, but with two words in the title
        namespace = args.parse("title=a title description=something")
        self.assertEqual(namespace.title, "a title")
        self.assertEqual(namespace.description, "something")

        # Try short options
        namespace = args.parse("description=well t=ok")
        self.assertEqual(namespace.title, "ok")
        self.assertEqual(namespace.description, "well")

        # Try again, but with two words in the title
        namespace = args.parse("t=a title description=hi")
        self.assertEqual(namespace.title, "a title")
        self.assertEqual(namespace.description, "hi")

        # Test with word argument
        args = CommandArgs()
        args.add_argument("word")
        options = args.add_argument("options")
        options.add_option("t", "title", dest="title")
        options.add_option("d", "description", dest="description")
        namespace = args.parse("and d=something else title=ok")
        self.assertEqual(namespace.word, "and")
        self.assertEqual(namespace.title, "ok")
        self.assertEqual(namespace.description, "something else")

        # Test mandatory and optional options
        args = CommandArgs()
        options = args.add_argument("options")
        options.add_option("t", "title", default="nothing", dest="title")
        options.add_option("d", "description", dest="description")
        namespace = args.parse("d=a description")
        self.assertEqual(namespace.title, "nothing")
        self.assertEqual(namespace.description, "a description")
示例#3
0
    def test_word(self):
        """Test one or several words in arguments."""
        # Try to enter a single word
        args = CommandArgs()
        args.add_argument("word", dest="simple")
        namespace = args.parse("something")
        self.assertEqual(namespace.simple, "something")

        # Try to enter several words
        args = CommandArgs()
        args.add_argument("word", dest="first")
        args.add_argument("word", dest="second")
        namespace = args.parse("something else")
        self.assertEqual(namespace.first, "something")
        self.assertEqual(namespace.second, "else")
示例#4
0
    def test_number(self):
        """Test a number argument."""
        args = CommandArgs()
        args.add_argument("number")
        namespace = args.parse("38")
        self.assertEqual(namespace.number, 38)

        # Try an invalid number
        args = CommandArgs()
        number = args.add_argument("number")
        result = args.parse("no")
        self.assertIsInstance(result, ArgumentError)
        self.assertEqual(str(result),
                         number.msg_invalid_number.format(number="no"))

        # Try with an optional number
        args = CommandArgs()
        args.add_argument("number", optional=True, default=1)
        args.add_argument("text")
        namespace = args.parse("2 red apples")
        self.assertEqual(namespace.number, 2)
        self.assertEqual(namespace.text, "red apples")
        namespace = args.parse("red apple")
        self.assertEqual(namespace.number, 1)
        self.assertEqual(namespace.text, "red apple")

        # Try with words and an optional number
        args = CommandArgs()
        args.add_argument("number", dest="left", optional=True, default=1)
        args.add_argument("word")
        args.add_argument("number", dest="right")
        namespace = args.parse("2 times 3")
        self.assertEqual(namespace.left, 2)
        self.assertEqual(namespace.word, "times")
        self.assertEqual(namespace.right, 3)
        namespace = args.parse("neg 5")
        self.assertEqual(namespace.left, 1)
        self.assertEqual(namespace.word, "neg")
        self.assertEqual(namespace.right, 5)
示例#5
0
class Command:
    """
    Base class for commands.

    Static commands are mostly defined and dynamically imported
    from the file system, from the 'command' directory and its
    sub-directories.

    A static command can have a name, inferred from the class
    name if not provided.  It can also have aliases, a command
    category and basic command permissions.  It's help entry is
    inferred from the command's docstring.

    Here's a very basic example.  Assuming you want to add the 'look'
    command, just create a file with the '.py' extension in the command
    package (it can be in a sub-package, for the sake of organization).

    ```python
    from command import Command, CommandArgs

    class Look(Command): # 'look' (lowercase class name) will be the name

        '''
        Look command, to look around you.

        Syntax:
            look [object]

        This command is used to look around you or look a specific object
        more in details.

        '''

        # If you're not happy with the default name, just provide a
        # class variable 'name' and set the command name to it.  You can
        # define the category in the 'category' class variable as a
        # string, and the command permissions, still as a string, in the
        # 'permissions' class variable.  If they're not defined,
        # these last two values will be searched in parent packages
        # following a capitalized convention (see below).
        args = CommandArgs()
        args.add_argument("object", optional=True)

        async def run(self, args):
            '''The command is executed.'''
            await self.msg("Ho, look!")
    ```

    If the `category` and/or `permissions` class variable aren't
    provided, they're automatically searched in the module and
    parent packages.  A `CATEGORY` variable is searched in this module
    and, if not found, in parent packages recursively.  Similarly,
    a `PERMISSIONS` variable is searched in the module and parent
    packages.  This system allows to easily organize commands.

    For instance, assuming you want to create commands that only the
    administrators should use:  you could create a directory called
    'admin' inside of the 'command' package.  In this new directory,
    add an `__init__.py` file.  Inside write the following:

    ```python
    CATEGORY = "Admin commands"
    PERMISSIONS = "admin"
    ```

    Then you can create a file per command as usual.  For instance,
    create a file 'services.py' that contains the following code:

    ```python
    from command import Command, CommandArgs

    class Services(Command):

        '''Command services, to look for active services in the game.'''

        # name will be 'services'
        # ...
    ```

    Because we haven't defined a `category` class variable, a
    `CATEGORY` top-level variable will be searched in the module
    itself.  Since we haven't defined one, it will be searched in the
    parent package (that is, in our 'command/admin/__init.py' file).
    In this file is our `CATEGORY` variable, so the 'services'
    command will have its category set to 'Admin commands'.  The same
    process applies for permissions.  Notice that you can always
    override this behavior should the need arise, but it makes
    organization much easier.

    """

    #name = "command name"
    #category = "command category"
    #permissions = "command permissions"
    args = CommandArgs()
    seps = " "
    alias = ()

    def __init__(self, character=None, sep=None, arguments=""):
        self.character = character
        self.sep = sep
        self.arguments = arguments

    @property
    def session(self):
        return self.character and self.character.session or None

    @property
    def db(self):
        """Return the attribute handler for command storage."""
        if (handler := getattr(self, "cached_db_handler", None)):
            return handler

        from data.handlers import AttributeHandler
        if self.character is None:
            raise ValueError(
                "the character is not set, can't access attributes")

        handler = AttributeHandler(self.character)
        handler.subset = f"cmd.{self.layer}.{self.name}"
        self.cached_db_handler = handler
        return handler
示例#6
0
 def new_parser(self):
     """Simply return an empty command argument parser."""
     return CommandArgs()
示例#7
0
class Command:
    """
    Base class for commands.

    Static commands are mostly defined and dynamically imported
    from the file system, from the 'command' directory and its
    sub-directories.

    A static command can have a name, inferred from the class
    name if not provided.  It can also have aliases, a command
    category and basic command permissions.  It's help entry is
    inferred from the command's docstring.

    Here's a very basic example.  Assuming you want to add the 'look'
    command, just create a file with the '.py' extension in the command
    package (it can be in a sub-package, for the sake of organization).

    ```python
    from command import Command, CommandArgs

    class Look(Command): # 'look' (lowercase class name) will be the name

        '''
        Look command, to look around you.

        Syntax:
            look [object]

        This command is used to look around you or look a specific object
        more in details.

        '''

        # If you're not happy with the default name, just provide a
        # class variable 'name' and set the command name to it.  You can
        # define the category in the 'category' class variable as a
        # string, and the command permissions, still as a string, in the
        # 'permissions' class variable.  If they're not defined,
        # these last two values will be searched in parent packages
        # following a capitalized convention (see below).
        args = CommandArgs()
        args.add_argument("object", optional=True)

        async def call(self, args):
            '''The command is executed.'''
            await self.msg("Ho, look!")
    ```

    If the `category` and/or `permissions` class variable aren't
    provided, they're automatically searched in the module and
    parent packages.  A `CATEGORY` variable is searched in this module
    and, if not found, in parent packages recursively.  Similarly,
    a `PERMISSIONS` variable is searched in the module and parent
    packages.  This system allows to easily organize commands.

    For instance, assuming you want to create commands that only the
    administrators should use:  you could create a directory called
    'admin' inside of the 'command' package.  In this new directory,
    add an `__init__.py` file.  Inside write the following:

    ```python
    CATEGORY = "Admin commands"
    PERMISSIONS = "admin"
    ```

    Then you can create a file per command as usual.  For instance,
    create a file 'services.py' that contains the following code:

    ```python
    from command import Command, CommandArgs

    class Services(Command):

        '''Command services, to look for active services in the game.'''

        # name will be 'services'
        # ...
    ```

    Becasue we haven't defined a `category` class variable, a
    `CATEGORY` top-level variable will be searched in the module
    itself.  Since we haven't defined one, it will be searched in the
    parent package (that is, in our 'command/admin/__init.py' file).
    In this file is our `CATEGORY` variable, so the 'services'
    command will have its category set to 'Admin commands'.  The same
    process applies for permissions.  Notice that you can always
    override this behavior should the need arise, but it makes
    organization much easier.

    """

    #name = "command name"
    #category = "command category"
    #permissions = "command permissions"
    args = CommandArgs()
    seps = " "

    def __init__(self, character=None, sep=None, arguments=""):
        self.character = character
        self.sep = sep
        self.arguments = arguments

    @property
    def session(self):
        return self.character and self.character.session or None

    @classmethod
    def can_run(cls, character) -> bool:
        """
        Can the command be run by the specified character?

        Args:
            character (Character): the character to run this command.

        By default, check the command's permissions.  You can,
        however, override this method in individual commands
        to perform other checks.

        Returns:
            can_run (bool): whether this character can run this command.

        """
        if cls.permissions:
            return character.permissions.has(cls.permissions)

        return True

    def parse(self):
        """Parse the command, returning the namespace or an error."""
        return type(self).args.parse(self.arguments)

    async def run(self, args):
        """
        Run the command with the provided arguments.

        The command arguments are first parsed through the command
        argument parser (see `command/args.py`), so that this method
        is only called if arguments are parsed correctly.

        Args:
            args (namespace): The correctly parsed arguments.

        """
        await self.msg(f"Great!  You reached the dcommand {self.name}.")

    async def msg(self, text: str):
        """
        Send the message to the character running the command.

        Args:
            text (str): text to send to the character.

        """
        await self.character.msg(text)

    @classmethod
    def extrapolate(cls, path: Path):
        """
        Extrapolate name, category and permissions if not set.

        This will entail looking into parent modules if needed.

        Args:
            path (pathlib.Path): path leading to the command.

        """
        # Try to find the command name
        if not hasattr(cls, "name"):
            cls.name = cls.__name__.lower()

        # Try to find the command category
        if not hasattr(cls, "category"):
            cls.category = cls._explore_for(path,
                                            "CATEGORY",
                                            default="General")

        # Look for the command permissions
        if not hasattr(cls, "permissions"):
            cls.permissions = cls._explore_for(path, "PERMISSIONS", default="")

        return cls

    @staticmethod
    def _explore_for(path: Path, name: str, default: Any):
        """Explore for the given variable name."""
        pypath = ".".join(path.parts)[:-3]
        module = import_module(pypath)
        value = getattr(module, name, NOT_SET)
        if value is not NOT_SET:
            return value

        # Import parent directories
        rindex = 1
        while rindex < len(path.parts) - 1:
            pypath = ".".join(path.parts[:-rindex])
            module = import_module(pypath)
            value = getattr(module, name, NOT_SET)
            if value is not NOT_SET:
                return value
            rindex += 1

        return default