Dropped in the reworked version of the new command system
The reworked version has refactored and simplified architecture, which also involves simplified dispatching.
This commit is contained in:
		
							parent
							
								
									ca127b8d31
								
							
						
					
					
						commit
						958d937d5c
					
				
					 15 changed files with 1055 additions and 937 deletions
				
			
		| 
						 | 
				
			
			@ -51,7 +51,13 @@ from common.pep import MOODS, ACTIVITIES
 | 
			
		|||
from common.xmpp.protocol import NS_XHTML, NS_XHTML_IM, NS_FILE, NS_MUC
 | 
			
		||||
from common.xmpp.protocol import NS_RECEIPTS, NS_ESESSION
 | 
			
		||||
 | 
			
		||||
from commands.implementation import CommonCommands, ChatCommands
 | 
			
		||||
from command_system.implementation.middleware import ChatCommandProcessor
 | 
			
		||||
from command_system.implementation.middleware import CommandTools
 | 
			
		||||
from command_system.implementation.hosts import ChatCommands
 | 
			
		||||
 | 
			
		||||
# Here we load the module with the standard commands, so they are being detected
 | 
			
		||||
# and dispatched.
 | 
			
		||||
import command_system.implementation.standard
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
	import gtkspell
 | 
			
		||||
| 
						 | 
				
			
			@ -81,7 +87,7 @@ if gajim.config.get('use_speller') and HAS_GTK_SPELL:
 | 
			
		|||
	del tv
 | 
			
		||||
 | 
			
		||||
################################################################################
 | 
			
		||||
class ChatControlBase(MessageControl, CommonCommands):
 | 
			
		||||
class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
 | 
			
		||||
	'''A base class containing a banner, ConversationTextview, MessageTextView
 | 
			
		||||
	'''
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1164,12 +1170,14 @@ class ChatControlBase(MessageControl, CommonCommands):
 | 
			
		|||
		# FIXME: Set sensitivity for toolbar
 | 
			
		||||
 | 
			
		||||
################################################################################
 | 
			
		||||
class ChatControl(ChatControlBase, ChatCommands):
 | 
			
		||||
class ChatControl(ChatControlBase):
 | 
			
		||||
	'''A control for standard 1-1 chat'''
 | 
			
		||||
	TYPE_ID = message_control.TYPE_CHAT
 | 
			
		||||
	old_msg_kind = None # last kind of the printed message
 | 
			
		||||
 | 
			
		||||
	DISPATCHED_BY = ChatCommands
 | 
			
		||||
	# Set a command host to bound to. Every command given through a chat will be
 | 
			
		||||
	# processed with this command host.
 | 
			
		||||
	COMMAND_HOST = ChatCommands
 | 
			
		||||
 | 
			
		||||
	def __init__(self, parent_win, contact, acct, session, resource = None):
 | 
			
		||||
		ChatControlBase.__init__(self, self.TYPE_ID, parent_win,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										20
									
								
								src/command_system/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/command_system/__init__.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,20 @@
 | 
			
		|||
# Copyright (C) 2009  red-agent <hell.director@gmail.com>
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# This program is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
The command system providing scalable, clean and convenient architecture in
 | 
			
		||||
combination with declarative way of defining commands and a fair amount of
 | 
			
		||||
automatization for routine processes.
 | 
			
		||||
"""
 | 
			
		||||
							
								
								
									
										89
									
								
								src/command_system/dispatching.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/command_system/dispatching.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,89 @@
 | 
			
		|||
# Copyright (C) 2009  red-agent <hell.director@gmail.com>
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# This program is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
The backbone of the command system. Provides automatic dispatching which does
 | 
			
		||||
not require explicit registering commands or containers and remains active even
 | 
			
		||||
after everything is done, so new commands can be added during the runtime.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from types import NoneType
 | 
			
		||||
 | 
			
		||||
class Dispatcher(type):
 | 
			
		||||
 | 
			
		||||
    containers = {}
 | 
			
		||||
    commands = {}
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def register_host(klass, host):
 | 
			
		||||
        klass.containers[host] = []
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def register_container(klass, container):
 | 
			
		||||
        for host in container.HOSTS:
 | 
			
		||||
            klass.containers[host].append(container)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def register_commands(klass, container):
 | 
			
		||||
        klass.commands[container] = {}
 | 
			
		||||
        for command in klass.traverse_commands(container):
 | 
			
		||||
            for name in command.names:
 | 
			
		||||
                klass.commands[container][name] = command
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_command(klass, host, name):
 | 
			
		||||
        for container in klass.containers[host]:
 | 
			
		||||
            command = klass.commands[container].get(name)
 | 
			
		||||
            if command:
 | 
			
		||||
                return command
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def list_commands(klass, host):
 | 
			
		||||
        for container in klass.containers[host]:
 | 
			
		||||
            commands = klass.commands[container]
 | 
			
		||||
            for name, command in commands.iteritems():
 | 
			
		||||
                yield name, command
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def traverse_commands(klass, container):
 | 
			
		||||
        for name in dir(container):
 | 
			
		||||
            attribute = getattr(container, name)
 | 
			
		||||
            if klass.is_command(attribute):
 | 
			
		||||
                yield attribute
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def is_root(ns):
 | 
			
		||||
        meta = ns.get('__metaclass__', NoneType)
 | 
			
		||||
        return issubclass(meta, Dispatcher)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def is_command(attribute):
 | 
			
		||||
        name = attribute.__class__.__name__
 | 
			
		||||
        return name == 'Command'
 | 
			
		||||
 | 
			
		||||
class HostDispatcher(Dispatcher):
 | 
			
		||||
 | 
			
		||||
    def __init__(klass, name, bases, ns):
 | 
			
		||||
        if not Dispatcher.is_root(ns):
 | 
			
		||||
            HostDispatcher.register_host(klass)
 | 
			
		||||
        super(HostDispatcher, klass).__init__(name, bases, ns)
 | 
			
		||||
 | 
			
		||||
class ContainerDispatcher(Dispatcher):
 | 
			
		||||
 | 
			
		||||
    def __init__(klass, name, bases, ns):
 | 
			
		||||
        if not Dispatcher.is_root(ns):
 | 
			
		||||
            ContainerDispatcher.register_container(klass)
 | 
			
		||||
            ContainerDispatcher.register_commands(klass)
 | 
			
		||||
        super(ContainerDispatcher, klass).__init__(name, bases, ns)
 | 
			
		||||
							
								
								
									
										41
									
								
								src/command_system/errors.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/command_system/errors.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,41 @@
 | 
			
		|||
# Copyright (C) 2009  red-agent <hell.director@gmail.com>
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# This program is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
class BaseError(Exception):
 | 
			
		||||
    """
 | 
			
		||||
    Common base for errors which relate to a specific command. Encapsulates
 | 
			
		||||
    everything needed to identify a command, by either its object or name.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, message, command=None, name=None):
 | 
			
		||||
        self.command = command
 | 
			
		||||
        self.name = name
 | 
			
		||||
 | 
			
		||||
        if command and not name:
 | 
			
		||||
            self.name = command.first_name
 | 
			
		||||
 | 
			
		||||
        super(BaseError, self).__init__(message)
 | 
			
		||||
 | 
			
		||||
class DefinitionError(BaseError):
 | 
			
		||||
    """
 | 
			
		||||
    Used to indicate errors occured on command definition.
 | 
			
		||||
    """
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
class CommandError(BaseError):
 | 
			
		||||
    """
 | 
			
		||||
    Used to indicate errors occured during command execution.
 | 
			
		||||
    """
 | 
			
		||||
    pass
 | 
			
		||||
							
								
								
									
										320
									
								
								src/command_system/framework.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										320
									
								
								src/command_system/framework.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,320 @@
 | 
			
		|||
# Copyright (C) 2009  red-agent <hell.director@gmail.com>
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# This program is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
Provides a tiny framework with simple, yet powerful and extensible architecture
 | 
			
		||||
to implement commands in a streight and flexible, declarative way.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import re
 | 
			
		||||
from types import FunctionType
 | 
			
		||||
from inspect import getargspec
 | 
			
		||||
 | 
			
		||||
from dispatching import Dispatcher, HostDispatcher, ContainerDispatcher
 | 
			
		||||
from mapping import parse_arguments, adapt_arguments
 | 
			
		||||
from errors import DefinitionError, CommandError
 | 
			
		||||
 | 
			
		||||
class CommandHost(object):
 | 
			
		||||
    """
 | 
			
		||||
    Command host is a hub between numerous command processors and command
 | 
			
		||||
    containers. Aimed to participate in a dispatching process in order to
 | 
			
		||||
    provide clean and transparent architecture.
 | 
			
		||||
    """
 | 
			
		||||
    __metaclass__ = HostDispatcher
 | 
			
		||||
 | 
			
		||||
class CommandContainer(object):
 | 
			
		||||
    """
 | 
			
		||||
    Command container is an entity which holds defined commands, allowing them
 | 
			
		||||
    to be dispatched and proccessed correctly. Each command container may be
 | 
			
		||||
    bound to a one or more command hosts.
 | 
			
		||||
 | 
			
		||||
    Bounding is controlled by the HOSTS variable, which must be defined in the
 | 
			
		||||
    body of the command container. This variable should contain a list of hosts
 | 
			
		||||
    to bound to, as a tuple or list.
 | 
			
		||||
    """
 | 
			
		||||
    __metaclass__ = ContainerDispatcher
 | 
			
		||||
 | 
			
		||||
class CommandProcessor(object):
 | 
			
		||||
    """
 | 
			
		||||
    Command processor is an immediate command emitter. It does not participate
 | 
			
		||||
    in the dispatching process directly, but must define a host to bound to.
 | 
			
		||||
 | 
			
		||||
    Bounding is controlled by the COMMAND_HOST variable, which must be defined
 | 
			
		||||
    in the body of the command processor. This variable should be set to a
 | 
			
		||||
    specific command host.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    # This defines a command prefix (or an initializer), which should preceede a
 | 
			
		||||
    # a text in order it to be processed as a command.
 | 
			
		||||
    COMMAND_PREFIX = '/'
 | 
			
		||||
 | 
			
		||||
    def process_as_command(self, text):
 | 
			
		||||
        """
 | 
			
		||||
        Try to process text as a command. Returns True if it has been processed
 | 
			
		||||
        as a command and False otherwise.
 | 
			
		||||
        """
 | 
			
		||||
        if not text.startswith(self.COMMAND_PREFIX):
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        body = text[len(self.COMMAND_PREFIX):]
 | 
			
		||||
        body = body.strip()
 | 
			
		||||
 | 
			
		||||
        parts = body.split(None, 1)
 | 
			
		||||
        name, arguments = parts if len(parts) > 1 else (parts[0], None)
 | 
			
		||||
 | 
			
		||||
        flag = self.looks_like_command(text, body, name, arguments)
 | 
			
		||||
        if flag is not None:
 | 
			
		||||
            return flag
 | 
			
		||||
 | 
			
		||||
        self.execute_command(name, arguments)
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def execute_command(self, name, arguments):
 | 
			
		||||
        command = self.get_command(name)
 | 
			
		||||
 | 
			
		||||
        args, opts = parse_arguments(arguments) if arguments else ([], [])
 | 
			
		||||
        args, kwargs = adapt_arguments(command, arguments, args, opts)
 | 
			
		||||
 | 
			
		||||
        if self.command_preprocessor(command, name, arguments, args, kwargs):
 | 
			
		||||
            return
 | 
			
		||||
        value = command(self, *args, **kwargs)
 | 
			
		||||
        self.command_postprocessor(command, name, arguments, args, kwargs, value)
 | 
			
		||||
 | 
			
		||||
    def command_preprocessor(self, command, name, arguments, args, kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Redefine this method in the subclass to execute custom code before
 | 
			
		||||
        command gets executed.
 | 
			
		||||
 | 
			
		||||
        If returns True then command execution will be interrupted and command
 | 
			
		||||
        will not be executed.
 | 
			
		||||
        """
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def command_postprocessor(self, command, name, arguments, args, kwargs, value):
 | 
			
		||||
        """
 | 
			
		||||
        Redefine this method in the subclass to execute custom code after
 | 
			
		||||
        command gets executed.
 | 
			
		||||
        """
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def looks_like_command(self, text, body, name, arguments):
 | 
			
		||||
        """
 | 
			
		||||
        This hook is being called before any processing, but after it was
 | 
			
		||||
        determined that text looks like a command.
 | 
			
		||||
 | 
			
		||||
        If returns value other then None - then further processing will be
 | 
			
		||||
        interrupted and that value will be used to return from
 | 
			
		||||
        process_as_command.
 | 
			
		||||
        """
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def get_command(self, name):
 | 
			
		||||
        command = Dispatcher.get_command(self.COMMAND_HOST, name)
 | 
			
		||||
        if not command:
 | 
			
		||||
            raise CommandError("Command does not exist", name=name)
 | 
			
		||||
        return command
 | 
			
		||||
 | 
			
		||||
    def list_commands(self):
 | 
			
		||||
        commands = Dispatcher.list_commands(self.COMMAND_HOST)
 | 
			
		||||
        commands = dict(commands)
 | 
			
		||||
        return sorted(set(commands.itervalues()))
 | 
			
		||||
 | 
			
		||||
