changeset b6ef0080edf2 in /home/hg/repos/gajim
details:http://hg.gajim.org/gajim?cmd=changeset;node=b6ef0080edf2
description: Few fixes for the command system
diffstat:
src/commands/framework.py | 15 +++++++++++++--
1 files changed, 13 insertions(+), 2 deletions(-)
diffs (32 lines):
diff -r 27317648a081 -r b6ef0080edf2 src/commands/framework.py
--- a/src/commands/framework.py Thu Sep 17 04:38:39 2009 +0300
+++ b/src/commands/framework.py Thu Sep 17 09:03:57 2009 +0300
@@ -65,6 +65,11 @@
# unless it is already set by the one who raised an exception.
if not exception.command and not exception.name:
raise CommandError(exception.message, self)
+
+ # Do not forget to re-raise an exception just like it was if at
+ # least either, command or name attribute is set properly.
+ 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.
@@ -603,8 +608,14 @@
for key, value in opts:
initial = norm_kwargs.get(key)
if isinstance(initial, (TupleType, ListType)) and value not in initial:
- if value:
- raise CommandError("Wrong argument", command)
+ raise CommandError("Wrong argument", command)
+
+ # Detect every switch and ensure it will not receive any arguments.
+ # Normally this does not happen unless overlapping is enabled.
+ for key, value in opts:
+ initial = norm_kwargs.get(key)
+ if isinstance(initial, BooleanType) and not isinstance(value, BooleanType):
+ raise CommandError("Switches do not take arguments", command)
# We need to encode every keyword argument to a simple string, not the
# unicode one, because ** expansion does not support it.
changeset 27317648a081 in /home/hg/repos/gajim
details:http://hg.gajim.org/gajim?cmd=changeset;node=27317648a081
description: Moderate refactoring and parser/adapter enhancements
diffstat:
src/commands/framework.py | 194 +++++++++++++++++++++++++++++++-------
src/commands/implementation.py | 42 ++++---
src/commands/middleware.py | 9 -
3 files changed, 182 insertions(+), 63 deletions(-)
diffs (truncated from 472 to 300 lines):
diff -r 75b4ebd9e2cd -r 27317648a081 src/commands/framework.py
--- a/src/commands/framework.py Tue Sep 15 23:40:29 2009 +0300
+++ b/src/commands/framework.py Thu Sep 17 04:38:39 2009 +0300
@@ -23,7 +23,7 @@
from inspect import getargspec
from operator import itemgetter
-class CommandInternalError(Exception):
+class InternalError(Exception):
pass
class CommandError(Exception):
@@ -46,13 +46,14 @@
ARG_USAGE_PATTERN = 'Usage: %s %s'
- def __init__(self, handler, usage, source, raw, extra, empty, expand_short):
+ def __init__(self, handler, usage, source, raw, extra, overlap, empty, expand_short):
self.handler = handler
self.usage = usage
self.source = source
self.raw = raw
self.extra = extra
+ self.overlap = overlap
self.empty = empty
self.expand_short = expand_short
@@ -120,7 +121,7 @@
# Removing self from arguments specification. Command handler should
# normally be an instance method.
if spec_args.pop(0) != 'self':
- raise CommandInternalError("First argument must be self")
+ raise InternalError("First argument must be self")
return spec_args, spec_kwargs, var_args, var_kwargs
@@ -142,9 +143,14 @@
for key, value in spec_kwargs:
letter = key[0]
+ key = key.replace('_', '-')
- key = key.replace('_', '-')
- value = ('=%s' % value) if not isinstance(value, BooleanType) else str()
+ if isinstance(value, BooleanType):
+ value = str()
+ elif isinstance(value, (TupleType, ListType)):
+ value = '={%s}' % ', '.join(value)
+ else:
+ value = '=%s' % value
if letter not in letters:
kwargs.append('-(-%s)%s%s' % (letter, key[1:], value))
@@ -155,9 +161,12 @@
usage = str()
args = str()
- if len(spec_args) == 1 and self.raw:
- args += ('(|%s|)' if self.empty else '|%s|') % spec_args[0]
- elif spec_args or var_args or sp_extra:
+ if self.raw:
+ spec_len = len(spec_args) - 1
+ if spec_len:
+ args += ('<%s>' % ', '.join(spec_args[:spec_len])) + ' '
+ args += ('(|%s|)' if self.empty else '|%s|') % spec_args[-1]
+ else:
if spec_args:
args += '<%s>' % ', '.join(spec_args)
if var_args or sp_extra:
@@ -185,8 +194,10 @@
hosted = {}
def __init__(cls, name, bases, dct):
- Dispatcher.check_if_dispatchable(bases, dct)
- Dispatcher.check_if_hostable(bases, dct)
+ dispatchable = Dispatcher.check_if_dispatchable(bases, dct)
+ hostable = Dispatcher.check_if_hostable(bases, dct)
+
+ cls.check_if_conformed(dispatchable, hostable)
if Dispatcher.is_suitable(cls, dct):
Dispatcher.register_processor(cls)
@@ -207,20 +218,27 @@
def check_if_dispatchable(cls, bases, dct):
dispatcher = dct.get('DISPATCHED_BY')
if not dispatcher:
- return
+ return False
if dispatcher not in bases:
- raise CommandInternalError("Should be dispatched by the same processor it inherits from")
+ raise InternalError("Should be dispatched by the same processor it inherits from")
+ return True
@classmethod
def check_if_hostable(cls, bases, dct):
hosters = dct.get('HOSTED_BY')
if not hosters:
- return
+ return False
if not isinstance(hosters, (TupleType, ListType)):
hosters = (hosters,)
for hoster in hosters:
if hoster not in bases:
- raise CommandInternalError("Should be hosted by the same processors it inherits from")
+ raise InternalError("Should be hosted by the same processors it inherits from")
+ return True
+
+ @classmethod
+ def check_if_conformed(cls, dispatchable, hostable):
+ if dispatchable and hostable:
+ raise InternalError("Processor can not be dispatchable and hostable at the same time")
@classmethod
def register_processor(cls, proc):
@@ -261,7 +279,7 @@
if name not in cls.table[proc]:
cls.table[proc][name] = command
else:
- raise CommandInternalError("Command with name %s already exists" % name)
+ raise InternalError("Command with name %s already exists" % name)
@classmethod
def register_adhocs(cls, proc):
hosters = proc.HOSTED_BY
@@ -421,6 +439,13 @@
position = match.span()
args.append((body, position))
+ # In rare occasions quoted options are being captured, while they should
+ # not be. This fixes the problem by finding options which intersect with
+ # arguments and removing them.
+ for key, value, position in opts[:]:
+ if intersects_args(position):
+ opts.remove((key, value, position))
+
return args, opts
@classmethod
@@ -441,20 +466,85 @@
cases) then this option will be treated as a switch, that is an option
which does not take an argument. Argument preceded by a switch will be
treated just like a normal positional argument.
+
+ If keyword argument's initial value is a sequence (tuple or a string)
+ then possible values of the option will be restricted to one of the
+ values given by the sequence.
"""
spec_args, spec_kwargs, var_args, var_kwargs = command.extract_arg_spec()
- spec_kwargs = dict(spec_kwargs)
+ 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 on.
if command.raw:
- if len(spec_args) == 1 and not spec_kwargs and not var_args and not var_kwargs:
- if arguments or command.empty:
- return (arguments,), {}
- raise CommandError("Can not be used without arguments", command)
- raise CommandInternalError("Raw command must define no more then one argument")
+ if spec_kwargs or var_args or var_kwargs:
+ raise InternalError("Raw commands should define only positional arguments")
+ 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.
+ for key, value, (start, end) in opts[:spec_len]:
+ if value:
+ end -= len(value) + 1
+ args.append((arguments[start:end], (start, end)))
+ args.append((value, (end, end + len(value) + 1)))
+ 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.
+ args.sort(key=itemgetter(1))
+
+ if spec_len > 1:
+ stopper, (start, end) = args[spec_len - 2]
+
+ 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.
+ args = args[:spec_len - 1]
+ opts = []
+
+ args.append((raw, (end, arguments_end)))
+ elif spec_len == 1:
+ args = [(arguments, (0, arguments_end))]
+ else:
+ raise InternalError("Raw command must define a collector")
+ else:
+ if command.empty:
+ args.append((None, (0, 0)))
+ 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 (_).
+ 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.
if command.expand_short:
expanded = []
- for spec_key, spec_value in spec_kwargs.iteritems():
+ for spec_key, spec_value in norm_kwargs.iteritems():
letter = spec_key[0] if len(spec_key) > 1 else None
if letter and letter not in expanded:
for index, (key, value, position) in enumerate(opts):
@@ -463,8 +553,10 @@
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.
for index, (key, value, position) in enumerate(opts):
- if isinstance(spec_kwargs.get(key), BooleanType):
+ if isinstance(norm_kwargs.get(key), BooleanType):
opts[index] = (key, True, position)
if value:
args.append((value, position))
@@ -479,18 +571,40 @@
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.extra:
if not var_args:
- positional_len = len(spec_args) - (1 if not command.source else 2)
- extra = args[positional_len:]
- args = args[:positional_len]
+ spec_fix = 1 if not command.source else 2
+ spec_len = len(spec_args) - spec_fix
+ extra = args[spec_len:]
+ args = args[:spec_len]
args.append(extra)
else:
- raise CommandInternalError("Can not have both, extra and *args")
+ raise InternalError("Can not have both, extra and *args")
- for index, (key, value) in enumerate(opts):
- if '-' in key:
- opts[index] = (key.replace('-', '_'), value)
+ # 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:
+ if command.overlap:
+ overlapped = args[spec_len:]
+ args = args[:spec_len]
+ for arg, (spec_key, spec_value) in zip(overlapped, spec_kwargs):
+ opts.append((spec_key, arg))
+ else:
+ raise CommandError("Excessive arguments", command)
+
+ # Detect every contraint sequences and ensure that if corresponding
+ # options are given - they contain proper values, within constraint
+ # range.
+ for key, value in opts:
+ initial = norm_kwargs.get(key)
+ if isinstance(initial, (TupleType, ListType)) and value not in initial:
+ if value:
+ raise CommandError("Wrong argument", command)
# We need to encode every keyword argument to a simple string, not the
# unicode one, because ** expansion does not support it.
@@ -498,9 +612,13 @@
if isinstance(key, UnicodeType):
opts[index] = (key.encode(cls.ARG_ENCODING), value)
+ # 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)
changeset f31ac80a0b5a in /home/hg/repos/gajim
details:http://hg.gajim.org/gajim?cmd=changeset;node=f31ac80a0b5a
description: Enhanced command arguments parser/adapter
diffstat:
src/commands/framework.py | 97 ++++++++++++++++++++++++++++++++++--------------
1 files changed, 68 insertions(+), 29 deletions(-)
diffs (177 lines):
diff -r 938c39c32b42 -r f31ac80a0b5a src/commands/framework.py
--- a/src/commands/framework.py Tue Sep 15 17:46:02 2009 +0300
+++ b/src/commands/framework.py Tue Sep 15 22:57:01 2009 +0300
@@ -19,8 +19,9 @@
"""
import re
-from types import FunctionType, UnicodeType, TupleType, ListType
+from types import FunctionType, UnicodeType, TupleType, ListType, BooleanType
from inspect import getargspec
+from operator import itemgetter
class CommandInternalError(Exception):
pass
@@ -59,8 +60,13 @@
try:
return self.handler(*args, **kwargs)
except CommandError, exception:
+ # Re-raise an excepttion with a proper command attribute set,
+ # unless it is already set by the one who raised an exception.
if not exception.command and not exception.name:
raise CommandError(exception.message, self)
+ # 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.
except TypeError:
raise CommandError("Command received invalid arguments", self)
@@ -136,13 +142,15 @@
for key, value in spec_kwargs:
letter = key[0]
+
key = key.replace('_', '-')
+ value = ('=%s' % value) if not isinstance(value, BooleanType) else str()
if letter not in letters:
- kwargs.append('-(-%s)%s=%s' % (letter, key[1:], value))
+ kwargs.append('-(-%s)%s%s' % (letter, key[1:], value))
letters.append(letter)
else:
- kwargs.append('--%s=%s' % (key, value))
+ kwargs.append('--%s%s' % (key, value))
usage = str()
args = str()
@@ -371,9 +379,10 @@
def parse_command_arguments(cls, arguments):
"""
Simple yet effective and sufficient in most cases parser which parses
- command arguments and returns them as two lists, first representing
- positional arguments, and second representing options as (key, value)
- tuples.
+ command arguments and returns them as two lists. First 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.
The format of the input arguments should be:
<arg1, arg2> <<extra>> [-(-o)ption=value1, -(-a)nother=value2] [[extra_options]]
@@ -386,27 +395,36 @@
"""
args, opts = [], []
- # Need to store position of every option we have parsed in order to get
- # arguments to be parsed correct later.
- opt_positions = []
+ def intersects_opts((given_start, given_end)):
+ """
+ Check if something intersects with boundaries of any parsed option.
+ """
+ for key, value, (start, end) in opts:
+ if given_start >= start and given_end <= end:
+ return True
+ return False
- def intersects((given_start, given_end)):
+ def intersects_args((given_start, given_end)):
"""
- Check if something intersects with boundaries of any parsed options.
+ Check if something intersects with boundaries of any parsed argument.
"""
- for start, end in opt_positions:
- if given_start == start or given_end == end:
+ for arg, (start, end) in args:
+ if given_start >= start and given_end <= end:
return True
return False
for match in re.finditer(cls.OPT_PATTERN, arguments):
if match:
- opt_positions.append(match.span())
- opts.append((match.group('key'), match.group('value') or True))
+ key = match.group('key')
+ value = match.group('value') or None
+ position = match.span()
+ opts.append((key, value, position))
for match in re.finditer(cls.ARG_PATTERN, arguments):
- if match and not intersects(match.span()):
- args.append(match.group('body'))
+ if match and not intersects_opts(match.span()):
+ body = match.group('body')
+ position = match.span()
+ args.append((body, position))
return args, opts
@@ -423,17 +441,49 @@
Dashes (-) in the option names will be converted to underscores. So you
can map --one-more-option to a one_more_option=None.
+
+ If 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. Argument preceded by a switch will be
+ treated just like a normal positional argument.
"""
spec_args, spec_kwargs, var_args, var_kwargs = command.extract_arg_spec()
spec_kwargs = dict(spec_kwargs)
if command.raw:
- if len(spec_args) == 1:
+ if len(spec_args) == 1 and not spec_kwargs and not var_args and not var_kwargs:
if arguments or command.empty:
return (arguments,), {}
raise CommandError("Can not be used without arguments", command)
raise CommandInternalError("Raw command must define no more then one argument")
+ if command.expand_short:
+ expanded = []
+ for spec_key, spec_value in spec_kwargs.iteritems():
+ letter = spec_key[0] if len(spec_key) > 1 else None
+ if letter and letter not in expanded:
+ for index, (key, value, position) in enumerate(opts):
+ if key == letter:
+ expanded.append(letter)
+ opts[index] = (spec_key, value, position)
+ break
+
+ for index, (key, value, position) in enumerate(opts):
+ if isinstance(spec_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.
+ 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.
+ args = map(lambda (arg, position): arg, args)
+ opts = map(lambda (key, value, position): (key, value), opts)
+
if command.extra:
if not var_args:
positional_len = len(spec_args) - (1 if not command.source else 2)
@@ -443,17 +493,6 @@
else:
raise CommandInternalError("Can not have both, extra and *args")
- if command.expand_short:
- expanded = []
- for spec_key, spec_value in spec_kwargs.iteritems():
- letter = spec_key[0] if len(spec_key) > 1 else None
- if letter and letter not in expanded:
- for index, (key, value) in enumerate(opts):
- if key == letter:
- expanded.append(letter)
- opts[index] = (spec_key, value)
- break
-
for index, (key, value) in enumerate(opts):
if '-' in key:
opts[index] = (key.replace('-', '_'), value)
changeset 938c39c32b42 in /home/hg/repos/gajim
details:http://hg.gajim.org/gajim?cmd=changeset;node=938c39c32b42
description: More code refactoring
diffstat:
src/commands/framework.py | 104 +++++++++++++++++++++------------------------------
1 files changed, 43 insertions(+), 61 deletions(-)
diffs (210 lines):
diff -r 795bf82c2061 -r 938c39c32b42 src/commands/framework.py
--- a/src/commands/framework.py Tue Sep 15 03:33:02 2009 +0300
+++ b/src/commands/framework.py Tue Sep 15 17:46:02 2009 +0300
@@ -45,12 +45,13 @@
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.usage = usage
+ self.source = source
self.raw = raw
- self.optional = optional
+ self.extra = extra
self.empty = empty
self.expand_short = expand_short
@@ -100,8 +101,7 @@
keep them simple yet meaningful.
"""
doc = self.extract_doc()
- if doc:
- return doc.split('\n', 1)[0]
+ return doc.split('\n', 1)[0] if doc else None
def extract_arg_spec(self):
names, var_args, var_kwargs, defaults = getargspec(self.handler)
@@ -126,19 +126,10 @@
"""
spec_args, spec_kwargs, var_args, var_kwargs = self.extract_arg_spec()
- # 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__')
-
- # 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__')
+ # 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 self.source else None
+ sp_extra = spec_args.pop() if self.extra else None
kwargs = []
letters = []
@@ -158,11 +149,11 @@
if len(spec_args) == 1 and self.raw:
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:
args += '<%s>' % ', '.join(spec_args)
- if var_args or sp_optional:
- args += (' ' if spec_args else str()) + '<<%s>>' % (var_args or self.optional)
+ if var_args or sp_extra:
+ args += (' ' if spec_args else str()) + '<<%s>>' % (var_args or sp_extra)
usage += args
@@ -385,7 +376,7 @@
tuples.
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
--option value or -option value. Keys without values will get True as
@@ -426,17 +417,6 @@
of arguments specified on command definition. That is transforms them to
*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
passed as if they were value for keywords, in the order keywords are
defined and printed in usage.
@@ -447,10 +427,6 @@
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 len(spec_args) == 1:
if arguments or command.empty:
@@ -458,18 +434,14 @@
raise CommandError("Can not be used without arguments", command)
raise CommandInternalError("Raw command must define no more then one argument")
- if sp_optional:
+ if command.extra:
if not var_args:
- hard_len = len(spec_args) - (1 if not sp_arguments else 2)
- optional = args[hard_len:]
- args = args[:hard_len]
- args.insert(spec_args.index('__optional__'), optional)
+ positional_len = len(spec_args) - (1 if not command.source else 2)
+ extra = args[positional_len:]
+ args = args[:positional_len]
+ args.append(extra)
else:
- raise CommandInternalError("Can not have both, __optional__ and *args")
-
- for index, (key, value) in enumerate(opts):
- if '-' in key:
- opts[index] = (key.replace('-', '_'), value)
+ raise CommandInternalError("Can not have both, extra and *args")
if command.expand_short:
expanded = []
@@ -482,16 +454,18 @@
opts[index] = (spec_key, value)
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
# unicode one, because ** expansion does not support it.
for index, (key, value) in enumerate(opts):
if isinstance(key, UnicodeType):
opts[index] = (key.encode(cls.ARG_ENCODING), value)
- if sp_arguments:
- if len(spec_args) == 1 and not spec_kwargs and not var_args and not var_kwargs:
- return (arguments,), {}
- args.insert(spec_args.index('__arguments__'), arguments)
+ if command.source:
+ args.insert(0, arguments)
return tuple(args), dict(opts)
@@ -561,22 +535,29 @@
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
- are empty - then the name of the command will be set to native (extracted
- from the handler name). If include_native=True argument is given and names
- is non-empty - then native name will be added as well.
+ 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 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
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
- 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
- ('optional' by-default) in the usage info will be substitued by whatever is
- given.
+ If empty=True is given - then when raw=True is set and command receives no
+ arguments - an exception will be raised.
- If empty=True is given - then if raw is enabled it will allow to pass empty
- (None) raw arguments to a command.
+ If extra=True is given - then last positional argument will receive every
+ 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
options (like -v or -f) they will be expanded to a verbose ones (like
@@ -589,13 +570,14 @@
include_native = kwargs.get('include_native', True)
usage = kwargs.get('usage', True)
+ source = kwargs.get('source', False)
raw = kwargs.get('raw', False)
- optional = kwargs.get('optional', 'optional')
+ extra = kwargs.get('extra', False)
empty = kwargs.get('empty', False)
expand_short = kwargs.get('expand_short', True)
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
# first one in the list.
changeset 795bf82c2061 in /home/hg/repos/gajim
details:http://hg.gajim.org/gajim?cmd=changeset;node=795bf82c2061
description: Made command options refactoring along with some fixes
diffstat:
src/commands/framework.py | 115 ++++++++++++++++++-------------------
1 files changed, 56 insertions(+), 59 deletions(-)
diffs (247 lines):
diff -r 3f6d9e2d69fb -r 795bf82c2061 src/commands/framework.py
--- a/src/commands/framework.py Tue Sep 15 01:20:49 2009 +0300
+++ b/src/commands/framework.py Tue Sep 15 03:33:02 2009 +0300
@@ -45,15 +45,14 @@
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.is_instance = is_instance
self.usage = usage
self.raw = raw
- self.dashes = dashes
self.optional = optional
self.empty = empty
+ self.expand_short = expand_short
def __call__(self, *args, **kwargs):
try:
@@ -110,12 +109,12 @@
# 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 = 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
- # is an instance method.
- if self.is_instance and spec_args.pop(0) != 'self':
- raise CommandInternalError("Invalid arguments specification")
+ # Removing self from arguments specification. Command handler should
+ # normally be an instance method.
+ if spec_args.pop(0) != 'self':
+ raise CommandInternalError("First argument must be self")
return spec_args, spec_kwargs, var_args, var_kwargs
@@ -125,27 +124,28 @@
human-readable format. If complete is given - then ARG_USAGE_PATTERN
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()
- '__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 optional:
+ # 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 = []
letters = []
- # The reason we don't iterate here through spec_kwargs, like we would
- # 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]
+ for key, value in spec_kwargs:
letter = key[0]
-
- if self.dashes:
- key = key.replace('_', '-')
+ key = key.replace('_', '-')
if letter not in letters:
kwargs.append('-(-%s)%s=%s' % (letter, key[1:], value))
@@ -158,10 +158,10 @@
if len(spec_args) == 1 and self.raw:
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:
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)
usage += args
@@ -341,8 +341,6 @@
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))?')
- EXPAND_SHORT_OPTIONS = True
-
COMMAND_PREFIX = '/'
CASE_SENSITIVE_COMMANDS = False
@@ -428,13 +426,6 @@
of arguments specified on command definition. That is transforms them to
*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
argument will receive raw and unprocessed arguments. Also, if nothing
except __arguments__ (including *args, *kwargs splatting) is defined -
@@ -449,8 +440,16 @@
Extra arguments which are not considered extra (or optional) - will be
passed as if they were value for keywords, in the order keywords are
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_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 len(spec_args) == 1:
@@ -459,21 +458,20 @@
raise CommandError("Can not be used without arguments", command)
raise CommandInternalError("Raw command must define no more then one argument")
- if '__optional__' in spec_args:
+ if sp_optional:
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:]
args = args[:hard_len]
args.insert(spec_args.index('__optional__'), optional)
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):
- if '-' in key:
- opts[index] = (key.replace('-', '_'), value)
+ for index, (key, value) in enumerate(opts):
+ if '-' in key:
+ opts[index] = (key.replace('-', '_'), value)
- if cls.EXPAND_SHORT_OPTIONS:
+ if command.expand_short:
expanded = []
for spec_key, spec_value in spec_kwargs.iteritems():
letter = spec_key[0] if len(spec_key) > 1 else None
@@ -485,12 +483,12 @@
break
# 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):
if isinstance(key, UnicodeType):
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:
return (arguments,), {}
args.insert(spec_args.index('__arguments__'), arguments)
@@ -564,52 +562,51 @@
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
- from the handler name). If no_native=True argument is given and names is
- non-empty - then native name will not be added.
-
- 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.
+ from the handler name). 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
- 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.
- 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
('optional' by-default) in the usage info will be substitued by whatever is
given.
If empty=True is given - then if raw is enabled it will allow to pass empty
(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)
+ 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)
raw = kwargs.get('raw', False)
- dashes = kwargs.get('dashes', True)
optional = kwargs.get('optional', 'optional')
empty = kwargs.get('empty', False)
+ expand_short = kwargs.get('expand_short', True)
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
# 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)
command.names = tuple(names)
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):
return decorator(names.pop())
changeset 3f6d9e2d69fb in /home/hg/repos/gajim
details:http://hg.gajim.org/gajim?cmd=changeset;node=3f6d9e2d69fb
description: Slighlty changed command arguments parser/adapter internal logic
Made parser to preserve the order of options, so I can use it later to provide
some enhancments for the adapter.
diffstat:
src/commands/framework.py | 62 +++++++++++++++---------------
1 files changed, 31 insertions(+), 31 deletions(-)
diffs (126 lines):
diff -r 27beeb72d149 -r 3f6d9e2d69fb src/commands/framework.py
--- a/src/commands/framework.py Mon Sep 14 22:04:49 2009 +0300
+++ b/src/commands/framework.py Tue Sep 15 01:20:49 2009 +0300
@@ -382,8 +382,9 @@
def parse_command_arguments(cls, arguments):
"""
Simple yet effective and sufficient in most cases parser which parses
- command arguments and maps them to *args and **kwargs, which we all use
- extensivly in daily Python coding.
+ command arguments and returns them as two lists, first representing
+ positional arguments, and second representing options as (key, value)
+ tuples.
The format of the input arguments should be:
<arg1, arg2> <<optional>> [-(-o)ption=value1, -(-a)nother=value2] [[extra_options]]
@@ -394,37 +395,38 @@
'one two three' or "one two three"; that is between single or double
quotes.
"""
- args, kwargs = [], {}
+ args, opts = [], []
- # Need to store every option we have parsed in order to get arguments
- # to be parsed correct later.
- options = []
+ # Need to store position of every option we have parsed in order to get
+ # arguments to be parsed correct later.
+ opt_positions = []
def intersects((given_start, given_end)):
"""
Check if something intersects with boundaries of any parsed options.
"""
- for start, end in options:
+ for start, end in opt_positions:
if given_start == start or given_end == end:
return True
return False
for match in re.finditer(cls.OPT_PATTERN, arguments):
if match:
- options.append(match.span())
- kwargs[match.group('key')] = match.group('value') or True
+ opt_positions.append(match.span())
+ opts.append((match.group('key'), match.group('value') or True))
for match in re.finditer(cls.ARG_PATTERN, arguments):
if match and not intersects(match.span()):
args.append(match.group('body'))
- return args, kwargs
+ return args, opts
@classmethod
- def adapt_command_arguments(cls, command, arguments, args, kwargs):
+ def adapt_command_arguments(cls, command, arguments, args, opts):
"""
- Adapts *args and **kwargs got from a parser to a specific handler by
- means of arguments specified on command definition.
+ Adapts args and opts got from the parser to a specific handler by means
+ of arguments specified on command definition. That is transforms them to
+ *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
@@ -467,35 +469,33 @@
raise CommandInternalError("Cant have both, __optional__ and *args")
if command.dashes:
- for key, value in kwargs.items():
+ for index, (key, value) in enumerate(opts):
if '-' in key:
- del kwargs[key]
- kwargs[key.replace('-', '_')] = value
+ opts[index] = (key.replace('-', '_'), value)
if cls.EXPAND_SHORT_OPTIONS:
expanded = []
- for key, value in spec_kwargs.iteritems():
- letter = key[0] if len(key) > 1 else None
- if letter and letter in kwargs and letter not in expanded:
- expanded.append(letter)
- kwargs[key] = kwargs[letter]
- del kwargs[letter]
+ for spec_key, spec_value in spec_kwargs.iteritems():
+ letter = spec_key[0] if len(spec_key) > 1 else None
+ if letter and letter not in expanded:
+ for index, (key, value) in enumerate(opts):
+ if key == letter:
+ expanded.append(letter)
+ opts[index] = (spec_key, value)
+ break
# We need to encode every keyword argument to a simple string, not the
- # unicode one, because ** expanding does not support it. The nasty issue
- # here to consider is that if dict key was initially set as u'test',
- # then resetting it to just 'test' leaves u'test' as it was...
- for key, value in kwargs.items():
+ # unicode one, because ** expanding does not support it.
+ for index, (key, value) in enumerate(opts):
if isinstance(key, UnicodeType):
- del kwargs[key]
- kwargs[key.encode(cls.ARG_ENCODING)] = value
+ opts[index] = (key.encode(cls.ARG_ENCODING), value)
if '__arguments__' in spec_args:
if len(spec_args) == 1 and not spec_kwargs and not var_args and not var_kwargs:
return (arguments,), {}
args.insert(spec_args.index('__arguments__'), arguments)
- return args, kwargs
+ return tuple(args), dict(opts)
def process_as_command(self, text):
"""
@@ -526,8 +526,8 @@
def execute_command(self, name, arguments):
command = self.retrieve_command(name)
- args, kwargs = self.parse_command_arguments(arguments) if arguments else ([], {})
- args, kwargs = self.adapt_command_arguments(command, arguments, args, kwargs)
+ args, opts = self.parse_command_arguments(arguments) if arguments else ([], [])
+ args, kwargs = self.adapt_command_arguments(command, arguments, args, opts)
if self.command_preprocessor(name, command, arguments, args, kwargs):
return
changeset 27beeb72d149 in /home/hg/repos/gajim
details:http://hg.gajim.org/gajim?cmd=changeset;node=27beeb72d149
description: Made some cosmetic API changes to the command system
diffstat:
src/commands/custom.py | 8 ++++----
src/commands/framework.py | 25 ++++++++++++-------------
src/commands/implementation.py | 12 ++++++------
3 files changed, 22 insertions(+), 23 deletions(-)
diffs (153 lines):
diff -r b3e1cbac3666 -r 27beeb72d149 src/commands/custom.py
--- a/src/commands/custom.py Sun Sep 13 22:11:40 2009 +0300
+++ b/src/commands/custom.py Mon Sep 14 22:04:49 2009 +0300
@@ -29,7 +29,7 @@
here will be available to all of them.
"""
- IS_COMMAND_PROCESSOR = True
+ DISPATCH = True
HOSTED_BY = ChatCommands, PrivateChatCommands, GroupChatCommands
@command
@@ -52,7 +52,7 @@
only to a chat.
"""
- IS_COMMAND_PROCESSOR = True
+ DISPATCH = True
HOSTED_BY = ChatCommands
@command
@@ -66,7 +66,7 @@
available only to a private chat.
"""
- IS_COMMAND_PROCESSOR = True
+ DISPATCH = True
HOSTED_BY = PrivateChatCommands
@command
@@ -80,7 +80,7 @@
available only to a group chat.
"""
- IS_COMMAND_PROCESSOR = True
+ DISPATCH = True
HOSTED_BY = GroupChatCommands
@command
diff -r b3e1cbac3666 -r 27beeb72d149 src/commands/framework.py
--- a/src/commands/framework.py Sun Sep 13 22:11:40 2009 +0300
+++ b/src/commands/framework.py Mon Sep 14 22:04:49 2009 +0300
@@ -201,8 +201,8 @@
@classmethod
def is_suitable(cls, proc, dct):
is_not_root = dct.get('__metaclass__') is not cls
- is_processor = bool(dct.get('IS_COMMAND_PROCESSOR'))
- return is_not_root and is_processor
+ to_be_dispatched = bool(dct.get('DISPATCH'))
+ return is_not_root and to_be_dispatched
@classmethod
def check_if_dispatchable(cls, bases, dct):
@@ -231,18 +231,18 @@
@classmethod
def register_processor(cls, proc):
cls.table[proc] = {}
- inherited = proc.__dict__.get('INHERITED')
+ inherit = proc.__dict__.get('INHERIT')
if 'HOSTED_BY' in proc.__dict__:
cls.register_adhocs(proc)
- commands = cls.traverse_commands(proc, inherited)
+ commands = cls.traverse_commands(proc, inherit)
cls.register_commands(proc, commands)
@classmethod
def sanitize_names(cls, proc):
- inherited = proc.__dict__.get('INHERITED')
- commands = cls.traverse_commands(proc, inherited)
+ inherit = proc.__dict__.get('INHERIT')
+ commands = cls.traverse_commands(proc, inherit)
for key, command in commands:
if not proc.SAFE_NAME_SCAN_PATTERN.match(key):
setattr(proc, proc.SAFE_NAME_SUBS_PATTERN % key, command)
@@ -252,8 +252,8 @@
pass
@classmethod
- def traverse_commands(cls, proc, inherited=True):
- keys = dir(proc) if inherited else proc.__dict__.iterkeys()
+ def traverse_commands(cls, proc, inherit=True):
+ keys = dir(proc) if inherit else proc.__dict__.iterkeys()
for key in keys:
value = getattr(proc, key)
if isinstance(value, Command):
@@ -295,8 +295,8 @@
commands = dict(cls.traverse_commands(proc.DISPATCHED_BY))
if proc.DISPATCHED_BY in cls.hosted:
for adhoc in cls.hosted[proc.DISPATCHED_BY]:
- inherited = adhoc.__dict__.get('INHERITED')
- commands.update(dict(cls.traverse_commands(adhoc, inherited)))
+ inherit = adhoc.__dict__.get('INHERIT')
+ commands.update(dict(cls.traverse_commands(adhoc, inherit)))
return commands.values()
class CommandProcessor(object):
@@ -311,8 +311,7 @@
to an object you are adding commands to.
Your subclass, which will contain commands should define in its body
- IS_COMMAND_PROCESSOR = True in order to be included in the dispatching
- table.
+ DISPATCH = True in order to be included in the dispatching table.
Every class you will drop the processor in should define DISPATCHED_BY set
to the same processor you are inheriting from.
@@ -326,7 +325,7 @@
whatever includes the host) you need to inherit you processor from the host
and set HOSTED_BY to that host.
- INHERITED controls whether commands inherited from base classes (which could
+ INHERIT controls whether commands inherited from base classes (which could
include other processors) will be registered or not. This is disabled
by-default because it leads to unpredictable consequences when used in adhoc
processors which inherit from more then one processor or has such processors
diff -r b3e1cbac3666 -r 27beeb72d149 src/commands/implementation.py
--- a/src/commands/implementation.py Sun Sep 13 22:11:40 2009 +0300
+++ b/src/commands/implementation.py Mon Sep 14 22:04:49 2009 +0300
@@ -92,8 +92,8 @@
an instance of ChatControl when command is being called.
"""
- IS_COMMAND_PROCESSOR = True
- INHERITED = True
+ DISPATCH = True
+ INHERIT = True
@command
def ping(self):
@@ -111,8 +111,8 @@
self is set to an instance of PrivateChatControl when command is being called.
"""
- IS_COMMAND_PROCESSOR = True
- INHERITED = True
+ DISPATCH = True
+ INHERIT = True
class GroupChatCommands(CommonCommands):
"""
@@ -121,8 +121,8 @@
self is set to an instance of GroupchatControl when command is being called.
"""
- IS_COMMAND_PROCESSOR = True
- INHERITED = True
+ DISPATCH = True
+ INHERIT = True
@command(raw=True)
def nick(self, new_nick):