argparse_oe.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import sys
  2. import argparse
  3. from collections import defaultdict, OrderedDict
  4. class ArgumentUsageError(Exception):
  5. """Exception class you can raise (and catch) in order to show the help"""
  6. def __init__(self, message, subcommand=None):
  7. self.message = message
  8. self.subcommand = subcommand
  9. class ArgumentParser(argparse.ArgumentParser):
  10. """Our own version of argparse's ArgumentParser"""
  11. def __init__(self, *args, **kwargs):
  12. kwargs.setdefault('formatter_class', OeHelpFormatter)
  13. self._subparser_groups = OrderedDict()
  14. super(ArgumentParser, self).__init__(*args, **kwargs)
  15. def error(self, message):
  16. sys.stderr.write('ERROR: %s\n' % message)
  17. self.print_help()
  18. sys.exit(2)
  19. def error_subcommand(self, message, subcommand):
  20. if subcommand:
  21. for action in self._actions:
  22. if isinstance(action, argparse._SubParsersAction):
  23. for choice, subparser in action.choices.items():
  24. if choice == subcommand:
  25. subparser.error(message)
  26. return
  27. self.error(message)
  28. def add_subparsers(self, *args, **kwargs):
  29. ret = super(ArgumentParser, self).add_subparsers(*args, **kwargs)
  30. # Need a way of accessing the parent parser
  31. ret._parent_parser = self
  32. # Ensure our class gets instantiated
  33. ret._parser_class = ArgumentSubParser
  34. # Hacky way of adding a method to the subparsers object
  35. ret.add_subparser_group = self.add_subparser_group
  36. return ret
  37. def add_subparser_group(self, groupname, groupdesc, order=0):
  38. self._subparser_groups[groupname] = (groupdesc, order)
  39. class ArgumentSubParser(ArgumentParser):
  40. def __init__(self, *args, **kwargs):
  41. if 'group' in kwargs:
  42. self._group = kwargs.pop('group')
  43. if 'order' in kwargs:
  44. self._order = kwargs.pop('order')
  45. super(ArgumentSubParser, self).__init__(*args, **kwargs)
  46. for agroup in self._action_groups:
  47. if agroup.title == 'optional arguments':
  48. agroup.title = 'options'
  49. break
  50. def parse_known_args(self, args=None, namespace=None):
  51. # This works around argparse not handling optional positional arguments being
  52. # intermixed with other options. A pretty horrible hack, but we're not left
  53. # with much choice given that the bug in argparse exists and it's difficult
  54. # to subclass.
  55. # Borrowed from http://stackoverflow.com/questions/20165843/argparse-how-to-handle-variable-number-of-arguments-nargs
  56. # with an extra workaround (in format_help() below) for the positional
  57. # arguments disappearing from the --help output, as well as structural tweaks.
  58. # Originally simplified from http://bugs.python.org/file30204/test_intermixed.py
  59. positionals = self._get_positional_actions()
  60. for action in positionals:
  61. # deactivate positionals
  62. action.save_nargs = action.nargs
  63. action.nargs = 0
  64. namespace, remaining_args = super(ArgumentSubParser, self).parse_known_args(args, namespace)
  65. for action in positionals:
  66. # remove the empty positional values from namespace
  67. if hasattr(namespace, action.dest):
  68. delattr(namespace, action.dest)
  69. for action in positionals:
  70. action.nargs = action.save_nargs
  71. # parse positionals
  72. namespace, extras = super(ArgumentSubParser, self).parse_known_args(remaining_args, namespace)
  73. return namespace, extras
  74. def format_help(self):
  75. # Quick, restore the positionals!
  76. positionals = self._get_positional_actions()
  77. for action in positionals:
  78. if hasattr(action, 'save_nargs'):
  79. action.nargs = action.save_nargs
  80. return super(ArgumentParser, self).format_help()
  81. class OeHelpFormatter(argparse.HelpFormatter):
  82. def _format_action(self, action):
  83. if hasattr(action, '_get_subactions'):
  84. # subcommands list
  85. groupmap = defaultdict(list)
  86. ordermap = {}
  87. subparser_groups = action._parent_parser._subparser_groups
  88. groups = sorted(subparser_groups.keys(), key=lambda item: subparser_groups[item][1], reverse=True)
  89. for subaction in self._iter_indented_subactions(action):
  90. parser = action._name_parser_map[subaction.dest]
  91. group = getattr(parser, '_group', None)
  92. groupmap[group].append(subaction)
  93. if group not in groups:
  94. groups.append(group)
  95. order = getattr(parser, '_order', 0)
  96. ordermap[subaction.dest] = order
  97. lines = []
  98. if len(groupmap) > 1:
  99. groupindent = ' '
  100. else:
  101. groupindent = ''
  102. for group in groups:
  103. subactions = groupmap[group]
  104. if not subactions:
  105. continue
  106. if groupindent:
  107. if not group:
  108. group = 'other'
  109. groupdesc = subparser_groups.get(group, (group, 0))[0]
  110. lines.append(' %s:' % groupdesc)
  111. for subaction in sorted(subactions, key=lambda item: ordermap[item.dest], reverse=True):
  112. lines.append('%s%s' % (groupindent, self._format_action(subaction).rstrip()))
  113. return '\n'.join(lines)
  114. else:
  115. return super(OeHelpFormatter, self)._format_action(action)