From c0318c7e9724777c904ac815ac81013760d9ed40 Mon Sep 17 00:00:00 2001 From: chriskiehl Date: Sat, 22 Nov 2014 20:01:38 -0500 Subject: [PATCH] Added GooeyParser - wrapper for Argparse to allow custom widget desclarations --- gooey/_tmp/mockapp.py | 19 +++-- gooey/gui/controller.py | 7 +- gooey/gui/windows/base_window.py | 2 +- gooey/mockapplications/mockapp.py | 18 +++-- gooey/python_bindings/argparse_to_json.py | 87 +++++++++++++---------- gooey/python_bindings/gooey_decorator.py | 50 ++++++++++--- gooey/python_bindings/source_parser.py | 3 +- 7 files changed, 120 insertions(+), 66 deletions(-) diff --git a/gooey/_tmp/mockapp.py b/gooey/_tmp/mockapp.py index 61a7bd2..0c9d37b 100644 --- a/gooey/_tmp/mockapp.py +++ b/gooey/_tmp/mockapp.py @@ -5,15 +5,15 @@ Created on Dec 21, 2013 ''' import sys import hashlib -from time import time as _time +from time import time as _time, time from time import sleep as _sleep # from argparse import ArgumentParser # import argparse import argparse as ap from argparse import ArgumentParser as AP - -a = globals() +from gooey import Gooey +from gooey.python_bindings.gooey_decorator import GooeyParser def main(): @@ -22,9 +22,9 @@ def main(): ''' desc = "Mock application to test Gooey's functionality" file_help_msg = "Name of the file you want to process" - my_cool_parser = ap.ArgumentParser(description=desc) - my_cool_parser.add_argument("filename", help=file_help_msg, metavar='asdf') # positional - my_cool_parser.add_argument("outfile", help="Name of the file where you'll save the output") # positional + my_cool_parser = GooeyParser(description=desc) + my_cool_parser.add_argument("filename", help=file_help_msg, widget="FileChooser") # positional + my_cool_parser.add_argument("outfile", help="Name of the file where you'll save the output", widget="FileChooser") # positional my_cool_parser.add_argument('-c', '--countdown', default=10, type=int, help='sets the time to count down from you see its quite simple!') my_cool_parser.add_argument("-s", "--showtime", action="store_true", help="display the countdown timer") my_cool_parser.add_argument("-d", "--delay", action="store_true", help="Delay execution for a bit") @@ -36,8 +36,10 @@ def main(): verbosity = my_cool_parser.add_mutually_exclusive_group() verbosity.add_argument('-t', '--verbozze', dest='verbose', action="store_true", help="Show more details") verbosity.add_argument('-q', '--quiet', dest='quiet', action="store_true", help="Only output on error") + print my_cool_parser._actions print 'inside of main(), my_cool_parser =', my_cool_parser + args = my_cool_parser.parse_args() print sys.argv @@ -53,8 +55,11 @@ def main(): print 'printing message at: %s' % hashlib.md5(str(_time())).hexdigest() _sleep(.5) print 'Finished running the program. Byeeeeesss!' + raise ValueError("Something has gone wrong! AHHHHHHHHHHH") + +def here_is_smore(): + pass -# raise ValueError("Something has gone wrong! AHHHHHHHHHHH") if __name__ == '__main__': print sys.argv diff --git a/gooey/gui/controller.py b/gooey/gui/controller.py index 2f4388a..8ffac5e 100644 --- a/gooey/gui/controller.py +++ b/gooey/gui/controller.py @@ -55,10 +55,11 @@ class Controller(object): self.ShowDialog(i18n.translate('error_title'), "Must fill in all fields in the Required section!", wx.ICON_ERROR) return + command = '{0} {1}'.format(self.build_spec['target'], cmd_line_args) self.core_gui.NextPage() - self.RunClientCode(None) + self.RunClientCode(command) - def RunClientCode(self, process): + def RunClientCode(self, command): def doInBackground(process, callback): while True: line = process.stdout.readline() @@ -67,7 +68,7 @@ class Controller(object): self.core_gui.PublishConsoleMsg(line) callback(process) - p = subprocess.Popen(r'python C:\Users\Chris\Desktop\Untitled\prog.py', bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p = subprocess.Popen(command, bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE) _pool = Pool(1) _pool.apply_async(doInBackground, (p, self.HandleResult)) diff --git a/gooey/gui/windows/base_window.py b/gooey/gui/windows/base_window.py index c99c792..0e4e0d4 100644 --- a/gooey/gui/windows/base_window.py +++ b/gooey/gui/windows/base_window.py @@ -55,7 +55,7 @@ class BaseWindow(wx.Frame): _desc = self.build_spec['program_description'] self.head_panel = header.FrameHeader( heading=i18n.translate("settings_title"), - subheading=_desc if _desc is not None else '', + subheading=_desc or '', parent=self) self.config_panel = BodyPanel(self) self.runtime_display = RuntimeDisplay(self) diff --git a/gooey/mockapplications/mockapp.py b/gooey/mockapplications/mockapp.py index e378469..c05a6ce 100644 --- a/gooey/mockapplications/mockapp.py +++ b/gooey/mockapplications/mockapp.py @@ -5,7 +5,7 @@ Created on Dec 21, 2013 ''' import sys import hashlib -from time import time as _time +from time import time as _time, time from time import sleep as _sleep # from argparse import ArgumentParser # import argparse @@ -13,8 +13,7 @@ import argparse as ap from argparse import ArgumentParser as AP from gooey import Gooey - -a = globals() +from gooey.python_bindings.gooey_decorator import GooeyParser @Gooey @@ -24,9 +23,9 @@ def main(): ''' desc = "Mock application to test Gooey's functionality" file_help_msg = "Name of the file you want to process" - my_cool_parser = ap.ArgumentParser(description=desc) - my_cool_parser.add_argument("filename", help=file_help_msg, metavar='asdf') # positional - my_cool_parser.add_argument("outfile", help="Name of the file where you'll save the output") # positional + my_cool_parser = GooeyParser(description=desc) + my_cool_parser.add_argument("filename", help=file_help_msg, widget="FileChooser") # positional + my_cool_parser.add_argument("outfile", help="Name of the file where you'll save the output", widget="FileChooser") # positional my_cool_parser.add_argument('-c', '--countdown', default=10, type=int, help='sets the time to count down from you see its quite simple!') my_cool_parser.add_argument("-s", "--showtime", action="store_true", help="display the countdown timer") my_cool_parser.add_argument("-d", "--delay", action="store_true", help="Delay execution for a bit") @@ -38,8 +37,10 @@ def main(): verbosity = my_cool_parser.add_mutually_exclusive_group() verbosity.add_argument('-t', '--verbozze', dest='verbose', action="store_true", help="Show more details") verbosity.add_argument('-q', '--quiet', dest='quiet', action="store_true", help="Only output on error") + print my_cool_parser._actions print 'inside of main(), my_cool_parser =', my_cool_parser + args = my_cool_parser.parse_args() print sys.argv @@ -55,8 +56,11 @@ def main(): print 'printing message at: %s' % hashlib.md5(str(_time())).hexdigest() _sleep(.5) print 'Finished running the program. Byeeeeesss!' + raise ValueError("Something has gone wrong! AHHHHHHHHHHH") + +def here_is_smore(): + pass -# raise ValueError("Something has gone wrong! AHHHHHHHHHHH") if __name__ == '__main__': print sys.argv diff --git a/gooey/python_bindings/argparse_to_json.py b/gooey/python_bindings/argparse_to_json.py index 3c33d97..ab70d0c 100644 --- a/gooey/python_bindings/argparse_to_json.py +++ b/gooey/python_bindings/argparse_to_json.py @@ -7,33 +7,42 @@ from argparse import ( _HelpAction, _StoreConstAction, _StoreFalseAction, - _StoreTrueAction -) + _StoreTrueAction, + ArgumentParser) import itertools VALID_WIDGETS = ( - '@FileChooser', - '@DirChooser', - '@DateChooser', - '@TextField', - '@Dropdown', - '@Counter', - '@RadioGroup' + 'FileChooser', + 'DirChooser', + 'DateChooser', + 'TextField', + 'Dropdown', + 'Counter', + 'RadioGroup', + 'CheckBox' ) +class UnknownWidgetType(Exception): + pass + -def convert(argparser): +def convert(parser): + widget_dict = getattr(parser, 'widgets', None) mutually_exclusive_group = [ mutex_action - for group_actions in argparser._mutually_exclusive_groups + for group_actions in parser._mutually_exclusive_groups for mutex_action in group_actions._group_actions] - base_actions = [action for action in argparser._actions + + base_actions = [(action, widget_dict.get(action.dest, None)) + for action in parser._actions if action not in mutually_exclusive_group] + + positional_args = get_required_and_positional_args(base_actions) choice_args = get_optionals_with_choices(base_actions) @@ -63,12 +72,13 @@ def get_required_and_positional_args(actions): In argparse, positionals are defined by either an empty option_strings or by the option_strings parameters being sans a leading hyphen """ - filtered_actions = [action for action in actions - if not action.option_strings - or action.required == True] + filtered_actions = [(action, widget) + for action, widget in actions + if not action.option_strings + or action.required == True] - return [as_json(action, default_widget='TextField') - for action in filtered_actions] + return [as_json(action, widget=widget or 'TextField') + for action, widget in filtered_actions] def get_optionals_with_choices(actions): @@ -76,12 +86,12 @@ def get_optionals_with_choices(actions): All optional arguments which are constrained to specific choices. """ - filtered_actions = [action - for action in actions + filtered_actions = [(action, widget) + for action, widget in actions if action.choices] - return [as_json(action, default_widget='Dropdown') - for action in filtered_actions] + return [as_json(action, widget=widget or 'Dropdown') + for action, widget in filtered_actions] def get_optionals_without_choices(actions): @@ -99,8 +109,8 @@ def get_optionals_without_choices(actions): _StoreTrueAction ) filtered_actions = [ - action - for action in actions + (action, widget) + for action, widget in actions if action.option_strings and not action.choices and not isinstance(action, _CountAction) @@ -108,8 +118,8 @@ def get_optionals_without_choices(actions): and type(action) not in boolean_actions ] - return [as_json(action, default_widget='TextField') - for action in filtered_actions] + return [as_json(action, widget=widget or 'TextField') + for action, widget in filtered_actions] def get_flag_style_optionals(actions): @@ -123,27 +133,27 @@ def get_flag_style_optionals(actions): _StoreConst """ filtered_actions = [ - action - for action in actions + (action, widget) + for action, widget in actions if isinstance(action, _StoreTrueAction) or isinstance(action, _StoreFalseAction) or isinstance(action, _StoreConstAction) ] - return [as_json(action, default_widget='CheckBox') - for action in filtered_actions] + return [as_json(action, widget=widget or 'CheckBox') + for action, widget in filtered_actions] def get_counter_style_optionals(actions): """ Returns all instances of type _CountAction """ - filtered_actions = [action - for action in actions + filtered_actions = [(action, widget) + for action, widget in actions if isinstance(action, _CountAction)] - _json_options = [as_json(action, default_widget='Counter') - for action in filtered_actions] + _json_options = [as_json(action, widget=widget or 'Counter') + for action, widget in filtered_actions] # Counter should show as Dropdowns, so pre-populare with numeric choices for opt in _json_options: @@ -172,12 +182,15 @@ def get_mutually_exclusive_optionals(mutex_group): }] -def as_json(action, default_widget): +def as_json(action, widget): + print 'widget:', widget + if widget not in VALID_WIDGETS: + raise UnknownWidgetType('Widget Type {0} is unrecognized'.format(widget)) + option_strings = action.option_strings - _type = option_strings[-1] if option_strings else None return { - 'type': widget_type(_type) if is_widget_spec(_type) else default_widget, - 'data' : { + 'type': widget, + 'data': { 'display_name': action.dest, 'help': action.help, 'nargs': action.nargs or '', diff --git a/gooey/python_bindings/gooey_decorator.py b/gooey/python_bindings/gooey_decorator.py index 49ec3dc..8692467 100644 --- a/gooey/python_bindings/gooey_decorator.py +++ b/gooey/python_bindings/gooey_decorator.py @@ -41,11 +41,13 @@ to us. No more complicated ast stuff. Just a little bit of string parsing and we done. ''' -from argparse import ArgumentParser + +from argparse import ArgumentParser as RealArgParser, ArgumentParser from functools import partial import os import sys +import types import wx @@ -57,7 +59,6 @@ import source_parser ROOT_DIR = os.path.dirname(__import__(__name__.split('.')[0]).__file__) TMP_DIR = os.path.join(ROOT_DIR, '_tmp') - def Gooey(f=None, advanced=True, language='english', show_config=True, program_name=None, program_description=None): @@ -142,13 +143,7 @@ def clean_source(module_path): with open(module_path, 'r') as f: return ''.join( line for line in f.readlines() - if '@gooey' not in line.lower() - and 'import gooey' not in line.lower()) - - -def run(): - parser = source_parser.extract_parser(module_path) - client_module = create_cleaned_backup() + if '@gooey' not in line.lower()) def get_parser(module_path): @@ -159,5 +154,42 @@ def get_caller_path(): return tmp_sys.argv[0] +class GooeyParser(object): + def __init__(self, **kwargs): + self.__dict__['parser'] = ArgumentParser(**kwargs) + self.widgets = {} + + @property + def _mutually_exclusive_groups(self): + return self.parser._mutually_exclusive_groups + + @property + def _actions(self): + return self.parser._actions + + @property + def description(self): + return self.parser.description + + def add_argument(self, *args, **kwargs): + widget = kwargs.pop('widget', None) + self.parser.add_argument(*args, **kwargs) + self.widgets[self.parser._actions[-1].dest] = widget + + def add_mutually_exclusive_group(self, **kwargs): + return self.parser.add_mutually_exclusive_group(**kwargs) + + def add_argument_group(self, *args, **kwargs): + return self.parser.add_argument_group(*args, **kwargs) + + def parse_args(self, args=None, namespace=None): + return self.parser.parse_args(args, namespace) + + def __getattr__(self, item): + return getattr(self.parser, item) + + def __setattr__(self, key, value): + return setattr(self.parser, key, value) + if __name__ == '__main__': pass diff --git a/gooey/python_bindings/source_parser.py b/gooey/python_bindings/source_parser.py index a29b1ff..81a2be1 100644 --- a/gooey/python_bindings/source_parser.py +++ b/gooey/python_bindings/source_parser.py @@ -123,8 +123,7 @@ def format_source_to_return_parser(source, cutoff_line, restart_line, col_offset # stitch it all back together excluding the Gooey decorator new_source = (line for line in chain(top, return_statement, bottom) - if '@gooey' not in line.lower() - and 'import gooey' not in line.lower()) + if '@gooey' not in line.lower()) return ''.join(new_source)