Repository: elcaminoreal/elcaminoreal

Issue:
Allow regular arguments to commands and dependencies
The following should work:

```
@COMMANDS.dependency(
        dependencies=['foo',  'bar'],
        possible_dependencies=['baz'],
        regular=True)
def thing(foo , bar, build_baz):
   val = foo()
    if val > 5:
        val -= build_baz() 
    return val + bar()

@COMMANDS.command(
                  dependencies=['thing'],
                  parser=ca.command('',
                                    ca.positional('lala', type=str)),
                   regular=True)
def do_stuff(thing, lala):
    return str(thing) + lala
```

Discussion:

Patch:
diff --git a/src/elcaminoreal/_gather.py b/src/elcaminoreal/_gather.py
index 63f85a9..cbe7637 100644
--- a/src/elcaminoreal/_gather.py
+++ b/src/elcaminoreal/_gather.py
@@ -24,12 +24,13 @@ class Commands(object):
     def command(self,
                 name=None,
                 parser=caparg.command(''),
-                dependencies=pyrsistent.v()):
+                dependencies=pyrsistent.v(),
+                regular=False):
         """
         Register as a command.
 
         """
-        transform = gather.Wrapper.glue((dependencies, parser))
+        transform = gather.Wrapper.glue((dependencies, parser, regular))
         ret = self._command_collector.register(name, transform=transform)
         return ret
 
@@ -47,20 +48,27 @@ class Commands(object):
         parsed = command.parse(args)
         subcommand = ' '.join(parsed['__caparg_subcommand__'])
         func = collection[subcommand].original
-        dependencies, _ignored = collection[subcommand].extra
+        dependencies, _ignored, regular = collection[subcommand].extra
         graph = self.mkgraph(dependencies)
         graph.update(override_dependencies)
-        return func(parsed, graph)
+        if not regular:
+            return func(parsed, graph)
+        args = {dependency: graph[dependency]
+                for dependency in dependencies}
+        args.update(parsed)
+        del args['__caparg_subcommand__']
+        return func(**args)
 
     def dependency(self,
                    name=None,
                    dependencies=pyrsistent.v(),
-                   possible_dependencies=pyrsistent.v()):
+                   possible_dependencies=pyrsistent.v(),
+                   regular=False):
         """
         Register as a dependency.
 
         """
-        glue = (dependencies, possible_dependencies)
+        glue = (dependencies, possible_dependencies, regular)
         transform = gather.Wrapper.glue(glue)
         ret = self._collector.register(name, transform=transform)
         return ret
@@ -83,14 +91,20 @@ class Commands(object):
             on_route = on_route.add(thing)
             plugin = collection[thing]
             func = plugin.original
-            dependencies, possible_dependencies = plugin.extra
+            dependencies, possible_dependencies, regular = plugin.extra
             my_dependencies, my_possible_dependencies = {}, {}
             for other_thing in dependencies:
                 my_dependencies[other_thing] = _build(other_thing, on_route)
             for other_thing in possible_dependencies:
                 builder = functools.partial(_build, other_thing, on_route)
                 my_possible_dependencies[other_thing] = builder
-            ret[thing] = func(my_dependencies, my_possible_dependencies)
+            if regular:
+                args = {'build_' + key: value
+                        for key, value in my_possible_dependencies.items()}
+                args.update(my_dependencies)
+                ret[thing] = func(**args)
+            else:
+                ret[thing] = func(my_dependencies, my_possible_dependencies)
             return ret[thing]
         for thing in things:
             _build(thing)
diff --git a/tox.ini b/tox.ini
index 0ef3c06..297cc23 100644
--- a/tox.ini
+++ b/tox.ini
@@ -30,7 +30,7 @@ commands =
     # E0704 -- bare raise outside except (rare, when it's done I mean it)
     # R0201 -- unused self in methods (methods can be used for polymorphism)
     # R0903 -- too few public methods (attrs-based classes have implicit ones)
