Completely rewrote the dispatcher in the command system

This commit is contained in:
Alexander Cherniuk 2010-08-05 21:47:46 +03:00
parent 162dd1eb6f
commit 4635a08f94
7 changed files with 178 additions and 101 deletions

View 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)

View File

@ -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)

View File

@ -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()))

View File

@ -41,6 +41,7 @@ from ..framework import CommandContainer, command, doc
from hosts import *
class Execute(CommandContainer):
AUTOMATIC = True
HOSTS = ChatCommands, PrivateChatCommands, GroupChatCommands
DIRECTORY = "~"

View File

@ -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

View File

@ -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)

View 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]