Realigned width of text meant for humans to 72 characters

This commit is contained in:
Alexander Cherniuk 2010-02-26 12:35:09 +02:00
parent ef05eb75d9
commit c272fce312
10 changed files with 257 additions and 229 deletions

View file

@ -14,7 +14,7 @@
# 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.
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.
"""

View file

@ -14,9 +14,10 @@
# 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.
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

View file

@ -15,8 +15,9 @@
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.
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):

View file

@ -14,8 +14,9 @@
# 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.
Provides a tiny framework with simple, yet powerful and extensible
architecture to implement commands in a streight and flexible,
declarative way.
"""
import re
@ -28,42 +29,43 @@ 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.
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.
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.
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.
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.
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.
# 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.
Try to process text as a command. Returns True if it has been
processed as a command and False otherwise.
"""
prefix = text.startswith(self.COMMAND_PREFIX)
length = len(text) > len(self.COMMAND_PREFIX)
@ -97,28 +99,28 @@ class CommandProcessor(object):
def command_preprocessor(self, command, name, arguments, args, kwargs):
"""
Redefine this method in the subclass to execute custom code before
command gets executed.
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.
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.
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.
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
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
@ -136,8 +138,9 @@ class CommandProcessor(object):
class Command(object):
# These two regular expression patterns control how command documentation
# will be formatted to be transformed to a normal, readable state.
# 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)
@ -145,8 +148,8 @@ class Command(object):
self.handler = handler
self.names = names
# Automatically set all the properties passed to a constructor by the
# command decorator.
# Automatically set all the properties passed to a constructor
# by the command decorator.
for key, value in properties.iteritems():
setattr(self, key, value)
@ -154,18 +157,20 @@ class Command(object):
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.
# 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(error.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.
# 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)
@ -185,8 +190,8 @@ class Command(object):
def extract_documentation(self):
"""
Extract handler's documentation which is a doc-string and transform it
to a usable format.
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.
@ -211,19 +216,19 @@ class Command(object):
def extract_specification(self):
"""
Extract handler's arguments specification, as it was defined preserving
their order.
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.
# 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.
# 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)
@ -231,44 +236,47 @@ class Command(object):
def command(*names, **properties):
"""
A decorator for defining commands in a declarative way. Provides facilities
for setting command's names and 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.
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 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 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 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 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 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 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 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.
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)
@ -300,22 +308,23 @@ def command(*names, **properties):
def decorator(handler):
"""
Decorator which receives handler as a first argument and then wraps it
in the command which then returns back.
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.
# 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.
# 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))
@ -323,11 +332,8 @@ def command(*names, **properties):
def documentation(text):
"""
This decorator is used to bind a documentation (a help) to a command.
Though this can be done easily by using doc-strings in a declarative and
Pythonic way - some of Gajim's developers are against it because of the
scaffolding needed to support the tranlation of such documentation.
This decorator is used to bind a documentation (a help) to a
command.
"""
def decorator(target):
if isinstance(target, Command):

View file

@ -14,6 +14,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
The implementation and auxilary systems which implement the standard Gajim
commands and also provide an infrastructure for adding custom commands.
The implementation and auxilary systems which implement the standard
Gajim commands and also provide an infrastructure for adding custom
commands.
"""

View file

@ -14,11 +14,11 @@
# 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.
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.
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, documentation
@ -27,8 +27,9 @@ 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.
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)
@ -39,12 +40,13 @@ class CustomCommonCommands(CommandContainer):
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 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.
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..."
@ -63,8 +65,9 @@ class CustomChatCommands(CommandContainer):
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.
This command container bounds only to the PrivateChatCommands
command host. Therefore command defined here will be available only
to a private chat.
"""
HOSTS = (PrivateChatCommands,)
@ -75,8 +78,9 @@ class CustomPrivateChatCommands(CommandContainer):
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.
This command container bounds only to the GroupChatCommands command
host. Therefore command defined here will be available only to a
group chat.
"""
HOSTS = (GroupChatCommands,)

View file

@ -14,29 +14,29 @@
# 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.
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.
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.
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.
This command host is bound to the command processor which processes
commands from a group chat.
"""
pass

