Skip to content
Snippets Groups Projects
Select Git revision
  • 81c6efcfcd8b286bf6019859df88c110a7bd4dd7
  • master default
  • dev
  • ci/windows
  • feature/installer
  • release/1.0.2
  • feature/tox
  • pydoc
  • jenkins
  • issue10
  • json-config
  • v2.0.0
  • v2.0.0-beta.5
  • v1.0.2
  • v2.0.0-beta.4
  • v2.0.0-beta.3
  • v1.0.1
  • v1.0.0
18 results

test_pycgtool.py

Blame
  • interface.py 8.40 KiB
    """
    This module contains classes for interaction at the terminal.
    """
    import collections
    import curses
    import curses.textpad
    import time
    
    
    class Options:
        """
        Class to hold program options not specified at the initial command line.
    
        Values can be queried by indexing as a dictionary or by attribute.  Iterable.
        """
        def __init__(self, default, args=None):
            """
            Create Options instance from iterable of keys and default values.
    
            :param default: Iterable of key, default value pairs (e.g. list of tuples)
            :param args: Optional program arguments from Argparse, will be displayed in interactive mode
            """
            self._dict = collections.OrderedDict()
            for key, val in default:
                try:
                    val = val.lower()
                except AttributeError:
                    pass
    
                self._dict[key.lower()] = (val, type(val))
    
            # Allow to carry options from argparse
            self.args = args
    
        def __getattr__(self, attr):
            return self._dict[attr.lower()][0]
    
        def __repr__(self):
            res = "[" + ", ".join((str((key, val[0])) for key, val in self._dict.items())) + "]"
            return res
    
        def __iter__(self):
            return iter(((key, val[0]) for key, val in self._dict.items()))
    
        def __len__(self):
            return len(self._dict)
    
        def __getitem__(self, item):
            try:
                return self._dict[item]
            except KeyError:
                try:
                    opt = list(self._dict.keys())[item]
                    return self._dict[opt][0]
                except TypeError:
                    raise TypeError("Must access Options using either a string or an integer")
    
        def set(self, opt, val):
            """
            Set an argument by name.
    
            :param opt: Option to set
            :param val: Value to set option to
            """
            opt = opt.lower()
            try:
                val = val.lower()
            except AttributeError:
                pass
            _type = self._dict[opt][1]
    
            if _type is not type(val):
                if _type is bool:
                    self._dict[opt] = (_truthy(val), bool)
                else:
                    self._dict[opt] = (_type(val), _type)
            else:
                self._dict[opt] = (val, _type)
    
        def _set_by_num(self, opt_num, val):
            """
            Set an argument if only its position in sequence is known.
            For use in Options._inter.
    
            :param opt_num: Sequence number of option to set
            :param val: Value to set option to
            """
            opt = list(self._dict.keys())[opt_num]
            self.set(opt, val)
    
        def toggle_boolean(self, opt):
            """
            Toggle a boolean argument by name.
    
            :param opt: Option to toggle
            """
            entry = self._dict[opt]
            if entry[1] is bool:
                self._dict[opt] = (not entry[0], entry[1])
            else:
                raise TypeError("Only boolean options can be toggled")
    
        def _toggle_boolean_by_num(self, opt_num):
            """
            Toggle a boolean argument if only its position in sequence is known.
            For use in Options._inter.
    
            :param opt_num: Sequence number of option to toggle
            """
            opt = list(self._dict.keys())[opt_num]
            self.toggle_boolean(opt)
    
        def interactive(self):
            """
            Read options in interactive terminal mode using curses.
            """
            curses.wrapper(self._inter)
    
        def _inter(self, stdscr):
            """
            Read options in interactive terminal mode using curses.
    
            :param stdscr: Curses window to use as interface
            """
            stdscr.clear()
            if self.args is not None:
                stdscr.addstr(1, 1, "Using GRO: {0}".format(self.args.gro))
                stdscr.addstr(2, 1, "Using XTC: {0}".format(self.args.xtc))
            stdscr.addstr(4, 1, "Press q to proceed")
            stdscr.box()
            stdscr.refresh()
    
            nrows = len(self)
    
            errscr = stdscr.derwin(3, curses.COLS - 3, nrows + 8, 1)
            errscr.border()
    
            window_config = stdscr.derwin(nrows + 2, curses.COLS - 3, 5, 1)
            window_config.box()
            window_config.refresh()
            window_keys = window_config.derwin(nrows, 20, 1, 0)
            window_config.vline(1, 18, curses.ACS_VLINE, nrows)
            window_vals = window_config.derwin(nrows, curses.COLS - 24, 1, 20)
            text_edit_wins = []
            text_inputs = []
    
            for i, (key, value) in enumerate(self):
                window_keys.addstr(i, 0, key)
                text_edit_wins.append(window_vals.derwin(1, 30, i, 0))
                text_edit_wins[-1].addstr(0, 0, str(value))
                text_inputs.append(curses.textpad.Textbox(text_edit_wins[-1]))
    
            stdscr.refresh()
            window_keys.refresh()
            for window in text_edit_wins:
                window.refresh()
    
            pos = 0
            move = {"KEY_UP": lambda x: (x - 1) % nrows,
                    "KEY_DOWN": lambda x: (x + 1) % nrows,
                    "KEY_LEFT": lambda x: x,
                    "KEY_RIGHT": lambda x: x}
    
            while True:
                key = text_edit_wins[pos].getkey(0, 0)
                errscr.erase()
                if key in move:
                    pos = move[key](pos)
                if key == "\n":
                    if type(self[pos]) is bool:
                        self._toggle_boolean_by_num(pos)
                    else:
                        val = text_inputs[pos].edit().strip()
                        try:
                            self._set_by_num(pos, val)
                        except ValueError:
                            errscr.addstr(0, 0, "Invalid value '{0}' for option".format(val))
                            errscr.addstr(1, 0, "Value has been reset".format(val))
    
                    text_edit_wins[pos].erase()
                    text_edit_wins[pos].addstr(0, 0, str(self[pos]))
                    text_edit_wins[pos].refresh()
    
                errscr.refresh()
                if key == "q":
                    break
    
    
    def _truthy(string):
        """
        Evaluate a string as True or False in the natural way.
    
        :param string: String to evaluate
        :return: True or False
        """
        truthy_strings = ("yes", "y", "on", "true", "t", "1")
        falsey_strings = ("no", "n", "off", "false", "f", "0")
    
        string = string.lower().strip()
        if string in truthy_strings:
            return True
        elif string in falsey_strings:
            return False
        else:
            raise ValueError("Value '{0}' could not be converted to boolean".format(string))
    
    
    class Progress:
        """
        Display a progress bar during the main loop of a program.
        """
    
        def __init__(self, maxits, length=20, prewhile=None, postwhile=None, quiet=False):
            """
            Return progress bar instance to handle printing of a progress bar within loops.
    
            :param maxits: Expected number of iterations
            :param length: Length of progress bar in characters
            :param prewhile: Function to check before each iteration, stops if False
            :param postwhile: Function to check after each iteration, stops if False
            :param quiet: Skip printing of progress bar - for testing
            """
            self._maxits = maxits
            self._length = length
            self._prewhile = prewhile
            self._postwhile = postwhile
            self._quiet = quiet
            self._its = 0
            self._start_time = time.clock()
    
        def __iter__(self):
            return self
    
        def __next__(self):
            """
            Allow iteration over Progress while testing prewhile and postwhile conditions.
    
            :return: Iteration number
            """
            if self._postwhile is not None and self._its > 0 and not self._postwhile():
                self._stop()
    
            if self._prewhile is not None and not self._prewhile():
                self._stop()
    
            self._its += 1
            if self._its % 10 == 0 and not self._quiet:
                self._display()
    
            if self._its >= self._maxits:
                self._stop()
    
            return self._its
    
        def _stop(self):
            if not self._quiet:
                done = int(self._length * (self._its / self._maxits))
                left = self._length - done
                time_taken = int(time.clock() - self._start_time)
                print("{0} [".format(self._its) + done * "#" + left * "-" + "] {0} took {1}s".format(self._maxits, time_taken))
            raise StopIteration
    
        def _display(self):
            done = int(self._length * (self._its / self._maxits))
            left = self._length - done
            time_remain = int((time.clock() - self._start_time) * ((self._maxits - self._its) / self._its))
            print("{0} [".format(self._its) + done * "#" + left * "-" + "] {0} {1}s left".format(self._maxits, time_remain), end="\r")