logger-36 2025.18__tar.gz → 2025.20__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. {logger_36-2025.18 → logger_36-2025.20}/PKG-INFO +1 -1
  2. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/api/content.py +0 -1
  3. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/catalog/config/console_rich.py +1 -1
  4. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/catalog/handler/console.py +6 -7
  5. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/catalog/handler/console_rich.py +12 -22
  6. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/catalog/handler/file.py +2 -6
  7. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/catalog/handler/generic.py +29 -43
  8. logger_36-2025.20/package/logger_36/constant/rule.py +42 -0
  9. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/task/format/message.py +1 -60
  10. logger_36-2025.20/package/logger_36/type/handler.py +201 -0
  11. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/type/logger.py +93 -80
  12. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/version.py +1 -1
  13. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36.egg-info/PKG-INFO +1 -1
  14. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36.egg-info/SOURCES.txt +2 -3
  15. logger_36-2025.18/package/logger_36/task/format/rule.py +0 -63
  16. logger_36-2025.18/package/logger_36/type/handler.py +0 -149
  17. logger_36-2025.18/package/logger_36/type/message.py +0 -76
  18. {logger_36-2025.18 → logger_36-2025.20}/MANIFEST.in +0 -0
  19. {logger_36-2025.18 → logger_36-2025.20}/README-COPYRIGHT-utf8.txt +0 -0
  20. {logger_36-2025.18 → logger_36-2025.20}/README-LICENCE-utf8.txt +0 -0
  21. {logger_36-2025.18 → logger_36-2025.20}/README.rst +0 -0
  22. {logger_36-2025.18 → logger_36-2025.20}/documentation/wiki/description.asciidoc +0 -0
  23. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/__init__.py +0 -0
  24. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/api/gpu.py +0 -0
  25. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/api/memory.py +0 -0
  26. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/api/storage.py +0 -0
  27. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/api/system.py +0 -0
  28. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/api/time.py +0 -0
  29. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/api/type.py +0 -0
  30. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/catalog/config/optional.py +0 -0
  31. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/catalog/logger/chronos.py +0 -0
  32. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/catalog/logger/gpu.py +0 -0
  33. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/catalog/logger/memory.py +0 -0
  34. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/catalog/logger/system.py +0 -0
  35. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/config/issue.py +0 -0
  36. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/config/memory.py +0 -0
  37. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/config/message.py +0 -0
  38. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/config/system.py +0 -0
  39. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/constant/error.py +0 -0
  40. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/constant/generic.py +0 -0
  41. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/constant/html.py +0 -0
  42. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/constant/issue.py +0 -0
  43. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/constant/logger.py +0 -0
  44. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/constant/memory.py +0 -0
  45. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/constant/message.py +0 -0
  46. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/constant/path.py +0 -0
  47. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/constant/record.py +0 -0
  48. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/constant/system.py +0 -0
  49. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/extension/html_.py +0 -0
  50. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/extension/line.py +0 -0
  51. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/instance/logger.py +0 -0
  52. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/instance/loggers.py +0 -0
  53. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/task/format/memory.py +0 -0
  54. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/task/inspection.py +0 -0
  55. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/task/measure/chronos.py +0 -0
  56. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/task/measure/memory.py +0 -0
  57. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/task/storage.py +0 -0
  58. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/type/issue.py +0 -0
  59. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36/type/loggers.py +0 -0
  60. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36.egg-info/dependency_links.txt +0 -0
  61. {logger_36-2025.18 → logger_36-2025.20}/package/logger_36.egg-info/top_level.txt +0 -0
  62. {logger_36-2025.18 → logger_36-2025.20}/pyproject.toml +0 -0
  63. {logger_36-2025.18 → logger_36-2025.20}/setup.cfg +0 -0
  64. {logger_36-2025.18 → logger_36-2025.20}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: logger-36
3
- Version: 2025.18
3
+ Version: 2025.20
4
4
  Summary: Simple logger with a catalog of handlers
5
5
  Home-page: https://src.koda.cnrs.fr/eric.debreuve/logger-36/
6
6
  Author: Eric Debreuve
@@ -6,7 +6,6 @@ SEE COPYRIGHT NOTICE BELOW
6
6
 
7
7
  from logger_36.constant.message import LINE_INDENT # noqa
8
8
  from logger_36.task.format.message import MessageWithActualExpected # noqa
9
- from logger_36.task.format.rule import Rule, RuleAsText # noqa
10
9
 
11
10
  """
12
11
  COPYRIGHT NOTICE
@@ -24,7 +24,7 @@ LEVEL_COLOR: dict[int, str | style_t] = {
24
24
  }
25
25
  ACTUAL_COLOR = LEVEL_COLOR[l.CRITICAL]
26
26
  EXPECTED_COLOR = "green3"
27
- DATE_TIME_COLOR = "sky_blue3"
27
+ RULE_COLOR = "sky_blue3"
28
28
 
29
29
  ALTERNATIVE_BACKGROUND_FOR_LIGHT = style_t(bgcolor=color_t.from_rgb(230, 230, 230))
30
30
  ALTERNATIVE_BACKGROUND_FOR_DARK = style_t(bgcolor=color_t.from_rgb(25, 25, 25))
@@ -8,7 +8,6 @@ import logging as l
8
8
  import sys as s
9
9
  import typing as h
10
10
 
11
- from logger_36.task.format.rule import RuleAsText
12
11
  from logger_36.type.handler import handler_t as base_t
13
12
 
14
13
 
@@ -21,18 +20,18 @@ class console_handler_t(base_t):
21
20
  name: str | None = None,
22
21
  message_width: int = -1,
23
22
  level: int = l.NOTSET,
24
- formatter: l.Formatter | None = None,
25
23
  **_,
26
24
  ) -> h.Self:
27
25
  """"""
28
- return cls(name, message_width, level, formatter)
26
+ return cls(name, message_width, None, level)
29
27
 
30
28
  def emit(self, record: l.LogRecord, /) -> None:
31
29
  """"""
32
- output = self.MessageFromRecord(
33
- record, RuleAsText, line_width=self.message_width
34
- )
35
- s.__stdout__.write(output[0] + "\n")
30
+ s.__stdout__.write(self.MessageFromRecord(record)[0] + "\n")
31
+
32
+ def EmitAsIs(self, message: str, /) -> None:
33
+ """"""
34
+ s.__stdout__.write(message + "\n")
36
35
 
37
36
 
38
37
  """
@@ -10,6 +10,7 @@ import typing as h
10
10
  from rich.console import Console as console_t # noqa
11
11
  from rich.console import RenderableType as renderable_t # noqa
12
12
  from rich.markup import escape as EscapedVersion # noqa
13
+ from rich.rule import Rule as rule_t # noqa
13
14
  from rich.text import Text as text_t # noqa
14
15
  from rich.traceback import install as InstallTracebackHandler # noqa
15
16
 
@@ -17,15 +18,14 @@ from logger_36.catalog.config.console_rich import (
17
18
  ACTUAL_COLOR,
18
19
  ALTERNATIVE_BACKGROUND_FOR_DARK,
19
20
  ALTERNATIVE_BACKGROUND_FOR_LIGHT,
20
- DATE_TIME_COLOR,
21
21
  EXPECTED_COLOR,
22
22
  GRAY_COLOR,
23
23
  LEVEL_COLOR,
24
+ RULE_COLOR,
24
25
  WHITE_COLOR,
25
26
  )
26
27
  from logger_36.config.message import ACTUAL_PATTERNS, EXPECTED_PATTERNS, WHERE_SEPARATOR
27
28
  from logger_36.constant.message import CONTEXT_LENGTH
28
- from logger_36.task.format.rule import Rule
29
29
  from logger_36.type.handler import handler_t as base_t
30
30
 
31
31
  _COMMON_TRACEBACK_ARGUMENTS = ("theme", "width")
@@ -51,12 +51,7 @@ class console_rich_handler_t(base_t):
51
51
  """
52
52
 
53
53
  def __init__(
54
- self,
55
- name: str | None,
56
- message_width: int,
57
- level: int,
58
- formatter: l.Formatter | None,
59
- kwargs,
54
+ self, name: str | None, message_width: int, level: int, kwargs
60
55
  ) -> None:
61
56
  """"""
62
57
  alternating_logs = kwargs.pop("alternating_logs", 0)
@@ -64,7 +59,7 @@ class console_rich_handler_t(base_t):
64
59
 
65
60
  assert alternating_logs in (0, 1, 2)
66
61
 
67
- base_t.__init__(self, name, message_width, level, formatter, kwargs)
62
+ base_t.__init__(self, name, message_width, EscapedVersion, level, kwargs)
68
63
 
69
64
  self.console = None # console_t | None.
70
65
  self.alternating_logs = alternating_logs
@@ -96,21 +91,20 @@ class console_rich_handler_t(base_t):
96
91
  name: str | None = None,
97
92
  message_width: int = -1,
98
93
  level: int = l.NOTSET,
99
- formatter: l.Formatter | None = None,
100
94
  **kwargs,
101
95
  ) -> h.Self:
102
96
  """"""
103
- return cls(name, message_width, level, formatter, kwargs)
97
+ return cls(name, message_width, level, kwargs)
98
+
99
+ def Rule(self, text: str | None, /, *, color: str = "black") -> str | rule_t:
100
+ """"""
101
+ if text is None:
102
+ return rule_t(style=color)
103
+ return rule_t(title=text_t(text, style=f"bold {color}"), style=color)
104
104
 
105
105
  def emit(self, record: l.LogRecord, /) -> None:
106
106
  """"""
107
- message, is_not_a_rule = self.MessageFromRecord(
108
- record,
109
- Rule,
110
- line_width=self.message_width,
111
- color=DATE_TIME_COLOR,
112
- PreProcessed=EscapedVersion,
113
- )
107
+ message, is_not_a_rule = self.MessageFromRecord(record, rule_color=RULE_COLOR)
114
108
  if is_not_a_rule:
115
109
  message = HighlightedVersion(
116
110
  self.console,
@@ -126,10 +120,6 @@ class console_rich_handler_t(base_t):
126
120
  """"""
127
121
  self.console.print(message, crop=False, overflow="ignore")
128
122
 
129
- def EmitRule(self, /, *, text: str | None = None, color: str = "black") -> None:
130
- """"""
131
- self.EmitAsIs(Rule(text, color))
132
-
133
123
 
134
124
  def HighlightedVersion(
135
125
  _: console_t,
@@ -8,7 +8,6 @@ import logging as l
8
8
  import typing as h
9
9
  from pathlib import Path as path_t
10
10
 
11
- from logger_36.task.format.rule import RuleAsText
12
11
  from logger_36.type.handler import file_handler_t as base_t
13
12
 
14
13
 
@@ -21,18 +20,15 @@ class file_handler_t(base_t):
21
20
  name: str | None = None,
22
21
  message_width: int = -1,
23
22
  level: int = l.NOTSET,
24
- formatter: l.Formatter | None = None,
25
23
  path: str | path_t | None = None,
26
24
  **_,
27
25
  ) -> h.Self:
28
26
  """"""
29
- return cls(name, message_width, level, formatter, path)
27
+ return cls(name, message_width, None, level, path)
30
28
 
31
29
  def emit(self, record: l.LogRecord, /) -> None:
32
30
  """"""
33
- output = self.MessageFromRecord(
34
- record, RuleAsText, line_width=self.message_width
35
- )
31
+ output = self.MessageFromRecord(record)
36
32
  self.stream.write(output[0] + "\n")
37
33
  self.stream.flush()
38
34
 
@@ -10,28 +10,21 @@ import typing as h
10
10
  from logger_36.catalog.config.optional import RICH_IS_AVAILABLE
11
11
 
12
12
  if RICH_IS_AVAILABLE:
13
- from rich.console import Console as console_t # noqa
14
- from rich.console import ConsoleOptions as console_options_t # noqa
15
- from rich.markup import escape as EscapedForRich # noqa
16
- from rich.terminal_theme import DEFAULT_TERMINAL_THEME # noqa
13
+ from rich.console import Console as console_t
14
+ from rich.markup import escape as EscapedForRich
15
+ from rich.rule import Rule as rule_t
16
+ from rich.terminal_theme import DEFAULT_TERMINAL_THEME
17
+ from rich.text import Text as text_t
17
18
 
18
- from logger_36.catalog.config.console_rich import DATE_TIME_COLOR
19
+ from logger_36.catalog.config.console_rich import RULE_COLOR
19
20
  from logger_36.catalog.handler.console_rich import HighlightedVersion
20
21
  else:
21
- DATE_TIME_COLOR = HighlightedVersion = console_t = console_options_t = (
22
- EscapedForRich
23
- ) = DEFAULT_TERMINAL_THEME = None
22
+ console_t = EscapedForRich = rule_t = DEFAULT_TERMINAL_THEME = text_t = (
23
+ RULE_COLOR
24
+ ) = HighlightedVersion = None
24
25
 
25
- from logger_36.task.format.rule import Rule, RuleAsText
26
26
  from logger_36.type.handler import handler_t as base_t
27
27
 
28
- LogAsIs_h = h.Callable[[str | h.Any], None]
29
-
30
-
31
- @h.runtime_checkable
32
- class EmitRule_p(h.Protocol):
33
- def __call__(self, /, *, text: str | None = None, color: str = "white") -> None: ...
34
-
35
28
 
36
29
  class generic_handler_t(base_t):
37
30
  """
@@ -42,12 +35,7 @@ class generic_handler_t(base_t):
42
35
  """
43
36
 
44
37
  def __init__(
45
- self,
46
- name: str | None,
47
- message_width: int,
48
- level: int,
49
- formatter: l.Formatter | None,
50
- kwargs,
38
+ self, name: str | None, message_width: int, level: int, kwargs
51
39
  ) -> None:
52
40
  """
53
41
  EmitAsIs: By definition, the generic handler does not know how to output
@@ -59,12 +47,13 @@ class generic_handler_t(base_t):
59
47
 
60
48
  assert alternating_logs in (0, 1, 2)
61
49
 
62
- base_t.__init__(self, name, message_width, level, formatter, kwargs)
50
+ base_t.__init__(self, name, message_width, None, level, kwargs)
63
51
 
64
52
  if EmitAsIs is not None:
65
53
  self.EmitAsIs = EmitAsIs
54
+ self.is_rich = False
66
55
  self.console = None # console_t | None.
67
- self.console_options = None # console_options_t | None.
56
+ self.console_options = None # rich.console.ConsoleOptions | None.
68
57
  self.alternating_logs = alternating_logs
69
58
  self._log_parity = False
70
59
 
@@ -73,11 +62,12 @@ class generic_handler_t(base_t):
73
62
  def __post_init_local__(self, supports_html: bool) -> None:
74
63
  """"""
75
64
  if supports_html and (console_t is not None):
65
+ self.PreProcessedMessage = EscapedForRich
66
+ self.is_rich = True
76
67
  self.console = console_t(highlight=False, force_terminal=True)
77
68
  self.console_options = self.console.options.update(
78
69
  overflow="ignore", no_wrap=True
79
70
  )
80
- self.EmitRule = self._EmitRichRule
81
71
 
82
72
  @classmethod
83
73
  def New(
@@ -87,25 +77,25 @@ class generic_handler_t(base_t):
87
77
  name: str | None = None,
88
78
  message_width: int = -1,
89
79
  level: int = l.NOTSET,
90
- formatter: l.Formatter | None = None,
91
80
  **kwargs,
92
81
  ) -> h.Self:
93
82
  """"""
94
- return cls(name, message_width, level, formatter, kwargs)
83
+ return cls(name, message_width, level, kwargs)
84
+
85
+ def Rule(self, text: str | None, /, *, color: str = "black") -> str | rule_t:
86
+ """"""
87
+ if self.is_rich:
88
+ if text is None:
89
+ return rule_t(style=color)
90
+ return rule_t(title=text_t(text, style=f"bold {color}"), style=color)
91
+
92
+ return base_t.Rule(self, text, color=color)
95
93
 
96
94
  def emit(self, record: l.LogRecord, /) -> None:
97
95
  """"""
98
- if self.console is None:
99
- message = self.MessageFromRecord(
100
- record, RuleAsText, line_width=self.message_width
101
- )[0]
102
- else:
96
+ if self.is_rich:
103
97
  message, is_not_a_rule = self.MessageFromRecord(
104
- record,
105
- Rule,
106
- line_width=self.message_width,
107
- color=DATE_TIME_COLOR,
108
- PreProcessed=EscapedForRich,
98
+ record, rule_color=RULE_COLOR
109
99
  )
110
100
  if is_not_a_rule:
