More code refactoring

This commit is contained in:
red-agent 2009-09-15 17:46:02 +03:00
parent c194b92136
commit 8a72870a6d

View file

@ -45,12 +45,13 @@ class Command(object):
ARG_USAGE_PATTERN = 'Usage: %s %s' ARG_USAGE_PATTERN = 'Usage: %s %s'
def __init__(self, handler, usage, raw, optional, empty, expand_short): def __init__(self, handler, usage, source, raw, extra, empty, expand_short):
self.handler = handler self.handler = handler
self.usage = usage self.usage = usage
self.source = source
self.raw = raw self.raw = raw
self.optional = optional self.extra = extra
self.empty = empty self.empty = empty
self.expand_short = expand_short self.expand_short = expand_short
@ -100,8 +101,7 @@ class Command(object):
keep them simple yet meaningful. keep them simple yet meaningful.
""" """
doc = self.extract_doc() doc = self.extract_doc()
if doc: return doc.split('\n', 1)[0] if doc else None
return doc.split('\n', 1)[0]
def extract_arg_spec(self): def extract_arg_spec(self):
names, var_args, var_kwargs, defaults = getargspec(self.handler) names, var_args, var_kwargs, defaults = getargspec(self.handler)
@ -126,19 +126,10 @@ class Command(object):
""" """
spec_args, spec_kwargs, var_args, var_kwargs = self.extract_arg_spec() spec_args, spec_kwargs, var_args, var_kwargs = self.extract_arg_spec()
# If command defines special __arguments__ parameter - it should not be # Remove some special positional arguments from the specifiaction, but
# included in the usage information, but may be used for internal # store their names so they can be used for usage info generation.
# purposes while generating usage information. sp_source = spec_args.pop(0) if self.source else None
sp_arguments = '__arguments__' in spec_args sp_extra = spec_args.pop() if self.extra else None
if sp_arguments:
spec_args.remove('__arguments__')
# If command defines special __optional__ parameter - it should not be
# included in the usage information, but may be used for internal
# purposes while generating usage information.
sp_optional = '__optional__' in spec_args
if sp_optional:
spec_args.remove('__optional__')
kwargs = [] kwargs = []
letters = [] letters = []
@ -158,11 +149,11 @@ class Command(object):
if len(spec_args) == 1 and self.raw: if len(spec_args) == 1 and self.raw:
args += ('(|%s|)' if self.empty else '|%s|') % spec_args[0] args += ('(|%s|)' if self.empty else '|%s|') % spec_args[0]
elif spec_args or var_args or sp_optional: elif spec_args or var_args or sp_extra:
if spec_args: if spec_args:
args += '<%s>' % ', '.join(spec_args) args += '<%s>' % ', '.join(spec_args)
if var_args or sp_optional: if var_args or sp_extra:
args += (' ' if spec_args else str()) + '<<%s>>' % (var_args or self.optional) args += (' ' if spec_args else str()) + '<<%s>>' % (var_args or sp_extra)
usage += args usage += args
@ -385,7 +376,7 @@ class CommandProcessor(object):
tuples. tuples.
The format of the input arguments should be: The format of the input arguments should be:
<arg1, arg2> <<optional>> [-(-o)ption=value1, -(-a)nother=value2] [[extra_options]] <arg1, arg2> <<extra>> [-(-o)ption=value1, -(-a)nother=value2] [[extra_options]]
Options may be given in --long or -short format. As --option=value or 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 --option value or -option value. Keys without values will get True as
@ -426,17 +417,6 @@ class CommandProcessor(object):
of arguments specified on command definition. That is transforms them to of arguments specified on command definition. That is transforms them to
*args and **kwargs suitable for passing to a command handler. *args and **kwargs suitable for passing to a command handler.
If command defines __arguments__ as a first argument - then this
argument will receive raw and unprocessed arguments. Also, if nothing
except __arguments__ (including *args, *kwargs splatting) is defined -
then all parsed arguments will be discarded. It will be discarded in the
argument usage information.
If command defines __optional__ - that is an analogue for *args, to
collect extra arguments. This is a preffered way over *args. Because of
some Python limitations, *args could not be mapped to as expected. And
it is hardly advised to define it after all hard arguments.
Extra arguments which are not considered extra (or optional) - will be Extra arguments which are not considered extra (or optional) - will be
passed as if they were value for keywords, in the order keywords are passed as if they were value for keywords, in the order keywords are
defined and printed in usage. defined and printed in usage.
@ -447,10 +427,6 @@ class CommandProcessor(object):
spec_args, spec_kwargs, var_args, var_kwargs = command.extract_arg_spec() spec_args, spec_kwargs, var_args, var_kwargs = command.extract_arg_spec()
spec_kwargs = dict(spec_kwargs) spec_kwargs = dict(spec_kwargs)
# Check if some special arguments are present.
sp_arguments = '__arguments__' in spec_args
sp_optional = '__optional__' in spec_args
if command.raw: if command.raw:
if len(spec_args) == 1: if len(spec_args) == 1:
if arguments or command.empty: if arguments or command.empty:
@ -458,18 +434,14 @@ class CommandProcessor(object):
raise CommandError("Can not be used without arguments", command) raise CommandError("Can not be used without arguments", command)
raise CommandInternalError("Raw command must define no more then one argument") raise CommandInternalError("Raw command must define no more then one argument")
if sp_optional: if command.extra:
if not var_args: if not var_args:
hard_len = len(spec_args) - (1 if not sp_arguments else 2) positional_len = len(spec_args) - (1 if not command.source else 2)
optional = args[hard_len:] extra = args[positional_len:]
args = args[:hard_len] args = args[:positional_len]
args.insert(spec_args.index('__optional__'), optional) args.append(extra)
else: else:
raise CommandInternalError("Can not have both, __optional__ and *args") raise CommandInternalError("Can not have both, extra and *args")
for index, (key, value) in enumerate(opts):
if '-' in key:
opts[index] = (key.replace('-', '_'), value)
if command.expand_short: if command.expand_short:
expanded = [] expanded = []
@ -482,16 +454,18 @@ class CommandProcessor(object):
opts[index] = (spec_key, value) opts[index] = (spec_key, value)
break break
for index, (key, value) in enumerate(opts):
if '-' in key:
opts[index] = (key.replace('-', '_'), value)
# We need to encode every keyword argument to a simple string, not the # We need to encode every keyword argument to a simple string, not the
# unicode one, because ** expansion does not support it. # unicode one, because ** expansion does not support it.
for index, (key, value) in enumerate(opts): for index, (key, value) in enumerate(opts):
if isinstance(key, UnicodeType): if isinstance(key, UnicodeType):
opts[index] = (key.encode(cls.ARG_ENCODING), value) opts[index] = (key.encode(cls.ARG_ENCODING), value)
if sp_arguments: if command.source:
if len(spec_args) == 1 and not spec_kwargs and not var_args and not var_kwargs: args.insert(0, arguments)
return (arguments,), {}
args.insert(spec_args.index('__arguments__'), arguments)
return tuple(args), dict(opts) return tuple(args), dict(opts)
@ -561,22 +535,29 @@ def command(*names, **kwargs):
A decorator which provides a declarative way of defining commands. 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 You can specify a set of names by which you can call the command. If names
are empty - then the name of the command will be set to native (extracted 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 from the handler name).
is non-empty - then native name will be added as well.
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 If usage=True is given - then handler's doc will be appended with an
auto-generated usage info. 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 If raw=True is given - then command should define only one argument to
which all raw, unprocessed command arguments will be given. which all raw and unprocessed source arguments will be given.
If optional is set to a string then if __optional__ specified - its name If empty=True is given - then when raw=True is set and command receives no
('optional' by-default) in the usage info will be substitued by whatever is arguments - an exception will be raised.
given.
If empty=True is given - then if raw is enabled it will allow to pass empty If extra=True is given - then last positional argument will receive every
(None) raw arguments to a command. 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 expand_short=True is given - then if command receives one-letter 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 options (like -v or -f) they will be expanded to a verbose ones (like
@ -589,13 +570,14 @@ def command(*names, **kwargs):
include_native = kwargs.get('include_native', True) include_native = kwargs.get('include_native', True)
usage = kwargs.get('usage', True) usage = kwargs.get('usage', True)
source = kwargs.get('source', False)
raw = kwargs.get('raw', False) raw = kwargs.get('raw', False)
optional = kwargs.get('optional', 'optional') extra = kwargs.get('extra', False)
empty = kwargs.get('empty', False) empty = kwargs.get('empty', False)
expand_short = kwargs.get('expand_short', True) expand_short = kwargs.get('expand_short', True)
def decorator(handler): def decorator(handler):
command = Command(handler, usage, raw, optional, empty, expand_short) command = Command(handler, usage, source, raw, extra, empty, expand_short)
# Extract and inject native name while making sure it is going to be the # Extract and inject native name while making sure it is going to be the
# first one in the list. # first one in the list.