logger-36 2025.18__py3-none-any.whl → 2025.20__py3-none-any.whl

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.
logger_36/api/content.py CHANGED
@@ -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
@@ -4,31 +4,10 @@ Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023
4
4
  SEE COPYRIGHT NOTICE BELOW
5
5
  """
6
6
 
7
- from logger_36.catalog.config.optional import RICH_IS_AVAILABLE
8
-
9
-
10
- def RuleAsText(text: str | None, _: None, /) -> str:
11
- """"""
12
- if text is None:
13
- return "---- ---- ---- ---- ---- ---- ---- ---- ----"
14
- else:
15
- return f"---- ---- ---- ---- {text} ---- ---- ---- ----"
16
-
17
-
18
- if RICH_IS_AVAILABLE:
19
- from rich.rule import Rule as rule_t # noqa
20
- from rich.text import Text as text_t # noqa
21
-
22
- def Rule(text: str | None, color: str, /) -> rule_t | str:
23
- """"""
24
- if text is None:
25
- return rule_t(style=color)
26
- else:
27
- return rule_t(title=text_t(text, style=f"bold {color}"), style=color)
28
-
29
- else:
30
- rule_t = text_t = None
31
- Rule = lambda _, __: RuleAsText(_, None)
7
+ RULE_CHARACTER = "-"
8
+ MIN_HALF_RULE_LENGTH = 4
9
+ DEFAULT_RULE_LENGTH = 50
10
+ DEFAULT_RULE = DEFAULT_RULE_LENGTH * RULE_CHARACTER
32
11
 
33
12
  """
34
13
  COPYRIGHT NOTICE
