argparse_oe.py 7.1 KB

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