Welcome, guest | Sign In | My Account | Store | Cart

This is an implementation of the interface provided by the cmdln module but using argparse to provide the option/arg heavy parsing.

An example of usage is provided in the test function, which should produce the following from the command line:

$ python argdeclare.py --help

usage: argdeclare.py [-h] [-v] {uninstall,install,delete} ...

a description of the test app

optional arguments:
  -h, --help            show this help message and exit
  -v, --version         show program's version number and exit

subcommands:
  valid subcommands

  {uninstall,install,delete}
                        additional help
    delete              help text for delete subcmd
    install             help text for install subcmd
    uninstall           help text for uninstall subcmd

$ python argdeclare.py install --help

usage: argdeclare.py install [-h] [-t TYPE] [--log] [-f] package

positional arguments:
  package               package to be (un)installed

optional arguments:
  -h, --help            show this help message and exit
  -t TYPE, --type TYPE  specify type of package
  --log, -l             log is on
  -f, --force           force through installation

enjoy!

SA

Python, 122 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import sys, argparse

# option decorator
def option(*args, **kwds):
    def _decorator(func):
        _option = (args, kwds)
        if hasattr(func, 'options'):
            func.options.append(_option)
        else:
            func.options = [_option]
        return func
    return _decorator

# arg decorator
arg = option

# combines option decorators
def option_group(*options):
    def _decorator(func):
        for option in options:
            func = option(func)
        return func
    return _decorator


class MetaCommander(type):
    def __new__(cls, classname, bases, classdict):
        subcmds = {}
        for name, func in classdict.items():
            if name.startswith('do_'):
                name = name[3:]
                subcmd = {
                    'name': name,
                    'func': func,
                    'options': []
                }
                if hasattr(func, 'options'):
                    subcmd['options'] = func.options
                subcmds[name] = subcmd
        classdict['_argparse_subcmds'] = subcmds
        return type.__new__(cls, classname, bases, classdict)



class Commander(object):
    __metaclass__ = MetaCommander
    name = 'app'
    description = 'a description'
    version = '0.0'
    epilog = ''
    default_args = []
    
    def cmdline(self):
        parser = argparse.ArgumentParser(
            # prog = self.name,
            formatter_class = argparse.RawDescriptionHelpFormatter,
            description=self.__doc__,
            epilog = self.epilog,
        )

        parser.add_argument('-v', '--version', action='version',
                            version = '%(prog)s '+ self.version)

        subparsers = parser.add_subparsers(
            title='subcommands',
            description='valid subcommands',
            help='additional help',
        )
        
        for name in sorted(self._argparse_subcmds.keys()):
            subcmd = self._argparse_subcmds[name]            
            subparser = subparsers.add_parser(subcmd['name'],
                                     help=subcmd['func'].__doc__)
            for args, kwds in subcmd['options']:
                subparser.add_argument(*args, **kwds)
            subparser.set_defaults(func=subcmd['func'])

        if len(sys.argv) <= 1:
            options = parser.parse_args(self.default_args)
        else:
            options = parser.parse_args()
        options.func(self, options)
    


def test():
    # only for options which are repeated across different funcs
    common_options = option_group(
        option('-t', '--type', action='store', help='specify type of package'),
        arg('package', help='package to be (un)installed'),
        option('--log', '-l', action='store_true', help='log is on')
    )
    
    class Application(Commander):
        'a description of the test app'
        name = 'app1'
        version = '0.1'
        default_args = ['install', '--help']
        
        @option('--log', '-l', action='store_true', help='log is on')
        @arg('pattern', help="pattern to delete")
        def do_delete(self, options):
            "help text for delete subcmd"
            print options

        @option('-f', '--force', action='store_true',
                        help='force through installation')
        @common_options
        def do_install(self, options):
            "help text for install subcmd"
            print options

        @common_options
        def do_uninstall(self, options):
            "help text for uninstall subcmd"
            print options

    app = Application()
    app.cmdline()

if __name__ == '__main__':
    test()
  • added option_groups for combining common options.

  • updated to argparse 1.1

2 comments

Stan Chan 11 years, 11 months ago  # | flag

Using this implementation, how would you provide multiple levels of sub-parsers. I'm trying to replicate a multi-branch, tree-like, command structure (like svn) using argparse and argdeclare.

Shakeeb Alireza (author) 11 years, 9 months ago  # | flag

@Stan: argdeclare is really intended for rapidly developing simple command line interfaces using the infrastructure provided by argparse. If you have a more complex requirement (e.g. needing multiple levels of sub-parsers), you'd probably be better served reverting to original api.