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/__init__.py +133 -0
- protoerror/__patch__.py +8 -0
- protoerror/checker.py +78 -0
- protoerror/cli.py +88 -0
- protoerror/config.py +78 -0
- protoerror/control.py +330 -0
- protoerror/finding.py +145 -0
- protoerror/group.py +158 -0
- protoerror/linter.py +428 -0
- protoerror/merger.py +69 -0
- protoerror/messages.py +95 -0
- protoerror/paged.py +109 -0
- protoerror/question.py +138 -0
- protoerror/report.py +210 -0
- protoerror/report_parser.py +58 -0
- protoerror/simple.py +85 -0
- protoerror/solution.py +300 -0
- protoerror/utils.py +49 -0
- protoerror-3.20.2.dist-info/METADATA +21 -0
- protoerror-3.20.2.dist-info/RECORD +22 -0
- protoerror-3.20.2.dist-info/WHEEL +5 -0
- protoerror-3.20.2.dist-info/top_level.txt +1 -0
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
|
+
'*'
|
|
258
|
+
>>> escape('*#$')
|
|
259
|
+
'*#$'
|
|
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'<{char}>', f'<{char}>')
|
|
273
|
+
text = text.replace(f'</{char}>', 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 @@
|
|
|
1
|
+
protoerror
|