mirror of https://github.com/chriskiehl/Gooey.git
chriskiehl
7 years ago
11 changed files with 892 additions and 353 deletions
Split View
Diff Options
-
197gooey/gui/components/widgets/base.py
-
10gooey/gui/containers/application.py
-
150gooey/gui/processor.py
-
75gooey/gui/util/formatters.py
-
4gooey/gui/util/functional.py
-
513gooey/python_bindings/argparse_to_json.py
-
4gooey/python_bindings/config_generator.py
-
16gooey/python_bindings/gooey_decorator.py
-
188gooey/python_bindings/gooey_parser.py
-
30gooey/python_bindings/groupings.py
-
58gooey/util/__init__.py
@ -0,0 +1,197 @@ |
|||
import wx |
|||
|
|||
from gooey.gui.util import wx_util |
|||
|
|||
|
|||
|
|||
|
|||
class BaseWidget(wx.Panel): |
|||
|
|||
def __init__(self, parent, *args, **kwargs ): |
|||
super(BaseWidget, self).__init__(*args, **kwargs) |
|||
|
|||
|
|||
def arrange(self, label, text): |
|||
raise NotImplementedError |
|||
|
|||
def getWidget(self, ): |
|||
return self.widget_class(self) |
|||
|
|||
def connectSignal(self): |
|||
raise NotImplementedError |
|||
|
|||
def getSublayout(self, *args, **kwargs): |
|||
raise NotImplementedError |
|||
|
|||
def setValue(self, value): |
|||
raise NotImplementedError |
|||
|
|||
def receiveChange(self, *args, **kwargs): |
|||
raise NotImplementedError |
|||
|
|||
def dispatchChange(self, value, **kwargs): |
|||
raise NotImplementedError |
|||
|
|||
def formatOutput(self, metatdata, value): |
|||
raise NotImplementedError |
|||
|
|||
|
|||
|
|||
|
|||
class TextContainer(BaseWidget): |
|||
''' |
|||
|
|||
type: "N" |
|||
validator: "x > 10 and x < 11" |
|||
|
|||
''' |
|||
widget_class = None |
|||
|
|||
def __init__(self, parent, widgetInfo, *args, **kwargs): |
|||
super(TextContainer, self).__init__(parent, *args, **kwargs) |
|||
|
|||
self._id = widgetInfo['id'] |
|||
self._meta = widgetInfo['data'] |
|||
self.label = wx.StaticText('<b>{}</b>'.format(widgetInfo['data']['display_name'])) |
|||
self.help_text = wx.StaticText(widgetInfo['data']['help']) |
|||
self.error_text = wx.StaticText(widgetInfo['data']['help']) |
|||
self.widget = self.getWidget() |
|||
self.layout = self.arrange(self.label, self.help_text) |
|||
self.value = Subject() |
|||
self.connectSignal() |
|||
|
|||
def arrange(self, label, text): |
|||
layout = QVBoxLayout() |
|||
layout.addWidget(label, alignment=Qt.AlignTop) |
|||
if text: |
|||
layout.addWidget(text) |
|||
else: |
|||
layout.addStretch(1) |
|||
layout.addLayout(self.getSublayout()) |
|||
return layout |
|||
|
|||
def getWidget(self,): |
|||
return self.widget_class(self) |
|||
|
|||
def connectSignal(self): |
|||
self.widget.textChanged.connect(self.dispatchChange) |
|||
|
|||
def getSublayout(self, *args, **kwargs): |
|||
raise NotImplementedError |
|||
|
|||
def setValue(self, value): |
|||
raise NotImplementedError |
|||
|
|||
def receiveChange(self, metatdata, value): |
|||
raise NotImplementedError |
|||
|
|||
def dispatchChange(self, value, **kwargs): |
|||
raise NotImplementedError |
|||
|
|||
def formatOutput(self, metadata, value): |
|||
raise NotImplementedError |
|||
|
|||
|
|||
|
|||
|
|||
class BaseGuiComponent(object): |
|||
widget_class = None |
|||
|
|||
def __init__(self, parent, title, msg, choices=None): |
|||
''' |
|||
:param data: field info (title, help, etc..) |
|||
:param widget_pack: internal wxWidgets to render |
|||
''' |
|||
# parent |
|||
self.parent = parent |
|||
|
|||
# Widgets |
|||
self.title = None |
|||
self.help_msg = None |
|||
self.choices = choices |
|||
|
|||
# Internal WidgetPack set in subclasses |
|||
|
|||
self.do_layout(parent, title, msg) |
|||
|
|||
def do_layout(self, parent, title, msg): |
|||
self.panel = wx.Panel(parent) |
|||
|
|||
self.widget_pack = self.widget_class() |
|||
|
|||
self.title = self.format_title(self.panel, title) |
|||
self.help_msg = self.format_help_msg(self.panel, msg) |
|||
self.help_msg.SetMinSize((0, -1)) |
|||
core_widget_set = self.widget_pack.build(self.panel, {}, self.choices) |
|||
|
|||
vertical_container = wx.BoxSizer(wx.VERTICAL) |
|||
|
|||
vertical_container.Add(self.title) |
|||
vertical_container.AddSpacer(2) |
|||
|
|||
if self.help_msg.GetLabelText(): |
|||
vertical_container.Add(self.help_msg, 1, wx.EXPAND) |
|||
vertical_container.AddSpacer(2) |
|||
else: |
|||
vertical_container.AddStretchSpacer(1) |
|||
|
|||
vertical_container.Add(core_widget_set, 0, wx.EXPAND) |
|||
self.panel.SetSizer(vertical_container) |
|||
|
|||
return self.panel |
|||
|
|||
def bind(self, *args, **kwargs): |
|||
print(self.widget_pack.widget.Bind(*args, **kwargs)) |
|||
|
|||
def get_title(self): |
|||
return self.title.GetLabel() |
|||
|
|||
def set_title(self, text): |
|||
self.title.SetLabel(text) |
|||
|
|||
def get_help_msg(self): |
|||
return self.help_msg.GetLabelText() |
|||
|
|||
def set_label_text(self, text): |
|||
self.help_msg.SetLabel(text) |
|||
|
|||
def format_help_msg(self, parent, msg): |
|||
base_text = wx.StaticText(parent, label=msg or '') |
|||
wx_util.dark_grey(base_text) |
|||
return base_text |
|||
|
|||
def format_title(self, parent, title): |
|||
text = wx.StaticText(parent, label=title) |
|||
wx_util.make_bold(text) |
|||
return text |
|||
|
|||
def onResize(self, evt): |
|||
# handle internal widgets |
|||
# self.panel.Freeze() |
|||
self._onResize(evt) |
|||
# propagate event to child widgets |
|||
self.widget_pack.onResize(evt) |
|||
evt.Skip() |
|||
# self.panel.Thaw() |
|||
|
|||
def _onResize(self, evt): |
|||
if not self.help_msg: |
|||
return |
|||
self.panel.Size = evt.GetSize() |
|||
container_width, _ = self.panel.Size |
|||
text_width, _ = self.help_msg.Size |
|||
|
|||
if text_width != container_width: |
|||
self.help_msg.SetLabel(self.help_msg.GetLabelText().replace('\n', ' ')) |
|||
self.help_msg.Wrap(container_width) |
|||
evt.Skip() |
|||
|
|||
def get_value(self): |
|||
return self.widget_pack.get_value() |
|||
|
|||
def set_value(self, val): |
|||
if val: |
|||
self.widget_pack.widget.SetValue(str(val)) |
|||
|
|||
def __repr__(self): |
|||
return self.__class__.__name__ |
@ -0,0 +1,10 @@ |
|||
|
|||
import wx |
|||
|
|||
|
|||
|
|||
class MainWindow(wx.Frame): |
|||
|
|||
def __init__(self, *args, **kwargs): |
|||
super(MainWindow, self).__init__(*args, **kwargs) |
|||
|
@ -0,0 +1,75 @@ |
|||
import os |
|||
|
|||
from gooey.gui.util.quoting import quote |
|||
|
|||
|
|||
def checkbox(metadata, value): |
|||
return metadata['commands'][0] if value else None |
|||
|
|||
|
|||
def radioGroup(metadata, value): |
|||
# TODO |
|||
try: |
|||
return self.commands[self._value.index(True)][0] |
|||
except ValueError: |
|||
return None |
|||
|
|||
|
|||
def multiFileChooser(metadata, value): |
|||
paths = ' '.join(quote(x) for x in value.split(os.pathsep) if x) |
|||
if metadata['commands'] and paths: |
|||
return u'{} {}'.format(metadata['commands'][0], paths) |
|||
return paths or None |
|||
|
|||
|
|||
def textArea(metadata, value): |
|||
if metadata['commands'] and value: |
|||
return '{} {}'.format(metadata['commands'][0], quote(value.encode('unicode_escape'))) |
|||
else: |
|||
return quote(value.encode('unicode_escape')) if value else '' |
|||
|
|||
|
|||
def commandField(metadata, value): |
|||
if metadata['commands'] and value: |
|||
return u'{} {}'.format(metadata['commands'][0], value) |
|||
else: |
|||
return value or None |
|||
|
|||
|
|||
def counter(metatdata, value): |
|||
''' |
|||
Returns |
|||
str(option_string * DropDown Value) |
|||
e.g. |
|||
-vvvvv |
|||
''' |
|||
if not str(value).isdigit(): |
|||
return None |
|||
arg = str(metatdata['commands'][0]).replace('-', '') |
|||
repeated_args = arg * int(value) |
|||
return '-' + repeated_args |
|||
|
|||
|
|||
def dropdown(metadata, value): |
|||
if value == 'Select Option': |
|||
return None |
|||
elif metadata['commands'] and value: |
|||
return u'{} {}'.format(metadata['commands'][0], quote(value)) |
|||
else: |
|||
return quote(value) if value else '' |
|||
|
|||
|
|||
def general(metadata, value): |
|||
if metadata['commands'] and value: |
|||
if not metadata['nargs']: |
|||
v = quote(value) |
|||
else: |
|||
v = value |
|||
return u'{0} {1}'.format(metadata['commands'][0], v) |
|||
else: |
|||
if not value: |
|||
return None |
|||
elif not metadata['nargs']: |
|||
return quote(value) |
|||
else: |
|||
return value |
@ -1,96 +1,112 @@ |
|||
from argparse import ArgumentParser, _SubParsersAction, _MutuallyExclusiveGroup |
|||
|
|||
from gooey.gui.lang.i18n import _ |
|||
|
|||
|
|||
class GooeySubParser(_SubParsersAction): |
|||
def __init__(self, *args, **kwargs): |
|||
super(GooeySubParser, self).__init__(*args, **kwargs) |
|||
def __init__(self, *args, **kwargs): |
|||
super(GooeySubParser, self).__init__(*args, **kwargs) |
|||
|
|||
class GooeyMutuallyExclusiveGroup(_MutuallyExclusiveGroup): |
|||
def __init__(self, parser, widgets, *args, **kwargs): |
|||
self.parser = parser |
|||
self.widgets = widgets |
|||
super(GooeyMutuallyExclusiveGroup, self).__init__(self.parser, *args, **kwargs) |
|||
|
|||
def add_argument(self, *args, **kwargs): |
|||
widget = kwargs.pop('widget', None) |
|||
metavar = kwargs.pop('metavar', None) |
|||
super(GooeyMutuallyExclusiveGroup, self).add_argument(*args, **kwargs) |
|||
self.parser._actions[-1].metavar = metavar |
|||
self.widgets[self.parser._actions[-1].dest] = widget |
|||
class GooeyMutuallyExclusiveGroup(_MutuallyExclusiveGroup): |
|||
def __init__(self, parser, metadata, *args, **kwargs): |
|||
self.parser = parser |
|||
self.metadata = metadata |
|||
title = kwargs.pop('title', None) # todo inc group num? |
|||
super(GooeyMutuallyExclusiveGroup, self).__init__(self.parser, *args, **kwargs) |
|||
self.title = title |
|||
|
|||
def add_argument(self, *args, **kwargs): |
|||
widget = kwargs.pop('widget', None) |
|||
validator = kwargs.pop('validate', None) |
|||
metavar = kwargs.pop('metavar', None) |
|||
super(GooeyMutuallyExclusiveGroup, self).add_argument(*args, **kwargs) |
|||
self.parser._actions[-1].metavar = metavar |
|||
self.metadata['widgets'][self.parser._actions[-1].dest] = widget |
|||
self.metadata['validators'][self.parser._actions[-1].dest] = validator |
|||
|
|||
|
|||
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) |
|||
metavar = kwargs.pop('metavar', None) |
|||
self.parser.add_argument(*args, **kwargs) |
|||
self.parser._actions[-1].metavar = metavar |
|||
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_mutually_exclusive_group(self, **kwargs): |
|||
group = GooeyMutuallyExclusiveGroup(self.parser, self.widgets, **kwargs) |
|||
self.parser._mutually_exclusive_groups.append(group) |
|||
return group |
|||
|
|||
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 add_subparsers(self, **kwargs): |
|||
if self._subparsers is not None: |
|||
self.error(_('cannot have multiple subparser arguments')) |
|||
|
|||
# add the parser class to the arguments if it's not present |
|||
kwargs.setdefault('parser_class', type(self)) |
|||
|
|||
if 'title' in kwargs or 'description' in kwargs: |
|||
title = _(kwargs.pop('title', 'subcommands')) |
|||
description = _(kwargs.pop('description', None)) |
|||
self._subparsers = self.add_argument_group(title, description) |
|||
else: |
|||
self._subparsers = self._positionals |
|||
|
|||
# prog defaults to the usage message of this parser, skipping |
|||
# optional arguments and with no "usage:" prefix |
|||
if kwargs.get('prog') is None: |
|||
formatter = self._get_formatter() |
|||
positionals = self._get_positional_actions() |
|||
groups = self._mutually_exclusive_groups |
|||
formatter.add_usage(self.usage, positionals, groups, '') |
|||
kwargs['prog'] = formatter.format_help().strip() |
|||
|
|||
# create the parsers action and add it to the positionals list |
|||
parsers_class = self._pop_action_class(kwargs, 'parsers') |
|||
action = parsers_class(option_strings=[], **kwargs) |
|||
self._subparsers._add_action(action) |
|||
|
|||
# return the created parsers action |
|||
return action |
|||
|
|||
def __getattr__(self, item): |
|||
return getattr(self.parser, item) |
|||
|
|||
def __setattr__(self, key, value): |
|||
return setattr(self.parser, key, value) |
|||
def __init__(self, **kwargs): |
|||
self.__dict__['parser'] = ArgumentParser(**kwargs) |
|||
# self.widgets = {} |
|||
# self.validators = {} |
|||
self.metadata = { |
|||
'widgets': {}, |
|||
'validators': {} |
|||
} |
|||
|
|||
@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) |
|||
validator = kwargs.pop('validate', None) |
|||
metavar = kwargs.pop('metavar', None) |
|||
self.parser.add_argument(*args, **kwargs) |
|||
self.parser._actions[-1].metavar = metavar |
|||
self.metadata['widgets'][self.parser._actions[-1].dest] = widget |
|||
self.metadata['validators'][self.parser._actions[-1].dest] = validator |
|||
|
|||
|
|||
def add_mutually_exclusive_group(self, **kwargs): |
|||
group = GooeyMutuallyExclusiveGroup(self.parser, self.metadata, **kwargs) |
|||
self.parser._mutually_exclusive_groups.append(group) |
|||
return group |
|||
|
|||
|
|||
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 add_subparsers(self, **kwargs): |
|||
if self._subparsers is not None: |
|||
self.error(_('cannot have multiple subparser arguments')) |
|||
|
|||
# add the parser class to the arguments if it's not present |
|||
kwargs.setdefault('parser_class', type(self)) |
|||
|
|||
if 'title' in kwargs or 'description' in kwargs: |
|||
title = _(kwargs.pop('title', 'subcommands')) |
|||
description = _(kwargs.pop('description', None)) |
|||
self._subparsers = self.add_argument_group(title, description) |
|||
else: |
|||
self._subparsers = self._positionals |
|||
|
|||
# prog defaults to the usage message of this parser, skipping |
|||
# optional arguments and with no "usage:" prefix |
|||
if kwargs.get('prog') is None: |
|||
formatter = self._get_formatter() |
|||
positionals = self._get_positional_actions() |
|||
groups = self._mutually_exclusive_groups |
|||
formatter.add_usage(self.usage, positionals, groups, '') |
|||
kwargs['prog'] = formatter.format_help().strip() |
|||
|
|||
# create the parsers action and add it to the positionals list |
|||
parsers_class = self._pop_action_class(kwargs, 'parsers') |
|||
action = parsers_class(option_strings=[], **kwargs) |
|||
self._subparsers._add_action(action) |
|||
|
|||
# return the created parsers action |
|||
return action |
|||
|
|||
|
|||
def __getattr__(self, item): |
|||
return getattr(self.parser, item) |
|||
|
|||
|
|||
def __setattr__(self, key, value): |
|||
return setattr(self.parser, key, value) |
@ -0,0 +1,30 @@ |
|||
from collections import OrderedDict |
|||
|
|||
|
|||
def positional(actions): |
|||
groups = OrderedDict([('Positional Arguments', []), ('Optional Arguments', [])]) |
|||
for action in actions: |
|||
if action['group_name'] == 'Positional Arguments': |
|||
groups['Positional Arguments'].append(action) |
|||
else: |
|||
groups['Optional Arguments'].append(action) |
|||
return groups |
|||
|
|||
|
|||
def requiredAndOptional(actions): |
|||
groups = OrderedDict([('Required', []), ('Optional', [])]) |
|||
for action in actions: |
|||
if action['required']: |
|||
groups['Required'].append(action) |
|||
else: |
|||
groups['Optional'].append(action) |
|||
return groups |
|||
|
|||
|
|||
def argparseGroups(actions): |
|||
groups = OrderedDict() |
|||
for action in actions: |
|||
if action['group_name'] not in groups: |
|||
groups[action['group_name']] = [] |
|||
groups[action['group_name']].append(action) |
|||
return groups |
@ -0,0 +1,58 @@ |
|||
from functools import reduce |
|||
from inspect import signature |
|||
|
|||
|
|||
|
|||
def apply_transforms(data, func): |
|||
return func(data) |
|||
|
|||
|
|||
def bootlegCurry(f): |
|||
''' |
|||
a bootleg curry. |
|||
''' |
|||
def _curry(f, remaining): |
|||
def inner(*args): |
|||
if len(args) >= remaining: |
|||
return f(*args) |
|||
else: |
|||
newfunc = lambda *rem: f(*args, *rem) |
|||
return _curry(newfunc, remaining - len(args)) |
|||
return inner |
|||
return _curry(f, len(signature(f).parameters)) |
|||
|
|||
|
|||
def excluding(item_dict, *to_exclude): |
|||
excluded = set(to_exclude) |
|||
return {key: val for key, val in item_dict.items() |
|||
if key not in excluded} |
|||
|
|||
|
|||
def indentity(x): |
|||
return x |
|||
|
|||
|
|||
def merge(*args): |
|||
return reduce(lambda acc, val: acc.update(val) or acc, args, {}) |
|||
|
|||
|
|||
def partition_by(f, coll): |
|||
a = [] |
|||
b = [] |
|||
for item in coll: |
|||
bucket = a if f(item) else b |
|||
bucket.append(item) |
|||
return a, b |
|||
|
|||
|
|||
|
|||
|
|||
if __name__ == '__main__': |
|||
pass |
|||
# a = { |
|||
# 'a': 111, |
|||
# 'b': 111, |
|||
# 'c': 111, |
|||
# 1: 111 |
|||
# } |
|||
# print(excluding(a, 'a', 'c', 1)) |
Write
Preview
Loading…
Cancel
Save