111
101
  message = HighlightedVersion(
@@ -138,16 +128,12 @@ class generic_handler_t(base_t):
138
128
  message = (
139
129
  "<pre style='margin-bottom:0px'>" + "".join(html_segments) + "</pre>"
140
130
  )
131
+ else:
132
+ message = self.MessageFromRecord(record)[0]
141
133
 
142
134
  self.EmitAsIs(message)
143
135
  self._log_parity = not self._log_parity
144
136
 
145
- def _EmitRichRule(
146
- self, /, *, text: str | None = None, color: str = "black"
147
- ) -> None:
148
- """"""
149
- self.EmitAsIs(Rule(text, color))
150
-
151
137
 
152
138
  """
153
139
  COPYRIGHT NOTICE
@@ -0,0 +1,42 @@
1
+ """
2
+ Copyright CNRS (https://www.cnrs.fr/index.php/en)
3
+ Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023
4
+ SEE COPYRIGHT NOTICE BELOW
5
+ """
6
+
7
+ RULE_CHARACTER = "-"
8
+ MIN_HALF_RULE_LENGTH = 4
9
+ DEFAULT_RULE_LENGTH = 50
10
+ DEFAULT_RULE = DEFAULT_RULE_LENGTH * RULE_CHARACTER
11
+
12
+ """
13
+ COPYRIGHT NOTICE
14
+
15
+ This software is governed by the CeCILL license under French law and
16
+ abiding by the rules of distribution of free software. You can use,
17
+ modify and/ or redistribute the software under the terms of the CeCILL
18
+ license as circulated by CEA, CNRS and INRIA at the following URL
19
+ "http://www.cecill.info".
20
+
21
+ As a counterpart to the access to the source code and rights to copy,
22
+ modify and redistribute granted by the license, users are provided only
23
+ with a limited warranty and the software's author, the holder of the
24
+ economic rights, and the successive licensors have only limited
25
+ liability.
26
+
27
+ In this respect, the user's attention is drawn to the risks associated
28
+ with loading, using, modifying and/or developing or reproducing the
29
+ software by the user in light of its specific status of free software,
30
+ that may mean that it is complicated to manipulate, and that also
31
+ therefore means that it is reserved for developers and experienced
32
+ professionals having in-depth computer knowledge. Users are therefore
33
+ encouraged to load and test the software's suitability as regards their
34
+ requirements in conditions enabling the security of their systems and/or
35
+ data to be ensured and, more generally, to use and operate it in the
36
+ same conditions as regards security.
37
+
38
+ The fact that you are presently reading this means that you have had
39
+ knowledge of the CeCILL license and that you accept its terms.
40
+
41
+ SEE LICENCE NOTICE: file README-LICENCE-utf8.txt at project source root.
42
+ """
@@ -5,69 +5,10 @@ SEE COPYRIGHT NOTICE BELOW
5
5
  """
6
6
 
7
7
  import difflib as diff
8
- import logging as l
9
8
  import typing as h
10
9
 
11
- from logger_36.config.message import (
12
- LEVEL_CLOSING,
13
- LEVEL_OPENING,
14
- MESSAGE_MARKER,
15
- WHERE_SEPARATOR,
16
- )
17
10
  from logger_36.constant.generic import NOT_PASSED
18
- from logger_36.constant.message import NEXT_LINE_PROLOGUE, expected_op_h
19
- from logger_36.constant.record import SHOW_W_RULE_ATTR
20
- from logger_36.extension.line import WrappedLines
21
- from logger_36.type.message import RuleWithText_h
22
-
23
-
24
- def MessageFromRecord(
25
- record: l.LogRecord,
26
- RuleWithText: RuleWithText_h,
27
- /,
28
- *,
29
- line_width: int = 0,
30
- color: str | None = None,
31
- PreProcessed: h.Callable[[str], str] | None = None,
32
- ) -> tuple[str, bool]:
33
- """
34
- See logger_36.catalog.handler.README.txt.
35
-
36
- The second returned value is is_not_a_rule.
37
- """
38
- message = record.msg
39
-
40
- if hasattr(record, SHOW_W_RULE_ATTR):
41
- return RuleWithText(message, color), False
42
-
43
- if PreProcessed is not None:
44
- message = PreProcessed(message)
45
- if (line_width <= 0) or (message.__len__() <= line_width):
46
- if "\n" in message:
47
- message = NEXT_LINE_PROLOGUE.join(message.splitlines())
48
- else:
49
- if "\n" in message:
50
- lines = WrappedLines(message.splitlines(), line_width)
51
- else:
52
- lines = WrappedLines([message], line_width)
53
- message = NEXT_LINE_PROLOGUE.join(lines)
54
-
55
- when_or_elapsed = getattr(record, "when_or_elapsed", None)
56
- if when_or_elapsed is None:
57
- return message, True
58
-
59
- level_first_letter = getattr(record, "level_first_letter", "")
60
-
61
- if (where := getattr(record, "where", None)) is None:
62
- where = ""
63
- else:
64
- where = f"{NEXT_LINE_PROLOGUE}{WHERE_SEPARATOR} {where}"
65
-
66
- return (
67
- f"{when_or_elapsed}"
68
- f"{LEVEL_OPENING}{level_first_letter}{LEVEL_CLOSING} "
69
- f"{MESSAGE_MARKER} {message}{where}"
70
- ), True
11
+ from logger_36.constant.message import expected_op_h
71
12
 
72
13
 
73
14
  def MessageWithActualExpected(
@@ -0,0 +1,201 @@
1
+ """
2
+ Copyright CNRS (https://www.cnrs.fr/index.php/en)
3
+ Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023
4
+ SEE COPYRIGHT NOTICE BELOW
5
+ """
6
+
7
+ import logging as l
8
+ import typing as h
9
+ from pathlib import Path as path_t
10
+
11
+ from logger_36.config.message import (
12
+ FALLBACK_MESSAGE_WIDTH,
13
+ LEVEL_CLOSING,
14
+ LEVEL_OPENING,
15
+ MESSAGE_MARKER,
16
+ WHERE_SEPARATOR,
17
+ )
18
+ from logger_36.constant.message import NEXT_LINE_PROLOGUE
19
+ from logger_36.constant.record import SHOW_W_RULE_ATTR
20
+ from logger_36.constant.rule import (
21
+ DEFAULT_RULE,
22
+ DEFAULT_RULE_LENGTH,
23
+ MIN_HALF_RULE_LENGTH,
24
+ RULE_CHARACTER,
25
+ )
26
+ from logger_36.extension.line import WrappedLines
27
+
28
+
29
+ class extension_t:
30
+ def __init__(
31
+ self,
32
+ name: str | None,
33
+ message_width: int,
34
+ PreProcessedMessage: h.Callable[[str], str] | None,
35
+ ) -> None:
36
+ """"""
37
+ self.name = name
38
+ self.message_width = message_width
39
+ self.PreProcessedMessage = PreProcessedMessage
40
+
41
+ self.__post_init__()
42
+
43
+ def __post_init__(self) -> None:
44
+ """"""
45
+ if self.name is None:
46
+ self.name = f"{type(self).__name__}:{hex(id(self))[2:]}"
47
+
48
+ if 0 < self.message_width < FALLBACK_MESSAGE_WIDTH:
49
+ self.message_width = FALLBACK_MESSAGE_WIDTH
50
+
51
+ @classmethod
52
+ def New(cls, **kwargs) -> h.Self:
53
+ """
54
+ Interest: default arguments, no prescribed argument order, variable argument list.
55
+ """
56
+ raise NotImplementedError
57
+
58
+ def MessageFromRecord(
59
+ self, record: l.LogRecord, /, *, rule_color: str | None = None
60
+ ) -> tuple[str, bool]:
61
+ """
62
+ The second returned value is is_not_a_rule.
63
+ """
64
+ message = record.msg # See logger_36.catalog.handler.README.txt.
65
+ if self.PreProcessedMessage is not None:
66
+ message = self.PreProcessedMessage(message)
67
+
68
+ if hasattr(record, SHOW_W_RULE_ATTR):
69
+ return self.Rule(message, color=rule_color), False
70
+
71
+ if (self.message_width <= 0) or (message.__len__() <= self.message_width):
72
+ if "\n" in message:
73
+ message = NEXT_LINE_PROLOGUE.join(message.splitlines())
74
+ else:
75
+ if "\n" in message:
76
+ lines = WrappedLines(message.splitlines(), self.message_width)
77
+ else:
78
+ lines = WrappedLines([message], self.message_width)
79
+ message = NEXT_LINE_PROLOGUE.join(lines)
80
+
81
+ when_or_elapsed = getattr(record, "when_or_elapsed", None)
82
+ if when_or_elapsed is None:
83
+ return message, True
84
+
85
+ if (where := getattr(record, "where", None)) is None:
86
+ where = ""
87
+ else:
88
+ where = f"{NEXT_LINE_PROLOGUE}{WHERE_SEPARATOR} {where}"
89
+
90
+ return (
91
+ f"{when_or_elapsed}"
92
+ f"{LEVEL_OPENING}{record.levelname[0]}{LEVEL_CLOSING} "
93
+ f"{MESSAGE_MARKER} {message}{where}"
94
+ ), True
95
+
96
+ def Rule(self, text: str | None, /, *, color: str = "black") -> str | h.Any:
97
+ """
98
+ Return type hint h.Any: For Rich, for example.
99
+ """
100
+ if text is None:
101
+ if self.message_width > 0:
102
+ return self.message_width * RULE_CHARACTER
103
+ return DEFAULT_RULE
104
+
105
+ if self.message_width > 0:
106
+ target_width = self.message_width
107
+ else:
108
+ target_width = DEFAULT_RULE_LENGTH
109
+ half_rule_length = max(
110
+ (target_width - text.__len__() - 2) // 2, MIN_HALF_RULE_LENGTH
111
+ )
112
+ half_rule = half_rule_length * RULE_CHARACTER
113
+
114
+ return f"{half_rule} {text} {half_rule}"
115
+
116
+ def EmitAsIs(self, message: str, /) -> None:
117
+ """"""
118
+ raise NotImplementedError
119
+
120
+ def EmitRule(self, /, *, text: str | None = None, color: str = "black") -> None:
121
+ """"""
122
+ self.EmitAsIs(self.Rule(text, color=color))
123
+
124
+
125
+ class handler_t(l.Handler, extension_t):
126
+ def __init__(
127
+ self,
128
+ name: str | None,
129
+ message_width: int,
130
+ PreProcessedMessage: h.Callable[[str], str] | None,
131
+ level: int,
132
+ *_,
133
+ ) -> None:
134
+ """"""
135
+ l.Handler.__init__(self)
136
+ extension_t.__init__(self, name, message_width, PreProcessedMessage)
137
+ __post_init__(self, level)
138
+
139
+
140
+ class file_handler_t(l.FileHandler, extension_t):
141
+ def __init__(
142
+ self,
143
+ name: str | None,
144
+ message_width: int,
145
+ PreProcessedMessage: h.Callable[[str], str] | None,
146
+ level: int,
147
+ path: str | path_t | None,
148
+ *_,
149
+ ) -> None:
150
+ """"""
151
+ if path is None:
152
+ raise ValueError("Missing file or folder.")
153
+ if isinstance(path, str):
154
+ path = path_t(path)
155
+ if path.exists():
156
+ raise ValueError(f"File or folder already exists: {path}.")
157
+
158
+ l.FileHandler.__init__(self, path)
159
+ extension_t.__init__(self, name, message_width, PreProcessedMessage)
160
+ __post_init__(self, level)
161
+
162
+
163
+ any_handler_t = handler_t | file_handler_t
164
+
165
+
166
+ def __post_init__(handler: any_handler_t, level: int) -> None:
167
+ """"""
168
+ handler.setLevel(level)
169
+
170
+
171
+ """
172
+ COPYRIGHT NOTICE
173
+
174
+ This software is governed by the CeCILL license under French law and
175
+ abiding by the rules of distribution of free software. You can use,
176
+ modify and/ or redistribute the software under the terms of the CeCILL
177
+ license as circulated by CEA, CNRS and INRIA at the following URL
178
+ "http://www.cecill.info".
179
+
180
+ As a counterpart to the access to the source code and rights to copy,
181
+ modify and redistribute granted by the license, users are provided only
182
+ with a limited warranty and the software's author, the holder of the
183
+ economic rights, and the successive licensors have only limited
184
+ liability.
185
+
186
+ In this respect, the user's attention is drawn to the risks associated
187
+ with loading, using, modifying and/or developing or reproducing the
188
+ software by the user in light of its specific status of free software,
189
+ that may mean that it is complicated to manipulate, and that also
190
+ therefore means that it is reserved for developers and experienced
191
+ professionals having in-depth computer knowledge. Users are therefore
192
+ encouraged to load and test the software's suitability as regards their
193
+ requirements in conditions enabling the security of their systems and/or
194
+ data to be ensured and, more generally, to use and operate it in the
195
+ same conditions as regards security.
196
+
197
+ The fact that you are presently reading this means that you have had
198
+ knowledge of the CeCILL license and that you accept its terms.
199
+
200
+ SEE LICENCE NOTICE: file README-LICENCE-utf8.txt at project source root.
201
+ """