Source code for doot.reporters.formatter

  1#!/usr/bin/env python3
  2"""
  3
  4"""
  5# ruff: noqa:
  6
  7# Imports:
  8from __future__ import annotations
  9
 10# ##-- stdlib imports
 11import datetime
 12import enum
 13import functools as ftz
 14import itertools as itz
 15import logging as logmod
 16import pathlib as pl
 17import re
 18import time
 19import types
 20import collections
 21import contextlib
 22import hashlib
 23from copy import deepcopy
 24from uuid import UUID, uuid1
 25from weakref import ref
 26import atexit # for @atexit.register
 27import faulthandler
 28# ##-- end stdlib imports
 29
 30from jgdv import Proto
 31from . import _interface as API
 32
 33# ##-- types
 34# isort: off
 35import abc
 36import collections.abc
 37from typing import TYPE_CHECKING, cast, assert_type, assert_never
 38from typing import Generic, NewType, Never
 39# Protocols:
 40from typing import Protocol, runtime_checkable
 41# Typing Decorators:
 42from typing import no_type_check, final, override, overload
 43# from dataclasses import InitVar, dataclass, field
 44# from pydantic import BaseModel, Field, model_validator, field_validator, ValidationError
 45
 46if TYPE_CHECKING:
 47    from jgdv import Maybe
 48    from typing import Final
 49    from typing import ClassVar, Any, LiteralString
 50    from typing import Self, Literal
 51    from typing import TypeGuard
 52    from collections.abc import Iterable, Iterator, Callable, Generator
 53    from collections.abc import Sequence, Mapping, MutableMapping, Hashable
 54
 55##--|
 56
 57# isort: on
 58# ##-- end types
 59
 60##-- logging
 61logging = logmod.getLogger(__name__)
 62##-- end logging
 63
 64# Vars:
 65
 66# Body:
 67
[docs] 68@Proto(API.ReportFormatter_p) 69class ReportFormatter: 70 """ ReportFormatter abstracts the logic of creating a contextual message. 71 72 """ 73 74 def __init__(self, *, segments:Maybe[dict]=None): 75 self._segments = (segments or API.TRACE_LINES_ASCII).copy() 76 self.line_fmt = API.LINE_PASS_FMT 77 self.msg_fmt = API.LINE_MSG_FMT 78 self._process_segments() 79 80 def __call__(self, key:str, *, info:Maybe[str]=None, msg:Maybe[str]=None, ctx:Maybe[list]=None) -> str: 81 """ Build the formatted report line. 82 83 key : the segment type to use 84 info/msg : values to format into the report 85 ctx : list[str] of values prefixing the report 86 """ 87 extra = {} 88 extra['time']= datetime.datetime.now().strftime(API.TIME_FMT) # noqa: DTZ005 89 match self._segments.get(key, None): 90 case str() if key in self._segments: 91 extra['act'] = self._segments[key] 92 extra['gap'] = " "*max(1, (API.ACT_SPACING - len(extra['act']))) 93 case (str() as l, str() as m, str() as r): 94 # Ensure the same gap between the end of the act, and start of the info 95 extra['act'] = f"{l}{m}{r}" 96 extra['gap'] = " "*max(1, (API.ACT_SPACING - len(r))) 97 case x: 98 raise TypeError(type(x)) 99 100 match msg: 101 case None: 102 fmt = self.line_fmt 103 case str() if not bool(msg) and not bool(info): 104 fmt = self.line_fmt 105 case _: 106 fmt = self.msg_fmt 107 extra['info'] = str(info or "") 108 extra['detail'] = str(msg) 109 # Ensure the same gap between the end of the info, and start of the msg 110 extra['gap2'] = " "*max(1, (API.MSG_SPACING - len(extra['info']))) 111 112 extra['ctx'] = self._build_ctx(ctx) 113 result : str = fmt.format_map(extra) 114 return result 115
[docs] 116 def get_segment(self, key:str) -> Maybe[str]: 117 match self._segments.get(key, None): 118 case None: 119 return None 120 case str() as val: 121 return val 122 case left, mid, right: 123 return None 124 case _: 125 raise ValueError("Unexpected value in reporter segments", key)
126 127 ##--|
[docs] 128 def _process_segments(self): 129 """ Ensure all needed segments exist and are the right size 130 131 if any are missing, use doot.reporters._interface.TRACE_LINES_ASCII's values 132 """ 133 processed = {} 134 for x,y in API.TRACE_LINES_ASCII.items(): 135 processed.setdefault(x, y) 136 else: 137 start_i, mid_i, end_i = API.SEGMENT_SIZES 138 just_char = self._segments.get("just_char", API.TRACE_LINES_ASCII["just_char"]) 139 for x,y in self._segments.items(): 140 match y: 141 case str(): 142 processed[x] = y 143 case start, mid, end: 144 processed[x] = (start.ljust(start_i, just_char), 145 mid.ljust(mid_i, just_char), 146 end.ljust(end_i, just_char)) 147 case other: 148 raise ValueError("Unexpected segment", other) 149 150 else: 151 self._segments = processed
152
[docs] 153 def _build_ctx(self, ctx:Maybe[list]) -> str: 154 """ Given a current context list, builds a prefix string for the current print call """ 155 match ctx: 156 case None | []: 157 return "" 158 case list(): 159 return API.GAP.join(ctx) + API.GAP 160 case x: 161 raise TypeError(type(x))