@@ -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(
logger_36/type/handler.py CHANGED
@@ -5,23 +5,38 @@ SEE COPYRIGHT NOTICE BELOW
5
5
  """
6
6
 
7
7
  import logging as l
8
- import sys as s
9
8
  import typing as h
10
9
  from pathlib import Path as path_t
11
10
 
12
- from logger_36.config.message import FALLBACK_MESSAGE_WIDTH
13
- from logger_36.task.format.message import MessageFromRecord
14
- from logger_36.task.format.rule import RuleAsText
15
- from logger_36.type.message import MessageFromRecord_h, RuleWithText_h
16
-
17
-
18
- class _base_t:
19
- def __init__(self, name: str | None, message_width: int) -> None:
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:
20
36
  """"""
21
37
  self.name = name
22
38
  self.message_width = message_width
23
- #
24
- self.MessageFromRecord: MessageFromRecord_h | None = None
39
+ self.PreProcessedMessage = PreProcessedMessage
25
40
 
26
41
  self.__post_init__()
27
42
 
@@ -40,37 +55,95 @@ class _base_t:
40
55
  """
41
56
  raise NotImplementedError
42
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
+
43
116
  def EmitAsIs(self, message: str, /) -> None:
44
117
  """"""
45
- s.__stdout__.write(message + "\n")
118
+ raise NotImplementedError
46
119
 
47
120
  def EmitRule(self, /, *, text: str | None = None, color: str = "black") -> None:
48
121
  """"""
49
- self.EmitAsIs(RuleAsText(text, None))
122
+ self.EmitAsIs(self.Rule(text, color=color))
50
123
 
51
124
 
52
- class handler_t(l.Handler, _base_t):
125
+ class handler_t(l.Handler, extension_t):
53
126
  def __init__(
54
127
  self,
55
128
  name: str | None,
56
129
  message_width: int,
130
+ PreProcessedMessage: h.Callable[[str], str] | None,
57
131
  level: int,
58
- formatter: l.Formatter | None,
59
132
  *_,
60
133
  ) -> None:
61
134
  """"""
62
135
  l.Handler.__init__(self)
63
- _base_t.__init__(self, name, message_width)
64
- __post_init__(self, level, formatter)
136
+ extension_t.__init__(self, name, message_width, PreProcessedMessage)
137
+ __post_init__(self, level)
65
138
 
66
139
 
67
- class file_handler_t(l.FileHandler, _base_t):
140
+ class file_handler_t(l.FileHandler, extension_t):
68
141
  def __init__(
69
142
  self,
70
143
  name: str | None,
71
144
  message_width: int,
145
+ PreProcessedMessage: h.Callable[[str], str] | None,
72
146
  level: int,
73
- formatter: l.Formatter | None,
74
147
  path: str | path_t | None,
75
148
  *_,
76
149
  ) -> None:
@@ -83,38 +156,17 @@ class file_handler_t(l.FileHandler, _base_t):
83
156
  raise ValueError(f"File or folder already exists: {path}.")
84
157
 
85
158
  l.FileHandler.__init__(self, path)
86
- _base_t.__init__(self, name, message_width)
87
- __post_init__(self, level, formatter)
159
+ extension_t.__init__(self, name, message_width, PreProcessedMessage)
160
+ __post_init__(self, level)
88
161
 
89
162
 
90
163
  any_handler_t = handler_t | file_handler_t
91
164
 
92
165
 
93
- def __post_init__(
94
- handler: any_handler_t, level: int, formatter: l.Formatter | None
95
- ) -> None:
166
+ def __post_init__(handler: any_handler_t, level: int) -> None:
96
167
  """"""
97
168
  handler.setLevel(level)
98
169
 
99
- if formatter is None:
100
- handler.MessageFromRecord = MessageFromRecord
101
- else:
102
- handler.setFormatter(formatter)
103
- _MessageFromRecordRaw = handler.formatter.format
104
-
105
- def _MessageFromRecord(
106
- record: l.LogRecord,
107
- _: RuleWithText_h,
108
- /,
109
- *,
110
- line_width: int = 0,
111
- PreProcessed: h.Callable[[str], str] | None = None,
112
- ) -> tuple[str, bool]:
113
- #
114
- return _MessageFromRecordRaw(record), False
115
-
116
- handler.MessageFromRecord = _MessageFromRecord
117
-
118
170
 
119
171
  """
120
172
  COPYRIGHT NOTICE
logger_36/type/logger.py CHANGED
@@ -38,12 +38,12 @@ from logger_36.constant.memory import UNKNOWN_MEMORY_USAGE
38
38
  from logger_36.constant.message import LINE_INDENT, TIME_LENGTH_m_1, expected_op_h
39
39
  from logger_36.constant.path import PROJECT_FILE_RELATIVE, USER_FOLDER
40
40
  from logger_36.constant.record import SHOW_W_RULE_ATTR, SHOW_WHERE_ATTR
41
- from logger_36.task.format.message import MessageFromRecord, MessageWithActualExpected
42
- from logger_36.task.format.rule import RuleAsText
41
+ from logger_36.task.format.message import MessageWithActualExpected
43
42
  from logger_36.task.measure.chronos import ElapsedTime
44
43
  from logger_36.task.measure.memory import CanCheckUsage as CanCheckMemoryUsage
45
44
  from logger_36.task.measure.memory import CurrentUsage as CurrentMemoryUsage
46
45
  from logger_36.type.handler import any_handler_t as base_handler_t
46
+ from logger_36.type.handler import extension_t as handler_extension_t
47
47
  from logger_36.type.issue import NewIssue, issue_t
48
48
 
49
49
  if RICH_IS_AVAILABLE:
@@ -98,9 +98,10 @@ class logger_t(base_t):
98
98
  init=False, default_factory=dict
99
99
  )
100
100
  intercepts_exceptions: bool = d.field(init=False, default=False)
101
- _on_hold: list[l.LogRecord] = d.field(init=False, default_factory=list)
102
101
  _should_hold_messages: bool = d.field(init=False, default=True)
103
102
  _should_activate_log_interceptions: bool = d.field(init=False, default=False)
103
+ _on_hold: list[l.LogRecord] = d.field(init=False, default_factory=list)
104
+ _recording_handler: handler_extension_t | None = d.field(init=False, default=None)
104
105
 
105
106
  name_: d.InitVar[str | None] = None
106
107
  level_: d.InitVar[int] = l.NOTSET
@@ -164,8 +165,11 @@ class logger_t(base_t):
164
165
  self.setLevel(level_)
165
166
  self.propagate = False # Part of base_t.
166
167
 
167
- for level in l.getLevelNamesMapping().values():
168
- self.events[level] = 0
168
+ if self.should_record_messages:
169
+ self.ActivateMessageRecording()
170
+
171
+ for level_id in l.getLevelNamesMapping().values():
172
+ self.events[level_id] = 0
169
173
 
170
174
  if activate_wrn_interceptions:
171
175
  self.ToggleWarningInterceptions(True)
@@ -190,35 +194,10 @@ class logger_t(base_t):
190
194
  elapsed_time, now = ElapsedTime(should_return_now=True)
191
195
 
192
196
  if (self._on_hold.__len__() > 0) and not self._should_hold_messages:
193
- for held in self._on_hold:
194
- if self.should_record_messages:
195
- self.recorded.append(
196
- (held.levelno, MessageFromRecord(held, RuleAsText)[0])
197
- )
198
- base_t.handle(self, held)
199
- self._on_hold.clear()
197
+ self._FlushRecordsOnHold()
200
198
 
201
199
  if (date := now.date()) != self.last_message_date:
202
- self.last_message_date = date
203
- date_record = l.makeLogRecord(
204
- {
205
- "name": self.name,
206
- "levelno": l.INFO, # For management by logging.Logger.handle.
207
- "msg": f"DATE: {date.strftime(DATE_FORMAT)}",
208
- SHOW_W_RULE_ATTR: True,
209
- }
210
- )
211
- if self._should_hold_messages:
212
- self._on_hold.append(date_record)
213
- else:
214
- if self.should_record_messages:
215
- self.recorded.append(
216
- (
217
- date_record.levelno,
218
- MessageFromRecord(date_record, RuleAsText)[0],
219
- )
220
- )
221
- base_t.handle(self, date_record)
200
+ self._AcknowledgeDateChange(date)
222
201
 
223
202
  # When.
224
203
  if now - self.last_message_now > LONG_ENOUGH:
@@ -246,9 +225,6 @@ class logger_t(base_t):
246
225
  else:
247
226
  where = None
248
227
 
249
- # How.
250
- record.level_first_letter = record.levelname[0]
251
-
252
228
  # What.
253
229
  if not isinstance(record.msg, str):
254
230
  record.msg = str(record.msg)
@@ -256,11 +232,11 @@ class logger_t(base_t):
256
232
  if self._should_hold_messages:
257
233
  self._on_hold.append(record)
258
234
  else:
259
- if self.should_record_messages:
260
- self.recorded.append(
261
- (record.levelno, MessageFromRecord(record, RuleAsText)[0])
262
- )
263
- base_t.handle(self, record)
235
+ self._HandleRecord(record)
236
+ self.events[record.levelno] += 1
237
+
238
+ if self.should_watch_memory_usage:
239
+ self.memory_usages.append((where, CurrentMemoryUsage()))
264
240
 
265
241
  if (self.exit_on_critical and (record.levelno is l.CRITICAL)) or (
266
242
  self.exit_on_error and (record.levelno is l.ERROR)
@@ -269,34 +245,49 @@ class logger_t(base_t):
269
245
  # __post_init__ set self.exit_on_critical if self.exit_on_error.
270
246
  s.exit(1)
271
247
 
272
- self.events[record.levelno] += 1
248
+ def _FlushRecordsOnHold(self) -> None:
249
+ """"""
250
+ for held in self._on_hold:
251
+ self._HandleRecord(held)
273
252
 
274
- if self.should_watch_memory_usage:
275
- self.memory_usages.append((where, CurrentMemoryUsage()))
253
+ self._on_hold.clear()
276
254
 
277
- def MakeMonochrome(self) -> None:
255
+ def _AcknowledgeDateChange(self, date: date_t, /) -> None:
278
256
  """"""
279
- self.AddHandler(console_handler_t)
257
+ self.last_message_date = date
258
+
259
+ record = l.makeLogRecord(
260
+ {
261
+ "name": self.name,
262
+ "levelno": l.INFO, # For management by logging.Logger.handle.
263
+ "msg": f"DATE: {date.strftime(DATE_FORMAT)}",
264
+ SHOW_W_RULE_ATTR: True,
265
+ }
266
+ )
280
267
 
281
- def MakeRich(self, *, alternating_logs: int = 0) -> None:
268
+ if self._should_hold_messages:
269
+ self._on_hold.append(record)
270
+ else:
271
+ self._HandleRecord(record)
272
+
273
+ def _HandleRecord(self, record: l.LogRecord, /) -> None:
282
274
  """"""
283
- if MISSING_RICH_MESSAGE is not None:
284
- s.__stderr__.write(MISSING_RICH_MESSAGE + "\n")
275
+ if self.should_record_messages:
276
+ message = self._recording_handler.MessageFromRecord(record)[0]
277
+ self.recorded.append((record.levelno, message))
285
278
 
286
- if console_rich_handler_t is console_handler_t:
287
- handler_kwargs = {}
288
- else:
289
- handler_kwargs = {"alternating_logs": alternating_logs}
290
- self.AddHandler(console_rich_handler_t, **handler_kwargs)
279
+ base_t.handle(self, record)
291
280
 
292
- def MakePermanent(self, path: str | path_t, /) -> None:
281
+ def ActivateMessageRecording(self) -> None:
293
282
  """"""
294
- self.AddHandler(file_handler_t, path=path)
283
+ self._recording_handler = handler_extension_t("recording_handler", 0, None)
284
+ self.should_record_messages = True # Useless if called from __post_init__.
285
+ self.info(f'Message recording activated for logger "{self.name}"')
295
286
 
296
287
  def ResetEventCounts(self) -> None:
297
288
  """"""
298
- for level in self.events:
299
- self.events[level] = 0
289
+ for level_id in self.events:
290
+ self.events[level_id] = 0
300
291
 
301
292
  def ToggleWarningInterceptions(self, state: bool, /) -> None:
302
293
  """"""
@@ -373,27 +364,17 @@ class logger_t(base_t):
373
364
  self.intercepts_exceptions = False
374
365
  self.info("Exception Interception: OFF")
375
366
 
376
- def DealWithException(self, _, exc_value, exc_traceback, /) -> None:
377
- """"""
378
- exception = exc_value.with_traceback(exc_traceback)
379
- self.LogException(exception, level=l.CRITICAL)
380
- s.exit(1)
381
-
382
- def DealWithExceptionInThread(
383
- self, exc_type, exc_value, exc_traceback, _, /
384
- ) -> None:
385
- """"""
386
- self.DealWithException(exc_type, exc_value, exc_traceback)
387
-
388
367
  def AddHandler(
389
368
  self,
390
- handler_t: type[base_handler_t],
369
+ handler_t_or_handler: type[base_handler_t]
370
+ | base_handler_t
371
+ | l.Handler
372
+ | l.FileHandler,
391
373
  /,
392
374
  *,
393
375
  name: str | None = None,
394
376
  level: int = l.INFO,
395
377
  message_width: int = -1,
396
- formatter: l.Formatter | None = None,
397
378
  should_still_hold_messages: bool = False,
398
379
  **kwargs,
399
380
  ) -> None:
@@ -404,13 +385,12 @@ class logger_t(base_t):
404
385
 
405
386
  self._should_hold_messages = should_still_hold_messages
406
387
 
407
- handler = handler_t.New(
408
- name=name,
409
- message_width=message_width,
410
- level=level,
411
- formatter=formatter,
412
- **kwargs,
413
- )
388
+ if isinstance(handler_t_or_handler, type):
389
+ handler = handler_t_or_handler.New(
390
+ name=name, message_width=message_width, level=level, **kwargs
391
+ )
392
+ else:
393
+ handler = handler_t_or_handler
414
394
  base_t.addHandler(self, handler)
415
395
 
416
396
  path = getattr(handler, "baseFilename", "")
@@ -421,6 +401,25 @@ class logger_t(base_t):
421
401
  f"level {handler.level}={l.getLevelName(handler.level)}{path}"
422
402
  )
423
403
 
404
+ def MakeMonochrome(self) -> None:
405
+ """"""
406
+ self.AddHandler(console_handler_t)
407
+
408
+ def MakeRich(self, *, alternating_logs: int = 0) -> None:
409
+ """"""
410
+ if MISSING_RICH_MESSAGE is not None:
411
+ s.__stderr__.write(MISSING_RICH_MESSAGE + "\n")
412
+
413
+ if console_rich_handler_t is console_handler_t:
414
+ handler_kwargs = {}
415
+ else:
416
+ handler_kwargs = {"alternating_logs": alternating_logs}
417
+ self.AddHandler(console_rich_handler_t, **handler_kwargs)
418
+
419
+ def MakePermanent(self, path: str | path_t, /) -> None:
420
+ """"""
421
+ self.AddHandler(file_handler_t, path=path)
422
+
424
423
  def __call__(self, *args, **kwargs) -> None:
425
424
  """
426
425
  For a print-like calling for print-based debugging.
@@ -483,13 +482,26 @@ class logger_t(base_t):
483
482
  message = f"Exception of type {type(exception).__name__}\n----\n{formatted}"
484
483
  self.log(level, message, extra={SHOW_WHERE_ATTR: False})
485
484
 
485
+ def DealWithException(self, _, exc_value, exc_traceback, /) -> None:
486
+ """"""
487
+ exception = exc_value.with_traceback(exc_traceback)
488
+ self.LogException(exception, level=l.CRITICAL)
489
+ s.exit(1)
490
+
491
+ def DealWithExceptionInThread(
492
+ self, exc_type, exc_value, exc_traceback, _, /
493
+ ) -> None:
494
+ """"""
495
+ self.DealWithException(exc_type, exc_value, exc_traceback)
496
+
486
497
  def LogAsIs(self, message: str, /, *, indented: bool = False) -> None:
487
498
  """"""
488
499
  if indented:
489
500
  message = text.indent(message, LINE_INDENT)
490
501
 
491
502
  for handler in self.handlers:
492
- handler.EmitAsIs(message)
503
+ if (EmitAsIs := getattr(handler, "EmitAsIs", None)) is not None:
504
+ EmitAsIs(message)
493
505
 
494
506
  info_raw = LogAsIs # To follow the convention of the logging methods info, error...
495
507
 
@@ -498,7 +510,8 @@ class logger_t(base_t):
498
510
  ) -> None:
499
511
  """"""
500
512
  for handler in self.handlers:
501
- handler.EmitRule(text=message, color=color)
513
+ if (EmitRule := getattr(handler, "EmitRule", None)) is not None:
514
+ EmitRule(text=message, color=color)
502
515
 
503
516
  def AddContextLevel(self, new_level: str, /) -> None:
504
517
  """"""
logger_36/version.py CHANGED
@@ -4,7 +4,7 @@ Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023
4
4
  SEE COPYRIGHT NOTICE BELOW
5
5
  """
6
6
 
7
- __version__ = "2025.18"
7
+ __version__ = "2025.20"
8
8
 
9
9
  """
10
10
  COPYRIGHT NOTICE
@@ -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
@@ -1,18 +1,18 @@
1
1
  logger_36/__init__.py,sha256=mK6AD0eWI2Sk42oxleTvsxzYJ28FbHK5WNkpLgAhnNE,2129
2
- logger_36/version.py,sha256=IGK2AtFu27rKPgT-azGp5zuAMQXCLFqA56YY96_pPQA,1680
3
- logger_36/api/content.py,sha256=0pKjxSG1hmxAkf4tBvXXOI_qrtbSx7_4Vyz-9BF_-MQ,1855
2
+ logger_36/version.py,sha256=vDO1d9-QYkQ8yZUGGG46kdG9NRQx6ONSvh6-E6JGHu4,1680
3
+ logger_36/api/content.py,sha256=DuT4UX4r_1DTXzuuRD-tvsTZk5X-Nj11loBKhuWOMw0,1791
4
4
  logger_36/api/gpu.py,sha256=NNs1IvQ7bh8Dppm8O8K2YLWbm4rogc3Ie_-D6xzkX3g,1726
