# https://gist.github.com/fonic/fe6cade2e1b9eaf3401cc732f48aeebd
import sys
try:
from ucollections import namedtuple
except ImportError:
from collections import namedtuple
[docs]class WithCmdArgParser:
def __init__(self, parser):
self.parser = parser
def __call__(self, func):
if not self.parser:
self.parser = func(None, None, None, True)
def wrapped_function(*args, **kwargs):
try:
return func(*args, parsed=self.parser.parse_args(args[1].split()), **kwargs)
except SystemExit:
return
return wrapped_function
class _ArgError(BaseException):
pass
class _Arg:
def __init__(self, names, dest, action, nargs, const, default, type, help):
self.names = names
self.dest = dest
self.action = action
self.nargs = nargs
self.const = const
self.default = default
self.type = type
self.help = help
def parse(self, optname, eq_arg, args):
# parse args for this arg
if self.action == "store" or self.action == "append":
if self.nargs is None:
if eq_arg is not None:
ret = eq_arg
elif args:
ret = args.pop(0)
else:
raise _ArgError("expecting value for %s" % optname)
return self.type(ret)
elif self.nargs == "?":
if eq_arg is not None:
ret = eq_arg
elif args:
ret = args.pop(0)
else:
return self.default
return self.type(ret)
else:
if self.nargs == "*":
n = -1
elif self.nargs == "+":
if not args:
raise _ArgError("expecting value for %s" % optname)
n = -1
else:
n = int(self.nargs)
ret = []
stop_at_opt = True
while args and n != 0:
if stop_at_opt and args[0].startswith("-") and args[0] != "-":
if args[0] == "--":
stop_at_opt = False
args.pop(0)
else:
break
else:
ret.append(args.pop(0))
n -= 1
if n > 0:
raise _ArgError("expecting value for %s" % optname)
return ret
elif self.action == "store_const":
return self.const
else:
assert False
def _dest_from_optnames(opt_names):
dest = opt_names[0]
for name in opt_names:
if name.startswith("--"):
dest = name
break
return dest.lstrip("-").replace("-", "_")
[docs]class ArgumentParser:
def __init__(self, *, prog=None, description="", add_help=None):
self.prog = prog
self.description = description
self.opt = []
self.pos = []
if add_help:
self.add_argument("-h", "--help", help="show this help message and exit")
[docs] def add_argument(self, *args, **kwargs):
action = kwargs.get("action", "store")
if action == "store_true":
action = "store_const"
const = True
default = kwargs.get("default", False)
elif action == "store_false":
action = "store_const"
const = False
default = kwargs.get("default", True)
elif action == "append":
const = None
default = kwargs.get("default", [])
else:
const = kwargs.get("const", None)
default = kwargs.get("default", None)
if args and args[0].startswith("-"):
list = self.opt
dest = kwargs.get("dest")
if dest is None:
dest = _dest_from_optnames(args)
else:
list = self.pos
dest = kwargs.get("dest")
if dest is None:
dest = args[0]
if not args:
args = [dest]
list.append(
_Arg(args, dest, action, kwargs.get("nargs", None),
const, default, kwargs.get("type", str), kwargs.get("help", "")))
[docs] @staticmethod
def error(msg):
sys.stderr.write("error: %s\n" % msg)
sys.exit(2)
[docs] def parse_args(self, args=None, namespace=None):
return self._parse_args_impl(args, namespace, False)
[docs] def parse_known_args(self, args=None, namespace=None):
return self._parse_args_impl(args, namespace, True)
def _parse_args_impl(self, args, namespace, return_unknown):
if args is None:
args = sys.argv[1:]
else:
args = args[:]
if namespace is None:
namespace = Namespace()
try:
return self._parse_args(args, namespace, return_unknown)
except _ArgError as e:
self.print_usage()
self.error(str(e))
def _parse_args(self, args, argholder, return_unknown):
# add optional args with defaults
for opt in self.opt:
setattr(argholder, opt.dest, opt.default)
# deal with unknown arguments, if needed
unknown = []
def consume_unknown():
while args and not args[0].startswith("-"):
unknown.append(args.pop(0))
# parse all args
parsed_pos = False
while args or not parsed_pos:
if args and args[0].startswith("-") and args[0] != "-" and args[0] != "--":
# optional arg
a = args.pop(0)
if a in ("-h", "--help"):
self.print_help()
sys.exit(0)
eq_arg = None
if a.startswith("--") and "=" in a:
a, eq_arg = a.split("=", 1)
found = False
for i, opt in enumerate(self.opt):
if a in opt.names:
val = opt.parse(a, eq_arg, args)
if opt.action == "append":
getattr(argholder, opt.dest).append(val)
else:
setattr(argholder, opt.dest, val)
found = True
break
if not found:
if return_unknown:
unknown.append(a)
consume_unknown()
else:
raise _ArgError("unknown option %s" % a)
else:
# positional arg
if parsed_pos:
if return_unknown:
unknown = unknown + args
break
else:
raise _ArgError("extra args: %s" % " ".join(args))
for pos in self.pos:
setattr(argholder, pos.dest, pos.parse(pos.names[0], None, args))
parsed_pos = True
if return_unknown:
consume_unknown()
return (argholder, unknown) if return_unknown else argholder
[docs] def print_usage(self, file=None):
self.format_usage()
[docs] def print_help(self, file=None):
self.format_help()
# class ArgumentParser:
# def __init__(self, **kwargs):
# self.program = {key: kwargs[key] for key in kwargs}
# self.positionals = []
# self.options = []
# if "add_help" in self.program and self.program['add_help'] is True:
# self.add_argument("-h", "--help", help="show this help message and exit")
#
# def add_argument(self, *args, **kwargs):
# argument = {key: kwargs[key] for key in kwargs}
#
# # Positional: argument with only one name not starting with '-' provided as
# # positional argument to method -or- no name and only a 'dest=' argument
# # if no default was supplied, use the parser-level default
#
# argument["action"] = kwargs.get("action", "store")
# if argument["action"] == "store_true":
# argument["action"] = "store_const"
# argument["const"] = True
# argument["default"] = kwargs.get("default", False)
# elif argument["action"] == "store_false":
# argument["action"] = "store_const"
# argument["const"] = False
# argument["default"] = kwargs.get("default", True)
# else:
# argument["const"] = kwargs.get("const", None)
# argument["default"] = kwargs.get("default", None)
#
# if args and args[0].startswith("-"):
# # argument["list"] = self.options
# argument["dest"] = kwargs.get("dest")
# if argument["dest"] is None:
# argument["dest"] = args[0]
# for name in args:
# if name.startswith("--"):
# argument["dest"] = name
# break
# argument["dest"] = argument["dest"].lstrip("-").replace("-", "_")
# argument["name"] = argument["dest"]
# else:
# # argument["list"] = self.positionals
# argument["dest"] = kwargs.get("dest")
# if argument["dest"] is None:
# argument["dest"] = args[0]
# if not args:
# # argument["dest"] = [argument["dest"]]
# argument["name"] = argument["dest"]
#
# if len(args) == 0 or (
# len(args) == 1 and isinstance(args[0], str) and not args[0].startswith("-")
# ):
# if (len(args) > 0):
# argument["name"] = args[0]
# else:
# argument["name"] = argument["dest"]
# self.positionals.append(argument)
# return
#
# # Option: argument with one or more flags starting with '-' provided as
# # positional arguments to method
# argument["flags"] = [item for item in args]
# argument["nargs"] = kwargs.get("nargs", None)
# self.options.append(argument)
#
# @staticmethod
# def _parse(data, args):
# if 'action' in data and data['action'] == "store":
# if 'nargs' in data and data['nargs'] is None:
# if args:
# arg_val = args.pop(0)
# else:
# raise _ArgError("expecting value for %s" % data['name'])
# elif 'nargs' in data and data['nargs'] == "?":
# if args:
# arg_val = args.pop(0)
# else:
# arg_val = data['default']
# else:
# if 'nargs' in data and data['nargs'] == "*":
# n = -1
# elif 'nargs' in data and data['nargs'] == "+":
# if not args:
# raise _ArgError("expecting value for %s" % data['name'])
# n = -1
# else:
# if 'nargs' in data:
# n = int(data['nargs'])
# else:
# n = -1
# ret = []
# stop_at_opt = True
# while args and n != 0:
# if stop_at_opt and args[0].startswith("-") and args[0] != "-":
# if args[0] == "--":
# stop_at_opt = False
# args.pop(0)
# else:
# break
# else:
# ret.append(args.pop(0))
# n -= 1
# if n > 0:
# raise _ArgError("expecting value for %s" % data['name'])
# arg_val = ret
# elif 'action' in data and data['action'] == "store_const":
# arg_val = data['const']
# else:
# assert False
#
# return arg_val
#
# def parse_args(self, args=None):
#
# if args is None:
# args = sys.argv[1:]
# else:
# args = args[:]
# try:
# return self._parse_args(args, False)
# except _ArgError as e:
# self.error(e)
#
# def _parse_args(self, args, return_unknown):
# # add optional args with defaults
# arg_dest = []
# arg_vals = []
# for opt in self.options:
# arg_dest.append(opt['dest'])
# arg_vals.append(opt['default'])
#
# # deal with unknown arguments, if needed
# unknown = []
#
# def consume_unknown():
# while args and not args[0].startswith("-"):
# unknown.append(args.pop(0))
#
# # parse all args
# parsed_pos = False
# while args or not parsed_pos:
# if args and args[0].startswith("-") and args[0] != "-" and args[0] != "--":
# # optional arg
# a = args.pop(0)
# if a in ("-h", "--help"):
# self.print_help()
# sys.exit(0)
# found = False
# count = 0
# for opt in self.options:
#
# if a in opt['flags']:
# arg_vals[count] = self._parse(opt, args)
# found = True
# break
# count += 1
# if not found:
# if return_unknown:
# unknown.append(a)
# consume_unknown()
# else:
# raise _ArgError("unknown option %s" % a)
# else:
# # positional arg
# if parsed_pos:
# if return_unknown:
# unknown = unknown + args
# break
# else:
# raise _ArgError("extra args: %s" % " ".join(args))
# for pos in self.positionals:
# arg_dest.append(pos['dest'])
# arg_vals.append(self._parse(pos, args))
#
# parsed_pos = True
# if return_unknown:
# consume_unknown()
#
# # build and return named tuple with arg values
# values = namedtuple("args", arg_dest)(*arg_vals)
# if return_unknown:
# return values, unknown
# else:
# return values
#
# def format_usage(self):
#
# # Use user-defined usage message
# if "usage" in self.program:
# if self.program['usage'] == "" or str.isspace(self.program['usage']):
# return "Usage: No usage information available"
# return "Usage: %s" % self.program['usage']
#
# # Use user-defined programme name
# if 'prog' in self.program and not self.program['prog'] == "" and not str.isspace(self.program['prog']):
# prog_text = self.program['prog']
# else:
# prog_text = sys.argv[0]
#
# # Prepare output
# output = [
# "Usage:",
# prog_text
# ]
#
# # argument
# argument_list = []
# for option in self.options:
# flags = str.join("|", option["flags"])
# argument_list.append(
# "[%s]" % flags
# if (
# "action" in option
# and (
# option["action"] == "store_true"
# or option["action"] == "store_false"
# )
# )
# else "[%s %s]" % (flags, option["metavar"])
# if ("metavar" in option)
# else "[%s]" % flags
# if ("dest" in option)
# else "[%s]" % flags
# )
# for positional in self.positionals:
# argument_list.append(
# "%s" % positional["metavar"]
# if ("metavar" in positional)
# else "%s" % positional["name"]
# )
# output.append(str.join(" ", argument_list))
#
# return str.join(" ", output)
#
# def format_help(self):
# # Add usage message to output
# output = [self.format_usage()]
#
# # Add description to output if present
# if (
# "description" in self.program
# and self.program["description"] != ""
# and not str.isspace(self.program["description"])
# ):
# output.append("")
# output.append(self.program["description"])
#
# # Ad left key
# for positional in self.positionals:
# positional["left"] = (
# positional["metavar"]
# if ("metavar" in positional)
# else positional["name"]
# )
#
# for option in self.options:
# if "action" in option and (
# option["action"] == "store_true" or option["action"] == "store_false"
# ):
# option["left"] = str.join(", ", option["flags"])
# else:
# option["left"] = str.join(
# ", ",
# [
# "%s %s" % (item, option["metavar"])
# if ("metavar" in option)
# else "%s" % item
# if ("dest" in option)
# else item
# for item in option["flags"]
# ],
# )
#
# # Determine determine max string lengths for left part
# left_max_len = 0
# for argument in self.positionals + self.options:
# left_max_len = max(left_max_len, len(argument["left"]))
#
# # Output format
# outtmp = " %-" + str(left_max_len) + "s %s"
#
# # Add positional arguments to output
# if len(self.positionals) > 0:
# output.append("")
# output.append("Positionals:")
# for positional in self.positionals:
# output.append(outtmp % (positional["left"], positional["help"]))
#
# # Add option arguments to output
# if len(self.options) > 0:
# output.append("")
# output.append("Options:")
# for option in self.options:
# output.append(outtmp % (option["left"], option["help"]))
#
# # Add epilog to output if present
# if (
# "epilog" in self.program
# and self.program["epilog"] != ""
# and not str.isspace(self.program["epilog"])
# ):
# output.append("")
# output.append(self.program["epilog"])
#
# # Return output as single string
# return str.join("\n", output)
#
# def print_usage(self, file=None):
# if file is None:
# file = sys.stdout
# file.write("%s\n" % self.format_usage())
#
# def print_help(self, file=None):
# if file is None:
# file = sys.stdout
# file.write("%s\n" % self.format_help())
#
# def error(self, message):
# sys.stderr.write("%s\n" % self.format_usage())
# sys.stderr.write("Error: %s\n" % message)
# sys.exit(2)