Source code for progressive.tree

from __future__ import division

from copy import deepcopy

from progressive.bar import Bar
from progressive.cursor import Cursor
from progressive.util import floor, ensure, merge_dicts
from progressive.exceptions import LengthOverflowError


[docs]class Value(object): """Container class for use with ``BarDescriptor`` Should be used for ``value`` argument when initializing ``BarDescriptor``, e.g., ``BarDescriptor(type=..., value=Value(10))`` """ def __init__(self, val=0): self.value = val @property def value(self): return self._value @value.setter def value(self, val): self._value = floor(val)
[docs]class BarDescriptor(dict): """Bar descriptor To be used in leaf of a tree describing a hierarchy for ``ProgressTree``, e.g.,: tree = {"Job": {"Task1": BarDescriptor(...)}, {"Task2": {"Subtask1": BarDescriptor(...)}, }, } :type type: Bar|subclass of Bar :param type: The type of Bar to use to display that leaf :type value: Value :param value: Amount to fill the progress bar vs. its max value :type args: list :param args: A list of args to instantiate ``type`` with :type kwargs: dict :param kwargs: A dict of kwargs to instantiate ``type`` with """
[docs]class ProgressTree(object): """Progress display for trees For drawing a hierarchical progress view from a tree :type term: NoneType|blessings.Terminal :param term: Terminal instance; if not given, will be created by the class :type indent: int :param indent: The amount of indentation between each level in hierarchy """ def __init__(self, term=None, indent=4): self.cursor = Cursor(term) self.indent = indent ################## # Public Methods # ##################
[docs] def draw(self, tree, bar_desc=None, save_cursor=True, flush=True): """Draw ``tree`` to the terminal :type tree: dict :param tree: ``tree`` should be a tree representing a hierarchy; each key should be a string describing that hierarchy level and value should also be ``dict`` except for leaves which should be ``BarDescriptors``. See ``BarDescriptor`` for a tree example. :type bar_desc: BarDescriptor|NoneType :param bar_desc: For describing non-leaf bars in that will be drawn from ``tree``; certain attributes such as ``value`` and ``kwargs["max_value"]`` will of course be overridden if provided. :type flush: bool :param flush: If this is set, output written will be flushed :type save_cursor: bool :param save_cursor: If this is set, cursor location will be saved before drawing; this will OVERWRITE a previous save, so be sure to set this accordingly (to your needs). """ if save_cursor: self.cursor.save() tree = deepcopy(tree) # TODO: Automatically collapse hierarchy so something # will always be displayable (well, unless the top-level) # contains too many to display lines_required = self.lines_required(tree) ensure(lines_required <= self.cursor.term.height, LengthOverflowError, "Terminal is not long ({} rows) enough to fit all bars " "({} rows).".format(self.cursor.term.height, lines_required)) bar_desc = BarDescriptor(type=Bar) if not bar_desc else bar_desc self._calculate_values(tree, bar_desc) self._draw(tree) if flush: self.cursor.flush()
[docs] def make_room(self, tree): """Clear lines in terminal below current cursor position as required This is important to do before drawing to ensure sufficient room at the bottom of your terminal. :type tree: dict :param tree: tree as described in ``BarDescriptor`` """ lines_req = self.lines_required(tree) self.cursor.clear_lines(lines_req)
[docs] def lines_required(self, tree, count=0): """Calculate number of lines required to draw ``tree``""" if all([ isinstance(tree, dict), type(tree) != BarDescriptor ]): return sum(self.lines_required(v, count=count) for v in tree.values()) + 2 elif isinstance(tree, BarDescriptor): if tree.get("kwargs", {}).get("title_pos") in ["left", "right"]: return 1 else: return 2
################### # Private Methods # ################### def _calculate_values(self, tree, bar_d): """Calculate values for drawing bars of non-leafs in ``tree`` Recurses through ``tree``, replaces ``dict``s with ``(BarDescriptor, dict)`` so ``ProgressTree._draw`` can use the ``BarDescriptor``s to draw the tree """ if all([ isinstance(tree, dict), type(tree) != BarDescriptor ]): # Calculate value and max_value max_val = 0 value = 0 for k in tree: # Get descriptor by recursing bar_desc = self._calculate_values(tree[k], bar_d) # Reassign to tuple of (new descriptor, tree below) tree[k] = (bar_desc, tree[k]) value += bar_desc["value"].value max_val += bar_desc.get("kwargs", {}).get("max_value", 100) # Merge in values from ``bar_d`` before returning descriptor kwargs = merge_dicts( [bar_d.get("kwargs", {}), dict(max_value=max_val)], deepcopy=True ) ret_d = merge_dicts( [bar_d, dict(value=Value(floor(value)), kwargs=kwargs)], deepcopy=True ) return BarDescriptor(ret_d) elif isinstance(tree, BarDescriptor): return tree else: raise TypeError("Unexpected type {}".format(type(tree))) def _draw(self, tree, indent=0): """Recurse through ``tree`` and draw all nodes""" if all([ isinstance(tree, dict), type(tree) != BarDescriptor ]): for k, v in sorted(tree.items()): bar_desc, subdict = v[0], v[1] args = [self.cursor.term] + bar_desc.get("args", []) kwargs = dict(title_pos="above", indent=indent, title=k) kwargs.update(bar_desc.get("kwargs", {})) b = Bar(*args, **kwargs) b.draw(value=bar_desc["value"].value, flush=False) self._draw(subdict, indent=indent + self.indent)