def handle_option_merge(group_defaults, incoming_options, title):
    """
    Merges a set of group defaults with incoming options.

    A bunch of ceremony here is to ensure backwards compatibility with the old
    num_required_cols and num_optional_cols decorator args. They are used as
    the seed values for the new group defaults which keeps the old behavior
    _mostly_ in tact.

    Known failure points:
        * Using custom groups / names. No 'positional arguments' group
          means no required_cols arg being honored
        * Non-positional args marked as required. It would take group
          shuffling along the lines of that required to make
          mutually exclusive groups show in the correct place. In short, not
          worth the complexity for a legacy feature that's been succeeded by
          a much more powerful alternative.
    """
    if title == 'positional arguments':
        # the argparse default 'required' bucket
        req_cols = getin(group_defaults, ['legacy', 'required_cols'], 2)
        new_defaults = assoc(group_defaults, 'columns', req_cols)
        return merge(new_defaults, incoming_options)
    else:
        opt_cols = getin(group_defaults, ['legacy', 'optional_cols'], 2)
        new_defaults = assoc(group_defaults, 'columns', opt_cols)
        return merge(new_defaults, incoming_options)
Exemplo n.º 2
0
def action_to_json(action, widget, options):
    dropdown_types = {'Listbox', 'Dropdown', 'Counter'}
    if action.required:
        # Text fields get a default check that user input is present
        # and not just spaces, dropdown types get a simplified
        # is-it-present style check
        validator = ('user_input and not user_input.isspace()'
                     if widget not in dropdown_types else 'user_input')
        error_msg = 'This field is required'
    else:
        # not required; do nothing;
        validator = 'True'
        error_msg = ''

    base = merge(
        item_default, {
            'validator': {
                'type': 'ExpressionValidator',
                'test': validator,
                'message': error_msg
            },
        })

    if (options.get(action.dest) or {}).get('initial_value') != None:
        value = options[action.dest]['initial_value']
        options[action.dest]['initial_value'] = handle_initial_values(
            action, widget, value)
    default = handle_initial_values(action, widget, action.default)
    if default == argparse.SUPPRESS:
        default = None

    final_options = merge(base, options.get(action.dest) or {})
    validate_gooey_options(action, widget, final_options)

    return {
        'id':
        action.option_strings[0] if action.option_strings else action.dest,
        'type': widget,
        'cli_type': choose_cli_type(action),
        'required': action.required,
        'data': {
            'display_name': action.metavar or action.dest,
            'help': action.help,
            'required': action.required,
            'nargs': action.nargs or '',
            'commands': action.option_strings,
            'choices':
            list(map(str, action.choices)) if action.choices else [],
            'default': default,
            'dest': action.dest,
        },
        'options': final_options
    }
Exemplo n.º 3
0
def collect_errors(parser, error_registry: Dict[str, Exception],
                   args: Dict[str, Try]) -> Dict[str, str]:
    """
    Merges all the errors from the Args mapping and error registry
    into a final dict.
    """
    # As is a theme throughout this module, to avoid Argparse
    # short-circuiting during parse-time, we pass a placeholder string
    # for required positional arguments which haven't yet been provided
    # by the user. So, what's happening here is that we're now collecting
    # all the args which have the placeholders so that we can flag them
    # all as required and missing.
    # Again, to be hyper clear, this is about being able to collect ALL
    # errors, versus just ONE error (Argparse default).
    required_but_missing = {
        k: 'This field is required'
        for k, v in args.items()
        if isinstance(v, Success) and v.value == VALUE_PLACEHOLDER
    }

    mutexes_required_but_missing = collect_mutex_errors(parser, args)

    errors = {
        k: str(v.error)
        for k, v in args.items() if v is not None and isinstance(v, Failure)
    }
    # Secondary errors are those which get frustratingly applied by
    # Argparse in a way which can't be easily tracked with patching
    # or higher order functions. See: `check_value` for more details.
    secondary = {k: str(e) for k, e in error_registry.items() if e}
    return merge(required_but_missing, errors, secondary,
                 mutexes_required_but_missing)
