Completely rewrote the dispatcher in the command system
This commit is contained in:
parent
162dd1eb6f
commit
4635a08f94
7 changed files with 178 additions and 101 deletions
117
src/command_system/dispatcher.py
Normal file
117
src/command_system/dispatcher.py
Normal file
|
@ -0,0 +1,117 @@
|
|||
# Copyright (c) 2010, Alexander Cherniuk (ts33kr@gmail.com)
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
#
|
||||
# * Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""
|
||||
Backbone of the command system. Provides smart and controllable
|
||||
dispatching mechanism with an auto-discovery functionality. In addition
|
||||
to automatic discovery and dispatching, also features manual control
|
||||
over the process.
|
||||
"""
|
||||
|
||||
from types import NoneType
|
||||
from tools import remove
|
||||
|
||||
COMMANDS = {}
|
||||
CONTAINERS = {}
|
||||
|
||||
def add_host(host):
|
||||
CONTAINERS[host] = []
|
||||
|
||||
def remove_host(host):
|
||||
remove(CONTAINERS, host)
|
||||
|
||||
def add_container(container):
|
||||
for host in container.HOSTS:
|
||||
CONTAINERS[host].append(container)
|
||||
|
||||
def remove_container(container):
|
||||
for host in container.HOSTS:
|
||||
remove(CONTAINERS[host], container)
|
||||
|
||||
def add_commands(container):
|
||||
commands = COMMANDS.setdefault(container, {})
|
||||
for command in traverse_commands(container):
|
||||
for name in command.names:
|
||||
commands[name] = command
|
||||
|
||||
def remove_commands(container):
|
||||
remove(COMMANDS, container)
|
||||
|
||||
def traverse_commands(container):
|
||||
for name in dir(container):
|
||||
attribute = getattr(container, name)
|
||||
if is_command(attribute):
|
||||
yield attribute
|
||||
|
||||
def is_command(attribute):
|
||||
from framework import Command
|
||||
return isinstance(attribute, Command)
|
||||
|
||||
def is_root(namespace):
|
||||
metaclass = namespace.get("__metaclass__", NoneType)
|
||||
return issubclass(metaclass, Dispatchable)
|
||||
|
||||
def get_command(host, name):
|
||||
for container in CONTAINERS[host]:
|
||||
command = COMMANDS[container].get(name)
|
||||
if command:
|
||||
return command
|
||||
|
||||
def list_commands(host):
|
||||
for container in CONTAINERS[host]:
|
||||
commands = COMMANDS[container]
|
||||
for name, command in commands.iteritems():
|
||||
yield name, command
|
||||
|
||||
class Dispatchable(type):
|
||||
|
||||
def __init__(self, name, bases, namespace):
|
||||
parents = super(Dispatchable, self)
|
||||
parents.__init__(name, bases, namespace)
|
||||
if not is_root(namespace):
|
||||
self.dispatch()
|
||||
|
||||
def dispatch(self):
|
||||
if self.AUTOMATIC:
|
||||
self.enable()
|
||||
|
||||
class Host(Dispatchable):
|
||||
|
||||
def enable(self):
|
||||
add_host(self)
|
||||
|
||||
def disable(self):
|
||||
remove_host(self)
|
||||
|
||||
class Container(Dispatchable):
|
||||
|
||||
def enable(self):
|
||||
add_container(self)
|
||||
add_commands(self)
|
||||
|
||||
def disable(self):
|
||||
remove_commands(self)
|
||||
remove_container(self)
|
|
@ -1,90 +0,0 @@
|
|||
# Copyright (C) 2009-2010 Alexander Cherniuk <ts33kr@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(cls, host):
|
||||
cls.containers[host] = []
|
||||
|
||||
@classmethod
|
||||
def register_container(cls, container):
|
||||
for host in container.HOSTS:
|
||||
cls.containers[host].append(container)
|
||||
|
||||
@classmethod
|
||||
def register_commands(cls, container):
|
||||
cls.commands[container] = {}
|
||||
for command in cls.traverse_commands(container):
|
||||
for name in command.names:
|
||||
cls.commands[container][name] = command
|
||||
|
||||
@classmethod
|
||||
def get_command(cls, host, name):
|
||||
for container in cls.containers[host]:
|
||||
command = cls.commands[container].get(name)
|
||||
if command:
|
||||
return command
|
||||
|
||||
@classmethod
|
||||
def list_commands(cls, host):
|
||||
for container in cls.containers[host]:
|
||||
commands = cls.commands[container]
|
||||
for name, command in commands.iteritems():
|
||||
yield name, command
|
||||
|
||||
@classmethod
|
||||
def traverse_commands(cls, container):
|
||||
for name in dir(container):
|
||||
attribute = getattr(container, name)
|
||||
if cls.is_command(attribute):
|
||||
yield attribute
|
||||
|
||||
@staticmethod
|
||||
def is_root(ns):
|
||||
metaclass = ns.get('__metaclass__', NoneType)
|
||||
return issubclass(metaclass, Dispatcher)
|
||||
|
||||
@staticmethod
|
||||
def is_command(attribute):
|
||||
from framework import Command
|
||||
return isinstance(attribute, Command)
|
||||
|
||||
class HostDispatcher(Dispatcher):
|
||||
|
||||
def __init__(self, name, bases, ns):
|
||||
if not Dispatcher.is_root(ns):
|
||||
HostDispatcher.register_host(self)
|
||||
super(HostDispatcher, self).__init__(name, bases, ns)
|
||||
|
||||
class ContainerDispatcher(Dispatcher):
|
||||
|
||||
def __init__(self, name, bases, ns):
|
||||
if not Dispatcher.is_root(ns):
|
||||
ContainerDispatcher.register_container(self)
|
||||
ContainerDispatcher.register_commands(self)
|
||||
super(ContainerDispatcher, self).__init__(name, bases, ns)
|
|
@ -23,7 +23,8 @@ import re
|
|||
from types import FunctionType
|
||||
from inspect import getargspec, getdoc
|
||||
|
||||
from dispatching import Dispatcher, HostDispatcher, ContainerDispatcher
|
||||
from dispatcher import Host, Container
|
||||
from dispatcher import get_command, list_commands
|
||||
from mapping import parse_arguments, adapt_arguments
|
||||
from errors import DefinitionError, CommandError, NoCommandError
|
||||
|
||||
|
@ -32,8 +33,12 @@ 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.
|
||||
|
||||
The AUTOMATIC class variable, which must be defined by a command
|
||||
host, specifies whether the command host should be automatically
|
||||
dispatched and enabled by the dispatcher or not.
|
||||
"""
|
||||
__metaclass__ = HostDispatcher
|
||||
__metaclass__ = Host
|
||||
|
||||
class CommandContainer(object):
|
||||
"""
|
||||
|
@ -41,11 +46,15 @@ class CommandContainer(object):
|
|||
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.
|
||||
The AUTOMATIC class variable, which must be defined by a command
|
||||
processor, specifies whether the command processor should be
|
||||
automatically dispatched and enabled by the dispatcher or not.
|
||||
|
||||
Bounding is controlled by the HOSTS class variable, which must be
|
||||
defined by the command container. This variable should contain a
|
||||
sequence of hosts to bound to, as a tuple or list.
|
||||
"""
|
||||
__metaclass__ = ContainerDispatcher
|
||||
__metaclass__ = Container
|
||||
|
||||
class CommandProcessor(object):
|
||||
"""
|
||||
|
@ -126,13 +135,13 @@ class CommandProcessor(object):
|
|||
pass
|
||||
|
||||
def get_command(self, name):
|
||||
command = Dispatcher.get_command(self.COMMAND_HOST, name)
|
||||
command = get_command(self.COMMAND_HOST, name)
|
||||
if not command:
|
||||
raise NoCommandError("Command does not exist", name=name)
|
||||
return command
|
||||
|
||||
def list_commands(self):
|
||||
commands = Dispatcher.list_commands(self.COMMAND_HOST)
|
||||
commands = list_commands(self.COMMAND_HOST)
|
||||
commands = dict(commands)
|
||||
return sorted(set(commands.itervalues()))
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ from ..framework import CommandContainer, command, doc
|
|||
from hosts import *
|
||||
|
||||
class Execute(CommandContainer):
|
||||
AUTOMATIC = True
|
||||
HOSTS = ChatCommands, PrivateChatCommands, GroupChatCommands
|
||||
|
||||
DIRECTORY = "~"
|
||||
|
|
|
@ -25,18 +25,18 @@ class ChatCommands(CommandHost):
|
|||
This command host is bound to the command processor which processes
|
||||
commands from a chat.
|
||||
"""
|
||||
pass
|
||||
AUTOMATIC = True
|
||||
|
||||
class PrivateChatCommands(CommandHost):
|
||||
"""
|
||||
This command host is bound to the command processor which processes
|
||||
commands from a private chat.
|
||||
"""
|
||||
pass
|
||||
AUTOMATIC = True
|
||||
|
||||
class GroupChatCommands(CommandHost):
|
||||
"""
|
||||
This command host is bound to the command processor which processes
|
||||
commands from a group chat.
|
||||
"""
|
||||
pass
|
||||
AUTOMATIC = True
|
||||
|
|
|
@ -43,6 +43,7 @@ class StandardCommonCommands(CommandContainer):
|
|||
to all - chat, private chat, group chat.
|
||||
"""
|
||||
|
||||
AUTOMATIC = True
|
||||
HOSTS = ChatCommands, PrivateChatCommands, GroupChatCommands
|
||||
|
||||
@command
|
||||
|
@ -164,6 +165,7 @@ class StandardCommonChatCommands(CommandContainer):
|
|||
to a chat and a private chat only.
|
||||
"""
|
||||
|
||||
AUTOMATIC = True
|
||||
HOSTS = ChatCommands, PrivateChatCommands
|
||||
|
||||
@command
|
||||
|
@ -221,6 +223,7 @@ class StandardChatCommands(CommandContainer):
|
|||
to a chat.
|
||||
"""
|
||||
|
||||
AUTOMATIC = True
|
||||
HOSTS = (ChatCommands,)
|
||||
|
||||
class StandardPrivateChatCommands(CommandContainer):
|
||||
|
@ -229,6 +232,7 @@ class StandardPrivateChatCommands(CommandContainer):
|
|||
to a private chat.
|
||||
"""
|
||||
|
||||
AUTOMATIC = True
|
||||
HOSTS = (PrivateChatCommands,)
|
||||
|
||||
class StandardGroupChatCommands(CommandContainer):
|
||||
|
@ -237,6 +241,7 @@ class StandardGroupChatCommands(CommandContainer):
|
|||
to a group chat.
|
||||
"""
|
||||
|
||||
AUTOMATIC = True
|
||||
HOSTS = (GroupChatCommands,)
|
||||
|
||||
@command(raw=True)
|
||||
|
|
35
src/command_system/tools.py
Normal file
35
src/command_system/tools.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
# Copyright (c) 2010, Alexander Cherniuk (ts33kr@gmail.com)
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
#
|
||||
# * Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from types import *
|
||||
|
||||
def remove(sequence, target):
|
||||
if isinstance(sequence, ListType):
|
||||
if target in sequence:
|
||||
sequence.remove(target)
|
||||
elif isinstance(sequence, DictType):
|
||||
if target in sequence:
|
||||
del sequence[target]
|
Loading…
Add table
Reference in a new issue