Made command options refactoring along with some fixes

This commit is contained in:
red-agent 2009-09-15 03:33:02 +03:00
parent a264608b29
commit c194b92136
1 changed files with 56 additions and 59 deletions

View File

@ -45,15 +45,14 @@ class Command(object):
ARG_USAGE_PATTERN = 'Usage: %s %s' ARG_USAGE_PATTERN = 'Usage: %s %s'
def __init__(self, handler, is_instance, usage, raw, dashes, optional, empty): def __init__(self, handler, usage, raw, optional, empty, expand_short):
self.handler = handler self.handler = handler
self.is_instance = is_instance
self.usage = usage self.usage = usage
self.raw = raw self.raw = raw
self.dashes = dashes
self.optional = optional self.optional = optional
self.empty = empty self.empty = empty
self.expand_short = expand_short
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
try: try:
@ -110,12 +109,12 @@ class Command(object):
# Behavior of this code need to be checked. Might yield incorrect # Behavior of this code need to be checked. Might yield incorrect
# results on some rare occasions. # results on some rare occasions.
spec_args = names[:-len(defaults) if defaults else len(names)] spec_args = names[:-len(defaults) if defaults else len(names)]
spec_kwargs = dict(zip(names[-len(defaults):], defaults)) if defaults else {} spec_kwargs = list(zip(names[-len(defaults):], defaults)) if defaults else {}
# Removing self from arguments specification in case if command handler # Removing self from arguments specification. Command handler should
# is an instance method. # normally be an instance method.
if self.is_instance and spec_args.pop(0) != 'self': if spec_args.pop(0) != 'self':
raise CommandInternalError("Invalid arguments specification") raise CommandInternalError("First argument must be self")
return spec_args, spec_kwargs, var_args, var_kwargs return spec_args, spec_kwargs, var_args, var_kwargs
@ -125,27 +124,28 @@ class Command(object):
human-readable format. If complete is given - then ARG_USAGE_PATTERN human-readable format. If complete is given - then ARG_USAGE_PATTERN
will be used to render it completly. will be used to render it completly.
""" """
names, _var_args, _var_kwargs, defaults = getargspec(self.handler)
spec_args, spec_kwargs, var_args, var_kwargs = self.extract_arg_spec() spec_args, spec_kwargs, var_args, var_kwargs = self.extract_arg_spec()
'__arguments__' not in spec_args or spec_args.remove('__arguments__') # If command defines special __arguments__ parameter - it should not be
# included in the usage information, but may be used for internal
# purposes while generating usage information.
sp_arguments = '__arguments__' in spec_args
if sp_arguments:
spec_args.remove('__arguments__')
optional = '__optional__' in spec_args # If command defines special __optional__ parameter - it should not be
if optional: # 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__') spec_args.remove('__optional__')
kwargs = [] kwargs = []
letters = [] letters = []
# The reason we don't iterate here through spec_kwargs, like we would for key, value in spec_kwargs:
# normally do is that it does not retains order of items. We need to be
# sure that arguments will be printed in the order they were specified.
for key in (names[-len(defaults):] if defaults else ()):
value = spec_kwargs[key]
letter = key[0] letter = key[0]
key = key.replace('_', '-')
if self.dashes:
key = key.replace('_', '-')
if letter not in letters: if letter not in letters:
kwargs.append('-(-%s)%s=%s' % (letter, key[1:], value)) kwargs.append('-(-%s)%s=%s' % (letter, key[1:], value))
@ -158,10 +158,10 @@ 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 optional: elif spec_args or var_args or sp_optional:
if spec_args: if spec_args:
args += '<%s>' % ', '.join(spec_args) args += '<%s>' % ', '.join(spec_args)
if var_args or optional: if var_args or sp_optional:
args += (' ' if spec_args else str()) + '<<%s>>' % (var_args or self.optional) args += (' ' if spec_args else str()) + '<<%s>>' % (var_args or self.optional)
usage += args usage += args
@ -341,8 +341,6 @@ class CommandProcessor(object):
ARG_PATTERN = re.compile(r'(\'|")?(?P<body>(?(1).+?|\S+))(?(1)\1)') 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))?') OPT_PATTERN = re.compile(r'(?<!\w)--?(?P<key>[\w-]+)(?:(?:=|\s)(\'|")?(?P<value>(?(2)[^-]+?|[^-\s]+))(?(2)\2))?')
EXPAND_SHORT_OPTIONS = True
COMMAND_PREFIX = '/' COMMAND_PREFIX = '/'
CASE_SENSITIVE_COMMANDS = False CASE_SENSITIVE_COMMANDS = False
@ -428,13 +426,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.
When EXPAND_SHORT_OPTIONS is set then if command receives one-latter
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
argumens. Expansion is made on a first-latter comparison basis. If more
then one long option with the same first letter defined - only first one
will be used in expanding.
If command defines __arguments__ as a first argument - then this If command defines __arguments__ as a first argument - then this
argument will receive raw and unprocessed arguments. Also, if nothing argument will receive raw and unprocessed arguments. Also, if nothing
except __arguments__ (including *args, *kwargs splatting) is defined - except __arguments__ (including *args, *kwargs splatting) is defined -
@ -449,8 +440,16 @@ class CommandProcessor(object):
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.
Dashes (-) in the option names will be converted to underscores. So you
can map --one-more-option to a one_more_option=None.
""" """
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)
# 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:
@ -459,21 +458,20 @@ 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 '__optional__' in spec_args: if sp_optional:
if not var_args: if not var_args:
hard_len = len(spec_args) - 1 hard_len = len(spec_args) - (1 if not sp_arguments else 2)
optional = args[hard_len:] optional = args[hard_len:]
args = args[:hard_len] args = args[:hard_len]
args.insert(spec_args.index('__optional__'), optional) args.insert(spec_args.index('__optional__'), optional)
else: else:
raise CommandInternalError("Cant have both, __optional__ and *args") raise CommandInternalError("Can not have both, __optional__ and *args")
if command.dashes: for index, (key, value) in enumerate(opts):
for index, (key, value) in enumerate(opts): if '-' in key:
if '-' in key: opts[index] = (key.replace('-', '_'), value)
opts[index] = (key.replace('-', '_'), value)
if cls.EXPAND_SHORT_OPTIONS: if command.expand_short:
expanded = [] expanded = []
for spec_key, spec_value in spec_kwargs.iteritems(): for spec_key, spec_value in spec_kwargs.iteritems():
letter = spec_key[0] if len(spec_key) > 1 else None letter = spec_key[0] if len(spec_key) > 1 else None
@ -485,12 +483,12 @@ class CommandProcessor(object):
break break
# 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 ** expanding 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 '__arguments__' in spec_args: if sp_arguments:
if len(spec_args) == 1 and not spec_kwargs and not var_args and not var_kwargs: if len(spec_args) == 1 and not spec_kwargs and not var_args and not var_kwargs:
return (arguments,), {} return (arguments,), {}
args.insert(spec_args.index('__arguments__'), arguments) args.insert(spec_args.index('__arguments__'), arguments)
@ -564,52 +562,51 @@ def command(*names, **kwargs):
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 are empty - then the name of the command will be set to native (extracted
from the handler name). If no_native=True argument is given and names is from the handler name). If include_native=True argument is given and names
non-empty - then native name will not be added. is non-empty - then native name will be added as well.
If command handler is not an instance method then is_instance=False should
be given. Though mentioned case is not covered by defined behaviour, and
should not be used, unless you know what you are doing.
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-gereated usage info. auto-generated usage info.
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, unprocessed command arguments will be given.
If dashes=True is given, then dashes (-) in the option
names will be converted to underscores. So you can map --one-more-option to
a one_more_option=None.
If optional is set to a string then if __optional__ specified - its name If optional is set to a string then if __optional__ specified - its name
('optional' by-default) in the usage info will be substitued by whatever is ('optional' by-default) in the usage info will be substitued by whatever is
given. given.
If empty=True is given - then if raw is enabled it will allow to pass empty If empty=True is given - then if raw is enabled it will allow to pass empty
(None) raw arguments to a command. (None) raw arguments to a command.
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) names = list(names)
include_native = kwargs.get('include_native', True)
no_native = kwargs.get('no_native', False)
is_instance = kwargs.get('is_instance', True)
usage = kwargs.get('usage', True) usage = kwargs.get('usage', True)
raw = kwargs.get('raw', False) raw = kwargs.get('raw', False)
dashes = kwargs.get('dashes', True)
optional = kwargs.get('optional', 'optional') optional = kwargs.get('optional', 'optional')
empty = kwargs.get('empty', False) empty = kwargs.get('empty', False)
expand_short = kwargs.get('expand_short', True)
def decorator(handler): def decorator(handler):
command = Command(handler, is_instance, usage, raw, dashes, optional, empty) command = Command(handler, usage, raw, optional, 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.
if not names or names and not no_native: if not names or include_native:
names.insert(0, command.native_name) names.insert(0, command.native_name)
command.names = tuple(names) command.names = tuple(names)
return command return command
# Workaround if we are getting called without parameters. # 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): if len(names) == 1 and isinstance(names[0], FunctionType):
return decorator(names.pop()) return decorator(names.pop())