You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

183 lines
5.2 KiB

  1. """
  2. Converts argparse parser actions into json "Build Specs"
  3. """
  4. import argparse
  5. from argparse import (
  6. _CountAction,
  7. _HelpAction,
  8. _StoreConstAction,
  9. _StoreFalseAction,
  10. _StoreTrueAction,
  11. ArgumentParser, _SubParsersAction)
  12. from collections import OrderedDict
  13. from functools import partial
  14. VALID_WIDGETS = (
  15. 'FileChooser',
  16. 'MultiFileChooser',
  17. 'FileSaver',
  18. 'DirChooser',
  19. 'DateChooser',
  20. 'TextField',
  21. 'Dropdown',
  22. 'Counter',
  23. 'RadioGroup',
  24. 'CheckBox',
  25. 'MultiDirChooser'
  26. )
  27. class UnknownWidgetType(Exception):
  28. pass
  29. class UnsupportedConfiguration(Exception):
  30. pass
  31. def convert(parser):
  32. widget_dict = getattr(parser, 'widgets', {})
  33. actions = parser._actions
  34. if has_subparsers(actions):
  35. if has_required(actions):
  36. raise UnsupportedConfiguration("Gooey doesn't currently support required arguments when subparsers are present.")
  37. layout_type = 'column'
  38. layout_data = {name.lower(): process(sub_parser, widget_dict) for name, sub_parser in get_subparser(actions).choices.iteritems()}
  39. else:
  40. layout_type = 'standard'
  41. layout_data = process(parser, widget_dict)
  42. return {
  43. 'layout_type': layout_type,
  44. 'widgets': layout_data
  45. }
  46. def process(parser, widget_dict):
  47. mutually_exclusive_group = [
  48. mutex_action
  49. for group_actions in parser._mutually_exclusive_groups
  50. for mutex_action in group_actions._group_actions]
  51. base_actions = [action for action in parser._actions
  52. if action not in mutually_exclusive_group
  53. and action.dest != 'help']
  54. required_actions = filter(is_required, base_actions)
  55. optional_actions = filter(is_optional, base_actions)
  56. return list(categorize(required_actions, widget_dict, required=True)) + \
  57. list(categorize(optional_actions, widget_dict)) + \
  58. build_radio_group(mutually_exclusive_group)
  59. def categorize(actions, widget_dict, required=False):
  60. _get_widget = partial(get_widget, widgets=widget_dict)
  61. for action in actions:
  62. if is_standard(action):
  63. yield as_json(action, _get_widget(action) or 'TextField', required)
  64. elif is_choice(action):
  65. yield as_json(action, _get_widget(action) or 'Dropdown', required)
  66. elif is_flag(action):
  67. yield as_json(action, _get_widget(action) or 'CheckBox', required)
  68. elif is_counter(action):
  69. _json = as_json(action, _get_widget(action) or 'Dropdown', required)
  70. # pre-fill the 'counter' dropdown
  71. _json['choices'] = range(1, 11)
  72. yield _json
  73. else:
  74. raise UnknownWidgetType(action)
  75. def get_widget(action, widgets):
  76. supplied_widget = widgets.get(action.dest, None)
  77. type_arg_widget = 'FileChooser' if action.type == argparse.FileType else None
  78. return supplied_widget or type_arg_widget or None
  79. def is_required(action):
  80. '''_actions which are positional or possessing the `required` flag '''
  81. return not action.option_strings and not isinstance(action, _SubParsersAction) or action.required == True
  82. def has_required(actions):
  83. return filter(None, filter(is_required, actions))
  84. def is_subparser(action):
  85. return isinstance(action,_SubParsersAction)
  86. def has_subparsers(actions):
  87. return filter(is_subparser, actions)
  88. def get_subparser(actions):
  89. return filter(is_subparser, actions)[0]
  90. def is_optional(action):
  91. '''_actions not positional or possessing the `required` flag'''
  92. return action.option_strings and not action.required
  93. def is_choice(action):
  94. ''' action with choices supplied '''
  95. return action.choices
  96. def is_standard(action):
  97. """ actions which are general "store" instructions.
  98. e.g. anything which has an argument style like:
  99. $ script.py -f myfilename.txt
  100. """
  101. boolean_actions = (
  102. _StoreConstAction, _StoreFalseAction,
  103. _StoreTrueAction
  104. )
  105. return (not action.choices
  106. and not isinstance(action, _CountAction)
  107. and not isinstance(action, _HelpAction)
  108. and type(action) not in boolean_actions)
  109. def is_flag(action):
  110. """ _actions which are either storeconst, store_bool, etc.. """
  111. action_types = [_StoreTrueAction, _StoreFalseAction, _StoreConstAction]
  112. return any(map(lambda Action: isinstance(action, Action), action_types))
  113. def is_counter(action):
  114. """ _actions which are of type _CountAction """
  115. return isinstance(action, _CountAction)
  116. def build_radio_group(mutex_group):
  117. if not mutex_group:
  118. return []
  119. options = [
  120. {
  121. 'display_name': mutex_arg.dest,
  122. 'help': mutex_arg.help,
  123. 'nargs': mutex_arg.nargs or '',
  124. 'commands': mutex_arg.option_strings,
  125. 'choices': mutex_arg.choices,
  126. } for mutex_arg in mutex_group
  127. ]
  128. return [{
  129. 'type': 'RadioGroup',
  130. 'group_name': 'Choose Option',
  131. 'required': False,
  132. 'data': options
  133. }]
  134. def as_json(action, widget, required):
  135. if widget not in VALID_WIDGETS:
  136. raise UnknownWidgetType('Widget Type {0} is unrecognized'.format(widget))
  137. return {
  138. 'type': widget,
  139. 'required': required,
  140. 'data': {
  141. 'display_name': action.dest,
  142. 'help': action.help,
  143. 'nargs': action.nargs or '',
  144. 'commands': action.option_strings,
  145. 'choices': action.choices or [],
  146. }
  147. }