Philipp Hörist pushed to branch master at gajim / gajim

Commits:

14 changed files:

Changes:

  • gajim/command_system/__init__.py deleted
    1
    -# Copyright (C) 2009-2010  Alexander Cherniuk <ts33kr@gmail.com>
    
    2
    -#
    
    3
    -# This program is free software: you can redistribute it and/or modify
    
    4
    -# it under the terms of the GNU General Public License as published by
    
    5
    -# the Free Software Foundation, either version 3 of the License, or
    
    6
    -# (at your option) any later version.
    
    7
    -#
    
    8
    -# This program is distributed in the hope that it will be useful,
    
    9
    -# but WITHOUT ANY WARRANTY; without even the implied warranty of
    
    10
    -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    
    11
    -# GNU General Public License for more details.
    
    12
    -#
    
    13
    -# You should have received a copy of the GNU General Public License
    
    14
    -# along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
    15
    -
    
    16
    -"""
    
    17
    -The command system providing scalable, clean and convenient architecture
    
    18
    -in combination with declarative way of defining commands and a fair
    
    19
    -amount of automatization for routine processes.
    
    20
    -"""

  • gajim/command_system/dispatcher.py deleted
    1
    -# Copyright (c) 2010, Alexander Cherniuk (ts33kr@gmail.com)
    
    2
    -# All rights reserved.
    
    3
    -#
    
    4
    -# Redistribution and use in source and binary forms, with or without
    
    5
    -# modification, are permitted provided that the following conditions
    
    6
    -# are met:
    
    7
    -#
    
    8
    -# * Redistributions of source code must retain the above copyright
    
    9
    -#   notice, this list of conditions and the following disclaimer.
    
    10
    -#
    
    11
    -# * Redistributions in binary form must reproduce the above copyright
    
    12
    -#   notice, this list of conditions and the following disclaimer in the
    
    13
    -#   documentation and/or other materials provided with the distribution.
    
    14
    -#
    
    15
    -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    
    16
    -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    
    17
    -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    
    18
    -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    
    19
    -# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    
    20
    -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    
    21
    -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    
    22
    -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    
    23
    -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    
    24
    -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    
    25
    -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    
    26
    -
    
    27
    -"""
    
    28
    -Backbone of the command system. Provides smart and controllable
    
    29
    -dispatching mechanism with an auto-discovery functionality. In addition
    
    30
    -to automatic discovery and dispatching, also features manual control
    
    31
    -over the process.
    
    32
    -"""
    
    33
    -
    
    34
    -from typing import Any
    
    35
    -
    
    36
    -from gajim.command_system.tools import remove
    
    37
    -
    
    38
    -COMMANDS: dict[Any, Any] = {}
    
    39
    -CONTAINERS: dict[Any, Any] = {}
    
    40
    -
    
    41
    -
    
    42
    -def add_host(host):
    
    43
    -    CONTAINERS[host] = []
    
    44
    -
    
    45
    -
    
    46
    -def remove_host(host):
    
    47
    -    remove(CONTAINERS, host)
    
    48
    -
    
    49
    -
    
    50
    -def add_container(container):
    
    51
    -    for host in container.HOSTS:
    
    52
    -        CONTAINERS[host].append(container)
    
    53
    -
    
    54
    -
    
    55
    -def remove_container(container):
    
    56
    -    for host in container.HOSTS:
    
    57
    -        remove(CONTAINERS[host], container)
    
    58
    -
    
    59
    -
    
    60
    -def add_commands(container):
    
    61
    -    commands = COMMANDS.setdefault(container, {})
    
    62
    -    for command in traverse_commands(container):
    
    63
    -        for name in command.names:
    
    64
    -            commands[name] = command
    
    65
    -
    
    66
    -
    
    67
    -def remove_commands(container):
    
    68
    -    remove(COMMANDS, container)
    
    69
    -
    
    70
    -
    
    71
    -def traverse_commands(container):
    
    72
    -    for name in dir(container):
    
    73
    -        attribute = getattr(container, name)
    
    74
    -        if is_command(attribute):
    
    75
    -            yield attribute
    
    76
    -
    
    77
    -
    
    78
    -def is_command(attribute):
    
    79
    -    from gajim.command_system.framework import Command
    
    80
    -    return isinstance(attribute, Command)
    
    81
    -
    
    82
    -
    
    83
    -def is_root(namespace):
    
    84
    -    metaclass = namespace.get("__metaclass__", None)
    
    85
    -    if not metaclass:
    
    86
    -        return False
    
    87
    -    return issubclass(metaclass, Dispatchable)
    
    88
    -
    
    89
    -
    
    90
    -def get_command(host, name):
    
    91
    -    for container in CONTAINERS[host]:
    
    92
    -        command = COMMANDS[container].get(name)
    
    93
    -        if command:
    
    94
    -            return command
    
    95
    -
    
    96
    -
    
    97
    -def list_commands(host):
    
    98
    -    for container in CONTAINERS[host]:
    
    99
    -        commands = COMMANDS[container]
    
    100
    -        for name, command in commands.items():
    
    101
    -            yield name, command
    
    102
    -
    
    103
    -
    
    104
    -class Dispatchable(type):
    
    105
    -    # pylint: disable=no-value-for-parameter
    
    106
    -    def __init__(cls, name, bases, namespace):
    
    107
    -        parents = super(Dispatchable, cls)
    
    108
    -        parents.__init__(name, bases, namespace)
    
    109
    -        if not is_root(namespace):
    
    110
    -            cls.dispatch()
    
    111
    -
    
    112
    -    def dispatch(cls):
    
    113
    -        if cls.AUTOMATIC:
    
    114
    -            cls.enable()
    
    115
    -
    
    116
    -
    
    117
    -class Host(Dispatchable):
    
    118
    -
    
    119
    -    def enable(cls):
    
    120
    -        add_host(cls)
    
    121
    -
    
    122
    -    def disable(cls):
    
    123
    -        remove_host(cls)
    
    124
    -
    
    125
    -
    
    126
    -class Container(Dispatchable):
    
    127
    -
    
    128
    -    def enable(cls):
    
    129
    -        add_container(cls)
    
    130
    -        add_commands(cls)
    
    131
    -
    
    132
    -    def disable(cls):
    
    133
    -        remove_commands(cls)
    
    134
    -        remove_container(cls)

  • gajim/command_system/errors.py deleted
    1
    -# Copyright (C) 2009-2010  Alexander Cherniuk <ts33kr@gmail.com>
    
    2
    -#
    
    3
    -# This program is free software: you can redistribute it and/or modify
    
    4
    -# it under the terms of the GNU General Public License as published by
    
    5
    -# the Free Software Foundation, either version 3 of the License, or
    
    6
    -# (at your option) any later version.
    
    7
    -#
    
    8
    -# This program is distributed in the hope that it will be useful,
    
    9
    -# but WITHOUT ANY WARRANTY; without even the implied warranty of
    
    10
    -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    
    11
    -# GNU General Public License for more details.
    
    12
    -#
    
    13
    -# You should have received a copy of the GNU General Public License
    
    14
    -# along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
    15
    -
    
    16
    -
    
    17
    -class BaseError(Exception):
    
    18
    -    """
    
    19
    -    Common base for errors which relate to a specific command.
    
    20
    -    Encapsulates everything needed to identify a command, by either its
    
    21
    -    object or name.
    
    22
    -    """
    
    23
    -
    
    24
    -    def __init__(self, message, command=None, name=None):
    
    25
    -        self.message = message
    
    26
    -
    
    27
    -        self.command = command
    
    28
    -        self.name = name
    
    29
    -
    
    30
    -        if command and not name:
    
    31
    -            self.name = command.first_name
    
    32
    -
    
    33
    -        super(BaseError, self).__init__()
    
    34
    -
    
    35
    -    def __str__(self):
    
    36
    -        return self.message
    
    37
    -
    
    38
    -
    
    39
    -class DefinitionError(BaseError):
    
    40
    -    """
    
    41
    -    Used to indicate errors occurred on command definition.
    
    42
    -    """
    
    43
    -
    
    44
    -
    
    45
    -class CommandError(BaseError):
    
    46
    -    """
    
    47
    -    Used to indicate errors occurred during command execution.
    
    48
    -    """
    
    49
    -
    
    50
    -
    
    51
    -class NoCommandError(BaseError):
    
    52
    -    """
    
    53
    -    Used to indicate an inability to find the specified command.
    
    54
    -    """

  • gajim/command_system/framework.py deleted
    1
    -# Copyright (C) 2009-2010  Alexander Cherniuk <ts33kr@gmail.com>
    
    2
    -#
    
    3
    -# This program is free software: you can redistribute it and/or modify
    
    4
    -# it under the terms of the GNU General Public License as published by
    
    5
    -# the Free Software Foundation, either version 3 of the License, or
    
    6
    -# (at your option) any later version.
    
    7
    -#
    
    8
    -# This program is distributed in the hope that it will be useful,
    
    9
    -# but WITHOUT ANY WARRANTY; without even the implied warranty of
    
    10
    -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    
    11
    -# GNU General Public License for more details.
    
    12
    -#
    
    13
    -# You should have received a copy of the GNU General Public License
    
    14
    -# along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
    15
    -
    
    16
    -"""
    
    17
    -Provides a tiny framework with simple, yet powerful and extensible
    
    18
    -architecture to implement commands in a straight and flexible,
    
    19
    -declarative way.
    
    20
    -"""
    
    21
    -
    
    22
    -from types import FunctionType
    
    23
    -from inspect import getfullargspec
    
    24
    -from inspect import getdoc
    
    25
    -
    
    26
    -from gajim.command_system.dispatcher import Host
    
    27
    -from gajim.command_system.dispatcher import Container
    
    28
    -from gajim.command_system.dispatcher import get_command
    
    29
    -from gajim.command_system.dispatcher import list_commands
    
    30
    -from gajim.command_system.mapping import parse_arguments
    
    31
    -from gajim.command_system.mapping import adapt_arguments
    
    32
    -from gajim.command_system.errors import DefinitionError
    
    33
    -from gajim.command_system.errors import CommandError
    
    34
    -from gajim.command_system.errors import NoCommandError
    
    35
    -
    
    36
    -
    
    37
    -class CommandHost(metaclass=Host):
    
    38
    -    """
    
    39
    -    Command host is a hub between numerous command processors and
    
    40
    -    command containers. Aimed to participate in a dispatching process in
    
    41
    -    order to provide clean and transparent architecture.
    
    42
    -
    
    43
    -    The AUTOMATIC class variable, which must be defined by a command
    
    44
    -    host, specifies whether the command host should be automatically
    
    45
    -    dispatched and enabled by the dispatcher or not.
    
    46
    -    """
    
    47
    -    __metaclass__ = Host
    
    48
    -
    
    49
    -
    
    50
    -class CommandContainer(metaclass=Container):
    
    51
    -    """
    
    52
    -    Command container is an entity which holds defined commands,
    
    53
    -    allowing them to be dispatched and processed correctly. Each
    
    54
    -    command container may be bound to a one or more command hosts.
    
    55
    -
    
    56
    -    The AUTOMATIC class variable, which must be defined by a command
    
    57
    -    processor, specifies whether the command processor should be
    
    58
    -    automatically dispatched and enabled by the dispatcher or not.
    
    59
    -
    
    60
    -    Bounding is controlled by the HOSTS class variable, which must be
    
    61
    -    defined by the command container. This variable should contain a
    
    62
    -    sequence of hosts to bound to, as a tuple or list.
    
    63
    -    """
    
    64
    -    __metaclass__ = Container
    
    65
    -
    
    66
    -
    
    67
    -class CommandProcessor:
    
    68
    -    """
    
    69
    -    Command processor is an immediate command emitter. It does not
    
    70
    -    participate in the dispatching process directly, but must define a
    
    71
    -    host to bound to.
    
    72
    -
    
    73
    -    Bounding is controlled by the COMMAND_HOST variable, which must be
    
    74
    -    defined in the body of the command processor. This variable should
    
    75
    -    be set to a specific command host.
    
    76
    -    """
    
    77
    -
    
    78
    -    # This defines a command prefix (or an initializer), which should
    
    79
    -    # precede a text in order for it to be processed as a command.
    
    80
    -    COMMAND_PREFIX = '/'
    
    81
    -
    
    82
    -    def process_as_command(self, text):
    
    83
    -        """
    
    84
    -        Try to process text as a command. Returns True if it has been
    
    85
    -        processed as a command and False otherwise.
    
    86
    -        """
    
    87
    -        # pylint: disable=assignment-from-no-return
    
    88
    -        prefix = text.startswith(self.COMMAND_PREFIX)
    
    89
    -        length = len(text) > len(self.COMMAND_PREFIX)
    
    90
    -        if not (prefix and length):
    
    91
    -            return False
    
    92
    -
    
    93
    -        body = text[len(self.COMMAND_PREFIX):]
    
    94
    -        body = body.strip()
    
    95
    -
    
    96
    -        parts = body.split(None, 1)
    
    97
    -        name, arguments = parts if len(parts) > 1 else (parts[0], None)
    
    98
    -
    
    99
    -        flag = self.looks_like_command(text, body, name, arguments)
    
    100
    -        if flag is not None:
    
    101
    -            return flag
    
    102
    -
    
    103
    -        self.execute_command(name, arguments)
    
    104
    -
    
    105
    -        return True
    
    106
    -
    
    107
    -    def execute_command(self, name, arguments):
    
    108
    -        cmd = self.get_command(name)
    
    109
    -
    
    110
    -        args, opts = parse_arguments(arguments) if arguments else ([], [])
    
    111
    -        args, kwargs = adapt_arguments(cmd, arguments, args, opts)
    
    112
    -
    
    113
    -        if self.command_preprocessor(cmd, name, arguments, args, kwargs):
    
    114
    -            return
    
    115
    -        value = cmd(self, *args, **kwargs)
    
    116
    -        self.command_postprocessor(cmd, name, arguments, args, kwargs, value)
    
    117
    -
    
    118
    -    def command_preprocessor(self, cmd, name, arguments, args, kwargs):
    
    119
    -        """
    
    120
    -        Redefine this method in the subclass to execute custom code
    
    121
    -        before command gets executed.
    
    122
    -
    
    123
    -        If returns True then command execution will be interrupted and
    
    124
    -        command will not be executed.
    
    125
    -        """
    
    126
    -
    
    127
    -    def command_postprocessor(self, cmd, name, arguments, args, kwargs, value):
    
    128
    -        """
    
    129
    -        Redefine this method in the subclass to execute custom code
    
    130
    -        after command gets executed.
    
    131
    -        """
    
    132
    -
    
    133
    -    def looks_like_command(self, text, body, name, arguments):
    
    134
    -        """
    
    135
    -        This hook is being called before any processing, but after it
    
    136
    -        was determined that text looks like a command.
    
    137
    -
    
    138
    -        If returns value other then None - then further processing will
    
    139
    -        be interrupted and that value will be used to return from
    
    140
    -        process_as_command.
    
    141
    -        """
    
    142
    -
    
    143
    -    def get_command(self, name):
    
    144
    -        cmd = get_command(self.COMMAND_HOST, name)
    
    145
    -        if not cmd:
    
    146
    -            raise NoCommandError("Command does not exist", name=name)
    
    147
    -        return cmd
    
    148
    -
    
    149
    -    def list_commands(self):
    
    150
    -        commands = list_commands(self.COMMAND_HOST)
    
    151
    -        commands = dict(commands)
    
    152
    -        return sorted(set(commands.values()), key=repr)
    
    153
    -
    
    154
    -
    
    155
    -class Command:
    
    156
    -
    
    157
    -    def __init__(self, handler, *names, **properties):
    
    158
    -        self.handler = handler
    
    159
    -        self.names = names
    
    160
    -
    
    161
    -        # Automatically set all the properties passed to a constructor
    
    162
    -        # by the command decorator.
    
    163
    -        for key, value in properties.items():
    
    164
    -            setattr(self, key, value)
    
    165
    -
    
    166
    -    def __call__(self, *args, **kwargs):
    
    167
    -        try:
    
    168
    -            return self.handler(*args, **kwargs)
    
    169
    -
    
    170
    -        # This allows to use a shortcut way of raising an exception
    
    171
    -        # inside a handler. That is to raise a CommandError without
    
    172
    -        # command or name attributes set. They will be set to a
    
    173
    -        # corresponding values right here in case if they was not set by
    
    174
    -        # the one who raised an exception.
    
    175
    -        except CommandError as error:
    
    176
    -            if not error.command and not error.name:
    
    177
    -                raise CommandError(error.message, self)
    
    178
    -            raise
    
    179
    -
    
    180
    -        # This one is a little bit too wide, but as Python does not have
    
    181
    -        # anything more constrained - there is no other choice. Take a
    
    182
    -        # look here if command complains about invalid arguments while
    
    183
    -        # they are ok.
    
    184
    -        except TypeError:
    
    185
    -            raise CommandError("Command received invalid arguments", self)
    
    186
    -
    
    187
    -    def __repr__(self):
    
    188
    -        return "<Command %s>" % ', '.join(self.names)
    
    189
    -
    
    190
    -    def __cmp__(self, other):
    
    191
    -        if self.first_name > other.first_name:
    
    192
    -            return 1
    
    193
    -        if self.first_name < other.first_name:
    
    194
    -            return -1
    
    195
    -        return 0
    
    196
    -
    
    197
    -    @property
    
    198
    -    def first_name(self):
    
    199
    -        return self.names[0]
    
    200
    -
    
    201
    -    @property
    
    202
    -    def native_name(self):
    
    203
    -        return self.handler.__name__
    
    204
    -
    
    205
    -    def extract_documentation(self):
    
    206
    -        """
    
    207
    -        Extract handler's documentation which is a doc-string and
    
    208
    -        transform it to a usable format.
    
    209
    -        """
    
    210
    -        return getdoc(self.handler)
    
    211
    -
    
    212
    -    def extract_description(self):
    
    213
    -        """
    
    214
    -        Extract handler's description (which is a first line of the
    
    215
    -        documentation). Try to keep them simple yet meaningful.
    
    216
    -        """
    
    217
    -        documentation = self.extract_documentation()
    
    218
    -        return documentation.split('\n', 1)[0] if documentation else None
    
    219
    -
    
    220
    -    def extract_specification(self):
    
    221
    -        """
    
    222
    -        Extract handler's arguments specification, as it was defined
    
    223
    -        preserving their order.
    
    224
    -        """
    
    225
    -        names, var_args, var_kwargs, defaults, _, _, _ = getfullargspec(
    
    226
    -            self.handler)
    
    227
    -
    
    228
    -        # Behavior of this code need to be checked. Might yield
    
    229
    -        # incorrect results on some rare occasions.
    
    230
    -        spec_args = names[:-len(defaults) if defaults else len(names)]
    
    231
    -        spec_kwargs = list(
    
    232
    -            zip(names[-len(defaults):], defaults)) if defaults else {}
    
    233
    -
    
    234
    -        # Removing self from arguments specification. Command handler
    
    235
    -        # should receive the processors as a first argument, which
    
    236
    -        # should be self by the canonical means.
    
    237
    -        if spec_args.pop(0) != 'self':
    
    238
    -            raise DefinitionError("First argument must be self", self)
    
    239
    -
    
    240
    -        return spec_args, spec_kwargs, var_args, var_kwargs
    
    241
    -
    
    242
    -
    
    243
    -def command(*names, **properties):
    
    244
    -    """
    
    245
    -    A decorator for defining commands in a declarative way. Provides
    
    246
    -    facilities for setting command's names and properties.
    
    247
    -
    
    248
    -    Names should contain a set of names (aliases) by which the command
    
    249
    -    can be reached. If no names are given - the native name (the one
    
    250
    -    extracted from the command handler) will be used.
    
    251
    -
    
    252
    -    If native=True is given (default) and names is non-empty - then the
    
    253
    -    native name of the command will be prepended in addition to the
    
    254
    -    given names.
    
    255
    -
    
    256
    -    If usage=True is given (default) - then command help will be
    
    257
    -    appended with autogenerated usage info, based of the command handler
    
    258
    -    arguments introspection.
    
    259
    -
    
    260
    -    If source=True is given - then the first argument of the command
    
    261
    -    will receive the source arguments, as a raw, unprocessed string. The
    
    262
    -    further mapping of arguments and options will not be affected.
    
    263
    -
    
    264
    -    If raw=True is given - then command considered to be raw and should
    
    265
    -    define positional arguments only. If it defines only one positional
    
    266
    -    argument - this argument will receive all the raw and unprocessed
    
    267
    -    arguments. If the command defines more then one positional argument
    
    268
    -    - then all the arguments except the last one will be processed
    
    269
    -    normally; the last argument will get what is left after the
    
    270
    -    processing as raw and unprocessed string.
    
    271
    -
    
    272
    -    If empty=True is given - this will allow to call a raw command
    
    273
    -    without arguments.
    
    274
    -
    
    275
    -    If extra=True is given - then all the extra arguments passed to a
    
    276
    -    command will be collected into a sequence and given to the last
    
    277
    -    positional argument.
    
    278
    -
    
    279
    -    If overlap=True is given - then all the extra arguments will be
    
    280
    -    mapped as if they were values for the keyword arguments.
    
    281
    -
    
    282
    -    If expand=True is given (default) - then short, one-letter options
    
    283
    -    will be expanded to a verbose ones, based of the comparison of the
    
    284
    -    first letter. If more then one option with the same first letter is
    
    285
    -    given - then only first one will be used in the expansion.
    
    286
    -    """
    
    287
    -    names = list(names)
    
    288
    -
    
    289
    -    native = properties.get('native', True)
    
    290
    -
    
    291
    -    usage = properties.get('usage', True)
    
    292
    -    source = properties.get('source', False)
    
    293
    -    raw = properties.get('raw', False)
    
    294
    -    empty = properties.get('empty', False)
    
    295
    -    extra = properties.get('extra', False)
    
    296
    -    overlap = properties.get('overlap', False)
    
    297
    -    expand = properties.get('expand', True)
    
    298
    -
    
    299
    -    if empty and not raw:
    
    300
    -        raise DefinitionError("Empty option can be used only with raw commands")
    
    301
    -
    
    302
    -    if extra and overlap:
    
    303
    -        raise DefinitionError("Extra and overlap options can not be used "
    
    304
    -                              "together")
    
    305
    -
    
    306
    -    properties = {
    
    307
    -        'usage': usage,
    
    308
    -        'source': source,
    
    309
    -        'raw': raw,
    
    310
    -        'extra': extra,
    
    311
    -        'overlap': overlap,
    
    312
    -        'empty': empty,
    
    313
    -        'expand': expand
    
    314
    -    }
    
    315
    -
    
    316
    -    def decorator(handler):
    
    317
    -        """
    
    318
    -        Decorator which receives handler as a first argument and then
    
    319
    -        wraps it in the command which then returns back.
    
    320
    -        """
    
    321
    -        cmd = Command(handler, *names, **properties)
    
    322
    -
    
    323
    -        # Extract and inject a native name if either no other names are
    
    324
    -        # specified or native property is enabled, while making
    
    325
    -        # sure it is going to be the first one in the list.
    
    326
    -        if not names or native:
    
    327
    -            names.insert(0, cmd.native_name)
    
    328
    -            cmd.names = tuple(names)
    
    329
    -
    
    330
    -        return cmd
    
    331
    -
    
    332
    -    # Workaround if we are getting called without parameters. Keep in
    
    333
    -    # mind that in that case - first item in the names will be the
    
    334
    -    # handler.
    
    335
    -    if names and isinstance(names[0], FunctionType):
    
    336
    -        return decorator(names.pop(0))
    
    337
    -
    
    338
    -    return decorator
    
    339
    -
    
    340
    -
    
    341
    -def doc(text):
    
    342
    -    """
    
    343
    -    This decorator is used to bind a documentation (a help) to a
    
    344
    -    command.
    
    345
    -    """
    
    346
    -    def decorator(target):
    
    347
    -        if isinstance(target, Command):
    
    348
    -            target.handler.__doc__ = text
    
    349
    -        else:
    
    350
    -            target.__doc__ = text
    
    351
    -        return target
    
    352
    -
    
    353
    -    return decorator

  • gajim/command_system/implementation/__init__.py deleted
    1
    -# Copyright (C) 2009-2010  Alexander Cherniuk <ts33kr@gmail.com>
    
    2
    -#
    
    3
    -# This program is free software: you can redistribute it and/or modify
    
    4
    -# it under the terms of the GNU General Public License as published by
    
    5
    -# the Free Software Foundation, either version 3 of the License, or
    
    6
    -# (at your option) any later version.
    
    7
    -#
    
    8
    -# This program is distributed in the hope that it will be useful,
    
    9
    -# but WITHOUT ANY WARRANTY; without even the implied warranty of
    
    10
    -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    
    11
    -# GNU General Public License for more details.
    
    12
    -#
    
    13
    -# You should have received a copy of the GNU General Public License
    
    14
    -# along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
    15
    -
    
    16
    -"""
    
    17
    -The implementation and auxiliary systems which implement the standard
    
    18
    -Gajim commands and also provide an infrastructure for adding custom
    
    19
    -commands.
    
    20
    -"""

  • gajim/command_system/implementation/custom.py deleted
    1
    -# Copyright (c) 2009-2010, Alexander Cherniuk (ts33kr@gmail.com)
    
    2
    -# All rights reserved.
    
    3
    -#
    
    4
    -# Redistribution and use in source and binary forms, with or without
    
    5
    -# modification, are permitted provided that the following conditions
    
    6
    -# are met:
    
    7
    -#
    
    8
    -# * Redistributions of source code must retain the above copyright
    
    9
    -#   notice, this list of conditions and the following disclaimer.
    
    10
    -#
    
    11
    -# * Redistributions in binary form must reproduce the above copyright
    
    12
    -#   notice, this list of conditions and the following disclaimer in the
    
    13
    -#   documentation and/or other materials provided with the distribution.
    
    14
    -#
    
    15
    -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    
    16
    -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    
    17
    -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    
    18
    -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    
    19
    -# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    
    20
    -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    
    21
    -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    
    22
    -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    
    23
    -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    
    24
    -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    
    25
    -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    
    26
    -
    
    27
    -"""
    
    28
    -This module contains examples of how to create your own commands, by
    
    29
    -creating a new command container, bounded to a specific command host,
    
    30
    -and defining a set of commands inside of it.
    
    31
    -
    
    32
    -Keep in mind that this module is not being loaded from anywhere, so the
    
    33
    -code in here will not be executed and commands defined here will not be
    
    34
    -detected.
    
    35
    -"""
    
    36
    -
    
    37
    -from gajim.common.i18n import _
    
    38
    -from gajim.command_system.framework import CommandContainer
    
    39
    -from gajim.command_system.framework import command
    
    40
    -from gajim.command_system.framework import doc
    
    41
    -from gajim.command_system.implementation.hosts import ChatCommands
    
    42
    -from gajim.command_system.implementation.hosts import PrivateChatCommands
    
    43
    -from gajim.command_system.implementation.hosts import GroupChatCommands
    
    44
    -
    
    45
    -
    
    46
    -class CustomCommonCommands(CommandContainer):
    
    47
    -    """
    
    48
    -    The AUTOMATIC class variable, set to a positive value, instructs the
    
    49
    -    command system to automatically discover the command container and
    
    50
    -    enable it.
    
    51
    -
    
    52
    -    This command container bounds to all three available in the default
    
    53
    -    implementation command hosts. This means that commands defined in
    
    54
    -    this container will be available to all: chat, private chat and a
    
    55
    -    group chat.
    
    56
    -    """
    
    57
    -
    
    58
    -    AUTOMATIC = True
    
    59
    -    HOSTS = ChatCommands, PrivateChatCommands, GroupChatCommands
    
    60
    -
    
    61
    -    @command
    
    62
    -    def dance(self):
    
    63
    -        """
    
    64
    -        First line of the doc string is called a description and will be
    
    65
    -        programmatically extracted and formatted.
    
    66
    -
    
    67
    -        After that you can give more help, like explanation of the
    
    68
    -        options. This one will be programmatically extracted and
    
    69
    -        formatted too.
    
    70
    -
    
    71
    -        After all the documentation - there will be autogenerated (based
    
    72
    -        on the method signature) usage information appended. You can
    
    73
    -        turn it off, if you want.
    
    74
    -        """
    
    75
    -        return "I don't dance."
    
    76
    -
    
    77
    -
    
    78
    -class CustomChatCommands(CommandContainer):
    
    79
    -    """
    
    80
    -    This command container bounds only to the ChatCommands command host.
    
    81
    -    Therefore commands defined inside of the container will be available
    
    82
    -    only to a chat.
    
    83
    -    """
    
    84
    -
    
    85
    -    AUTOMATIC = True
    
    86
    -    HOSTS = (ChatCommands,)
    
    87
    -
    
    88
    -    @command("squal", "bawl")
    
    89
    -    def sing(self):
    
    90
    -        """
    
    91
    -        This command has an additional aliases. It means the command will
    
    92
    -        be available under three names: sing (the native name), squal
    
    93
    -        (the first alias), bawl (the second alias).
    
    94
    -
    
    95
    -        You can turn off the usage of the native name, if you want, and
    
    96
    -        specify a name or a set of names, as aliases, under which a
    
    97
    -        command will be available.
    
    98
    -        """
    
    99
    -        return "Buy yourself a stereo."
    
    100
    -
    
    101
    -
    
    102
    -class CustomPrivateChatCommands(CommandContainer):
    
    103
    -    """
    
    104
    -    This command container bounds only to the PrivateChatCommands
    
    105
    -    command host. Therefore commands defined inside of the container
    
    106
    -    will be available only to a private chat.
    
    107
    -    """
    
    108
    -
    
    109
    -    AUTOMATIC = True
    
    110
    -    HOSTS = (PrivateChatCommands,)
    
    111
    -
    
    112
    -    @command
    
    113
    -    #Example string. Do not translate
    
    114
    -    @doc(_("The same as using a doc-string, except it supports translation"))
    
    115
    -    def make_coffee(self):
    
    116
    -        return "I'm not a coffee machine!"
    
    117
    -
    
    118
    -
    
    119
    -class CustomGroupChatCommands(CommandContainer):
    
    120
    -    """
    
    121
    -    This command container bounds only to the GroupChatCommands command
    
    122
    -    host. Therefore commands defined inside of the container will be
    
    123
    -    available only to a group chat.
    
    124
    -    """
    
    125
    -
    
    126
    -    AUTOMATIC = True
    
    127
    -    HOSTS = (GroupChatCommands,)
    
    128
    -
    
    129
    -    @command
    
    130
    -    def fetch(self):
    
    131
    -        return "Buy yourself a dog."

  • gajim/command_system/implementation/execute.py deleted
    1
    -# Copyright (c) 2010, Alexander Cherniuk (ts33kr@gmail.com)
    
    2
    -# All rights reserved.
    
    3
    -#
    
    4
    -# Redistribution and use in source and binary forms, with or without
    
    5
    -# modification, are permitted provided that the following conditions
    
    6
    -# are met:
    
    7
    -#
    
    8
    -# * Redistributions of source code must retain the above copyright
    
    9
    -#   notice, this list of conditions and the following disclaimer.
    
    10
    -#
    
    11
    -# * Redistributions in binary form must reproduce the above copyright
    
    12
    -#   notice, this list of conditions and the following disclaimer in the
    
    13
    -#   documentation and/or other materials provided with the distribution.
    
    14
    -#
    
    15
    -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    
    16
    -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    
    17
    -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    
    18
    -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    
    19
    -# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    
    20
    -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    
    21
    -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    
    22
    -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    
    23
    -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    
    24
    -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    
    25
    -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    
    26
    -
    
    27
    -"""
    
    28
    -Provides facilities to safely execute expressions inside a shell process
    
    29
    -and capture the resulting output, in an asynchronous fashion, avoiding
    
    30
    -deadlocks. If the process execution time reaches the threshold - it is
    
    31
    -forced to terminate. Consists of a tiny framework and a couple of
    
    32
    -commands as a frontend.
    
    33
    -"""
    
    34
    -
    
    35
    -from subprocess import Popen, PIPE
    
    36
    -from os.path import expanduser
    
    37
    -
    
    38
    -from gi.repository import GLib
    
    39
    -
    
    40
    -from gajim.common import app
    
    41
    -from gajim.common.i18n import _
    
    42
    -from gajim.command_system.framework import CommandContainer
    
    43
    -from gajim.command_system.framework import command
    
    44
    -from gajim.command_system.framework import doc
    
    45
    -from gajim.command_system.implementation.hosts import ChatCommands
    
    46
    -from gajim.command_system.implementation.hosts import PrivateChatCommands
    
    47
    -from gajim.command_system.implementation.hosts import GroupChatCommands
    
    48
    -
    
    49
    -
    
    50
    -class Execute(CommandContainer):
    
    51
    -    AUTOMATIC = True
    
    52
    -    HOSTS = ChatCommands, PrivateChatCommands, GroupChatCommands
    
    53
    -
    
    54
    -    DIRECTORY = "~"
    
    55
    -
    
    56
    -    POLL_INTERVAL = 100
    
    57
    -    POLL_COUNT = 5
    
    58
    -
    
    59
    -    @command("exec", raw=True)
    
    60
    -    @doc(_("Execute expression inside a shell, show output"))
    
    61
    -    def execute(self, expression):
    
    62
    -        Execute.spawn(self, expression)
    
    63
    -
    
    64
    -    @classmethod
    
    65
    -    def spawn(cls, processor, expression):
    
    66
    -        command_system_execute = app.settings.get('command_system_execute')
    
    67
    -        if command_system_execute:
    
    68
    -            pipes = dict(stdout=PIPE, stderr=PIPE)
    
    69
    -            directory = expanduser(cls.DIRECTORY)
    
    70
    -            popen = Popen(expression, shell=True, cwd=directory, **pipes)
    
    71
    -            cls.monitor(processor, popen)
    
    72
    -        else:
    
    73
    -            processor.echo_error(
    
    74
    -                _('Command disabled. This command can be enabled by '
    
    75
    -                  'setting \'command_system_execute\' to True in ACE '
    
    76
    -                  '(Advanced Configuration Editor).'))
    
    77
    -            return
    
    78
    -
    
    79
    -    @classmethod
    
    80
    -    def monitor(cls, processor, popen):
    
    81
    -        poller = cls.poller(processor, popen)
    
    82
    -        GLib.timeout_add(cls.POLL_INTERVAL, next, poller)
    
    83
    -
    
    84
    -    @classmethod
    
    85
    -    def poller(cls, processor, popen):
    
    86
    -        for _ in range(cls.POLL_COUNT):
    
    87
    -            yield cls.brush(processor, popen)
    
    88
    -        cls.overdue(processor, popen)
    
    89
    -        yield False
    
    90
    -
    
    91
    -    @classmethod
    
    92
    -    def brush(cls, processor, popen):
    
    93
    -        if popen.poll() is not None:
    
    94
    -            cls.terminated(processor, popen)
    
    95
    -            return False
    
    96
    -        return True
    
    97
    -
    
    98
    -    @classmethod
    
    99
    -    def terminated(cls, processor, popen):
    
    100
    -        stdout, stderr = cls.fetch(popen)
    
    101
    -        success = popen.returncode == 0
    
    102
    -        if success and stdout:
    
    103
    -            processor.echo(stdout)
    
    104
    -        elif not success and stderr:
    
    105
    -            processor.echo_error(stderr)
    
    106
    -
    
    107
    -    @classmethod
    
    108
    -    def overdue(cls, processor, popen):
    
    109
    -        popen.terminate()
    
    110
    -
    
    111
    -    @classmethod
    
    112
    -    def fetch(cls, popen):
    
    113
    -        data = popen.communicate()
    
    114
    -        return map(cls.clean, data)
    
    115
    -
    
    116
    -    @staticmethod
    
    117
    -    def clean(text):
    
    118
    -        strip = chr(10) + chr(32)
    
    119
    -        return text.decode().strip(strip)
    
    120
    -
    
    121
    -
    
    122
    -class Show(Execute):
    
    123
    -
    
    124
    -    @command("sh", raw=True)
    
    125
    -    @doc(_("Execute expression inside a shell, send output"))
    
    126
    -    def show(self, expression):
    
    127
    -        Show.spawn(self, expression)
    
    128
    -
    
    129
    -    @classmethod
    
    130
    -    def terminated(cls, processor, popen):
    
    131
    -        stdout, stderr = cls.fetch(popen)
    
    132
    -        success = popen.returncode == 0
    
    133
    -        if success and stdout:
    
    134
    -            processor.send(stdout)
    
    135
    -        elif not success and stderr:
    
    136
    -            processor.echo_error(stderr)

  • gajim/command_system/implementation/hosts.py deleted
    1
    -# Copyright (C) 2009-2010  Alexander Cherniuk <ts33kr@gmail.com>
    
    2
    -#
    
    3
    -# This program is free software: you can redistribute it and/or modify
    
    4
    -# it under the terms of the GNU General Public License as published by
    
    5
    -# the Free Software Foundation, either version 3 of the License, or
    
    6
    -# (at your option) any later version.
    
    7
    -#
    
    8
    -# This program is distributed in the hope that it will be useful,
    
    9
    -# but WITHOUT ANY WARRANTY; without even the implied warranty of
    
    10
    -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    
    11
    -# GNU General Public License for more details.
    
    12
    -#
    
    13
    -# You should have received a copy of the GNU General Public License
    
    14
    -# along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
    15
    -
    
    16
    -"""
    
    17
    -The module defines a set of command hosts, which are bound to a
    
    18
    -different command processors, which are the source of commands.
    
    19
    -"""
    
    20
    -
    
    21
    -from gajim.command_system.framework import CommandHost
    
    22
    -
    
    23
    -
    
    24
    -class ChatCommands(CommandHost):
    
    25
    -    """
    
    26
    -    This command host is bound to the command processor which processes
    
    27
    -    commands from a chat.
    
    28
    -    """
    
    29
    -    AUTOMATIC = True
    
    30
    -
    
    31
    -
    
    32
    -class PrivateChatCommands(CommandHost):
    
    33
    -    """
    
    34
    -    This command host is bound to the command processor which processes
    
    35
    -    commands from a private chat.
    
    36
    -    """
    
    37
    -    AUTOMATIC = True
    
    38
    -
    
    39
    -
    
    40
    -class GroupChatCommands(CommandHost):
    
    41
    -    """
    
    42
    -    This command host is bound to the command processor which processes
    
    43
    -    commands from a group chat.
    
    44
    -    """
    
    45
    -    AUTOMATIC = True

  • gajim/command_system/implementation/middleware.py deleted
    1
    -# Copyright (c) 2009-2010, Alexander Cherniuk (ts33kr@gmail.com)
    
    2
    -# All rights reserved.
    
    3
    -#
    
    4
    -# Redistribution and use in source and binary forms, with or without
    
    5
    -# modification, are permitted provided that the following conditions
    
    6
    -# are met:
    
    7
    -#
    
    8
    -# * Redistributions of source code must retain the above copyright
    
    9
    -#   notice, this list of conditions and the following disclaimer.
    
    10
    -#
    
    11
    -# * Redistributions in binary form must reproduce the above copyright
    
    12
    -#   notice, this list of conditions and the following disclaimer in the
    
    13
    -#   documentation and/or other materials provided with the distribution.
    
    14
    -#
    
    15
    -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    
    16
    -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    
    17
    -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    
    18
    -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    
    19
    -# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    
    20
    -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    
    21
    -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    
    22
    -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    
    23
    -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    
    24
    -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    
    25
    -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    
    26
    -
    
    27
    -"""
    
    28
    -Provides a glue to tie command system framework and the actual code
    
    29
    -where it would be dropped in. Defines a little bit of scaffolding to
    
    30
    -support interaction between the two and a few utility methods so you
    
    31
    -don't need to dig up the code itself to write basic commands.
    
    32
    -"""
    
    33
    -
    
    34
    -from traceback import print_exc
    
    35
    -
    
    36
    -# from gi.repository import Pango
    
    37
    -
    
    38
    -from gajim.common import app
    
    39
    -from gajim.common.i18n import _
    
    40
    -
    
    41
    -from gajim.command_system.framework import CommandProcessor
    
    42
    -from gajim.command_system.errors import CommandError
    
    43
    -from gajim.command_system.errors import NoCommandError
    
    44
    -
    
    45
    -
    
    46
    -class ChatCommandProcessor(CommandProcessor):
    
    47
    -    """
    
    48
    -    A basic scaffolding to provide convenient interaction between the
    
    49
    -    command system and chat controls. It will be merged directly into
    
    50
    -    the controls, by ChatCommandProcessor being among superclasses of
    
    51
    -    the controls.
    
    52
    -    """
    
    53
    -
    
    54
    -    def process_as_command(self, text):
    
    55
    -        self.command_succeeded = False
    
    56
    -        parents = super(ChatCommandProcessor, self)
    
    57
    -        flag = parents.process_as_command(text)
    
    58
    -        if flag and self.command_succeeded:
    
    59
    -            self.clear_input()
    
    60
    -        return flag
    
    61
    -
    
    62
    -    def execute_command(self, name, arguments):
    
    63
    -        try:
    
    64
    -            parents = super(ChatCommandProcessor, self)
    
    65
    -            parents.execute_command(name, arguments)
    
    66
    -        except NoCommandError as error:
    
    67
    -            details = dict(name=error.name, message=error.message)
    
    68
    -            message = "%(name)s: %(message)s\n" % details
    
    69
    -            message += "Try using the //%(name)s or /say /%(name)s " % details
    
    70
    -            message += "construct if you intended to send it as a text."
    
    71
    -            self.echo_error(message)
    
    72
    -        except CommandError as error:
    
    73
    -            self.echo_error("%s: %s" % (error.name, error.message))
    
    74
    -        except Exception:
    
    75
    -            self.echo_error(_("Error during command execution!"))
    
    76
    -            print_exc()
    
    77
    -        else:
    
    78
    -            self.command_succeeded = True
    
    79
    -
    
    80
    -    def looks_like_command(self, text, body, name, arguments):
    
    81
    -        # Command escape stuff goes here. If text was prepended by the
    
    82
    -        # command prefix twice, like //not_a_command (if prefix is set
    
    83
    -        # to /) then it will be escaped, that is sent just as a regular
    
    84
    -        # message with one (only one) prefix removed, so message will be
    
    85
    -        # /not_a_command.
    
    86
    -        if body.startswith(self.COMMAND_PREFIX):
    
    87
    -            self.send(body)
    
    88
    -            return True
    
    89
    -
    
    90
    -    def command_preprocessor(self, command, name, arguments, args, kwargs):
    
    91
    -        # If command argument contain h or help option - forward it to
    
    92
    -        # the /help command. Don't forget to pass self, as all commands
    
    93
    -        # are unbound. And also don't forget to print output.
    
    94
    -        if 'h' in kwargs or 'help' in kwargs:
    
    95
    -            help_ = self.get_command('help')
    
    96
    -            self.echo(help_(self, name))
    
    97
    -            return True
    
    98
    -
    
    99
    -    def command_postprocessor(self, command, name, arguments, args, kwargs,
    
    100
    -                              value):
    
    101
    -        # If command returns a string - print it to a user. A convenient
    
    102
    -        # and sufficient in most simple cases shortcut to a using echo.
    
    103
    -        if value and isinstance(value, str):
    
    104
    -            self.echo(value)
    
    105
    -
    
    106
    -
    
    107
    -class CommandTools:
    
    108
    -    """
    
    109
    -    Contains a set of basic tools and shortcuts you can use in your
    
    110
    -    commands to perform some simple operations. These will be merged
    
    111
    -    directly into the controls, by CommandTools being among superclasses
    
    112
    -    of the controls.
    
    113
    -    """
    
    114
    -
    
    115
    -    def __init__(self):
    
    116
    -        pass
    
    117
    -
    
    118
    -    def echo(self, text, is_error=False):
    
    119
    -        """
    
    120
    -        Print given text to the user, as a regular command output.
    
    121
    -        """
    
    122
    -        self.conversation_view.add_command_output(text, is_error)
    
    123
    -
    
    124
    -    def echo_error(self, text):
    
    125
    -        """
    
    126
    -        Print given text to the user, as an error command output.
    
    127
    -        """
    
    128
    -        self.echo(text, is_error=True)
    
    129
    -
    
    130
    -    def send(self, text):
    
    131
    -        """
    
    132
    -        Send a message to the contact.
    
    133
    -        """
    
    134
    -        self.send_message(text, process_commands=False)
    
    135
    -
    
    136
    -    def set_input(self, text):
    
    137
    -        """
    
    138
    -        Set given text into the input.
    
    139
    -        """
    
    140
    -        buffer = self.msg_textview.get_buffer()
    
    141
    -        buffer.set_text(text)
    
    142
    -
    
    143
    -    def clear_input(self):
    
    144
    -        """
    
    145
    -        Clear input.
    
    146
    -        """
    
    147
    -        self.set_input(str())
    
    148
    -
    
    149
    -    @property
    
    150
    -    def connection(self):
    
    151
    -        """
    
    152
    -        Get the current connection object.
    
    153
    -        """
    
    154
    -        return app.connections[self.account]
    
    155
    -
    
    156
    -    @property
    
    157
    -    def full_jid(self):
    
    158
    -        """
    
    159
    -        Get a full JID of the contact.
    
    160
    -        """
    
    161
    -        return self.contact.jid

  • gajim/command_system/implementation/standard.py deleted
    1
    -# Copyright (C) 2009-2010  Alexander Cherniuk <ts33kr@gmail.com>
    
    2
    -#
    
    3
    -# This program is free software: you can redistribute it and/or modify
    
    4
    -# it under the terms of the GNU General Public License as published by
    
    5
    -# the Free Software Foundation, either version 3 of the License, or
    
    6
    -# (at your option) any later version.
    
    7
    -#
    
    8
    -# This program is distributed in the hope that it will be useful,
    
    9
    -# but WITHOUT ANY WARRANTY; without even the implied warranty of
    
    10
    -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    
    11
    -# GNU General Public License for more details.
    
    12
    -#
    
    13
    -# You should have received a copy of the GNU General Public License
    
    14
    -# along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
    15
    -
    
    16
    -"""
    
    17
    -Provides an actual implementation for the standard commands.
    
    18
    -"""
    
    19
    -
    
    20
    -from time import localtime
    
    21
    -from time import strftime
    
    22
    -from datetime import date
    
    23
    -
    
    24
    -from gi.repository import GLib
    
    25
    -
    
    26
    -from gajim.common import app
    
    27
    -from gajim.common import helpers
    
    28
    -from gajim.common.i18n import _
    
    29
    -from gajim.common.const import KindConstant
    
    30
    -
    
    31
    -from gajim.command_system.errors import CommandError
    
    32
    -from gajim.command_system.framework import CommandContainer
    
    33
    -from gajim.command_system.framework import command
    
    34
    -from gajim.command_system.framework import doc
    
    35
    -from gajim.command_system.mapping import generate_usage
    
    36
    -
    
    37
    -from gajim.command_system.implementation.hosts import ChatCommands
    
    38
    -from gajim.command_system.implementation.hosts import PrivateChatCommands
    
    39
    -from gajim.command_system.implementation.hosts import GroupChatCommands
    
    40
    -
    
    41
    -
    
    42
    -class StandardCommonCommands(CommandContainer):
    
    43
    -    """
    
    44
    -    This command container contains standard commands which are common
    
    45
    -    to all - chat, private chat, group chat.
    
    46
    -    """
    
    47
    -
    
    48
    -    AUTOMATIC = True
    
    49
    -    HOSTS = ChatCommands, PrivateChatCommands, GroupChatCommands
    
    50
    -
    
    51
    -    @command(overlap=True)
    
    52
    -    @doc(_("Show help on a given command or a list of available commands if "
    
    53
    -           "-a is given"))
    
    54
    -    def help(self, cmd=None, all_=False):
    
    55
    -        if cmd:
    
    56
    -            cmd = self.get_command(cmd)
    
    57
    -
    
    58
    -            documentation = _(cmd.extract_documentation())
    
    59
    -            usage = generate_usage(cmd)
    
    60
    -
    
    61
    -            text = []
    
    62
    -
    
    63
    -            if documentation:
    
    64
    -                text.append(documentation)
    
    65
    -            if cmd.usage:
    
    66
    -                text.append(usage)
    
    67
    -
    
    68
    -            return '\n\n'.join(text)
    
    69
    -
    
    70
    -        if all_:
    
    71
    -            for cmd_ in self.list_commands():
    
    72
    -                names = ', '.join(cmd_.names)
    
    73
    -                description = cmd_.extract_description()
    
    74
    -
    
    75
    -                self.echo("%s - %s" % (names, description))
    
    76
    -        else:
    
    77
    -            help_ = self.get_command('help')
    
    78
    -            self.echo(help_(self, 'help'))
    
    79
    -
    
    80
    -    @command(raw=True)
    
    81
    -    @doc(_("Send a message to the contact"))
    
    82
    -    def say(self, message):
    
    83
    -        self.send(message)
    
    84
    -
    
    85
    -    @command(raw=True)
    
    86
    -    @doc(_("Send action (in the third person) to the current chat"))
    
    87
    -    def me(self, action):
    
    88
    -        self.send("/me %s" % action)
    
    89
    -
    
    90
    -    @command('lastlog', overlap=True)
    
    91
    -    @doc(_("Show logged messages which mention given text"))
    
    92
    -    def grep(self, text, limit=None):
    
    93
    -        results = app.storage.archive.search_log(self.account, self.contact.jid, text)
    
    94
    -
    
    95
    -        if not results:
    
    96
    -            raise CommandError(_("%s: Nothing found") % text)
    
    97
    -
    
    98
    -        if limit:
    
    99
    -            try:
    
    100
    -                results = results[len(results) - int(limit):]
    
    101
    -            except ValueError:
    
    102
    -                raise CommandError(_("Limit must be an integer"))
    
    103
    -
    
    104
    -        for row in results:
    
    105
    -            contact = row.contact_name
    
    106
    -            if not contact:
    
    107
    -                if row.kind == KindConstant.CHAT_MSG_SENT:
    
    108
    -                    contact = app.nicks[self.account]
    
    109
    -                else:
    
    110
    -                    contact = self.contact.name
    
    111
    -
    
    112
    -            time_obj = localtime(row.time)
    
    113
    -            date_obj = date.fromtimestamp(row.time)
    
    114
    -            date_ = strftime('%Y-%m-%d', time_obj)
    
    115
    -            time_ = strftime('%H:%M:%S', time_obj)
    
    116
    -
    
    117
    -            if date_obj == date.today():
    
    118
    -                formatted = "[%s] %s: %s" % (time_, contact, row.message)
    
    119
    -            else:
    
    120
    -                formatted = "[%s, %s] %s: %s" % (
    
    121
    -                    date_, time_, contact, row.message)
    
    122
    -
    
    123
    -            self.echo(formatted)
    
    124
    -
    
    125
    -    @command(raw=True, empty=True)
    
    126
    -    # Do not translate online, away, chat, xa, dnd
    
    127
    -    @doc(_("""
    
    128
    -    Set the current status
    
    129
    -
    
    130
    -    Status can be given as one of the following values:
    
    131
    -    online, away, chat, xa, dnd.
    
    132
    -    """))
    
    133
    -    def status(self, status, message):
    
    134
    -        if status not in ('online', 'away', 'chat', 'xa', 'dnd'):
    
    135
    -            raise CommandError("Invalid status given")
    
    136
    -        for connection in app.connections.values():
    
    137
    -            if not app.settings.get_account_setting(connection.name,
    
    138
    -                                                    'sync_with_global_status'):
    
    139
    -                continue
    
    140
    -            if not connection.state.is_available:
    
    141
    -                continue
    
    142
    -            connection.change_status(status, message)
    
    143
    -
    
    144
    -    @command(raw=True, empty=True)
    
    145
    -    @doc(_("Set the current status to away"))
    
    146
    -    def away(self, message):
    
    147
    -        if not message:
    
    148
    -            message = _("Away")
    
    149
    -
    
    150
    -        for connection in app.connections.values():
    
    151
    -            if not app.settings.get_account_setting(connection.name,
    
    152
    -                                                    'sync_with_global_status'):
    
    153
    -                continue
    
    154
    -            if not connection.state.is_available:
    
    155
    -                continue
    
    156
    -            connection.change_status('away', message)
    
    157
    -
    
    158
    -    @command('back', raw=True, empty=True)
    
    159
    -    @doc(_("Set the current status to online"))
    
    160
    -    def online(self, message):
    
    161
    -        if not message:
    
    162
    -            message = _("Available")
    
    163
    -
    
    164
    -        for connection in app.connections.values():
    
    165
    -            if not app.settings.get_account_setting(connection.name,
    
    166
    -                                                    'sync_with_global_status'):
    
    167
    -                continue
    
    168
    -            if not connection.state.is_available:
    
    169
    -                continue
    
    170
    -            connection.change_status('online', message)
    
    171
    -
    
    172
    -    @command
    
    173
    -    @doc(_("Send a disco info request"))
    
    174
    -    def disco(self):
    
    175
    -        client = app.get_client(self.account)
    
    176
    -        if not client.state.is_available:
    
    177
    -            return
    
    178
    -
    
    179
    -        client.get_module('Discovery').disco_contact(self.contact)
    
    180
    -
    
    181
    -
    
    182
    -class StandardCommonChatCommands(CommandContainer):
    
    183
    -    """
    
    184
    -    This command container contains standard commands, which are common
    
    185
    -    to a chat and a private chat only.
    
    186
    -    """
    
    187
    -
    
    188
    -    AUTOMATIC = True
    
    189
    -    HOSTS = ChatCommands, PrivateChatCommands
    
    190
    -
    
    191
    -    @command
    
    192
    -    @doc(_("Clear the text window"))
    
    193
    -    def clear(self):
    
    194
    -        self.conversation_view.clear()
    
    195
    -
    
    196
    -    @command
    
    197
    -    @doc(_("Send a ping to the contact"))
    
    198
    -    def ping(self):
    
    199
    -        app.connections[self.account].get_module('Ping').send_ping(self.contact)
    
    200
    -
    
    201
    -    @command
    
    202
    -    @doc(_("Send DTMF sequence through an open voice chat"))
    
    203
    -    def dtmf(self, sequence):
    
    204
    -        if not self.audio_sid:
    
    205
    -            raise CommandError(_("No open voice chats with the contact"))
    
    206
    -        for tone in sequence:
    
    207
    -            if not (tone in ("*", "#") or tone.isdigit()):
    
    208
    -                raise CommandError(_("%s is not a valid tone") % tone)
    
    209
    -        gjs = self.connection.get_module('Jingle').get_jingle_session
    
    210
    -        session = gjs(self.full_jid, self.audio_sid)
    
    211
    -        content = session.get_content("audio")
    
    212
    -        content.batch_dtmf(sequence)
    
    213
    -
    
    214
    -    @command
    
    215
    -    @doc(_("Toggle Voice Chat"))
    
    216
    -    def audio(self):
    
    217
    -        if not self.audio_available:
    
    218
    -            raise CommandError(_("Voice chats are not available"))
    
    219
    -        # An audio session is toggled by inverting the state of the
    
    220
    -        # appropriate button.
    
    221
    -        state = self._audio_button.get_active()
    
    222
    -        self._audio_button.set_active(not state)
    
    223
    -
    
    224
    -    @command
    
    225
    -    @doc(_("Toggle Video Chat"))
    
    226
    -    def video(self):
    
    227
    -        if not self.video_available:
    
    228
    -            raise CommandError(_("Video chats are not available"))
    
    229
    -        # A video session is toggled by inverting the state of the
    
    230
    -        # appropriate button.
    
    231
    -        state = self._video_button.get_active()
    
    232
    -        self._video_button.set_active(not state)
    
    233
    -
    
    234
    -    @command(raw=True)
    
    235
    -    @doc(_("Send a message to the contact that will attract their attention"))
    
    236
    -    def attention(self, message):
    
    237
    -        self.send_message(message, process_commands=False, attention=True)
    
    238
    -
    
    239
    -
    
    240
    -class StandardChatCommands(CommandContainer):
    
    241
    -    """
    
    242
    -    This command container contains standard commands which are unique
    
    243
    -    to a chat.
    
    244
    -    """
    
    245
    -
    
    246
    -    AUTOMATIC = True
    
    247
    -    HOSTS = (ChatCommands,)
    
    248
    -
    
    249
    -
    
    250
    -class StandardPrivateChatCommands(CommandContainer):
    
    251
    -    """
    
    252
    -    This command container contains standard commands which are unique
    
    253
    -    to a private chat.
    
    254
    -    """
    
    255
    -
    
    256
    -    AUTOMATIC = True
    
    257
    -    HOSTS = (PrivateChatCommands,)
    
    258
    -
    
    259
    -
    
    260
    -class StandardGroupChatCommands(CommandContainer):
    
    261
    -    """
    
    262
    -    This command container contains standard commands which are unique
    
    263
    -    to a group chat.
    
    264
    -    """
    
    265
    -
    
    266
    -    AUTOMATIC = True
    
    267
    -    HOSTS = (GroupChatCommands,)
    
    268
    -
    
    269
    -    @command
    
    270
    -    @doc(_("Clear the text window"))
    
    271
    -    def clear(self):
    
    272
    -        self.conversation_view.clear()
    
    273
    -
    
    274
    -    @command(raw=True)
    
    275
    -    @doc(_("Change your nickname in a group chat"))
    
    276
    -    def nick(self, new_nick):
    
    277
    -        try:
    
    278
    -            new_nick = helpers.parse_resource(new_nick)
    
    279
    -        except Exception:
    
    280
    -            raise CommandError(_("Invalid nickname"))
    
    281
    -        # FIXME: Check state of MUC
    
    282
    -        self.connection.get_module('MUC').change_nick(
    
    283
    -            self.room_jid, new_nick)
    
    284
    -        self.new_nick = new_nick
    
    285
    -
    
    286
    -    @command('query', raw=True)
    
    287
    -    @doc(_("Open a private chat window with a specified participant"))
    
    288
    -    def chat(self, nick):
    
    289
    -        client = app.get_client(self.account)
    
    290
    -        groupchat_contact = client.get_module('Contacts').get_contact(
    
    291
    -            self.room_jid, groupchat=True)
    
    292
    -        nicks = groupchat_contact.get_user_nicknames()
    
    293
    -        if nick in nicks:
    
    294
    -            self.send_pm(nick)
    
    295
    -        else:
    
    296
    -            raise CommandError(_("Nickname not found"))
    
    297
    -
    
    298
    -    @command('msg', raw=True)
    
    299
    -    @doc(_("Open a private chat window with a specified participant and send "
    
    300
    -           "him a message"))
    
    301
    -    def message(self, nick, message):
    
    302
    -        client = app.get_client(self.account)
    
    303
    -        groupchat_contact = client.get_module('Contacts').get_contact(
    
    304
    -            self.room_jid, groupchat=True)
    
    305
    -        nicks = groupchat_contact.get_user_nicknames()
    
    306
    -        if nick in nicks:
    
    307
    -            self.send_pm(nick, message)
    
    308
    -        else:
    
    309
    -            raise CommandError(_("Nickname not found"))
    
    310
    -
    
    311
    -    @command(raw=True, empty=True)
    
    312
    -    @doc(_("Display or change a group chat topic"))
    
    313
    -    def topic(self, new_topic):
    
    314
    -        if new_topic:
    
    315
    -            self.connection.get_module('MUC').set_subject(
    
    316
    -                self.room_jid, new_topic)
    
    317
    -        else:
    
    318
    -            return self.subject
    
    319
    -
    
    320
    -    @command(raw=True, empty=True)
    
    321
    -    @doc(_("Invite a user to a group chat for a reason"))
    
    322
    -    def invite(self, jid, _reason):
    
    323
    -        control = app.window.get_control(self.account, self.room_jid)
    
    324
    -        if control is not None:
    
    325
    -            control.invite(jid)
    
    326
    -
    
    327
    -    @command(raw=True, empty=True)
    
    328
    -    @doc(_("Join a group chat given by an XMPP Address"))
    
    329
    -    def join(self, jid):
    
    330
    -        if '@' not in jid:
    
    331
    -            jid = jid + '@' + app.get_server_from_jid(self.room_jid)
    
    332
    -
    
    333
    -        app.app.activate_action(
    
    334
    -            'groupchat-join',
    
    335
    -            GLib.Variant('as', [self.account, jid]))
    
    336
    -
    
    337
    -    @command('part', 'close')
    
    338
    -    @doc(_("Leave the group chat"))
    
    339
    -    def leave(self):
    
    340
    -        # Use idle_add to let command system finish printing
    
    341
    -        variant = GLib.Variant('as', [self.account, str(self.room_jid)])
    
    342
    -        GLib.idle_add(app.window.activate_action, 'remove-chat', variant)
    
    343
    -
    
    344
    -    @command(raw=True, empty=True)
    
    345
    -    @doc(_("""
    
    346
    -    Ban user by a nick or a JID from a groupchat
    
    347
    -
    
    348
    -    If given nickname is not found it will be treated as a JID.
    
    349
    -    """))
    
    350
    -    def ban(self, who, reason=''):
    
    351
    -        client = app.get_client(self.account)
    
    352
    -        groupchat_contact = client.get_module('Contacts').get_contact(
    
    353
    -            self.room_jid, groupchat=True)
    
    354
    -        nick_list = groupchat_contact.get_user_nicknames()
    
    355
    -        if who in nick_list:
    
    356
    -            contact = groupchat_contact.get_resource(who)
    
    357
    -            who = contact.jid
    
    358
    -        self.connection.get_module('MUC').set_affiliation(
    
    359
    -            self.room_jid,
    
    360
    -            {who: {'affiliation': 'outcast',
    
    361
    -                   'reason': reason}})
    
    362
    -
    
    363
    -    @command(raw=True, empty=True)
    
    364
    -    @doc(_("Kick user from group chat by nickname"))
    
    365
    -    def kick(self, who, reason):
    
    366
    -        client = app.get_client(self.account)
    
    367
    -        groupchat_contact = client.get_module('Contacts').get_contact(
    
    368
    -            self.room_jid, groupchat=True)
    
    369
    -        nick_list = groupchat_contact.get_user_nicknames()
    
    370
    -        if who not in nick_list:
    
    371
    -            raise CommandError(_("Nickname not found"))
    
    372
    -        self.connection.get_module('MUC').set_role(
    
    373
    -            self.room_jid, who, 'none', reason)
    
    374
    -
    
    375
    -    @command(raw=True)
    
    376
    -    # Do not translate moderator, participant, visitor, none
    
    377
    -    @doc(_("""Set participant role in group chat.
    
    378
    -    Role can be given as one of the following values:
    
    379
    -    moderator, participant, visitor, none"""))
    
    380
    -    def role(self, who, role):
    
    381
    -        if role not in ('moderator', 'participant', 'visitor', 'none'):
    
    382
    -            raise CommandError(_("Invalid role given"))
    
    383
    -        client = app.get_client(self.account)
    
    384
    -        groupchat_contact = client.get_module('Contacts').get_contact(
    
    385
    -            self.room_jid, groupchat=True)
    
    386
    -        nick_list = groupchat_contact.get_user_nicknames()
    
    387
    -        if who not in nick_list:
    
    388
    -            raise CommandError(_("Nickname not found"))
    
    389
    -        self.connection.get_module('MUC').set_role(self.room_jid, who, role)
    
    390
    -
    
    391
    -    @command(raw=True)
    
    392
    -    # Do not translate owner, admin, member, outcast, none
    
    393
    -    @doc(_("""Set participant affiliation in group chat.
    
    394
    -    Affiliation can be given as one of the following values:
    
    395
    -    owner, admin, member, outcast, none"""))
    
    396
    -    def affiliate(self, who, affiliation):
    
    397
    -        if affiliation not in ('owner', 'admin', 'member', 'outcast', 'none'):
    
    398
    -            raise CommandError(_("Invalid affiliation given"))
    
    399
    -        client = app.get_client(self.account)
    
    400
    -        groupchat_contact = client.get_module('Contacts').get_contact(
    
    401
    -            self.room_jid, groupchat=True)
    
    402
    -        nick_list = groupchat_contact.get_user_nicknames()
    
    403
    -        if who not in nick_list:
    
    404
    -            raise CommandError(_("Nickname not found"))
    
    405
    -
    
    406
    -        contact = groupchat_contact.get_resource(who)
    
    407
    -
    
    408
    -        self.connection.get_module('MUC').set_affiliation(
    
    409
    -            self.room_jid,
    
    410
    -            {contact.jid: {'affiliation': affiliation}})
    
    411
    -
    
    412
    -    @command
    
    413
    -    @doc(_("Display names of all group chat participants"))
    
    414
    -    def names(self, verbose=False):
    
    415
    -        client = app.get_client(self.account)
    
    416
    -        groupchat_contact = client.get_module('Contacts').get_contact(
    
    417
    -            self.room_jid, groupchat=True)
    
    418
    -
    
    419
    -        def get_contact(nick):
    
    420
    -            return groupchat_contact.get_resource(nick)
    
    421
    -
    
    422
    -        def get_role(nick):
    
    423
    -            return get_contact(nick).role
    
    424
    -
    
    425
    -        nicks = groupchat_contact.get_user_nicknames()
    
    426
    -
    
    427
    -        nicks = sorted(nicks)
    
    428
    -        nicks = sorted(nicks, key=get_role)
    
    429
    -
    
    430
    -        if not verbose:
    
    431
    -            return ", ".join(nicks)
    
    432
    -
    
    433
    -        for nick in nicks:
    
    434
    -            contact = get_contact(nick)
    
    435
    -            role = helpers.get_uf_role(contact.role)
    
    436
    -            affiliation = helpers.get_uf_affiliation(contact.affiliation)
    
    437
    -            self.echo("%s - %s - %s" % (nick, role, affiliation))
    
    438
    -
    
    439
    -    @command('ignore', raw=True)
    
    440
    -    @doc(_("Forbid a participant to send you public or private messages"))
    
    441
    -    def block(self, who):
    
    442
    -        self.on_block(None, who)
    
    443
    -
    
    444
    -    @command('unignore', raw=True)
    
    445
    -    @doc(_("Allow a participant to send you public or private messages"))
    
    446
    -    def unblock(self, who):
    
    447
    -        self.on_unblock(None, who)
    
    448
    -
    
    449
    -    @command
    
    450
    -    @doc(_("Send a ping to the contact"))
    
    451
    -    def ping(self, nick):
    
    452
    -        client = app.get_client(self.account)
    
    453
    -        groupchat_contact = client.get_module('Contacts').get_contact(
    
    454
    -            self.room_jid, groupchat=True)
    
    455
    -        nick_list = groupchat_contact.get_user_nicknames()
    
    456
    -        if nick not in nick_list:
    
    457
    -            raise CommandError(_("Unknown nickname"))
    
    458
    -
    
    459
    -        client.get_module('Ping').send_ping(
    
    460
    -            groupchat_contact.get_resource(nick))

  • gajim/command_system/mapping.py deleted
    1
    -# Copyright (C) 2009-2010  Alexander Cherniuk <ts33kr@gmail.com>
    
    2
    -#
    
    3
    -# This program is free software: you can redistribute it and/or modify
    
    4
    -# it under the terms of the GNU General Public License as published by
    
    5
    -# the Free Software Foundation, either version 3 of the License, or
    
    6
    -# (at your option) any later version.
    
    7
    -#
    
    8
    -# This program is distributed in the hope that it will be useful,
    
    9
    -# but WITHOUT ANY WARRANTY; without even the implied warranty of
    
    10
    -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    
    11
    -# GNU General Public License for more details.
    
    12
    -#
    
    13
    -# You should have received a copy of the GNU General Public License
    
    14
    -# along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
    15
    -
    
    16
    -"""
    
    17
    -The module contains routines to parse command arguments and map them to
    
    18
    -the command handler's positional and keyword arguments.
    
    19
    -
    
    20
    -Mapping is done in two stages: 1) parse arguments into positional
    
    21
    -arguments and options; 2) adapt them to the specific command handler
    
    22
    -according to the command properties.
    
    23
    -"""
    
    24
    -
    
    25
    -import re
    
    26
    -from operator import itemgetter
    
    27
    -
    
    28
    -from gajim.common.i18n import _
    
    29
    -
    
    30
    -from gajim.command_system.errors import DefinitionError
    
    31
    -from gajim.command_system.errors import CommandError
    
    32
    -
    
    33
    -# Quite complex piece of regular expression logic to parse options and
    
    34
    -# arguments. Might need some tweaking along the way.
    
    35
    -ARG_PATTERN = re.compile(r'(\'|")?(?P<body>(?(1).+?|\S+))(?(1)\1)')
    
    36
    -OPT_PATTERN = re.compile(r'(?<!\w)--?(?P<key>[\w-]+)(?:(?:=|\s)(\'|")?(?P<value>(?(2)[^-]+?|[^-\s]+))(?(2)\2))?')
    
    37
    -
    
    38
    -# Option keys needs to be encoded to a specific encoding as Python does
    
    39
    -# not allow to expand dictionary with raw Unicode strings as keys from a
    
    40
    -# **kwargs.
    
    41
    -KEY_ENCODING = 'UTF-8'
    
    42
    -
    
    43
    -# Defines how complete representation of command usage (generated based
    
    44
    -# on command handler argument specification) will be rendered.
    
    45
    -USAGE_PATTERN = 'Usage: %s %s'
    
    46
    -
    
    47
    -
    
    48
    -def parse_arguments(arguments):
    
    49
    -    """
    
    50
    -    Simple yet effective and sufficient in most cases parser which
    
    51
    -    parses command arguments and returns them as two lists.
    
    52
    -
    
    53
    -    First list represents positional arguments as (argument, position),
    
    54
    -    and second representing options as (key, value, position) tuples,
    
    55
    -    where position is a (start, end) span tuple of where it was found in
    
    56
    -    the string.
    
    57
    -
    
    58
    -    Options may be given in --long or -short format. As --option=value
    
    59
    -    or --option value or -option value. Keys without values will get
    
    60
    -    None as value.
    
    61
    -
    
    62
    -    Arguments and option values that contain spaces may be given as 'one
    
    63
    -    two three' or "one two three"; that is between single or double
    
    64
    -    quotes.
    
    65
    -    """
    
    66
    -    args, opts = [], []
    
    67
    -
    
    68
    -    def intersects_opts(given_start, given_end):
    
    69
    -        """
    
    70
    -        Check if given span intersects with any of options.
    
    71
    -        """
    
    72
    -        for _key, _value, (start, end) in opts:
    
    73
    -            if given_start >= start and given_end <= end:
    
    74
    -                return True
    
    75
    -        return False
    
    76
    -
    
    77
    -    def intersects_args(given_start, given_end):
    
    78
    -        """
    
    79
    -        Check if given span intersects with any of arguments.
    
    80
    -        """
    
    81
    -        for _arg, (start, end) in args:
    
    82
    -            if given_start >= start and given_end <= end:
    
    83
    -                return True
    
    84
    -        return False
    
    85
    -
    
    86
    -    for match in re.finditer(OPT_PATTERN, arguments):
    
    87
    -        if match:
    
    88
    -            key = match.group('key')
    
    89
    -            value = match.group('value') or None
    
    90
    -            position = match.span()
    
    91
    -            opts.append((key, value, position))
    
    92
    -
    
    93
    -    for match in re.finditer(ARG_PATTERN, arguments):
    
    94
    -        if match:
    
    95
    -            body = match.group('body')
    
    96
    -            position = match.span()
    
    97
    -            args.append((body, position))
    
    98
    -
    
    99
    -    # Primitive but sufficiently effective way of disposing of
    
    100
    -    # conflicted sectors. Remove any arguments that intersect with
    
    101
    -    # options.
    
    102
    -    for arg, position in args[:]:
    
    103
    -        if intersects_opts(*position):
    
    104
    -            args.remove((arg, position))
    
    105
    -
    
    106
    -    # Primitive but sufficiently effective way of disposing of
    
    107
    -    # conflicted sectors. Remove any options that intersect with
    
    108
    -    # arguments.
    
    109
    -    for key, value, position in opts[:]:
    
    110
    -        if intersects_args(*position):
    
    111
    -            opts.remove((key, value, position))
    
    112
    -
    
    113
    -    return args, opts
    
    114
    -
    
    115
    -
    
    116
    -def adapt_arguments(command, arguments, args, opts):
    
    117
    -    """
    
    118
    -    Adapt args and opts got from the parser to a specific handler by
    
    119
    -    means of arguments specified on command definition. That is
    
    120
    -    transform them to *args and **kwargs suitable for passing to a
    
    121
    -    command handler.
    
    122
    -
    
    123
    -    Dashes (-) in the option names will be converted to underscores. So
    
    124
    -    you can map --one-more-option to a one_more_option=None.
    
    125
    -
    
    126
    -    If the initial value of a keyword argument is a boolean (False in
    
    127
    -    most cases) - then this option will be treated as a switch, that is
    
    128
    -    an option which does not take an argument. If a switch is followed
    
    129
    -    by an argument - then this argument will be treated just like a
    
    130
    -    normal positional argument.
    
    131
    -    """
    
    132
    -    spec_args, spec_kwargs, var_args, _var_kwargs = command.extract_specification()
    
    133
    -    norm_kwargs = dict(spec_kwargs)
    
    134
    -
    
    135
    -    # Quite complex piece of neck-breaking logic to extract raw
    
    136
    -    # arguments if there is more, then one positional argument specified
    
    137
    -    # by the command.  In case if it's just one argument which is the
    
    138
    -    # collector - this is fairly easy. But when it's more then one
    
    139
    -    # argument - the neck-breaking logic of how to retrieve residual
    
    140
    -    # arguments as a raw, all in one piece string, kicks in.
    
    141
    -    if command.raw:
    
    142
    -        if arguments:
    
    143
    -            spec_fix = 1 if command.source else 0
    
    144
    -            spec_len = len(spec_args) - spec_fix
    
    145
    -            arguments_end = len(arguments) - 1
    
    146
    -
    
    147
    -            # If there are any optional arguments given they should be
    
    148
    -            # either an unquoted positional argument or part of the raw
    
    149
    -            # argument. So we find all optional arguments that can
    
    150
    -            # possibly be unquoted argument and append them as is to the
    
    151
    -            # args.
    
    152
    -            for key, value, (start, end) in opts[:spec_len]:
    
    153
    -                if value:
    
    154
    -                    end -= len(value) + 1
    
    155
    -                    args.append((arguments[start:end], (start, end)))
    
    156
    -                    args.append((value, (end, end + len(value) + 1)))
    
    157
    -                else:
    
    158
    -                    args.append((arguments[start:end], (start, end)))
    
    159
    -
    
    160
    -            # We need in-place sort here because after manipulations
    
    161
    -            # with options order of arguments might be wrong and we just
    
    162
    -            # can't have more complex logic to not let that happen.
    
    163
    -            args.sort(key=itemgetter(1))
    
    164
    -
    
    165
    -            if spec_len > 1:
    
    166
    -                try:
    
    167
    -                    _stopper, (start, end) = args[spec_len - 2]
    
    168
    -                except IndexError:
    
    169
    -                    raise CommandError(_("Missing arguments"), command)
    
    170
    -
    
    171
    -                # The essential point of the whole play. After
    
    172
    -                # boundaries are being determined (supposedly correct)
    
    173
    -                # we separate raw part from the rest of arguments, which
    
    174
    -                # should be normally processed.
    
    175
    -                raw = arguments[end:]
    
    176
    -                raw = raw.strip() or None
    
    177
    -
    
    178
    -                if not raw and not command.empty:
    
    179
    -                    raise CommandError(_("Missing arguments"), command)
    
    180
    -
    
    181
    -                # Discard residual arguments and all of the options as
    
    182
    -                # raw command does not support options and if an option
    
    183
    -                # is given it is rather a part of a raw argument.
    
    184
    -                args = args[:spec_len - 1]
    
    185
    -                opts = []
    
    186
    -
    
    187
    -                args.append((raw, (end, arguments_end)))
    
    188
    -            else:
    
    189
    -                # Substitute all of the arguments with only one, which
    
    190
    -                # contain raw and unprocessed arguments as a string. And
    
    191
    -                # discard all the options, as raw command does not
    
    192
    -                # support them.
    
    193
    -                args = [(arguments, (0, arguments_end))]
    
    194
    -                opts = []
    
    195
    -        else:
    
    196
    -            if command.empty:
    
    197
    -                args.append((None, (0, 0)))
    
    198
    -            else:
    
    199
    -                raise CommandError(_("Missing arguments"), command)
    
    200
    -
    
    201
    -    # The first stage of transforming options we have got to a format
    
    202
    -    # that can be used to associate them with declared keyword
    
    203
    -    # arguments.  Substituting dashes (-) in their names with
    
    204
    -    # underscores (_).
    
    205
    -    for index, (key, value, position) in enumerate(opts):
    
    206
    -        if '-' in key:
    
    207
    -            opts[index] = (key.replace('-', '_'), value, position)
    
    208
    -
    
    209
    -    # The second stage of transforming options to an associable state.
    
    210
    -    # Expanding short, one-letter options to a verbose ones, if
    
    211
    -    # corresponding opt-in has been given.
    
    212
    -    if command.expand:
    
    213
    -        expanded = []
    
    214
    -        for spec_key in norm_kwargs.keys():
    
    215
    -            letter = spec_key[0] if len(spec_key) > 1 else None
    
    216
    -            if letter and letter not in expanded:
    
    217
    -                for index, (key, value, position) in enumerate(opts):
    
    218
    -                    if key == letter:
    
    219
    -                        expanded.append(letter)
    
    220
    -                        opts[index] = (spec_key, value, position)
    
    221
    -                        break
    
    222
    -
    
    223
    -    # Detect switches and set their values accordingly. If any of them
    
    224
    -    # carries a value - append it to args.
    
    225
    -    for index, (key, value, position) in enumerate(opts):
    
    226
    -        if isinstance(norm_kwargs.get(key), bool):
    
    227
    -            opts[index] = (key, True, position)
    
    228
    -            if value:
    
    229
    -                args.append((value, position))
    
    230
    -
    
    231
    -    # Sorting arguments and options (just to be sure) in regarding to
    
    232
    -    # their positions in the string.
    
    233
    -    args.sort(key=itemgetter(1))
    
    234
    -    opts.sort(key=itemgetter(2))
    
    235
    -
    
    236
    -    # Stripping down position information supplied with arguments and
    
    237
    -    # options as it won't be needed again.
    
    238
    -    args = list(map(lambda t: t[0], args))
    
    239
    -    opts = list(map(lambda t: (t[0], t[1]), opts))
    
    240
    -
    
    241
    -    # If command has extra option enabled - collect all extra arguments
    
    242
    -    # and pass them to a last positional argument command defines as a
    
    243
    -    # list.
    
    244
    -    if command.extra:
    
    245
    -        if not var_args:
    
    246
    -            spec_fix = 1 if not command.source else 2
    
    247
    -            spec_len = len(spec_args) - spec_fix
    
    248
    -            extra = args[spec_len:]
    
    249
    -            args = args[:spec_len]
    
    250
    -            args.append(extra)
    
    251
    -        else:
    
    252
    -            raise DefinitionError("Can not have both, extra and *args")
    
    253
    -
    
    254
    -    # Detect if positional arguments overlap keyword arguments. If so
    
    255
    -    # and this is allowed by command options - then map them directly to
    
    256
    -    # their options, so they can get proper further processing.
    
    257
    -    spec_fix = 1 if command.source else 0
    
    258
    -    spec_len = len(spec_args) - spec_fix
    
    259
    -    if len(args) > spec_len:
    
    260
    -        if command.overlap:
    
    261
    -            overlapped = args[spec_len:]
    
    262
    -            args = args[:spec_len]
    
    263
    -            for arg, spec_key, _spec_value in zip(overlapped, spec_kwargs):
    
    264
    -                opts.append((spec_key, arg))
    
    265
    -        else:
    
    266
    -            raise CommandError(_("Too many arguments"), command)
    
    267
    -
    
    268
    -    # Detect every switch and ensure it will not receive any arguments.
    
    269
    -    # Normally this does not happen unless overlapping is enabled.
    
    270
    -    for key, value in opts:
    
    271
    -        initial = norm_kwargs.get(key)
    
    272
    -        if isinstance(initial, bool):
    
    273
    -            if not isinstance(value, bool):
    
    274
    -                raise CommandError(
    
    275
    -                    "%s: Switch can not take an argument" % key, command)
    
    276
    -
    
    277
    -    # Inject the source arguments as a string as a first argument, if
    
    278
    -    # command has enabled the corresponding option.
    
    279
    -    if command.source:
    
    280
    -        args.insert(0, arguments)
    
    281
    -
    
    282
    -    # Return *args and **kwargs in the form suitable for passing to a
    
    283
    -    # command handler and being expanded.
    
    284
    -    return tuple(args), dict(opts)
    
    285
    -
    
    286
    -
    
    287
    -def generate_usage(command, complete=True):
    
    288
    -    """
    
    289
    -    Extract handler's arguments specification and wrap them in a
    
    290
    -    human-readable format usage information. If complete is given - then
    
    291
    -    USAGE_PATTERN will be used to render the specification completely.
    
    292
    -    """
    
    293
    -    spec_args, spec_kwargs, var_args, var_kwargs = command.extract_specification()
    
    294
    -
    
    295
    -    # Remove some special positional arguments from the specification,
    
    296
    -    # but store their names so they can be used for usage info
    
    297
    -    # generation.
    
    298
    -    _sp_source = spec_args.pop(0) if command.source else None
    
    299
    -    sp_extra = spec_args.pop() if command.extra else None
    
    300
    -
    
    301
    -    kwargs = []
    
    302
    -    letters = []
    
    303
    -
    
    304
    -    for key, value in spec_kwargs:
    
    305
    -        letter = key[0]
    
    306
    -        key = key.replace('_', '-')
    
    307
    -
    
    308
    -        if isinstance(value, bool):
    
    309
    -            value = str()
    
    310
    -        else:
    
    311
    -            value = '=%s' % value
    
    312
    -
    
    313
    -        if letter not in letters:
    
    314
    -            kwargs.append('-(-%s)%s%s' % (letter, key[1:], value))
    
    315
    -            letters.append(letter)
    
    316
    -        else:
    
    317
    -            kwargs.append('--%s%s' % (key, value))
    
    318
    -
    
    319
    -    usage = str()
    
    320
    -    args = str()
    
    321
    -
    
    322
    -    if command.raw:
    
    323
    -        spec_len = len(spec_args) - 1
    
    324
    -        if spec_len:
    
    325
    -            args += ('<%s>' % ', '.join(spec_args[:spec_len])) + ' '
    
    326
    -        args += ('(|%s|)' if command.empty else '|%s|') % spec_args[-1]
    
    327
    -    else:
    
    328
    -        if spec_args:
    
    329
    -            args += '<%s>' % ', '.join(spec_args)
    
    330
    -        if var_args or sp_extra:
    
    331
    -            args += (' ' if spec_args else str()) + '<<%s>>' % (
    
    332
    -                var_args or sp_extra)
    
    333
    -
    
    334
    -    usage += args
    
    335
    -
    
    336
    -    if kwargs or var_kwargs:
    
    337
    -        if kwargs:
    
    338
    -            usage += (' ' if args else str()) + '[%s]' % ', '.join(kwargs)
    
    339
    -        if var_kwargs:
    
    340
    -            usage += (' ' if args else str()) + '[[%s]]' % var_kwargs
    
    341
    -
    
    342
    -    # Native name will be the first one if it is included. Otherwise,
    
    343
    -    # names will be in the order they were specified.
    
    344
    -    if len(command.names) > 1:
    
    345
    -        names = '%s (%s)' % (command.first_name, ', '.join(command.names[1:]))
    
    346
    -    else:
    
    347
    -        names = command.first_name
    
    348
    -
    
    349
    -    return USAGE_PATTERN % (names, usage) if complete else usage

  • gajim/command_system/tools.py deleted
    1
    -# Copyright (c) 2010, Alexander Cherniuk (ts33kr@gmail.com)
    
    2
    -# All rights reserved.
    
    3
    -#
    
    4
    -# Redistribution and use in source and binary forms, with or without
    
    5
    -# modification, are permitted provided that the following conditions
    
    6
    -# are met:
    
    7
    -#
    
    8
    -# * Redistributions of source code must retain the above copyright
    
    9
    -#   notice, this list of conditions and the following disclaimer.
    
    10
    -#
    
    11
    -# * Redistributions in binary form must reproduce the above copyright
    
    12
    -#   notice, this list of conditions and the following disclaimer in the
    
    13
    -#   documentation and/or other materials provided with the distribution.
    
    14
    -#
    
    15
    -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    
    16
    -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    
    17
    -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    
    18
    -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    
    19
    -# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    
    20
    -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    
    21
    -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    
    22
    -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    
    23
    -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    
    24
    -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    
    25
    -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    
    26
    -
    
    27
    -
    
    28
    -def remove(sequence, target):
    
    29
    -    if isinstance(sequence, list):
    
    30
    -        if target in sequence:
    
    31
    -            sequence.remove(target)
    
    32
    -    elif isinstance(sequence, dict):
    
    33
    -        if target in sequence:
    
    34
    -            del sequence[target]

  • gajim/common/setting_values.py
    ... ... @@ -45,7 +45,6 @@ class _ACCOUNT_DEFAULT:
    45 45
         'change_roster_title',
    
    46 46
         'chat_merge_consecutive_nickname',
    
    47 47
         'check_for_update',
    
    48
    -    'command_system_execute',
    
    49 48
         'confirm_close_muc',
    
    50 49
         'confirm_close_multiple_tabs',
    
    51 50
         'confirm_on_window_delete',
    
    ... ... @@ -191,7 +190,6 @@ class _ACCOUNT_DEFAULT:
    191 190
         'chat_merge_consecutive_nickname': True,
    
    192 191
         'chat_timestamp_format': '%H:%M',
    
    193 192
         'check_for_update': True,
    
    194
    -    'command_system_execute': False,
    
    195 193
         'confirm_block': '',
    
    196 194
         'confirm_close_muc': True,
    
    197 195
         'confirm_close_multiple_tabs': True,
    
    ... ... @@ -647,9 +645,6 @@ class WorkspaceSettings(TypedDict):
    647 645
                 'if there are multiple messages from the same sender within a '
    
    648 646
                 'specific timespan.'),
    
    649 647
             'chat_timestamp_format': 'https://docs.python.org/3/library/time.html#time.strftime',  # noqa: E501
    
    650
    -        'command_system_execute': _(
    
    651
    -            'If enabled, Gajim will execute commands '
    
    652
    -            '(/show, /sh, /execute, /exec).'),
    
    653 648
             'confirm_block': _(
    
    654 649
                 'Show a confirmation dialog to block a contact? Empty string '
    
    655 650
                 'means never show the dialog.'),
    

  • setup.cfg
    ... ... @@ -7,7 +7,6 @@ exclude =
    7 7
         debian_build,
    
    8 8
         test,
    
    9 9
         typings,
    
    10
    -    gajim/command_system,
    
    11 10
         gajim/common/config.py,
    
    12 11
         gajim/common/optparser.py,
    
    13 12
         gajim/common/socks5.py,