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