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.
Files changed (72) hide show
  1. codetool_shell/__init__.py +11 -0
  2. codetool_shell/api.py +59 -0
  3. codetool_shell/bin/windows-arm64/codetool-shell-rust.exe +0 -0
  4. codetool_shell/filters/__init__.py +14 -0
  5. codetool_shell/filters/build_compiler/__init__.py +7 -0
  6. codetool_shell/filters/build_compiler/detector.py +412 -0
  7. codetool_shell/filters/build_compiler/reducer.py +166 -0
  8. codetool_shell/filters/build_compiler/summary.py +617 -0
  9. codetool_shell/filters/ci_job_log/__init__.py +7 -0
  10. codetool_shell/filters/ci_job_log/detector.py +64 -0
  11. codetool_shell/filters/ci_job_log/reducer.py +99 -0
  12. codetool_shell/filters/ci_job_log/summary.py +243 -0
  13. codetool_shell/filters/diff/__init__.py +7 -0
  14. codetool_shell/filters/diff/detector.py +136 -0
  15. codetool_shell/filters/diff/reducer.py +308 -0
  16. codetool_shell/filters/generic_log/__init__.py +7 -0
  17. codetool_shell/filters/generic_log/detector.py +175 -0
  18. codetool_shell/filters/generic_log/reducer.py +99 -0
  19. codetool_shell/filters/generic_log/summary.py +161 -0
  20. codetool_shell/filters/git.py +514 -0
  21. codetool_shell/filters/html_cleanup/__init__.py +7 -0
  22. codetool_shell/filters/html_cleanup/detector.py +136 -0
  23. codetool_shell/filters/html_cleanup/reducer.py +27 -0
  24. codetool_shell/filters/html_cleanup/summary.py +422 -0
  25. codetool_shell/filters/json_payload/__init__.py +7 -0
  26. codetool_shell/filters/json_payload/detector.py +62 -0
  27. codetool_shell/filters/json_payload/reducer.py +81 -0
  28. codetool_shell/filters/json_payload/summary.py +233 -0
  29. codetool_shell/filters/listing/__init__.py +7 -0
  30. codetool_shell/filters/listing/detector.py +294 -0
  31. codetool_shell/filters/listing/reducer.py +30 -0
  32. codetool_shell/filters/log_template/__init__.py +7 -0
  33. codetool_shell/filters/log_template/constants.py +76 -0
  34. codetool_shell/filters/log_template/detector.py +331 -0
  35. codetool_shell/filters/log_template/reducer.py +78 -0
  36. codetool_shell/filters/log_template/template.py +280 -0
  37. codetool_shell/filters/log_template/types.py +21 -0
  38. codetool_shell/filters/opaque_payload/__init__.py +7 -0
  39. codetool_shell/filters/opaque_payload/detector.py +563 -0
  40. codetool_shell/filters/opaque_payload/reducer.py +142 -0
  41. codetool_shell/filters/opaque_payload/summary.py +61 -0
  42. codetool_shell/filters/package_manager/__init__.py +7 -0
  43. codetool_shell/filters/package_manager/detector.py +220 -0
  44. codetool_shell/filters/package_manager/reducer.py +110 -0
  45. codetool_shell/filters/package_manager/summary.py +172 -0
  46. codetool_shell/filters/pipeline.py +65 -0
  47. codetool_shell/filters/rg.py +250 -0
  48. codetool_shell/filters/system_output/__init__.py +7 -0
  49. codetool_shell/filters/system_output/detector.py +600 -0
  50. codetool_shell/filters/system_output/reducer.py +331 -0
  51. codetool_shell/filters/system_output/summary.py +164 -0
  52. codetool_shell/filters/table/__init__.py +7 -0
  53. codetool_shell/filters/table/detector.py +244 -0
  54. codetool_shell/filters/table/reducer.py +57 -0
  55. codetool_shell/filters/table/summary.py +37 -0
  56. codetool_shell/filters/test_runner/__init__.py +7 -0
  57. codetool_shell/filters/test_runner/ansi.py +80 -0
  58. codetool_shell/filters/test_runner/detector.py +409 -0
  59. codetool_shell/filters/test_runner/reducer.py +288 -0
  60. codetool_shell/filters/test_runner/summary.py +449 -0
  61. codetool_shell/filters/text.py +38 -0
  62. codetool_shell/filters/traceback/__init__.py +7 -0
  63. codetool_shell/filters/traceback/detector.py +209 -0
  64. codetool_shell/filters/traceback/reducer.py +141 -0
  65. codetool_shell/filters/traceback/summary.py +122 -0
  66. codetool_shell/filters/tree.py +59 -0
  67. codetool_shell/py.typed +0 -0
  68. codetool_shell/python_backend.py +38 -0
  69. codetool_shell/rust_backend.py +254 -0
  70. codetool_shell-0.1.1.dist-info/METADATA +152 -0
  71. codetool_shell-0.1.1.dist-info/RECORD +72 -0
  72. 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)
@@ -0,0 +1,7 @@
1
+ """System command shellion."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .reducer import compress_system_output
6
+
7
+ __all__ = ["compress_system_output"]