codetool-shell 0.1.1__py3-none-win_amd64.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-x86_64/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,250 @@
|
|
|
1
|
+
"""Compression for ripgrep/path-location output."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
from .text import (
|
|
9
|
+
choose_smaller_lines,
|
|
10
|
+
join_preserving_final_newline,
|
|
11
|
+
split_preserving_final_newline,
|
|
12
|
+
)
|
|
13
|
+
from .tree import TreeRow, format_tree
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def compress_rg_output(text: str) -> str:
|
|
17
|
+
"""Compress rg-like path-location output by grouping repeated paths."""
|
|
18
|
+
|
|
19
|
+
lines, final_newline = split_preserving_final_newline(text)
|
|
20
|
+
output: list[str] = []
|
|
21
|
+
i = 0
|
|
22
|
+
|
|
23
|
+
while i < len(lines):
|
|
24
|
+
direct_block = _collect_direct_block(lines, i)
|
|
25
|
+
if direct_block is not None:
|
|
26
|
+
block_lines, block_items, i = direct_block
|
|
27
|
+
output.extend(_compress_rg_block(block_lines, block_items))
|
|
28
|
+
continue
|
|
29
|
+
|
|
30
|
+
heading_block = _collect_heading_block(lines, i)
|
|
31
|
+
if heading_block is not None:
|
|
32
|
+
block_lines, block_items, i = heading_block
|
|
33
|
+
output.extend(_compress_rg_block(block_lines, block_items))
|
|
34
|
+
continue
|
|
35
|
+
|
|
36
|
+
output.append(lines[i])
|
|
37
|
+
i += 1
|
|
38
|
+
|
|
39
|
+
return join_preserving_final_newline(output, final_newline)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class _RgEntry:
|
|
44
|
+
path: str
|
|
45
|
+
line: str
|
|
46
|
+
column: str | None
|
|
47
|
+
text: str
|
|
48
|
+
separator: str
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
_ANSI_RE = re.compile(r"\x1b\[[0-9;?]*[ -/]*[@-~]")
|
|
52
|
+
_RG_MATCH_RE = re.compile(r"^(.+?):([1-9]\d*)(?::([1-9]\d*))?:(.*)$")
|
|
53
|
+
_RG_CONTEXT_RE = re.compile(r"^(.+?)-([1-9]\d*)-(.*)$")
|
|
54
|
+
_HEADING_MATCH_RE = re.compile(r"^([1-9]\d*)(?::([1-9]\d*))?:(.*)$")
|
|
55
|
+
_HEADING_CONTEXT_RE = re.compile(r"^([1-9]\d*)-(.*)$")
|
|
56
|
+
_DIAGNOSTIC_TEXT_RE = re.compile(
|
|
57
|
+
r"^\s*(?:error|warning|note|help):|\b(?:error|warning)\s+TS\d+:",
|
|
58
|
+
re.IGNORECASE,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _strip_ansi(line: str) -> str:
|
|
63
|
+
return _ANSI_RE.sub("", line)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _collect_direct_block(
|
|
67
|
+
lines: list[str], start: int
|
|
68
|
+
) -> tuple[list[str], list[_RgEntry | None], int] | None:
|
|
69
|
+
parsed = _parse_path_location_line(lines[start])
|
|
70
|
+
if parsed is None:
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
block_lines = [lines[start]]
|
|
74
|
+
block_items: list[_RgEntry | None] = [parsed]
|
|
75
|
+
i = start + 1
|
|
76
|
+
while i < len(lines):
|
|
77
|
+
parsed = _parse_path_location_line(lines[i])
|
|
78
|
+
if parsed is not None:
|
|
79
|
+
block_lines.append(lines[i])
|
|
80
|
+
block_items.append(parsed)
|
|
81
|
+
i += 1
|
|
82
|
+
continue
|
|
83
|
+
if _is_context_separator(lines[i]) and _next_line_is_direct_entry(lines, i):
|
|
84
|
+
block_lines.append(lines[i])
|
|
85
|
+
block_items.append(None)
|
|
86
|
+
i += 1
|
|
87
|
+
continue
|
|
88
|
+
break
|
|
89
|
+
|
|
90
|
+
return block_lines, block_items, i
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _collect_heading_block(
|
|
94
|
+
lines: list[str], start: int
|
|
95
|
+
) -> tuple[list[str], list[_RgEntry | None], int] | None:
|
|
96
|
+
clean_header = _strip_ansi(lines[start]).strip()
|
|
97
|
+
if not _looks_like_rg_path(clean_header):
|
|
98
|
+
return None
|
|
99
|
+
if start + 1 >= len(lines):
|
|
100
|
+
return None
|
|
101
|
+
if _parse_heading_entry_line(lines[start + 1], clean_header) is None:
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
block_lines: list[str] = []
|
|
105
|
+
block_items: list[_RgEntry | None] = []
|
|
106
|
+
i = start
|
|
107
|
+
header_count = 0
|
|
108
|
+
|
|
109
|
+
while i < len(lines):
|
|
110
|
+
path = _strip_ansi(lines[i]).strip()
|
|
111
|
+
if not _looks_like_rg_path(path):
|
|
112
|
+
break
|
|
113
|
+
if i + 1 >= len(lines) or _parse_heading_entry_line(lines[i + 1], path) is None:
|
|
114
|
+
break
|
|
115
|
+
|
|
116
|
+
header_count += 1
|
|
117
|
+
block_lines.append(lines[i])
|
|
118
|
+
i += 1
|
|
119
|
+
|
|
120
|
+
while i < len(lines):
|
|
121
|
+
parsed = _parse_heading_entry_line(lines[i], path)
|
|
122
|
+
if parsed is not None:
|
|
123
|
+
block_lines.append(lines[i])
|
|
124
|
+
block_items.append(parsed)
|
|
125
|
+
i += 1
|
|
126
|
+
continue
|
|
127
|
+
if _is_context_separator(lines[i]) and _next_line_is_heading_entry_or_path(
|
|
128
|
+
lines, i, path
|
|
129
|
+
):
|
|
130
|
+
block_lines.append(lines[i])
|
|
131
|
+
block_items.append(None)
|
|
132
|
+
i += 1
|
|
133
|
+
continue
|
|
134
|
+
break
|
|
135
|
+
|
|
136
|
+
entries = [item for item in block_items if item is not None]
|
|
137
|
+
if header_count == 0 or len(entries) < 2:
|
|
138
|
+
return None
|
|
139
|
+
return block_lines, block_items, i
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _parse_path_location_line(line: str) -> _RgEntry | None:
|
|
143
|
+
clean = _strip_ansi(line)
|
|
144
|
+
match = _RG_MATCH_RE.match(clean)
|
|
145
|
+
if match is not None:
|
|
146
|
+
path, line_number, column, snippet = match.groups()
|
|
147
|
+
if _looks_like_rg_path(path):
|
|
148
|
+
return _RgEntry(path, line_number, column, snippet, ":")
|
|
149
|
+
|
|
150
|
+
context = _RG_CONTEXT_RE.match(clean)
|
|
151
|
+
if context is None:
|
|
152
|
+
return None
|
|
153
|
+
path, line_number, snippet = context.groups()
|
|
154
|
+
if not _looks_like_rg_path(path):
|
|
155
|
+
return None
|
|
156
|
+
return _RgEntry(path, line_number, None, snippet, "-")
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _parse_heading_entry_line(line: str, path: str) -> _RgEntry | None:
|
|
160
|
+
clean = _strip_ansi(line)
|
|
161
|
+
match = _HEADING_MATCH_RE.match(clean)
|
|
162
|
+
if match is not None:
|
|
163
|
+
line_number, column, snippet = match.groups()
|
|
164
|
+
return _RgEntry(path, line_number, column, snippet, ":")
|
|
165
|
+
|
|
166
|
+
context = _HEADING_CONTEXT_RE.match(clean)
|
|
167
|
+
if context is None:
|
|
168
|
+
return None
|
|
169
|
+
line_number, snippet = context.groups()
|
|
170
|
+
return _RgEntry(path, line_number, None, snippet, "-")
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _is_context_separator(line: str) -> bool:
|
|
174
|
+
return _strip_ansi(line).strip() == "--"
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _next_line_is_direct_entry(lines: list[str], index: int) -> bool:
|
|
178
|
+
return index + 1 < len(lines) and _parse_path_location_line(lines[index + 1]) is not None
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _next_line_is_heading_entry_or_path(lines: list[str], index: int, path: str) -> bool:
|
|
182
|
+
if index + 1 >= len(lines):
|
|
183
|
+
return False
|
|
184
|
+
if _parse_heading_entry_line(lines[index + 1], path) is not None:
|
|
185
|
+
return True
|
|
186
|
+
next_clean = _strip_ansi(lines[index + 1]).strip()
|
|
187
|
+
return (
|
|
188
|
+
_looks_like_rg_path(next_clean)
|
|
189
|
+
and index + 2 < len(lines)
|
|
190
|
+
and _parse_heading_entry_line(lines[index + 2], next_clean) is not None
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _looks_like_rg_path(path: str) -> bool:
|
|
195
|
+
path = path.strip()
|
|
196
|
+
if not path or path.isdigit() or path[0].isspace():
|
|
197
|
+
return False
|
|
198
|
+
if len(path) > 240 or "://" in path:
|
|
199
|
+
return False
|
|
200
|
+
if any(ch.isspace() for ch in path):
|
|
201
|
+
return False
|
|
202
|
+
if path[0] in "-+|":
|
|
203
|
+
return False
|
|
204
|
+
if any(ch in path for ch in "{}[]\"',"):
|
|
205
|
+
return False
|
|
206
|
+
return (
|
|
207
|
+
"/" in path
|
|
208
|
+
or "\\" in path
|
|
209
|
+
or "." in path
|
|
210
|
+
or path.startswith(("~", "/"))
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _compress_rg_block(block_lines: list[str], items: list[_RgEntry | None]) -> list[str]:
|
|
215
|
+
entries = [item for item in items if item is not None]
|
|
216
|
+
if len(entries) < 2 or _looks_like_diagnostic_block(entries):
|
|
217
|
+
return block_lines
|
|
218
|
+
|
|
219
|
+
grouped: list[tuple[str, list[str]]] = []
|
|
220
|
+
for item in items:
|
|
221
|
+
if item is None:
|
|
222
|
+
if grouped:
|
|
223
|
+
grouped[-1][1].append("--")
|
|
224
|
+
continue
|
|
225
|
+
|
|
226
|
+
entry = _format_entry(item)
|
|
227
|
+
for path, path_entries in grouped:
|
|
228
|
+
if path == item.path:
|
|
229
|
+
path_entries.append(entry)
|
|
230
|
+
break
|
|
231
|
+
else:
|
|
232
|
+
grouped.append((item.path, [entry]))
|
|
233
|
+
|
|
234
|
+
rows = [TreeRow(path, children=tuple(entries)) for path, entries in grouped]
|
|
235
|
+
candidate = format_tree(rows).splitlines()
|
|
236
|
+
return choose_smaller_lines(block_lines, candidate)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _format_entry(entry: _RgEntry) -> str:
|
|
240
|
+
if entry.separator == "-":
|
|
241
|
+
return f"{entry.line}-{entry.text}"
|
|
242
|
+
location = entry.line if entry.column is None else f"{entry.line}:{entry.column}"
|
|
243
|
+
return f"{location}:{entry.text}"
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _looks_like_diagnostic_block(entries: list[_RgEntry]) -> bool:
|
|
247
|
+
diagnostic_count = sum(
|
|
248
|
+
1 for entry in entries if _DIAGNOSTIC_TEXT_RE.search(entry.text) is not None
|
|
249
|
+
)
|
|
250
|
+
return diagnostic_count >= 2 or diagnostic_count == len(entries)
|