5
5
  logger_36/api/memory.py,sha256=vOY4cTTrC3u7L0OrKXdPNlsCahYjCrY4h7iqpGZv9kU,2217
6
6
  logger_36/api/storage.py,sha256=RYjn2TD-E1zfNTMgm4b2mbYNVtTwsCUMbuPlMbuvgP0,1774
7
7
  logger_36/api/system.py,sha256=h-3GfhZPwawv0UKBWKkT1LzxSCZwpA2VIsy03lLYi6w,1725
8
8
  logger_36/api/time.py,sha256=JG0vgzPSRZ7UWQyoihnVu4sjPC-okFIKA3ZyNh2GaZo,1798
9
9
  logger_36/api/type.py,sha256=eLZ2yuH-sYeh4Z2KnAwTRJEbmkmgzBPMncdqXfFUTG8,1760
10
- logger_36/catalog/config/console_rich.py,sha256=tHUM1hUg-RTBXamjK_EkNO1Woob9ML2R_o6WBkzwe4A,2335
10
+ logger_36/catalog/config/console_rich.py,sha256=t9p9-AkSgPiLAsm1evAdbz77g7JcVLePhUJ1FzNi3cY,2330
11
11
  logger_36/catalog/config/optional.py,sha256=5vabOlEQIFxoT_y4AjP19rpOjBuUJplpuBkLoCIKImA,1872
