Source code for psprint.printer

#!/usr/bin/env python3
# -*- coding:utf-8; mode:python -*-
#
# Copyright 2020 Pradyumna Paranjape
# This file is part of psprint.
#
# psprint is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# psprint is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with psprint.  If not, see <https://www.gnu.org/licenses/>.
#
'''
Information- Prepended Print object
'''

import os
import sys
from typing import Dict, List, Optional, Union

import yaml

from .ansi import ANSI
from .errors import BadMark
from .mark_types import InfoMark


[docs]class PrintSpace(): ''' Fancy Print class that also prints the type of message Args: config: path to default configuration file (shipped) Attributes: pref_max: int: maximum length of prefix string info_style; dict: pre-defined prefix styles info_index: list: keys of `info_style` mapped to int print_kwargs: dict : library of kwargs_accepted by print_function switches: dict: user-customizations: pad, short, bland, disabled pad: bool: prefix is padded to start text at the same level short: bool: display short, 1 character- prefix bland: bool: do not show ANSI color/styles for prefix/text disabled: bool: behave like python default print_function ''' def __init__(self, config: os.PathLike) -> None: # Standard info styles self.switches = { 'pad': False, 'short': False, 'bland': False, 'disabled': False } self.print_kwargs = { 'file': sys.stdout, 'sep': "\t", 'end': "\n", 'flush': False } self.pref_max = None self.info_style: Dict[str, InfoMark] = {} self.info_index: List[str] = [] self.set_opts(config=config)
[docs] def set_opts(self, config: os.PathLike = None) -> None: ''' Configure from rcfile Args: rcfile: .psprintrc file to read Raises: BadMark ''' if config is None: return info_index: Optional[Dict[str, str]] = None with open(config, 'r') as rcfile: conf: Dict[str, dict] = yaml.safe_load(rcfile) for mark, settings in conf.items(): if mark == "FLAGS": # switches / flags self.pref_max = settings.get("pref_max_len", None) for b_sw in self.switches: self.switches[b_sw] = settings.get(b_sw, False) self.print_kwargs['sep'] = settings.get("sep", "\t") self.print_kwargs['end'] = settings.get("end", "\n") self.print_kwargs['flush'] = settings.get("flush", False) fname = settings.get("file", None) # Discouraged if fname is not None: # pragma: no cover self.print_kwargs['file'] = open(fname, "a") elif mark == 'order': info_index = settings else: # Mark definition try: self.edit_style(mark=mark, **settings) except (ValueError, TypeError): raise BadMark(str(mark), rcfile.name) from None if info_index is not None: self.info_index = list( filter(lambda x: x in self.info_style, info_index))
[docs] def edit_style(self, pref: str, index_int: int = None, mark: str = None, **kwargs) -> str: ''' Edit loaded style Args: pref: str: prefix string long [length < 10 characters] index_int: Index number that will call this InfoMark mark: Mark string that will call this ``InfoMark`` **kwargs: * pref_s: str: prefix string short [1 character] * code: * color: {[0-15],[[l]krgybmcw],[[light] <color_name>]} * gloss: {[0-3],[rcdb],{reset,normal,dim,bright}} * for- * pref_color: color of of prefix * pref_gloss: gloss of prefix * pref_bgcol: background color of prefix * text_color: color of of text * text_gloss: gloss of text * text_bgcol: background color of text Returns Summary of new (updated) ``PrintSpace`` ''' # correct pref if mark is None: mark = pref[:4] kwargs['pref'] = pref if index_int is None or \ not 0 <= index_int <= len(self.info_index): self.info_index.append(mark) else: self.info_index.insert(index_int, mark) self.info_style[mark] = InfoMark(pref_max=self.pref_max, **kwargs) return str(self)
[docs] def remove_style(self, mark: str = None, index_int: int = None) -> str: ''' Args: mark: is popped out of defined styles index_int: is used to locate index_str if it is not provided Returns Summary of new (updated) ``PrintSpace`` ''' if mark is None: if index_int is not None: if index_int < len(self.info_style): mark = self.info_index.pop(index_int) if mark is None: raise SyntaxError(''' At least one of ``mark`` and ``index_int`` should be provided ''') del self.info_style[mark] return str(self)
def __repr__(self) -> str: ''' Returns: Formatted summary of info_style ''' outstr = '\npref\tlong\tshort\ttext\n\n' outstr += "\n".join((f"{k}:{v}" for k, v in self.info_style.items())) return outstr def _which_mark(self, mark: Union[str, int, InfoMark] = None, **kwargs) -> InfoMark: ''' Define a mark based on arguments supplied * may be a pre-defined mark OR * mark defined on the fly Args: mark: mark that identifies a defined prefix **kwargs: * pref: str: prefix string long [length < 10 characters] * pref_s: str: prefix string short [1 character] * code: * color: {[0-15],[[l]krgybmcw],[[light] <color_name>]} * gloss: {[0-3],[rcdb],{reset,normal,dim,bright}} * for- * pref_color: color of of prefix * pref_gloss: gloss of prefix * pref_bgcol: background color of prefix * text_color: color of of text * text_gloss: gloss of text * text_bgcol: background color of text ''' base_mark: InfoMark = self.info_style['cont'] if mark is not None: # mark was supplied if isinstance(mark, InfoMark): # ready-made mark was served base_mark = mark elif isinstance(mark, int): # mark supplied as index int if not 0 <= mark < len(self.info_index): mark = 0 base_mark = self.info_style[self.info_index[mark]] elif isinstance(mark, str): # mark named key supplied base_mark = self.info_style.get(mark) or base_mark else: raise BadMark(mark=str(mark), config="**kwargs") if any(arg in kwargs for arg in [ 'pref', 'pref_s', 'pref_color', 'pref_gloss', 'pref_bgcol', 'text_color', 'text_gloss', 'text_bgcol', ]): return InfoMark(parent=base_mark, pref_max=self.pref_max, **kwargs) return base_mark
[docs] def psfmt(self, *args, mark: Union[str, int, InfoMark] = None, sep: str = None, **kwargs) -> Union[List[str], str]: """ Prefix String represenattion. Args: *args: passed to print_function for printing mark: pre-declared `InfoMark` defaults: * cont or 0 or anything else: nothing * info or 1: [INFO] * act or 2: [ACTION] * list or 3: [LIST] * warn or 4: [WARNING] * error:or 5: [ERROR] * bug: or 6 [DEBUG] * `Other marks defined in .psprintrc` sep: If not ``None``, return `*args` joined by separator. **kwargs: * pref: str: prefix string long [length < 10 characters] * pref_s: str: prefix string short [1 character] * code: * color: {[0-15],[[l]krgybmcw],[[light] <color_name>]} * gloss: {[0-3],[rcdb],{reset,normal,dim,bright}} * for- * pref_color: color of of prefix * pref_gloss: gloss of prefix * pref_bgcol: background color of prefix * text_color: color of of text * text_gloss: gloss of text * text_bgcol: background color of text * pad: bool: prefix is padded to start text at the same level * short: bool: display short, 1 character- prefix * bland: bool: do not show ANSI color/styles for prefix/text * disabled: bool: behave like python default print_function Raises: BadMark: mark couldn't be interpreted Returns: * PSPRINT-like represented args. When `these` args is printed using standard print, PSPRINT-like output appears. * If a sep is provided, it is used to join args and return a string """ switches = { key: { **self.switches, **kwargs }[key] for key in self.switches } args_l = list(args) # typecast if switches['disabled'] or not args: if sep is not None: return sep.join(args_l) return args_l mark = self._which_mark(mark=mark, **kwargs) # add prefix to *args[0] if not switches.get('bland'): args_l[0] = str(mark.text) + str(args_l[0]) args_l[-1] = str(args_l[-1]) + ANSI.RESET_ALL args_l[0] = mark.pref.to_str(**switches) + str(args_l[0]) if sep is not None: return sep.join(args_l) return args_l
[docs] def psprint(self, *args, mark: Union[str, int, InfoMark] = None, **kwargs) -> None: """ Prefix String PRINT Args: *args: passed to print_function for printing mark: pre-declared `InfoMark` defaults: * cont or 0 or anything else: nothing * info or 1: [INFO] * act or 2: [ACTION] * list or 3: [LIST] * warn or 4: [WARNING] * error:or 5: [ERROR] * bug: or 6 [DEBUG] * `Other marks defined in .psprintrc` **kwargs: * pref: str: prefix string long [length < 10 characters] * pref_s: str: prefix string short [1 character] * code: * color: {[0-15],[[l]krgybmcw],[[light] <color_name>]} * gloss: {[0-3],[rcdb],{reset,normal,dim,bright}} * for- * pref_color: color of of prefix * pref_gloss: gloss of prefix * pref_bgcol: background color of prefix * text_color: color of of text * text_gloss: gloss of text * text_bgcol: background color of text * pad: bool: prefix is padded to start text at the same level * short: bool: display short, 1 character- prefix * bland: bool: do not show ANSI color/styles for prefix/text * disabled: bool: behave like python default print_function * file: IO: passed to print function * sep: str: passed to print function * end: str: passed to print function * flush: bool: passed to print function Raises: BadMark: mark couldn't be interpreted """ # Extract print-kwargs print_kwargs = { key: { **self.print_kwargs, **kwargs }[key] for key in self.print_kwargs } print(*self.psfmt(*args, mark=mark, **kwargs), **print_kwargs)