Commits

Andy Mikhailenko committed 62c012c

Added @command decorator. Warning: no tests yet (though it just adds a thin layer upon those already tested).

Comments (0)

Files changed (4)

-version = '0.8.1'
+version = '0.9.0'
 ==================
 """
 from functools import wraps
+import inspect
 
 
-__all__ = ['alias', 'plain_signature', 'arg']
+__all__ = ['alias', 'arg', 'command', 'plain_signature']
 
 
 def alias(name):
         func.argh_args.insert(0, (args, kwargs))
         return func
     return wrapper
+
+def command(func):
+    """Infers argument specifications from given function. Wraps the function
+    in the :func:`plain_signature` decorator and also in an :func:`arg`
+    decorator for every actual argument the function expects.
+
+    Usage::
+
+        @command
+        def foo(bar, quux=123):
+            yield bar, quux
+
+    This is equivalent to::
+
+        @arg('-b', '--bar')
+        @arg('-q', '--quux', default=123)
+        def foo(args):
+            yield args.bar, args.quux
+
+    """
+    # @plain_signature
+    func = plain_signature(func)
+
+    # @arg (inferred)
+    spec = inspect.getargspec(func)
+    kwargs = dict(zip(*[reversed(x) for x in (spec.args, spec.defaults or [])]))
+    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)
+        else:
+            func = arg(a)(func)
+
+    return func
 * infer command name from function name;
 * infer agrument type from the default value;
 * infer argument action from the default value (for booleans);
+* infer arguments from function signature;
 * add an alias root command ``help`` for the ``--help`` argument;
 * enable passing unwrapped arguments to certain functions instead of a
   :class:`argparse.Namespace` object.
 Tutorial
 ========
 
-Defining commands
------------------
+`Argh` is a small library that provides several layers of abstraction on top of
+`argparse`. You are free to use any layer that fits given task best. The layers
+can be mixed. It is always possible to declare a command with the highest
+possible (and least flexible) layer — the :func:`~argh.decorators.command`
+decorator — and then tune the behaviour with any of the lower layers:
+:func:`~argh.decorators.arg`, :func:`~argh.helpers.add_commands`,
+:func:`~argh.helpers.dispatch` or directly via the `argparse` API.
+
+Dive in
+-------
+
+Defining commands is dead simple::
+
+    from argh import *
+    
+    @command
+    def load(path, format='json'):
+        print loaders[format].load(path)
+
+    argh.dispatch()
+
+And then call your script like this::
+
+    $ ./script.py load fixture.json
+    $ ./script.py load fixture.yaml --format=yaml
+
+I guess you get the picture. Still, there's much more to commands than this.
+You'll want to provide help per commands and per argument, you will want to
+specify aliases, data types, namespaces and... just read on.
+
+Declaring commands
+------------------
 
 Let's start with an almost real-life example where we define some commands.
 First, import :class:`~argh.helpers.ArghParser` (an extended version of the
 or dispatcher. The script must know how to interpret the arguments passed in by
 the user.
 
+Assembling commands
+-------------------
+
+.. note::
+
+    `Argh` decorators introduce a declarative mode for defining commands. You
+    can access the `argparse` API after a parser instance is created.
+
 Our next step is to assemble all the commands — web-related and miscellaneous —
 within a single argument parser. First, create the parser itself::
 
 
 .. note::
 
-    You don't have to use :class:`argh.ArghParser`; the standard
+    You don't have to use :class:`~argh.helpers.ArghParser`; the standard
     :class:`argparse.ArgumentParser` will do. You will just need to call
-    stand-alone functions :func:`argh.add_commands` and :func:`argh.dispatch`
-    instead of :class:`argh.ArghParser` methods.
+    stand-alone functions :func:`~argh.helpers.add_commands` and
+    :func:`~argh.helpers.dispatch` instead of :class:`~argh.helpers.ArghParser`
+    methods.
 
 Generated help
 --------------
 .. note::
 
     If you return a string, it is printed as is. A list or tuple is iterated
-    and printed line by line. This is how :func:`dispatcher <argh.dispatch>`
-    works.
+    and printed line by line. This is how :func:`dispatcher
+    <argh.helpers.dispatch>` works.
 
 This is fine, but what about non-linear code with if/else, exceptions and
 interactive promts? Well, you don't need to manage the stack of results within
 
 This works but the print-and-exit tasks are repetitive; moreover, there are
 cases when you don't want to raise `SystemExit` and just want to collect the
-output in a uniform way. Use :class:`~argh.CommandError`::
+output in a uniform way. Use :class:`~argh.exceptions.CommandError`::
 
     @arg('key')
     def show_item(args):
             yield item
 
 `Argh` will wrap this exception and choose the right way to display its
-message (depending on how :func:`argh.dispatch` was called).
+message (depending on how :func:`~argh.helpers.dispatch` was called).