class Command(object):
 | 
			
		||||
 | 
			
		||||
    # These two regular expression patterns control how command documentation
 | 
			
		||||
    # will be formatted to be transformed to a normal, readable state.
 | 
			
		||||
    DOC_STRIP_PATTERN = re.compile(r'(?:^[ \t]+|\A\n)', re.MULTILINE)
 | 
			
		||||
    DOC_FORMAT_PATTERN = re.compile(r'(?<!\n)\n(?!\n)', re.MULTILINE)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, handler, *names, **properties):
 | 
			
		||||
        self.handler = handler
 | 
			
		||||
        self.names = names
 | 
			
		||||
 | 
			
		||||
        # Automatically set all the properties passed to a constructor by the
 | 
			
		||||
        # command decorator.
 | 
			
		||||
        for key, value in properties.iteritems():
 | 
			
		||||
            setattr(self, key, value)
 | 
			
		||||
 | 
			
		||||
    def __call__(self, *args, **kwargs):
 | 
			
		||||
        try:
 | 
			
		||||
            return self.handler(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
        # This allows to use a shortcuted way of raising an exception inside a
 | 
			
		||||
        # handler. That is to raise a CommandError without command or name
 | 
			
		||||
        # attributes set. They will be set to a corresponding values right here
 | 
			
		||||
        # in case if they was not set by the one who raised an exception.
 | 
			
		||||
        except CommandError, error:
 | 
			
		||||
            if not error.command and not error.name:
 | 
			
		||||
                raise CommandError(exception.message, self)
 | 
			
		||||
            raise
 | 
			
		||||
 | 
			
		||||
        # This one is a little bit too wide, but as Python does not have
 | 
			
		||||
        # anything more constrained - there is no other choice. Take a look here
 | 
			
		||||
        # if command complains about invalid arguments while they are ok.
 | 
			
		||||
        except TypeError:
 | 
			
		||||
            raise CommandError("Command received invalid arguments", self)
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return "<Command %s>" % ', '.join(self.names)
 | 
			
		||||
 | 
			
		||||
    def __cmp__(self, other):
 | 
			
		||||
        return cmp(self.first_name, other.first_name)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def first_name(self):
 | 
			
		||||
        return self.names[0]
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def native_name(self):
 | 
			
		||||
        return self.handler.__name__
 | 
			
		||||
 | 
			
		||||
    def extract_documentation(self):
 | 
			
		||||
        """
 | 
			
		||||
        Extract handler's documentation which is a doc-string and transform it
 | 
			
		||||
        to a usable format.
 | 
			
		||||
 | 
			
		||||
        Transformation is done based on the DOC_STRIP_PATTERN and
 | 
			
		||||
        DOC_FORMAT_PATTERN regular expression patterns.
 | 
			
		||||
        """
 | 
			
		||||
        documentation = self.handler.__doc__ or None
 | 
			
		||||
 | 
			
		||||
        if not documentation:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        documentation = re.sub(self.DOC_STRIP_PATTERN, str(), documentation)
 | 
			
		||||
        documentation = re.sub(self.DOC_FORMAT_PATTERN, ' ', documentation)
 | 
			
		||||
 | 
			
		||||
        return documentation
 | 
			
		||||
 | 
			
		||||
    def extract_description(self):
 | 
			
		||||
        """
 | 
			
		||||
        Extract handler's description (which is a first line of the
 | 
			
		||||
        documentation). Try to keep them simple yet meaningful.
 | 
			
		||||
        """
 | 
			
		||||
        documentation = self.extract_documentation()
 | 
			
		||||
        return documentation.split('\n', 1)[0] if documentation else None
 | 
			
		||||
 | 
			
		||||
    def extract_specification(self):
 | 
			
		||||
        """
 | 
			
		||||
        Extract handler's arguments specification, as it was defined preserving
 | 
			
		||||
        their order.
 | 
			
		||||
        """
 | 
			
		||||
        names, var_args, var_kwargs, defaults = getargspec(self.handler)
 | 
			
		||||
 | 
			
		||||
        # Behavior of this code need to be checked. Might yield incorrect
 | 
			
		||||
        # results on some rare occasions.
 | 
			
		||||
        spec_args = names[:-len(defaults) if defaults else len(names)]
 | 
			
		||||
        spec_kwargs = list(zip(names[-len(defaults):], defaults)) if defaults else {}
 | 
			
		||||
 | 
			
		||||
        # Removing self from arguments specification. Command handler should
 | 
			
		||||
        # receive the processors as a first argument, which should be self by
 | 
			
		||||
        # the canonical means.
 | 
			
		||||
        if spec_args.pop(0) != 'self':
 | 
			
		||||
            raise DefinitionError("First argument must be self", self)
 | 
			
		||||
 | 
			
		||||
        return spec_args, spec_kwargs, var_args, var_kwargs
 | 
			
		||||
 | 
			
		||||
def command(*names, **properties):
 | 
			
		||||
    """
 | 
			
		||||
    A decorator for defining commands in a declarative way. Provides facilities
 | 
			
		||||
    for setting command's names and properties.
 | 
			
		||||
 | 
			
		||||
    Names should contain a set of names (aliases) by which the command can be
 | 
			
		||||
    reached. If no names are given - the the native name (the one extracted from
 | 
			
		||||
    the command handler) will be used.
 | 
			
		||||
 | 
			
		||||
    If include_native=True is given (default) and names is non-empty - then the
 | 
			
		||||
    native name of the command will be prepended in addition to the given names.
 | 
			
		||||
 | 
			
		||||
    If usage=True is given (default) - then command help will be appended with
 | 
			
		||||
    autogenerated usage info, based of the command handler arguments
 | 
			
		||||
    introspection.
 | 
			
		||||
 | 
			
		||||
    If source=True is given - then the first argument of the command will
 | 
			
		||||
    receive the source arguments, as a raw, unprocessed string. The further
 | 
			
		||||
    mapping of arguments and options will not be affected.
 | 
			
		||||
 | 
			
		||||
    If raw=True is given - then command considered to be raw and should define
 | 
			
		||||
    positional arguments only. If it defines only one positional argument - this
 | 
			
		||||
    argument will receive all the raw and unprocessed arguments. If the command
 | 
			
		||||
    defines more then one positional argument - then all the arguments except
 | 
			
		||||
    the last one will be processed normally; the last argument will get what is
 | 
			
		||||
    left after the processing as raw and unprocessed string.
 | 
			
		||||
 | 
			
		||||
    If empty=True is given - this will allow to call a raw command without
 | 
			
		||||
    arguments.
 | 
			
		||||
 | 
			
		||||
    If extra=True is given - then all the extra arguments passed to a command
 | 
			
		||||
    will be collected into a sequence and given to the last positional argument.
 | 
			
		||||
 | 
			
		||||
    If overlap=True is given - then all the extra arguments will be mapped as if
 | 
			
		||||
    they were values for the keyword arguments.
 | 
			
		||||
 | 
			
		||||
    If expand_short=True is given (default) - then short, one-letter options
 | 
			
		||||
    will be expanded to a verbose ones, based of the comparison of the first
 | 
			
		||||
    letter. If more then one option with the same first letter is given - then
 | 
			
		||||
    only first one will be used in the expansion.
 | 
			
		||||
    """
 | 
			
		||||
    names = list(names)
 | 
			
		||||
 | 
			
		||||
    include_native = properties.get('include_native', True)
 | 
			
		||||
 | 
			
		||||
    usage = properties.get('usage', True)
 | 
			
		||||
    source = properties.get('source', False)
 | 
			
		||||
    raw = properties.get('raw', False)
 | 
			
		||||
    empty = properties.get('empty', False)
 | 
			
		||||
    extra = properties.get('extra', False)
 | 
			
		||||
    overlap = properties.get('overlap', False)
 | 
			
		||||
    expand_short = properties.get('expand_short', True)
 | 
			
		||||
 | 
			
		||||
    if empty and not raw:
 | 
			
		||||
        raise DefinitionError("Empty option can be used only with raw commands")
 | 
			
		||||
 | 
			
		||||
    if extra and overlap:
 | 
			
		||||
        raise DefinitionError("Extra and overlap options can not be used together")
 | 
			
		||||
 | 
			
		||||
    properties = {
 | 
			
		||||
        'usage': usage,
 | 
			
		||||
        'source': source,
 | 
			
		||||
        'raw': raw,
 | 
			
		||||
        'extra': extra,
 | 
			
		||||
        'overlap': overlap,
 | 
			
		||||
        'empty': empty,
 | 
			
		||||
        'expand_short': expand_short
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    def decorator(handler):
 | 
			
		||||
        """
 | 
			
		||||
        Decorator which receives handler as a first argument and then wraps it
 | 
			
		||||
        in the command which then returns back.
 | 
			
		||||
        """
 | 
			
		||||
        command = Command(handler, *names, **properties)
 | 
			
		||||
 | 
			
		||||
        # Extract and inject a native name if either no other names are
 | 
			
		||||
        # specified or include_native property is enabled, while making sure it
 | 
			
		||||
        # is going to be the first one in the list.
 | 
			
		||||
        if not names or include_native:
 | 
			
		||||
            names.insert(0, command.native_name)
 | 
			
		||||
            command.names = tuple(names)
 | 
			
		||||
 | 
			
		||||
        return command
 | 
			
		||||
 | 
			
		||||
    # Workaround if we are getting called without parameters. Keep in mind that
 | 
			
		||||
    # in that case - first item in the names will be the handler.
 | 
			
		||||
    if names and isinstance(names[0], FunctionType):
 | 
			
		||||
        return decorator(names.pop(0))
 | 
			
		||||
 | 
			
		||||
    return decorator
 | 
			
		||||
| 
						 | 
				
			
			@ -14,7 +14,6 @@
 | 
			
		|||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
The command system providing scalable and convenient architecture in combination
 | 
			
		||||
with declarative way of defining commands and a fair amount of automatization
 | 
			
		||||
for routine processes.
 | 
			
		||||
The implementation and auxilary systems which implement the standard Gajim
 | 
			
		||||
commands and also provide an infrastructure for adding custom commands.
 | 
			
		||||
"""
 | 
			
		||||
							
								
								
									
										85
									
								
								src/command_system/implementation/custom.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								src/command_system/implementation/custom.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,85 @@
 | 
			
		|||
# Copyright (C) 2009  red-agent <hell.director@gmail.com>
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# This program is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
The module contains examples of how to create your own commands, by creating a
 | 
			
		||||
new command container and definding a set of commands.
 | 
			
		||||
 | 
			
		||||
Keep in mind that this module is not being loaded, so the code will not be
 | 
			
		||||
executed and commands defined here will not be detected.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from ..framework import CommandContainer, command
 | 
			
		||||
from hosts import ChatCommands, PrivateChatCommands, GroupChatCommands
 | 
			
		||||
 | 
			
		||||
class CustomCommonCommands(CommandContainer):
 | 
			
		||||
    """
 | 
			
		||||
    This command container bounds to all three available in the default
 | 
			
		||||
    implementation command hosts. This means that commands defined in this
 | 
			
		||||
    container will be available to all - chat, private chat and a group chat.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    HOSTS = (ChatCommands, PrivateChatCommands, GroupChatCommands)
 | 
			
		||||
 | 
			
		||||
    @command
 | 
			
		||||
    def dance(self):
 | 
			
		||||
        """
 | 
			
		||||
        First line of the doc string is called a description and will be
 | 
			
		||||
        programmatically extracted and formatted.
 | 
			
		||||
 | 
			
		||||
        After that you can give more help, like explanation of the options. This
 | 
			
		||||
        one will be programatically extracted and formatted too.
 | 
			
		||||
 | 
			
		||||
        After all the documentation - there will be autogenerated (based on the
 | 
			
		||||
        method signature) usage information appended. You can turn it off
 | 
			
		||||
        though, if you want.
 | 
			
		||||
        """
 | 
			
		||||
        return "I can't dance, you stupid fuck, I'm just a command system! A cool one, though..."
 | 
			
		||||
 | 
			
		||||
class CustomChatCommands(CommandContainer):
 | 
			
		||||
    """
 | 
			
		||||
    This command container bounds only to the ChatCommands command host.
 | 
			
		||||
    Therefore command defined here will be available only to a chat.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    HOSTS = (ChatCommands,)
 | 
			
		||||
 | 
			
		||||
    @command
 | 
			
		||||
    def sing(self):
 | 
			
		||||
        return "Are you phreaking kidding me? Buy yourself a damn stereo..."
 | 
			
		||||
 | 
			
		||||
class CustomPrivateChatCommands(CommandContainer):
 | 
			
		||||
    """
 | 
			
		||||
    This command container bounds only to the PrivateChatCommands command host.
 | 
			
		||||
    Therefore command defined here will be available only to a private chat.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    HOSTS = (PrivateChatCommands,)
 | 
			
		||||
 | 
			
		||||
    @command
 | 
			
		||||
    def make_coffee(self):
 | 
			
		||||
        return "What do I look like, you ass? A coffee machine!?"
 | 
			
		||||
 | 
			
		||||
class CustomGroupChatCommands(CommandContainer):
 | 
			
		||||
    """
 | 
			
		||||
    This command container bounds only to the GroupChatCommands command host.
 | 
			
		||||
    Therefore command defined here will be available only to a group chat.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    HOSTS = (GroupChatCommands,)
 | 
			
		||||
 | 
			
		||||
    @command
 | 
			
		||||
    def fetch(self):
 | 
			
		||||
        return "You should really buy yourself a dog and start torturing it instead of me..."
 | 
			
		||||
							
								
								
									
										42
									
								
								src/command_system/implementation/hosts.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/command_system/implementation/hosts.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,42 @@
 | 
			
		|||
# Copyright (C) 2009  red-agent <hell.director@gmail.com>
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# This program is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
The module defines a set of command hosts, which are bound to a different
 | 
			
		||||
command processors, which are the source of commands.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from ..framework import CommandHost
 | 
			
		||||
 | 
			
		||||
class ChatCommands(CommandHost):
 | 
			
		||||
    """
 | 
			
		||||
    This command host is bound to the command processor which processes commands
 | 
			
		||||
    from a chat.
 | 
			
		||||
    """
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
class PrivateChatCommands(CommandHost):
 | 
			
		||||
    """
 | 
			
		||||
    This command host is bound to the command processor which processes commands
 | 
			
		||||
    from a private chat.
 | 
			
		||||
    """
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
class GroupChatCommands(CommandHost):
 | 
			
		||||
    """
 | 
			
		||||
    This command host is bound to the command processor which processes commands
 | 
			
		||||
    from a group chat.
 | 
			
		||||
    """
 | 
			
		||||
    pass
 | 
			
		||||
| 
						 | 
				
			
			@ -16,53 +16,70 @@
 | 
			
		|||
"""
 | 
			
		||||
Provides a glue to tie command system framework and the actual code where it
 | 
			
		||||
would be dropped in. Defines a little bit of scaffolding to support interaction
 | 
			
		||||
between the two and a few utility methods so you don't need to dig up the host
 | 
			
		||||
code to write basic commands.
 | 
			
		||||
between the two and a few utility methods so you don't need to dig up the code
 | 
			
		||||
itself code to write basic commands.
 | 
			
		||||
"""
 | 
			
		||||
from common import gajim
 | 
			
		||||
 | 
			
		||||
from types import StringTypes
 | 
			
		||||
from framework import CommandProcessor, CommandError
 | 
			
		||||
from traceback import print_exc
 | 
			
		||||
 | 
			
		||||
class ChatMiddleware(CommandProcessor):
 | 
			
		||||
from common import gajim
 | 
			
		||||
 | 
			
		||||
from ..framework import CommandProcessor
 | 
			
		||||
from ..errors import CommandError
 | 
			
		||||
 | 
			
		||||
class ChatCommandProcessor(CommandProcessor):
 | 
			
		||||
    """
 | 
			
		||||
    Provides basic scaffolding for the convenient interaction with ChatControl.
 | 
			
		||||
    Also provides some few basic utilities for the same purpose.
 | 
			
		||||
    A basic scaffolding to provide convenient interaction between the command
 | 
			
		||||
    system and chat controls.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def execute_command(self, text, name, arguments):
 | 
			
		||||
    def process_as_command(self, text):
 | 
			
		||||
        flag = super(ChatCommandProcessor, self).process_as_command(text)
 | 
			
		||||
        if flag:
 | 
			
		||||
            self.add_history(text)
 | 
			
		||||
            self.clear_input()
 | 
			
		||||
        return flag
 | 
			
		||||
 | 
			
		||||
    def execute_command(self, name, arguments):
 | 
			
		||||
        try:
 | 
			
		||||
            super(ChatMiddleware, self).execute_command(text, name, arguments)
 | 
			
		||||
        except CommandError, exception:
 | 
			
		||||
            self.echo("%s: %s" %(exception.name, exception.message), 'error')
 | 
			
		||||
            super(ChatCommandProcessor, self).execute_command(name, arguments)
 | 
			
		||||
        except CommandError, error:
 | 
			
		||||
            self.echo("%s: %s" %(error.name, error.message), 'error')
 | 
			
		||||
        except Exception:
 | 
			
		||||
            self.echo("An error occured while trying to execute the command", 'error')
 | 
			
		||||
            print_exc()
 | 
			
		||||
        finally:
 | 
			
		||||
            self.add_history(text)
 | 
			
		||||
            self.clear_input()
 | 
			
		||||
 | 
			
		||||
    def looks_like_command(self, text, name, arguments):
 | 
			
		||||
    def looks_like_command(self, text, body, name, arguments):
 | 
			
		||||
        # Command escape stuff ggoes here. If text was prepended by the command
 | 
			
		||||
        # prefix twice, like //not_a_command (if prefix is set to /) then it
 | 
			
		||||
        # will be escaped, that is sent just as a regular message with one (only
 | 
			
		||||
        # one) prefix removed, so message will be /not_a_command.
 | 
			
		||||
        if name.startswith(self.COMMAND_PREFIX):
 | 
			
		||||
            self._say_(self, text)
 | 
			
		||||
        if body.startswith(self.COMMAND_PREFIX):
 | 
			
		||||
            self.send(body)
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
    def command_preprocessor(self, name, command, arguments, args, kwargs):
 | 
			
		||||
    def command_preprocessor(self, command, name, arguments, args, kwargs):
 | 
			
		||||
        # If command argument contain h or help option - forward it to the /help
 | 
			
		||||
        # command. Dont forget to pass self, as all commands are unbound. And
 | 
			
		||||
        # also don't forget to print output.
 | 
			
		||||
        if 'h' in kwargs or 'help' in kwargs:
 | 
			
		||||
            # Forwarding to the /help command. Dont forget to pass self, as
 | 
			
		||||
            # all commands are unbound. And also don't forget to print output.
 | 
			
		||||
            self.echo(self._help_(self, name))
 | 
			
		||||
            help = self.get_command('help')
 | 
			
		||||
            self.echo(help(self, name))
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
    def command_postprocessor(self, name, command, arguments, args, kwargs, value):
 | 
			
		||||
    def command_postprocessor(self, command, name, arguments, args, kwargs, value):
 | 
			
		||||
        # If command returns a string - print it to a user. A convenient and
 | 
			
		||||
        # sufficient in most simple cases shortcut to a using echo.
 | 
			
		||||
        if value and isinstance(value, StringTypes):
 | 
			
		||||
            self.echo(value)
 | 
			
		||||
 | 
			
		||||
class CommandTools:
 | 
			
		||||
    """
 | 
			
		||||
    Contains a set of basic tools and shortcuts you can use in your commands to
 | 
			
		||||
    performe some simple operations.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def echo(self, text, kind='info'):
 | 
			
		||||
        """
 | 
			
		||||
        Print given text to the user.
 | 
			
		||||
| 
						 | 
				
			
			@ -79,8 +96,8 @@ class ChatMiddleware(CommandProcessor):
 | 
			
		|||
        """
 | 
			
		||||
        Set given text into the input.
 | 
			
		||||
        """
 | 
			
		||||
        message_buffer = self.msg_textview.get_buffer()
 | 
			
		||||
        message_buffer.set_text(text)
 | 
			
		||||
        buffer = self.msg_textview.get_buffer()
 | 
			
		||||
        buffer.set_text(text)
 | 
			
		||||
 | 
			
		||||
    def clear_input(self):
 | 
			
		||||
        """
 | 
			
		||||
| 
						 | 
				
			
			@ -90,8 +107,8 @@ class ChatMiddleware(CommandProcessor):
 | 
			
		|||
 | 
			
		||||
    def add_history(self, text):
 | 
			
		||||
        """
 | 
			
		||||
        Add given text to the input history, so user can scroll through it
 | 
			
		||||
        using ctrl + up/down arrow keys.
 | 
			
		||||
        Add given text to the input history, so user can scroll through it using
 | 
			
		||||
        ctrl + up/down arrow keys.
 | 
			
		||||
        """
 | 
			
		||||
        self.save_sent_message(text)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -14,7 +14,7 @@
 | 
			
		|||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
Provides an actual implementation of the standard commands.
 | 
			
		||||
Provides an actual implementation for the standard commands.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import dialogs
 | 
			
		||||
| 
						 | 
				
			
			@ -22,16 +22,19 @@ from common import gajim
 | 
			
		|||
from common import helpers
 | 
			
		||||
from common.exceptions import GajimGeneralException
 | 
			
		||||
 | 
			
		||||
from framework import command, CommandError
 | 
			
		||||
from middleware import ChatMiddleware
 | 
			
		||||
from ..framework import CommandContainer, command
 | 
			
		||||
from ..mapping import generate_usage
 | 
			
		||||
 | 
			
		||||
class CommonCommands(ChatMiddleware):
 | 
			
		||||
from hosts import ChatCommands, PrivateChatCommands, GroupChatCommands
 | 
			
		||||
 | 
			
		||||
class StandardCommonCommands(CommandContainer):
 | 
			
		||||
    """
 | 
			
		||||
    Here defined commands will be common to all, chat, private chat and group
 | 
			
		||||
    chat. Keep in mind that self is set to an instance of either ChatControl,
 | 
			
		||||
    PrivateChatControl or GroupchatControl when command is being called.
 | 
			
		||||
    This command container contains standard commands which are common to all -
 | 
			
		||||
    chat, private chat, group chat.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    HOSTS = (ChatCommands, PrivateChatCommands, GroupChatCommands)
 | 
			
		||||
 | 
			
		||||
    @command
 | 
			
		||||
    def clear(self):
 | 
			
		||||
        """
 | 
			
		||||
| 
						 | 
				
			
			@ -44,24 +47,30 @@ class CommonCommands(ChatMiddleware):
 | 
			
		|||
        """
 | 
			
		||||
        Hide the chat buttons
 | 
			
		||||
        """
 | 
			
		||||
        self.chat_buttons_set_visible(not self.hide_chat_buttons)
 | 
			
		||||
        new_status = not self.hide_chat_buttons
 | 
			
		||||
        self.chat_buttons_set_visible(new_status)
 | 
			
		||||
 | 
			
		||||
    @command(overlap=True)
 | 
			
		||||
    def help(self, command=None, all=False):
 | 
			
		||||
        """
 | 
			
		||||
        Show help on a given command or a list of available commands if -(-a)ll is
 | 
			
		||||
        given
 | 
			
		||||
        Show help on a given command or a list of available commands if -(-a)ll
 | 
			
		||||
        is given
 | 
			
		||||
        """
 | 
			
		||||
        if command:
 | 
			
		||||
            command = self.retrieve_command(command)
 | 
			
		||||
            command = self.get_command(command)
 | 
			
		||||
 | 
			
		||||
            doc = _(command.extract_doc())
 | 
			
		||||
            usage = command.extract_arg_usage()
 | 
			
		||||
            documentation = _(command.extract_documentation())
 | 
			
		||||
            usage = generate_usage(command)
 | 
			
		||||
 | 
			
		||||
            if doc:
 | 
			
		||||
                return (doc + '\n\n' + usage) if command.usage else doc
 | 
			
		||||
            else:
 | 
			
		||||
                return usage
 | 
			
		||||
            text = str()
 | 
			
		||||
 | 
			
		||||
            if documentation:
 | 
			
		||||
                text += documentation
 | 
			
		||||
 | 
			
		||||
            if command.usage:
 | 
			
		||||
                text += ('\n\n' + usage) if text else usage
 | 
			
		||||
 | 
			
		||||
            return text
 | 
			
		||||
        elif all:
 | 
			
		||||
            for command in self.list_commands():
 | 
			
		||||
                names = ', '.join(command.names)
 | 
			
		||||
| 
						 | 
				
			
			@ -69,7 +78,8 @@ class CommonCommands(ChatMiddleware):
 | 
			
		|||
 | 
			
		||||
                self.echo("%s - %s" % (names, description))
 | 
			
		||||
        else:
 | 
			
		||||
            self.echo(self._help_(self, 'help'))
 | 
			
		||||
            help = self.get_command('help')
 | 
			
		||||
            self.echo(help(self, 'help'))
 | 
			
		||||
 | 
			
		||||
    @command(raw=True)
 | 
			
		||||
    def say(self, message):
 | 
			
		||||
| 
						 | 
				
			
			@ -85,15 +95,12 @@ class CommonCommands(ChatMiddleware):
 | 
			
		|||
        """
 | 
			
		||||
        self.send("/me %s" % action)
 | 
			
		||||
 | 
			
		||||
class ChatCommands(CommonCommands):
 | 
			
		||||
class StandardChatCommands(CommandContainer):
 | 
			
		||||
    """
 | 
			
		||||
    Here defined commands will be unique to a chat. Use it as a hoster to provide
 | 
			
		||||
    commands which should be unique to a chat. Keep in mind that self is set to
 | 
			
		||||
    an instance of ChatControl when command is being called.
 | 
			
		||||
    This command container contains standard command which are unique to a chat.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    DISPATCH = True
 | 
			
		||||
    INHERIT = True
 | 
			
		||||
    HOSTS = (ChatCommands,)
 | 
			
		||||
 | 
			
		||||
    @command
 | 
			
		||||
    def ping(self):
 | 
			
		||||
| 
						 | 
				
			
			@ -104,25 +111,21 @@ class ChatCommands(CommonCommands):
 | 
			
		|||
            raise CommandError(_('Command is not supported for zeroconf accounts'))
 | 
			
		||||
        gajim.connections[self.account].sendPing(self.contact)
 | 
			
		||||
 | 
			
		||||
class PrivateChatCommands(CommonCommands):
 | 
			
		||||
class StandardPrivateChatCommands(CommandContainer):
 | 
			
		||||
    """
 | 
			
		||||
    Here defined commands will be unique to a private chat. Use it as a hoster to
 | 
			
		||||
    provide commands which should be unique to a private chat. Keep in mind that
 | 
			
		||||
    self is set to an instance of PrivateChatControl when command is being called.
 | 
			
		||||
    This command container contains standard command which are unique to a
 | 
			
		||||
    private chat.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    DISPATCH = True
 | 
			
		||||
    INHERIT = True
 | 
			
		||||
    HOSTS = (PrivateChatCommands,)
 | 
			
		||||
 | 
			
		||||
class GroupChatCommands(CommonCommands):
 | 
			
		||||
class StandardGroupchatCommands(CommandContainer):
 | 
			
		||||
    """
 | 
			
		||||
    Here defined commands will be unique to a group chat. Use it as a hoster to
 | 
			
		||||
    provide commands which should be unique to a group chat. Keep in mind that
 | 
			
		||||
    self is set to an instance of GroupchatControl when command is being called.
 | 
			
		||||
    This command container contains standard command which are unique to a group
 | 
			
		||||
    chat.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    DISPATCH = True
 | 
			
		||||
    INHERIT = True
 | 
			
		||||
    HOSTS = (GroupChatCommands,)
 | 
			
		||||
 | 
			
		||||
    @command(raw=True)
 | 
			
		||||
    def nick(self, new_nick):
 | 
			
		||||
| 
						 | 
				
			
			@ -192,7 +195,7 @@ class GroupChatCommands(CommonCommands):
 | 
			
		|||
            gajim.interface.instances[self.account]['join_gc'].window.present()
 | 
			
		||||
        except KeyError:
 | 
			
		||||
            try:
 | 
			
		||||
                dialogs.JoinGroupchatWindow(account=self.account, room_jid=jid, nick=nick)
 | 
			
		||||
                dialogs.JoinGroupchatWindow(account=None, room_jid=jid, nick=nick)
 | 
			
		||||
            except GajimGeneralException:
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -257,6 +260,6 @@ class GroupChatCommands(CommonCommands):
 | 
			
		|||
    @command(raw=True)
 | 
			
		||||
    def unblock(self, who):
 | 
			
		||||
        """
 | 
			
		||||
        Allow an occupant to send you public or privates messages
 | 
			
		||||
        Allow an occupant to send you public or private messages
 | 
			
		||||
        """
 | 
			
		||||
        self.on_unblock(None, who)
 | 
			
		||||
							
								
								
									
										350
									
								
								src/command_system/mapping.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										350
									
								
								src/command_system/mapping.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,350 @@
 | 
			
		|||
# Copyright (C) 2009  red-agent <hell.director@gmail.com>
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# This program is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
The module contains routines to parse command arguments and map them to the
 | 
			
		||||
command handler's positonal and keyword arguments.
 | 
			
		||||
 | 
			
		||||
Mapping is done in two stages: 1) parse arguments into positional arguments and
 | 
			
		||||
options; 2) adapt them to the specific command handler according to the command
 | 
			
		||||
properties.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import re
 | 
			
		||||
from types import BooleanType, UnicodeType
 | 
			
		||||
from types import TupleType, ListType
 | 
			
		||||
from operator import itemgetter
 | 
			
		||||
 | 
			
		||||
from errors import DefinitionError, CommandError
 | 
			
		||||
 | 
			
		||||
# Quite complex piece of regular expression logic to parse options and
 | 
			
		||||
# arguments. Might need some tweaking along the way.
 | 
			
		||||
ARG_PATTERN = re.compile(r'(\'|")?(?P<body>(?(1).+?|\S+))(?(1)\1)')
 | 
			
		||||
OPT_PATTERN = re.compile(r'(?<!\w)--?(?P<key>[\w-]+)(?:(?:=|\s)(\'|")?(?P<value>(?(2)[^-]+?|[^-\s]+))(?(2)\2))?')
 | 
			
		||||
 | 
			
		||||
# Option keys needs to be encoded to a specific encoding as Python does not
 | 
			
		||||
# allow to expand dictionary with raw unicode strings as keys from a **kwargs.
 | 
			
		||||
KEY_ENCODING = 'UTF-8'
 | 
			
		||||
 | 
			
		||||
# Defines how complete representation of command usage (generated based on
 | 
			
		||||
# command handler argument specification) will be rendered.
 | 
			
		||||
USAGE_PATTERN = 'Usage: %s %s'
 | 
			
		||||
 | 
			
		||||
def parse_arguments(arguments):
 | 
			
		||||
    """
 | 
			
		||||
    Simple yet effective and sufficient in most cases parser which parses
 | 
			
		||||
    command arguments and returns them as two lists.
 | 
			
		||||
 | 
			
		||||
    First list represents positional arguments as (argument, position), and
 | 
			
		||||
    second representing options as (key, value, position) tuples, where position
 | 
			
		||||
    is a (start, end) span tuple of where it was found in the string.
 | 
			
		||||
 | 
			
		||||
    Options may be given in --long or -short format. As --option=value or
 | 
			
		||||
    --option value or -option value. Keys without values will get None as value.
 | 
			
		||||
 | 
			
		||||
    Arguments and option values that contain spaces may be given as 'one two
 | 
			
		||||
    three' or "one two three"; that is between single or double quotes.
 | 
			
		||||
    """
 | 
			
		||||
    args, opts = [], []
 | 
			
		||||
 | 
			
		||||
    def intersects_opts((given_start, given_end)):
 | 
			
		||||
        """
 | 
			
		||||
        Check if given span intersects with any of options.
 | 
			
		||||
        """
 | 
			
		||||
        for key, value, (start, end) in opts:
 | 
			
		||||
            if given_start >= start and given_end <= end:
 | 
			
		||||
                return True
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    def intersects_args((given_start, given_end)):
 | 
			
		||||
        """
 | 
			
		||||
        Check if given span intersects with any of arguments.
 | 
			
		||||
        """
 | 
			
		||||
        for arg, (start, end) in args:
 | 
			
		||||
            if given_start >= start and given_end <= end:
 | 
			
		||||
                return True
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    for match in re.finditer(OPT_PATTERN, arguments):
 | 
			
		||||
        if match:
 | 
			
		||||
            key = match.group('key')
 | 
			
		||||
            value = match.group('value') or None
 | 
			
		||||
            position = match.span()
 | 
			
		||||
            opts.append((key, value, position))
 | 
			
		||||
 | 
			
		||||
    for match in re.finditer(ARG_PATTERN, arguments):
 | 
			
		||||
        if match:
 | 
			
		||||
            body = match.group('body')
 | 
			
		||||
            position = match.span()
 | 
			
		||||
            args.append((body, position))
 | 
			
		||||
 | 
			
		||||
    # Primitive but sufficiently effective way of disposing of conflicted
 | 
			
		||||
    # sectors. Remove any arguments that intersect with options.
 | 
			
		||||
    for arg, position in args[:]:
 | 
			
		||||
        if intersects_opts(position):
 | 
			
		||||
            args.remove((arg, position))
 | 
			
		||||
 | 
			
		||||
    # Primitive but sufficiently effective way of disposing of conflicted
 | 
			
		||||
    # sectors. Remove any options that intersect with arguments.
 | 
			
		||||
    for key, value, position in opts[:]:
 | 
			
		||||
        if intersects_args(position):
 | 
			
		||||
            opts.remove((key, value, position))
 | 
			
		||||
 | 
			
		||||
    return args, opts
 | 
			
		||||
 | 
			
		||||
def adapt_arguments(command, arguments, args, opts):
 | 
			
		||||
    """
 | 
			
		||||
    Adapt args and opts got from the parser to a specific handler by means of
 | 
			
		||||
    arguments specified on command definition. That is transform them to *args
 | 
			
		||||
    and **kwargs suitable for passing to a command handler.
 | 
			
		||||
 | 
			
		||||
    Dashes (-) in the option names will be converted to underscores. So you can
 | 
			
		||||
    map --one-more-option to a one_more_option=None.
 | 
			
		||||
 | 
			
		||||
    If the initial value of a keyword argument is a boolean (False in most
 | 
			
		||||
    cases) - then this option will be treated as a switch, that is an option
 | 
			
		||||
    which does not take an argument. If a switch is followed by an argument -
 | 
			
		||||
    then this argument will be treated just like a normal positional argument.
 | 
			
		||||
 | 
			
		||||
    If the initial value of a keyword argument is a sequence, that is a tuple or
 | 
			
		||||
    list - then a value of this option will be considered correct only if it is
 | 
			
		||||
    present in the sequence.
 | 
			
		||||
    """
 | 
			
		||||
    spec_args, spec_kwargs, var_args, var_kwargs = command.extract_specification()
 | 
			
		||||
    norm_kwargs = dict(spec_kwargs)
 | 
			
		||||
 | 
			
		||||
    # Quite complex piece of neck-breaking logic to extract raw arguments if
 | 
			
		||||
    # there is more, then one positional argument specified by the command.  In
 | 
			
		||||
    # case if it's just one argument which is the collector - this is fairly
 | 
			
		||||
    # easy. But when it's more then one argument - the neck-breaking logic of
 | 
			
		||||
    # how to retrieve residual arguments as a raw, all in one piece string,
 | 
			
		||||
    # kicks in.
 | 
			
		||||
    if command.raw:
 | 
			
		||||
        if arguments:
 | 
			
		||||
            spec_fix = 1 if command.source else 0
 | 
			
		||||
            spec_len = len(spec_args) - spec_fix
 | 
			
		||||
            arguments_end = len(arguments) - 1
 | 
			
		||||
 | 
			
		||||
            # If there are any optional arguments given they should be either an
 | 
			
		||||
            # unquoted postional argument or part of the raw argument. So we
 | 
			
		||||
            # find all optional arguments that can possibly be unquoted argument
 | 
			
		||||
            # and append them as is to the args.
 | 
			
		||||
            for key, value, (start, end) in opts[:spec_len]:
 | 
			
		||||
                if value:
 | 
			
		||||
                    end -= len(value) + 1
 | 
			
		||||
                    args.append((arguments[start:end], (start, end)))
 | 
			
		||||
                    args.append((value, (end, end + len(value) + 1)))
 | 
			
		||||
                else:
 | 
			
		||||
                    args.append((arguments[start:end], (start, end)))
 | 
			
		||||
 | 
			
		||||
            # We need in-place sort here because after manipulations with
 | 
			
		||||
            # options order of arguments might be wrong and we just can't have
 | 
			
		||||
            # more complex logic to not let that happen.
 | 
			
		||||
            args.sort(key=itemgetter(1))
 | 
			
		||||
 | 
			
		||||
            if spec_len > 1:
 | 
			
		||||
                try:
 | 
			
		||||
                    stopper, (start, end) = args[spec_len - 2]
 | 
			
		||||
                except IndexError:
 | 
			
		||||
                    raise CommandError("Missing arguments", command)
 | 
			
		||||
 | 
			
		||||
                # The essential point of the whole play. After boundaries are
 | 
			
		||||
                # being determined (supposingly correct) we separate raw part
 | 
			
		||||
                # from the rest of arguments, which should be normally
 | 
			
		||||
                # processed.
 | 
			
		||||
                raw = arguments[end:]
 | 
			
		||||
                raw = raw.strip() or None
 | 
			
		||||
 | 
			
		||||
                if not raw and not command.empty:
 | 
			
		||||
                    raise CommandError("Missing arguments", command)
 | 
			
		||||
 | 
			
		||||
                # Discard residual arguments and all of the options as raw
 | 
			
		||||
                # command does not support options and if an option is given it
 | 
			
		||||
                # is rather a part of a raw argument.
 | 
			
		||||
                args = args[:spec_len - 1]
 | 
			
		||||
                opts = []
 | 
			
		||||
 | 
			
		||||
                args.append((raw, (end, arguments_end)))
 | 
			
		||||
            else:
 | 
			
		||||
                # Substitue all of the arguments with only one, which contain
 | 
			
		||||
                # raw and unprocessed arguments as a string. And discard all the
 | 
			
		||||
                # options, as raw command does not support them.
 | 
			
		||||
                args = [(arguments, (0, arguments_end))]
 | 
			
		||||
                opts = []
 | 
			
		||||
        else:
 | 
			
		||||
            if command.empty:
 | 
			
		||||
                args.append((None, (0, 0)))
 | 
			
		||||
            else:
 | 
			
		||||
                raise CommandError("Missing arguments", command)
 | 
			
		||||
 | 
			
		||||
    # The first stage of transforming options we have got to a format that can
 | 
			
		||||
    # be used to associate them with declared keyword arguments.  Substituting
 | 
			
		||||
    # dashes (-) in their names with underscores (_).
 | 
			
		||||
    for index, (key, value, position) in enumerate(opts):
 | 
			
		||||
        if '-' in key:
 | 
			
		||||
            opts[index] = (key.replace('-', '_'), value, position)
 | 
			
		||||
 | 
			
		||||
    # The second stage of transforming options to an associatable state.
 | 
			
		||||
    # Expanding short, one-letter options to a verbose ones, if corresponding
 | 
			
		||||
    # optin has been given.
 | 
			
		||||
    if command.expand_short:
 | 
			
		||||
        expanded = []
 | 
			
		||||
        for spec_key, spec_value in norm_kwargs.iteritems():
 | 
			
		||||
            letter = spec_key[0] if len(spec_key) > 1 else None
 | 
			
		||||
            if letter and letter not in expanded:
 | 
			
		||||
                for index, (key, value, position) in enumerate(opts):
 | 
			
		||||
                    if key == letter:
 | 
			
		||||
                        expanded.append(letter)
 | 
			
		||||
                        opts[index] = (spec_key, value, position)
 | 
			
		||||
                        break
 | 
			
		||||
 | 
			
		||||
    # Detect switches and set their values accordingly. If any of them carries a
 | 
			
		||||
    # value - append it to args.
 | 
			
		||||
    for index, (key, value, position) in enumerate(opts):
 | 
			
		||||
        if isinstance(norm_kwargs.get(key), BooleanType):
 | 
			
		||||
            opts[index] = (key, True, position)
 | 
			
		||||
            if value:
 | 
			
		||||
                args.append((value, position))
 | 
			
		||||
 | 
			
		||||
    # Sorting arguments and options (just to be sure) in regarding to their
 | 
			
		||||
    # positions in the string.
 | 
			
		||||
    args.sort(key=itemgetter(1))
 | 
			
		||||
    opts.sort(key=itemgetter(2))
 | 
			
		||||
 | 
			
		||||
    # Stripping down position information supplied with arguments and options as
 | 
			
		||||
    # it won't be needed again.
 | 
			
		||||
    args = map(lambda (arg, position): arg, args)
 | 
			
		||||
    opts = map(lambda (key, value, position): (key, value), opts)
 | 
			
		||||
 | 
			
		||||
    # If command has extra option enabled - collect all extra arguments and pass
 | 
			
		||||
    # them to a last positional argument command defines as a list.
 | 
			
		||||
    if command.extra:
 | 
			
		||||
        if not var_args:
 | 
			
		||||
            spec_fix = 1 if not command.source else 2
 | 
			
		||||
            spec_len = len(spec_args) - spec_fix
 | 
			
		||||
            extra = args[spec_len:]
 | 
			
		||||
            args = args[:spec_len]
 | 
			
		||||
            args.append(extra)
 | 
			
		||||
        else:
 | 
			
		||||
            raise DefinitionError("Can not have both, extra and *args")
 | 
			
		||||
 | 
			
		||||
    # Detect if positional arguments overlap keyword arguments. If so and this
 | 
			
		||||
    # is allowed by command options - then map them directly to their options,
 | 
			
		||||
    # so they can get propert further processings.
 | 
			
		||||
    spec_fix = 1 if command.source else 0
 | 
			
		||||
    spec_len = len(spec_args) - spec_fix
 | 
			
		||||
    if len(args) > spec_len:
 | 
			
		||||
        if command.overlap:
 | 
			
		||||
            overlapped = args[spec_len:]
 | 
			
		||||
            args = args[:spec_len]
 | 
			
		||||
            for arg, (spec_key, spec_value) in zip(overlapped, spec_kwargs):
 | 
			
		||||
                opts.append((spec_key, arg))
 | 
			
		||||
        else:
 | 
			
		||||
            raise CommandError("Excessive arguments", command)
 | 
			
		||||
 | 
			
		||||
    # Detect every switch and ensure it will not receive any arguments.
 | 
			
		||||
    # Normally this does not happen unless overlapping is enabled.
 | 
			
		||||
    for key, value in opts:
 | 
			
		||||
        initial = norm_kwargs.get(key)
 | 
			
		||||
        if isinstance(initial, BooleanType):
 | 
			
		||||
            if not isinstance(value, BooleanType):
 | 
			
		||||
                raise CommandError("%s: Switch can not take an argument" % key, command)
 | 
			
		||||
 | 
			
		||||
    # Detect every sequence constraint and ensure that if corresponding options
 | 
			
		||||
    # are given - they contain proper values, within the constraint range.
 | 
			
		||||
    for key, value in opts:
 | 
			
		||||
        initial = norm_kwargs.get(key)
 | 
			
		||||
        if isinstance(initial, (TupleType, ListType)):
 | 
			
		||||
            if value not in initial:
 | 
			
		||||
                raise CommandError("%s: Invalid argument" % key, command)
 | 
			
		||||
 | 
			
		||||
    # We need to encode every keyword argument to a simple string, not the
 | 
			
		||||
    # unicode one, because ** expansion does not support it.
 | 
			
		||||
    for index, (key, value) in enumerate(opts):
 | 
			
		||||
        if isinstance(key, UnicodeType):
 | 
			
		||||
            opts[index] = (key.encode(KEY_ENCODING), value)
 | 
			
		||||
 | 
			
		||||
    # Inject the source arguments as a string as a first argument, if command
 | 
			
		||||
    # has enabled the corresponding option.
 | 
			
		||||
    if command.source:
 | 
			
		||||
        args.insert(0, arguments)
 | 
			
		||||
 | 
			
		||||
    # Return *args and **kwargs in the form suitable for passing to a command
 | 
			
		||||
    # handler and being expanded.
 | 
			
		||||
    return tuple(args), dict(opts)
 | 
			
		||||
 | 
			
		||||
def generate_usage(command, complete=True):
 | 
			
		||||
    """
 | 
			
		||||
    Extract handler's arguments specification and wrap them in a human-readable
 | 
			
		||||
    format usage information. If complete is given - then USAGE_PATTERN will be
 | 
			
		||||
    used to render the specification completly.
 | 
			
		||||
    """
 | 
			
		||||
    spec_args, spec_kwargs, var_args, var_kwargs = command.extract_specification()
 | 
			
		||||
 | 
			
		||||
    # Remove some special positional arguments from the specifiaction, but store
 | 
			
		||||
    # their names so they can be used for usage info generation.
 | 
			
		||||
    sp_source = spec_args.pop(0) if command.source else None
 | 
			
		||||
    sp_extra = spec_args.pop() if command.extra else None
 | 
			
		||||
 | 
			
		||||
    kwargs = []
 | 
			
		||||
    letters = []
 | 
			
		||||
 | 
			
		||||
    for key, value in spec_kwargs:
 | 
			
		||||
        letter = key[0]
 | 
			
		||||
        key = key.replace('_', '-')
 | 
			
		||||
 | 
			
		||||
        if isinstance(value, BooleanType):
 | 
			
		||||
            value = str()
 | 
			
		||||
        elif isinstance(value, (TupleType, ListType)):
 | 
			
		||||
            value = '={%s}' % ', '.join(value)
 | 
			
		||||
        else:
 | 
			
		||||
            value = '=%s' % value
 | 
			
		||||
 | 
			
		||||
        if letter not in letters:
 | 
			
		||||
            kwargs.append('-(-%s)%s%s' % (letter, key[1:], value))
 | 
			
		||||
            letters.append(letter)
 | 
			
		||||
        else:
 | 
			
		||||
            kwargs.append('--%s%s' % (key, value))
 | 
			
		||||
 | 
			
		||||
    usage = str()
 | 
			
		||||
    args = str()
 | 
			
		||||
 | 
			
		||||
    if command.raw:
 | 
			
		||||
        spec_len = len(spec_args) - 1
 | 
			
		||||
        if spec_len:
 | 
			
		||||
            args += ('<%s>' % ', '.join(spec_args[:spec_len])) + ' '
 | 
			
		||||
        args += ('(|%s|)' if command.empty else '|%s|') % spec_args[-1]
 | 
			
		||||
    else:
 | 
			
		||||
        if spec_args:
 | 
			
		||||
            args += '<%s>' % ', '.join(spec_args)
 | 
			
		||||
        if var_args or sp_extra:
 | 
			
		||||
            args += (' ' if spec_args else str()) + '<<%s>>' % (var_args or sp_extra)
 | 
			
		||||
 | 
			
		||||
    usage += args
 | 
			
		||||
 | 
			
		||||
    if kwargs or var_kwargs:
 | 
			
		||||
        if kwargs:
 | 
			
		||||
            usage += (' ' if args else str()) + '[%s]' % ', '.join(kwargs)
 | 
			
		||||
        if var_kwargs:
 | 
			
		||||
            usage += (' ' if args else str()) + '[[%s]]' % var_kwargs
 | 
			
		||||
 | 
			
		||||
    # Native name will be the first one if it is included. Otherwise, names will
 | 
			
		||||
    # be in the order they were specified.
 | 
			
		||||
    if len(command.names) > 1:
 | 
			
		||||
        names = '%s (%s)' % (command.first_name, ', '.join(command.names[1:]))
 | 
			
		||||
    else:
 | 
			
		||||
        names = command.first_name
 | 
			
		||||
 | 
			
		||||
    return USAGE_PATTERN % (names, usage) if complete else usage
 | 
			
		||||
| 
						 | 
				
			
			@ -1,88 +0,0 @@
 | 
			
		|||
# Copyright (C) 2009  red-agent <hell.director@gmail.com>
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# This program is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
This module contains examples of how to create your own commands by creating an
 | 
			
		||||
adhoc command processor. Each adhoc command processor should be hosted by one or
 | 
			
		||||
more which dispatch the real deal and droppped in to where it belongs.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from framework import command
 | 
			
		||||
from implementation import ChatCommands, PrivateChatCommands, GroupChatCommands
 | 
			
		||||
 | 
			
		||||
class CustomCommonCommands(ChatCommands, PrivateChatCommands, GroupChatCommands):
 | 
			
		||||
    """
 | 
			
		||||
    This adhoc processor will be hosted by a multiple processors which dispatch
 | 
			
		||||
    commands from all, chat, private chat and group chat. So commands defined
 | 
			
		||||
    here will be available to all of them.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    DISPATCH = True
 | 
			
		||||
    HOSTED_BY = ChatCommands, PrivateChatCommands, GroupChatCommands
 | 
			
		||||
 | 
			
		||||
    @command
 | 
			
		||||
    def dance(self):
 | 
			
		||||
        """
 | 
			
		||||
        First line of the doc string is called a description and will be
 | 
			
		||||
        programmatically extracted.
 | 
			
		||||
 | 
			
		||||
        After that you can give more help, like explanation of the options. This
 | 
			
		||||
        one will be programatically extracted and formatted too. After this one
 | 
			
		||||
        there will be autogenerated (based on the method signature) usage
 | 
			
		||||
        information appended. You can turn it off though, if you want.
 | 
			
		||||
        """
 | 
			
		||||
        return "I can't dance, you stupid fuck, I'm just a command system! A cool one, though..."
 | 
			
		||||
 | 
			
		||||
class CustomChatCommands(ChatCommands):
 | 
			
		||||
    """
 | 
			
		||||
    This adhoc processor will be hosted by a ChatCommands processor which
 | 
			
		||||
    dispatches commands from a chat. So commands defined here will be available
 | 
			
		||||
    only to a chat.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    DISPATCH = True
 | 
			
		||||
    HOSTED_BY = ChatCommands
 | 
			
		||||
 | 
			
		||||
    @command
 | 
			
		||||
    def sing(self):
 | 
			
		||||
        return "Are you phreaking kidding me? Buy yourself a damn stereo..."
 | 
			
		||||
 | 
			
		||||
class CustomPrivateChatCommands(PrivateChatCommands):
 | 
			
		||||
    """
 | 
			
		||||
    This adhoc processor will be hosted by a PrivateChatCommands processor which
 | 
			
		||||
    dispatches commands from a private chat. So commands defined here will be
 | 
			
		||||
    available only to a private chat.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    DISPATCH = True
 | 
			
		||||
    HOSTED_BY = PrivateChatCommands
 | 
			
		||||
 | 
			
		||||
    @command
 | 
			
		||||
    def make_coffee(self):
 | 
			
		||||
        return "What do I look like, you ass? A coffee machine!?"
 | 
			
		||||
 | 
			
		||||
class CustomGroupChatCommands(GroupChatCommands):
 | 
			
		||||
    """
 | 
			
		||||
    This adhoc processor will be hosted by a GroupChatCommands processor which
 | 
			
		||||
    dispatches commands from a group chat. So commands defined here will be
 | 
			
		||||
    available only to a group chat.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    DISPATCH = True
 | 
			
		||||
    HOSTED_BY = GroupChatCommands
 | 
			
		||||
 | 
			
		||||
    @command
 | 
			
		||||
    def fetch(self):
 | 
			
		||||
        return "You should really buy yourself a dog and start torturing it instead of me..."
 | 
			
		||||
| 
						 | 
				
			
			@ -1,765 +0,0 @@
 | 
			
		|||
# Copyright (C) 2009  red-agent <hell.director@gmail.com>
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# This program is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
Provides a tiny framework with simple, yet powerful and extensible architecture
 | 
			
		||||
to implement commands in a streight and flexible, declarative way.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import re
 | 
			
		||||
from types import FunctionType, UnicodeType, TupleType, ListType, BooleanType
 | 
			
		||||
from inspect import getargspec
 | 
			
		||||
from operator import itemgetter
 | 
			
		||||
 | 
			
		||||
class InternalError(Exception):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
class CommandError(Exception):
 | 
			
		||||
    def __init__(self, message=None, command=None, name=None):
 | 
			
		||||
        self.command = command
 | 
			
		||||
        self.name = name
 | 
			
		||||
 | 
			
		||||
        if command:
 | 
			
		||||
            self.name = command.first_name
 | 
			
		||||
 | 
			
		||||
        if message:
 | 
			
		||||
            super(CommandError, self).__init__(message)
 | 
			
		||||
        else:
 | 
			
		||||
            super(CommandError, self).__init__()
 | 
			
		||||
 | 
			
		||||
class Command(object):
 | 
			
		||||
 | 
			
		||||
    DOC_STRIP_PATTERN = re.compile(r'(?:^[ \t]+|\A\n)', re.MULTILINE)
 | 
			
		||||
    DOC_FORMAT_PATTERN = re.compile(r'(?<!\n)\n(?!\n)', re.MULTILINE)
 | 
			
		||||
 | 
			
		||||
    ARG_USAGE_PATTERN = 'Usage: %s %s'
 | 
			
		||||
 | 
			
		||||
    def __init__(self, handler, usage, source, raw, extra, overlap, empty, expand_short):
 | 
			
		||||
        self.handler = handler
 | 
			
		||||
 | 
			
		||||
        self.usage = usage
 | 
			
		||||
        self.source = source
 | 
			
		||||
        self.raw = raw
 | 
			
		||||
        self.extra = extra
 | 
			
		||||
        self.overlap = overlap
 | 
			
		||||
        self.empty = empty
 | 
			
		||||
        self.expand_short = expand_short
 | 
			
		||||
 | 
			
		||||
    def __call__(self, *args, **kwargs):
 | 
			
		||||
        try:
 | 
			
		||||
            return self.handler(*args, **kwargs)
 | 
			
		||||
        except CommandError, exception:
 | 
			
		||||
            # Re-raise an excepttion with a proper command attribute set,
 | 
			
		||||
            # unless it is already set by the one who raised an exception.
 | 
			
		||||
            if not exception.command and not exception.name:
 | 
			
		||||
                raise CommandError(exception.message, self)
 | 
			
		||||
 | 
			
		||||
            # Do not forget to re-raise an exception just like it was if at
 | 
			
		||||
            # least either, command or name attribute is set properly.
 | 
			
		||||
            raise
 | 
			
		||||
 | 
			
		||||
        # This one is a little bit too wide, but as Python does not have
 | 
			
		||||
        # anything more constrained - there is no other choice. Take a look here
 | 
			
		||||
        # if command complains about invalid arguments while they are ok.
 | 
			
		||||
        except TypeError:
 | 
			
		||||
            raise CommandError("Command received invalid arguments", self)
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return "<Command %s>" % ', '.join(self.names)
 | 
			
		||||
 | 
			
		||||
    def __cmp__(self, other):
 | 
			
		||||
        """
 | 
			
		||||
        Comparison is implemented based on a first name.
 | 
			
		||||
        """
 | 
			
		||||
        return cmp(self.first_name, other.first_name)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def first_name(self):
 | 
			
		||||
        return self.names[0]
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def native_name(self):
 | 
			
		||||
        return self.handler.__name__
 | 
			
		||||
 | 
			
		||||
    def extract_doc(self):
 | 
			
		||||
        """
 | 
			
		||||
        Extract handler's doc-string and transform it to a usable format.
 | 
			
		||||
        """
 | 
			
		||||
        doc = self.handler.__doc__ or None
 | 
			
		||||
 | 
			
		||||
        if not doc:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        doc = re.sub(self.DOC_STRIP_PATTERN, str(), doc)
 | 
			
		||||
        doc = re.sub(self.DOC_FORMAT_PATTERN, ' ', doc)
 | 
			
		||||
 | 
			
		||||
        return doc
 | 
			
		||||
 | 
			
		||||
    def extract_description(self):
 | 
			
		||||
        """
 | 
			
		||||
        Extract handler's description (which is a first line of the doc). Try to
 | 
			
		||||
        keep them simple yet meaningful.
 | 
			
		||||
        """
 | 
			
		||||
        doc = self.extract_doc()
 | 
			
		||||
        return doc.split('\n', 1)[0] if doc else None
 | 
			
		||||
 | 
			
		||||
    def extract_arg_spec(self):
 | 
			
		||||
        names, var_args, var_kwargs, defaults = getargspec(self.handler)
 | 
			
		||||
 | 
			
		||||
        # Behavior of this code need to be checked. Might yield incorrect
 | 
			
		||||
        # results on some rare occasions.
 | 
			
		||||
        spec_args = names[:-len(defaults) if defaults else len(names)]
 | 
			
		||||
        spec_kwargs = list(zip(names[-len(defaults):], defaults)) if defaults else {}
 | 
			
		||||
 | 
			
		||||
        # Removing self from arguments specification. Command handler should
 | 
			
		||||
        # normally be an instance method.
 | 
			
		||||
        if spec_args.pop(0) != 'self':
 | 
			
		||||
            raise InternalError("First argument must be self")
 | 
			
		||||
 | 
			
		||||
        return spec_args, spec_kwargs, var_args, var_kwargs
 | 
			
		||||
 | 
			
		||||
    def extract_arg_usage(self, complete=True):
 | 
			
		||||
        """
 | 
			
		||||
        Extract handler's arguments specification and wrap them in a
 | 
			
		||||
        human-readable format. If complete is given - then ARG_USAGE_PATTERN
 | 
			
		||||
        will be used to render it completly.
 | 
			
		||||
        """
 | 
			
		||||
        spec_args, spec_kwargs, var_args, var_kwargs = self.extract_arg_spec()
 | 
			
		||||
 | 
			
		||||
        # Remove some special positional arguments from the specifiaction, but
 | 
			
		||||
        # store their names so they can be used for usage info generation.
 | 
			
		||||
        sp_source = spec_args.pop(0) if self.source else None
 | 
			
		||||
        sp_extra = spec_args.pop() if self.extra else None
 | 
			
		||||
 | 
			
		||||
        kwargs = []
 | 
			
		||||
        letters = []
 | 
			
		||||
 | 
			
		||||
        for key, value in spec_kwargs:
 | 
			
		||||
            letter = key[0]
 | 
			
		||||
            key = key.replace('_', '-')
 | 
			
		||||
 | 
			
		||||
            if isinstance(value, BooleanType):
 | 
			
		||||
                value = str()
 | 
			
		||||
            elif isinstance(value, (TupleType, ListType)):
 | 
			
		||||
                value = '={%s}' % ', '.join(value)
 | 
			
		||||
            else:
 | 
			
		||||
                value = '=%s' % value
 | 
			
		||||
 | 
			
		||||
            if letter not in letters:
 | 
			
		||||
                kwargs.append('-(-%s)%s%s' % (letter, key[1:], value))
 | 
			
		||||
                letters.append(letter)
 | 
			
		||||
            else:
 | 
			
		||||
                kwargs.append('--%s%s' % (key, value))
 | 
			
		||||
 | 
			
		||||
        usage = str()
 | 
			
		||||
        args = str()
 | 
			
		||||
 | 
			
		||||
        if self.raw:
 | 
			
		||||
            spec_len = len(spec_args) - 1
 | 
			
		||||
            if spec_len:
 | 
			
		||||
                args += ('<%s>' % ', '.join(spec_args[:spec_len])) + ' '
 | 
			
		||||
            args += ('(|%s|)' if self.empty else '|%s|') % spec_args[-1]
 | 
			
		||||
        else:
 | 
			
		||||
            if spec_args:
 | 
			
		||||
                args += '<%s>' % ', '.join(spec_args)
 | 
			
		||||
            if var_args or sp_extra:
 | 
			
		||||
                args += (' ' if spec_args else str()) + '<<%s>>' % (var_args or sp_extra)
 | 
			
		||||
 | 
			
		||||
        usage += args
 | 
			
		||||
 | 
			
		||||
        if kwargs or var_kwargs:
 | 
			
		||||
            if kwargs:
 | 
			
		||||
                usage += (' ' if args else str()) + '[%s]' % ', '.join(kwargs)
 | 
			
		||||
            if var_kwargs:
 | 
			
		||||
                usage += (' ' if args else str()) + '[[%s]]' % var_kwargs
 | 
			
		||||
 | 
			
		||||
        # Native name will be the first one if it is included. Otherwise, names
 | 
			
		||||
        # will be in the order they were specified.
 | 
			
		||||
        if len(self.names) > 1:
 | 
			
		||||
            names = '%s (%s)' % (self.first_name, ', '.join(self.names[1:]))
 | 
			
		||||
        else:
 | 
			
		||||
            names = self.first_name
 | 
			
		||||
 | 
			
		||||
        return usage if not complete else self.ARG_USAGE_PATTERN % (names, usage)
 | 
			
		||||
 | 
			
		||||
class Dispatcher(type):
 | 
			
		||||
    table = {}
 | 
			
		||||
    hosted = {}
 | 
			
		||||
 | 
			
		||||
    def __init__(cls, name, bases, dct):
 | 
			
		||||
        dispatchable = Dispatcher.check_if_dispatchable(bases, dct)
 | 
			
		||||
        hostable = Dispatcher.check_if_hostable(bases, dct)
 | 
			
		||||
 | 
			
		||||
        cls.check_if_conformed(dispatchable, hostable)
 | 
			
		||||
 | 
			
		||||
        if Dispatcher.is_suitable(cls, dct):
 | 
			
		||||
            Dispatcher.register_processor(cls)
 | 
			
		||||
 | 
			
		||||
        # Sanitize names even if processor is not suitable for registering,
 | 
			
		||||
        # because it might be inherited by an another processor.
 | 
			
		||||
        Dispatcher.sanitize_names(cls)
 | 
			
		||||
 | 
			
		||||
        super(Dispatcher, cls).__init__(name, bases, dct)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def is_suitable(cls, proc, dct):
 | 
			
		||||
        is_not_root = dct.get('__metaclass__') is not cls
 | 
			
		||||
        to_be_dispatched = bool(dct.get('DISPATCH'))
 | 
			
		||||
        return is_not_root and to_be_dispatched
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def check_if_dispatchable(cls, bases, dct):
 | 
			
		||||
        dispatcher = dct.get('DISPATCHED_BY')
 | 
			
		||||
        if not dispatcher:
 | 
			
		||||
            return False
 | 
			
		||||
        if dispatcher not in bases:
 | 
			
		||||
            raise InternalError("Should be dispatched by the same processor it inherits from")
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def check_if_hostable(cls, bases, dct):
 | 
			
		||||
        hosters = dct.get('HOSTED_BY')
 | 
			
		||||
        if not hosters:
 | 
			
		||||
            return False
 | 
			
		||||
        if not isinstance(hosters, (TupleType, ListType)):
 | 
			
		||||
            hosters = (hosters,)
 | 
			
		||||
        for hoster in hosters:
 | 
			
		||||
            if hoster not in bases:
 | 
			
		||||
                raise InternalError("Should be hosted by the same processors it inherits from")
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def check_if_conformed(cls, dispatchable, hostable):
 | 
			
		||||
        if dispatchable and hostable:
 | 
			
		||||
            raise InternalError("Processor can not be dispatchable and hostable at the same time")
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def register_processor(cls, proc):
 | 
			
		||||
        cls.table[proc] = {}
 | 
			
		||||
        inherit = proc.__dict__.get('INHERIT')
 | 
			
		||||
 | 
			
		||||
        if 'HOSTED_BY' in proc.__dict__:
 | 
			
		||||
            cls.register_adhocs(proc)
 | 
			
		||||
 | 
			
		||||
        commands = cls.traverse_commands(proc, inherit)
 | 
			
		||||
        cls.register_commands(proc, commands)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def sanitize_names(cls, proc):
 | 
			
		||||
        inherit = proc.__dict__.get('INHERIT')
 | 
			
		||||
        commands = cls.traverse_commands(proc, inherit)
 | 
			
		||||
        for key, command in commands:
 | 
			
		||||
            if not proc.SAFE_NAME_SCAN_PATTERN.match(key):
 | 
			
		||||
                setattr(proc, proc.SAFE_NAME_SUBS_PATTERN % key, command)
 | 
			
		||||
                try:
 | 
			
		||||
                    delattr(proc, key)
 | 
			
		||||
                except AttributeError:
 | 
			
		||||
                    pass
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def traverse_commands(cls, proc, inherit=True):
 | 
			
		||||
        keys = dir(proc) if inherit else proc.__dict__.iterkeys()
 | 
			
		||||
        for key in keys:
 | 
			
		||||
            value = getattr(proc, key)
 | 
			
		||||
            if isinstance(value, Command):
 | 
			
		||||
                yield key, value
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def register_commands(cls, proc, commands):
 | 
			
		||||
        for key, command in commands:
 | 
			
		||||
            for name in command.names:
 | 
			
		||||
                name = proc.prepare_name(name)
 | 
			
		||||
                if name not in cls.table[proc]:
 | 
			
		||||
                    cls.table[proc][name] = command
 | 
			
		||||
                else:
 | 
			
		||||
                    raise InternalError("Command with name %s already exists" % name)
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def register_adhocs(cls, proc):
 | 
			
		||||
        hosters = proc.HOSTED_BY
 | 
			
		||||
        if not isinstance(hosters, (TupleType, ListType)):
 | 
			
		||||
            hosters = (hosters,)
 | 
			
		||||
        for hoster in hosters:
 | 
			
		||||
            if hoster in cls.hosted:
 | 
			
		||||
                cls.hosted[hoster].append(proc)
 | 
			
		||||
            else:
 | 
			
		||||
                cls.hosted[hoster] = [proc]
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def retrieve_command(cls, proc, name):
 | 
			
		||||
        command = cls.table[proc.DISPATCHED_BY].get(name)
 | 
			
		||||
        if command:
 | 
			
		||||
            return command
 | 
			
		||||
        if proc.DISPATCHED_BY in cls.hosted:
 | 
			
		||||
            for adhoc in cls.hosted[proc.DISPATCHED_BY]:
 | 
			
		||||
                command = cls.table[adhoc].get(name)
 | 
			
		||||
                if command:
 | 
			
		||||
                    return command
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def list_commands(cls, proc):
 | 
			
		||||
        commands = dict(cls.traverse_commands(proc.DISPATCHED_BY))
 | 
			
		||||
        if proc.DISPATCHED_BY in cls.hosted:
 | 
			
		||||
            for adhoc in cls.hosted[proc.DISPATCHED_BY]:
 | 
			
		||||
                inherit = adhoc.__dict__.get('INHERIT')
 | 
			
		||||
                commands.update(dict(cls.traverse_commands(adhoc, inherit)))
 | 
			
		||||
        return commands.values()
 | 
			
		||||
 | 
			
		||||
class CommandProcessor(object):
 | 
			
		||||
    """
 | 
			
		||||
    A base class for a drop-in command processor which you can drop (make your
 | 
			
		||||
    class to inherit from it) in any of your classes to support commands. In
 | 
			
		||||
    order to get it done you need to make your own processor, inheriter from
 | 
			
		||||
    CommandProcessor and then drop it in. Don't forget about few important steps
 | 
			
		||||
    described below.
 | 
			
		||||
 | 
			
		||||
    Every command in the processor (normally) will gain full access through self
 | 
			
		||||
    to an object you are adding commands to.
 | 
			
		||||
 | 
			
		||||
    Your subclass, which will contain commands should define in its body
 | 
			
		||||
    DISPATCH = True in order to be included in the dispatching table.
 | 
			
		||||
 | 
			
		||||
    Every class you will drop the processor in should define DISPATCHED_BY set
 | 
			
		||||
    to the same processor you are inheriting from.
 | 
			
		||||
 | 
			
		||||
    Names of the commands after preparation stuff id done will be sanitized
 | 
			
		||||
    (based on SAFE_NAME_SCAN_PATTERN and SAFE_NAME_SUBS_PATTERN) in order not to
 | 
			
		||||
    interfere with the methods defined in a class you will drop a processor in.
 | 
			
		||||
 | 
			
		||||
    If you want to create an adhoc processor (then one that parasites on the
 | 
			
		||||
    other one (the host), so it does not have to be included directly into
 | 
			
		||||
    whatever includes the host) you need to inherit you processor from the host
 | 
			
		||||
    and set HOSTED_BY to that host.
 | 
			
		||||
 | 
			
		||||
    INHERIT controls whether commands inherited from base classes (which could
 | 
			
		||||
    include other processors) will be registered or not. This is disabled
 | 
			
		||||
    by-default because it leads to unpredictable consequences when used in adhoc
 | 
			
		||||
    processors which inherit from more then one processor or has such processors
 | 
			
		||||
    in its inheritance tree. In that case - encapsulation is being broken and
 | 
			
		||||
    some (all) commands are shared between non-related processors.
 | 
			
		||||
    """
 | 
			
		||||
    __metaclass__ = Dispatcher
 | 
			
		||||
 | 
			
		||||
    SAFE_NAME_SCAN_PATTERN = re.compile(r'_(?P<name>\w+)_')
 | 
			
		||||
    SAFE_NAME_SUBS_PATTERN = '_%s_'
 | 
			
		||||
 | 
			
		||||
    # Quite complex piece of regular expression logic.
 | 
			
		||||
    ARG_PATTERN = re.compile(r'(\'|")?(?P<body>(?(1).+?|\S+))(?(1)\1)')
 | 
			
		||||
    OPT_PATTERN = re.compile(r'(?<!\w)--?(?P<key>[\w-]+)(?:(?:=|\s)(\'|")?(?P<value>(?(2)[^-]+?|[^-\s]+))(?(2)\2))?')
 | 
			
		||||
 | 
			
		||||
    COMMAND_PREFIX = '/'
 | 
			
		||||
    CASE_SENSITIVE_COMMANDS = False
 | 
			
		||||
 | 
			
		||||
    ARG_ENCODING = 'utf8'
 | 
			
		||||
 | 
			
		||||
    def __getattr__(self, name):
 | 
			
		||||
        """
 | 
			
		||||
        This allows to reach and directly (internally) call commands which are
 | 
			
		||||
        defined in (other) adhoc processors.
 | 
			
		||||
        """
 | 
			
		||||
        command_name = self.SAFE_NAME_SCAN_PATTERN.match(name)
 | 
			
		||||
        if command_name:
 | 
			
		||||
            command = self.retrieve_command(command_name.group('name'))
 | 
			
		||||
            if command:
 | 
			
		||||
                return command
 | 
			
		||||
        raise AttributeError(name)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def prepare_name(cls, name):
 | 
			
		||||
        return name if cls.CASE_SENSITIVE_COMMANDS else name.lower()
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def retrieve_command(cls, name):
 | 
			
		||||
        name = cls.prepare_name(name)
 | 
			
		||||
        command = Dispatcher.retrieve_command(cls, name)
 | 
			
		||||
        if not command:
 | 
			
		||||
            raise CommandError("Command does not exist", name=name)
 | 
			
		||||
        return command
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def list_commands(cls):
 | 
			
		||||
        commands = Dispatcher.list_commands(cls)
 | 
			
		||||
        return sorted(set(commands))
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def parse_command_arguments(cls, arguments):
 | 
			
		||||
        """
 | 
			
		||||
        Simple yet effective and sufficient in most cases parser which parses
 | 
			
		||||
        command arguments and returns them as two lists. First represents
 | 
			
		||||
        positional arguments as (argument, position), and second representing
 | 
			
		||||
        options as (key, value, position) tuples, where position is a (start,
 | 
			
		||||
        end) span tuple of where it was found in the string.
 | 
			
		||||
 | 
			
		||||
        The format of the input arguments should be:
 | 
			
		||||
            <arg1, arg2> <<extra>> [-(-o)ption=value1, -(-a)nother=value2] [[extra_options]]
 | 
			
		||||
 | 
			
		||||
        Options may be given in --long or -short format. As --option=value or
 | 
			
		||||
        --option value or -option value. Keys without values will get True as
 | 
			
		||||
        value. Arguments and option values that contain spaces may be given as
 | 
			
		||||
        'one two three' or "one two three"; that is between single or double
 | 
			
		||||
        quotes.
 | 
			
		||||
        """
 | 
			
		||||
        args, opts = [], []
 | 
			
		||||
 | 
			
		||||
        def intersects_opts((given_start, given_end)):
 | 
			
		||||
            """
 | 
			
		||||
            Check if something intersects with boundaries of any parsed option.
 | 
			
		||||
            """
 | 
			
		||||
            for key, value, (start, end) in opts:
 | 
			
		||||
                if given_start >= start and given_end <= end:
 | 
			
		||||
                    return True
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        def intersects_args((given_start, given_end)):
 | 
			
		||||
            """
 | 
			
		||||
            Check if something intersects with boundaries of any parsed argument.
 | 
			
		||||
            """
 | 
			
		||||
            for arg, (start, end) in args:
 | 
			
		||||
                if given_start >= start and given_end <= end:
 | 
			
		||||
                    return True
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        for match in re.finditer(cls.OPT_PATTERN, arguments):
 | 
			
		||||
            if match:
 | 
			
		||||
                key = match.group('key')
 | 
			
		||||
                value = match.group('value') or None
 | 
			
		||||
                position = match.span()
 | 
			
		||||
                opts.append((key, value, position))
 | 
			
		||||
 | 
			
		||||
        for match in re.finditer(cls.ARG_PATTERN, arguments):
 | 
			
		||||
            if match and not intersects_opts(match.span()):
 | 
			
		||||
                body = match.group('body')
 | 
			
		||||
                position = match.span()
 | 
			
		||||
                args.append((body, position))
 | 
			
		||||
 | 
			
		||||
        # In rare occasions quoted options are being captured, while they should
 | 
			
		||||
        # not be. This fixes the problem by finding options which intersect with
 | 
			
		||||
        # arguments and removing them.
 | 
			
		||||
        for key, value, position in opts[:]:
 | 
			
		||||
            if intersects_args(position):
 | 
			
		||||
                opts.remove((key, value, position))
 | 
			
		||||
 | 
			
		||||
        return args, opts
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def adapt_command_arguments(cls, command, arguments, args, opts):
 | 
			
		||||
        """
 | 
			
		||||
        Adapts args and opts got from the parser to a specific handler by means
 | 
			
		||||
        of arguments specified on command definition. That is transforms them to
 | 
			
		||||
        *args and **kwargs suitable for passing to a command handler.
 | 
			
		||||
 | 
			
		||||
        Extra arguments which are not considered extra (or optional) - will be
 | 
			
		||||
        passed as if they were value for keywords, in the order keywords are
 | 
			
		||||
        defined and printed in usage.
 | 
			
		||||
 | 
			
		||||
        Dashes (-) in the option names will be converted to underscores. So you
 | 
			
		||||
        can map --one-more-option to a one_more_option=None.
 | 
			
		||||
 | 
			
		||||
        If initial value of a keyword argument is a boolean (False in most
 | 
			
		||||
        cases) then this option will be treated as a switch, that is an option
 | 
			
		||||
        which does not take an argument. Argument preceded by a switch will be
 | 
			
		||||
        treated just like a normal positional argument.
 | 
			
		||||
 | 
			
		||||
        If keyword argument's initial value is a sequence (tuple or a string)
 | 
			
		||||
        then possible values of the option will be restricted to one of the
 | 
			
		||||
        values given by the sequence.
 | 
			
		||||
        """
 | 
			
		||||
        spec_args, spec_kwargs, var_args, var_kwargs = command.extract_arg_spec()
 | 
			
		||||
        norm_kwargs = dict(spec_kwargs)
 | 
			
		||||
 | 
			
		||||
        # Quite complex piece of neck-breaking logic to extract raw arguments if
 | 
			
		||||
        # there is more, then one positional argument specified by the command.
 | 
			
		||||
        # In case if it's just one argument which is the collector this is
 | 
			
		||||
        # fairly easy. But when it's more then one argument - the neck-breaking
 | 
			
		||||
        # logic of how to retrieve residual arguments as a raw, all in one piece
 | 
			
		||||
        # string, kicks on.
 | 
			
		||||
        if command.raw:
 | 
			
		||||
            if spec_kwargs or var_args or var_kwargs:
 | 
			
		||||
                raise InternalError("Raw commands should define only positional arguments")
 | 
			
		||||
 | 
			
		||||
            if arguments:
 | 
			
		||||
                spec_fix = 1 if command.source else 0
 | 
			
		||||
                spec_len = len(spec_args) - spec_fix
 | 
			
		||||
                arguments_end = len(arguments) - 1
 | 
			
		||||
 | 
			
		||||
                # If there are any optional arguments given they should be
 | 
			
		||||
                # either an unquoted postional argument or part of the raw
 | 
			
		||||
                # argument. So we find all optional arguments that can possibly
 | 
			
		||||
                # be unquoted argument and append them as is to the args.
 | 
			
		||||
                for key, value, (start, end) in opts[:spec_len]:
 | 
			
		||||
                    if value:
 | 
			
		||||
                        end -= len(value) + 1
 | 
			
		||||
                        args.append((arguments[start:end], (start, end)))
 | 
			
		||||
                        args.append((value, (end, end + len(value) + 1)))
 | 
			
		||||
                    else:
 | 
			
		||||
                        args.append((arguments[start:end], (start, end)))
 | 
			
		||||
 | 
			
		||||
                # We need in-place sort here because after manipulations with
 | 
			
		||||
                # options order of arguments might be wrong and we just can't
 | 
			
		||||
                # have more complex logic to not let that happen.
 | 
			
		||||
                args.sort(key=itemgetter(1))
 | 
			
		||||
 | 
			
		||||
                if spec_len > 1:
 | 
			
		||||
                    try:
 | 
			
		||||
                        stopper, (start, end) = args[spec_len - 2]
 | 
			
		||||
                    except IndexError:
 | 
			
		||||
                        raise CommandError("Missing arguments", command)
 | 
			
		||||
 | 
			
		||||
                    raw = arguments[end:]
 | 
			
		||||
                    raw = raw.strip() or None
 | 
			
		||||
 | 
			
		||||
                    if not raw and not command.empty:
 | 
			
		||||
                        raise CommandError("Missing arguments", command)
 | 
			
		||||
 | 
			
		||||
                    # Discard residual arguments and all of the options as raw
 | 
			
		||||
                    # command does not support options and if an option is given
 | 
			
		||||
                    # it is rather a part of a raw argument.
 | 
			
		||||
                    args = args[:spec_len - 1]
 | 
			
		||||
                    opts = []
 | 
			
		||||
 | 
			
		||||
                    args.append((raw, (end, arguments_end)))
 | 
			
		||||
                elif spec_len == 1:
 | 
			
		||||
                    args = [(arguments, (0, arguments_end))]
 | 
			
		||||
                    opts = []
 | 
			
		||||
                else:
 | 
			
		||||
                    raise InternalError("Raw command must define a collector")
 | 
			
		||||
            else:
 | 
			
		||||
                if command.empty:
 | 
			
		||||
                    args.append((None, (0, 0)))
 | 
			
		||||
                else:
 | 
			
		||||
                    raise CommandError("Missing arguments", command)
 | 
			
		||||
 | 
			
		||||
        # The first stage of transforming options we have got to a format that
 | 
			
		||||
        # can be used to associate them with declared keyword arguments.
 | 
			
		||||
        # Substituting dashes (-) in their names with underscores (_).
 | 
			
		||||
        for index, (key, value, position) in enumerate(opts):
 | 
			
		||||
            if '-' in key:
 | 
			
		||||
                opts[index] = (key.replace('-', '_'), value, position)
 | 
			
		||||
 | 
			
		||||
        # The second stage of transforming options to an associatable state.
 | 
			
		||||
        # Expanding short, one-letter options to a verbose ones, if
 | 
			
		||||
        # corresponding optin has been given.
 | 
			
		||||
        if command.expand_short:
 | 
			
		||||
            expanded = []
 | 
			
		||||
            for spec_key, spec_value in norm_kwargs.iteritems():
 | 
			
		||||
                letter = spec_key[0] if len(spec_key) > 1 else None
 | 
			
		||||
                if letter and letter not in expanded:
 | 
			
		||||
                    for index, (key, value, position) in enumerate(opts):
 | 
			
		||||
                        if key == letter:
 | 
			
		||||
                            expanded.append(letter)
 | 
			
		||||
                            opts[index] = (spec_key, value, position)
 | 
			
		||||
                            break
 | 
			
		||||
 | 
			
		||||
        # Detect switches and set their values accordingly. If any of them
 | 
			
		||||
        # carries a value - append it to args.
 | 
			
		||||
        for index, (key, value, position) in enumerate(opts):
 | 
			
		||||
            if isinstance(norm_kwargs.get(key), BooleanType):
 | 
			
		||||
                opts[index] = (key, True, position)
 | 
			
		||||
                if value:
 | 
			
		||||
                    args.append((value, position))
 | 
			
		||||
 | 
			
		||||
        # Sorting arguments and options (just to be sure) in regarding to their
 | 
			
		||||
        # positions in the string.
 | 
			
		||||
        args.sort(key=itemgetter(1))
 | 
			
		||||
        opts.sort(key=itemgetter(2))
 | 
			
		||||
 | 
			
		||||
        # Stripping down position information supplied with arguments and options as it
 | 
			
		||||
        # won't be needed again.
 | 
			
		||||
        args = map(lambda (arg, position): arg, args)
 | 
			
		||||
        opts = map(lambda (key, value, position): (key, value), opts)
 | 
			
		||||
 | 
			
		||||
        # If command has extra option enabled - collect all extra arguments and
 | 
			
		||||
        # pass them to a last positional argument command defines as a list.
 | 
			
		||||
        if command.extra:
 | 
			
		||||
            if not var_args:
 | 
			
		||||
                spec_fix = 1 if not command.source else 2
 | 
			
		||||
                spec_len = len(spec_args) - spec_fix
 | 
			
		||||
                extra = args[spec_len:]
 | 
			
		||||
                args = args[:spec_len]
 | 
			
		||||
                args.append(extra)
 | 
			
		||||
            else:
 | 
			
		||||
                raise InternalError("Can not have both, extra and *args")
 | 
			
		||||
 | 
			
		||||
        # Detect if positional arguments overlap keyword arguments. If so and
 | 
			
		||||
        # this is allowed by command options - then map them directly to their
 | 
			
		||||
        # options, so they can get propert further processings.
 | 
			
		||||
        spec_fix = 1 if command.source else 0
 | 
			
		||||
        spec_len = len(spec_args) - spec_fix
 | 
			
		||||
        if len(args) > spec_len:
 | 
			
		||||
            if command.overlap:
 | 
			
		||||
                overlapped = args[spec_len:]
 | 
			
		||||
                args = args[:spec_len]
 | 
			
		||||
                for arg, (spec_key, spec_value) in zip(overlapped, spec_kwargs):
 | 
			
		||||
                    opts.append((spec_key, arg))
 | 
			
		||||
            else:
 | 
			
		||||
                raise CommandError("Excessive arguments", command)
 | 
			
		||||
 | 
			
		||||
        # Detect every contraint sequences and ensure that if corresponding
 | 
			
		||||
        # options are given - they contain proper values, within constraint
 | 
			
		||||
        # range.
 | 
			
		||||
        for key, value in opts:
 | 
			
		||||
            initial = norm_kwargs.get(key)
 | 
			
		||||
            if isinstance(initial, (TupleType, ListType)) and value not in initial:
 | 
			
		||||
                raise CommandError("Wrong argument", command)
 | 
			
		||||
 | 
			
		||||
        # Detect every switch and ensure it will not receive any arguments.
 | 
			
		||||
        # Normally this does not happen unless overlapping is enabled.
 | 
			
		||||
        for key, value in opts:
 | 
			
		||||
            initial = norm_kwargs.get(key)
 | 
			
		||||
            if isinstance(initial, BooleanType) and not isinstance(value, BooleanType):
 | 
			
		||||
                raise CommandError("Switches do not take arguments", command)
 | 
			
		||||
 | 
			
		||||
        # We need to encode every keyword argument to a simple string, not the
 | 
			
		||||
        # unicode one, because ** expansion does not support it.
 | 
			
		||||
        for index, (key, value) in enumerate(opts):
 | 
			
		||||
            if isinstance(key, UnicodeType):
 | 
			
		||||
                opts[index] = (key.encode(cls.ARG_ENCODING), value)
 | 
			
		||||
 | 
			
		||||
        # Inject the source arguments as a string as a first argument, if
 | 
			
		||||
        # command has enabled the corresponding option.
 | 
			
		||||
        if command.source:
 | 
			
		||||
            args.insert(0, arguments)
 | 
			
		||||
 | 
			
		||||
        # Return *args and **kwargs in the form suitable for passing to a
 | 
			
		||||
        # command handlers and being expanded.
 | 
			
		||||
        return tuple(args), dict(opts)
 | 
			
		||||
 | 
			
		||||
    def process_as_command(self, text):
 | 
			
		||||
        """
 | 
			
		||||
        Try to process text as a command. Returns True if it is a command and
 | 
			
		||||
        False if it is not.
 | 
			
		||||
        """
 | 
			
		||||
        if not text.startswith(self.COMMAND_PREFIX):
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        body = text[len(self.COMMAND_PREFIX):]
 | 
			
		||||
        body = body.strip()
 | 
			
		||||
 | 
			
		||||
        parts = body.split(' ', 1)
 | 
			
		||||
        name, arguments = parts if len(parts) > 1 else (parts[0], None)
 | 
			
		||||
 | 
			
		||||
        flag = self.looks_like_command(body, name, arguments)
 | 
			
		||||
        if flag is not None:
 | 
			
		||||
            return flag
 | 
			
		||||
 | 
			
		||||
        self.execute_command(text, name, arguments)
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def execute_command(self, text, name, arguments):
 | 
			
		||||
        command = self.retrieve_command(name)
 | 
			
		||||
 | 
			
		||||
        args, opts = self.parse_command_arguments(arguments) if arguments else ([], [])
 | 
			
		||||
        args, kwargs = self.adapt_command_arguments(command, arguments, args, opts)
 | 
			
		||||
 | 
			
		||||
        if self.command_preprocessor(name, command, arguments, args, kwargs):
 | 
			
		||||
            return
 | 
			
		||||
        value = command(self, *args, **kwargs)
 | 
			
		||||
        self.command_postprocessor(name, command, arguments, args, kwargs, value)
 | 
			
		||||
 | 
			
		||||
    def command_preprocessor(self, name, command, arguments, args, kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Redefine this method in the subclass to execute custom code before
 | 
			
		||||
        command gets executed. If returns True then command execution will be
 | 
			
		||||
        interrupted and command will not be executed.
 | 
			
		||||
        """
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def command_postprocessor(self, name, command, arguments, args, kwargs, output):
 | 
			
		||||
        """
 | 
			
		||||
        Redefine this method in the subclass to execute custom code after
 | 
			
		||||
        command gets executed.
 | 
			
		||||
        """
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def looks_like_command(self, text, name, arguments):
 | 
			
		||||
        """
 | 
			
		||||
        This hook is being called before any processing, but after it was
 | 
			
		||||
        determined that text looks like a command. If returns non None value
 | 
			
		||||
        - then further processing will be interrupted and that value will be
 | 
			
		||||
        used to return from process_as_command.
 | 
			
		||||
        """
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
def command(*names, **kwargs):
 | 
			
		||||
    """
 | 
			
		||||
    A decorator which provides a declarative way of defining commands.
 | 
			
		||||
 | 
			
		||||
    You can specify a set of names by which you can call the command. If names
 | 
			
		||||
    is empty - then the name of the command will be set to native one (extracted
 | 
			
		||||
    from the handler name).
 | 
			
		||||
 | 
			
		||||
    If include_native=True argument is given and names is non-empty - then
 | 
			
		||||
    native name will be added as well.
 | 
			
		||||
 | 
			
		||||
    If usage=True is given - then handler's doc will be appended with an
 | 
			
		||||
    auto-generated usage info.
 | 
			
		||||
 | 
			
		||||
    If source=True is given - then the first positional argument of the command
 | 
			
		||||
    handler will receive a string with a raw and unprocessed source arguments.
 | 
			
		||||
 | 
			
		||||
    If raw=True is given - then command should define only one argument to
 | 
			
		||||
    which all raw and unprocessed source arguments will be given.
 | 
			
		||||
 | 
			
		||||
    If empty=True is given - then when raw=True is set and command receives no
 | 
			
		||||
    arguments - an exception will be raised.
 | 
			
		||||
 | 
			
		||||
    If extra=True is given - then last positional argument will receive every
 | 
			
		||||
    extra positional argument that will be given to a command. This is an
 | 
			
		||||
    analogue to specifing *args, but the latter one should be used in simplest
 | 
			
		||||
    cases only because of some Python limitations on this - arguments can't be
 | 
			
		||||
    mapped correctly when there are keyword arguments present.
 | 
			
		||||
 | 
			
		||||
    If overlap=True is given - then if extra=False and there is extra arguments
 | 
			
		||||
    given to the command - they will be mapped as if they were values for the
 | 
			
		||||
    keyword arguments, in the order they are defined.
 | 
			
		||||
 | 
			
		||||
    If expand_short=True is given - then if command receives one-letter
 | 
			
		||||
    options (like -v or -f) they will be expanded to a verbose ones (like
 | 
			
		||||
    --verbose or --file) if the latter are defined as a command optional
 | 
			
		||||
    arguments. Expansion is made on a first-letter comparison basis. If more
 | 
			
		||||
    then one long option with the same first letter defined - only first one
 | 
			
		||||
    will be used in expansion.
 | 
			
		||||
    """
 | 
			
		||||
    names = list(names)
 | 
			
		||||
    include_native = kwargs.get('include_native', True)
 | 
			
		||||
 | 
			
		||||
    usage = kwargs.get('usage', True)
 | 
			
		||||
    source = kwargs.get('source', False)
 | 
			
		||||
    raw = kwargs.get('raw', False)
 | 
			
		||||
    extra = kwargs.get('extra', False)
 | 
			
		||||
    overlap = kwargs.get('overlap', False)
 | 
			
		||||
    empty = kwargs.get('empty', False)
 | 
			
		||||
    expand_short = kwargs.get('expand_short', True)
 | 
			
		||||
 | 
			
		||||
    if extra and overlap:
 | 
			
		||||
        raise InternalError("Extra and overlap options can not be used together")
 | 
			
		||||
 | 
			
		||||
    def decorator(handler):
 | 
			
		||||
        command = Command(handler, usage, source, raw, extra, overlap, empty, expand_short)
 | 
			
		||||
 | 
			
		||||
        # Extract and inject native name while making sure it is going to be the
 | 
			
		||||
        # first one in the list.
 | 
			
		||||
        if not names or include_native:
 | 
			
		||||
            names.insert(0, command.native_name)
 | 
			
		||||
        command.names = tuple(names)
 | 
			
		||||
 | 
			
		||||
        return command
 | 
			
		||||
 | 
			
		||||
    # Workaround if we are getting called without parameters. Keep in mind that
 | 
			
		||||
    # in that case - first item in the names will be the handler.
 | 
			
		||||
    if len(names) == 1 and isinstance(names[0], FunctionType):
 | 
			
		||||
        return decorator(names.pop())
 | 
			
		||||
 | 
			
		||||
    return decorator
 | 
			
		||||
| 
						 | 
				
			
			@ -231,14 +231,6 @@ from chat_control import ChatControl
 | 
			
		|||
from groupchat_control import GroupchatControl
 | 
			
		||||
from groupchat_control import PrivateChatControl
 | 
			
		||||
 | 
			
		||||
# Here custom adhoc processors should be loaded. At this point there is
 | 
			
		||||
# everything they need to function properly. The next line loads custom exmple
 | 
			
		||||
# adhoc processors. Technically, they could be loaded earlier as host processors
 | 
			
		||||
# themself does not depend on the chat controls, but that should not be done
 | 
			
		||||
# uless there is a really good reason for that..
 | 
			
		||||
#
 | 
			
		||||
# from commands import custom
 | 
			
		||||
 | 
			
		||||
from atom_window import AtomWindow
 | 
			
		||||
from session import ChatControlSession
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -47,7 +47,8 @@ from chat_control import ChatControl
 | 
			
		|||
from chat_control import ChatControlBase
 | 
			
		||||
from common.exceptions import GajimGeneralException
 | 
			
		||||
 | 
			
		||||
from commands.implementation import PrivateChatCommands, GroupChatCommands
 | 
			
		||||
from command_system.implementation.hosts import PrivateChatCommands
 | 
			
		||||
from command_system.implementation.hosts import GroupChatCommands
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
log = logging.getLogger('gajim.groupchat_control')
 | 
			
		||||
| 
						 | 
				
			
			@ -118,10 +119,12 @@ def tree_cell_data_func(column, renderer, model, iter_, tv=None):
 | 
			
		|||
			renderer.set_property('font',
 | 
			
		||||
				gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont'))
 | 
			
		||||
 | 
			
		||||
class PrivateChatControl(ChatControl, PrivateChatCommands):
 | 
			
		||||
class PrivateChatControl(ChatControl):
 | 
			
		||||
	TYPE_ID = message_control.TYPE_PM
 | 
			
		||||
 | 
			
		||||
	DISPATCHED_BY = PrivateChatCommands
 | 
			
		||||
   # Set a command host to bound to. Every command given through a private chat
 | 
			
		||||
   # will be processed with this command host.
 | 
			
		||||
	COMMAND_HOST = PrivateChatCommands
 | 
			
		||||
 | 
			
		||||
	def __init__(self, parent_win, gc_contact, contact, account, session):
 | 
			
		||||
		room_jid = contact.jid.split('/')[0]
 | 
			
		||||
| 
						 | 
				
			
			@ -185,10 +188,12 @@ class PrivateChatControl(ChatControl, PrivateChatCommands):
 | 
			
		|||
 | 
			
		||||
		self.session.negotiate_e2e(False)
 | 
			
		||||
 | 
			
		||||
class GroupchatControl(ChatControlBase, GroupChatCommands):
 | 
			
		||||
class GroupchatControl(ChatControlBase):
 | 
			
		||||
	TYPE_ID = message_control.TYPE_GC
 | 
			
		||||
 | 
			
		||||
	DISPATCHED_BY = GroupChatCommands
 | 
			
		||||
   # Set a command host to bound to. Every command given through a group chat
 | 
			
		||||
   # will be processed with this command host.
 | 
			
		||||
	COMMAND_HOST = GroupChatCommands
 | 
			
		||||
 | 
			
		||||
	def __init__(self, parent_win, contact, acct, is_continued=False):
 | 
			
		||||
		ChatControlBase.__init__(self, self.TYPE_ID, parent_win,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue