Source code for pysisyphus.TablePrinter

import logging
import textwrap

import pyparsing as pp


lit = pp.Literal
start = lit("{:")
end = lit("}")
alignment = lit(">") | lit("<") | lit("^")
sign = lit("+") | lit("-") | lit(" ")
int_num = pp.Word(pp.nums).set_parse_action(lambda s, l, t: int(t[0]))
width = int_num("width")
precision = pp.Suppress(lit(".")) + int_num("precision")
type_ = (
    lit("s")
    | lit("b")
    | lit("c")
    | lit("d")
    | lit("o")
    | lit("x")
    | lit("X")
    | lit("e")
    | lit("E")
    | lit("f")
    | lit("F")
    | lit("g")
    | lit("G")
    | lit("n")
    | lit("%")
)

fparser = (
    start
    + pp.Optional(alignment)("alignment")
    + pp.Optional(sign)
    + pp.Optional(width)
    + pp.Optional(precision)
    + pp.Optional(type_)
    + end
)


[docs] def parse_fstring(fstr): result = fparser.parse_string(fstr) return result.as_dict()
[docs] def center(string, width): length = len(string) whitespace = width - length if whitespace >= 2: before = " " * (whitespace // 2) after = before centered = f"{before}{string}{after}" else: centered = string return centered
[docs] class TablePrinter: def __init__( self, header, col_fmts, width=12, sub_underline=True, mark="*", offset=8, logger=None, level=logging.INFO, ): self.header = header self.col_fmts = col_fmts self.width = width self.sub_underline = sub_underline self.mark = mark self.logger = logger self.level = level assert len(header) == len(col_fmts), ( f"Number of {len(header)} fields does not match the number of " f"column formats {len(col_fmts)}" ) w = str(self.width) # Shortcut whalf = str(self.width // 2) self.fmts = { "str": "{:>" + w + "s}", "mark": "{:>1s}", # "int": "{:>" + w + "d}", "int_short": "{:>" + whalf + "d}", "int3": "{: >3d}", # "float": "{: >" + w + ".6f}", "float_short": "{: >" + whalf + ".3f}", # "complex_tdm": "{: >" + w + ".4f}", "complex_short": "{: >" + w + ".2f}", } if self.sub_underline: self.header = [h.replace("_", " ") for h in self.header] # Determine alignments and widths from given formats fmts = list() alignments = list() widths = list() for col_fmt in self.col_fmts: # Frist, try to look up the format in our prepopulated # dictionary. try: fmt = self.fmts[col_fmt] # Second, if not found in the dict, we assume that the # string is a valid f-string. except KeyError: fmt = col_fmt # In any case, we parse the given string to determine # alignment and width. res = parse_fstring(fmt) alignment = res.get("alignment", "<") alignments.append(alignment) width = res.get("width", self.width) widths.append(width) fmts.append(fmt) # TODO: Check if header fields are longer than the given width. # If so, update the widths in the formats. mark_fmt = self.fmts["mark"] # self.conv_str will be used to render the given fields in a row self.conv_str = " ".join([fmt + mark_fmt for fmt in fmts]) # Distance from the line start self.offset = offset # Whitespace that is prepended on every row self.prefix = " " * self.offset # Length of a given line w/o offset/prefix self.line_length = sum(widths) + len(widths) + len(widths) - 1 # Separator string self.sep = self.prefix + "-" * self.line_length header_fmts = list() for alignment, width in zip(alignments, widths): hfmt = "{:" + alignment + str(width) + "}" header_fmts.append(hfmt) # Join with 2 spaces as we also have the mark field header_fmt = " ".join(header_fmts) self.header_str = self.prefix + header_fmt.format(*self.header) def _print(self, msg, level=None): if level is None: level = self.level if self.logger: self.logger.log(level, msg) else: print(msg)
[docs] def print_sep(self): self._print(self.sep)
[docs] def print_header(self, with_sep=True): self._print(self.header_str) if with_sep: self.print_sep()
[docs] def print_row(self, args, marks=None): if marks is None: marks = ["" for _ in args] marked_args = list() for arg, to_mark in zip(args, marks): marked_args.append(arg) marked_args.append(self.mark if to_mark else " ") row = self.prefix + self.conv_str.format(*marked_args) self._print(row)
[docs] def print(self, *args, **kwargs): text = " ".join([str(a) for a in args]) try: level = kwargs["level"] except KeyError: level = 0 level_prefix = " " * level self._print(textwrap.indent(text, self.prefix + level_prefix))