From c194b92136991a4b34e2976a69ab1c584d7ec682 Mon Sep 17 00:00:00 2001
From: red-agent <hell.director@gmail.com>
Date: Tue, 15 Sep 2009 03:33:02 +0300
Subject: [PATCH] Made command options refactoring along with some fixes

---
 src/commands/framework.py | 117 +++++++++++++++++++-------------------
 1 file changed, 57 insertions(+), 60 deletions(-)

diff --git a/src/commands/framework.py b/src/commands/framework.py
index ab3133cc2b..3e883a8bd7 100644
--- a/src/commands/framework.py
+++ b/src/commands/framework.py
@@ -45,15 +45,14 @@ class Command(object):
 
     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 @@ class Command(object):
         # 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 @@ class Command(object):
         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__')
-
-        optional = '__optional__' in spec_args
-        if optional:
+        # 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__')
 
         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 @@ class Command(object):
 
         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 @@ class CommandProcessor(object):
     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 @@ class CommandProcessor(object):
         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 @@ class CommandProcessor(object):
         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 @@ class CommandProcessor(object):
                 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 @@ class CommandProcessor(object):
                             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 @@ def command(*names, **kwargs):
 
     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())
 
-- 
GitLab