Skip to content

Instantly share code, notes, and snippets.

@dougn
Created October 18, 2017 18:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dougn/14b043b73b8da0fdab97b554affbd138 to your computer and use it in GitHub Desktop.
Save dougn/14b043b73b8da0fdab97b554affbd138 to your computer and use it in GitHub Desktop.
Wrapper to argparse which provides decorator interface for command based commandlines
import argparse
import functools
def _call(func, args):
# RED_FLAG: add nargs
kwds = dict((name, getattr(args, name, None)) for name in func._arguments)
return func(**kwds)
def _command(subparsers, defaults, passthrough, description, **kwds):
def subcommand(parsers, defaults, passthrough, description, kwds, func):
name = func.__name__
if '__use_mod_in_name' in defaults:
defaults = dict(defaults)
usemodname = defaults.pop('__use_mod_in_name', False)
name = func.__module__.split('.',1)[-1] + '.' + func.__name__
args = dict(name=name, epilog=func.__doc__)
args.update(defaults)
args.update(kwds)
if description:
args['description'] = description
if 'help' not in args:
args['help'] = description
subparser = parsers.add_parser(**args)
call_func = functools.partial(_call, func)
call_func._func = func
call_func._commandname = args['name']
subparser.set_defaults(func=call_func)
argnames = list(passthrough)
for opts, kwds in reversed(getattr(func, '_arguments', [])):
arg = subparser.add_argument(*opts, **kwds)
name = arg.dest
assert name is not None
assert name not in argnames
# RED_FLAG: add inspect call to validate arg names.
argnames.append(name)
for group_name, group_attrs in getattr(func, '_groups', {}).iteritems():
group = None
if group_attrs['mutually_exclusive']:
group = subparser.add_mutually_exclusive_group(required=group_attrs['required'])
else:
group = subparser.add_argument_group(group_name, group_attrs['description'])
for opts, kwds in reversed(group_attrs['arguments']):
arg = group.add_argument(*opts, **kwds)
name = arg.dest
assert name is not None
assert name not in argnames
# RED_FLAG: add inspect call to validate arg names.
argnames.append(name)
# RED_FLAG: add processing for nargs.
func._arguments = argnames
func._parser = subparser
return func
return functools.partial(subcommand, subparsers,
defaults, passthrough, description, kwds)
def _argument(*args, **kwds):
def subargument(aargs, akwds, func):
if not hasattr(func, '_arguments'):
func._arguments = []
func._arguments.append((aargs, akwds))
return func
return functools.partial(subargument, args, kwds)
def _group(name, mutually_exclusive=False, required=False, description=''):
def group(name, mutually_exclusive, required, description, func):
if not hasattr(func, '_groups'):
func._groups = {}
gd = func._groups.setdefault(name, {})
gd['required'] = required
gd['mutually_exclusive'] = mutually_exclusive
gd['description'] = description
gd.setdefault('arguments', [])
return func
return functools.partial(group, name, mutually_exclusive, required, description)
def _group_argument(group_name, *args, **kwds):
def group_subargument(group_name, aargs, akwds, func):
if not hasattr(func, '_groups'):
func._groups = {}
gd = func._groups.setdefault(group_name,
dict(required=False,
mutually_exclusive=False,
description='',
arguments=list()))
gd['arguments'].append((aargs, akwds))
return func
return functools.partial(group_subargument, group_name, args, kwds)
def _passthrough(name):
def passthrough(name, func):
func._arguments.append(name)
return func
return functools.partial(passthrough, name)
def _postarg(parser, func):
if not hasattr(parser, '_postargs'):
parser._postargs = []
parser._postargs.append((func.__name__,func))
return func
def _main(parser):
args = parser.parse_args()
args.args=args
for name, func in reversed(getattr(parser, '_postargs', [])):
if name not in dir(args):
setattr(args, name, func(parser, args))
return args.func(args)
def Program(prog='np4admin',
formatter_class=argparse.RawDescriptionHelpFormatter,
command_default_args={},
command_implicit_args=[],
**kwdargs):
parser = argparse.ArgumentParser(prog=prog,
formatter_class=formatter_class,
**kwdargs)
subparsers = parser.add_subparsers(title='valid commands',
description='Use "<command> -h" for command-specific help.')
command_default_args = dict(command_default_args)
mod_command_default_args = dict(command_default_args)
mod_command_default_args['__use_mod_in_name'] = True
command_implicit_args = list(command_implicit_args)
parser.command = functools.partial(_command, subparsers,
command_default_args,
command_implicit_args)
parser.mod_command = functools.partial(_command, subparsers,
mod_command_default_args,
command_implicit_args)
parser.postarg = functools.partial(_postarg, parser)
parser.argument = _argument
parser.group = _group
parser.group_argument = _group_argument
parser.passthrough = _passthrough
parser.main = functools.partial(_main, parser)
return parser
def command_name(args):
if not args.func:
return ''
return args.func._commandname
@dougn
Copy link
Author

dougn commented Oct 18, 2017


import argutil
import P4

ui = argutil.Program(
    formatter_class=argutil.argparse.RawDescriptionHelpFormatter,
    command_default_args=dict(
        formatter_class=argutil.argparse.RawDescriptionHelpFormatter),
    command_implicit_args=['p4',],
    description="""
    Description TBD
""",epilog="""Details:
    Detailed Help TBD
""")

ui.add_argument('-v', '--version', action='version',
                version=__version__)
ui.add_argument('-u', '--p4user', metavar='P4USER', dest='P4USER',
                default=None,
                help="The perforce username to use.")
ui.add_argument('-p', '--p4port', metavar='P4PORT', dest='P4PORT',
                default=None,
                help="The perforce server to connect to.")

@ui.postarg
def p4(parser, args):    
    p4 = None
    with P4.p4except_as_error('Failed to connect to perforce server:'):
        prog = parser.prog
        command = argutil.command_name(args)
        if command:
            prog += '.'+ command
        prog += '-' + _version.__version__
        p4 = P4.P4(user=args.P4USER, port=args.P4PORT)
        p4.prog = prog
        p4.connect()

    return p4

@ui.command("Create a new client with .p4config file.")
@ui.group('stream', mutually_exclusive=True)
@ui.group_argument('stream', '-s', '--stream', action='store_true',
                   help="Client is for a stream. "
                        "Can not be used with the -i/--ignore-path argument.")
@ui.group_argument('stream', '-i', '--ignore-path', action='store_true', dest='newpath',
                   help="Allow for depot path to point to a directory that does not yet exist. "
                        "Can not be used with the -s/--stream option.")
@ui.argument('-n', '--no-sync', action='store_false', dest='sync',
             help="Sync the client files on creation.")
@ui.argument('--no-host', action='store_true', dest='nohost',
             help="Do not set the host field (allow sync from multiple hosts.)")
@ui.argument('path', 
             help="Root depot path for the client, or stream.")
def client(p4, path, stream=False, sync=True, nohost=False, newpath=False):
    """Create a client workspace for the path or stream name supplied. The client name 
    """
    pass

if __name__ == '__main__':
    ui.main()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment