logger-36 2025.13__tar.gz → 2025.14__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 (66) hide show
  1. {logger_36-2025.13 → logger_36-2025.14}/PKG-INFO +1 -1
  2. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/catalog/handler/console.py +8 -13
  3. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/catalog/handler/console_rich.py +53 -35
  4. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/catalog/handler/file.py +8 -13
  5. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/catalog/handler/generic.py +61 -41
  6. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/config/message.py +1 -0
  7. logger_36-2025.14/package/logger_36/constant/html.py +70 -0
  8. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/task/format/message.py +60 -1
  9. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/task/format/rule.py +3 -2
  10. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/task/storage.py +29 -8
  11. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/type/handler.py +24 -9
  12. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/type/logger.py +44 -30
  13. logger_36-2025.14/package/logger_36/type/message.py +90 -0
  14. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/version.py +1 -1
  15. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36.egg-info/PKG-INFO +1 -1
  16. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36.egg-info/SOURCES.txt +1 -0
  17. logger_36-2025.13/package/logger_36/type/message.py +0 -121
  18. {logger_36-2025.13 → logger_36-2025.14}/MANIFEST.in +0 -0
  19. {logger_36-2025.13 → logger_36-2025.14}/README-COPYRIGHT-utf8.txt +0 -0
  20. {logger_36-2025.13 → logger_36-2025.14}/README-LICENCE-utf8.txt +0 -0
  21. {logger_36-2025.13 → logger_36-2025.14}/README.rst +0 -0
  22. {logger_36-2025.13 → logger_36-2025.14}/documentation/wiki/description.asciidoc +0 -0
  23. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/__init__.py +0 -0
  24. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/api/content.py +0 -0
  25. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/api/exception.py +0 -0
  26. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/api/gpu.py +0 -0
  27. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/api/memory.py +0 -0
  28. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/api/storage.py +0 -0
  29. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/api/system.py +0 -0
  30. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/api/time.py +0 -0
  31. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/api/type.py +0 -0
  32. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/catalog/config/console_rich.py +0 -0
  33. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/catalog/config/optional.py +0 -0
  34. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/catalog/logger/chronos.py +0 -0
  35. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/catalog/logger/gpu.py +0 -0
  36. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/catalog/logger/memory.py +0 -0
  37. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/catalog/logger/system.py +0 -0
  38. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/config/issue.py +0 -0
  39. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/config/memory.py +0 -0
  40. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/config/system.py +0 -0
  41. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/constant/error.py +0 -0
  42. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/constant/generic.py +0 -0
  43. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/constant/handler.py +0 -0
  44. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/constant/issue.py +0 -0
  45. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/constant/logger.py +0 -0
  46. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/constant/memory.py +0 -0
  47. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/constant/message.py +0 -0
  48. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/constant/path.py +0 -0
  49. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/constant/record.py +0 -0
  50. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/constant/system.py +0 -0
  51. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/extension/exception.py +0 -0
  52. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/extension/html_.py +0 -0
  53. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/extension/line.py +0 -0
  54. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/instance/logger.py +0 -0
  55. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/instance/loggers.py +0 -0
  56. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/task/format/memory.py +0 -0
  57. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/task/inspection.py +0 -0
  58. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/task/measure/chronos.py +0 -0
  59. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/task/measure/memory.py +0 -0
  60. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/type/issue.py +0 -0
  61. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36/type/loggers.py +0 -0
  62. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36.egg-info/dependency_links.txt +0 -0
  63. {logger_36-2025.13 → logger_36-2025.14}/package/logger_36.egg-info/top_level.txt +0 -0
  64. {logger_36-2025.13 → logger_36-2025.14}/pyproject.toml +0 -0
  65. {logger_36-2025.13 → logger_36-2025.14}/setup.cfg +0 -0
  66. {logger_36-2025.13 → logger_36-2025.14}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: logger-36
3
- Version: 2025.13
3
+ Version: 2025.14
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
@@ -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.constant.record import SHOW_W_RULE_ATTR
12
11
  from logger_36.task.format.rule import RuleAsText
13
12
  from logger_36.type.handler import handler_t as base_t
14
13
 
@@ -26,29 +25,25 @@ class console_handler_t(base_t):
26
25
  message_width: int = -1,
27
26
  level: int = l.NOTSET,
