blob: 94a4ac50110f23202738bc1ce2d79b5d6e5a92d4 [file] [log] [blame]
Brad Bishopc342db32019-05-15 21:57:59 -04001#
2# SPDX-License-Identifier: GPL-2.0-only
3#
4
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05005import 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)
Patrick Williamsc0f7c042017-02-23 20:41:17 -060021 self._positionals.title = 'arguments'
22 self._optionals.title = 'options'
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050023
24 def error(self, message):
Patrick Williamsc0f7c042017-02-23 20:41:17 -060025 """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)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050032 sys.exit(2)
33
34 def error_subcommand(self, message, subcommand):
35 if subcommand:
Patrick Williamsc0f7c042017-02-23 20:41:17 -060036 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
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050044 self.error(message)
45
46 def add_subparsers(self, *args, **kwargs):
Patrick Williamsc0f7c042017-02-23 20:41:17 -060047 if 'dest' not in kwargs:
48 kwargs['dest'] = '_subparser_name'
49
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050050 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
Patrick Williamsc0f7c042017-02-23 20:41:17 -060062 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
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050094
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)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500102
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)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500174
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