protoerror 3.20.2__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.
protoerror/solution.py ADDED
@@ -0,0 +1,300 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2019-2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================
9
+ """Solution
10
+ ========
11
+
12
+ Solution describes a possible fix for the user to solve an error
13
+ which was told by checker.
14
+
15
+ Possible solution:
16
+ * Hint as text block
17
+ * Homepage
18
+ * Link to internal documentation
19
+
20
+ .. code-block :: none
21
+
22
+ Writen over border
23
+ ------------------
24
+ id:int
25
+ msgid/problem:E0007
26
+ title: Text element hits the autodetected border
27
+ description:
28
+ solution:open,hide,disagree,solved
29
+
30
+ solution:
31
+ open
32
+ hide
33
+ disagree
34
+ solved
35
+
36
+ Pattern
37
+ -------
38
+
39
+ Define a solution message in short pattern:
40
+
41
+ .. code-block:: python
42
+
43
+ S2000=\"""
44
+ TITLE
45
+
46
+ MESSAGE\"""
47
+
48
+ Define a solution message:
49
+
50
+ .. code-block:: python
51
+
52
+ SOLUTION_2000=\"""
53
+ TITLE
54
+
55
+ MESSAGE\"""
56
+
57
+ .. todo:: Enable printing solution list, do we need this?
58
+ """
59
+
60
+ import copy
61
+ import re
62
+
63
+ import iamraw
64
+ import jinja2
65
+ import utilo
66
+
67
+ import protoerror
68
+ import protoerror.messages
69
+ import protoerror.utils
70
+
71
+ Validators = list[callable]
72
+
73
+
74
+ class Solver:
75
+
76
+ def __init__(self):
77
+ self.solutions = {}
78
+
79
+ def add_solution(self, msgid: str, solution: iamraw.Solution):
80
+ assert msgid, solution
81
+ msgid = protoerror.messages.parse_msgid(msgid, idonly=True)
82
+ self.solutions[msgid] = solution
83
+
84
+ def append(self, item: iamraw.Solution):
85
+ msgid = protoerror.messages.parse_msgid(item.msgid, idonly=True)
86
+ self.solutions[msgid] = item
87
+
88
+ def solution(self, msgid: str, **kwargs) -> iamraw.Solution:
89
+ msgid = protoerror.messages.parse_msgid(msgid, idonly=True)
90
+ try:
91
+ result = copy.deepcopy(self.solutions[msgid])
92
+ except KeyError:
93
+ return None
94
+ result.title = render_template(result.title, **kwargs)
95
+ result.description = render_template(result.description, **kwargs)
96
+ return result
97
+
98
+ @classmethod
99
+ def fromlist(cls, solutions: list):
100
+ assert isinstance(solutions, list), str(solutions)
101
+ result = cls()
102
+ for item in solutions:
103
+ result.add_solution(item.msgid, item)
104
+ return result
105
+
106
+ @classmethod
107
+ def fromdict(cls, solutions: dict):
108
+ assert isinstance(solutions, dict), str(solutions)
109
+ result = cls()
110
+ for msgid, solution in solutions.items():
111
+ result.add_solution(msgid, solution)
112
+ return result
113
+
114
+
115
+ def render_template(raw: str, **kwargs) -> str:
116
+ template = jinja2.Template(
117
+ raw,
118
+ lstrip_blocks=True,
119
+ trim_blocks=True,
120
+ keep_trailing_newline=False,
121
+ )
122
+ kwargs = {
123
+ key: escape(value) if isinstance(value, str) else value
124
+ for key, value in kwargs.items()
125
+ }
126
+ rendered = template.render(**kwargs)
127
+ # strip final line
128
+ rendered = rendered.strip()
129
+ return rendered
130
+
131
+
132
+ SOLUTION_PATTERN = r'^SOLUTION_(?P<type>[A-Z]{0,1})(?P<number>\d{2,5})$'
133
+ SOLUTION_PATTERN_SIMPLE = r'^S(?P<type>[A-Z]){0,1}(?P<number>\d{2,5})$'
134
+
135
+
136
+ def parse_solutions( # pylint:disable=R1260
137
+ module,
138
+ tests: set = None,
139
+ skips: set = None,
140
+ ) -> iamraw.Solutions:
141
+ if isinstance(module, str):
142
+ module = protoerror.utils.module_fromname(module)
143
+ result = []
144
+ for name, value in vars(module).items():
145
+ # try different pattern to find solution, presented to the user
146
+ for pattern in (SOLUTION_PATTERN_SIMPLE, SOLUTION_PATTERN):
147
+ matched = re.match(pattern, name)
148
+ if matched:
149
+ break
150
+ if not matched:
151
+ continue
152
+ # extract solution id
153
+ number = int(matched['number'])
154
+ if should_skip(number, tests, skips):
155
+ continue
156
+ try:
157
+ typ = matched['type'] or protoerror.TYPE_DEFAULT
158
+ except IndexError:
159
+ # TODO: ADD DEFAULT LEVEL TO IAMRAW?
160
+ typ = protoerror.TYPE_DEFAULT
161
+ try:
162
+ title, message = value.split('\n\n', maxsplit=1)
163
+ except ValueError:
164
+ utilo.error(f'{name} requires newline between headline and content')
165
+ raise
166
+ label = f'{typ}{number}'
167
+ item = iamraw.Text(title=title, msgid=label, description=message)
168
+ result.append(item)
169
+ return result
170
+
171
+
172
+ def should_skip(number: int, tests: set = None, skips: set = None) -> bool:
173
+ """\
174
+ >>> should_skip(5, tests={1,2,3,4,5})
175
+ select: 5
176
+ False
177
+ >>> should_skip(5, skips={1,2,3,4,5})
178
+ skips: 5
179
+ True
180
+ >>> should_skip(5, tests={1,2,})
181
+ True
182
+ """
183
+ tests = tests if tests is not None else {}
184
+ skips = skips if skips is not None else {}
185
+ if number in skips:
186
+ utilo.log(f'skips: {number}')
187
+ return True
188
+ if tests:
189
+ if number not in tests:
190
+ return True
191
+ utilo.log(f'select: {number}')
192
+ return False
193
+
194
+
195
+ def parse_checkers(module, tests: set = None, skips: set = None) -> Validators:
196
+ """Parse def check_{number}_{name} methods out of a module."""
197
+ if isinstance(module, str):
198
+ module = protoerror.utils.module_fromname(module)
199
+ result = []
200
+ for name, value in vars(module).items():
201
+ parsed = parse_msgid(name)
202
+ if not parsed:
203
+ continue
204
+ parsed = int(parsed)
205
+ if should_skip(parsed, tests=tests, skips=skips):
206
+ continue
207
+ value.msgid = parsed
208
+ if not hasattr(value, 'confidence'):
209
+ # enable on default
210
+ value.confidence = 1.0
211
+ result.append(value)
212
+ return result
213
+
214
+
215
+ VALIDATOR_PATTERN = r'^check_(?P<type>[a-zA-Z]{0,1})(?P<number>\d{2,5})_'
216
+
217
+
218
+ def parse_msgid(name) -> int:
219
+ """Parse the message id of a checker method.
220
+
221
+ Expected pattern:
222
+
223
+ .. code-block :: python
224
+
225
+ def check_1337_this_is_an_error_checker():
226
+ pass
227
+ """
228
+ matched = re.match(VALIDATOR_PATTERN, name)
229
+ if not matched:
230
+ return None
231
+ result = int(matched['number'])
232
+ return result
233
+
234
+
235
+ def confidence(value=1.0):
236
+ """Decorator to define confidence a confidence level for a checker
237
+ method. The default confidence of a checker is 1.0, that means it is
238
+ enabled for the user. To pipe linting results as developer results,
239
+ a lower confidence must be defined."""
240
+
241
+ def decorating_function(user_function):
242
+ user_function.confidence = value
243
+ return user_function
244
+
245
+ return decorating_function
246
+
247
+
248
+ # Pay attention to the order to avoid internal replacements of html chars.
249
+ REPLACE = r'#*${}<>:=[]?!()@%/'
250
+
251
+
252
+ def escape(text: str) -> str:
253
+ """Convert chars to html code to avoid processing problems in
254
+ Markdown-Replacement.
255
+
256
+ >>> escape('*')
257
+ '&#42;'
258
+ >>> escape('*#$')
259
+ '&#42;&#35;&#36;'
260
+
261
+ Ensure that all characters are replaced
262
+ >>> assert escape(REPLACE).count('#') == len(REPLACE)
263
+ >>> escape('{{hn:4:nh}}')
264
+ '<sup>4</sup>'
265
+ """
266
+ # TODO: REMOVE LATER
267
+ text = re.sub(r'\{\{hn\:(\d{1,3})\:nh\}\}', r'<sup>\1</sup>', text)
268
+ for char in REPLACE:
269
+ text = text.replace(char, f'&#{ord(char)};')
270
+ # allow basic html styling
271
+ for char in 'i u b del sup'.split():
272
+ text = text.replace(f'&#60;{char}&#62;', f'<{char}>')
273
+ text = text.replace(f'&#60;&#47;{char}&#62;', f'</{char}>')
274
+ return text
275
+
276
+
277
+ SOLUTION = [
278
+ iamraw.Text(
279
+ title='Interner Fehler: Dokument konnte nicht gelesen werden',
280
+ description=(
281
+ 'Das Dokument konnte nicht eingelesen werden.\n\n'
282
+ 'Dieser Fehler wurde durch eine korrupte PDF-Datei verursacht oder '
283
+ 'ist durch einen technischen Fehler der `checkitweg`-Plattform '
284
+ 'entstanden.\n\n'
285
+ 'Ein Techniker arbeitet an der Loesung dieses Problems.'),
286
+ status=iamraw.ProblemStatus.CLOSED,
287
+ msgid='F0000',
288
+ ),
289
+ iamraw.Text(
290
+ title='Interner Fehler: Dokument konnte verarbeitet werden',
291
+ description=('Das Dokument konnte nicht verarbeitet werden.\n\n'
292
+ 'Dieser Fehler ist durch einen technischen Fehler der '
293
+ '`checkitweg`-Plattform verursacht worden.\n\n'
294
+ 'Ein Techniker arbeitet an der Loesung dieses Problems.'),
295
+ status=iamraw.ProblemStatus.CLOSED,
296
+ msgid='F0001',
297
+ ),
298
+ ]
299
+
300
+ SOLUTION = {item.msgid: item for item in SOLUTION}
protoerror/utils.py ADDED
@@ -0,0 +1,49 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2020-2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================
9
+
10
+ import collections
11
+ import importlib
12
+ import inspect
13
+
14
+ import utilo
15
+
16
+ RESULT_EMPTY = '[]', '[]'
17
+
18
+
19
+ def driver(**kwargs):
20
+ """\
21
+ >>> driver(name='Helmut', age=33)
22
+ Driver(name='Helmut', age=33)
23
+ """
24
+ driverx = collections.namedtuple('Driver', kwargs.keys())
25
+ result = driverx(**kwargs)
26
+ return result
27
+
28
+
29
+ def module_fromname(name: str):
30
+ module = importlib.import_module(name)
31
+ return module
32
+
33
+
34
+ def skip_method(msg: str = '', methodstart='check_'):
35
+ """\
36
+ >>> def magic_spell():
37
+ ... skip_method('No enough mana.', methodstart='magic')
38
+ >>> import utilo
39
+ >>> with utilo.level_tmp(utilo.Level.DEBUG):
40
+ ... magic_spell()
41
+ No enough mana.
42
+ skip: magic_spell
43
+ """
44
+ frame = inspect.currentframe()
45
+ caller = [item.function for item in inspect.getouterframes(frame)[0:5]]
46
+ caller = [item for item in caller if methodstart in item]
47
+ caller = ' '.join(caller)
48
+ utilo.debug(msg)
49
+ utilo.debug(f'skip: {caller}')
@@ -0,0 +1,21 @@
1
+ Metadata-Version: 2.4
2
+ Name: protoerror
3
+ Version: 3.20.2
4
+ Author-email: Helmut Konrad Schewe <helmutus@outlook.com>
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/anaticulae/protoerro
7
+ Project-URL: Repository, https://github.com/anaticulae/protoerro
8
+ Classifier: Programming Language :: Python :: 3.12
9
+ Classifier: Programming Language :: Python :: 3.13
10
+ Classifier: Programming Language :: Python :: 3.14
11
+ Requires-Python: >=3.12
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: utilo<3.0.0,>=2.107.4
14
+ Requires-Dist: iamraw<5.0.0,>=4.91.4
15
+ Requires-Dist: configos<2.0.0,>=1.0.4
16
+ Requires-Dist: Jinja2<4.0.0,>=3.1.6
17
+ Provides-Extra: dev
18
+ Requires-Dist: utilotest<2.0.0,>=1.0.2; extra == "dev"
19
+ Requires-Dist: utilosafe<2.0.0,>=1.0.0; extra == "dev"
20
+
21
+ # protoerror
@@ -0,0 +1,22 @@
1
+ protoerror/__init__.py,sha256=-j4COZ6ubJUEqQ6xm5v69_jLSx0tnm3IYhRQcGTWNhQ,4794
2
+ protoerror/__patch__.py,sha256=vLnBpo9EL5pN0mL2JD2lvg_6-4dWjykc4o9GRKerxBo,562
3
+ protoerror/checker.py,sha256=2KjaPrsFD_OqumyOfZXJFdGLV-K2ufcNZaMfVaj9ScU,2067
4
+ protoerror/cli.py,sha256=4qNoDqqKsmNB8CHlUplAtDTFOFTETOXLaQQNxqBkwoE,2694
5
+ protoerror/config.py,sha256=XEkjStkHrP49RBR8IKmq1Xk5x3aEUYNm0uqsy4hvVy4,2191
6
+ protoerror/control.py,sha256=YvBmYRjN5mH4IWKAW3KXh6HzCOugFfwjUG78gTQpcQE,9299
7
+ protoerror/finding.py,sha256=N_0lTJEcG51Rlih2bmtyz0GE0sdo-ZeCrz3bM3IomOs,5176
8
+ protoerror/group.py,sha256=oMoV8Wvjn3LsvAOU2aVHalTwSkqWwEPqf6-3Wl5sJSM,4852
9
+ protoerror/linter.py,sha256=fVsJBAU80-HzY3NPNYfRjtKN0HNeYa94Nm9ygljbusU,13327
10
+ protoerror/merger.py,sha256=8YEZTEmvErGvyfbr6Cczm3_zqqizqPd0lg7U2elAtQM,2362
11
+ protoerror/messages.py,sha256=o2JQTg_zCA2YhrIlTnzCcoq4Bno1dFr-NKOoEJb8ajA,3054
12
+ protoerror/paged.py,sha256=iG2GngYCTCy4acRjiyvfvehXRKmzlZ2WVZSwFzs2klE,3062
13
+ protoerror/question.py,sha256=ZBLpiEOhhEeo1Sd9w_5tNGv1FhaZa3BKlBOcjNu7cSU,4060
14
+ protoerror/report.py,sha256=zS1ztXVhfd9-IFCzLLv8C0GrOBojY1xnpNSEXz4Am68,5722
15
+ protoerror/report_parser.py,sha256=kW7tJYrBMbSxT5qaQEAu1vWemfOkqFbMA-_0b_MhxCg,2148
16
+ protoerror/simple.py,sha256=vRZdYZoY9mdKmMtcpee5MCxKVDuEY5FEuWnsE_STSIQ,2390
17
+ protoerror/solution.py,sha256=xQ2ARZfQ5IpY0QVdqJXpWVPL-sYP5q3itKIHknU6qT0,8680
18
+ protoerror/utils.py,sha256=CeFgNGO-RikGoGTYziJSfUweZQ9j7IgtKs3H--wEVzU,1547
19
+ protoerror-3.20.2.dist-info/METADATA,sha256=ciIHGp0NsOwXVkOk4fqFpAp4_kxE5PAqturNLyF55ho,774
20
+ protoerror-3.20.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
21
+ protoerror-3.20.2.dist-info/top_level.txt,sha256=kzMcpRU6k762x9d4rBG9oehHr3_xdAWKr4B1GcwoZTk,11
22
+ protoerror-3.20.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ protoerror