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