View file

@ -14,10 +14,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
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 code
itself code to write basic commands.
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 code itself code to write basic commands.
"""
from types import StringTypes
@ -30,8 +30,8 @@ from ..errors import CommandError
class ChatCommandProcessor(CommandProcessor):
"""
A basic scaffolding to provide convenient interaction between the command
system and chat controls.
A basic scaffolding to provide convenient interaction between the
command system and chat controls.
"""
def process_as_command(self, text):
@ -76,8 +76,8 @@ class ChatCommandProcessor(CommandProcessor):
class CommandTools:
"""
Contains a set of basic tools and shortcuts you can use in your commands to
performe some simple operations.
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'):
@ -107,8 +107,8 @@ class CommandTools:
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)

View file

@ -38,8 +38,8 @@ lc = Constants()
class StandardCommonCommands(CommandContainer):
"""
This command container contains standard commands which are common to all -
chat, private chat, group chat.
This command container contains standard commands which are common
to all - chat, private chat, group chat.
"""
HOSTS = (ChatCommands, PrivateChatCommands, GroupChatCommands)
@ -159,7 +159,8 @@ class StandardCommonCommands(CommandContainer):
class StandardChatCommands(CommandContainer):
"""
This command container contains standard command which are unique to a chat.
This command container contains standard command which are unique to
a chat.
"""
HOSTS = (ChatCommands,)
@ -211,16 +212,16 @@ class StandardChatCommands(CommandContainer):
class StandardPrivateChatCommands(CommandContainer):
"""
This command container contains standard command which are unique to a
private chat.
This command container contains standard command which are unique to
a private chat.
"""
HOSTS = (PrivateChatCommands,)
class StandardGroupchatCommands(CommandContainer):
"""
This command container contains standard command which are unique to a group
chat.
This command container contains standard command which are unique to
a group chat.
"""
HOSTS = (GroupChatCommands,)

View file

@ -14,12 +14,12 @@
# 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.
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.
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
@ -34,28 +34,32 @@ from errors import DefinitionError, CommandError
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.
# 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.
# 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.
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.
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.
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.
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 = [], []
@ -90,14 +94,16 @@ def parse_arguments(arguments):
position = match.span()
args.append((body, position))
# Primitive but sufficiently effective way of disposing of conflicted
# sectors. Remove any arguments that intersect with options.
# 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.
# 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))
@ -106,41 +112,44 @@ def parse_arguments(arguments):
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.
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.
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 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.
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.
# 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.
# 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
@ -149,9 +158,9 @@ def adapt_arguments(command, arguments, args, opts):
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.
# 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:
@ -160,27 +169,28 @@ def adapt_arguments(command, arguments, args, opts):
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.
# 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.
# 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.
# 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:
@ -189,16 +199,17 @@ def adapt_arguments(command, arguments, args, opts):
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 (_).
# 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.
# 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():
@ -210,26 +221,27 @@ def adapt_arguments(command, arguments, args, opts):
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.
# 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.
# 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.
# 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 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
@ -240,9 +252,9 @@ def adapt_arguments(command, arguments, args, opts):
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.
# 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:
@ -262,16 +274,17 @@ def adapt_arguments(command, arguments, args, opts):
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.
# 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)
# If argument to an option constrained by a sequence was not given - then
# it's value should be set to None.
# If argument to an option constrained by a sequence was not given -
# then it's value should be set to None.
for spec_key, spec_value in spec_kwargs:
if isinstance(spec_value, (TupleType, ListType)):
for key, value in opts:
@ -280,31 +293,32 @@ def adapt_arguments(command, arguments, args, opts):
else:
opts.append((spec_key, None))
# We need to encode every keyword argument to a simple string, not the
# unicode one, because ** expansion does not support it.
# 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.
# 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 *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.
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.
# 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
@ -350,8 +364,8 @@ def generate_usage(command, complete=True):
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.
# 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: