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)