1*4882a593Smuzhiyun# 2*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 3*4882a593Smuzhiyun# 4*4882a593Smuzhiyun 5*4882a593Smuzhiyunimport sys 6*4882a593Smuzhiyunimport argparse 7*4882a593Smuzhiyunfrom collections import defaultdict, OrderedDict 8*4882a593Smuzhiyun 9*4882a593Smuzhiyunclass ArgumentUsageError(Exception): 10*4882a593Smuzhiyun """Exception class you can raise (and catch) in order to show the help""" 11*4882a593Smuzhiyun def __init__(self, message, subcommand=None): 12*4882a593Smuzhiyun self.message = message 13*4882a593Smuzhiyun self.subcommand = subcommand 14*4882a593Smuzhiyun 15*4882a593Smuzhiyunclass ArgumentParser(argparse.ArgumentParser): 16*4882a593Smuzhiyun """Our own version of argparse's ArgumentParser""" 17*4882a593Smuzhiyun def __init__(self, *args, **kwargs): 18*4882a593Smuzhiyun kwargs.setdefault('formatter_class', OeHelpFormatter) 19*4882a593Smuzhiyun self._subparser_groups = OrderedDict() 20*4882a593Smuzhiyun super(ArgumentParser, self).__init__(*args, **kwargs) 21*4882a593Smuzhiyun self._positionals.title = 'arguments' 22*4882a593Smuzhiyun self._optionals.title = 'options' 23*4882a593Smuzhiyun 24*4882a593Smuzhiyun def error(self, message): 25*4882a593Smuzhiyun """error(message: string) 26*4882a593Smuzhiyun 27*4882a593Smuzhiyun Prints a help message incorporating the message to stderr and 28*4882a593Smuzhiyun exits. 29*4882a593Smuzhiyun """ 30*4882a593Smuzhiyun self._print_message('%s: error: %s\n' % (self.prog, message), sys.stderr) 31*4882a593Smuzhiyun self.print_help(sys.stderr) 32*4882a593Smuzhiyun sys.exit(2) 33*4882a593Smuzhiyun 34*4882a593Smuzhiyun def error_subcommand(self, message, subcommand): 35*4882a593Smuzhiyun if subcommand: 36*4882a593Smuzhiyun action = self._get_subparser_action() 37*4882a593Smuzhiyun try: 38*4882a593Smuzhiyun subparser = action._name_parser_map[subcommand] 39*4882a593Smuzhiyun except KeyError: 40*4882a593Smuzhiyun self.error('no subparser for name "%s"' % subcommand) 41*4882a593Smuzhiyun else: 42*4882a593Smuzhiyun subparser.error(message) 43*4882a593Smuzhiyun 44*4882a593Smuzhiyun self.error(message) 45*4882a593Smuzhiyun 46*4882a593Smuzhiyun def add_subparsers(self, *args, **kwargs): 47*4882a593Smuzhiyun if 'dest' not in kwargs: 48*4882a593Smuzhiyun kwargs['dest'] = '_subparser_name' 49*4882a593Smuzhiyun 50*4882a593Smuzhiyun ret = super(ArgumentParser, self).add_subparsers(*args, **kwargs) 51*4882a593Smuzhiyun # Need a way of accessing the parent parser 52*4882a593Smuzhiyun ret._parent_parser = self 53*4882a593Smuzhiyun # Ensure our class gets instantiated 54*4882a593Smuzhiyun ret._parser_class = ArgumentSubParser 55*4882a593Smuzhiyun # Hacky way of adding a method to the subparsers object 56*4882a593Smuzhiyun ret.add_subparser_group = self.add_subparser_group 57*4882a593Smuzhiyun return ret 58*4882a593Smuzhiyun 59*4882a593Smuzhiyun def add_subparser_group(self, groupname, groupdesc, order=0): 60*4882a593Smuzhiyun self._subparser_groups[groupname] = (groupdesc, order) 61*4882a593Smuzhiyun 62*4882a593Smuzhiyun def parse_args(self, args=None, namespace=None): 63*4882a593Smuzhiyun """Parse arguments, using the correct subparser to show the error.""" 64*4882a593Smuzhiyun args, argv = self.parse_known_args(args, namespace) 65*4882a593Smuzhiyun if argv: 66*4882a593Smuzhiyun message = 'unrecognized arguments: %s' % ' '.join(argv) 67*4882a593Smuzhiyun if self._subparsers: 68*4882a593Smuzhiyun subparser = self._get_subparser(args) 69*4882a593Smuzhiyun subparser.error(message) 70*4882a593Smuzhiyun else: 71*4882a593Smuzhiyun self.error(message) 72*4882a593Smuzhiyun sys.exit(2) 73*4882a593Smuzhiyun return args 74*4882a593Smuzhiyun 75*4882a593Smuzhiyun def _get_subparser(self, args): 76*4882a593Smuzhiyun action = self._get_subparser_action() 77*4882a593Smuzhiyun if action.dest == argparse.SUPPRESS: 78*4882a593Smuzhiyun self.error('cannot get subparser, the subparser action dest is suppressed') 79*4882a593Smuzhiyun 80*4882a593Smuzhiyun name = getattr(args, action.dest) 81*4882a593Smuzhiyun try: 82*4882a593Smuzhiyun return action._name_parser_map[name] 83*4882a593Smuzhiyun except KeyError: 84*4882a593Smuzhiyun self.error('no subparser for name "%s"' % name) 85*4882a593Smuzhiyun 86*4882a593Smuzhiyun def _get_subparser_action(self): 87*4882a593Smuzhiyun if not self._subparsers: 88*4882a593Smuzhiyun self.error('cannot return the subparser action, no subparsers added') 89*4882a593Smuzhiyun 90*4882a593Smuzhiyun for action in self._subparsers._group_actions: 91*4882a593Smuzhiyun if isinstance(action, argparse._SubParsersAction): 92*4882a593Smuzhiyun return action 93*4882a593Smuzhiyun 94*4882a593Smuzhiyun 95*4882a593Smuzhiyunclass ArgumentSubParser(ArgumentParser): 96*4882a593Smuzhiyun def __init__(self, *args, **kwargs): 97*4882a593Smuzhiyun if 'group' in kwargs: 98*4882a593Smuzhiyun self._group = kwargs.pop('group') 99*4882a593Smuzhiyun if 'order' in kwargs: 100*4882a593Smuzhiyun self._order = kwargs.pop('order') 101*4882a593Smuzhiyun super(ArgumentSubParser, self).__init__(*args, **kwargs) 102*4882a593Smuzhiyun 103*4882a593Smuzhiyun def parse_known_args(self, args=None, namespace=None): 104*4882a593Smuzhiyun # This works around argparse not handling optional positional arguments being 105*4882a593Smuzhiyun # intermixed with other options. A pretty horrible hack, but we're not left 106*4882a593Smuzhiyun # with much choice given that the bug in argparse exists and it's difficult 107*4882a593Smuzhiyun # to subclass. 108*4882a593Smuzhiyun # Borrowed from http://stackoverflow.com/questions/20165843/argparse-how-to-handle-variable-number-of-arguments-nargs 109*4882a593Smuzhiyun # with an extra workaround (in format_help() below) for the positional 110*4882a593Smuzhiyun # arguments disappearing from the --help output, as well as structural tweaks. 111*4882a593Smuzhiyun # Originally simplified from http://bugs.python.org/file30204/test_intermixed.py 112*4882a593Smuzhiyun positionals = self._get_positional_actions() 113*4882a593Smuzhiyun for action in positionals: 114*4882a593Smuzhiyun # deactivate positionals 115*4882a593Smuzhiyun action.save_nargs = action.nargs 116*4882a593Smuzhiyun action.nargs = 0 117*4882a593Smuzhiyun 118*4882a593Smuzhiyun namespace, remaining_args = super(ArgumentSubParser, self).parse_known_args(args, namespace) 119*4882a593Smuzhiyun for action in positionals: 120*4882a593Smuzhiyun # remove the empty positional values from namespace 121*4882a593Smuzhiyun if hasattr(namespace, action.dest): 122*4882a593Smuzhiyun delattr(namespace, action.dest) 123*4882a593Smuzhiyun for action in positionals: 124*4882a593Smuzhiyun action.nargs = action.save_nargs 125*4882a593Smuzhiyun # parse positionals 126*4882a593Smuzhiyun namespace, extras = super(ArgumentSubParser, self).parse_known_args(remaining_args, namespace) 127*4882a593Smuzhiyun return namespace, extras 128*4882a593Smuzhiyun 129*4882a593Smuzhiyun def format_help(self): 130*4882a593Smuzhiyun # Quick, restore the positionals! 131*4882a593Smuzhiyun positionals = self._get_positional_actions() 132*4882a593Smuzhiyun for action in positionals: 133*4882a593Smuzhiyun if hasattr(action, 'save_nargs'): 134*4882a593Smuzhiyun action.nargs = action.save_nargs 135*4882a593Smuzhiyun return super(ArgumentParser, self).format_help() 136*4882a593Smuzhiyun 137*4882a593Smuzhiyun 138*4882a593Smuzhiyunclass OeHelpFormatter(argparse.HelpFormatter): 139*4882a593Smuzhiyun def _format_action(self, action): 140*4882a593Smuzhiyun if hasattr(action, '_get_subactions'): 141*4882a593Smuzhiyun # subcommands list 142*4882a593Smuzhiyun groupmap = defaultdict(list) 143*4882a593Smuzhiyun ordermap = {} 144*4882a593Smuzhiyun subparser_groups = action._parent_parser._subparser_groups 145*4882a593Smuzhiyun groups = sorted(subparser_groups.keys(), key=lambda item: subparser_groups[item][1], reverse=True) 146*4882a593Smuzhiyun for subaction in self._iter_indented_subactions(action): 147*4882a593Smuzhiyun parser = action._name_parser_map[subaction.dest] 148*4882a593Smuzhiyun group = getattr(parser, '_group', None) 149*4882a593Smuzhiyun groupmap[group].append(subaction) 150*4882a593Smuzhiyun if group not in groups: 151*4882a593Smuzhiyun groups.append(group) 152*4882a593Smuzhiyun order = getattr(parser, '_order', 0) 153*4882a593Smuzhiyun ordermap[subaction.dest] = order 154*4882a593Smuzhiyun 155*4882a593Smuzhiyun lines = [] 156*4882a593Smuzhiyun if len(groupmap) > 1: 157*4882a593Smuzhiyun groupindent = ' ' 158*4882a593Smuzhiyun else: 159*4882a593Smuzhiyun groupindent = '' 160*4882a593Smuzhiyun for group in groups: 161*4882a593Smuzhiyun subactions = groupmap[group] 162*4882a593Smuzhiyun if not subactions: 163*4882a593Smuzhiyun continue 164*4882a593Smuzhiyun if groupindent: 165*4882a593Smuzhiyun if not group: 166*4882a593Smuzhiyun group = 'other' 167*4882a593Smuzhiyun groupdesc = subparser_groups.get(group, (group, 0))[0] 168*4882a593Smuzhiyun lines.append(' %s:' % groupdesc) 169*4882a593Smuzhiyun for subaction in sorted(subactions, key=lambda item: ordermap[item.dest], reverse=True): 170*4882a593Smuzhiyun lines.append('%s%s' % (groupindent, self._format_action(subaction).rstrip())) 171*4882a593Smuzhiyun return '\n'.join(lines) 172*4882a593Smuzhiyun else: 173*4882a593Smuzhiyun return super(OeHelpFormatter, self)._format_action(action) 174*4882a593Smuzhiyun 175*4882a593Smuzhiyundef int_positive(value): 176*4882a593Smuzhiyun ivalue = int(value) 177*4882a593Smuzhiyun if ivalue <= 0: 178*4882a593Smuzhiyun raise argparse.ArgumentTypeError( 179*4882a593Smuzhiyun "%s is not a positive int value" % value) 180*4882a593Smuzhiyun return ivalue 181