diff --git a/README.md b/README.md index 9e8b434..6831f3c 100644 --- a/README.md +++ b/README.md @@ -264,6 +264,7 @@ Just about everything in Gooey's overall look and feel can be customized by pass | show_restart_button | Toggles whether or not to show the restart button at the end of execution | | run_validators | Controls whether or not to have Gooey perform validation before calling your program | | poll_external_updates | (Experimental!) When True, Gooey will call your code with a `gooey-seed-ui` CLI argument and use the response to fill out dynamic values in the UI (See: [Using Dynamic Values](#using-dynamic-values))| +| use_cmd_args | Substitute any command line arguments provided at run time for the default values specified in the Gooey configuration | | return_to_config | When True, Gooey will return to the configuration settings window upon successful run | | progress_regex | A text regex used to pattern match runtime progress information. See: [Showing Progress](#showing-progress) for a detailed how-to | | progress_expr | A python expression applied to any matches found via the `progress_regex`. See: [Showing Progress](#showing-progress) for a detailed how-to | diff --git a/gooey/python_bindings/cmd_args.py b/gooey/python_bindings/cmd_args.py new file mode 100644 index 0000000..4d0e5e3 --- /dev/null +++ b/gooey/python_bindings/cmd_args.py @@ -0,0 +1,79 @@ +''' +Created on Jan 15 2019 + +@author: Jonathan Schultz + +This file contains code that allows the default argument values to be specified +on the command line. +''' + +from argparse import _SubParsersAction + +def parse_cmd_args(self, args=None): + + def prepare_to_read_cmd_args(item): + ''' + Before reading the command-line arguments, we need to fudge a few things: + 1. If there are subparsers, we need a dest in order to know in which + subparser the command-line values should be stored. + 2. Any required argument or mutex group needs to be made not required, + otherwise it will be compulsory to enter those values on the command + line. + We save the everything as it was before the fudge, so we can restore later. + ''' + for action in item._actions: + if isinstance(action, _SubParsersAction): + action.save_dest = action.dest + if not action.dest: + action.dest = '_subparser' + else: + action.save_required = action.required + action.required = False + action.save_nargs = action.nargs + if action.nargs == '+': + action.nargs = '*' + elif action.nargs is None: + action.nargs = '?' + + for mutex_group in item._mutually_exclusive_groups: + mutex_group.save_required = mutex_group.required + mutex_group.required = False + + def overwrite_default_values(item, cmd_args): + ''' + Subsistute arguments provided on the command line in the place of the + default values provided to argparse. + ''' + for action in item._actions: + if isinstance(action, _SubParsersAction): + subparser_arg = getattr(cmd_args, action.dest, None) + if subparser_arg: + overwrite_default_values(action.choices[subparser_arg], cmd_args) + else: + dest = getattr(action, 'dest', None) + if dest: + cmd_arg = getattr(cmd_args, dest, None) + if cmd_arg: + action.default = cmd_arg + + def restore_original_configuration(item): + ''' + Restore the old values as they were to start with. + ''' + for action in item._actions: + if isinstance(action, _SubParsersAction): + action.dest = action.save_dest + del action.save_dest + else: + action.required = action.save_required + del action.save_required + action.nargs = action.save_nargs + del action.save_nargs + + for mutex_group in item._mutually_exclusive_groups: + mutex_group.required = mutex_group.save_required + del mutex_group.save_required + + prepare_to_read_cmd_args(self) + overwrite_default_values(self, self.original_parse_args(args)) + restore_original_configuration(self) diff --git a/gooey/python_bindings/gooey_decorator.py b/gooey/python_bindings/gooey_decorator.py index f5fe0fe..fdde9a3 100644 --- a/gooey/python_bindings/gooey_decorator.py +++ b/gooey/python_bindings/gooey_decorator.py @@ -14,6 +14,7 @@ from argparse import ArgumentParser from gooey.gui.util.freeze import getResourcePath from gooey.util.functional import merge from . import config_generator +from . import cmd_args IGNORE_COMMAND = '--ignore-gooey' @@ -43,6 +44,7 @@ def Gooey(f=None, header_height=80, navigation='SIDEBAR', # TODO: add this to the docs tabbed_groups=False, + use_cmd_args=False, **kwargs): ''' Decorator for client code's main function. @@ -68,6 +70,9 @@ def Gooey(f=None, sys.exit(1) if not build_spec: + if use_cmd_args: + cmd_args.parse_cmd_args(self, args) + build_spec = config_generator.create_from_parser( self, source_path, diff --git a/gooey/tests/test_cmd_args.py b/gooey/tests/test_cmd_args.py new file mode 100644 index 0000000..9de7e74 --- /dev/null +++ b/gooey/tests/test_cmd_args.py @@ -0,0 +1,61 @@ +import unittest + +from gooey import GooeyParser +from gooey.python_bindings import cmd_args +from argparse import ArgumentParser + +class TextCommandLine(unittest.TestCase): + + def test_default_overwritten(self): + parser = GooeyParser() + ArgumentParser.original_parse_args = ArgumentParser.parse_args + + parser.add_argument('arg', type=int, default=0) + + # Supply 1 as command line argument, check that it overwrites argparse default + cmd_args.parse_cmd_args(parser, ['1']) + argdefault = next(action for action in parser._actions if action.dest == 'arg').default + self.assertEqual(argdefault, 1) + + def test_required_not_enforced(self): + parser = GooeyParser() + ArgumentParser.original_parse_args = ArgumentParser.parse_args + + parser.add_argument('--arg', type=int, required=True) + parser.add_argument('--argn', type=int, nargs='+') + parser.add_argument('argp', type=int) + mutex=parser.add_mutually_exclusive_group(required=True) + mutex.add_argument('--one', action='store_true') + mutex.add_argument('--two', action='store_true') + + # No error when we don't provide required arguments + cmd_args.parse_cmd_args(parser) + + # Test that required/argn have been restored in parser + argrequired = next(action for action in parser._actions if action.dest == 'arg').required + self.assertEqual(argrequired, True) + argnnargs = next(action for action in parser._actions if action.dest == 'argn').nargs + self.assertEqual(argnnargs, '+') + argpnargs = next(action for action in parser._actions if action.dest == 'argp').nargs + self.assertEqual(argpnargs, None) + mutexrequired = next(mutex for mutex in parser._mutually_exclusive_groups).required + self.assertEqual(mutexrequired, True) + + def test_cmd_args_subparser(self): + parser = GooeyParser() + subparsers = parser.add_subparsers(dest='subparser') + subparserA = subparsers.add_parser('A') + subparserB = subparsers.add_parser('B') + subparserA.add_argument('argA', type=int, default=0) + subparserB.add_argument('argB', type=int, default=0) + + ArgumentParser.original_parse_args = ArgumentParser.parse_args + + cmd_args.parse_cmd_args(parser, ['A', '1']) + + # Check that argA is overwritten but not argB + subparseraction = next(action for action in parser._actions if action.dest == 'subparser') + argAdefault = next(action for action in subparseraction.choices['A']._actions if action.dest == 'argA').default + self.assertEqual(argAdefault, 1) + argBdefault = next(action for action in subparseraction.choices['B']._actions if action.dest == 'argB').default + self.assertEqual(argBdefault, 0)