Exemplo n.º 4
0
def validateForm(self) -> Try[Mapping[str, str]]:  # or Exception
    config = self.navbar.getActiveConfig()
    localErrors: Mapping[str, str] = config.getErrors()
    dynamicResult: Try[Mapping[str, str]] = self.fetchDynamicValidations()

    combineErrors = lambda m: merge(localErrors, m)
    return dynamicResult.map(combineErrors)
def build_app(build_spec):
  app = wx.App(False)

  i18n.load(build_spec['language_dir'], build_spec['language'], build_spec['encoding'])
  imagesPaths = image_repository.loadImages(build_spec['image_dir'])
  gapp = GooeyApplication(merge(build_spec, imagesPaths))
  gapp.Show()
  return (app, gapp)
Exemplo n.º 6
0
 def getValue(self):
     for button, widget in zip(self.radioButtons, self.widgets):
         if button.GetValue():  # is Checked
             return merge(widget.getValue(), {'id': self._id})
     else:
         # just return the first widget's value even though it's
         # not active so that the expected interface is satisfied
         return self.widgets[0].getValue()
Exemplo n.º 7
0
 def getValue(self):
     for button, widget in zip(self.radioButtons, self.widgets):
         if button.GetValue():  # is Checked
             return merge(widget.getValue(), {'id': self._id})
     else:
         # just return the first widget's value even though it's
         # not active so that the expected interface is satisfied
         return self.widgets[0].getValue()
Exemplo n.º 8
0
def build_app(build_spec):
  app = wx.App(False)

  i18n.load(build_spec['language_dir'], build_spec['language'], build_spec['encoding'])
  imagesPaths = image_repository.loadImages(build_spec['image_dir'])
  gapp = GooeyApplication(merge(build_spec, imagesPaths))
  # wx.lib.inspection.InspectionTool().Show()
  gapp.Show()
  return app
def categorize2(groups, widget_dict, options):
    defaults = {'label_color': '#000000', 'description_color': '#363636'}
    return [{
        'name': group['name'],
        'items': list(categorize(group['items'], widget_dict, options)),
        'groups': categorize2(group['groups'], widget_dict, options),
        'description': group['description'],
        'options': merge(defaults, group['options'])
    } for group in groups]
Exemplo n.º 10
0
def action_to_json(action, widget, options):
    dropdown_types = {'Listbox', 'Dropdown', 'Counter'}
    if action.required:
        # Text fields get a default check that user input is present
        # and not just spaces, dropdown types get a simplified
        # is-it-present style check
        validator = ('user_input and not user_input.isspace()'
                     if widget not in dropdown_types else 'user_input')
        error_msg = 'This field is required'
    else:
        # not required; do nothing;
        validator = 'True'
        error_msg = ''

    base = merge(item_default, {
        'validator': {
            'test': validator,
            'message': error_msg
        },
    })

    default = coerce_default(action.default, widget)

    return {
        'id':
        action.option_strings[0] if action.option_strings else action.dest,
        'type': widget,
        'cli_type': choose_cli_type(action),
        'required': action.required,
        'data': {
            'display_name': action.metavar or action.dest,
            'help': action.help,
            'required': action.required,
            'nargs': action.nargs or '',
            'commands': action.option_strings,
            'choices':
            list(map(str, action.choices)) if action.choices else [],
            'default': default,
            'dest': action.dest,
        },
        'options': merge(base,
                         options.get(action.dest) or {})
    }
def action_to_json(action, widget, options):
    if action.required:
        # check that it's present and not just spaces
        validator = 'user_input and not user_input.isspace()'
        error_msg = 'This field is required'
    else:
        # not required; do nothing;
        validator = 'True'
        error_msg = ''

    base = merge(item_default, {
        'validator': {
            'test': validator,
            'message': error_msg
        },
    })

    return {
        'id':
        action.option_strings[0] if action.option_strings else action.dest,
        'type': widget,
        'cli_type': choose_cli_type(action),
        'required': action.required,
        'data': {
            'display_name': action.metavar or action.dest,
            'help': action.help,
            'required': action.required,
            'nargs': action.nargs or '',
            'commands': action.option_strings,
            'choices': action.choices or [],
            'default': clean_default(action.default),
            'dest': action.dest,
        },
        'options': merge(base,
                         options.get(action.dest) or {})
    }
