codetool-shell 0.1.1__py3-none-macosx_10_12_x86_64.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.
- codetool_shell/__init__.py +11 -0
- codetool_shell/api.py +59 -0
- codetool_shell/bin/macos-x86_64/codetool-shell-rust +0 -0
- codetool_shell/filters/__init__.py +14 -0
- codetool_shell/filters/build_compiler/__init__.py +7 -0
- codetool_shell/filters/build_compiler/detector.py +412 -0
- codetool_shell/filters/build_compiler/reducer.py +166 -0
- codetool_shell/filters/build_compiler/summary.py +617 -0
- codetool_shell/filters/ci_job_log/__init__.py +7 -0
- codetool_shell/filters/ci_job_log/detector.py +64 -0
- codetool_shell/filters/ci_job_log/reducer.py +99 -0
- codetool_shell/filters/ci_job_log/summary.py +243 -0
- codetool_shell/filters/diff/__init__.py +7 -0
- codetool_shell/filters/diff/detector.py +136 -0
- codetool_shell/filters/diff/reducer.py +308 -0
- codetool_shell/filters/generic_log/__init__.py +7 -0
- codetool_shell/filters/generic_log/detector.py +175 -0
- codetool_shell/filters/generic_log/reducer.py +99 -0
- codetool_shell/filters/generic_log/summary.py +161 -0
- codetool_shell/filters/git.py +514 -0
- codetool_shell/filters/html_cleanup/__init__.py +7 -0
- codetool_shell/filters/html_cleanup/detector.py +136 -0
- codetool_shell/filters/html_cleanup/reducer.py +27 -0
- codetool_shell/filters/html_cleanup/summary.py +422 -0
- codetool_shell/filters/json_payload/__init__.py +7 -0
- codetool_shell/filters/json_payload/detector.py +62 -0
- codetool_shell/filters/json_payload/reducer.py +81 -0
- codetool_shell/filters/json_payload/summary.py +233 -0
- codetool_shell/filters/listing/__init__.py +7 -0
- codetool_shell/filters/listing/detector.py +294 -0
- codetool_shell/filters/listing/reducer.py +30 -0
- codetool_shell/filters/log_template/__init__.py +7 -0
- codetool_shell/filters/log_template/constants.py +76 -0
- codetool_shell/filters/log_template/detector.py +331 -0
- codetool_shell/filters/log_template/reducer.py +78 -0
- codetool_shell/filters/log_template/template.py +280 -0
- codetool_shell/filters/log_template/types.py +21 -0
- codetool_shell/filters/opaque_payload/__init__.py +7 -0
- codetool_shell/filters/opaque_payload/detector.py +563 -0
- codetool_shell/filters/opaque_payload/reducer.py +142 -0
- codetool_shell/filters/opaque_payload/summary.py +61 -0
- codetool_shell/filters/package_manager/__init__.py +7 -0
- codetool_shell/filters/package_manager/detector.py +220 -0
- codetool_shell/filters/package_manager/reducer.py +110 -0
- codetool_shell/filters/package_manager/summary.py +172 -0
- codetool_shell/filters/pipeline.py +65 -0
- codetool_shell/filters/rg.py +250 -0
- codetool_shell/filters/system_output/__init__.py +7 -0
- codetool_shell/filters/system_output/detector.py +600 -0
- codetool_shell/filters/system_output/reducer.py +331 -0
- codetool_shell/filters/system_output/summary.py +164 -0
- codetool_shell/filters/table/__init__.py +7 -0
- codetool_shell/filters/table/detector.py +244 -0
- codetool_shell/filters/table/reducer.py +57 -0
- codetool_shell/filters/table/summary.py +37 -0
- codetool_shell/filters/test_runner/__init__.py +7 -0
- codetool_shell/filters/test_runner/ansi.py +80 -0
- codetool_shell/filters/test_runner/detector.py +409 -0
- codetool_shell/filters/test_runner/reducer.py +288 -0
- codetool_shell/filters/test_runner/summary.py +449 -0
- codetool_shell/filters/text.py +38 -0
- codetool_shell/filters/traceback/__init__.py +7 -0
- codetool_shell/filters/traceback/detector.py +209 -0
- codetool_shell/filters/traceback/reducer.py +141 -0
- codetool_shell/filters/traceback/summary.py +122 -0
- codetool_shell/filters/tree.py +59 -0
- codetool_shell/py.typed +0 -0
- codetool_shell/python_backend.py +38 -0
- codetool_shell/rust_backend.py +254 -0
- codetool_shell-0.1.1.dist-info/METADATA +152 -0
- codetool_shell-0.1.1.dist-info/RECORD +72 -0
- codetool_shell-0.1.1.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Public Python interface for shell output text compression."""
|
|
2
|
+
|
|
3
|
+
from .api import compress_text
|
|
4
|
+
from .rust_backend import RustBackendError, RustBackendUnavailable, is_rust_available
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"RustBackendError",
|
|
8
|
+
"RustBackendUnavailable",
|
|
9
|
+
"compress_text",
|
|
10
|
+
"is_rust_available",
|
|
11
|
+
]
|
codetool_shell/api.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Stable public API and backend selection for text compression."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .python_backend import compress_text_python
|
|
6
|
+
from .rust_backend import (
|
|
7
|
+
RustBackendError,
|
|
8
|
+
RustBackendUnavailable,
|
|
9
|
+
is_rust_available,
|
|
10
|
+
compress_text_rust,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
BackendName = str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def compress_text(
|
|
17
|
+
text: str,
|
|
18
|
+
*,
|
|
19
|
+
max_blank_lines: int = 1,
|
|
20
|
+
backend: BackendName = "auto",
|
|
21
|
+
) -> str:
|
|
22
|
+
"""Compress plain shell-output text.
|
|
23
|
+
|
|
24
|
+
This initial scaffold applies one conservative, semantics-preserving pass:
|
|
25
|
+
normalize line endings, trim trailing whitespace on each line, and collapse
|
|
26
|
+
repeated blank lines. Future pipeline variants can layer on this API.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
_validate_text(text)
|
|
30
|
+
max_blank_lines = _validate_max_blank_lines(max_blank_lines)
|
|
31
|
+
|
|
32
|
+
if backend == "python":
|
|
33
|
+
return compress_text_python(text, max_blank_lines=max_blank_lines)
|
|
34
|
+
|
|
35
|
+
if backend == "rust":
|
|
36
|
+
return compress_text_rust(text, max_blank_lines=max_blank_lines)
|
|
37
|
+
|
|
38
|
+
if backend == "auto":
|
|
39
|
+
if is_rust_available():
|
|
40
|
+
try:
|
|
41
|
+
return compress_text_rust(text, max_blank_lines=max_blank_lines)
|
|
42
|
+
except (RustBackendUnavailable, RustBackendError):
|
|
43
|
+
pass
|
|
44
|
+
return compress_text_python(text, max_blank_lines=max_blank_lines)
|
|
45
|
+
|
|
46
|
+
raise ValueError("backend must be one of: 'auto', 'python', 'rust'")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _validate_text(text: str) -> None:
|
|
50
|
+
if not isinstance(text, str):
|
|
51
|
+
raise TypeError("text must be a string")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _validate_max_blank_lines(max_blank_lines: int) -> int:
|
|
55
|
+
if isinstance(max_blank_lines, bool) or not isinstance(max_blank_lines, int):
|
|
56
|
+
raise TypeError("max_blank_lines must be an integer")
|
|
57
|
+
if max_blank_lines < 0:
|
|
58
|
+
raise ValueError("max_blank_lines must be greater than or equal to 0")
|
|
59
|
+
return max_blank_lines
|
|
Binary file
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Dedicated shell-output filters."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .git import compress_git_output
|
|
6
|
+
from .pipeline import TextFilter, apply_filter_pipeline
|
|
7
|
+
from .rg import compress_rg_output
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"TextFilter",
|
|
11
|
+
"apply_filter_pipeline",
|
|
12
|
+
"compress_git_output",
|
|
13
|
+
"compress_rg_output",
|
|
14
|
+
]
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
"""Detect conservative build/compiler diagnostic shapes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from .summary import (
|
|
8
|
+
has_error_signal,
|
|
9
|
+
has_warning_signal,
|
|
10
|
+
is_biome_diagnostic_line,
|
|
11
|
+
is_biome_summary_line,
|
|
12
|
+
is_c_family_diagnostic_line,
|
|
13
|
+
is_c_family_source_context_line,
|
|
14
|
+
is_eslint_file_header,
|
|
15
|
+
is_eslint_issue_line,
|
|
16
|
+
is_eslint_summary_line,
|
|
17
|
+
is_golangci_lint_issue_line,
|
|
18
|
+
is_golangci_lint_runner_error_line,
|
|
19
|
+
is_golangci_lint_summary_line,
|
|
20
|
+
is_go_build_diagnostic_line,
|
|
21
|
+
is_go_build_package_header,
|
|
22
|
+
is_final_summary_line,
|
|
23
|
+
is_make_ninja_cmake_diagnostic_line,
|
|
24
|
+
is_markdownlint_diagnostic_line,
|
|
25
|
+
is_maven_gradle_diagnostic_line,
|
|
26
|
+
is_msbuild_diagnostic_line,
|
|
27
|
+
is_msbuild_summary_line,
|
|
28
|
+
is_mypy_diagnostic_line,
|
|
29
|
+
is_nx_turbo_diagnostic_line,
|
|
30
|
+
is_oxlint_diagnostic_line,
|
|
31
|
+
is_oxlint_frame_line,
|
|
32
|
+
is_oxlint_summary_line,
|
|
33
|
+
is_pyright_diagnostic_line,
|
|
34
|
+
is_pyright_summary_line,
|
|
35
|
+
is_progress_line,
|
|
36
|
+
is_ruff_diagnostic_line,
|
|
37
|
+
is_ruff_summary_line,
|
|
38
|
+
is_rust_diagnostic_line,
|
|
39
|
+
is_rust_source_bar_line,
|
|
40
|
+
is_rust_span_line,
|
|
41
|
+
is_shellcheck_diagnostic_line,
|
|
42
|
+
is_shellcheck_gcc_diagnostic_line,
|
|
43
|
+
is_shellcheck_header_line,
|
|
44
|
+
is_swift_xcode_failure_line,
|
|
45
|
+
is_tsc_diagnostic_line,
|
|
46
|
+
is_yamllint_diagnostic_line,
|
|
47
|
+
is_yamllint_file_header,
|
|
48
|
+
strip_ansi,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True)
|
|
53
|
+
class BuildCompilerDiagnostics:
|
|
54
|
+
"""Detected diagnostic metadata."""
|
|
55
|
+
|
|
56
|
+
kind: str
|
|
57
|
+
failed: bool
|
|
58
|
+
warning: bool
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass(frozen=True)
|
|
62
|
+
class _DiagnosticCandidate:
|
|
63
|
+
kind: str
|
|
64
|
+
score: int
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def detect_build_compiler_diagnostics(
|
|
68
|
+
lines: list[str],
|
|
69
|
+
) -> BuildCompilerDiagnostics | None:
|
|
70
|
+
"""Return a strong compiler/static-analysis signal, or ``None``."""
|
|
71
|
+
|
|
72
|
+
clean_lines = [strip_ansi(line).rstrip() for line in lines]
|
|
73
|
+
nonblank = [line for line in clean_lines if line.strip()]
|
|
74
|
+
if len(nonblank) < 2:
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
text = "\n".join(nonblank)
|
|
78
|
+
lower = text.lower()
|
|
79
|
+
if _looks_like_excluded_output(lower):
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
candidates = [
|
|
83
|
+
candidate
|
|
84
|
+
for candidate in (
|
|
85
|
+
_classify_rust(nonblank, lower),
|
|
86
|
+
_classify_tsc(nonblank),
|
|
87
|
+
_classify_eslint(nonblank),
|
|
88
|
+
_classify_mypy(nonblank),
|
|
89
|
+
_classify_ruff(nonblank),
|
|
90
|
+
_classify_pyright(nonblank),
|
|
91
|
+
_classify_biome(nonblank),
|
|
92
|
+
_classify_oxlint(nonblank),
|
|
93
|
+
_classify_shellcheck(nonblank),
|
|
94
|
+
_classify_markdownlint(nonblank, lower),
|
|
95
|
+
_classify_yamllint(nonblank, lower),
|
|
96
|
+
_classify_golangci_lint(nonblank, lower),
|
|
97
|
+
_classify_c_family(nonblank, lower),
|
|
98
|
+
_classify_go_build(nonblank),
|
|
99
|
+
_classify_msbuild_dotnet(nonblank, lower),
|
|
100
|
+
_classify_maven_gradle(nonblank, lower),
|
|
101
|
+
_classify_make_ninja_cmake(nonblank),
|
|
102
|
+
_classify_nx_turbo(nonblank, lower),
|
|
103
|
+
_classify_swift_xcode(nonblank, lower),
|
|
104
|
+
)
|
|
105
|
+
if candidate is not None
|
|
106
|
+
]
|
|
107
|
+
if not candidates:
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
kind = max(
|
|
111
|
+
candidates,
|
|
112
|
+
key=lambda candidate: (candidate.score, candidate.kind),
|
|
113
|
+
).kind
|
|
114
|
+
failed = has_error_signal(nonblank) or _kind_defaults_to_failure(kind, nonblank)
|
|
115
|
+
warning = has_warning_signal(nonblank) or _kind_defaults_to_warning(kind, nonblank)
|
|
116
|
+
return BuildCompilerDiagnostics(
|
|
117
|
+
kind=kind,
|
|
118
|
+
failed=failed,
|
|
119
|
+
warning=warning,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _candidate(kind: str, enabled: bool, score: int) -> _DiagnosticCandidate | None:
|
|
124
|
+
if not enabled:
|
|
125
|
+
return None
|
|
126
|
+
return _DiagnosticCandidate(kind=kind, score=score)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _classify_rust(lines: list[str], lower: str) -> _DiagnosticCandidate | None:
|
|
130
|
+
rust_headers = sum(1 for line in lines if is_rust_diagnostic_line(line))
|
|
131
|
+
rust_spans = sum(1 for line in lines if is_rust_span_line(line))
|
|
132
|
+
rust_bars = sum(1 for line in lines if is_rust_source_bar_line(line))
|
|
133
|
+
progress_lines = sum(1 for line in lines if is_progress_line(line))
|
|
134
|
+
signal = rust_headers > 0 and (
|
|
135
|
+
rust_spans > 0
|
|
136
|
+
or rust_bars > 0
|
|
137
|
+
or "could not compile" in lower
|
|
138
|
+
or "aborting due to" in lower
|
|
139
|
+
)
|
|
140
|
+
return _candidate(
|
|
141
|
+
"cargo/rustc",
|
|
142
|
+
signal,
|
|
143
|
+
rust_headers * 3 + rust_spans * 2 + rust_bars + progress_lines,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _classify_tsc(lines: list[str]) -> _DiagnosticCandidate | None:
|
|
148
|
+
tsc_lines = sum(1 for line in lines if is_tsc_diagnostic_line(line))
|
|
149
|
+
return _candidate("tsc", tsc_lines > 0, tsc_lines * 4)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _classify_eslint(lines: list[str]) -> _DiagnosticCandidate | None:
|
|
153
|
+
eslint_issues = sum(1 for line in lines if is_eslint_issue_line(line))
|
|
154
|
+
eslint_headers = sum(1 for line in lines if is_eslint_file_header(line))
|
|
155
|
+
eslint_summaries = sum(1 for line in lines if is_eslint_summary_line(line))
|
|
156
|
+
return _candidate(
|
|
157
|
+
"eslint",
|
|
158
|
+
eslint_issues > 0 and (eslint_headers > 0 or eslint_summaries > 0),
|
|
159
|
+
eslint_issues * 3 + eslint_headers + eslint_summaries * 2,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _classify_mypy(lines: list[str]) -> _DiagnosticCandidate | None:
|
|
164
|
+
mypy_lines = sum(1 for line in lines if is_mypy_diagnostic_line(line))
|
|
165
|
+
mypy_summaries = sum(1 for line in lines if _is_mypy_summary_or_code_line(line))
|
|
166
|
+
return _candidate(
|
|
167
|
+
"mypy",
|
|
168
|
+
mypy_lines > 0 and (mypy_summaries > 0 or mypy_lines >= 3),
|
|
169
|
+
mypy_lines * 3 + mypy_summaries * 2,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _classify_ruff(lines: list[str]) -> _DiagnosticCandidate | None:
|
|
174
|
+
ruff_lines = sum(1 for line in lines if is_ruff_diagnostic_line(line))
|
|
175
|
+
ruff_summaries = sum(1 for line in lines if is_ruff_summary_line(line))
|
|
176
|
+
return _candidate(
|
|
177
|
+
"ruff",
|
|
178
|
+
ruff_lines > 0 and (ruff_summaries > 0 or ruff_lines >= 2),
|
|
179
|
+
ruff_lines * 4 + ruff_summaries * 2,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _classify_pyright(lines: list[str]) -> _DiagnosticCandidate | None:
|
|
184
|
+
pyright_lines = sum(1 for line in lines if is_pyright_diagnostic_line(line))
|
|
185
|
+
pyright_summaries = sum(1 for line in lines if is_pyright_summary_line(line))
|
|
186
|
+
return _candidate(
|
|
187
|
+
"pyright",
|
|
188
|
+
pyright_lines > 0 and (pyright_summaries > 0 or pyright_lines >= 2),
|
|
189
|
+
pyright_lines * 4 + pyright_summaries * 2,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _classify_biome(lines: list[str]) -> _DiagnosticCandidate | None:
|
|
194
|
+
biome_lines = sum(1 for line in lines if is_biome_diagnostic_line(line))
|
|
195
|
+
biome_summaries = sum(1 for line in lines if is_biome_summary_line(line))
|
|
196
|
+
return _candidate(
|
|
197
|
+
"biome",
|
|
198
|
+
biome_lines > 0 and (biome_summaries > 0 or biome_lines >= 2),
|
|
199
|
+
biome_lines * 3 + biome_summaries * 2,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _classify_oxlint(lines: list[str]) -> _DiagnosticCandidate | None:
|
|
204
|
+
oxlint_lines = sum(1 for line in lines if is_oxlint_diagnostic_line(line))
|
|
205
|
+
oxlint_frames = sum(1 for line in lines if is_oxlint_frame_line(line))
|
|
206
|
+
oxlint_summaries = sum(1 for line in lines if is_oxlint_summary_line(line))
|
|
207
|
+
return _candidate(
|
|
208
|
+
"oxlint",
|
|
209
|
+
oxlint_lines > 0
|
|
210
|
+
and (oxlint_summaries > 0 or oxlint_frames > 0 or oxlint_lines >= 2),
|
|
211
|
+
oxlint_lines * 4 + oxlint_frames * 2 + oxlint_summaries * 2,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _classify_shellcheck(lines: list[str]) -> _DiagnosticCandidate | None:
|
|
216
|
+
shellcheck_headers = sum(1 for line in lines if is_shellcheck_header_line(line))
|
|
217
|
+
shellcheck_lines = sum(1 for line in lines if is_shellcheck_diagnostic_line(line))
|
|
218
|
+
shellcheck_gcc_lines = sum(
|
|
219
|
+
1 for line in lines if is_shellcheck_gcc_diagnostic_line(line)
|
|
220
|
+
)
|
|
221
|
+
return _candidate(
|
|
222
|
+
"shellcheck",
|
|
223
|
+
shellcheck_lines > 0
|
|
224
|
+
and (
|
|
225
|
+
shellcheck_headers > 0
|
|
226
|
+
or shellcheck_gcc_lines > 0
|
|
227
|
+
or shellcheck_lines >= 2
|
|
228
|
+
),
|
|
229
|
+
shellcheck_lines * 4 + shellcheck_headers * 2,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _classify_markdownlint(lines: list[str], lower: str) -> _DiagnosticCandidate | None:
|
|
234
|
+
markdownlint_lines = sum(
|
|
235
|
+
1 for line in lines if is_markdownlint_diagnostic_line(line)
|
|
236
|
+
)
|
|
237
|
+
return _candidate(
|
|
238
|
+
"markdownlint",
|
|
239
|
+
markdownlint_lines > 0
|
|
240
|
+
and (
|
|
241
|
+
markdownlint_lines >= 2
|
|
242
|
+
or "markdownlint" in lower
|
|
243
|
+
or any(is_final_summary_line(line) for line in lines)
|
|
244
|
+
),
|
|
245
|
+
markdownlint_lines * 4,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _classify_yamllint(lines: list[str], lower: str) -> _DiagnosticCandidate | None:
|
|
250
|
+
yamllint_lines = sum(1 for line in lines if is_yamllint_diagnostic_line(line))
|
|
251
|
+
yamllint_headers = sum(1 for line in lines if is_yamllint_file_header(line))
|
|
252
|
+
return _candidate(
|
|
253
|
+
"yamllint",
|
|
254
|
+
yamllint_lines > 0
|
|
255
|
+
and (yamllint_headers > 0 or yamllint_lines >= 2 or "yamllint" in lower),
|
|
256
|
+
yamllint_lines * 4 + yamllint_headers,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _classify_golangci_lint(lines: list[str], lower: str) -> _DiagnosticCandidate | None:
|
|
261
|
+
golangci_lines = sum(1 for line in lines if is_golangci_lint_issue_line(line))
|
|
262
|
+
golangci_runner_errors = sum(
|
|
263
|
+
1 for line in lines if is_golangci_lint_runner_error_line(line)
|
|
264
|
+
)
|
|
265
|
+
golangci_summaries = sum(
|
|
266
|
+
1 for line in lines if is_golangci_lint_summary_line(line)
|
|
267
|
+
)
|
|
268
|
+
progress_lines = sum(1 for line in lines if is_progress_line(line))
|
|
269
|
+
signal = (
|
|
270
|
+
golangci_lines > 0
|
|
271
|
+
and (
|
|
272
|
+
golangci_lines >= 2
|
|
273
|
+
or golangci_summaries > 0
|
|
274
|
+
or golangci_runner_errors > 0
|
|
275
|
+
or "golangci-lint" in lower
|
|
276
|
+
)
|
|
277
|
+
) or (
|
|
278
|
+
golangci_runner_errors > 0
|
|
279
|
+
and ("golangci-lint" in lower or "level=info" in lower or progress_lines > 0)
|
|
280
|
+
)
|
|
281
|
+
return _candidate(
|
|
282
|
+
"golangci-lint",
|
|
283
|
+
signal,
|
|
284
|
+
golangci_lines * 4 + golangci_runner_errors * 3 + golangci_summaries,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def _classify_c_family(lines: list[str], lower: str) -> _DiagnosticCandidate | None:
|
|
289
|
+
diagnostics = sum(1 for line in lines if is_c_family_diagnostic_line(line))
|
|
290
|
+
context = sum(1 for line in lines if is_c_family_source_context_line(line))
|
|
291
|
+
summary = int("build failed" in lower or "compilation terminated" in lower)
|
|
292
|
+
signal = diagnostics > 0 and (diagnostics >= 2 or context > 0 or summary > 0)
|
|
293
|
+
return _candidate(
|
|
294
|
+
"gcc/clang",
|
|
295
|
+
signal,
|
|
296
|
+
diagnostics * 4 + context + summary * 2,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _classify_go_build(lines: list[str]) -> _DiagnosticCandidate | None:
|
|
301
|
+
headers = sum(1 for line in lines if is_go_build_package_header(line))
|
|
302
|
+
diagnostics = sum(1 for line in lines if is_go_build_diagnostic_line(line))
|
|
303
|
+
return _candidate(
|
|
304
|
+
"go-build",
|
|
305
|
+
headers > 0 and diagnostics > 0,
|
|
306
|
+
headers * 2 + diagnostics * 4,
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _classify_msbuild_dotnet(lines: list[str], lower: str) -> _DiagnosticCandidate | None:
|
|
311
|
+
diagnostics = sum(1 for line in lines if is_msbuild_diagnostic_line(line))
|
|
312
|
+
summaries = sum(1 for line in lines if is_msbuild_summary_line(line))
|
|
313
|
+
marker = "build failed" in lower or "msbuild" in lower or "dotnet build" in lower
|
|
314
|
+
return _candidate(
|
|
315
|
+
"msbuild/dotnet",
|
|
316
|
+
diagnostics > 0 and (summaries > 0 or marker),
|
|
317
|
+
diagnostics * 4 + summaries * 2 + int(marker),
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _classify_maven_gradle(lines: list[str], lower: str) -> _DiagnosticCandidate | None:
|
|
322
|
+
diagnostics = sum(1 for line in lines if is_maven_gradle_diagnostic_line(line))
|
|
323
|
+
marker = any(
|
|
324
|
+
token in lower
|
|
325
|
+
for token in (
|
|
326
|
+
"maven-surefire-plugin",
|
|
327
|
+
"[error] build failure",
|
|
328
|
+
"build failure",
|
|
329
|
+
"gradle",
|
|
330
|
+
"> task ",
|
|
331
|
+
"execution failed for task",
|
|
332
|
+
)
|
|
333
|
+
)
|
|
334
|
+
return _candidate(
|
|
335
|
+
"maven/gradle",
|
|
336
|
+
diagnostics > 0 and marker,
|
|
337
|
+
diagnostics * 3 + int(marker) * 2,
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _classify_make_ninja_cmake(lines: list[str]) -> _DiagnosticCandidate | None:
|
|
342
|
+
diagnostics = sum(1 for line in lines if is_make_ninja_cmake_diagnostic_line(line))
|
|
343
|
+
return _candidate(
|
|
344
|
+
"make/ninja/cmake",
|
|
345
|
+
diagnostics > 0,
|
|
346
|
+
diagnostics * 4,
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def _classify_nx_turbo(lines: list[str], lower: str) -> _DiagnosticCandidate | None:
|
|
351
|
+
diagnostics = sum(1 for line in lines if is_nx_turbo_diagnostic_line(line))
|
|
352
|
+
marker = "nx " in lower or "nx " in lower or "turbo" in lower
|
|
353
|
+
return _candidate(
|
|
354
|
+
"nx/turbo",
|
|
355
|
+
diagnostics > 0 and marker,
|
|
356
|
+
diagnostics * 3 + int(marker) * 2,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _classify_swift_xcode(lines: list[str], lower: str) -> _DiagnosticCandidate | None:
|
|
361
|
+
swift_diagnostics = sum(
|
|
362
|
+
1 for line in lines if is_c_family_diagnostic_line(line) and ".swift:" in line
|
|
363
|
+
)
|
|
364
|
+
xcode_failures = sum(1 for line in lines if is_swift_xcode_failure_line(line))
|
|
365
|
+
marker = "xcodebuild" in lower or "swiftcompile" in lower or "** build failed **" in lower
|
|
366
|
+
return _candidate(
|
|
367
|
+
"swift/xcode",
|
|
368
|
+
(swift_diagnostics > 0 and marker) or xcode_failures > 0,
|
|
369
|
+
swift_diagnostics * 4 + xcode_failures * 3 + int(marker),
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def _looks_like_excluded_output(lower: str) -> bool:
|
|
374
|
+
return (
|
|
375
|
+
lower.startswith("test output:")
|
|
376
|
+
or "test session starts" in lower
|
|
377
|
+
or "short test summary info" in lower
|
|
378
|
+
or "test result:" in lower
|
|
379
|
+
or "traceback (most recent call last)" in lower
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def _is_mypy_summary_or_code_line(line: str) -> bool:
|
|
384
|
+
lower = line.lower()
|
|
385
|
+
return (
|
|
386
|
+
("found " in lower and " error" in lower and " file" in lower)
|
|
387
|
+
or "success: no issues found" in lower
|
|
388
|
+
or ("[" in line and "]" in line and is_mypy_diagnostic_line(line))
|
|
389
|
+
or is_final_summary_line(line)
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def _kind_defaults_to_failure(kind: str, lines: list[str]) -> bool:
|
|
394
|
+
"""Classify linter issue-only formats without treating warnings as failures."""
|
|
395
|
+
|
|
396
|
+
if kind in {"markdownlint", "golangci-lint"}:
|
|
397
|
+
return True
|
|
398
|
+
if kind == "go-build":
|
|
399
|
+
return any(is_go_build_diagnostic_line(line) for line in lines)
|
|
400
|
+
if kind == "ruff":
|
|
401
|
+
return any(is_ruff_diagnostic_line(line) for line in lines)
|
|
402
|
+
if kind in {"biome", "oxlint"} and not has_warning_signal(lines):
|
|
403
|
+
return True
|
|
404
|
+
return False
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def _kind_defaults_to_warning(kind: str, lines: list[str]) -> bool:
|
|
408
|
+
if kind in {"shellcheck", "yamllint"}:
|
|
409
|
+
return True
|
|
410
|
+
if kind in {"biome", "oxlint"} and has_warning_signal(lines):
|
|
411
|
+
return True
|
|
412
|
+
return False
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""Conservative reducer for build/compiler diagnostics."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ..text import join_preserving_final_newline, score, split_preserving_final_newline
|
|
6
|
+
from .detector import BuildCompilerDiagnostics, detect_build_compiler_diagnostics
|
|
7
|
+
from .summary import (
|
|
8
|
+
diagnostic_context_budget,
|
|
9
|
+
is_eslint_issue_line,
|
|
10
|
+
is_diagnostic_file_header,
|
|
11
|
+
is_final_summary_line,
|
|
12
|
+
is_important_diagnostic_line,
|
|
13
|
+
is_mypy_diagnostic_line,
|
|
14
|
+
is_progress_line,
|
|
15
|
+
is_safe_snippet_line,
|
|
16
|
+
normalize_line,
|
|
17
|
+
parse_golangci_lint_issue_line,
|
|
18
|
+
parse_mypy_diagnostic_line,
|
|
19
|
+
strip_ansi,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def compress_build_compiler_diagnostics_output(text: str) -> str:
|
|
24
|
+
"""Compress compiler/build/static-analysis diagnostic output."""
|
|
25
|
+
|
|
26
|
+
lines, final_newline = split_preserving_final_newline(text)
|
|
27
|
+
signal = detect_build_compiler_diagnostics(lines)
|
|
28
|
+
if signal is None:
|
|
29
|
+
return text
|
|
30
|
+
|
|
31
|
+
if signal.kind == "mypy":
|
|
32
|
+
candidate_lines = _reduce_mypy_lines(lines, signal)
|
|
33
|
+
else:
|
|
34
|
+
candidate_lines = _reduce_lines(lines, signal)
|
|
35
|
+
if len(candidate_lines) < 2:
|
|
36
|
+
return text
|
|
37
|
+
|
|
38
|
+
candidate = join_preserving_final_newline(candidate_lines, final_newline)
|
|
39
|
+
if score(candidate) < score(text):
|
|
40
|
+
return candidate
|
|
41
|
+
return text
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _reduce_lines(
|
|
45
|
+
lines: list[str], signal: BuildCompilerDiagnostics
|
|
46
|
+
) -> list[str]:
|
|
47
|
+
selected = [_header(signal)]
|
|
48
|
+
progress_omitted = 0
|
|
49
|
+
snippet_remaining = 0
|
|
50
|
+
|
|
51
|
+
def flush_progress() -> None:
|
|
52
|
+
nonlocal progress_omitted
|
|
53
|
+
if progress_omitted:
|
|
54
|
+
plural = "" if progress_omitted == 1 else "s"
|
|
55
|
+
selected.append(f"… {progress_omitted} build progress line{plural} omitted")
|
|
56
|
+
progress_omitted = 0
|
|
57
|
+
|
|
58
|
+
for raw_line in lines:
|
|
59
|
+
line = strip_ansi(raw_line).rstrip()
|
|
60
|
+
stripped = line.strip()
|
|
61
|
+
if not stripped:
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
if is_progress_line(stripped):
|
|
65
|
+
progress_omitted += 1
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
if is_important_diagnostic_line(stripped) or is_diagnostic_file_header(
|
|
69
|
+
signal.kind, stripped
|
|
70
|
+
):
|
|
71
|
+
flush_progress()
|
|
72
|
+
selected.append(_normalize_for_signal(line, signal))
|
|
73
|
+
snippet_remaining = diagnostic_context_budget(signal.kind, stripped)
|
|
74
|
+
continue
|
|
75
|
+
|
|
76
|
+
if snippet_remaining and is_safe_snippet_line(line):
|
|
77
|
+
flush_progress()
|
|
78
|
+
selected.append(line.rstrip())
|
|
79
|
+
snippet_remaining -= 1
|
|
80
|
+
continue
|
|
81
|
+
|
|
82
|
+
if signal.failed and is_final_summary_line(stripped):
|
|
83
|
+
flush_progress()
|
|
84
|
+
selected.append(_normalize_for_signal(line, signal))
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
progress_omitted += 1
|
|
88
|
+
|
|
89
|
+
flush_progress()
|
|
90
|
+
return _drop_adjacent_duplicates(selected)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _reduce_mypy_lines(
|
|
94
|
+
lines: list[str], signal: BuildCompilerDiagnostics
|
|
95
|
+
) -> list[str]:
|
|
96
|
+
selected = [_header(signal)]
|
|
97
|
+
progress_omitted = 0
|
|
98
|
+
current_path: str | None = None
|
|
99
|
+
|
|
100
|
+
def flush_progress() -> None:
|
|
101
|
+
nonlocal progress_omitted
|
|
102
|
+
if progress_omitted:
|
|
103
|
+
plural = "" if progress_omitted == 1 else "s"
|
|
104
|
+
selected.append(f"… {progress_omitted} build progress line{plural} omitted")
|
|
105
|
+
progress_omitted = 0
|
|
106
|
+
|
|
107
|
+
for raw_line in lines:
|
|
108
|
+
line = strip_ansi(raw_line).rstrip()
|
|
109
|
+
stripped = line.strip()
|
|
110
|
+
if not stripped:
|
|
111
|
+
continue
|
|
112
|
+
|
|
113
|
+
parsed = parse_mypy_diagnostic_line(stripped)
|
|
114
|
+
if parsed is not None:
|
|
115
|
+
path, location, detail = parsed
|
|
116
|
+
flush_progress()
|
|
117
|
+
if path != current_path:
|
|
118
|
+
selected.append(path)
|
|
119
|
+
current_path = path
|
|
120
|
+
selected.append(f" {location}: {detail}")
|
|
121
|
+
continue
|
|
122
|
+
|
|
123
|
+
if is_progress_line(stripped):
|
|
124
|
+
progress_omitted += 1
|
|
125
|
+
continue
|
|
126
|
+
|
|
127
|
+
if is_final_summary_line(stripped) or (
|
|
128
|
+
is_mypy_diagnostic_line(stripped) and "[" in stripped and "]" in stripped
|
|
129
|
+
):
|
|
130
|
+
flush_progress()
|
|
131
|
+
selected.append(normalize_line(line))
|
|
132
|
+
continue
|
|
133
|
+
|
|
134
|
+
progress_omitted += 1
|
|
135
|
+
|
|
136
|
+
flush_progress()
|
|
137
|
+
return _drop_adjacent_duplicates(selected)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _header(signal: BuildCompilerDiagnostics) -> str:
|
|
141
|
+
outcome = "failure" if signal.failed else "warning" if signal.warning else "success"
|
|
142
|
+
return f"build diagnostics: {signal.kind} {outcome}"
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _normalize_for_signal(line: str, signal: BuildCompilerDiagnostics) -> str:
|
|
146
|
+
if signal.kind == "eslint" and is_eslint_issue_line(line):
|
|
147
|
+
return " ".join(line.strip().split())
|
|
148
|
+
if signal.kind == "golangci-lint":
|
|
149
|
+
parsed = parse_golangci_lint_issue_line(line)
|
|
150
|
+
if parsed is not None:
|
|
151
|
+
path, location, detail = parsed
|
|
152
|
+
return f"{path} {location}: {detail}"
|
|
153
|
+
if signal.kind == "shellcheck":
|
|
154
|
+
return line.rstrip()
|
|
155
|
+
return normalize_line(line)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _drop_adjacent_duplicates(lines: list[str]) -> list[str]:
|
|
159
|
+
output: list[str] = []
|
|
160
|
+
for line in lines:
|
|
161
|
+
if not line:
|
|
162
|
+
continue
|
|
163
|
+
if output and output[-1] == line:
|
|
164
|
+
continue
|
|
165
|
+
output.append(line)
|
|
166
|
+
return output
|