12
- logger_36/catalog/handler/console.py,sha256=fJt9zAjEh0XbORO-81mWX4QjPu0bJaf9sCbgTCtcFkY,2390
13
- logger_36/catalog/handler/console_rich.py,sha256=nGpt3iayFBkOVHqs_oa02W8__QmBykNDZb-QsK_Rrok,6347
14
- logger_36/catalog/handler/file.py,sha256=pezjcFCaA5zdV3GtHLaU_pfsp_YopvKYGHJAOZ69Vzs,2621
15
- logger_36/catalog/handler/generic.py,sha256=Gye1wS7VialSK6TbZi1g2yk67tG1bVIk5kWtw__BUlk,6683
12
+ logger_36/catalog/handler/console.py,sha256=OKtRG3PseT06_UwZBtbragNIKvCqa7ZsLphYEsgeq2c,2308
13
+ logger_36/catalog/handler/console_rich.py,sha256=b3B_pQefv0IeQFEj-tZ5-tH8RrcessZI8sI4Tyf17bg,6180
14
+ logger_36/catalog/handler/file.py,sha256=JReN8KruN8eKhBfG9sj72kkHfukHK4-8mdSxNpgFhLU,2455
15
+ logger_36/catalog/handler/generic.py,sha256=0Z4Vm-AAc92szghYEzihkm9CBRzCvqgdUTXH_p98S4o,6314
16
16
  logger_36/catalog/logger/chronos.py,sha256=S4m9TMPQy_Ju500mpE1jNzu2gZG-QKdVuvX9RVRKHR8,1911
17
17
  logger_36/catalog/logger/gpu.py,sha256=Py5YY0nD_pqJzJsEKQYoOGHcPqyNVJ3J2noOS3hDL6g,2890
18
18
  logger_36/catalog/logger/memory.py,sha256=J0ZGKO7j1FZA_aDGxpABtvzDy1RjCDiDmWYh4U98fEI,4253
@@ -30,6 +30,7 @@ logger_36/constant/memory.py,sha256=Q_E5tTWa-cGaNwrE_xmKa3BxQG6oJO6DHczrxc_M4sE,
30
30
  logger_36/constant/message.py,sha256=YJOEzdI0ZjUOdHo3CsiS56FVPhrfNoQYvXuUkprH61g,2312
31
31
  logger_36/constant/path.py,sha256=OfLh70Jyc8po9Ls34nQh_bRr3PXyQ3kF9ciR9QPhiqI,2213
32
32
  logger_36/constant/record.py,sha256=gQCGLxq8Vs789Ty_qaRNKy18mqlyMT_4kyN-T9r_rGE,1734
33
+ logger_36/constant/rule.py,sha256=tBKQgPTt6G_p5eInDdWoEEAvQFz4WMSt5THsS5jvk14,1779
33
34
  logger_36/constant/system.py,sha256=pLlLXG5sepQlSUOo3TphaGrHg8xzJBp-GxpL2NPP47k,1904
34
35
  logger_36/extension/html_.py,sha256=W9SyiYsaaYHUrHLGAAN2wiJGXUlwOBJ5gzdjmEcnF18,3342
35
36
  logger_36/extension/line.py,sha256=joeojQX1bZJM53333mOEU3s-YC5ExGOrN9Cu9Lh5-FU,2617
@@ -38,16 +39,14 @@ logger_36/instance/loggers.py,sha256=inBk4KKrQ-z3szaopQ29-qQwh1iSc842sWo5J6zJoiM
38
39
  logger_36/task/inspection.py,sha256=ZgPcrPo3h_kEnM-UpHDLg7PAFfB2fqsLFdfmi6hlPVA,4484
39
40
  logger_36/task/storage.py,sha256=KAILmJlF5IULxEX9QRCyXCwcalp5mpunWVh1oXuLvSs,3516
40
41
  logger_36/task/format/memory.py,sha256=J1Oy3jw8wjSp2kuiRUm_VFpzXOHX2FOc7nuRrCyrskw,3723
41
- logger_36/task/format/message.py,sha256=SqVtiWzcuMl5xPI2kmz2wkQP05amX3wKy0C0qdfXB3I,5245
42
- logger_36/task/format/rule.py,sha256=4wQpeBBbCeF13rXl3Q9UEvvYh4sJOFfk6hOvJHDXYvY,2384
42
+ logger_36/task/format/message.py,sha256=Rm6zymVEEGcgKfmxMPXP7q3PtwZJKlXGhqZ5tnvlwxA,3502
43
43
  logger_36/task/measure/chronos.py,sha256=7ijMZgP4EP18HbLV2yCxpNpRS9724Wyk523f-nkbhUM,2529
44
44
  logger_36/task/measure/memory.py,sha256=kkPHEIUTUhkCOLrAt01eLJLnsnkl0nFPNhFZdIB_JAw,1991
45
- logger_36/type/handler.py,sha256=JxUGjFSHFnX-6xM5f5vD00e1OQMPlPlLvopR32qd1kY,4763
45
+ logger_36/type/handler.py,sha256=ChxP1j9PXLmoiNcsOdxI4bYVdr75v7HeCWp_iYJ2WNY,6602
46
46
  logger_36/type/issue.py,sha256=QHAYf7QgrjJUtF2D46z6X630qTgeP_0FE5hIwf54RsE,2688
47
- logger_36/type/logger.py,sha256=UuZt8A4AtWU78k9ryCY6gFe-Q7xzFrDvqbTEX10EgXs,25012
47
+ logger_36/type/logger.py,sha256=tWdexeJT9IrxKK2NQmnHi1WVmN16xR8RNZPMc__SWBs,25447
48
48
  logger_36/type/loggers.py,sha256=7EX7Sg_RlduBjdfFlNZmUfNeDloH1xU30Rdkg_-rXh8,3172
