headson 0.6.4__tar.gz → 0.6.5__tar.gz
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.
- {headson-0.6.4 → headson-0.6.5}/Cargo.lock +1 -1
- {headson-0.6.4 → headson-0.6.5}/Cargo.toml +1 -1
- {headson-0.6.4 → headson-0.6.5}/PKG-INFO +8 -8
- {headson-0.6.4 → headson-0.6.5}/README.md +7 -7
- headson-0.6.5/docs/assets/tapes/demo.gif +0 -0
- {headson-0.6.4 → headson-0.6.5}/pyproject.toml +1 -1
- {headson-0.6.4 → headson-0.6.5}/python/Cargo.lock +2 -2
- {headson-0.6.4 → headson-0.6.5}/python/Cargo.toml +1 -1
- {headson-0.6.4 → headson-0.6.5}/python/README.md +9 -9
- {headson-0.6.4 → headson-0.6.5}/python/src/lib.rs +5 -3
- {headson-0.6.4 → headson-0.6.5}/src/ingest/formats/text/mod.rs +1 -0
- {headson-0.6.4 → headson-0.6.5}/src/lib.rs +162 -12
- {headson-0.6.4 → headson-0.6.5}/src/main.rs +151 -67
- {headson-0.6.4 → headson-0.6.5}/src/order/types.rs +5 -0
- {headson-0.6.4 → headson-0.6.5}/src/serialization/mod.rs +55 -6
- {headson-0.6.4 → headson-0.6.5}/src/serialization/types.rs +3 -0
- headson-0.6.5/src/utils/measure.rs +42 -0
- {headson-0.6.4 → headson-0.6.5}/src/utils/mod.rs +1 -0
- headson-0.6.4/docs/assets/tapes/demo.gif +0 -0
- {headson-0.6.4 → headson-0.6.5}/docs/assets/algorithm.svg +0 -0
- {headson-0.6.4 → headson-0.6.5}/docs/assets/logo.png +0 -0
- {headson-0.6.4 → headson-0.6.5}/docs/assets/logo.svg +0 -0
- {headson-0.6.4 → headson-0.6.5}/python/headson/__init__.py +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/format.rs +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/ingest/formats/json/builder.rs +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/ingest/formats/json/mod.rs +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/ingest/formats/json/samplers/default.rs +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/ingest/formats/json/samplers/head.rs +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/ingest/formats/json/samplers/mod.rs +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/ingest/formats/json/samplers/tail.rs +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/ingest/formats/mod.rs +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/ingest/formats/yaml/mod.rs +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/ingest/mod.rs +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/ingest/sampling/mod.rs +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/order/build.rs +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/order/mod.rs +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/order/scoring.rs +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/order/snapshots/headson__order__build__tests__order_empty_array_order.snap +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/order/snapshots/headson__order__build__tests__order_single_string_array_order.snap +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/serialization/color.rs +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/serialization/fileset.rs +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/serialization/output.rs +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/serialization/snapshots/headson__serialization__tests__arena_render_empty.snap +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/serialization/snapshots/headson__serialization__tests__arena_render_empty_yaml.snap +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/serialization/snapshots/headson__serialization__tests__arena_render_single.snap +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/serialization/snapshots/headson__serialization__tests__arena_render_single_yaml.snap +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/serialization/snapshots/headson__serialization__tests__array_internal_gaps_yaml.snap +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/serialization/snapshots/headson__serialization__tests__array_omitted_js_head.snap +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/serialization/snapshots/headson__serialization__tests__array_omitted_js_tail.snap +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/serialization/snapshots/headson__serialization__tests__array_omitted_pseudo_head.snap +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/serialization/snapshots/headson__serialization__tests__array_omitted_pseudo_tail.snap +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/serialization/snapshots/headson__serialization__tests__array_omitted_yaml_head.snap +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/serialization/snapshots/headson__serialization__tests__array_omitted_yaml_tail.snap +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/serialization/snapshots/headson__serialization__tests__inline_open_array_in_object_json.snap +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/serialization/snapshots/headson__serialization__tests__inline_open_array_in_object_yaml.snap +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/serialization/templates/core.rs +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/serialization/templates/js.rs +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/serialization/templates/json.rs +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/serialization/templates/mod.rs +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/serialization/templates/pseudo.rs +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/serialization/templates/text.rs +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/serialization/templates/yaml.rs +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/snapshots/headson__order__tests__order_empty_array_order.snap +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/snapshots/headson__order__tests__order_single_string_array_order.snap +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/utils/graph.rs +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/utils/json.rs +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/utils/search.rs +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/utils/text.rs +0 -0
- {headson-0.6.4 → headson-0.6.5}/src/utils/tree_arena.rs +0 -0
- {headson-0.6.4 → headson-0.6.5}/tests/fixtures/json/JSONTestSuite/LICENSE +0 -0
- {headson-0.6.4 → headson-0.6.5}/tests/fixtures/json/JSONTestSuite/README.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: headson
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.5
|
|
4
4
|
Classifier: Programming Language :: Python
|
|
5
5
|
Classifier: Programming Language :: Python :: 3
|
|
6
6
|
Classifier: Programming Language :: Rust
|
|
@@ -20,7 +20,7 @@ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
|
20
20
|
<br/>
|
|
21
21
|
</p>
|
|
22
22
|
|
|
23
|
-
`heal`/`tail` for JSON, YAML - but structure‑aware. Get a compact preview that shows both the shape and representative values of your data, all within a strict
|
|
23
|
+
`heal`/`tail` for JSON, YAML - but structure‑aware. Get a compact preview that shows both the shape and representative values of your data, all within a strict byte budget. (Just like `head`/`tail`, `headson` can also work with unstructured text files.)
|
|
24
24
|
|
|
25
25
|
Available as:
|
|
26
26
|
- CLI (see [Usage](#usage))
|
|
@@ -190,11 +190,11 @@ A thin Python extension module is available on PyPI as `headson`.
|
|
|
190
190
|
|
|
191
191
|
- Install: `pip install headson` (ABI3 wheels for Python 3.10+ on Linux/macOS/Windows).
|
|
192
192
|
- API:
|
|
193
|
-
- `headson.summarize(text: str, *, format: str = "auto", style: str = "default", input_format: str = "json",
|
|
193
|
+
- `headson.summarize(text: str, *, format: str = "auto", style: str = "default", input_format: str = "json", byte_budget: int | None = None, skew: str = "balanced") -> str`
|
|
194
194
|
- `format`: `"auto" | "json" | "yaml"` (auto maps to JSON family for single inputs)
|
|
195
195
|
- `style`: `"strict" | "default" | "detailed"`
|
|
196
196
|
- `input_format`: `"json" | "yaml"` (ingestion)
|
|
197
|
-
- `
|
|
197
|
+
- `byte_budget`: maximum output size in bytes (default: 500)
|
|
198
198
|
- `skew`: `"balanced" | "head" | "tail"` (affects display styles; strict JSON remains unannotated)
|
|
199
199
|
|
|
200
200
|
Examples:
|
|
@@ -204,7 +204,7 @@ import json
|
|
|
204
204
|
import headson
|
|
205
205
|
|
|
206
206
|
data = {"foo": [1, 2, 3], "bar": {"x": "y"}}
|
|
207
|
-
preview = headson.summarize(json.dumps(data), format="json", style="strict",
|
|
207
|
+
preview = headson.summarize(json.dumps(data), format="json", style="strict", byte_budget=200)
|
|
208
208
|
print(preview)
|
|
209
209
|
|
|
210
210
|
# Prefer the tail of arrays (annotations show with style="default"/"detailed")
|
|
@@ -213,14 +213,14 @@ print(
|
|
|
213
213
|
json.dumps(list(range(100))),
|
|
214
214
|
format="json",
|
|
215
215
|
style="detailed",
|
|
216
|
-
|
|
216
|
+
byte_budget=80,
|
|
217
217
|
skew="tail",
|
|
218
218
|
)
|
|
219
219
|
)
|
|
220
220
|
|
|
221
221
|
# YAML support
|
|
222
222
|
doc = "root:\n items: [1,2,3,4,5,6,7,8,9,10]\n"
|
|
223
|
-
print(headson.summarize(doc, format="yaml", style="default", input_format="yaml",
|
|
223
|
+
print(headson.summarize(doc, format="yaml", style="default", input_format="yaml", byte_budget=60))
|
|
224
224
|
```
|
|
225
225
|
|
|
226
226
|
# Algorithm
|
|
@@ -230,7 +230,7 @@ print(headson.summarize(doc, format="yaml", style="default", input_format="yaml"
|
|
|
230
230
|
## Footnotes
|
|
231
231
|
- <sup><b>[1]</b></sup> <b>Optimized tree representation</b>: An arena‑style tree stored in flat, contiguous buffers. Each node records its kind and value plus index ranges into shared child and key arrays. Arrays are ingested in a single pass and may be deterministically pre‑sampled: the first element is always kept; additional elements are selected via a fixed per‑index inclusion test; for kept elements, original indices are stored and full lengths are counted. This enables accurate omission info and internal gap markers later, while minimizing pointer chasing.
|
|
232
232
|
- <sup><b>[2]</b></sup> <b>Priority order</b>: Nodes are scored so previews surface representative structure and values first. Arrays can favor head/mid/tail coverage (default) or strictly the head; tail preference flips head/tail when configured. Object properties are ordered by key, and strings expand by grapheme with early characters prioritized over very deep expansions.
|
|
233
|
-
- <sup><b>[3]</b></sup> <b>Choose top N nodes (binary search)</b>: Iteratively picks N so that the rendered preview fits within the
|
|
233
|
+
- <sup><b>[3]</b></sup> <b>Choose top N nodes (binary search)</b>: Iteratively picks N so that the rendered preview fits within the byte budget, looping between “choose N” and a render attempt to converge quickly.
|
|
234
234
|
- <sup><b>[4]</b></sup> <b>Render attempt</b>: Serializes the currently included nodes using the selected template. Omission summaries and per-file section headers appear in display templates (pseudo/js); json remains strict. For arrays, display templates may insert internal gap markers between non‑contiguous kept items using original indices.
|
|
235
235
|
- <sup><b>[5]</b></sup> <b>Diagram source</b>: The Algorithm diagram is generated from `docs/diagrams/algorithm.mmd`. Regenerate the SVG with `cargo make diagrams` before releasing.
|
|
236
236
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<br/>
|
|
7
7
|
</p>
|
|
8
8
|
|
|
9
|
-
`heal`/`tail` for JSON, YAML - but structure‑aware. Get a compact preview that shows both the shape and representative values of your data, all within a strict
|
|
9
|
+
`heal`/`tail` for JSON, YAML - but structure‑aware. Get a compact preview that shows both the shape and representative values of your data, all within a strict byte budget. (Just like `head`/`tail`, `headson` can also work with unstructured text files.)
|
|
10
10
|
|
|
11
11
|
Available as:
|
|
12
12
|
- CLI (see [Usage](#usage))
|
|
@@ -176,11 +176,11 @@ A thin Python extension module is available on PyPI as `headson`.
|
|
|
176
176
|
|
|
177
177
|
- Install: `pip install headson` (ABI3 wheels for Python 3.10+ on Linux/macOS/Windows).
|
|
178
178
|
- API:
|
|
179
|
-
- `headson.summarize(text: str, *, format: str = "auto", style: str = "default", input_format: str = "json",
|
|
179
|
+
- `headson.summarize(text: str, *, format: str = "auto", style: str = "default", input_format: str = "json", byte_budget: int | None = None, skew: str = "balanced") -> str`
|
|
180
180
|
- `format`: `"auto" | "json" | "yaml"` (auto maps to JSON family for single inputs)
|
|
181
181
|
- `style`: `"strict" | "default" | "detailed"`
|
|
182
182
|
- `input_format`: `"json" | "yaml"` (ingestion)
|
|
183
|
-
- `
|
|
183
|
+
- `byte_budget`: maximum output size in bytes (default: 500)
|
|
184
184
|
- `skew`: `"balanced" | "head" | "tail"` (affects display styles; strict JSON remains unannotated)
|
|
185
185
|
|
|
186
186
|
Examples:
|
|
@@ -190,7 +190,7 @@ import json
|
|
|
190
190
|
import headson
|
|
191
191
|
|
|
192
192
|
data = {"foo": [1, 2, 3], "bar": {"x": "y"}}
|
|
193
|
-
preview = headson.summarize(json.dumps(data), format="json", style="strict",
|
|
193
|
+
preview = headson.summarize(json.dumps(data), format="json", style="strict", byte_budget=200)
|
|
194
194
|
print(preview)
|
|
195
195
|
|
|
196
196
|
# Prefer the tail of arrays (annotations show with style="default"/"detailed")
|
|
@@ -199,14 +199,14 @@ print(
|
|
|
199
199
|
json.dumps(list(range(100))),
|
|
200
200
|
format="json",
|
|
201
201
|
style="detailed",
|
|
202
|
-
|
|
202
|
+
byte_budget=80,
|
|
203
203
|
skew="tail",
|
|
204
204
|
)
|
|
205
205
|
)
|
|
206
206
|
|
|
207
207
|
# YAML support
|
|
208
208
|
doc = "root:\n items: [1,2,3,4,5,6,7,8,9,10]\n"
|
|
209
|
-
print(headson.summarize(doc, format="yaml", style="default", input_format="yaml",
|
|
209
|
+
print(headson.summarize(doc, format="yaml", style="default", input_format="yaml", byte_budget=60))
|
|
210
210
|
```
|
|
211
211
|
|
|
212
212
|
# Algorithm
|
|
@@ -216,7 +216,7 @@ print(headson.summarize(doc, format="yaml", style="default", input_format="yaml"
|
|
|
216
216
|
## Footnotes
|
|
217
217
|
- <sup><b>[1]</b></sup> <b>Optimized tree representation</b>: An arena‑style tree stored in flat, contiguous buffers. Each node records its kind and value plus index ranges into shared child and key arrays. Arrays are ingested in a single pass and may be deterministically pre‑sampled: the first element is always kept; additional elements are selected via a fixed per‑index inclusion test; for kept elements, original indices are stored and full lengths are counted. This enables accurate omission info and internal gap markers later, while minimizing pointer chasing.
|
|
218
218
|
- <sup><b>[2]</b></sup> <b>Priority order</b>: Nodes are scored so previews surface representative structure and values first. Arrays can favor head/mid/tail coverage (default) or strictly the head; tail preference flips head/tail when configured. Object properties are ordered by key, and strings expand by grapheme with early characters prioritized over very deep expansions.
|
|
219
|
-
- <sup><b>[3]</b></sup> <b>Choose top N nodes (binary search)</b>: Iteratively picks N so that the rendered preview fits within the
|
|
219
|
+
- <sup><b>[3]</b></sup> <b>Choose top N nodes (binary search)</b>: Iteratively picks N so that the rendered preview fits within the byte budget, looping between “choose N” and a render attempt to converge quickly.
|
|
220
220
|
- <sup><b>[4]</b></sup> <b>Render attempt</b>: Serializes the currently included nodes using the selected template. Omission summaries and per-file section headers appear in display templates (pseudo/js); json remains strict. For arrays, display templates may insert internal gap markers between non‑contiguous kept items using original indices.
|
|
221
221
|
- <sup><b>[5]</b></sup> <b>Diagram source</b>: The Algorithm diagram is generated from `docs/diagrams/algorithm.mmd`. Regenerate the SVG with `cargo make diagrams` before releasing.
|
|
222
222
|
|
|
Binary file
|
|
@@ -214,7 +214,7 @@ dependencies = [
|
|
|
214
214
|
|
|
215
215
|
[[package]]
|
|
216
216
|
name = "headson"
|
|
217
|
-
version = "0.6.
|
|
217
|
+
version = "0.6.5"
|
|
218
218
|
dependencies = [
|
|
219
219
|
"anyhow",
|
|
220
220
|
"clap",
|
|
@@ -228,7 +228,7 @@ dependencies = [
|
|
|
228
228
|
|
|
229
229
|
[[package]]
|
|
230
230
|
name = "headson-python"
|
|
231
|
-
version = "0.6.
|
|
231
|
+
version = "0.6.5"
|
|
232
232
|
dependencies = [
|
|
233
233
|
"anyhow",
|
|
234
234
|
"headson",
|
|
@@ -4,11 +4,11 @@ Minimal Python API for the `headson` preview renderer.
|
|
|
4
4
|
|
|
5
5
|
API
|
|
6
6
|
|
|
7
|
-
- `headson.summarize(text: str, *, format: str = "auto", style: str = "default", input_format: str = "json",
|
|
7
|
+
- `headson.summarize(text: str, *, format: str = "auto", style: str = "default", input_format: str = "json", byte_budget: int | None = None, skew: str = "balanced") -> str`
|
|
8
8
|
- `format`: output format — `"auto" | "json" | "yaml" | "text"`.
|
|
9
9
|
- `style`: output style — `"strict" | "default" | "detailed"`.
|
|
10
10
|
- `input_format`: ingestion format — `"json" | "yaml" | "text"`.
|
|
11
|
-
- `
|
|
11
|
+
- `byte_budget`: maximum output size in bytes (defaults to 500 if not set).
|
|
12
12
|
- `skew`: one of `"balanced" | "head" | "tail"`.
|
|
13
13
|
- `balanced` (default), `head` keeps first N, `tail` keeps last N. Display styles place omission markers accordingly; strict JSON remains unannotated.
|
|
14
14
|
- Notes:
|
|
@@ -20,26 +20,26 @@ Examples:
|
|
|
20
20
|
import headson
|
|
21
21
|
|
|
22
22
|
# Human-friendly JSON (Pseudo) with a small budget
|
|
23
|
-
print(headson.summarize('{"a": 1, "b": [1,2,3]}', format="json", style="default",
|
|
23
|
+
print(headson.summarize('{"a": 1, "b": [1,2,3]}', format="json", style="default", byte_budget=80))
|
|
24
24
|
|
|
25
25
|
# Strict JSON stays valid JSON
|
|
26
|
-
print(headson.summarize('{"a": 1, "b": {"c": 2}}', format="json", style="strict",
|
|
26
|
+
print(headson.summarize('{"a": 1, "b": {"c": 2}}', format="json", style="strict", byte_budget=10_000))
|
|
27
27
|
|
|
28
28
|
# Annotated JSON (JS) with tail skew: prefer the end of arrays when truncating
|
|
29
29
|
arr = ','.join(str(i) for i in range(100))
|
|
30
|
-
print(headson.summarize('{"arr": [' + arr + ']}', format="json", style="detailed",
|
|
30
|
+
print(headson.summarize('{"arr": [' + arr + ']}', format="json", style="detailed", byte_budget=60, skew="tail"))
|
|
31
31
|
|
|
32
32
|
# YAML styles: strict (no comments), default (… comments), detailed (counts)
|
|
33
33
|
doc = 'root:\n items: [1,2,3,4,5,6,7,8,9,10]\n'
|
|
34
|
-
print(headson.summarize(doc, format="yaml", style="strict", input_format="yaml",
|
|
35
|
-
print(headson.summarize(doc, format="yaml", style="default", input_format="yaml",
|
|
36
|
-
print(headson.summarize(doc, format="yaml", style="detailed", input_format="yaml",
|
|
34
|
+
print(headson.summarize(doc, format="yaml", style="strict", input_format="yaml", byte_budget=60))
|
|
35
|
+
print(headson.summarize(doc, format="yaml", style="default", input_format="yaml", byte_budget=60))
|
|
36
|
+
print(headson.summarize(doc, format="yaml", style="detailed", input_format="yaml", byte_budget=60))
|
|
37
37
|
|
|
38
38
|
# Note: tail mode affects only display styles; strict JSON stays strict.
|
|
39
39
|
|
|
40
40
|
# Text: render raw lines with omission markers depending on style
|
|
41
41
|
text = "one\ntwo\nthree\n"
|
|
42
|
-
print(headson.summarize(text, format="text", style="default", input_format="text",
|
|
42
|
+
print(headson.summarize(text, format="text", style="default", input_format="text", byte_budget=10))
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
Install for development:
|
|
@@ -59,6 +59,7 @@ fn render_config_with_sampler(
|
|
|
59
59
|
color_mode: ColorMode::Auto,
|
|
60
60
|
color_enabled: false,
|
|
61
61
|
style: s,
|
|
62
|
+
string_free_prefix_graphemes: None,
|
|
62
63
|
})
|
|
63
64
|
}
|
|
64
65
|
|
|
@@ -85,6 +86,7 @@ fn priority_config(
|
|
|
85
86
|
prefer_tail_arrays,
|
|
86
87
|
array_bias: headson_core::ArrayBias::HeadMidTail,
|
|
87
88
|
array_sampler: sampler,
|
|
89
|
+
line_budget_only: false,
|
|
88
90
|
}
|
|
89
91
|
}
|
|
90
92
|
|
|
@@ -93,19 +95,19 @@ fn to_pyerr(e: anyhow::Error) -> PyErr {
|
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
#[pyfunction]
|
|
96
|
-
#[pyo3(signature = (text, *, format="auto", style="default",
|
|
98
|
+
#[pyo3(signature = (text, *, format="auto", style="default", byte_budget=None, skew="balanced", input_format="json"))]
|
|
97
99
|
fn summarize(
|
|
98
100
|
py: Python<'_>,
|
|
99
101
|
text: &str,
|
|
100
102
|
format: &str,
|
|
101
103
|
style: &str,
|
|
102
|
-
|
|
104
|
+
byte_budget: Option<usize>,
|
|
103
105
|
skew: &str,
|
|
104
106
|
input_format: &str,
|
|
105
107
|
) -> PyResult<String> {
|
|
106
108
|
let sampler = parse_skew(skew).map_err(to_pyerr)?;
|
|
107
109
|
let cfg = render_config_with_sampler(format, style, sampler).map_err(to_pyerr)?;
|
|
108
|
-
let budget =
|
|
110
|
+
let budget = byte_budget.unwrap_or(500);
|
|
109
111
|
let per_file_for_priority = budget.max(1);
|
|
110
112
|
let prio = priority_config(per_file_for_priority, sampler);
|
|
111
113
|
let input = text.as_bytes().to_vec();
|
|
@@ -35,6 +35,12 @@ pub use serialization::types::{
|
|
|
35
35
|
ColorMode, OutputTemplate, RenderConfig, Style,
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
+
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
|
|
39
|
+
pub struct Budgets {
|
|
40
|
+
pub byte_budget: Option<usize>,
|
|
41
|
+
pub line_budget: Option<usize>,
|
|
42
|
+
}
|
|
43
|
+
|
|
38
44
|
pub fn headson(
|
|
39
45
|
input: Vec<u8>,
|
|
40
46
|
config: &RenderConfig,
|
|
@@ -43,7 +49,14 @@ pub fn headson(
|
|
|
43
49
|
) -> Result<String> {
|
|
44
50
|
let arena = crate::ingest::parse_json_one(input, priority_cfg)?;
|
|
45
51
|
let order_build = order::build_order(&arena, priority_cfg)?;
|
|
46
|
-
let out =
|
|
52
|
+
let out = find_largest_render_under_budgets(
|
|
53
|
+
&order_build,
|
|
54
|
+
config,
|
|
55
|
+
Budgets {
|
|
56
|
+
byte_budget: Some(budget),
|
|
57
|
+
line_budget: None,
|
|
58
|
+
},
|
|
59
|
+
);
|
|
47
60
|
Ok(out)
|
|
48
61
|
}
|
|
49
62
|
|
|
@@ -55,7 +68,14 @@ pub fn headson_many(
|
|
|
55
68
|
) -> Result<String> {
|
|
56
69
|
let arena = crate::ingest::parse_json_many(inputs, priority_cfg)?;
|
|
57
70
|
let order_build = order::build_order(&arena, priority_cfg)?;
|
|
58
|
-
let out =
|
|
71
|
+
let out = find_largest_render_under_budgets(
|
|
72
|
+
&order_build,
|
|
73
|
+
config,
|
|
74
|
+
Budgets {
|
|
75
|
+
byte_budget: Some(budget),
|
|
76
|
+
line_budget: None,
|
|
77
|
+
},
|
|
78
|
+
);
|
|
59
79
|
Ok(out)
|
|
60
80
|
}
|
|
61
81
|
|
|
@@ -68,7 +88,14 @@ pub fn headson_yaml(
|
|
|
68
88
|
) -> Result<String> {
|
|
69
89
|
let arena = crate::ingest::parse_yaml_one(input, priority_cfg)?;
|
|
70
90
|
let order_build = order::build_order(&arena, priority_cfg)?;
|
|
71
|
-
let out =
|
|
91
|
+
let out = find_largest_render_under_budgets(
|
|
92
|
+
&order_build,
|
|
93
|
+
config,
|
|
94
|
+
Budgets {
|
|
95
|
+
byte_budget: Some(budget),
|
|
96
|
+
line_budget: None,
|
|
97
|
+
},
|
|
98
|
+
);
|
|
72
99
|
Ok(out)
|
|
73
100
|
}
|
|
74
101
|
|
|
@@ -81,7 +108,14 @@ pub fn headson_many_yaml(
|
|
|
81
108
|
) -> Result<String> {
|
|
82
109
|
let arena = crate::ingest::parse_yaml_many(inputs, priority_cfg)?;
|
|
83
110
|
let order_build = order::build_order(&arena, priority_cfg)?;
|
|
84
|
-
let out =
|
|
111
|
+
let out = find_largest_render_under_budgets(
|
|
112
|
+
&order_build,
|
|
113
|
+
config,
|
|
114
|
+
Budgets {
|
|
115
|
+
byte_budget: Some(budget),
|
|
116
|
+
line_budget: None,
|
|
117
|
+
},
|
|
118
|
+
);
|
|
85
119
|
Ok(out)
|
|
86
120
|
}
|
|
87
121
|
|
|
@@ -94,7 +128,14 @@ pub fn headson_text(
|
|
|
94
128
|
) -> Result<String> {
|
|
95
129
|
let arena = crate::ingest::parse_text_one(input, priority_cfg)?;
|
|
96
130
|
let order_build = order::build_order(&arena, priority_cfg)?;
|
|
97
|
-
let out =
|
|
131
|
+
let out = find_largest_render_under_budgets(
|
|
132
|
+
&order_build,
|
|
133
|
+
config,
|
|
134
|
+
Budgets {
|
|
135
|
+
byte_budget: Some(budget),
|
|
136
|
+
line_budget: None,
|
|
137
|
+
},
|
|
138
|
+
);
|
|
98
139
|
Ok(out)
|
|
99
140
|
}
|
|
100
141
|
|
|
@@ -107,31 +148,44 @@ pub fn headson_many_text(
|
|
|
107
148
|
) -> Result<String> {
|
|
108
149
|
let arena = crate::ingest::parse_text_many(inputs, priority_cfg)?;
|
|
109
150
|
let order_build = order::build_order(&arena, priority_cfg)?;
|
|
110
|
-
let out =
|
|
151
|
+
let out = find_largest_render_under_budgets(
|
|
152
|
+
&order_build,
|
|
153
|
+
config,
|
|
154
|
+
Budgets {
|
|
155
|
+
byte_budget: Some(budget),
|
|
156
|
+
line_budget: None,
|
|
157
|
+
},
|
|
158
|
+
);
|
|
111
159
|
Ok(out)
|
|
112
160
|
}
|
|
113
161
|
|
|
114
|
-
|
|
162
|
+
/// New generalized budgeting: enforce optional char and/or line caps.
|
|
163
|
+
fn find_largest_render_under_budgets(
|
|
115
164
|
order_build: &PriorityOrder,
|
|
116
165
|
config: &RenderConfig,
|
|
117
|
-
|
|
166
|
+
budgets: Budgets,
|
|
118
167
|
) -> String {
|
|
119
168
|
// Binary search the largest k in [1, total] whose render
|
|
120
|
-
// fits within
|
|
169
|
+
// fits within all requested budgets.
|
|
121
170
|
let total = order_build.total_nodes;
|
|
122
171
|
if total == 0 {
|
|
123
172
|
return String::new();
|
|
124
173
|
}
|
|
125
174
|
// Each included node contributes at least some output; cap hi by budget.
|
|
126
175
|
let lo = 1usize;
|
|
127
|
-
|
|
176
|
+
// For the upper bound, when a byte budget is present, we can safely cap by it;
|
|
177
|
+
// otherwise, cap by total.
|
|
178
|
+
let hi = match budgets.byte_budget {
|
|
179
|
+
Some(c) => total.min(c.max(1)),
|
|
180
|
+
None => total,
|
|
181
|
+
};
|
|
128
182
|
// Reuse render-inclusion flags across render attempts to avoid clearing the vector.
|
|
129
183
|
// A node participates in the current render attempt when inclusion_flags[id] == render_set_id.
|
|
130
184
|
let mut inclusion_flags: Vec<u32> = vec![0; total];
|
|
131
185
|
// Each render attempt bumps this non-zero identifier to create a fresh inclusion set.
|
|
132
186
|
let mut render_set_id: u32 = 1;
|
|
133
187
|
// Measure length without color so ANSI escapes do not count toward the
|
|
134
|
-
//
|
|
188
|
+
// byte budget. Then render once more with the requested color setting.
|
|
135
189
|
let mut best_k: Option<usize> = None;
|
|
136
190
|
let mut measure_cfg = config.clone();
|
|
137
191
|
measure_cfg.color_enabled = false;
|
|
@@ -145,7 +199,12 @@ fn find_largest_render_under_budget(
|
|
|
145
199
|
&measure_cfg,
|
|
146
200
|
);
|
|
147
201
|
render_set_id = render_set_id.wrapping_add(1).max(1);
|
|
148
|
-
|
|
202
|
+
// Measure output using a unified stats helper and enforce
|
|
203
|
+
// all provided caps (chars and/or lines).
|
|
204
|
+
let stats = crate::utils::measure::count_output_stats(&s);
|
|
205
|
+
let fits_chars = budgets.byte_budget.is_none_or(|c| stats.bytes <= c);
|
|
206
|
+
let fits_lines = budgets.line_budget.is_none_or(|l| stats.lines <= l);
|
|
207
|
+
if fits_chars && fits_lines {
|
|
149
208
|
best_k = Some(mid);
|
|
150
209
|
true
|
|
151
210
|
} else {
|
|
@@ -174,3 +233,94 @@ fn find_largest_render_under_budget(
|
|
|
174
233
|
)
|
|
175
234
|
}
|
|
176
235
|
}
|
|
236
|
+
|
|
237
|
+
// Optional new public API that accepts both budgets explicitly.
|
|
238
|
+
pub fn headson_with_budgets(
|
|
239
|
+
input: Vec<u8>,
|
|
240
|
+
config: &RenderConfig,
|
|
241
|
+
priority_cfg: &PriorityConfig,
|
|
242
|
+
budgets: Budgets,
|
|
243
|
+
) -> Result<String> {
|
|
244
|
+
let arena = crate::ingest::parse_json_one(input, priority_cfg)?;
|
|
245
|
+
let order_build = order::build_order(&arena, priority_cfg)?;
|
|
246
|
+
Ok(find_largest_render_under_budgets(
|
|
247
|
+
&order_build,
|
|
248
|
+
config,
|
|
249
|
+
budgets,
|
|
250
|
+
))
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
pub fn headson_many_with_budgets(
|
|
254
|
+
inputs: Vec<(String, Vec<u8>)>,
|
|
255
|
+
config: &RenderConfig,
|
|
256
|
+
priority_cfg: &PriorityConfig,
|
|
257
|
+
budgets: Budgets,
|
|
258
|
+
) -> Result<String> {
|
|
259
|
+
let arena = crate::ingest::parse_json_many(inputs, priority_cfg)?;
|
|
260
|
+
let order_build = order::build_order(&arena, priority_cfg)?;
|
|
261
|
+
Ok(find_largest_render_under_budgets(
|
|
262
|
+
&order_build,
|
|
263
|
+
config,
|
|
264
|
+
budgets,
|
|
265
|
+
))
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
pub fn headson_yaml_with_budgets(
|
|
269
|
+
input: Vec<u8>,
|
|
270
|
+
config: &RenderConfig,
|
|
271
|
+
priority_cfg: &PriorityConfig,
|
|
272
|
+
budgets: Budgets,
|
|
273
|
+
) -> Result<String> {
|
|
274
|
+
let arena = crate::ingest::parse_yaml_one(input, priority_cfg)?;
|
|
275
|
+
let order_build = order::build_order(&arena, priority_cfg)?;
|
|
276
|
+
Ok(find_largest_render_under_budgets(
|
|
277
|
+
&order_build,
|
|
278
|
+
config,
|
|
279
|
+
budgets,
|
|
280
|
+
))
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
pub fn headson_many_yaml_with_budgets(
|
|
284
|
+
inputs: Vec<(String, Vec<u8>)>,
|
|
285
|
+
config: &RenderConfig,
|
|
286
|
+
priority_cfg: &PriorityConfig,
|
|
287
|
+
budgets: Budgets,
|
|
288
|
+
) -> Result<String> {
|
|
289
|
+
let arena = crate::ingest::parse_yaml_many(inputs, priority_cfg)?;
|
|
290
|
+
let order_build = order::build_order(&arena, priority_cfg)?;
|
|
291
|
+
Ok(find_largest_render_under_budgets(
|
|
292
|
+
&order_build,
|
|
293
|
+
config,
|
|
294
|
+
budgets,
|
|
295
|
+
))
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
pub fn headson_text_with_budgets(
|
|
299
|
+
input: Vec<u8>,
|
|
300
|
+
config: &RenderConfig,
|
|
301
|
+
priority_cfg: &PriorityConfig,
|
|
302
|
+
budgets: Budgets,
|
|
303
|
+
) -> Result<String> {
|
|
304
|
+
let arena = crate::ingest::parse_text_one(input, priority_cfg)?;
|
|
305
|
+
let order_build = order::build_order(&arena, priority_cfg)?;
|
|
306
|
+
Ok(find_largest_render_under_budgets(
|
|
307
|
+
&order_build,
|
|
308
|
+
config,
|
|
309
|
+
budgets,
|
|
310
|
+
))
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
pub fn headson_many_text_with_budgets(
|
|
314
|
+
inputs: Vec<(String, Vec<u8>)>,
|
|
315
|
+
config: &RenderConfig,
|
|
316
|
+
priority_cfg: &PriorityConfig,
|
|
317
|
+
budgets: Budgets,
|
|
318
|
+
) -> Result<String> {
|
|
319
|
+
let arena = crate::ingest::parse_text_many(inputs, priority_cfg)?;
|
|
320
|
+
let order_build = order::build_order(&arena, priority_cfg)?;
|
|
321
|
+
Ok(find_largest_render_under_budgets(
|
|
322
|
+
&order_build,
|
|
323
|
+
config,
|
|
324
|
+
budgets,
|
|
325
|
+
))
|
|
326
|
+
}
|