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/question.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
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 dataclasses
|
|
11
|
+
import enum
|
|
12
|
+
import re
|
|
13
|
+
|
|
14
|
+
import iamraw
|
|
15
|
+
import utilo
|
|
16
|
+
|
|
17
|
+
import protoerror
|
|
18
|
+
|
|
19
|
+
QUESTION_PATTERN = r'^QUESTION_(?P<number>\d{2,5})$'
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Answer(enum.Enum):
|
|
23
|
+
OPEN = enum.auto()
|
|
24
|
+
YES = enum.auto()
|
|
25
|
+
NO = enum.auto()
|
|
26
|
+
UNDECIDED = enum.auto()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Action(enum.Enum):
|
|
30
|
+
PAGE = enum.auto()
|
|
31
|
+
DOCU = enum.auto()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclasses.dataclass(unsafe_hash=True)
|
|
35
|
+
class Question(iamraw.Solution):
|
|
36
|
+
msgid: str = None
|
|
37
|
+
title: str = None
|
|
38
|
+
description: str = None
|
|
39
|
+
status: Answer = Answer.OPEN
|
|
40
|
+
|
|
41
|
+
action: Action = None
|
|
42
|
+
finding: int = None
|
|
43
|
+
yes: callable = None
|
|
44
|
+
no: callable = None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
Questions = list[Question]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def parse_questions(module) -> Questions:
|
|
51
|
+
if isinstance(module, str):
|
|
52
|
+
module = protoerror.utils.module_fromname(module)
|
|
53
|
+
enabler = parse_enabler(module)
|
|
54
|
+
result = []
|
|
55
|
+
for name, value in vars(module).items():
|
|
56
|
+
matched = re.match(QUESTION_PATTERN, name)
|
|
57
|
+
if not matched:
|
|
58
|
+
continue
|
|
59
|
+
number = matched['number']
|
|
60
|
+
try:
|
|
61
|
+
title, message = value.split('\n\n', maxsplit=1)
|
|
62
|
+
except ValueError:
|
|
63
|
+
title, message = value.strip(), ''
|
|
64
|
+
item = protoerror.Question(
|
|
65
|
+
title=title,
|
|
66
|
+
msgid=number,
|
|
67
|
+
description=message,
|
|
68
|
+
)
|
|
69
|
+
try:
|
|
70
|
+
selected = enabler[number]
|
|
71
|
+
except KeyError:
|
|
72
|
+
pass
|
|
73
|
+
else:
|
|
74
|
+
item.action = selected[0]
|
|
75
|
+
item.finding = selected[1]
|
|
76
|
+
item.yes = selected[2]
|
|
77
|
+
item.no = selected[3] # pylint:disable=C0103
|
|
78
|
+
|
|
79
|
+
result.append(item)
|
|
80
|
+
# sort ascending
|
|
81
|
+
result.sort(key=lambda x: x.msgid)
|
|
82
|
+
return result
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
ENABLE_PATTERN = r'^QUESTION_(?P<number>\d{2,5})_ENABLE$'
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def parse_enabler(module) -> int:
|
|
89
|
+
result = {}
|
|
90
|
+
for name, value in vars(module).items():
|
|
91
|
+
matched = re.match(ENABLE_PATTERN, name)
|
|
92
|
+
if not matched:
|
|
93
|
+
continue
|
|
94
|
+
number = matched['number']
|
|
95
|
+
result[number] = value
|
|
96
|
+
return result
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def pagemore(finding_count: int, yes: callable = None, no: callable = None):
|
|
100
|
+
return (Action.PAGE, finding_count, yes, no)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def documore(finding_count: int, yes: callable = None, no: callable = None):
|
|
104
|
+
return (Action.DOCU, finding_count, yes, no)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def answer_questions(path: str, questions: Questions) -> iamraw.Findings:
|
|
108
|
+
findings = [item.content for item in protoerror.findings_from_path(path)]
|
|
109
|
+
findings = utilo.flat(findings)
|
|
110
|
+
grouped = protoerror.byid(findings)
|
|
111
|
+
result = []
|
|
112
|
+
for question in questions:
|
|
113
|
+
try:
|
|
114
|
+
selected = grouped[question.msgid]
|
|
115
|
+
except KeyError:
|
|
116
|
+
# no potential message available
|
|
117
|
+
continue
|
|
118
|
+
if question.action == Action.PAGE:
|
|
119
|
+
bypage = protoerror.bypage(selected)
|
|
120
|
+
for paged in bypage:
|
|
121
|
+
if len(paged.content) < question.finding:
|
|
122
|
+
continue
|
|
123
|
+
solution = iamraw.Finding(
|
|
124
|
+
location=iamraw.Location.from_page(paged.page),
|
|
125
|
+
msgid=question.msgid,
|
|
126
|
+
solution=question,
|
|
127
|
+
)
|
|
128
|
+
result.append(solution)
|
|
129
|
+
elif question.action == Action.DOCU:
|
|
130
|
+
if len(selected) < question.finding:
|
|
131
|
+
continue
|
|
132
|
+
solution = iamraw.Finding(
|
|
133
|
+
location=protoerror.OVERVIEW,
|
|
134
|
+
msgid=question.msgid,
|
|
135
|
+
solution=question,
|
|
136
|
+
)
|
|
137
|
+
result.append(solution)
|
|
138
|
+
return result
|
protoerror/report.py
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# C O P Y R I G H T
|
|
3
|
+
# -----------------------------------------------------------------------------
|
|
4
|
+
# Copyright (c) 2021-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
|
+
"""Report Solution
|
|
10
|
+
===============
|
|
11
|
+
|
|
12
|
+
example
|
|
13
|
+
-------
|
|
14
|
+
|
|
15
|
+
.. code-block::none
|
|
16
|
+
|
|
17
|
+
<<titlepage>>
|
|
18
|
+
~7777:headline~
|
|
19
|
+
...
|
|
20
|
+
...
|
|
21
|
+
...
|
|
22
|
+
~7779:next headline~
|
|
23
|
+
...
|
|
24
|
+
...
|
|
25
|
+
...
|
|
26
|
+
<<bibliography>>
|
|
27
|
+
|
|
28
|
+
cli
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
decider show list [--short]
|
|
32
|
+
show titlepage
|
|
33
|
+
show 7777
|
|
34
|
+
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
import functools
|
|
38
|
+
import importlib
|
|
39
|
+
import os
|
|
40
|
+
import sys
|
|
41
|
+
import textwrap
|
|
42
|
+
|
|
43
|
+
import utilo
|
|
44
|
+
|
|
45
|
+
import protoerror
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def integrate(root: str, features: str):
|
|
49
|
+
hook = integrate_cli
|
|
50
|
+
run_ = functools.partial(run, root=root, features=features)
|
|
51
|
+
return hook, run_
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def show_list(root, features, short: bool = False):
|
|
55
|
+
print_features(root, features, choice=None, short=short)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def show_feature(root, features, choice, short: bool = False):
|
|
59
|
+
print_features(root, features, choice, short)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def show_solution(root, features, choice, short: bool = False):
|
|
63
|
+
parsed = parse_features(root, features)
|
|
64
|
+
if not parsed:
|
|
65
|
+
return
|
|
66
|
+
parsed = [(checkers, solutions) for (_, (checkers, solutions)) in parsed]
|
|
67
|
+
for checkers, solutions in parsed:
|
|
68
|
+
for check in sorted(checkers):
|
|
69
|
+
if check != choice:
|
|
70
|
+
continue
|
|
71
|
+
print_solution(check, solutions, short=short, single=True)
|
|
72
|
+
return
|
|
73
|
+
utilo.error(f'could not find solution: {choice}')
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def print_features(root, features, choice, short: bool = False):
|
|
77
|
+
parsed = parse_features(root, features)
|
|
78
|
+
if not parsed:
|
|
79
|
+
return
|
|
80
|
+
hit = False
|
|
81
|
+
for title, (checkers, solutions) in parsed:
|
|
82
|
+
if choice and title != choice:
|
|
83
|
+
continue
|
|
84
|
+
hit = True
|
|
85
|
+
utilo.log(f'<<{title}>>')
|
|
86
|
+
for check in sorted(checkers):
|
|
87
|
+
print_solution(check, solutions, short=short)
|
|
88
|
+
utilo.log()
|
|
89
|
+
if not hit:
|
|
90
|
+
utilo.error(f'could not find feature: {choice}')
|
|
91
|
+
sys.exit(utilo.FAILURE)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def print_solution(
|
|
95
|
+
check,
|
|
96
|
+
solutions,
|
|
97
|
+
*,
|
|
98
|
+
short: bool = False,
|
|
99
|
+
single: bool = False,
|
|
100
|
+
):
|
|
101
|
+
indent = '\t' if short and not single else ''
|
|
102
|
+
try:
|
|
103
|
+
title, description = solutions[check]
|
|
104
|
+
except KeyError:
|
|
105
|
+
utilo.log(f'{indent}~{check}:NO_SOLUTION~')
|
|
106
|
+
return
|
|
107
|
+
if single:
|
|
108
|
+
utilo.log(title)
|
|
109
|
+
else:
|
|
110
|
+
utilo.log(f'{indent}~{check}:{title}~')
|
|
111
|
+
if single:
|
|
112
|
+
utilo.log()
|
|
113
|
+
if not short:
|
|
114
|
+
prefix = '\t' if not single else ''
|
|
115
|
+
utilo.log(textwrap.indent(description.strip(), prefix))
|
|
116
|
+
utilo.log()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def run(args, root, features):
|
|
120
|
+
if 'show' not in args:
|
|
121
|
+
return
|
|
122
|
+
assert os.path.exists(root), str(root)
|
|
123
|
+
choice = args['show']
|
|
124
|
+
short = args.get('short', False)
|
|
125
|
+
if 'list' in choice:
|
|
126
|
+
show_list(root, features, short=short)
|
|
127
|
+
elif utilo.isnumber(choice):
|
|
128
|
+
choice = int(choice)
|
|
129
|
+
show_solution(root, features, choice, short=short)
|
|
130
|
+
else:
|
|
131
|
+
show_feature(root, features, choice)
|
|
132
|
+
sys.exit(utilo.SUCCESS)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def integrate_cli(parser):
|
|
136
|
+
sub = parser.add_subparsers(help='show additional linter information')
|
|
137
|
+
show = sub.add_parser('show')
|
|
138
|
+
show.add_argument('show', nargs='?', default='list')
|
|
139
|
+
show.add_argument('--short', action='store_true', help='display fewer content') # yapf:disable
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def parse_features(root: str, features: str):
|
|
143
|
+
if isinstance(features, str):
|
|
144
|
+
features = [features]
|
|
145
|
+
collected = [parse_python(root, feature) for feature in features]
|
|
146
|
+
result = []
|
|
147
|
+
for collect in collected:
|
|
148
|
+
for (name, item) in collect:
|
|
149
|
+
parsed = parse_feature(item.__name__)
|
|
150
|
+
if parsed is None:
|
|
151
|
+
continue
|
|
152
|
+
result.append((name, parsed))
|
|
153
|
+
result = sorted(result, key=lambda x: x[0])
|
|
154
|
+
if not result:
|
|
155
|
+
return result
|
|
156
|
+
# merge duplicated groups to single group
|
|
157
|
+
grouped = [result[0]]
|
|
158
|
+
for name, data in result[1:]:
|
|
159
|
+
before = grouped[-1]
|
|
160
|
+
if name == before[0]:
|
|
161
|
+
# add before
|
|
162
|
+
before[1][0].extend(data[0])
|
|
163
|
+
before[1][1].update(data[1])
|
|
164
|
+
else:
|
|
165
|
+
# new group
|
|
166
|
+
grouped.append((name, data))
|
|
167
|
+
return grouped
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def parse_python(root: str, feature: str) -> list:
|
|
171
|
+
"""Load python code from path."""
|
|
172
|
+
# TODO: THERE MUST BE A BETTER WAY
|
|
173
|
+
collected = [
|
|
174
|
+
item.replace('.py', '')
|
|
175
|
+
for item in os.listdir(os.path.join(root, feature.replace('.', '/')))
|
|
176
|
+
if '__init__' not in item and item.endswith('.py')
|
|
177
|
+
]
|
|
178
|
+
result = []
|
|
179
|
+
for item in collected:
|
|
180
|
+
current = importlib.import_module(
|
|
181
|
+
feature + '.' + item,
|
|
182
|
+
feature,
|
|
183
|
+
)
|
|
184
|
+
result.append((item, current))
|
|
185
|
+
return result
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def parse_feature(path: str):
|
|
189
|
+
# assert os.path.exists(path), str(path)
|
|
190
|
+
checkers = protoerror.parse_checkers(path)
|
|
191
|
+
checkers = [item.msgid for item in checkers]
|
|
192
|
+
solutions = protoerror.parse_solutions(path)
|
|
193
|
+
solutions = {
|
|
194
|
+
msgid(item.msgid): (item.title, item.description) for item in solutions
|
|
195
|
+
}
|
|
196
|
+
if not checkers and not solutions:
|
|
197
|
+
return None
|
|
198
|
+
return checkers, solutions
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def msgid(item: str) -> int:
|
|
202
|
+
"""\
|
|
203
|
+
>>> msgid('W1020')
|
|
204
|
+
1020
|
|
205
|
+
>>> msgid('333')
|
|
206
|
+
333
|
|
207
|
+
"""
|
|
208
|
+
if item[0].isalpha():
|
|
209
|
+
return int(item[1:])
|
|
210
|
+
return int(item)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# C O P Y R I G H T
|
|
3
|
+
# -----------------------------------------------------------------------------
|
|
4
|
+
# Copyright (c) 2021-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 re
|
|
12
|
+
|
|
13
|
+
Step = collections.namedtuple('Step', 'msgid title description')
|
|
14
|
+
Steps = list[Step]
|
|
15
|
+
Feature = collections.namedtuple('Feature', 'title solutions')
|
|
16
|
+
Report = collections.namedtuple('Report', 'name features')
|
|
17
|
+
Reports = list[Report]
|
|
18
|
+
|
|
19
|
+
TITLE = re.compile(r'^<<(?P<title>.+)>>\n', re.MULTILINE)
|
|
20
|
+
HEADLINE = re.compile(r'~(?P<id>\d{2,6}):(?P<headline>.+?)~\n')
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def parses(content: str, name: str = '', active: set = None) -> Report:
|
|
24
|
+
splitted = TITLE.split(content)
|
|
25
|
+
splitted = [item for item in splitted if item]
|
|
26
|
+
features = []
|
|
27
|
+
for title, area in xsome(splitted, count=2):
|
|
28
|
+
solutions = parse_steps(area.strip(), active=active)
|
|
29
|
+
if not solutions:
|
|
30
|
+
continue
|
|
31
|
+
features.append(Feature(title=title, solutions=solutions))
|
|
32
|
+
result = Report(name=name, features=features)
|
|
33
|
+
return result
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def parse_steps(content: str, active: set = None) -> Steps:
|
|
37
|
+
splitted = HEADLINE.split(content)
|
|
38
|
+
splitted = [item for item in splitted if item]
|
|
39
|
+
result = []
|
|
40
|
+
for msgid, headline, description in xsome(splitted, count=3):
|
|
41
|
+
msgid = int(msgid)
|
|
42
|
+
if active and msgid not in active:
|
|
43
|
+
# skip solution if solution is not selected
|
|
44
|
+
continue
|
|
45
|
+
description = description.strip()
|
|
46
|
+
result.append(Step(msgid, headline, description))
|
|
47
|
+
return result
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def xsome(items, count: 1):
|
|
51
|
+
# TODO: MOVE TO UTILA
|
|
52
|
+
collected = []
|
|
53
|
+
for item in items:
|
|
54
|
+
collected.append(item)
|
|
55
|
+
if len(collected) != count:
|
|
56
|
+
continue
|
|
57
|
+
yield collected
|
|
58
|
+
collected = []
|
protoerror/simple.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
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 functools
|
|
11
|
+
|
|
12
|
+
import iamraw
|
|
13
|
+
import utilo
|
|
14
|
+
|
|
15
|
+
import protoerror
|
|
16
|
+
|
|
17
|
+
ResultType = tuple[str, str]
|
|
18
|
+
ResultDefault = ('user', 'developer') # pylint:disable=C0103
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def run(
|
|
22
|
+
modulename,
|
|
23
|
+
driver=None,
|
|
24
|
+
location: iamraw.Location = None,
|
|
25
|
+
document: iamraw.DocInfo = None,
|
|
26
|
+
findings_merge: bool = True,
|
|
27
|
+
before_dump: callable = None,
|
|
28
|
+
):
|
|
29
|
+
if not location:
|
|
30
|
+
location = protoerror.OVERVIEW
|
|
31
|
+
# create linter
|
|
32
|
+
linter = protoerror.from_modules(
|
|
33
|
+
modules=modulename,
|
|
34
|
+
document=document,
|
|
35
|
+
)
|
|
36
|
+
# run linter
|
|
37
|
+
result = linting(
|
|
38
|
+
linter=linter,
|
|
39
|
+
checkers=linter.checkers,
|
|
40
|
+
driver=driver,
|
|
41
|
+
location=location,
|
|
42
|
+
)
|
|
43
|
+
if findings_merge:
|
|
44
|
+
result = protoerror.merge_findings(result)
|
|
45
|
+
if before_dump:
|
|
46
|
+
result = utilo.pass_required(
|
|
47
|
+
before_dump,
|
|
48
|
+
result=result,
|
|
49
|
+
driver=driver,
|
|
50
|
+
linter=linter,
|
|
51
|
+
)
|
|
52
|
+
# dump results
|
|
53
|
+
user, developer = protoerror.dump_result(
|
|
54
|
+
result,
|
|
55
|
+
checkers=linter.checkers,
|
|
56
|
+
)
|
|
57
|
+
return user, developer
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def linting(
|
|
61
|
+
linter: protoerror.Linter,
|
|
62
|
+
checkers: 'protoerror.Validators',
|
|
63
|
+
driver,
|
|
64
|
+
location: iamraw.Location,
|
|
65
|
+
):
|
|
66
|
+
failure = []
|
|
67
|
+
for checker in checkers:
|
|
68
|
+
utilo.debug(checker.__name__)
|
|
69
|
+
call = functools.partial(
|
|
70
|
+
linter.add_finding,
|
|
71
|
+
msgid=checker.msgid,
|
|
72
|
+
location=location,
|
|
73
|
+
)
|
|
74
|
+
try:
|
|
75
|
+
checker(call, driver)
|
|
76
|
+
except Exception as error: # pylint:disable=broad-except
|
|
77
|
+
utilo.error(error)
|
|
78
|
+
failure.append(error)
|
|
79
|
+
else:
|
|
80
|
+
utilo.debug('done')
|
|
81
|
+
if failure:
|
|
82
|
+
first_failure = failure[0]
|
|
83
|
+
raise RuntimeError('linter step error') from first_failure
|
|
84
|
+
result = linter.result(unique=True)
|
|
85
|
+
return result
|