codeterrain 0.3.0__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.
- codeterrain/__init__.py +3 -0
- codeterrain/_guard.py +243 -0
- codeterrain/_pytest_dump_plugin.py +253 -0
- codeterrain/_routes_runner.py +119 -0
- codeterrain/budget.py +128 -0
- codeterrain/cache.py +618 -0
- codeterrain/calls_join.py +416 -0
- codeterrain/calls_static.py +1099 -0
- codeterrain/cli.py +1447 -0
- codeterrain/code_static.py +2411 -0
- codeterrain/config.py +331 -0
- codeterrain/executables.py +130 -0
- codeterrain/fixtures_order.py +77 -0
- codeterrain/fixtures_static.py +501 -0
- codeterrain/hookspecs.py +25 -0
- codeterrain/impact.py +725 -0
- codeterrain/languages.py +293 -0
- codeterrain/plugins/__init__.py +0 -0
- codeterrain/plugins/fastapi_routes.py +193 -0
- codeterrain/plugins/manifest_join.py +107 -0
- codeterrain/protocol.py +138 -0
- codeterrain/routes_view.py +61 -0
- codeterrain/sandbox.py +565 -0
- codeterrain/views.py +198 -0
- codeterrain-0.3.0.dist-info/METADATA +295 -0
- codeterrain-0.3.0.dist-info/RECORD +30 -0
- codeterrain-0.3.0.dist-info/WHEEL +4 -0
- codeterrain-0.3.0.dist-info/entry_points.txt +2 -0
- codeterrain-0.3.0.dist-info/licenses/LICENSE +201 -0
- codeterrain-0.3.0.dist-info/licenses/NOTICE +4 -0
codeterrain/budget.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Output budget: text tables cut at about N tokens, estimated as one token per four characters."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from collections.abc import Callable, Sequence
|
|
7
|
+
|
|
8
|
+
DEFAULT_BUDGET = 2000
|
|
9
|
+
CHARS_PER_TOKEN = 4
|
|
10
|
+
MAX_COLUMN_WIDTH = 60
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
#: C0 controls other than tab and newline, DEL, C1 controls, the bidi marks, overrides and
|
|
14
|
+
#: isolates, the Unicode line and paragraph separators, and lone surrogates (an undecodable file
|
|
15
|
+
#: name).
|
|
16
|
+
_UNSAFE = re.compile(
|
|
17
|
+
"[\x00-\x08\x0b-\x1f\x7f-\x9f\u202a-\u202e\u2066-\u2069\u200e\u200f\u061c\u2028\u2029\ud800-\udfff]"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def terminal_safe(text: str) -> str:
|
|
22
|
+
"""Escape characters that a terminal would act on, so repository text cannot drive it.
|
|
23
|
+
|
|
24
|
+
Fixture names, paths and error messages come from the scanned repository; an escape
|
|
25
|
+
sequence in one could clear the screen or forge rows. Each unsafe character is shown as
|
|
26
|
+
``\\xNN`` or ``\\uNNNN``. Tab and newline stay, since the text output is line-based.
|
|
27
|
+
"""
|
|
28
|
+
return _UNSAFE.sub(
|
|
29
|
+
lambda m: f"\\x{ord(m[0]):02x}" if ord(m[0]) < 0x100 else f"\\u{ord(m[0]):04x}", text
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
#: Everything ``_UNSAFE`` covers, plus tab, newline and carriage return.
|
|
34
|
+
_UNSAFE_IN_FIELD = re.compile(
|
|
35
|
+
"[\x00-\x1f\x7f-\x9f\u202a-\u202e\u2066-\u2069\u200e\u200f\u061c\u2028\u2029\ud800-\udfff]"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def field_safe(text: str) -> str:
|
|
40
|
+
"""Escape one value taken from the repository before it is placed in a line of output.
|
|
41
|
+
|
|
42
|
+
``terminal_safe`` runs on the finished output and has to keep the renderer's own newlines and
|
|
43
|
+
tabs; a newline inside a fixture name or a route path would still start a forged row. A field
|
|
44
|
+
therefore has every control escaped, layout characters included.
|
|
45
|
+
"""
|
|
46
|
+
return _UNSAFE_IN_FIELD.sub(
|
|
47
|
+
lambda m: f"\\x{ord(m[0]):02x}" if ord(m[0]) < 0x100 else f"\\u{ord(m[0]):04x}", text
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def estimate_tokens(text: str) -> int:
|
|
52
|
+
return -(-len(text) // CHARS_PER_TOKEN)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def posix_path(path: str) -> str:
|
|
56
|
+
"""Output paths are repo-relative POSIX; Windows backslashes are normalised."""
|
|
57
|
+
return path.replace("\\", "/")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def cut_lines(
|
|
61
|
+
lines: Sequence[str],
|
|
62
|
+
budget: int,
|
|
63
|
+
hint: str,
|
|
64
|
+
keep: int = 1,
|
|
65
|
+
omitted: Callable[[int], str] | None = None,
|
|
66
|
+
more: int = 0,
|
|
67
|
+
) -> str:
|
|
68
|
+
"""Join ``lines`` within ``budget`` tokens; the first ``keep`` lines (headers) always stay.
|
|
69
|
+
|
|
70
|
+
When rows are dropped the last line reads ``+N more; narrow with <hint>``, or ``+N more:
|
|
71
|
+
<omitted(shown)>; narrow with <hint>`` to say what was left out (``shown`` rows were kept).
|
|
72
|
+
``more`` rows follow ``lines`` that were never formatted, and are always dropped. A budget of
|
|
73
|
+
0 or less means no limit.
|
|
74
|
+
"""
|
|
75
|
+
text = "\n".join(lines)
|
|
76
|
+
limit = budget * CHARS_PER_TOKEN
|
|
77
|
+
if budget <= 0 or (not more and len(text) + 1 <= limit):
|
|
78
|
+
return text
|
|
79
|
+
# The detail of every row is the longest the detail of the rows left out can be.
|
|
80
|
+
detail = f": {omitted(0)}" if omitted else ""
|
|
81
|
+
total_rows = len(lines) - keep + more
|
|
82
|
+
used = sum(len(line) + 1 for line in lines[:keep])
|
|
83
|
+
shown = 0
|
|
84
|
+
for line in lines[keep:]:
|
|
85
|
+
remaining = total_rows - shown - 1
|
|
86
|
+
footer = f"+{remaining} more{detail}; narrow with {hint}"
|
|
87
|
+
if used + len(line) + 1 + (len(footer) + 1 if remaining else 0) > limit:
|
|
88
|
+
break
|
|
89
|
+
used += len(line) + 1
|
|
90
|
+
shown += 1
|
|
91
|
+
dropped = total_rows - shown
|
|
92
|
+
kept = list(lines[: keep + shown])
|
|
93
|
+
if dropped:
|
|
94
|
+
detail = f": {omitted(shown)}" if omitted else ""
|
|
95
|
+
kept.append(f"+{dropped} more{detail}; narrow with {hint}")
|
|
96
|
+
return "\n".join(kept)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def render_table(
|
|
100
|
+
headers: Sequence[str],
|
|
101
|
+
rows: Sequence[Sequence[str]],
|
|
102
|
+
budget: int,
|
|
103
|
+
hint: str,
|
|
104
|
+
omitted: Callable[[int], str] | None = None,
|
|
105
|
+
) -> str:
|
|
106
|
+
"""Column-aligned text table (last column unpadded), cut to the budget."""
|
|
107
|
+
if budget > 0:
|
|
108
|
+
# Every row costs at least its newline (trailing empty cells are stripped), so no row
|
|
109
|
+
# past this many can fit: those are counted, never formatted. A table cut this way is
|
|
110
|
+
# aligned on the rows that could be shown.
|
|
111
|
+
fits = budget * CHARS_PER_TOKEN
|
|
112
|
+
if len(rows) > fits:
|
|
113
|
+
cut = render_table(headers, rows[:fits], 0, hint)
|
|
114
|
+
return cut_lines(
|
|
115
|
+
cut.split("\n"), budget, hint, omitted=omitted, more=len(rows) - fits
|
|
116
|
+
)
|
|
117
|
+
cells = [[field_safe(posix_path(str(c))) for c in row] for row in rows]
|
|
118
|
+
widths = [len(h) for h in headers]
|
|
119
|
+
for row in cells:
|
|
120
|
+
for i, cell in enumerate(row[:-1]):
|
|
121
|
+
widths[i] = max(widths[i], min(len(cell), MAX_COLUMN_WIDTH))
|
|
122
|
+
|
|
123
|
+
def fmt(row: Sequence[str]) -> str:
|
|
124
|
+
parts = [cell.ljust(widths[i]) for i, cell in enumerate(row[:-1])]
|
|
125
|
+
parts.append(row[-1])
|
|
126
|
+
return " ".join(parts).rstrip()
|
|
127
|
+
|
|
128
|
+
return cut_lines([fmt(headers), *(fmt(r) for r in cells)], budget, hint, omitted=omitted)
|