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