Exemplo n.º 12
0
def _build_app(build_spec, app):
    """
    Note: this method is broken out with app as
    an argument to facilitate testing.
    """
    # use actual program name instead of script file name in macOS menu
    app.SetAppDisplayName(build_spec['program_name'])

    i18n.load(build_spec['language_dir'], build_spec['language'],
              build_spec['encoding'])
    imagesPaths = image_repository.loadImages(build_spec['image_dir'])
    gapp = GooeyApplication(merge(build_spec, imagesPaths))
    # wx.lib.inspection.InspectionTool().Show()
    gapp.Show()
    return (app, gapp)
def build_radio_group(mutex_group, widget_group, options):
    return {
        'id': str(uuid4()),
        'type': 'RadioGroup',
        'cli_type': 'optional',
        'group_name': 'Choose Option',
        'required': mutex_group.required,
        'options': merge(item_default, getattr(mutex_group, 'gooey_options',
                                               {})),
        'data': {
            'commands':
            [action.option_strings for action in mutex_group._group_actions],
            'widgets':
            list(categorize(mutex_group._group_actions, widget_group, options))
        }
    }
Exemplo n.º 14
0
def _build_app(build_spec, app) -> Tuple[Any, wx.Frame]:
    """
    Note: this method is broken out with app as
    an argument to facilitate testing.
    """
    # use actual program name instead of script file name in macOS menu
    app.SetAppDisplayName(build_spec['program_name'])

    i18n.load(build_spec['language_dir'], build_spec['language'],
              build_spec['encoding'])
    imagesPaths = image_repository.loadImages(build_spec['image_dir'])
    gapp2 = render(create_element(RGooey, merge(build_spec, imagesPaths)),
                   None)
    # wx.lib.inspection.InspectionTool().Show()
    # gapp.Show()
    gapp2.Show()
    return (app, gapp2)
Exemplo n.º 15
0
def extract_groups(action_group):
    '''
    Recursively extract argument groups and associated actions
    from ParserGroup objects
    '''
    return {
        'name':
        action_group.title,
        'description':
        action_group.description,
        'items': [
            action for action in action_group._group_actions
            if not is_help_message(action)
        ],
        'groups':
        [extract_groups(group) for group in action_group._action_groups],
        'options':
        merge(group_defaults, getattr(action_group, 'gooey_options', {}))
    }
Exemplo n.º 16
0
def run_integration(module, assertionFunction, **kwargs):
    """
    Integration test harness.

    WXPython is *super* finicky when it comes to integration tests. It needs
    the main Python thread for its app loop, which means we have to integration
    test on a separate thread. The causes further strangeness in how Unittest
    and WXPython interact. In short, each test must be in its own module and
    thus import its own wx instance, and be run in its own "space."

    So long as the above is satisfied, then integration tests can run reliably.

    """
    from gooey.gui import application
    options = merge(
        {
            'image_dir': '::gooey/default',
            'language_dir': getResourcePath('languages'),
            'show_success_modal': False
        }, kwargs)
    module_path = os.path.abspath(module.__file__)
    parser = module.get_parser()
    build_spec = config_generator.create_from_parser(parser, module_path,
                                                     **options)

    time.sleep(2)
    app = application.build_app(build_spec=build_spec)
    executor = futures.ThreadPoolExecutor(max_workers=1)
    # executor runs in parallel and will submit a wx.Destroy request
    # when done making its assertions
    testResult = executor.submit(assertionFunction, app, build_spec)
    # main loop blocks the main thread
    app.MainLoop()
    # .result() blocks as well while we wait for the thread to finish
    # any waiting it may be doing.
    testResult.result()
    del app