28
27
  formatter: l.Formatter | None = None,
29
- **unused,
28
+ **_,
30
29
  ) -> h.Self:
31
30
  """"""
32
31
  return cls(name, should_store_memory_usage, message_width, level, formatter)
33
32
 
34
33
  def emit(self, record: l.LogRecord, /) -> None:
35
34
  """"""
36
- if hasattr(record, SHOW_W_RULE_ATTR):
37
- message = RuleAsText(record.msg)
38
- else:
39
- message = self.MessageFromRecord(record, line_width=self.message_width)
40
- s.__stdout__.write(message + "\n")
35
+ message = self.MessageFromRecord(
36
+ record, RuleAsText, line_width=self.message_width
37
+ )
38
+ s.__stdout__.write(message[0] + "\n")
41
39
 
42
40
  def LogAsIs(self, message: str, /) -> None:
43
- """
44
- See documentation of
45
- logger_36.catalog.handler.generic.generic_handler_t.LogAsIs.
46
- """
41
+ """"""
47
42
  s.__stdout__.write(message + "\n")
48
43
 
49
- def DisplayRule(self, /, *, text: str | None = None, color: str = "white") -> None:
44
+ def DisplayRule(self, /, *, text: str | None = None, color: str = "black") -> None:
50
45
  """"""
51
- self.LogAsIs(RuleAsText(text))
46
+ self.LogAsIs(RuleAsText(text, None))
52
47
 
53
48
 
54
49
  """
@@ -25,7 +25,6 @@ from logger_36.catalog.config.console_rich import (
25
25
  )
26
26
  from logger_36.config.message import ACTUAL_PATTERNS, EXPECTED_PATTERNS, WHERE_SEPARATOR
27
27
  from logger_36.constant.message import CONTEXT_LENGTH
28
- from logger_36.constant.record import SHOW_W_RULE_ATTR
29
28
  from logger_36.task.format.rule import Rule
30
29
  from logger_36.type.handler import handler_t as base_t
31
30
 
@@ -53,33 +52,36 @@ class console_rich_handler_t(base_t):
53
52
 
54
53
  kind: h.ClassVar[str] = "c"
55
54
 
56
- @classmethod
57
- def New(
58
- cls,
59
- /,
60
- *,
61
- name: str | None = None,
62
- should_store_memory_usage: bool = False,
63
- message_width: int = -1,
64
- level: int = l.NOTSET,
65
- formatter: l.Formatter | None = None,
66
- **kwargs,
67
- ) -> h.Self:
55
+ def __init__(
56
+ self,
57
+ name: str | None,
58
+ should_store_memory_usage: bool,
59
+ message_width: int,
60
+ level: int,
61
+ formatter: l.Formatter | None,
62
+ kwargs,
63
+ ) -> None:
68
64
  """"""
69
65
  alternating_logs = kwargs.pop("alternating_logs", 0)
70
66
  should_install_traceback = kwargs.pop("should_install_traceback", False)
71
67
 
72
68
  assert alternating_logs in (0, 1, 2)
73
69
 
74
- output = cls(name, should_store_memory_usage, message_width, level, formatter)
70
+ base_t.__init__(
71
+ self,
72
+ name,
73
+ should_store_memory_usage,
74
+ message_width,
75
+ level,
76
+ formatter,
77
+ kwargs,
78
+ )
75
79
 
76
- output.console = None # console_t | None.
77
- output.alternating_logs = alternating_logs
78
- output.log_parity = False
80
+ self.console = None # console_t | None.
81
+ self.alternating_logs = alternating_logs
82
+ self._log_parity = False
79
83
 
80
- output.__post_init_local__(should_install_traceback, **kwargs)
81
-
82
- return output
84
+ self.__post_init_local__(should_install_traceback, **kwargs)
83
85
 
84
86
  def __post_init_local__(self, should_install_traceback: bool, **kwargs) -> None:
85
87
  """"""
@@ -97,32 +99,48 @@ class console_rich_handler_t(base_t):
97
99
  traceback_kwargs["console"] = self.console
98
100
  InstallTracebackHandler(**traceback_kwargs)
99
101
 
102
+ @classmethod
103
+ def New(
104
+ cls,
105
+ /,
106
+ *,
107
+ name: str | None = None,
108
+ should_store_memory_usage: bool = False,
109
+ message_width: int = -1,
110
+ level: int = l.NOTSET,
111
+ formatter: l.Formatter | None = None,
112
+ **kwargs,
113
+ ) -> h.Self:
114
+ """"""
115
+ return cls(
116
+ name, should_store_memory_usage, message_width, level, formatter, kwargs
117
+ )
118
+
100
119
  def emit(self, record: l.LogRecord, /) -> None:
101
120
  """"""
102
- if hasattr(record, SHOW_W_RULE_ATTR):
103
- richer = Rule(record.msg, DATE_TIME_COLOR)
104
- else:
105
- message = self.MessageFromRecord(
106
- record, line_width=self.message_width, PreProcessed=EscapedVersion
107
- )
108
- richer = HighlightedVersion(
121
+ message, is_not_a_rule = self.MessageFromRecord(
122
+ record,
123
+ Rule,
124
+ line_width=self.message_width,
125
+ color=DATE_TIME_COLOR,
126
+ PreProcessed=EscapedVersion,
127
+ )
128
+ if is_not_a_rule:
129
+ message = HighlightedVersion(
109
130
  self.console,
110
131
  message,
111
132
  record.levelno,
112
133
  self.alternating_logs,
113
- self.log_parity,
134
+ self._log_parity,
114
135
  )
115
- self.console.print(richer, crop=False, overflow="ignore")
116
- self.log_parity = not self.log_parity
136
+ self.console.print(message, crop=False, overflow="ignore")
137
+ self._log_parity = not self._log_parity
117
138
 
118
139
  def LogAsIs(self, message: str | renderable_t, /) -> None:
119
- """
120
- See documentation of
121
- logger_36.catalog.handler.generic.generic_handler_t.LogAsIs.
122
- """
140
+ """"""
123
141
  self.console.print(message, crop=False, overflow="ignore")
124
142
 
125
- def DisplayRule(self, /, *, text: str | None = None, color: str = "white") -> None:
143
+ def DisplayRule(self, /, *, text: str | None = None, color: str = "black") -> None:
126
144
  """"""
127
145
  self.LogAsIs(Rule(text, color))
128
146
 
@@ -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.constant.record import SHOW_W_RULE_ATTR
12
11
  from logger_36.task.format.rule import RuleAsText
13
12
  from logger_36.type.handler import file_handler_t as base_t
14
13
 
@@ -27,7 +26,7 @@ class file_handler_t(base_t):
27
26
  level: int = l.NOTSET,
28
27
  formatter: l.Formatter | None = None,
29
28
  path: str | path_t | None = None,
30
- **unused,
29
+ **_,
31
30
  ) -> h.Self:
32
31
  """"""
33
32
  return cls(
@@ -36,24 +35,20 @@ class file_handler_t(base_t):
36
35
 
37
36
  def emit(self, record: l.LogRecord, /) -> None:
38
37
  """"""
39
- if hasattr(record, SHOW_W_RULE_ATTR):
40
- message = RuleAsText(record.msg)
41
- else:
42
- message = self.MessageFromRecord(record, line_width=self.message_width)
43
- self.stream.write(message + "\n")
38
+ message = self.MessageFromRecord(
39
+ record, RuleAsText, line_width=self.message_width
40
+ )
41
+ self.stream.write(message[0] + "\n")
44
42
  self.stream.flush()
45
43
 
46
44
  def LogAsIs(self, message: str, /) -> None:
47
- """
48
- See documentation of
49
- logger_36.catalog.handler.generic.generic_handler_t.LogAsIs.
50
- """
45
+ """"""
51
46
  self.stream.write(message + "\n")
52
47
  self.stream.flush()
53
48
 
54
- def DisplayRule(self, /, *, text: str | None = None, color: str = "white") -> None:
49
+ def DisplayRule(self, /, *, text: str | None = None, color: str = "black") -> None:
55
50
  """"""
56
- self.LogAsIs(RuleAsText(text))
51
+ self.LogAsIs(RuleAsText(text, None))
57
52
 
58
53
 
59
54
  """
@@ -22,8 +22,7 @@ else:
22
22
  EscapedForRich
23
23
  ) = DEFAULT_TERMINAL_THEME = None
24
24
 
