codetool-shell 0.1.1__py3-none-win_arm64.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/windows-arm64/codetool-shell-rust.exe +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,61 @@
|
|
|
1
|
+
"""Summary line formatting for opaque payloads."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def byte_len(text: str) -> int:
|
|
7
|
+
"""Return UTF-8 byte length for summary accounting."""
|
|
8
|
+
|
|
9
|
+
return len(text.encode("utf-8"))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def format_base64_summary(*, line_count: int, byte_count: int) -> str:
|
|
13
|
+
return (
|
|
14
|
+
"[opaque payload omitted: "
|
|
15
|
+
f"kind=base64 lines={line_count} bytes={byte_count}]"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def format_pem_summary(*, label: str, line_count: int, byte_count: int) -> str:
|
|
20
|
+
return (
|
|
21
|
+
"[opaque payload omitted: "
|
|
22
|
+
f"kind=pem label={_quote(label)} lines={line_count} bytes={byte_count}]"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def format_data_url_summary(*, mime: str, encoding: str, byte_count: int) -> str:
|
|
27
|
+
return (
|
|
28
|
+
"[opaque payload omitted: "
|
|
29
|
+
f"kind=data-url mime={mime} encoding={encoding} bytes={byte_count}]"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def format_html_summary(*, byte_count: int, title: str | None) -> str:
|
|
34
|
+
if title:
|
|
35
|
+
return (
|
|
36
|
+
"[opaque payload omitted: "
|
|
37
|
+
f"kind=html bytes={byte_count} title={_quote(title)}]"
|
|
38
|
+
)
|
|
39
|
+
return f"[opaque payload omitted: kind=html bytes={byte_count}]"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def format_json_summary(*, kind: str, keys: list[str], byte_count: int) -> str:
|
|
43
|
+
return (
|
|
44
|
+
"[opaque payload omitted: "
|
|
45
|
+
f"kind={kind} keys={','.join(keys)} bytes={byte_count}]"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def format_minified_js_summary(*, byte_count: int) -> str:
|
|
50
|
+
return f"[opaque payload omitted: kind=minified-js bytes={byte_count}]"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def with_final_newline(line: str, final_newline: bool) -> str:
|
|
54
|
+
if final_newline:
|
|
55
|
+
return f"{line}\n"
|
|
56
|
+
return line
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _quote(value: str) -> str:
|
|
60
|
+
escaped = value.replace("\\", "\\\\").replace('"', '\\"')
|
|
61
|
+
return f'"{escaped}"'
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""Detect conservative package-manager log shapes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
from .summary import has_compiler_diagnostics
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class PackageManagerLog:
|
|
13
|
+
"""Detected package-manager log metadata."""
|
|
14
|
+
|
|
15
|
+
manager: str
|
|
16
|
+
failed: bool
|
|
17
|
+
warning: bool
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
_MANAGER_PATTERNS: tuple[tuple[str, tuple[re.Pattern[str], ...]], ...] = (
|
|
21
|
+
(
|
|
22
|
+
"uv/pip",
|
|
23
|
+
(
|
|
24
|
+
re.compile(r"\b(?:collecting|using cached|successfully installed)\b", re.I),
|
|
25
|
+
re.compile(r"\b(?:resolved|prepared|installed|uninstalled) \d+ packages?\b", re.I),
|
|
26
|
+
re.compile(
|
|
27
|
+
r"\b(?:uv sync|uv\.lock|lockfile|lock file|--frozen|--locked|"
|
|
28
|
+
r"no solution found|virtual environment at|audited \d+ packages?)\b",
|
|
29
|
+
re.I,
|
|
30
|
+
),
|
|
31
|
+
re.compile(r"^\s*[+\-~]\s+[A-Za-z0-9_.-]+==", re.I),
|
|
32
|
+
),
|
|
33
|
+
),
|
|
34
|
+
(
|
|
35
|
+
"poetry",
|
|
36
|
+
(
|
|
37
|
+
re.compile(r"\b(?:poetry\.lock|poetry install|poetry update)\b", re.I),
|
|
38
|
+
re.compile(r"^\s*Installing dependencies from lock file", re.I),
|
|
39
|
+
re.compile(r"^\s*Package operations:\s+\d+\s+installs?", re.I),
|
|
40
|
+
re.compile(r"^\s*-\s+(?:Installing|Updating|Upgrading|Downgrading|Removing)\s+[-A-Za-z0-9_.]+\s+\(", re.I),
|
|
41
|
+
re.compile(r"\b(?:version solving failed|SolverProblemError)\b", re.I),
|
|
42
|
+
),
|
|
43
|
+
),
|
|
44
|
+
(
|
|
45
|
+
"npm",
|
|
46
|
+
(
|
|
47
|
+
re.compile(r"^\s*npm\s+(?:warn|err!|notice|http|timing|verb|sill)\b", re.I),
|
|
48
|
+
re.compile(r"\b(?:added|removed|changed|updated) \d+ packages?\b", re.I),
|
|
49
|
+
re.compile(r"\b(?:audited \d+ packages?|found \d+ vulnerabilities?|npm audit fix)\b", re.I),
|
|
50
|
+
),
|
|
51
|
+
),
|
|
52
|
+
(
|
|
53
|
+
"pnpm",
|
|
54
|
+
(
|
|
55
|
+
re.compile(r"^\s*(?:Progress:\s+resolved|Packages:\s*[+\-]\d+|Lockfile is up to date)", re.I),
|
|
56
|
+
re.compile(r"\b(?:pnpm-lock\.yaml|ERR_PNPM_|WARN_PNPM_|frozen-lockfile|pnpm audit)\b", re.I),
|
|
57
|
+
re.compile(r"^\s*(?:dependencies|devDependencies|optionalDependencies|peerDependencies):\s*$", re.I),
|
|
58
|
+
re.compile(r"^\s*[+\-]\s+[@A-Za-z0-9_.-][@A-Za-z0-9_.+\-/]*\s+\d", re.I),
|
|
59
|
+
),
|
|
60
|
+
),
|
|
61
|
+
(
|
|
62
|
+
"yarn",
|
|
63
|
+
(
|
|
64
|
+
re.compile(r"^\s*yarn install v?\d", re.I),
|
|
65
|
+
re.compile(r"^\s*\[\d+/\d+\]\s+(?:Resolving|Fetching|Linking|Building)", re.I),
|
|
66
|
+
re.compile(r"^\s*(?:success Saved lockfile|success Saved \d+|Done in [\d.]+s|info Direct dependencies|info All dependencies)", re.I),
|
|
67
|
+
re.compile(r"^\s*➤\s+YN\d{4}:", re.I),
|
|
68
|
+
re.compile(r"\b(?:yarn\.lock|peer dependency|unmet peer|immutable install|lockfile would have been modified)\b", re.I),
|
|
69
|
+
),
|
|
70
|
+
),
|
|
71
|
+
(
|
|
72
|
+
"bun",
|
|
73
|
+
(
|
|
74
|
+
re.compile(r"\bbun install v?\d", re.I),
|
|
75
|
+
re.compile(r"\b(?:saved lockfile|\d+ packages? installed)\b", re.I),
|
|
76
|
+
re.compile(r"^\s*\+\s+[@A-Za-z0-9_.-]+@", re.I),
|
|
77
|
+
),
|
|
78
|
+
),
|
|
79
|
+
(
|
|
80
|
+
"composer",
|
|
81
|
+
(
|
|
82
|
+
re.compile(r"\b(?:composer\.json|composer\.lock|composer audit|security vulnerability advisories)\b", re.I),
|
|
83
|
+
re.compile(r"^\s*(?:Loading composer repositories|Installing dependencies from lock file|Generating autoload files)", re.I),
|
|
84
|
+
re.compile(r"^\s*(?:Package operations|Lock file operations):", re.I),
|
|
85
|
+
re.compile(r"^\s*-\s+(?:Locking|Installing|Updating|Upgrading|Removing|Downloading)\s+[-A-Za-z0-9_.]+/[-A-Za-z0-9_.]+\s+\(", re.I),
|
|
86
|
+
),
|
|
87
|
+
),
|
|
88
|
+
(
|
|
89
|
+
"bundler",
|
|
90
|
+
(
|
|
91
|
+
re.compile(r"\b(?:Bundle complete|Bundle updated|Gemfile\.lock|Bundler could not|rubygems\.org)\b", re.I),
|
|
92
|
+
re.compile(r"^\s*Fetching gem metadata", re.I),
|
|
93
|
+
re.compile(r"^\s*(?:Using|Fetching|Installing)\s+[-A-Za-z0-9_.]+\s+(?:\(|v?\d)", re.I),
|
|
94
|
+
re.compile(r"\bgems? now installed\b", re.I),
|
|
95
|
+
),
|
|
96
|
+
),
|
|
97
|
+
(
|
|
98
|
+
"homebrew",
|
|
99
|
+
(
|
|
100
|
+
re.compile(r"^\s*==>\s+(?:Fetching|Downloading|Pouring|Installing|Caveats|Summary|Homebrew|Autoremoving)", re.I),
|
|
101
|
+
re.compile(r"\b(?:/opt/homebrew/Cellar|/usr/local/Cellar|Homebrew/homebrew|brew install|bottle\.tar\.gz)\b", re.I),
|
|
102
|
+
re.compile(r"^\s*🍺"),
|
|
103
|
+
),
|
|
104
|
+
),
|
|
105
|
+
(
|
|
106
|
+
"cargo",
|
|
107
|
+
(
|
|
108
|
+
re.compile(r"\b(?:updating crates\.io index|downloading crates)\b", re.I),
|
|
109
|
+
re.compile(r"\b(?:locking \d+ packages?|installed package|installing [A-Za-z0-9_.-]+ v?\d)\b", re.I),
|
|
110
|
+
re.compile(r"^\s*(?:adding|removing|updating|downgrading)\s+[A-Za-z0-9_.-]+\s+v?\d", re.I),
|
|
111
|
+
),
|
|
112
|
+
),
|
|
113
|
+
(
|
|
114
|
+
"pre-commit",
|
|
115
|
+
(
|
|
116
|
+
re.compile(r"^\s*pre-commit installed at\b", re.I),
|
|
117
|
+
re.compile(r"^\s*\[INFO\]\s+(?:Initializing|Installing|Updating) environment for\b", re.I),
|
|
118
|
+
re.compile(r"\b(?:\.cache/pre-commit|pre-commit\.log)\b", re.I),
|
|
119
|
+
re.compile(r"^\s*\[ERROR\].*(?:pre-commit|unexpected error|CalledProcessError|log at)", re.I),
|
|
120
|
+
),
|
|
121
|
+
),
|
|
122
|
+
)
|
|
123
|
+
_FAILURE_RE = re.compile(
|
|
124
|
+
r"\b(?:error|err!|err_[a-z0-9_]+|failed|failure|fatal|no solution|conflict|"
|
|
125
|
+
r"could not|version solving failed|SolverProblemError)\b|^×\s|YN(?:0001|0028)",
|
|
126
|
+
re.I,
|
|
127
|
+
)
|
|
128
|
+
_WARNING_RE = re.compile(
|
|
129
|
+
r"\b(?:warn|warning|vulnerab|security advisory|advisory|advisories|peer dependency|unmet peer)\b|"
|
|
130
|
+
r"YN(?:0002|0060|0086)",
|
|
131
|
+
re.I,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def detect_package_manager_log(lines: list[str]) -> PackageManagerLog | None:
|
|
136
|
+
"""Return a strong package-manager signal, or ``None``."""
|
|
137
|
+
|
|
138
|
+
nonblank = [line for line in lines if line.strip()]
|
|
139
|
+
if len(nonblank) < 2:
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
text = "\n".join(nonblank)
|
|
143
|
+
if has_compiler_diagnostics(text):
|
|
144
|
+
return None
|
|
145
|
+
if _has_pre_commit_hook_results(nonblank):
|
|
146
|
+
return None
|
|
147
|
+
|
|
148
|
+
manager_scores: dict[str, int] = {}
|
|
149
|
+
for manager, patterns in _MANAGER_PATTERNS:
|
|
150
|
+
score = sum(1 for line in nonblank for pattern in patterns if pattern.search(line))
|
|
151
|
+
if score:
|
|
152
|
+
manager_scores[manager] = score
|
|
153
|
+
|
|
154
|
+
if "pnpm" in manager_scores and not _has_pnpm_anchor(nonblank):
|
|
155
|
+
del manager_scores["pnpm"]
|
|
156
|
+
if "homebrew" in manager_scores and not _has_homebrew_anchor(nonblank):
|
|
157
|
+
del manager_scores["homebrew"]
|
|
158
|
+
if "bundler" in manager_scores and not _has_bundler_anchor(nonblank):
|
|
159
|
+
del manager_scores["bundler"]
|
|
160
|
+
|
|
161
|
+
if not manager_scores:
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
total_score = sum(manager_scores.values())
|
|
165
|
+
has_strong_multiline_signal = total_score >= 2 and len(nonblank) >= 4
|
|
166
|
+
has_failure_signal = total_score >= 1 and len(nonblank) >= 4 and _FAILURE_RE.search(text)
|
|
167
|
+
if not (has_strong_multiline_signal or has_failure_signal):
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
manager = max(manager_scores.items(), key=lambda item: (item[1], item[0]))[0]
|
|
171
|
+
return PackageManagerLog(
|
|
172
|
+
manager=manager,
|
|
173
|
+
failed=_FAILURE_RE.search(text) is not None,
|
|
174
|
+
warning=_WARNING_RE.search(text) is not None,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _has_pre_commit_hook_results(lines: list[str]) -> bool:
|
|
179
|
+
return any(
|
|
180
|
+
re.search(r"\.{3,}(?:Passed|Failed|Skipped)$", line.strip()) for line in lines
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _has_pnpm_anchor(lines: list[str]) -> bool:
|
|
185
|
+
text = "\n".join(lines)
|
|
186
|
+
return re.search(
|
|
187
|
+
r"\b(?:pnpm-lock\.yaml|ERR_PNPM_|WARN_PNPM_|frozen-lockfile|pnpm audit|pnpm install)\b",
|
|
188
|
+
text,
|
|
189
|
+
re.I,
|
|
190
|
+
) is not None or any(
|
|
191
|
+
re.search(
|
|
192
|
+
r"^\s*(?:Progress:\s+resolved|Packages:\s*[+\-]\d+|Lockfile is up to date)",
|
|
193
|
+
line,
|
|
194
|
+
re.I,
|
|
195
|
+
)
|
|
196
|
+
for line in lines
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _has_homebrew_anchor(lines: list[str]) -> bool:
|
|
201
|
+
return any(
|
|
202
|
+
re.search(
|
|
203
|
+
r"\b(?:Homebrew|brew\b|Cellar|bottle|formula|cask|tap)\b|"
|
|
204
|
+
r"(?:/opt/homebrew|/usr/local/Homebrew|/usr/local/Cellar)\b|^\s*🍺",
|
|
205
|
+
line,
|
|
206
|
+
re.I,
|
|
207
|
+
)
|
|
208
|
+
for line in lines
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _has_bundler_anchor(lines: list[str]) -> bool:
|
|
213
|
+
return any(
|
|
214
|
+
re.search(
|
|
215
|
+
r"\b(?:Gemfile\.lock|bundler|rubygems\.org|Bundle complete|Bundle updated|gems? now installed)\b",
|
|
216
|
+
line,
|
|
217
|
+
re.I,
|
|
218
|
+
)
|
|
219
|
+
for line in lines
|
|
220
|
+
)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Conservative reducer for package-manager logs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ..text import join_preserving_final_newline, score, split_preserving_final_newline
|
|
6
|
+
from .detector import PackageManagerLog, detect_package_manager_log
|
|
7
|
+
from .summary import (
|
|
8
|
+
is_important_line,
|
|
9
|
+
is_package_change_line,
|
|
10
|
+
is_progress_line,
|
|
11
|
+
normalize_selected_line,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
_PACKAGE_HEAD = 3
|
|
15
|
+
_PACKAGE_TAIL = 2
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def compress_package_manager_output(text: str) -> str:
|
|
19
|
+
"""Compress conservative package install/update logs."""
|
|
20
|
+
|
|
21
|
+
lines, final_newline = split_preserving_final_newline(text)
|
|
22
|
+
signal = detect_package_manager_log(lines)
|
|
23
|
+
if signal is None:
|
|
24
|
+
return text
|
|
25
|
+
|
|
26
|
+
candidate_lines = _reduce_lines(lines, signal)
|
|
27
|
+
if len(candidate_lines) < 2:
|
|
28
|
+
return text
|
|
29
|
+
|
|
30
|
+
candidate = join_preserving_final_newline(candidate_lines, final_newline)
|
|
31
|
+
if score(candidate) < score(text):
|
|
32
|
+
return candidate
|
|
33
|
+
return text
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _reduce_lines(lines: list[str], signal: PackageManagerLog) -> list[str]:
|
|
37
|
+
outcome = "failure" if signal.failed else "warning" if signal.warning else "success"
|
|
38
|
+
selected = [f"package manager: {signal.manager} {outcome}"]
|
|
39
|
+
progress_omitted = 0
|
|
40
|
+
package_run: list[str] = []
|
|
41
|
+
|
|
42
|
+
def flush_progress() -> None:
|
|
43
|
+
nonlocal progress_omitted
|
|
44
|
+
if progress_omitted:
|
|
45
|
+
plural = "" if progress_omitted == 1 else "s"
|
|
46
|
+
selected.append(
|
|
47
|
+
f"… {progress_omitted} package-manager progress line{plural} omitted"
|
|
48
|
+
)
|
|
49
|
+
progress_omitted = 0
|
|
50
|
+
|
|
51
|
+
def flush_package_run() -> None:
|
|
52
|
+
if not package_run:
|
|
53
|
+
return
|
|
54
|
+
flush_progress()
|
|
55
|
+
selected.extend(_format_package_run(package_run))
|
|
56
|
+
package_run.clear()
|
|
57
|
+
|
|
58
|
+
for raw_line in lines:
|
|
59
|
+
line = raw_line.strip()
|
|
60
|
+
if not line:
|
|
61
|
+
continue
|
|
62
|
+
|
|
63
|
+
if is_package_change_line(line):
|
|
64
|
+
package_run.append(normalize_selected_line(line))
|
|
65
|
+
continue
|
|
66
|
+
|
|
67
|
+
flush_package_run()
|
|
68
|
+
|
|
69
|
+
if is_important_line(line):
|
|
70
|
+
flush_progress()
|
|
71
|
+
selected.append(normalize_selected_line(line))
|
|
72
|
+
continue
|
|
73
|
+
|
|
74
|
+
if is_progress_line(line):
|
|
75
|
+
progress_omitted += 1
|
|
76
|
+
continue
|
|
77
|
+
|
|
78
|
+
if signal.failed:
|
|
79
|
+
flush_progress()
|
|
80
|
+
selected.append(normalize_selected_line(line))
|
|
81
|
+
else:
|
|
82
|
+
progress_omitted += 1
|
|
83
|
+
|
|
84
|
+
flush_package_run()
|
|
85
|
+
flush_progress()
|
|
86
|
+
return _drop_adjacent_duplicates(selected)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _format_package_run(lines: list[str]) -> list[str]:
|
|
90
|
+
if len(lines) <= _PACKAGE_HEAD + _PACKAGE_TAIL + 1:
|
|
91
|
+
return list(lines)
|
|
92
|
+
|
|
93
|
+
omitted = len(lines) - _PACKAGE_HEAD - _PACKAGE_TAIL
|
|
94
|
+
plural = "" if omitted == 1 else "s"
|
|
95
|
+
return [
|
|
96
|
+
*lines[:_PACKAGE_HEAD],
|
|
97
|
+
f"… {omitted} package change line{plural} omitted",
|
|
98
|
+
*lines[-_PACKAGE_TAIL:],
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _drop_adjacent_duplicates(lines: list[str]) -> list[str]:
|
|
103
|
+
output: list[str] = []
|
|
104
|
+
for line in lines:
|
|
105
|
+
if not line:
|
|
106
|
+
continue
|
|
107
|
+
if output and output[-1] == line:
|
|
108
|
+
continue
|
|
109
|
+
output.append(line)
|
|
110
|
+
return output
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Line classification helpers for package-manager logs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
_URL_RE = re.compile(r"\b[A-Za-z][A-Za-z0-9+.-]*://\S+")
|
|
8
|
+
_PATH_RE = re.compile(
|
|
9
|
+
r"(^|[\s=:\"'`(\[])"
|
|
10
|
+
r"(?:\.{0,2}/|/|~/|[A-Za-z]:[\\/]|[A-Za-z0-9_.@+-]+/)"
|
|
11
|
+
r"[A-Za-z0-9_.@+/\-\\]+"
|
|
12
|
+
)
|
|
13
|
+
_FINAL_SUMMARY_RE = re.compile(
|
|
14
|
+
r"\b(?:"
|
|
15
|
+
r"successfully installed|installed \d+ packages?|\d+ packages? installed|uninstalled \d+ packages?|"
|
|
16
|
+
r"prepared \d+ packages?|resolved \d+ packages?|audited \d+ packages?|"
|
|
17
|
+
r"added \d+ packages?|removed \d+ packages?|changed \d+ packages?|"
|
|
18
|
+
r"updated \d+ packages?|found \d+ vulnerabilit(?:y|ies)|found 0 vulnerabilities|"
|
|
19
|
+
r"saved lockfile|installed package|installing collected packages|"
|
|
20
|
+
r"package operations|lock file operations|writing lock file|installing dependencies from lock file|"
|
|
21
|
+
r"bundle complete|bundle updated|gems? now installed|bundled gems are installed into|"
|
|
22
|
+
r"generating autoload files|security vulnerability advisor(?:y|ies)|"
|
|
23
|
+
r"lock(?:file| file)|frozen|frozen-lockfile|locked|outdated lockfile|"
|
|
24
|
+
r"lockfile is up to date|immutable install|lockfile would have been modified|"
|
|
25
|
+
r"no solution found|version solving failed|peer dependenc|unmet peer|incorrect peer dependency|"
|
|
26
|
+
r"creating virtual environment at|pre-commit installed at|check the log at"
|
|
27
|
+
r")\b|"
|
|
28
|
+
r"^\s*(?:dependencies|devDependencies|optionalDependencies|peerDependencies):\s*$|"
|
|
29
|
+
r"^\s*(?:Done in [\d.]+s\.?|success Saved \d+|success Saved lockfile|"
|
|
30
|
+
r"info Direct dependencies|info All dependencies)|"
|
|
31
|
+
r"^\s*==>\s+(?:Caveats|Summary)\b|^\s*🍺|"
|
|
32
|
+
r"^\s*➤\s+YN(?:0001|0002|0028|0060|0086|[1-9]\d{3}):",
|
|
33
|
+
re.IGNORECASE,
|
|
34
|
+
)
|
|
35
|
+
_ERROR_WARNING_RE = re.compile(
|
|
36
|
+
r"(^|\b|[!])(?:error|err|warn|warning|failed|failure|fatal|could not)\b|"
|
|
37
|
+
r"\bERR_[A-Z0-9_]+\b|^×\s|^\s*➤\s+YN(?:0001|0028):",
|
|
38
|
+
re.IGNORECASE,
|
|
39
|
+
)
|
|
40
|
+
_HINT_RE = re.compile(
|
|
41
|
+
r"^\s*(?:hint|note|help):|"
|
|
42
|
+
r"\b(?:run `?npm audit fix|npm audit fix|composer audit|to address all issues|"
|
|
43
|
+
r"because .+ depends on|requires .+ but|fixed in:|advisory:|severity:|"
|
|
44
|
+
r"name:|version:|"
|
|
45
|
+
r"Run `?yarn explain peer-requirements|peer requirement)\b",
|
|
46
|
+
re.IGNORECASE,
|
|
47
|
+
)
|
|
48
|
+
_SECURITY_DETAIL_RE = re.compile(
|
|
49
|
+
r"^\s*(?:name|version|advisory|severity|fixed in):|"
|
|
50
|
+
r"^\s*\|?\s*(?:Package|Severity|CVE|Title|Affected versions?)\s*\|",
|
|
51
|
+
re.IGNORECASE,
|
|
52
|
+
)
|
|
53
|
+
_SECURITY_TABLE_VALUE_RE = re.compile(
|
|
54
|
+
r"\b(?:CVE-\d{4}-\d+|GHSA-[A-Za-z0-9-]+|critical|high|moderate|medium|low)\b",
|
|
55
|
+
re.IGNORECASE,
|
|
56
|
+
)
|
|
57
|
+
_HOMEBREW_CAVEAT_COMMAND_RE = re.compile(
|
|
58
|
+
r"^\s*(?:brew\s+(?:services|link|unlink|install|reinstall|restart|start|stop|tap)\b|"
|
|
59
|
+
r"(?:sudo\s+)?(?:launchctl|systemctl)\b)",
|
|
60
|
+
re.IGNORECASE,
|
|
61
|
+
)
|
|
62
|
+
_PROGRESS_RE = re.compile(
|
|
63
|
+
r"^\s*(?:"
|
|
64
|
+
r"collecting |downloading |downloaded |using cached |fetching |"
|
|
65
|
+
r"resolving |resolve |resolved, downloaded|preparing |prepared metadata|"
|
|
66
|
+
r"using cpython|using python|creating virtualenv|creating virtual environment|"
|
|
67
|
+
r"building wheel|created wheel|stored in directory|"
|
|
68
|
+
r"requirement already satisfied|installing build dependencies|"
|
|
69
|
+
r"getting requirements to build wheel|checking if build backend|"
|
|
70
|
+
r"npm http fetch|npm timing|npm sill|npm verb|"
|
|
71
|
+
r"loading composer repositories|updating dependencies|generating optimized autoload files|"
|
|
72
|
+
r"fetching gem metadata|"
|
|
73
|
+
r"progress: resolved|already up to date|"
|
|
74
|
+
r"\[\d+/\d+\]\s+(?:resolving|fetching|linking|building)|"
|
|
75
|
+
r"info (?:no lockfile found|there appears to be trouble|visit https?://)|"
|
|
76
|
+
r"==>\s+(?:fetching|downloading|pouring|installing|autoremoving)|"
|
|
77
|
+
r"\[INFO\]\s+(?:initializing|installing|updating) environment for|"
|
|
78
|
+
r"\[INFO\]\s+(?:once installed|this may take|stashing unstaged files|restored changes)|"
|
|
79
|
+
r"➤\s+YN0000:.*(?:resolution step|fetch step|link step|done|completed|yarn )|"
|
|
80
|
+
r"updating crates\.io index|downloading crates|compiling |checking |fresh |"
|
|
81
|
+
r"installing [A-Za-z0-9_.@+-]+ v?\d"
|
|
82
|
+
r")",
|
|
83
|
+
re.IGNORECASE,
|
|
84
|
+
)
|
|
85
|
+
_PACKAGE_CHANGE_RE = re.compile(
|
|
86
|
+
r"^\s*(?:"
|
|
87
|
+
r"[+\-~]\s+[@A-Za-z0-9_.-][@A-Za-z0-9_.+\-/]*(?:==|@| v?\d|\d|[<>=~^])|"
|
|
88
|
+
r"[├└]─\s+[@A-Za-z0-9_.-][@A-Za-z0-9_.+\-/]*@|"
|
|
89
|
+
r"-\s+(?:Installing|Updating|Upgrading|Downgrading|Removing|Locking|Downloading)\s+[-A-Za-z0-9_./]+(?:\s+\(|\s+v?\d|\s+dev-)|"
|
|
90
|
+
r"(?:Using|Fetching|Installing)\s+(?!CPython\b|Python\b)[-A-Za-z0-9_.]+(?:\s+\(|\s+\d)|"
|
|
91
|
+
r"(?:adding|removing|updating|downgrading)\s+[@A-Za-z0-9_.-]+(?:\s+v?\d|@|$)|"
|
|
92
|
+
r"installed\s+[@A-Za-z0-9_.-]+[@A-Za-z0-9_.+\-/]*(?:@|==| v?\d)"
|
|
93
|
+
r")",
|
|
94
|
+
re.IGNORECASE,
|
|
95
|
+
)
|
|
96
|
+
_COMPILER_SPAN_RE = re.compile(r"^\s*-->\s+\S+:\d+:\d+", re.MULTILINE)
|
|
97
|
+
_RUST_ERROR_CODE_RE = re.compile(r"^\s*(?:error|warning)\[E\d+\]", re.MULTILINE)
|
|
98
|
+
_RUST_SOURCE_BAR_RE = re.compile(r"^\s*(?:\d+\s+)?\|", re.MULTILINE)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def is_error_warning_line(line: str) -> bool:
|
|
102
|
+
return _ERROR_WARNING_RE.search(line.strip()) is not None
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def is_final_summary_line(line: str) -> bool:
|
|
106
|
+
return _FINAL_SUMMARY_RE.search(line) is not None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def is_hint_line(line: str) -> bool:
|
|
110
|
+
return (
|
|
111
|
+
_HINT_RE.search(line) is not None
|
|
112
|
+
or _SECURITY_DETAIL_RE.search(line) is not None
|
|
113
|
+
or _SECURITY_TABLE_VALUE_RE.search(line) is not None
|
|
114
|
+
or _HOMEBREW_CAVEAT_COMMAND_RE.search(line) is not None
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def has_reference(line: str) -> bool:
|
|
119
|
+
return _URL_RE.search(line) is not None or _PATH_RE.search(line) is not None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def is_package_change_line(line: str) -> bool:
|
|
123
|
+
return _PACKAGE_CHANGE_RE.search(line) is not None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def is_progress_line(line: str) -> bool:
|
|
127
|
+
stripped = line.strip()
|
|
128
|
+
if not stripped:
|
|
129
|
+
return False
|
|
130
|
+
if set(stripped) <= {"-", "=", ">", ".", " ", "#"}:
|
|
131
|
+
return True
|
|
132
|
+
return _PROGRESS_RE.search(stripped) is not None
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def is_important_line(line: str) -> bool:
|
|
136
|
+
"""Return whether a package-manager line must be preserved."""
|
|
137
|
+
|
|
138
|
+
if is_progress_line(line):
|
|
139
|
+
return (
|
|
140
|
+
is_error_warning_line(line)
|
|
141
|
+
or is_final_summary_line(line)
|
|
142
|
+
or is_hint_line(line)
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
is_error_warning_line(line)
|
|
147
|
+
or is_final_summary_line(line)
|
|
148
|
+
or is_hint_line(line)
|
|
149
|
+
or has_reference(line)
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def has_compiler_diagnostics(text: str) -> bool:
|
|
154
|
+
"""Return true for Rust/compiler diagnostic spans outside this filter scope."""
|
|
155
|
+
|
|
156
|
+
lower = text.lower()
|
|
157
|
+
return (
|
|
158
|
+
_RUST_ERROR_CODE_RE.search(text) is not None
|
|
159
|
+
or (
|
|
160
|
+
_COMPILER_SPAN_RE.search(text) is not None
|
|
161
|
+
and (
|
|
162
|
+
_RUST_SOURCE_BAR_RE.search(text) is not None
|
|
163
|
+
or "could not compile" in lower
|
|
164
|
+
or "error:" in lower
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
or "could not compile" in lower
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def normalize_selected_line(line: str) -> str:
|
|
172
|
+
return line.strip()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Filter pipeline orchestration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Callable
|
|
7
|
+
|
|
8
|
+
from .build_compiler import compress_build_compiler_diagnostics_output
|
|
9
|
+
from .ci_job_log import compress_ci_job_log_output
|
|
10
|
+
from .diff import compress_diff_output
|
|
11
|
+
from .generic_log import compress_generic_log_output
|
|
12
|
+
from .git import compress_git_output
|
|
13
|
+
from .html_cleanup import compress_html_cleanup_output
|
|
14
|
+
from .json_payload import compress_json_payload_output
|
|
15
|
+
from .listing import compress_listing_output
|
|
16
|
+
from .log_template import compress_log_template_output
|
|
17
|
+
from .opaque_payload import compress_opaque_payload_output
|
|
18
|
+
from .package_manager import compress_package_manager_output
|
|
19
|
+
from .rg import compress_rg_output
|
|
20
|
+
from .system_output import compress_system_output
|
|
21
|
+
from .table import compress_table_output
|
|
22
|
+
from .test_runner import compress_test_runner_output
|
|
23
|
+
from .text import score
|
|
24
|
+
from .traceback import compress_traceback_output
|
|
25
|
+
|
|
26
|
+
FilterFn = Callable[[str], str]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class TextFilter:
|
|
31
|
+
"""A named output filter in the compression pipeline."""
|
|
32
|
+
|
|
33
|
+
name: str
|
|
34
|
+
apply: FilterFn
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
FILTER_PIPELINE: tuple[TextFilter, ...] = (
|
|
38
|
+
TextFilter("git", compress_git_output),
|
|
39
|
+
TextFilter("diff", compress_diff_output),
|
|
40
|
+
TextFilter("test-runner", compress_test_runner_output),
|
|
41
|
+
TextFilter("ci-job-log", compress_ci_job_log_output),
|
|
42
|
+
TextFilter("traceback", compress_traceback_output),
|
|
43
|
+
TextFilter("json", compress_json_payload_output),
|
|
44
|
+
TextFilter("html", compress_html_cleanup_output),
|
|
45
|
+
TextFilter("opaque-payload", compress_opaque_payload_output),
|
|
46
|
+
TextFilter("system-output", compress_system_output),
|
|
47
|
+
TextFilter("table", compress_table_output),
|
|
48
|
+
TextFilter("listing", compress_listing_output),
|
|
49
|
+
TextFilter("build-compiler", compress_build_compiler_diagnostics_output),
|
|
50
|
+
TextFilter("rg", compress_rg_output),
|
|
51
|
+
TextFilter("package-manager", compress_package_manager_output),
|
|
52
|
+
TextFilter("log-template", compress_log_template_output),
|
|
53
|
+
TextFilter("generic-log", compress_generic_log_output),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def apply_filter_pipeline(text: str) -> str:
|
|
58
|
+
"""Run all dedicated filters and keep only size-improving results."""
|
|
59
|
+
|
|
60
|
+
current = text
|
|
61
|
+
for text_filter in FILTER_PIPELINE:
|
|
62
|
+
candidate = text_filter.apply(current)
|
|
63
|
+
if score(candidate) < score(current):
|
|
64
|
+
current = candidate
|
|
65
|
+
return current
|