xref: /OK3568_Linux_fs/yocto/poky/scripts/lib/argparse_oe.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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