Exemplo n.º 17
0
def gooey_params(**kwargs) -> GooeyParams:
    """
    Builds the full GooeyParams object from an arbitrary subset of supplied values
    """
    return GooeyParams(**{  # type: ignore
        'show_preview_warning': kwargs.get('show_preview_warning', True),
        'language': kwargs.get('language', 'english'),
        'target': kwargs.get('target'),

        'dump_build_config': kwargs.get('dump_build_config', False),
        'load_build_config': kwargs.get('load_build_config'),
        'use_cmd_args': kwargs.get('use_cmd_args', False),

        'suppress_gooey_flag': kwargs.get('suppress_gooey_flag') or False,
        # TODO: I should not read from the environment.
        # remains here for legacy reasons pending refactor
        'program_name': kwargs.get('program_name') or os.path.basename(sys.argv[0]).replace('.py', ''),
        'program_description': kwargs.get('program_description') or '',
        'sidebar_title': kwargs.get('sidebar_title', 'Actions'),
        'default_size': kwargs.get('default_size', (610, 530)),
        'auto_start': kwargs.get('auto_start', False),
        'advanced': kwargs.get('advanced', True),
        'run_validators': kwargs.get('run_validators', True),
        'encoding': kwargs.get('encoding', 'utf-8'),
        'show_stop_warning': kwargs.get('show_stop_warning', True),
        'show_success_modal': kwargs.get('show_success_modal', True),
        'show_failure_modal': kwargs.get('show_failure_modal', True),
        'force_stop_is_error': kwargs.get('force_stop_is_error', True),
        'poll_external_updates': kwargs.get('poll_external_updates', False),
        'return_to_config': kwargs.get('return_to_config', False),
        'show_restart_button': kwargs.get('show_restart_button', True),
        'requires_shell': kwargs.get('requires_shell', True),
        'menu': kwargs.get('menu', []),
        'clear_before_run': kwargs.get('clear_before_run', False),
        'fullscreen': kwargs.get('fullscreen', False),

        'use_legacy_titles': kwargs.get('use_legacy_titles', True),
        'required_cols': kwargs.get('required_cols', 2),
        'optional_cols': kwargs.get('optional_cols', 2),
        'manual_start': False,
        'monospace_display': kwargs.get('monospace_display', False),

        'image_dir': kwargs.get('image_dir', '::gooey/default'),
        # TODO: this directory resolution shouldn't happen here!
        # TODO: leaving due to legacy for now
        'language_dir': kwargs.get('language_dir', getResourcePath('languages')),
        'progress_regex': kwargs.get('progress_regex'),
        'progress_expr': kwargs.get('progress_expr'),
        'hide_progress_msg': kwargs.get('hide_progress_msg', False),

        'timing_options': merge({
            'show_time_remaining': False,
            'hide_time_remaining_on_complete': True
        }, kwargs.get('timing_options', {})),
        'disable_progress_bar_animation': kwargs.get('disable_progress_bar_animation', False),
        'disable_stop_button': kwargs.get('disable_stop_button'),
        'shutdown_signal': kwargs.get('shutdown_signal', signal.SIGTERM),
        'use_events': parse_events(kwargs.get('use_events', [])),


        'navigation': kwargs.get('navigation', constants.SIDEBAR),
        'show_sidebar': kwargs.get('show_sidebar', False),
        'tabbed_groups': kwargs.get('tabbed_groups', False),
        'group_by_type': kwargs.get('group_by_type', True),


        'body_bg_color': kwargs.get('body_bg_color', '#f0f0f0'),
        'header_bg_color': kwargs.get('header_bg_color', '#ffffff'),
        'header_height': kwargs.get('header_height', 90),
        'header_show_title': kwargs.get('header_show_title', True),
        'header_show_subtitle': kwargs.get('header_show_subtitle', True),
        'header_image_center': kwargs.get('header_image_center', False),
        'footer_bg_color': kwargs.get('footer_bg_color', '#f0f0f0'),
        'sidebar_bg_color': kwargs.get('sidebar_bg_color', '#f2f2f2'),

        'terminal_panel_color': kwargs.get('terminal_panel_color', '#F0F0F0'),
        'terminal_font_color': kwargs.get('terminal_font_color', '#000000'),
        'terminal_font_family': kwargs.get('terminal_font_family', None),
        'terminal_font_weight': _get_font_weight(kwargs),
        'terminal_font_size': kwargs.get('terminal_font_size', None),
        'richtext_controls': kwargs.get('richtext_controls', False),
        'error_color': kwargs.get('error_color', '#ea7878'),
        # TODO: remove. Only useful for testing
        'cli': kwargs.get('cli', sys.argv),
    })
