def _get_format_callable(term, color, back_color): """Get string-coloring callable Get callable for string output using ``color`` on ``back_color`` on ``term`` :param term: blessings.Terminal instance :param color: Color that callable will color the string it's passed :param back_color: Back color for the string :returns: callable(s: str) -> str """ if isinstance(color, str): ensure( any(isinstance(back_color, t) for t in [str, type(None)]), TypeError, "back_color must be a str or NoneType" ) if back_color: return getattr(term, "_".join( [color, "on", back_color] )) elif back_color is None: return getattr(term, color) elif isinstance(color, int): return term.on_color(color) else: raise TypeError("Invalid type {} for color".format( type(color) ))
def _write(self, s, s_length=None, flush=False, ignore_overflow=False, err_msg=None): """Write ``s`` :type s: str|unicode :param s: String to write :param s_length: Custom length of ``s`` :param flush: Set this to flush the terminal stream after writing :param ignore_overflow: Set this to ignore if s will exceed the terminal's width :param err_msg: The error message given to WidthOverflowError if it is triggered """ if not ignore_overflow: s_length = len(s) if s_length is None else s_length if err_msg is None: err_msg = ( "Terminal has {} columns; attempted to write " "a string {} of length {}.".format( self.columns, repr(s), s_length) ) ensure(s_length <= self.columns, WidthOverflowError, err_msg) self.cursor.write(s) if flush: self.cursor.flush()
def _write(self, s, s_length=None, flush=False, ignore_overflow=False, err_msg=None): """Write ``s`` :type s: str|unicode :param s: String to write :param s_length: Custom length of ``s`` :param flush: Set this to flush the terminal stream after writing :param ignore_overflow: Set this to ignore if s will exceed the terminal's width :param err_msg: The error message given to WidthOverflowError if it is triggered """ if not ignore_overflow: s_length = len(s) if s_length is None else s_length if err_msg is None: err_msg = ("Terminal has {} columns; attempted to write " "a string {} of length {}.".format( self.columns, repr(s), s_length)) ensure(s_length <= self.columns, WidthOverflowError, err_msg) self.cursor.write(s) if flush: self.cursor.flush()
def _supports_colors(term, raise_err, colors): """Check if ``term`` supports ``colors`` :raises ColorUnsupportedError: This is raised if ``raise_err`` is ``False`` and a color in ``colors`` is unsupported by ``term`` :type raise_err: bool :param raise_err: Set to ``False`` to return a ``bool`` indicating color support rather than raising ColorUnsupportedError :type colors: [str, ...] """ for color in colors: try: if isinstance(color, str): req_colors = 16 if "bright" in color else 8 ensure(term.number_of_colors >= req_colors, ColorUnsupportedError, "{} is unsupported by your terminal.".format(color)) elif isinstance(color, int): ensure(term.number_of_colors >= color, ColorUnsupportedError, "{} is unsupported by your terminal.".format(color)) except ColorUnsupportedError as e: if raise_err: raise e else: return False else: return True
def _supports_colors(term, raise_err, colors): """Check if ``term`` supports ``colors`` :raises ColorUnsupportedError: This is raised if ``raise_err`` is ``False`` and a color in ``colors`` is unsupported by ``term`` :type raise_err: bool :param raise_err: Set to ``False`` to return a ``bool`` indicating color support rather than raising ColorUnsupportedError :type colors: [str, ...] """ for color in colors: try: if isinstance(color, str): req_colors = 16 if "bright" in color else 8 ensure(term.number_of_colors >= req_colors, ColorUnsupportedError, "{} is unsupported by your terminal.".format(color)) elif isinstance(color, int): ensure(term.number_of_colors >= color, ColorUnsupportedError, "{} is unsupported by your terminal.".format(color)) except ColorUnsupportedError as e: if raise_err: raise e else: return False else: return True
def _get_format_callable(term, color, back_color): """Get string-coloring callable Get callable for string output using ``color`` on ``back_color`` on ``term`` :param term: blessings.Terminal instance :param color: Color that callable will color the string it's passed :param back_color: Back color for the string :returns: callable(s: str) -> str """ if isinstance(color, str): ensure(any(isinstance(back_color, t) for t in [str, type(None)]), TypeError, "back_color must be a str or NoneType") if back_color: return getattr(term, "_".join([color, "on", back_color])) elif back_color is None: return getattr(term, color) elif isinstance(color, int): return term.on_color(color) else: raise TypeError("Invalid type {} for color".format(type(color)))
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()
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()
def max_width(self): """Get maximum width of progress bar :rtype: int :returns: Maximum column width of progress bar """ value, unit = float(self._width_str[:-1]), self._width_str[-1] ensure(unit in ["c", "%"], ValueError, "Width unit must be either 'c' or '%'") if unit == "c": ensure(value <= self.columns, ValueError, "Terminal only has {} columns, cannot draw " "bar of size {}.".format(self.columns, value)) retval = value else: # unit == "%" ensure(0 < value <= 100, ValueError, "value=={} does not satisfy 0 < value <= 100".format(value)) dec = value / 100 retval = dec * self.columns return floor(retval)
def max_width(self): """Get maximum width of progress bar :rtype: int :returns: Maximum column width of progress bar """ value, unit = float(self._width_str[:-1]), self._width_str[-1] ensure(unit in ["c", "%"], ValueError, "Width unit must be either 'c' or '%'") if unit == "c": ensure( value <= self.columns, ValueError, "Terminal only has {} columns, cannot draw " "bar of size {}.".format(self.columns, value)) retval = value else: # unit == "%" ensure(0 < value <= 100, ValueError, "value=={} does not satisfy 0 < value <= 100".format(value)) dec = value / 100 retval = dec * self.columns return floor(retval)
def __init__( self, term=None, max_value=100, width="25%", title_pos="left", title="Progress", num_rep="fraction", indent=0, filled_color=2, empty_color=7, back_color=None, filled_char=u' ', empty_char=u' ', start_char=u'', end_char=u'', fallback=True, fallback_empty_char=u'◯', fallback_filled_char=u'◉', force_color=None ): self.cursor = Cursor(term) self.term = self.cursor.term self._measure_terminal() self._width_str = width self._max_value = max_value ensure(title_pos in ["left", "right", "above", "below"], ValueError, "Invalid choice for title position.") self._title_pos = title_pos self._title = title ensure(num_rep in ["fraction", "percentage"], ValueError, "num_rep must be either 'fraction' or 'percentage'.") self._num_rep = num_rep ensure(indent < self.columns, ValueError, "Indent must be smaller than terminal width.") self._indent = indent self._start_char = start_char self._end_char = end_char # Setup callables and characters depending on if terminal has # has color support if force_color is not None: supports_colors = force_color else: supports_colors = self._supports_colors( term=self.term, raise_err=not fallback, colors=(filled_color, empty_color) ) if supports_colors: self._filled_char = filled_char self._empty_char = empty_char self._filled = self._get_format_callable( term=self.term, color=filled_color, back_color=back_color ) self._empty = self._get_format_callable( term=self.term, color=empty_color, back_color=back_color ) else: self._empty_char = fallback_empty_char self._filled_char = fallback_filled_char self._filled = self._empty = lambda s: s ensure(self.full_line_width <= self.columns, WidthOverflowError, "Attempting to initialize Bar with full_line_width {}; " "terminal has width of only {}.".format( self.full_line_width, self.columns))
def __init__(self, term=None, max_value=100, width="25%", title_pos="left", title="Progress", num_rep="fraction", indent=0, filled_color=2, empty_color=7, back_color=None, filled_char=u' ', empty_char=u' ', start_char=u'', end_char=u'', fallback=True, fallback_empty_char=u'◯', fallback_filled_char=u'◉', force_color=None): self.cursor = Cursor(term) self.term = self.cursor.term self._measure_terminal() self._width_str = width self._max_value = max_value ensure(title_pos in ["left", "right", "above", "below"], ValueError, "Invalid choice for title position.") self._title_pos = title_pos self._title = title ensure(num_rep in ["fraction", "percentage"], ValueError, "num_rep must be either 'fraction' or 'percentage'.") self._num_rep = num_rep ensure(indent < self.columns, ValueError, "Indent must be smaller than terminal width.") self._indent = indent self._start_char = start_char self._end_char = end_char # Setup callables and characters depending on if terminal has # has color support if force_color is not None: supports_colors = force_color else: supports_colors = self._supports_colors(term=self.term, raise_err=not fallback, colors=(filled_color, empty_color)) if supports_colors: self._filled_char = filled_char self._empty_char = empty_char self._filled = self._get_format_callable(term=self.term, color=filled_color, back_color=back_color) self._empty = self._get_format_callable(term=self.term, color=empty_color, back_color=back_color) else: self._empty_char = fallback_empty_char self._filled_char = fallback_filled_char self._filled = self._empty = lambda s: s ensure( self.full_line_width <= self.columns, WidthOverflowError, "Attempting to initialize Bar with full_line_width {}; " "terminal has width of only {}.".format(self.full_line_width, self.columns))