25
- from logger_36.constant.record import SHOW_W_RULE_ATTR
26
- from logger_36.task.format.rule import Rule, RuleAsText
25
+ from logger_36.task.format.rule import Rule, RuleAsText, rule_t
27
26
  from logger_36.type.handler import handler_t as base_t
28
27
 
29
28
  LogAsIs_h = h.Callable[[str | h.Any], None]
@@ -54,18 +53,15 @@ class generic_handler_t(base_t):
54
53
 
55
54
  kind: h.ClassVar[str] = "g"
56
55
 
57
- @classmethod
58
- def New(
59
- cls,
60
- /,
61
- *,
62
- name: str | None = None,
63
- should_store_memory_usage: bool = False,
64
- message_width: int = -1,
65
- level: int = l.NOTSET,
66
- formatter: l.Formatter | None = None,
67
- **kwargs,
68
- ) -> h.Self:
56
+ def __init__(
57
+ self,
58
+ name: str | None,
59
+ should_store_memory_usage: bool,
60
+ message_width: int,
61
+ level: int,
62
+ formatter: l.Formatter | None,
63
+ kwargs,
64
+ ) -> None:
69
65
  """"""
70
66
  alternating_logs = kwargs.pop("alternating_logs", 0)
71
67
  LogAsIs = kwargs.pop("LogAsIs", lambda _, indented=False: print(_))
@@ -73,18 +69,24 @@ class generic_handler_t(base_t):
73
69
 
74
70
  assert alternating_logs in (0, 1, 2)
75
71
 
76
- output = cls(name, should_store_memory_usage, message_width, level, formatter)
77
-
78
- output.LogAsIs = LogAsIs
79
- output.console = None # console_t | None.
80
- output.console_options = None # console_options_t | None.
81
- output.alternating_logs = alternating_logs
82
- output.log_parity = False
83
- output.DisplayRule = None # DisplayRule_p | None.
84
-
85
- output.__post_init_local__(supports_html)
86
-
87
- return output
72
+ base_t.__init__(
73
+ self,
74
+ name,
75
+ should_store_memory_usage,
76
+ message_width,
77
+ level,
78
+ formatter,
79
+ kwargs,
80
+ )
81
+
82
+ self.LogAsIs = LogAsIs
83
+ self.DisplayRule = None # DisplayRule_p | None.
84
+ self.console = None # console_t | None.
85
+ self.console_options = None # console_options_t | None.
86
+ self.alternating_logs = alternating_logs
87
+ self._log_parity = False
88
+
89
+ self.__post_init_local__(supports_html)
88
90
 
89
91
  def __post_init_local__(self, supports_html: bool) -> None:
90
92
  """"""
@@ -97,28 +99,46 @@ class generic_handler_t(base_t):
97
99
  else:
98
100
  self.DisplayRule = self._DisplayRuleAsText
99
101
 
102
+ @classmethod
103
+ def New(
104
+ cls,
105
+ /,
106
+ *,
107
+ name: str | None = None,
108
+ should_store_memory_usage: bool = False,
109
+ message_width: int = -1,
110
+ level: int = l.NOTSET,
111
+ formatter: l.Formatter | None = None,
112
+ **kwargs,
113
+ ) -> h.Self:
114
+ """"""
115
+ return cls(
116
+ name, should_store_memory_usage, message_width, level, formatter, kwargs
117
+ )
118
+
100
119
  def emit(self, record: l.LogRecord, /) -> None:
101
120
  """"""
102
121
  if self.console is None:
103
- if hasattr(record, SHOW_W_RULE_ATTR):
104
- message = RuleAsText(record.msg)
105
- else:
106
- message = self.MessageFromRecord(record, line_width=self.message_width)
122
+ message = self.MessageFromRecord(
123
+ record, RuleAsText, line_width=self.message_width
124
+ )[0]
107
125
  else:
108
- if hasattr(record, SHOW_W_RULE_ATTR):
109
- richer = Rule(record.msg, DATE_TIME_COLOR)
110
- else:
111
- message = self.MessageFromRecord(
112
- record, line_width=self.message_width, PreProcessed=EscapedForRich
113
- )
114
- richer = HighlightedVersion(
126
+ message, is_not_a_rule = self.MessageFromRecord(
127
+ record,
128
+ Rule,
129
+ line_width=self.message_width,
130
+ color=DATE_TIME_COLOR,
131
+ PreProcessed=EscapedForRich,
132
+ )
133
+ if is_not_a_rule:
134
+ message = HighlightedVersion(
115
135
  self.console,
116
136
  message,
117
137
  record.levelno,
118
138
  self.alternating_logs,
119
- self.log_parity,
139
+ self._log_parity,
120
140
  )
121
- segments = self.console.render(richer, options=self.console_options)
141
+ segments = self.console.render(message, options=self.console_options)
122
142
 
123
143
  # Inspired from the code of: rich.console.export_html.
124
144
  html_segments = []
@@ -143,13 +163,13 @@ class generic_handler_t(base_t):
143
163
  )
144
164
 
145
165
  self.LogAsIs(message)
146
- self.log_parity = not self.log_parity
166
+ self._log_parity = not self._log_parity
147
167
 
148
168
  def _DisplayRuleAsText(
149
169
  self, /, *, text: str | None = None, color: str = "white"
150
170
  ) -> None:
151
171
  """"""
152
- self.LogAsIs(RuleAsText(text))
172
+ self.LogAsIs(RuleAsText(text, None))
153
173
 
154
174
  def _DisplayRule(self, /, *, text: str | None = None, color: str = "white") -> None:
155
175
  """"""
@@ -11,6 +11,7 @@ LEVEL_CLOSING = ")"
11
11
  MESSAGE_MARKER = "|"
12
12
  WHERE_SEPARATOR = "@"
13
13
  ELAPSED_TIME_SEPARATOR = "+"
14
+ FALLBACK_MESSAGE_WIDTH = 5
14
15
 
15
16
  DATE_FORMAT = "%Y-%m-%d"
16
17
  TIME_FORMAT = "%H:%M:%S"
@@ -0,0 +1,70 @@
1
+ """
2
+ Copyright CNRS/Inria/UniCA
3
+ Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023
4
+ SEE COPYRIGHT NOTICE BELOW
5
+ """
6
+
7
+ TITLE_PLACEHOLDER = "TITLE"
8
+ BODY_PLACEHOLDER = "BODY"
9
+
10
+ MINIMAL_HTML = f"""
11
+ <!DOCTYPE html>
12
+ <html lang="en">
13
+ <head>
14
+ <meta charset="utf-8">
15
+ <title>{TITLE_PLACEHOLDER}</title>
16
+ </head>
17
+ <body>
18
+ <pre>
19
+ {BODY_PLACEHOLDER}
20
+ </pre>
21
+ </body>
22
+ </html>
23
+ """
24
+
25
+
26
+ """
27
+ COPYRIGHT NOTICE
28
+
29
+ This software is governed by the CeCILL license under French law and
30
+ abiding by the rules of distribution of free software. You can use,
31
+ modify and/ or redistribute the software under the terms of the CeCILL
32
+ license as circulated by CEA, CNRS and INRIA at the following URL
33
+ "http://www.cecill.info".
34
+
35
+ As a counterpart to the access to the source code and rights to copy,
36
+ modify and redistribute granted by the license, users are provided only
37
+ with a limited warranty and the software's author, the holder of the
38
+ economic rights, and the successive licensors have only limited
39
+ liability.
40
+
41
+ In this respect, the user's attention is drawn to the risks associated
42
+ with loading, using, modifying and/or developing or reproducing the
43
+ software by the user in light of its specific status of free software,
44
+ that may mean that it is complicated to manipulate, and that also
45
+ therefore means that it is reserved for developers and experienced
46
+ professionals having in-depth computer knowledge. Users are therefore
47
+ encouraged to load and test the software's suitability as regards their
48
+ requirements in conditions enabling the security of their systems and/or
49
+ data to be ensured and, more generally, to use and operate it in the
50
+ same conditions as regards security.
51
+
52
+ The fact that you are presently reading this means that you have had
53
+ knowledge of the CeCILL license and that you accept its terms.
54
+
55
+ SEE LICENCE NOTICE: file README-LICENCE-utf8.txt at project source root.
56
+
57
+ This software is being developed by Eric Debreuve, a CNRS employee and
58
+ member of team Morpheme.
59
+ Team Morpheme is a joint team between Inria, CNRS, and UniCA.
60
+ It is hosted by the Centre Inria d'Université Côte d'Azur, Laboratory
61
+ I3S, and Laboratory iBV.
62
+
63
+ CNRS: https://www.cnrs.fr/index.php/en
64
+ Inria: https://www.inria.fr/en/
65
+ UniCA: https://univ-cotedazur.eu/
66
+ Centre Inria d'Université Côte d'Azur: https://www.inria.fr/en/centre/sophia/
67
+ I3S: https://www.i3s.unice.fr/en/
68
+ iBV: http://ibv.unice.fr/
69
+ Team Morpheme: https://team.inria.fr/morpheme/
70
+ """
@@ -5,10 +5,69 @@ SEE COPYRIGHT NOTICE BELOW
5
5
  """
6
6
 
7
7
  import difflib as diff
8
+ import logging as l
8
9
  import typing as h
9
10
 
11
+ from logger_36.config.message import (
12
+ LEVEL_CLOSING,
13
+ LEVEL_OPENING,
14
+ MESSAGE_MARKER,
15
+ WHERE_SEPARATOR,
16
+ )
10
17
  from logger_36.constant.generic import NOT_PASSED
11
- from logger_36.constant.message import expected_op_h
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
12
71
 
13
72
 
14
73
  def MessageWithActualExpected(
@@ -7,7 +7,7 @@ SEE COPYRIGHT NOTICE BELOW
7
7
  from logger_36.catalog.config.optional import RICH_IS_AVAILABLE
8
8
 
9
9
 
10
- def RuleAsText(text: str | None, /) -> str:
10
+ def RuleAsText(text: str | None, _: None, /) -> str:
11
11
  """"""
12
12
  if text is None:
13
13
  return "---- ---- ---- ---- ---- ---- ---- ---- ----"
@@ -27,7 +27,8 @@ if RICH_IS_AVAILABLE:
27
27
  return rule_t(title=text_t(text, style=f"bold {color}"), style=color)
28
28
 
29
29
  else:
30
- Rule = lambda _, __: RuleAsText(_)
30
+ rule_t = text_t = None
31
+ Rule = lambda _, __: RuleAsText(_, None)
31
32
 
32
33
  """