-    py27-lint: pylint --disable=unsupported-assignment-operation --disable=no-member --disable=not-callable --disable=unsubscriptable-object --disable=E0704 --disable=R0903 --disable=R0201 src/elcaminoreal
+    py27-lint: pylint --disable=blacklisted-name --disable=unsupported-assignment-operation --disable=no-member --disable=not-callable --disable=unsubscriptable-object --disable=E0704 --disable=R0903 --disable=R0201 src/elcaminoreal
     py27-lint: flake8 src/elcaminoreal
     #{py27,pypy,py36,py35}-func: python -m elcaminoreal.example selftest
     #{py27,pypy,py35}-func: python -m elcaminoreal.example selftest


Test patch:
diff --git a/src/elcaminoreal/test/some_plugins.py b/src/elcaminoreal/test/some_plugins.py
index a885594..9507b74 100644
--- a/src/elcaminoreal/test/some_plugins.py
+++ b/src/elcaminoreal/test/some_plugins.py
@@ -21,6 +21,18 @@ def a_foo(dependencies, _possible_dependencies):
     return dict(bar=dependencies['bar'])
 
 
+@COMMANDS.dependency(dependencies=["bar", "quux"],
+                     possible_dependencies=["foo"],
+                     regular=True)
+def regular(bar, quux, build_foo):
+    """
+    Depend on bar, maybe on foo
+
+    Use regular arguments.
+    """
+    return dict(bar=bar, quux=quux, foo=build_foo())
+
+
 @COMMANDS.dependency(possible_dependencies=["bar"])
 def foo_2(_dependencies, possible_dependencies):
     """
@@ -37,6 +49,14 @@ def a_bar(_dependencies, _possible_dependencies):
     return "I'm a bar"
 
 
+@COMMANDS.dependency(name="quux")
+def a_quux(_dependencies, _possible_dependencies):
+    """
+    Return a quux-like object.
+    """
+    return "I'm a quux"
+
+
 @COMMANDS.dependency()
 def rand(_dependencies, _possible_dependencies):
     """
@@ -83,6 +103,28 @@ def _print(_dependencies, _possible_dependencies):
     return print
 
 
+@COMMANDS.dependency(name='output')
+def dummy_output(_dependencies, _possible_dependencies):
+    """
+    Literally do nothing.
+
+    This is designed for being overridden.
+    """
+
+
+@COMMANDS.command(dependencies=['foo', 'output'],
+                  parser=ca.command('',
+                                    ca.positional('lili', type=str)),
+                  regular=True)
+def regular_command(foo, lili, output):
+    """
+    Use regular arguments
+
+    Output results
+    """
+    output(foo, lili)
+
+
 @COMMANDS.command(dependencies=['foo', 'print'],
                   parser=ca.command('',
                                     ca.positional('lala', type=str)))
diff --git a/src/elcaminoreal/test/test_gathering.py b/src/elcaminoreal/test/test_gathering.py
index 8049cd1..7ec2028 100644
--- a/src/elcaminoreal/test/test_gathering.py
+++ b/src/elcaminoreal/test/test_gathering.py
@@ -46,6 +46,15 @@ class DependencyResolverTester(unittest.TestCase):
         result = some_plugins.COMMANDS.mkgraph(['foo_2'])
         self.assertEquals(result['foo_2'], dict(bar="I'm a bar"))
 
+    def test_mkgraph_regular(self):
+        """
+        mkgraph regular functions work
+        """
+        result = some_plugins.COMMANDS.mkgraph(['regular'])
+        self.assertEquals(result['regular']['bar'], result['bar'])
+        self.assertEquals(result['regular']['quux'], result['quux'])
+        self.assertEquals(result['regular']['foo'], result['foo'])
+
 
 class RunnerResolverTester(unittest.TestCase):
 
@@ -100,3 +109,18 @@ class RunnerResolverTester(unittest.TestCase):
             some_plugins.COMMANDS.run(['no-such-command'])
         error_message = filep.getvalue().splitlines()
         self.assertEquals(error_message.pop(0), 'Usage:')
+
+    def test_regular(self):
+        """
+        Asking for regular arguments calls functions with argument names
+        """
+        output = []
+
+        def _my_output(*args):
+            output.append(args)
+        dependencies = dict(output=_my_output)
+        some_plugins.COMMANDS.run(['regular-command', 'thing'],
+                                  override_dependencies=dependencies)
+        self.assertEquals(len(output), 1)
+        self.assertEquals(output[0][0]['bar'], "I'm a bar")
+        self.assertEquals(output[0][1], 'thing')