diff --git a/README.md b/README.md
index 9d0ba88..20bd817 100644
--- a/README.md
+++ b/README.md
@@ -18,32 +18,33 @@ Table of Contents
-----------------
- [Gooey](#gooey)
-- [Table of contents](#table-of-contents)
-- [Latest Update](#latest-update)
-- [Quick Start](#quick-start)
- - [Installation Instructions](#installation-instructions)
+ - [Table of Contents](#table-of-contents)
+ - [Quick Start](#quick-start)
+ - [Installation instructions](#installation-instructions)
- [Usage](#usage)
- [Examples](#examples)
-- [What It Is](#what-is-it)
-- [Why Is It](#why)
-- [Who is this for](#who-is-this-for)
-- [How does it work](#how-does-it-work)
-- [Internationalization](#internationalization)
-- [Global Configuration](#global-configuration)
-- [Layout Customization](#layout-customization)
-- [Run Modes](#run-modes)
- - [Full/Advanced](#advanced)
+ - [What is it?](#what-is-it)
+ - [Why?](#why)
+ - [Who is this for?](#who-is-this-for)
+ - [How does it work?](#how-does-it-work)
+ - [Mappings:](#mappings)
+ - [GooeyParser](#gooeyparser)
+ - [Internationalization](#internationalization)
+ - [Global Configuration](#global-configuration)
+ - [Layout Customization](#layout-customization)
+ - [Run Modes](#run-modes)
+ - [Advanced](#advanced)
- [Basic](#basic)
- [No Config](#no-config)
-- [Menus](#menus)
-- [Input Validation](#input-validation)
-- [Using Dynamic Values](#using-dynamic-values)
-- [Showing Progress](#showing-progress)
-- [Customizing Icons](#customizing-icons)
-- [Packaging](#packaging)
-- [Screenshots](#screenshots)
-- [Contributing](#wanna-help)
-- [Image Credits](#image-credits)
+ - [Menus](#menus)
+ - [Input Validation](#input-validation)
+ - [Using Dynamic Values](#using-dynamic-values)
+ - [Showing Progress](#showing-progress)
+ - [Elapsed / Remaining Time](#elapsed--remaining-time)
+ - [Customizing Icons](#customizing-icons)
+ - [Packaging](#packaging)
+ - [Screenshots](#screenshots)
+ - [Wanna help?](#wanna-help)
@@ -156,16 +157,16 @@ At run-time, it parses your Python script for all references to `ArgumentParser`
Gooey does its best to choose sensible defaults based on the options it finds. Currently, `ArgumentParser._actions` are mapped to the following `WX` components.
-| Parser Action | Widget | Example |
-|:----------------------|-----------|------|
-| store | TextCtrl | |
-| store_const | CheckBox | |
-| store_true| CheckBox | |
-| store_False | CheckBox| |
-| append | TextCtrl | |
-| count| DropDown | |
-| Mutually Exclusive Group | RadioGroup |
-|choice | DropDown | |
+| Parser Action | Widget | Example |
+| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
+| store | TextCtrl | |
+| store_const | CheckBox | |
+| store_true | CheckBox | |
+| store_False | CheckBox | |
+| append | TextCtrl | |
+| count | DropDown | |
+| Mutually Exclusive Group | RadioGroup | |
+| choice | DropDown | |
### GooeyParser
@@ -199,14 +200,14 @@ However, by dropping in `GooeyParser` and supplying a `widget` name, you can dis
**Custom Widgets:**
-| Widget | Example |
-|----------------|------------------------------|
-| DirChooser, FileChooser, MultiFileChooser, FileSaver, MultiFileSaver |
|
-| DateChooser/TimeChooser | Please note that for both of these widgets the values passed to the application will always be in [ISO format](https://www.wxpython.org/Phoenix/docs/html/wx.DateTime.html#wx.DateTime.FormatISOTime) while localized values may appear in some parts of the GUI depending on end-user settings.
|
-| PasswordField | |
-| Listbox | ![image](https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/31590191-fadd06f2-b1c0-11e7-9a49-7cbf0c6d33d1.png) |
-| BlockCheckbox | ![image](https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/46922288-9296f200-cfbb-11e8-8b0d-ddde08064247.png)
The default InlineCheck box can look less than ideal if a large help text block is present. `BlockCheckbox` moves the text block to the normal position and provides a short-form `block_label` for display next to the control. Use `gooey_options.checkbox_label` to control the label text |
-| ColourChooser | |
+| Widget | Example |
+| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| DirChooser, FileChooser, MultiFileChooser, FileSaver, MultiFileSaver | |
+| DateChooser/TimeChooser | Please note that for both of these widgets the values passed to the application will always be in [ISO format](https://www.wxpython.org/Phoenix/docs/html/wx.DateTime.html#wx.DateTime.FormatISOTime) while localized values may appear in some parts of the GUI depending on end-user settings.
|
+| PasswordField | |
+| Listbox | ![image](https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/31590191-fadd06f2-b1c0-11e7-9a49-7cbf0c6d33d1.png) |
+| BlockCheckbox | ![image](https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/46922288-9296f200-cfbb-11e8-8b0d-ddde08064247.png)
The default InlineCheck box can look less than ideal if a large help text block is present. `BlockCheckbox` moves the text block to the normal position and provides a short-form `block_label` for display next to the control. Use `gooey_options.checkbox_label` to control the label text |
+| ColourChooser | |
@@ -271,6 +272,9 @@ Just about everything in Gooey's overall look and feel can be customized by pass
| progress_expr | A python expression applied to any matches found via the `progress_regex`. See: [Showing Progress](#showing-progress) for a detailed how-to |
| hide_progress_msg | Option to hide textual progress updates which match the `progress_regex`. See: [Showing Progress](#showing-progress) for a detailed how-to |
| disable_progress_bar_animation | Disable the progress bar |
+| timing_options | This contains the options for displaying time remaining and elapsed time, to be used with `progress_regex` and `progress_expr`. [Elapsed / Remaining Time](#elapsed--remaining-time). Contained as a dictionary with the options `show_time_remaining` and `hide_time_remaining_on_complete`. Eg: `timing_options={'show_time_remaining':True,'hide_time_remaining_on_complete':True}` |
+| show_time_remaining | Disable the time remaining text see [Elapsed / Remaining Time](#elapsed--remaining-time) |
+| hide_time_remaining_on_complete | Hide time remaining on complete screen see [Elapsed / Remaining Time](#elapsed--remaining-time) |
| requires_shell | Controls whether or not the `shell` argument is used when invoking your program. [More info here](https://stackoverflow.com/questions/3172470/actual-meaning-of-shell-true-in-subprocess#3172488) |
| navigation | Sets the "navigation" style of Gooey's top level window.
Options: TABBED | SIDEBAR |
| |
|
| sidebar_title | Controls the heading title above the SideBar's navigation pane. Defaults to: "Actions" |
@@ -302,9 +306,9 @@ You can achieve fairly flexible layouts with Gooey by using a few simple customi
At the highest level, you have several overall layout options controllable via various arguments to the Gooey decorator.
-| `show_sidebar=True` | `show_sidebar=False` | `navigation='TABBED'` | `tabbed_groups=True` |
-|---------------------|----------------------|----------------------|------------------------|
-| | | | |
+| `show_sidebar=True` | `show_sidebar=False` | `navigation='TABBED'` | `tabbed_groups=True` |
+| -------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
+| | | | |
**Grouping Inputs**
@@ -690,6 +694,22 @@ progress: 3/100
There are lots of options for telling Gooey about progress as your program is running. Checkout the [Gooey Examples](https://github.com/chriskiehl/GooeyExamples) repository for more detailed usage and examples!
+### Elapsed / Remaining Time
+
+Gooey also supports tracking elapsed / remaining time when progress is used! This is done in a similar manner to that of the project [tqdm](https://github.com/tqdm/tqdm). This can be enabled with `timing_options`, the `timing_options` argument takes in a dictionary with the keys `show_time_remaining` and `hide_time_remaining_on_complete`. The default behavior is True for `show_time_remaining` and False for `hide_time_remaining_on_complete`. This will only work when `progress_regex` and `progress_expr` are used.
+
+```python
+@Gooey(progress_regex=r"^progress: (?P\d+)/(?P\d+)$",
+ progress_expr="current / total * 100",
+ timing_options = {
+ 'show_time_remaining':True,
+ 'hide_time_remaining_on_complete':True,
+ })
+```
+
+
+![Elapsed/Remaining Time](https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/gooey-estimated-finish.gif)
+
--------------------------------------
@@ -723,13 +743,13 @@ Detailed step by step instructions can be found [here](http://chriskiehl.com/art
Screenshots
------------
-| Flat Layout | Column Layout |Success Screen | Error Screen | Warning Dialog |
-|-------------|---------------|---------------|--------------|----------------|
-| | | | | |
+| Flat Layout | Column Layout | Success Screen | Error Screen | Warning Dialog |
+| ----------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
+| | | | | |
-| Custom Groups | Tabbed Groups | Tabbed Navigation | Sidebar Navigation | Input Validation |
-|-------------|---------------|---------------|--------------|----------------|
-| | | | | |
+| Custom Groups | Tabbed Groups | Tabbed Navigation | Sidebar Navigation | Input Validation |
+| -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
+| | | | | |
diff --git a/gooey/gui/components/footer.py b/gooey/gui/components/footer.py
index f964c7c..adfec4b 100644
--- a/gooey/gui/components/footer.py
+++ b/gooey/gui/components/footer.py
@@ -18,6 +18,7 @@ class Footer(wx.Panel):
self.buildSpec = buildSpec
self.SetMinSize((30, 53))
+ self.SetDoubleBuffered(True)
# components
self.cancel_button = None
self.start_button = None
@@ -39,6 +40,18 @@ class Footer(wx.Panel):
self.Bind(wx.EVT_LEFT_DOWN, notifyMouseEvent)
+ def updateTimeRemaining(self,*args,**kwargs):
+ estimate_time_remaining = kwargs.get('estimatedRemaining')
+ elapsed_time_value = kwargs.get('elapsed_time')
+ if elapsed_time_value is None:
+ return
+ elif estimate_time_remaining is not None:
+ self.time_remaining_text.SetLabel(f"{elapsed_time_value}<{estimate_time_remaining}")
+ return
+ else:
+ self.time_remaining_text.SetLabel(f"{elapsed_time_value}")
+
+
def updateProgressBar(self, *args, **kwargs):
'''
value, disable_animation=False
@@ -85,6 +98,8 @@ class Footer(wx.Panel):
self.progress_bar = wx.Gauge(self, range=100)
+ self.time_remaining_text = wx.StaticText(self)
+
self.buttons = [self.cancel_button, self.start_button,
self.stop_button, self.close_button,
self.restart_button, self.edit_button]
@@ -103,7 +118,8 @@ class Footer(wx.Panel):
h_sizer.Add(self.progress_bar, 1,
wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 20)
- self.progress_bar.Hide()
+
+ h_sizer.Add(self.time_remaining_text,0,wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 20)
h_sizer.AddStretchSpacer(1)
h_sizer.Add(self.cancel_button, 0,wx.RIGHT, 20)
@@ -119,6 +135,7 @@ class Footer(wx.Panel):
self.edit_button.Hide()
self.restart_button.Hide()
self.close_button.Hide()
+ self.progress_bar.Hide()
v_sizer.AddStretchSpacer(1)
self.SetSizer(v_sizer)
diff --git a/gooey/gui/containers/application.py b/gooey/gui/containers/application.py
index 991ba2e..78d08c9 100644
--- a/gooey/gui/containers/application.py
+++ b/gooey/gui/containers/application.py
@@ -20,6 +20,7 @@ from gooey.gui.components.sidebar import Sidebar
from gooey.gui.components.tabbar import Tabbar
from gooey.gui.lang.i18n import _
from gooey.gui.processor import ProcessController
+from gooey.gui.util.time import Timing
from gooey.gui.pubsub import pub
from gooey.gui.util import wx_util
from gooey.gui.util.wx_util import transactUI
@@ -45,6 +46,7 @@ class GooeyApplication(wx.Frame):
self.footer = Footer(self, buildSpec)
self.console = Console(self, buildSpec)
self.layoutComponent()
+ self.timer = Timing(self)
self.clientRunner = ProcessController(
self.buildSpec.get('progress_regex'),
@@ -63,6 +65,7 @@ class GooeyApplication(wx.Frame):
pub.subscribe(events.CONSOLE_UPDATE, self.console.logOutput)
pub.subscribe(events.EXECUTION_COMPLETE, self.onComplete)
pub.subscribe(events.PROGRESS_UPDATE, self.footer.updateProgressBar)
+ pub.subscribe(events.TIME_UPDATE, self.footer.updateTimeRemaining)
# Top level wx close event
self.Bind(wx.EVT_CLOSE, self.onClose)
@@ -91,6 +94,7 @@ class GooeyApplication(wx.Frame):
else:
config.displayErrors()
self.Layout()
+
def onEdit(self):
@@ -236,6 +240,7 @@ class GooeyApplication(wx.Frame):
self.header.setSubtitle(self.buildSpec['program_description'])
self.footer.showButtons('cancel_button', 'start_button')
self.footer.progress_bar.Show(False)
+ self.footer.time_remaining_text.Show(False)
def showConsole(self):
@@ -246,6 +251,10 @@ class GooeyApplication(wx.Frame):
self.header.setSubtitle(_('running_msg'))
self.footer.showButtons('stop_button')
self.footer.progress_bar.Show(True)
+ self.footer.time_remaining_text.Show(False)
+ if self.buildSpec.get('timing_options')['show_time_remaining']:
+ self.timer.start()
+ self.footer.time_remaining_text.Show(True)
if not self.buildSpec['progress_regex']:
self.footer.progress_bar.Pulse()
@@ -258,6 +267,12 @@ class GooeyApplication(wx.Frame):
else ['edit_button', 'close_button'])
self.footer.showButtons(*buttons)
self.footer.progress_bar.Show(False)
+ if self.buildSpec.get('timing_options')['show_time_remaining']:
+ self.timer.stop()
+ self.footer.time_remaining_text.Show(True)
+ if self.buildSpec.get('timing_options')['hide_time_remaining_on_complete']:
+ self.footer.time_remaining_text.Show(False)
+
def showSuccess(self):
diff --git a/gooey/gui/events.py b/gooey/gui/events.py
index 47916db..bca961c 100644
--- a/gooey/gui/events.py
+++ b/gooey/gui/events.py
@@ -21,6 +21,7 @@ LIST_BOX = wx.Window.NewControlId()
CONSOLE_UPDATE = wx.Window.NewControlId()
EXECUTION_COMPLETE = wx.Window.NewControlId()
PROGRESS_UPDATE = wx.Window.NewControlId()
+TIME_UPDATE = wx.Window.NewControlId()
USER_INPUT = wx.Window.NewControlId()
diff --git a/gooey/gui/processor.py b/gooey/gui/processor.py
index c7bc756..1310cf9 100644
--- a/gooey/gui/processor.py
+++ b/gooey/gui/processor.py
@@ -13,8 +13,7 @@ from gooey.util.functional import unit, bind
class ProcessController(object):
- def __init__(self, progress_regex, progress_expr, hide_progress_msg,
- encoding, shell=True):
+ def __init__(self, progress_regex, progress_expr, hide_progress_msg,encoding, shell=True):
self._process = None
self.progress_regex = progress_regex
self.progress_expr = progress_expr
@@ -69,6 +68,7 @@ class ProcessController(object):
if not line:
break
_progress = self._extract_progress(line)
+
pub.send_message(events.PROGRESS_UPDATE, progress=_progress)
if _progress is None or self.hide_progress_msg is False:
pub.send_message(events.CONSOLE_UPDATE,
diff --git a/gooey/gui/util/functional.py b/gooey/gui/util/functional.py
index e6212fa..d0d2a70 100644
--- a/gooey/gui/util/functional.py
+++ b/gooey/gui/util/functional.py
@@ -1,6 +1,30 @@
+'''
+Utils for functional methodologies throughout Gooey
+'''
+def merge_dictionaries(x,y):
+ """
+ Merge 2 dictionaries with y taking overwriting x if a key collision is found
+ This is mainly useful for maintaining the dictionary arguments to allow for more expressive & extensible arguments.
+ https://stackoverflow.com/questions/38987/how-do-i-merge-two-dictionaries-in-a-single-expression-in-python-taking-union-o
+ Args:
+ x (dict): Input dictionary
+ y (dict): Input dictionary
+ Returns:
+ The combined dictionary of x & y with y taking preference on the occasion of key collision
+ """
+ if x is None:
+ x = {}
+ if y is None:
+ y = {}
+ try:
+ return {**x,**y}
+ except:
+ z = x.copy()
+ z.update(y)
+ return z
diff --git a/gooey/gui/util/time.py b/gooey/gui/util/time.py
new file mode 100644
index 0000000..89fb98b
--- /dev/null
+++ b/gooey/gui/util/time.py
@@ -0,0 +1,96 @@
+"""
+Module for evaluating time elapsed & time remaining from progress
+"""
+import wx
+from gooey.gui.pubsub import pub
+from gooey.gui import events
+
+class Timing(object):
+
+ def __init__(self, parent):
+ self.startTime = 0
+ self.estimatedRemaining = None
+ self.wxTimer = wx.Timer(parent)
+ self.parent = parent
+ parent.Bind(wx.EVT_TIMER, self.publishTime, self.wxTimer)
+
+ pub.subscribe(events.PROGRESS_UPDATE, self._updateEstimate)
+
+ def _updateEstimate(self, *args, **kwargs):
+ prog = kwargs.get('progress')
+ if(not prog):
+ self.estimatedRemaining = None
+ return
+ if(prog > 0):
+ self.estimatedRemaining = estimate_time_remaining(prog,self.startTime)
+
+ def publishTime(self, *args, **kwargs):
+ pub.send_message(
+ events.TIME_UPDATE,
+ start=self.startTime,
+ current=get_current_time(),
+ elapsed_time=format_interval(get_elapsed_time(self.startTime)),
+ estimatedRemaining=format_interval(self.estimatedRemaining))
+
+ def start(self):
+ self.startTime = get_current_time()
+ self.estimatedRemaining = None
+ self.wxTimer.Start()
+
+ def stop(self):
+ self.wxTimer.Stop()
+
+def format_interval(timeValue):
+ """
+ Formats a number of seconds as a clock time, [H:]MM:SS
+ Parameters
+ ----------
+ t : int
+ Number of seconds.
+ Returns
+ -------
+ out : str
+ [H:]MM:SS
+ """
+ # https://github.com/tqdm/tqdm/blob/0cd9448b2bc08125e74538a2aea6af42ee1a7b6f/tqdm/std.py#L228
+ try:
+ mins, s = divmod(int(timeValue), 60)
+ h, m = divmod(mins, 60)
+ if h:
+ return '{0:d}:{1:02d}:{2:02d}'.format(h, m, s)
+ else:
+ return '{0:02d}:{1:02d}'.format(m, s)
+ except:
+ return None
+
+def get_elapsed_time(startTime):
+ """
+ Get elapsed time in form of seconds. Provide a start time in seconds as float.
+
+ Args:
+ startTime (float): Start time to compare against in seconds.
+
+ Returns:
+ float: Time between start time and now
+ """
+ return get_current_time() - startTime
+
+def estimate_time_remaining(progress,startTime):
+ # https://github.com/tqdm/tqdm/blob/0cd9448b2bc08125e74538a2aea6af42ee1a7b6f/tqdm/std.py#L392
+ # https://github.com/tqdm/tqdm/blob/0cd9448b2bc08125e74538a2aea6af42ee1a7b6f/tqdm/std.py#L417
+ _rate = progress / get_elapsed_time(startTime)
+ return ((100 - progress) / _rate)
+
+def get_current_time():
+ """
+ Returns a float of the current time in seconds. Attempt to import perf_counter (more accurate in 3.4+), otherwise utilise timeit.
+
+ Returns:
+ float: Current time in seconds from performance counter.
+ """
+ try:
+ from time import perf_counter
+ return perf_counter()
+ except:
+ import timeit
+ return timeit.default_timer()
diff --git a/gooey/python_bindings/config_generator.py b/gooey/python_bindings/config_generator.py
index 5ee0b89..5b48a4c 100644
--- a/gooey/python_bindings/config_generator.py
+++ b/gooey/python_bindings/config_generator.py
@@ -5,6 +5,8 @@ import textwrap
from gooey.python_bindings import argparse_to_json
from gooey.gui.util.quoting import quote
from gooey.python_bindings import constants
+from gooey.python_bindings import gooey_decorator
+from gooey.gui.util.functional import merge_dictionaries
default_layout = {
'widgets': [{
@@ -69,6 +71,7 @@ def create_from_parser(parser, source_path, **kwargs):
'progress_regex': kwargs.get('progress_regex'),
'progress_expr': kwargs.get('progress_expr'),
'hide_progress_msg': kwargs.get('hide_progress_msg', False),
+ 'timing_options': merge_dictionaries(gooey_decorator.defaults.get('timing_options'),kwargs.get('timing_options')),
'disable_progress_bar_animation': kwargs.get('disable_progress_bar_animation'),
'disable_stop_button': kwargs.get('disable_stop_button'),
diff --git a/gooey/python_bindings/gooey_decorator.py b/gooey/python_bindings/gooey_decorator.py
index e8610d6..17d3f3a 100644
--- a/gooey/python_bindings/gooey_decorator.py
+++ b/gooey/python_bindings/gooey_decorator.py
@@ -48,6 +48,10 @@ defaults = {
'navigation': 'SIDEBAR', # TODO: add this to the docs
'tabbed_groups': False,
'use_cmd_args': False,
+ 'timing_options': {
+ 'show_time_remaining': False,
+ 'hide_time_remaining_on_complete': True
+ }
}
# TODO: kwargs all the things
diff --git a/gooey/tests/test_processor.py b/gooey/tests/test_processor.py
index c0a7530..0fd8666 100644
--- a/gooey/tests/test_processor.py
+++ b/gooey/tests/test_processor.py
@@ -15,7 +15,6 @@ class TestProcessor(unittest.TestCase):
processor = ProcessController(r"total: (\d+)%$", None, False, 'utf-8')
self.assertEqual(processor._extract_progress(b'my cool total: 100%'), 100)
-
def test_extract_progress_returns_none_if_no_regex_supplied(self):
processor = ProcessController(None, None, False, 'utf-8')
self.assertIsNone(processor._extract_progress(b'Total progress: 100%'))
@@ -29,14 +28,12 @@ class TestProcessor(unittest.TestCase):
def test_eval_progress(self):
# given a match in the string, should eval the result
regex = r'(\d+)/(\d+)$'
- processor = ProcessController(regex, r'x[0] / x[1]', False, 'utf-8')
+ processor = ProcessController(regex, r'x[0] / x[1]', False,False, 'utf-8')
match = re.search(regex, '50/50')
self.assertEqual(processor._eval_progress(match), 1.0)
-
-
def test_eval_progress_returns_none_on_failure(self):
# given a match in the string, should eval the result
regex = r'(\d+)/(\d+)$'
- processor = ProcessController(regex, r'x[0] *^/* x[1]', False, 'utf-8')
+ processor = ProcessController(regex, r'x[0] *^/* x[1]', False, False,'utf-8')
match = re.search(regex, '50/50')
self.assertIsNone(processor._eval_progress(match))
diff --git a/gooey/tests/test_time_remaining.py b/gooey/tests/test_time_remaining.py
new file mode 100644
index 0000000..6a26089
--- /dev/null
+++ b/gooey/tests/test_time_remaining.py
@@ -0,0 +1,63 @@
+import time
+import unittest
+from argparse import ArgumentParser
+from itertools import *
+
+from tests.harness import instrumentGooey
+
+class TestFooterTimeRemaining(unittest.TestCase):
+
+ def make_parser(self):
+ parser = ArgumentParser(description='description')
+ return parser
+
+ def test_time_remaining_visibility(self):
+ for testdata in self.testcases():
+ with self.subTest(testdata):
+ with instrumentGooey(self.make_parser(), timing_options=testdata) as (app, gooeyApp):
+
+ gooeyApp.showConsole()
+ footer = gooeyApp.footer
+
+
+ self.assertEqual(
+ footer.time_remaining_text.Shown,
+ testdata.get('show_time_remaining',False)
+ )
+
+ def test_time_remaining_visibility_on_complete(self):
+ for testdata in self.testcases():
+ with self.subTest(testdata):
+ with instrumentGooey(self.make_parser(), timing_options=testdata) as (app, gooeyApp):
+
+ gooeyApp.showComplete()
+ footer = gooeyApp.footer
+
+
+ if not testdata.get('show_time_remaining') and testdata:
+ self.assertEqual(
+ footer.time_remaining_text.Shown,
+ testdata.get('hide_time_remaining_on_complete',True)
+ )
+ else:
+ return True
+
+ def testcases(self):
+ """
+ Generate a powerset of all possible combinations of
+ the header parameters (empty, some present, all present, all combos)
+ """
+ iterable = product(['show_time_remaining', 'hide_time_remaining_on_complete'], [True, False])
+ allCombinations = list(powerset(iterable))
+ return [{k: v for k,v in args}
+ for args in allCombinations]
+
+
+def powerset(iterable):
+ "powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)"
+ s = list(iterable)
+ return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))
+
+
+if __name__ == '__main__':
+ unittest.main()
\ No newline at end of file
diff --git a/gooey/tests/test_util.py b/gooey/tests/test_util.py
new file mode 100644
index 0000000..b0f7e36
--- /dev/null
+++ b/gooey/tests/test_util.py
@@ -0,0 +1,32 @@
+import re
+import unittest
+
+from gooey.gui.util.time import get_current_time,get_elapsed_time,estimate_time_remaining,format_interval
+
+
+class TestTimeUtil(unittest.TestCase):
+ def test_time_elapsed(self):
+ # Check that time elapsed is greater than zero
+ _start_time = get_current_time()
+ elapsed = get_elapsed_time(_start_time)
+ self.assertGreater(elapsed,0)
+
+ def test_time_remaining(self):
+ # Check that time elapsed is greater than zero
+ _start_time = get_current_time()
+ remaining = estimate_time_remaining(30,_start_time)
+ self.assertGreater(remaining,0)
+
+ def test_current_time(self):
+ # Test that current time is greater than zero
+ _start_time = get_current_time()
+ self.assertGreater(_start_time,0)
+
+
+ def test_format_interval(self):
+ # Test same as TQDM https://github.com/tqdm/tqdm/blob/0cd9448b2bc08125e74538a2aea6af42ee1a7b6f/tqdm/tests/tests_tqdm.py#L234
+ # but in unittest form
+
+ self.assertEqual(format_interval(60), '01:00')
+ self.assertEqual(format_interval(6160), '1:42:40')
+ self.assertEqual(format_interval(238113), '66:08:33')