33
34
  COPYRIGHT NOTICE
@@ -8,6 +8,7 @@ import logging as l
8
8
  from io import IOBase as io_base_t
9
9
  from pathlib import Path as path_t
10
10
 
11
+ from logger_36.constant.html import BODY_PLACEHOLDER, MINIMAL_HTML, TITLE_PLACEHOLDER
11
12
  from logger_36.instance.logger import L
12
13
 
13
14
 
@@ -35,19 +36,39 @@ def SaveLOGasHTML(path: str | path_t | io_base_t | None = None) -> None:
35
36
  path = path_t(path)
36
37
 
37
38
  actual_file = isinstance(path, path_t)
39
+
38
40
  if actual_file and path.exists():
39
41
  L.warning(f'{cannot_save}: File "{path}" already exists.')
40
42
  return
41
43
 
42
- html = "\n".join(L.recorded)
43
- if html is None:
44
- L.warning(f"{cannot_save}: No handler could provide an HTML output.")
44
+ body = "\n".join(map(_HighlightedEvent, L.recorded))
45
+ html = MINIMAL_HTML.replace(TITLE_PLACEHOLDER, L.name).replace(
46
+ BODY_PLACEHOLDER, body
47
+ )
48
+ if actual_file:
49
+ with open(path, "w") as accessor:
50
+ accessor.write(html)
45
51
  else:
46
- if actual_file:
47
- with open(path, "w") as accessor:
48
- accessor.write(html)
49
- else:
50
- path.write(html)
52
+ path.write(html)
53
+
54
+
55
+ def _HighlightedEvent(event: tuple[int, str], /) -> str:
56
+ """"""
57
+ level, message = event
58
+ if level == l.DEBUG:
59
+ color = "BlueViolet"
60
+ elif level == l.INFO:
61
+ color = "black"
62
+ elif level == l.WARNING:
63
+ color = "gold"
64
+ elif level == l.ERROR:
65
+ color = "orange"
66
+ elif level == l.CRITICAL:
67
+ color = "red"
68
+ else:
69
+ color = "black"
70
+
71
+ return f'<span style="color:{color}">{message}</span>'
51
72
 
52
73
 
53
74
  """