pre-commit-bigfix 2.0.1__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.
- pre_commit_bigfix/__init__.py +3 -0
- pre_commit_bigfix/bes_actionscript_lint_schclass.py +937 -0
- pre_commit_bigfix/bes_actionscript_validate_prefetch.py +1258 -0
- pre_commit_bigfix/bes_actionscript_validate_script.py +2871 -0
- pre_commit_bigfix/bes_common.py +271 -0
- pre_commit_bigfix/bes_conventions_check.py +3484 -0
- pre_commit_bigfix/bes_relevance_convert_group.py +407 -0
- pre_commit_bigfix/bes_relevance_lint.py +402 -0
- pre_commit_bigfix/bes_schema_validate.py +84 -0
- pre_commit_bigfix/bes_siterepo_validate_layout.py +1203 -0
- pre_commit_bigfix/run_all.py +305 -0
- pre_commit_bigfix/schclass.py +284 -0
- pre_commit_bigfix/schclass_data/ExpandedActionScript.schclass +412 -0
- pre_commit_bigfix/schclass_data/bigfix_overrides.schclass +43 -0
- pre_commit_bigfix/schclass_tokenizer.py +321 -0
- pre_commit_bigfix-2.0.1.dist-info/METADATA +821 -0
- pre_commit_bigfix-2.0.1.dist-info/RECORD +21 -0
- pre_commit_bigfix-2.0.1.dist-info/WHEEL +5 -0
- pre_commit_bigfix-2.0.1.dist-info/entry_points.txt +10 -0
- pre_commit_bigfix-2.0.1.dist-info/licenses/LICENSE +21 -0
- pre_commit_bigfix-2.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Pre-commit hook: lint the relevance inside BES files.
|
|
3
|
+
|
|
4
|
+
Every other hook in this repo treats relevance as opaque -- `{...}` is a lexer
|
|
5
|
+
state mechanically identical to a string literal, and the most any check proves
|
|
6
|
+
about a condition is that it starts with a brace. This hook is where that stops:
|
|
7
|
+
it hands each relevance statement to `bigfix-relevance-analyzer`, which parses
|
|
8
|
+
it, binds `it`, resolves every inspector against the dumps, type-checks the
|
|
9
|
+
result, and scores what it costs to evaluate.
|
|
10
|
+
|
|
11
|
+
Every relevance site the analyzer's extractor recognizes is checked -- <Relevance>
|
|
12
|
+
bodies, <SuccessCriteria Option="CustomRelevance"> bodies, Analysis <Property>
|
|
13
|
+
bodies, and the `{...}` substitutions inside <ActionScript> -- so a defect in a
|
|
14
|
+
substitution is reported at the line it lives on, not at the top of the file.
|
|
15
|
+
|
|
16
|
+
Checks:
|
|
17
|
+
E600 the statement could not be parsed
|
|
18
|
+
E601 the statement contains text that could not be lexed
|
|
19
|
+
E602 `it` is used where there is no context to bind it to
|
|
20
|
+
E603 the type checker reported a problem beyond an unbound `it`
|
|
21
|
+
E604 the complexity score is above the ceiling (raise with --max-score)
|
|
22
|
+
E605 the evaluation cost is above the ceiling (raise with
|
|
23
|
+
--max-evaluation-cost)
|
|
24
|
+
E606 a directory tree was deeper than the walk's limit, so it was not
|
|
25
|
+
fully scanned. Only reachable when auto-discovering -- pre-commit
|
|
26
|
+
always passes filenames
|
|
27
|
+
E607 a path given to the linter does not exist, could not be read, or
|
|
28
|
+
is not a file type the analyzer's extractor recognizes (under
|
|
29
|
+
pre-commit the hook's `files:` pattern keeps those away)
|
|
30
|
+
E608 the value does not fit the kind of site it was extracted from -- a
|
|
31
|
+
non-boolean <Relevance>, or an `if`/`elseif`/`continue if`
|
|
32
|
+
condition that is not a boolean or string
|
|
33
|
+
E609 inspectors exclusive to client relevance and to session relevance in
|
|
34
|
+
one statement, which no engine can answer
|
|
35
|
+
E610 an ActionScript command word used as a relevance name -- usually an
|
|
36
|
+
ActionScript line pasted or doubled into relevance
|
|
37
|
+
E611 a plural operand where the engine requires a single value, which it
|
|
38
|
+
refuses before evaluating anything. Split out of E603; where the
|
|
39
|
+
analyzer can work out `unique value of` or the singular spelling of
|
|
40
|
+
an aggregate, it is printed as a suggested fix
|
|
41
|
+
E612 an ActionScript `{` with anything after it on its line and no `}` to
|
|
42
|
+
close it there, which fails the action
|
|
43
|
+
E613 a `<?Relevance` with no closing `?>`, so its relevance was not linted
|
|
44
|
+
E614 a BES file that is not well-formed XML, so nothing in it was linted.
|
|
45
|
+
Off by default -- bes-schema-validate owns file validity -- and
|
|
46
|
+
switched back on with --enable E614
|
|
47
|
+
W600 a name no inspector dump defines. A warning rather than an error
|
|
48
|
+
because a repo running a newer client than the analyzer's snapshot
|
|
49
|
+
legitimately uses names it has never heard of
|
|
50
|
+
W601 a property written singular over an object that may be plural.
|
|
51
|
+
Disabled by default in .pre-commit-hooks.yaml: it fires ~6 times per
|
|
52
|
+
file across real content, which drowns everything else. Enable it
|
|
53
|
+
deliberately with --enable W601
|
|
54
|
+
W602 a singular spelling mid-chain -- under a `whose` filter, or where a
|
|
55
|
+
plural is built from it -- where the plural reads safer
|
|
56
|
+
W603 two version-looking strings compared as strings, not as versions
|
|
57
|
+
W604 a version comparison that truncates to the shorter operand's
|
|
58
|
+
components
|
|
59
|
+
W605 an ordinary ActionScript `{...}` substitution (in a `run`, `wait`,
|
|
60
|
+
`parameter`, ...) whose value is an opaque object with no text form
|
|
61
|
+
-- a `bes *`, `dmi *`, `smbios *`, `xml dom *`, `active directory *`
|
|
62
|
+
or `sqlite *` object, or a bare `registry`, `process`, `wmi
|
|
63
|
+
object`, ... -- so there is nothing to embed in the command line.
|
|
64
|
+
A warning rather than an E608 error because which types cannot
|
|
65
|
+
render is the analyzer's curated inference, not a confirmed engine
|
|
66
|
+
fact
|
|
67
|
+
W606 an ordinary ActionScript `{...}` substitution whose value may be
|
|
68
|
+
more than one value. The client joins a plural with no separator,
|
|
69
|
+
so the action runs but probably not as meant; use
|
|
70
|
+
`concatenation "<sep>" of (...)` or a tighter filter
|
|
71
|
+
W607 a relevance-tagged markdown code fence that is never closed, so its
|
|
72
|
+
relevance was not linted (markdown only; never from a BES file)
|
|
73
|
+
|
|
74
|
+
E-codes fail the hook; warnings fail only under --strict.
|
|
75
|
+
|
|
76
|
+
Unparsable XML is skipped, not reported (E614 is off by default) --
|
|
77
|
+
bes-schema-validate owns file validity, and this hook must not duplicate its
|
|
78
|
+
findings. That does mean a
|
|
79
|
+
truncated file passes here: a clean run says the relevance the extractor could
|
|
80
|
+
see is sound, not that the file parses.
|
|
81
|
+
|
|
82
|
+
A file opts out of every check here with `pre-commit-skip: bes-relevance-lint`
|
|
83
|
+
anywhere in it, the same marker the sibling hooks honor. There is no per-rule
|
|
84
|
+
marker: --disable takes the code repo-wide, and a single file that legitimately
|
|
85
|
+
needs relevance this complex is what --max-score is for.
|
|
86
|
+
|
|
87
|
+
There is no auto-fix: files are never rewritten. Where the analyzer can work
|
|
88
|
+
out a safe rewrite of a whole statement (today, the singular spellings behind
|
|
89
|
+
W602 and the `unique value of` / singular-aggregate rewrites behind E611), the fixed statement is printed once under that statement's first
|
|
90
|
+
finding as `suggested fix: ...`, for a human to copy in. Everything else needs
|
|
91
|
+
a human to decide what the relevance was meant to say.
|
|
92
|
+
|
|
93
|
+
Usage:
|
|
94
|
+
bes-relevance-lint [--strict] [--disable E604] [--enable W601] [FILES...]
|
|
95
|
+
|
|
96
|
+
Exit status:
|
|
97
|
+
0 no E-code findings (and, without --strict, no warnings either), or the
|
|
98
|
+
interpreter is older than 3.11, where the analyzer cannot be installed
|
|
99
|
+
and the hook skips with a notice rather than failing a build that has
|
|
100
|
+
no way to go green
|
|
101
|
+
1 an E-code fired, a warning fired under --strict, or the analyzer is
|
|
102
|
+
missing on a 3.11+ interpreter, where it should have been installed
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
import argparse
|
|
106
|
+
import sys
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
from bigfix_relevance_analyzer import (
|
|
110
|
+
LintConfig,
|
|
111
|
+
Severity,
|
|
112
|
+
lint_directory,
|
|
113
|
+
lint_paths,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
IMPORT_ERROR = None
|
|
117
|
+
except ImportError as exc: # pragma: no cover - exercised only on an old Python
|
|
118
|
+
LintConfig = lint_directory = lint_paths = Severity = None
|
|
119
|
+
IMPORT_ERROR = exc
|
|
120
|
+
|
|
121
|
+
__all__ = ["main"]
|
|
122
|
+
|
|
123
|
+
# Every rule the analyzer can report, mapped to this repo's code vocabulary.
|
|
124
|
+
# Two spellings exist because a repo configures hooks by E-code (--disable E604,
|
|
125
|
+
# same as every sibling hook) while the analyzer names its rules in prose; the
|
|
126
|
+
# analyzer's own name is printed in the message tail so either one greps to the
|
|
127
|
+
# same line.
|
|
128
|
+
#
|
|
129
|
+
# tests/test_bes_relevance_lint.py fails if a key of the analyzer's RULES is
|
|
130
|
+
# missing here -- that is what turns "the library added a rule" from a silent
|
|
131
|
+
# dropped finding into a red build.
|
|
132
|
+
CODES = {
|
|
133
|
+
"parse-error": "E600",
|
|
134
|
+
"error-token": "E601",
|
|
135
|
+
"unbound-it": "E602",
|
|
136
|
+
"type-error": "E603",
|
|
137
|
+
"complexity": "E604",
|
|
138
|
+
"evaluation-cost": "E605",
|
|
139
|
+
"max-depth-exceeded": "E606",
|
|
140
|
+
"file-error": "E607",
|
|
141
|
+
"site-type-mismatch": "E608",
|
|
142
|
+
"mixed-dialect": "E609",
|
|
143
|
+
"actionscript-keyword": "E610",
|
|
144
|
+
"singular-required": "E611",
|
|
145
|
+
"unterminated-substitution": "E612",
|
|
146
|
+
"unterminated-processing-instruction": "E613",
|
|
147
|
+
"xml-parse-error": "E614",
|
|
148
|
+
"unknown-inspector": "W600",
|
|
149
|
+
"non-unique-risk": "W601",
|
|
150
|
+
"plural-preferred": "W602",
|
|
151
|
+
"version-like-string-compare": "W603",
|
|
152
|
+
"version-truncating-compare": "W604",
|
|
153
|
+
"non-renderable-substitution": "W605",
|
|
154
|
+
"plural-substitution": "W606",
|
|
155
|
+
"unterminated-code-fence": "W607",
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
KNOWN_CODES = frozenset(CODES.values())
|
|
159
|
+
|
|
160
|
+
# The reverse direction, for --disable/--enable: a repo names an E-code, the
|
|
161
|
+
# analyzer's LintConfig wants its own.
|
|
162
|
+
ANALYZER_CODES = {code: name for name, code in CODES.items()}
|
|
163
|
+
|
|
164
|
+
# Off unless a repo names them in --enable. E614 is a BES file that is not
|
|
165
|
+
# well-formed XML: bes-schema-validate already reports that, and this hook must
|
|
166
|
+
# not duplicate its findings, so unparsable XML stays skipped here.
|
|
167
|
+
DEFAULT_DISABLED = frozenset({"E614"})
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
MIN_ANALYZER_PYTHON = (3, 11)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _analyzer_unavailable():
|
|
174
|
+
"""Explain the missing analyzer and return the exit status to use.
|
|
175
|
+
|
|
176
|
+
The two cases are not the same failure. Below 3.11 the dependency cannot
|
|
177
|
+
be installed at all -- setup.cfg holds it back with an environment marker
|
|
178
|
+
on purpose, because this package still supports 3.8 -- so nothing the repo
|
|
179
|
+
writes in its config makes the hook run, and failing would leave it a
|
|
180
|
+
permanently red build with no fix available. There the hook skips: it
|
|
181
|
+
prints why and exits 0. On 3.11+ the analyzer is a hard dependency, so a
|
|
182
|
+
missing one is a broken install and stays a failure.
|
|
183
|
+
"""
|
|
184
|
+
running = f"{sys.version_info[0]}.{sys.version_info[1]}"
|
|
185
|
+
if sys.version_info < MIN_ANALYZER_PYTHON:
|
|
186
|
+
print(
|
|
187
|
+
"bes-relevance-lint: skipped. It requires bigfix-relevance-analyzer, "
|
|
188
|
+
f"which needs Python 3.11 or newer; this interpreter is {running}. "
|
|
189
|
+
"To actually run this hook, point pre-commit at a 3.11+ interpreter "
|
|
190
|
+
"with `default_language_version` in your .pre-commit-config.yaml. "
|
|
191
|
+
f"({IMPORT_ERROR})"
|
|
192
|
+
)
|
|
193
|
+
return 0
|
|
194
|
+
print(
|
|
195
|
+
"bes-relevance-lint requires bigfix-relevance-analyzer, which is not "
|
|
196
|
+
f"importable on this Python {running} interpreter even though it is new "
|
|
197
|
+
"enough for it. Reinstall this package's dependencies (pip install "
|
|
198
|
+
"--upgrade pre-commit-bigfix), or drop this hook from your config. "
|
|
199
|
+
f"({IMPORT_ERROR})"
|
|
200
|
+
)
|
|
201
|
+
return 1
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _split_codes(raw):
|
|
205
|
+
"""Parse a comma-separated --disable/--enable value into a set of E-codes."""
|
|
206
|
+
return {code.strip().upper() for code in raw.split(",") if code.strip()}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _severities(disabled, enabled):
|
|
210
|
+
"""Per-code overrides for LintConfig, in the analyzer's own vocabulary."""
|
|
211
|
+
severities = {}
|
|
212
|
+
for code in disabled:
|
|
213
|
+
severities[ANALYZER_CODES[code]] = Severity.IGNORE
|
|
214
|
+
for code in enabled:
|
|
215
|
+
# Restore the analyzer's own default rather than forcing a severity:
|
|
216
|
+
# --enable exists to undo a default --disable from the hook
|
|
217
|
+
# declaration, not to promote a warning into an error. --strict is
|
|
218
|
+
# what does that, for every warning at once.
|
|
219
|
+
severities.pop(ANALYZER_CODES[code], None)
|
|
220
|
+
return severities
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
SKIP_MARKER = "pre-commit-skip: bes-relevance-lint"
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _opted_out(path):
|
|
227
|
+
"""Whether this file carries the marker that skips this hook entirely.
|
|
228
|
+
|
|
229
|
+
Read as bytes and matched against bytes: this runs before the analyzer
|
|
230
|
+
touches the file, so it must not care whether the file decodes cleanly.
|
|
231
|
+
An unreadable file is not opted out -- it simply yields no findings once
|
|
232
|
+
the extractor reaches it.
|
|
233
|
+
"""
|
|
234
|
+
try:
|
|
235
|
+
with open(path, "rb") as handle:
|
|
236
|
+
return SKIP_MARKER.encode("utf-8") in handle.read()
|
|
237
|
+
except OSError:
|
|
238
|
+
return False
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _report(findings):
|
|
242
|
+
"""Print each finding in this repo's format.
|
|
243
|
+
|
|
244
|
+
Returns (errors, warnings).
|
|
245
|
+
"""
|
|
246
|
+
errors = warnings = 0
|
|
247
|
+
# Every fixable finding from one statement carries the same whole-statement
|
|
248
|
+
# rewrite, so print it once per statement rather than once per finding.
|
|
249
|
+
suggested = set()
|
|
250
|
+
for finding in findings:
|
|
251
|
+
code = CODES[finding.code]
|
|
252
|
+
where = f"{finding.path}:{finding.line}"
|
|
253
|
+
label = "warning: " if finding.severity is Severity.WARNING else ""
|
|
254
|
+
print(f"{where}: [{code}] {label}{finding.message} ({finding.code})")
|
|
255
|
+
fix = getattr(finding, "autofix", None)
|
|
256
|
+
if fix is not None:
|
|
257
|
+
site_line = finding.site.line if finding.site is not None else None
|
|
258
|
+
key = (str(finding.path), site_line, fix.original)
|
|
259
|
+
if key not in suggested:
|
|
260
|
+
suggested.add(key)
|
|
261
|
+
print(f" suggested fix: {fix.fixed}")
|
|
262
|
+
if finding.severity is Severity.WARNING:
|
|
263
|
+
warnings += 1
|
|
264
|
+
else:
|
|
265
|
+
errors += 1
|
|
266
|
+
return errors, warnings
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def main(argv=None):
|
|
270
|
+
"""Execution starts here.
|
|
271
|
+
|
|
272
|
+
argv defaults to None so this works both as a console_scripts entry point
|
|
273
|
+
(pre-commit calls it with no arguments; argparse then reads sys.argv) and
|
|
274
|
+
when called directly as `main(sys.argv[1:])`.
|
|
275
|
+
"""
|
|
276
|
+
if IMPORT_ERROR is not None:
|
|
277
|
+
return _analyzer_unavailable()
|
|
278
|
+
|
|
279
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
280
|
+
parser.add_argument(
|
|
281
|
+
"--strict",
|
|
282
|
+
action="store_true",
|
|
283
|
+
help="treat warnings as failures (non-zero exit); default: advisory",
|
|
284
|
+
)
|
|
285
|
+
parser.add_argument(
|
|
286
|
+
"--disable",
|
|
287
|
+
default="",
|
|
288
|
+
metavar="CODES",
|
|
289
|
+
help="comma-separated check IDs to skip entirely, e.g. --disable E604",
|
|
290
|
+
)
|
|
291
|
+
parser.add_argument(
|
|
292
|
+
"--enable",
|
|
293
|
+
default="",
|
|
294
|
+
metavar="CODES",
|
|
295
|
+
help=(
|
|
296
|
+
"comma-separated check IDs to switch back on, undoing a --disable "
|
|
297
|
+
"this hook declares by default, e.g. --enable W601"
|
|
298
|
+
),
|
|
299
|
+
)
|
|
300
|
+
parser.add_argument(
|
|
301
|
+
"--max-score",
|
|
302
|
+
type=float,
|
|
303
|
+
default=None,
|
|
304
|
+
help=(
|
|
305
|
+
"raise E604's complexity ceiling above the analyzer's default; "
|
|
306
|
+
"content that legitimately needs to be this complex should raise "
|
|
307
|
+
"this rather than disable the check"
|
|
308
|
+
),
|
|
309
|
+
)
|
|
310
|
+
parser.add_argument(
|
|
311
|
+
"--max-evaluation-cost",
|
|
312
|
+
type=float,
|
|
313
|
+
default=None,
|
|
314
|
+
help="raise E605's evaluation-cost ceiling above the analyzer's default",
|
|
315
|
+
)
|
|
316
|
+
parser.add_argument(
|
|
317
|
+
"--dialect",
|
|
318
|
+
choices=["client", "session"],
|
|
319
|
+
default=None,
|
|
320
|
+
help="force the dialect instead of trusting what extraction says",
|
|
321
|
+
)
|
|
322
|
+
parser.add_argument(
|
|
323
|
+
"--platform",
|
|
324
|
+
default=None,
|
|
325
|
+
help="narrow client inspector lookups to one platform, e.g. windows",
|
|
326
|
+
)
|
|
327
|
+
parser.add_argument(
|
|
328
|
+
"--max-depth",
|
|
329
|
+
type=int,
|
|
330
|
+
default=6,
|
|
331
|
+
help=(
|
|
332
|
+
"how many directory levels to walk when auto-discovering "
|
|
333
|
+
"(no effect when files are given, which is always the case under "
|
|
334
|
+
"pre-commit)"
|
|
335
|
+
),
|
|
336
|
+
)
|
|
337
|
+
parser.add_argument(
|
|
338
|
+
"files",
|
|
339
|
+
nargs="*",
|
|
340
|
+
help=(
|
|
341
|
+
"files to check; a named file the analyzer's extractor does not "
|
|
342
|
+
"recognize is reported as E607. If omitted, the current folder and "
|
|
343
|
+
"below is walked"
|
|
344
|
+
),
|
|
345
|
+
)
|
|
346
|
+
args = parser.parse_args(argv)
|
|
347
|
+
|
|
348
|
+
disabled = _split_codes(args.disable)
|
|
349
|
+
enabled = _split_codes(args.enable)
|
|
350
|
+
unknown = (disabled | enabled) - KNOWN_CODES
|
|
351
|
+
if unknown:
|
|
352
|
+
print(
|
|
353
|
+
"warning: ignoring unknown --disable/--enable code(s): {}".format(
|
|
354
|
+
", ".join(sorted(unknown))
|
|
355
|
+
)
|
|
356
|
+
)
|
|
357
|
+
disabled -= unknown
|
|
358
|
+
enabled -= unknown
|
|
359
|
+
disabled |= DEFAULT_DISABLED - enabled
|
|
360
|
+
|
|
361
|
+
config_kwargs = {"severities": _severities(disabled, enabled)}
|
|
362
|
+
# The analyzer's ceilings are on by default and these only raise them, so a
|
|
363
|
+
# None here means "keep the built-in default", not "no limit".
|
|
364
|
+
if args.max_score is not None:
|
|
365
|
+
config_kwargs["max_score"] = args.max_score
|
|
366
|
+
if args.max_evaluation_cost is not None:
|
|
367
|
+
config_kwargs["max_evaluation_cost"] = args.max_evaluation_cost
|
|
368
|
+
if args.platform is not None:
|
|
369
|
+
config_kwargs["platform"] = args.platform
|
|
370
|
+
if args.dialect is not None:
|
|
371
|
+
from bigfix_relevance_analyzer.dialect import Dialect
|
|
372
|
+
|
|
373
|
+
config_kwargs["dialect"] = Dialect(args.dialect)
|
|
374
|
+
|
|
375
|
+
config = LintConfig(**config_kwargs)
|
|
376
|
+
|
|
377
|
+
if args.files:
|
|
378
|
+
findings = lint_paths(
|
|
379
|
+
[path for path in args.files if not _opted_out(path)], config
|
|
380
|
+
)
|
|
381
|
+
else:
|
|
382
|
+
# Filter after the walk rather than before it: the analyzer owns
|
|
383
|
+
# discovery, and re-walking here to drop opted-out files first would
|
|
384
|
+
# mean two definitions of what counts as a file to check.
|
|
385
|
+
findings = [
|
|
386
|
+
finding
|
|
387
|
+
for finding in lint_directory(".", config, max_depth=args.max_depth)
|
|
388
|
+
if not _opted_out(finding.path)
|
|
389
|
+
]
|
|
390
|
+
|
|
391
|
+
error_count, warning_count = _report(findings)
|
|
392
|
+
|
|
393
|
+
if warning_count:
|
|
394
|
+
print(f"{warning_count} warning(s).")
|
|
395
|
+
if error_count:
|
|
396
|
+
print(f"{error_count} issue(s).")
|
|
397
|
+
# E-codes always fail; warnings fail only under --strict
|
|
398
|
+
return 1 if (error_count or (warning_count and args.strict)) else 0
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
if __name__ == "__main__":
|
|
402
|
+
sys.exit(main(sys.argv[1:]))
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""A pre-commit hook to validate BigFix XML files against their schemas.
|
|
2
|
+
|
|
3
|
+
Covers BES and OJO files, Git External Custom Site files (siteConfig.xml,
|
|
4
|
+
site.xml, digest.xml) and BigFix Inventory signatures (Signatures/*.xml).
|
|
5
|
+
Only the schemas bundled with validate_bes_xml are used, never a *.xsd in the
|
|
6
|
+
current folder. Each error is printed as `path:line: message`; files that
|
|
7
|
+
pass print nothing. A .xml file whose root has no bundled schema (a sitemap
|
|
8
|
+
or a Maven site descriptor named site.xml, say) is not a BigFix file and is
|
|
9
|
+
skipped; a .bes or .ojo file with no schema fails.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
import validate_bes_xml
|
|
17
|
+
|
|
18
|
+
if __package__ in (None, ""): # run directly as a script, not as a module
|
|
19
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
20
|
+
|
|
21
|
+
from pre_commit_bigfix import bes_common
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def build_argument_parser():
|
|
25
|
+
"""Build and return the argument parser."""
|
|
26
|
+
|
|
27
|
+
parser = argparse.ArgumentParser(
|
|
28
|
+
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
|
|
29
|
+
)
|
|
30
|
+
parser.add_argument("filenames", nargs="*", help="Filenames to check.")
|
|
31
|
+
|
|
32
|
+
return parser
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def check_file(filename, schemas):
|
|
36
|
+
"""Validate one file; print its errors and return True if it failed."""
|
|
37
|
+
|
|
38
|
+
result = validate_bes_xml.validate_bes(path=filename, schema_pathnames=schemas)
|
|
39
|
+
if result:
|
|
40
|
+
return False
|
|
41
|
+
# compare against the value: every Status member is a non-empty str
|
|
42
|
+
if result.status == "no_schema":
|
|
43
|
+
if filename.lower().endswith(".xml"):
|
|
44
|
+
return False # not a BigFix file
|
|
45
|
+
# inferred_schema is left out: it is cut short for namespaced roots
|
|
46
|
+
print(f"{filename}: no schema to validate this file against")
|
|
47
|
+
return True
|
|
48
|
+
for line, message in result.errors:
|
|
49
|
+
if line is None:
|
|
50
|
+
print(f"{filename}: {message}")
|
|
51
|
+
else:
|
|
52
|
+
print(f"{filename}:{line}: {message}")
|
|
53
|
+
if not result.errors: # never expected, but a failure must say something
|
|
54
|
+
print(f"{filename}: {result.status.value}")
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def main(argv=None):
|
|
59
|
+
"""Main process."""
|
|
60
|
+
|
|
61
|
+
# Parse command line arguments.
|
|
62
|
+
argparser = build_argument_parser()
|
|
63
|
+
args = argparser.parse_args(argv)
|
|
64
|
+
|
|
65
|
+
if not args.filenames:
|
|
66
|
+
return 0
|
|
67
|
+
|
|
68
|
+
schemas = bes_common._bundled_schemas( # pylint: disable=protected-access
|
|
69
|
+
validate_bes_xml
|
|
70
|
+
)
|
|
71
|
+
if not schemas:
|
|
72
|
+
# an empty list would make validate_bes_xml fall back to the cwd schemas
|
|
73
|
+
print(
|
|
74
|
+
"bes-schema-validate: validate_bes_xml has no bundled schemas; "
|
|
75
|
+
'reinstall "validate_bes_xml>=2.4.1,<3"',
|
|
76
|
+
file=sys.stderr,
|
|
77
|
+
)
|
|
78
|
+
return len(args.filenames)
|
|
79
|
+
|
|
80
|
+
return sum(check_file(filename, schemas) for filename in args.filenames)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
if __name__ == "__main__":
|
|
84
|
+
sys.exit(main())
|