Exemplo n.º 18
0
def PasswordInput(_, parent, *args, **kwargs):
    style = {'style': wx.TE_PASSWORD}
    return TextInput(parent, *args, **merge(kwargs, style))
Exemplo n.º 19
0
def MultilineTextInput(_, parent, *args, **kwargs):
    style = {'style': wx.TE_MULTILINE}
    return TextInput(parent, *args, **merge(kwargs, style))
Exemplo n.º 20
0
 def __init__(self, *args, **kwargs):
     defaults = {'label': _('choose_colour'),
                 'style': wx.TE_RICH}
     super(ColourChooser, self).__init__(*args, **merge(kwargs, defaults))
Exemplo n.º 21
0
 def __init__(self, *args, **kwargs):
     defaults = {'label': _('choose_time')}
     super(TimeChooser, self).__init__(*args, **merge(kwargs, defaults))
Exemplo n.º 22
0
 def __init__(self, *args, **kwargs):
     defaults = {'label': 'Choose Date'}
     super(DateChooser, self).__init__(*args, **merge(kwargs, defaults))
Exemplo n.º 23
0
def loadImages(targetDir):
    defaultImages = resolvePaths(getResourcePath('images'), filenames)
    return {
        'images': merge(defaultImages, collectOverrides(targetDir, filenames))
    }
Exemplo n.º 24
0
def Gooey(
        f=None,
        advanced=True,
        language='english',
        auto_start=False,  # TODO: add this to the docs. Used to be `show_config=True`
        target=None,
        program_name=None,
        program_description=None,
        default_size=(610, 530),
        use_legacy_titles=True,
        required_cols=2,
        optional_cols=2,
        dump_build_config=False,
        load_build_config=None,
        monospace_display=False,  # TODO: add this to the docs
        image_dir='::gooey/default',
        language_dir=getResourcePath('languages'),
        progress_regex=None,  # TODO: add this to the docs
        progress_expr=None,  # TODO: add this to the docs
        hide_progress_msg=False,  # TODO: add this to the docs
        disable_progress_bar_animation=False,
        disable_stop_button=False,
        group_by_type=True,
        header_height=80,
        navigation='SIDEBAR',  # TODO: add this to the docs
        tabbed_groups=False,
        **kwargs):
    '''
  Decorator for client code's main function.
  Serializes argparse data to JSON for use with the Gooey front end
  '''

    params = merge(locals(), locals()['kwargs'])

    def build(payload):
        def run_gooey(self, args=None, namespace=None):
            # This import is delayed so it is not in the --ignore-gooey codepath.
            from gooey.gui import application
            source_path = sys.argv[0]

            build_spec = None
            if load_build_config:
                try:
                    exec_dir = os.path.dirname(sys.argv[0])
                    open_path = os.path.join(exec_dir, load_build_config)
                    build_spec = json.load(open(open_path, "r"))
                except Exception as e:
                    print(
                        'Exception loading Build Config from {0}: {1}'.format(
                            load_build_config, e))
                    sys.exit(1)

            if not build_spec:
                build_spec = config_generator.create_from_parser(
                    self, source_path, payload_name=payload.__name__, **params)

            if dump_build_config:
                config_path = os.path.join(os.path.dirname(sys.argv[0]),
                                           'gooey_config.json')
                print('Writing Build Config to: {}'.format(config_path))
                with open(config_path, 'w') as f:
                    f.write(json.dumps(build_spec, indent=2))
            application.run(build_spec)

        def inner2(*args, **kwargs):
            ArgumentParser.original_parse_args = ArgumentParser.parse_args
            ArgumentParser.parse_args = run_gooey
            return payload(*args, **kwargs)

        inner2.__name__ = payload.__name__
        return inner2

    def run_without_gooey(func):
        return lambda *args, **kwargs: func(*args, **kwargs)

    if IGNORE_COMMAND in sys.argv:
        sys.argv.remove(IGNORE_COMMAND)
        if callable(f):
            return run_without_gooey(f)
        return run_without_gooey

    if callable(f):
        return build(f)
    return build
