Skip to content

Instantly share code, notes, and snippets.

@wware
Last active May 2, 2019 17:36
Show Gist options
  • Save wware/2616e291389e1d7aedd60db3392d14c0 to your computer and use it in GitHub Desktop.
Save wware/2616e291389e1d7aedd60db3392d14c0 to your computer and use it in GitHub Desktop.
A little command infrastructure thing to simplify using argparse.
import argparse
import logging
import os
import pdb
import re
import subprocess
_cmds = {}
_args = []
dir_path = os.path.dirname(os.path.realpath(__file__))
logging.basicConfig(
format='%(asctime)-15s %(levelname)s ' +
'%(filename)s:%(lineno)d %(message)s',
level=logging.INFO
)
def command(func):
_cmds[func.__name__] = func
return func
def add_argument(*args, **kwargs):
_args.append((args, kwargs))
class MyHelpFormatter(argparse.HelpFormatter):
"""Blend RawDescriptionHelpFormatter with ArgumentDefaultsHelpFormatter
"""
def _fill_text(self, text, width, indent):
return ''.join([indent + line for line in text.splitlines(True)])
def _get_help_string(self, action):
help = action.help
if '%(default)' not in action.help:
if action.default is not argparse.SUPPRESS:
defaulting_nargs = [argparse.OPTIONAL, argparse.ZERO_OR_MORE]
if action.option_strings or action.nargs in defaulting_nargs:
help += ' (default: %(default)s)'
return help
def parse_options(script, docstring='', argv=None,
prep_opts=None, got_commands=True):
class MockOptions(object):
def __getattr__(self, key):
return None
if prep_opts is not None:
opts = MockOptions()
prep_opts(opts)
return opts
def mk_cmd_info(cmd):
r = " " + cmd.__name__ + " : "
if cmd.__doc__ is not None:
doc = re.sub(r"@.*", "", cmd.__doc__)
r += doc.strip()
else:
r += "no documentation available"
return r
cmds = _cmds.values()
cmds.sort(key=lambda _f: _f.__name__)
cmd_info = "\nCommands:\n" + "\n".join([mk_cmd_info(f) for f in cmds])
class HelpAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
file = None
try:
p = subprocess.Popen("which more", shell=True,
stdout=subprocess.PIPE)
_more, _ = p.communicate()
assert len(_more) > 0
class MoreFile(object):
def write(self, x):
p = subprocess.Popen(_more, shell=True,
stdin=subprocess.PIPE)
p.communicate(x)
file = MoreFile()
except (OSError, ValueError):
pass
parser.print_help(file=file)
parser.exit()
prog = os.path.basename(script)
parser = argparse.ArgumentParser(
prog=prog,
formatter_class=MyHelpFormatter,
description=docstring.format(prog), epilog=cmd_info,
add_help=False
)
parser.add_argument(
'-h', '--help', nargs=0, action=HelpAction,
help="Display this help"
)
for args, kwargs in _args:
parser.add_argument(*args, **kwargs)
if got_commands:
parser.add_argument(
'commands',
help='commands to run', nargs='*', default=[],
choices=_cmds.keys()
)
opts = parser.parse_args(argv)
logging.getLogger().setLevel(logging.DEBUG if opts.debug else logging.INFO)
if opts.pdb:
pdb.set_trace()
return opts
def step_thru_commands(options):
for cmd in options.commands:
_cmds[cmd](options)
add_argument('-D', '--debug', action='store_true',
help='turn on debug-level logging')
add_argument('-p', '--pdb', action='store_true',
help='run this script in the PDB debugger')
#!/usr/bin/env python
"""
The file-level docstring becomes part of the "--help" output.
"""
import logging
from commands import command, step_thru_commands, parse_options, add_argument
add_argument('-v', '--verbose',
action='store_true',
help='crank up the verbosity')
@command
def foo(options):
"""
print the string "foo"
"""
logging.debug(
"gonna print " +
("every single charcter of " if options.verbose else "") +
"'foo'"
)
print("foo")
@command
def bar(options):
"""
print the string "bar"
"""
logging.debug(
"gonna print " +
("every single charcter of " if options.verbose else "") +
"'bar'"
)
print("bar")
def main():
options = parse_options(__file__, __doc__)
step_thru_commands(options)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment