Andy Mikhailenko avatar Andy Mikhailenko committed 9765397

Fixed #12: the @command decorator would break on 2+ arguments which names start with the same character.

Comments (0)

Files changed (3)

-version = '0.14.0'
+version = '0.14.1'

argh/decorators.py

     # @arg (inferred)
     spec = inspect.getargspec(func)
     kwargs = dict(zip(*[reversed(x) for x in (spec.args, spec.defaults or [])]))
+
+    # define the list of conflicting option strings
+    # (short forms, i.e. single-character ones)
+    chars = [a[0] for a in spec.args]
+    char_counts = dict((char, chars.count(char)) for char in set(chars))
+    conflicting_opts = tuple(char for char in char_counts
+                             if 1 < char_counts[char])
+
     for a in reversed(spec.args):  # @arg adds specs in reversed order
         if a in kwargs:
-            func = arg(
-                '-{0}'.format(a[0]),
-                '--{0}'.format(a),
-                default=kwargs.get(a)
-            )(func)
+            if a.startswith(conflicting_opts):
+                func = arg(
+                    '--{0}'.format(a),
+                    default=kwargs.get(a)
+                )(func)
+            else:
+                print a, 'non conflicting'
+                func = arg(
+                    '-{0}'.format(a[0]),
+                    '--{0}'.format(a),
+                    default=kwargs.get(a)
+                )(func)
         else:
             func = arg(a)(func)
 
 import argparse
 import argh.helpers
 from argh import (
-    alias, ArghParser, arg, add_commands, CommandError, dispatch,
+    alias, ArghParser, arg, add_commands, command, CommandError, dispatch,
     plain_signature, wrap_errors
 )
 from argh import completion
     assert args.text == 'world', 'Do it yourself'  # bad manners :-(
     yield 'Hello %s' % args.text
 
+@command
+def command_deco(text='Hello'):
+    yield text
+
+@command
+def command_deco_issue12(foo=1, fox=2):
+    yield u'foo {0}, fox {1}'.format(foo, fox)
+
 
 class BaseArghTestCase(unittest.TestCase):
     commands = {}
                                 namespace=namespace)
 
 
+class CommandDecoratorTests(BaseArghTestCase):
+    commands = {None: [command_deco, command_deco_issue12]}
+
+    def test_command_decorator(self):
+        """The @command decorator creates arguments from function signature.
+        """
+        self.assert_cmd_returns('command-deco', 'Hello\n')
+        self.assert_cmd_returns('command-deco --text=hi', 'hi\n')
+
+    def test_regression_issue12(self):
+        """Issue #12: @command was broken if there were more than one argument
+        to begin with same character (i.e. short option names were inferred
+        incorrectly).
+        """
+        self.assert_cmd_returns('command-deco-issue12', 'foo 1, fox 2\n')
+        self.assert_cmd_returns('command-deco-issue12 --foo 3', 'foo 3, fox 2\n')
+        self.assert_cmd_returns('command-deco-issue12 --fox 3', 'foo 1, fox 3\n')
+        self.assert_cmd_fails('command-deco-issue12 -f 3', 'unrecognized')
+
+
 class ErrorWrappingTestCase(BaseArghTestCase):
     commands = {None: [strict_hello, strict_hello_smart]}
     def test_error_raised(self):
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.