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))