49
- logger_36/type/message.py,sha256=pv9yJKd_GXuctUFWIjvcFoekNrBu0hDg0fqj_KAFwjI,2537
50
- logger_36-2025.18.dist-info/METADATA,sha256=uUcAg2M2fBGUYwHYNmRvCpM-ShfH-VxsCNbL3oheDK0,6529
51
- logger_36-2025.18.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
52
- logger_36-2025.18.dist-info/top_level.txt,sha256=sM95BTMWmslEEgR_1pzwZsOeSp8C_QBiu8ImbFr0XLc,10
53
- logger_36-2025.18.dist-info/RECORD,,
49
+ logger_36-2025.20.dist-info/METADATA,sha256=On7rI0r81M6ehWSyX7ZPiBKYVZoLqMPj-1VJWRuSd50,6529
50
+ logger_36-2025.20.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
51
+ logger_36-2025.20.dist-info/top_level.txt,sha256=sM95BTMWmslEEgR_1pzwZsOeSp8C_QBiu8ImbFr0XLc,10
52
+ logger_36-2025.20.dist-info/RECORD,,
logger_36/type/message.py DELETED
@@ -1,76 +0,0 @@
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
-
10
- from logger_36.catalog.config.optional import RICH_IS_AVAILABLE
11
-
12
-
13
- @h.runtime_checkable
14
- class _RuleAsText_p(h.Protocol):
15
- def __call__(self, text: str | None, color: None, /) -> str: ...
16
-
17
-
18
- if RICH_IS_AVAILABLE:
19
- from rich.rule import Rule as rule_t # noqa
20
-
21
- @h.runtime_checkable
22
- class _Rule_p(h.Protocol):
23
- def __call__(self, text: str | None, color: str, /) -> rule_t | str: ...
24
- else:
25
- _Rule_p = None
26
-
27
- RuleWithText_h = _RuleAsText_p | _Rule_p
28
-
29
-
30
- @h.runtime_checkable
31
- class _MessageFromRecordPreprocessed_p(h.Protocol):
32
- def __call__(
33
- self,
34
- record: l.LogRecord,
35
- RuleWithText: RuleWithText_h,
36
- /,
37
- *,
38
- line_width: int = 0,
39
- PreProcessed: h.Callable[[str], str] | None = None,
40
- ) -> tuple[str, bool]: ...
41
-
42
-
43
- MessageFromRecord_h = _MessageFromRecordPreprocessed_p
44
-
45
-
46
- """
47
- COPYRIGHT NOTICE
48
-
49
- This software is governed by the CeCILL license under French law and
50
- abiding by the rules of distribution of free software. You can use,
51
- modify and/ or redistribute the software under the terms of the CeCILL
52
- license as circulated by CEA, CNRS and INRIA at the following URL
53
- "http://www.cecill.info".
54
-
55
- As a counterpart to the access to the source code and rights to copy,
56
- modify and redistribute granted by the license, users are provided only
57
- with a limited warranty and the software's author, the holder of the
58
- economic rights, and the successive licensors have only limited
59
- liability.
60
-
61
- In this respect, the user's attention is drawn to the risks associated
62
- with loading, using, modifying and/or developing or reproducing the
63
- software by the user in light of its specific status of free software,
64
- that may mean that it is complicated to manipulate, and that also
65
- therefore means that it is reserved for developers and experienced
66
- professionals having in-depth computer knowledge. Users are therefore
67
- encouraged to load and test the software's suitability as regards their
68
- requirements in conditions enabling the security of their systems and/or
69
- data to be ensured and, more generally, to use and operate it in the
70
- same conditions as regards security.
71
-
72
- The fact that you are presently reading this means that you have had
73
- knowledge of the CeCILL license and that you accept its terms.
74
-
75
- SEE LICENCE NOTICE: file README-LICENCE-utf8.txt at project source root.
76
- """