Exemplo n.º 25
0
def PasswordInput(_, parent, *args, **kwargs):
    style = {'style': wx.TE_PASSWORD}
    return TextInput(parent, *args, **merge(kwargs, style))
Exemplo n.º 26
0
 def __init__(self, *args, **kwargs):
     defaults = {'label': _('choose_date')}
     super(DateChooser, self).__init__(*args, **merge(kwargs, defaults))
Exemplo n.º 27
0
def MultilineTextInput(_, parent, *args, **kwargs):
    style = {'style': wx.TE_MULTILINE}
    return TextInput(parent, *args, **merge(kwargs, style))
Exemplo n.º 28
0
def loadImages(targetDir):
    defaultImages = resolvePaths(getResourcePath('images'), filenames)
    return {'images': merge(defaultImages, collectOverrides(targetDir, filenames))}
Exemplo n.º 29
0
def Gooey(f=None,
          advanced=True,
          language='english',
          auto_start=False,  # TODO: add this to the docs. Used to be `show_config=True`
          target=None,
          program_name=None,
          program_description=None,
          default_size=(610, 530),
          use_legacy_titles=True,
          required_cols=2,
          optional_cols=2,
          dump_build_config=False,
          load_build_config=None,
          monospace_display=False,  # TODO: add this to the docs
          image_dir='::gooey/default',
          language_dir=getResourcePath('languages'),
          progress_regex=None,  # TODO: add this to the docs
          progress_expr=None,  # TODO: add this to the docs
          disable_progress_bar_animation=False,
          disable_stop_button=False,
          group_by_type=True,
          header_height=80,
          navigation='SIDEBAR', # TODO: add this to the docs
          tabbed_groups=False,
          **kwargs):
  '''
  Decorator for client code's main function.
  Serializes argparse data to JSON for use with the Gooey front end
  '''

  params = merge(locals(), locals()['kwargs'])

  def build(payload):
    def run_gooey(self, args=None, namespace=None):
      source_path = sys.argv[0]

      build_spec = None
      if load_build_config:
        try:
          build_spec = json.load(open(load_build_config, "r"))
        except Exception as e:
          print( 'Exception loading Build Config from {0}: {1}'.format(load_build_config, e))
          sys.exit(1)

      if not build_spec:
        build_spec = config_generator.create_from_parser(
          self,
          source_path,
          payload_name=payload.__name__,
          **params)

      if dump_build_config:
        config_path = os.path.join(os.getcwd(), 'gooey_config.json')
        print('Writing Build Config to: {}'.format(config_path))
        with open(config_path, 'w') as f:
          f.write(json.dumps(build_spec, indent=2))
      application.run(build_spec)

    def inner2(*args, **kwargs):
      ArgumentParser.original_parse_args = ArgumentParser.parse_args
      ArgumentParser.parse_args = run_gooey
      return payload(*args, **kwargs)

    inner2.__name__ = payload.__name__
    return inner2

  def run_without_gooey(func):
    return lambda: func()

  if IGNORE_COMMAND in sys.argv:
    sys.argv.remove(IGNORE_COMMAND)
    if callable(f):
      return run_without_gooey(f)
    return run_without_gooey

  if callable(f):
    return build(f)
  return build