headson 0.2.5__tar.gz → 0.3.0__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.

Potentially problematic release.


This version of headson might be problematic. Click here for more details.

Files changed (48) hide show
  1. {headson-0.2.5 → headson-0.3.0}/Cargo.lock +1 -1
  2. {headson-0.2.5 → headson-0.3.0}/Cargo.toml +1 -1
  3. headson-0.3.0/PKG-INFO +168 -0
  4. headson-0.3.0/README.md +152 -0
  5. {headson-0.2.5 → headson-0.3.0}/pyproject.toml +2 -4
  6. {headson-0.2.5 → headson-0.3.0}/python/Cargo.lock +2 -2
  7. {headson-0.2.5 → headson-0.3.0}/python/Cargo.toml +1 -1
  8. {headson-0.2.5 → headson-0.3.0}/python/src/lib.rs +8 -7
  9. {headson-0.2.5 → headson-0.3.0}/src/main.rs +8 -6
  10. {headson-0.2.5 → headson-0.3.0}/src/order/build.rs +7 -1
  11. {headson-0.2.5 → headson-0.3.0}/src/order/types.rs +2 -0
  12. {headson-0.2.5 → headson-0.3.0}/src/serialization/mod.rs +81 -10
  13. {headson-0.2.5 → headson-0.3.0}/src/serialization/templates/core.rs +6 -1
  14. {headson-0.2.5 → headson-0.3.0}/src/serialization/templates/js.rs +7 -4
  15. {headson-0.2.5 → headson-0.3.0}/src/serialization/templates/mod.rs +1 -0
  16. {headson-0.2.5 → headson-0.3.0}/src/serialization/templates/pseudo.rs +3 -0
  17. {headson-0.2.5 → headson-0.3.0}/src/serialization/types.rs +2 -0
  18. {headson-0.2.5 → headson-0.3.0}/src/utils/tree_arena.rs +2 -3
  19. headson-0.2.5/PKG-INFO +0 -99
  20. headson-0.2.5/README.md +0 -83
  21. {headson-0.2.5 → headson-0.3.0}/JSONTestSuite/LICENSE +0 -0
  22. {headson-0.2.5 → headson-0.3.0}/JSONTestSuite/README.md +0 -0
  23. {headson-0.2.5 → headson-0.3.0}/LICENSE +0 -0
  24. {headson-0.2.5 → headson-0.3.0}/python/README.md +0 -0
  25. {headson-0.2.5 → headson-0.3.0}/python/headson/__init__.py +0 -0
  26. {headson-0.2.5 → headson-0.3.0}/src/json_ingest/builder.rs +0 -0
  27. {headson-0.2.5 → headson-0.3.0}/src/json_ingest/mod.rs +0 -0
  28. {headson-0.2.5 → headson-0.3.0}/src/lib.rs +0 -0
  29. {headson-0.2.5 → headson-0.3.0}/src/order/mod.rs +0 -0
  30. {headson-0.2.5 → headson-0.3.0}/src/order/scoring.rs +0 -0
  31. {headson-0.2.5 → headson-0.3.0}/src/order/snapshots/headson__order__build__tests__order_empty_array_order.snap +0 -0
  32. {headson-0.2.5 → headson-0.3.0}/src/order/snapshots/headson__order__build__tests__order_single_string_array_order.snap +0 -0
  33. {headson-0.2.5 → headson-0.3.0}/src/serialization/snapshots/headson__serialization__tests__arena_render_empty.snap +0 -0
  34. {headson-0.2.5 → headson-0.3.0}/src/serialization/snapshots/headson__serialization__tests__arena_render_single.snap +0 -0
  35. {headson-0.2.5 → headson-0.3.0}/src/serialization/templates/json.rs +0 -0
  36. {headson-0.2.5 → headson-0.3.0}/src/snapshots/headson__order__tests__order_empty_array_order.snap +0 -0
  37. {headson-0.2.5 → headson-0.3.0}/src/snapshots/headson__order__tests__order_single_string_array_order.snap +0 -0
  38. {headson-0.2.5 → headson-0.3.0}/src/snapshots/headson__order__tests__pq_empty_array_queue.snap +0 -0
  39. {headson-0.2.5 → headson-0.3.0}/src/snapshots/headson__order__tests__pq_single_string_array_queue.snap +0 -0
  40. {headson-0.2.5 → headson-0.3.0}/src/snapshots/headson__queue__tests__pq_empty_array_queue.snap +0 -0
  41. {headson-0.2.5 → headson-0.3.0}/src/snapshots/headson__queue__tests__pq_single_string_array_queue.snap +0 -0
  42. {headson-0.2.5 → headson-0.3.0}/src/snapshots/headson__tree__tests__build_tree_empty.snap +0 -0
  43. {headson-0.2.5 → headson-0.3.0}/src/snapshots/headson__tree__tests__build_tree_single.snap +0 -0
  44. {headson-0.2.5 → headson-0.3.0}/src/utils/graph.rs +0 -0
  45. {headson-0.2.5 → headson-0.3.0}/src/utils/json.rs +0 -0
  46. {headson-0.2.5 → headson-0.3.0}/src/utils/mod.rs +0 -0
  47. {headson-0.2.5 → headson-0.3.0}/src/utils/search.rs +0 -0
  48. {headson-0.2.5 → headson-0.3.0}/src/utils/text.rs +0 -0
@@ -266,7 +266,7 @@ dependencies = [
266
266
 
267
267
  [[package]]
268
268
  name = "headson"
269
- version = "0.2.5"
269
+ version = "0.3.0"
270
270
  dependencies = [
271
271
  "anyhow",
272
272
  "assert_cmd",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "headson"
3
- version = "0.2.5"
3
+ version = "0.3.0"
4
4
  edition = "2024"
5
5
  description = "Budget‑constrained JSON preview renderer"
6
6
  readme = "README.md"
headson-0.3.0/PKG-INFO ADDED
@@ -0,0 +1,168 @@
1
+ Metadata-Version: 2.4
2
+ Name: headson
3
+ Version: 0.3.0
4
+ Classifier: Programming Language :: Python
5
+ Classifier: Programming Language :: Python :: 3
6
+ Classifier: Programming Language :: Rust
7
+ Classifier: Operating System :: OS Independent
8
+ Requires-Dist: pytest>=8 ; extra == 'test'
9
+ Provides-Extra: test
10
+ License-File: LICENSE
11
+ Summary: Budget‑constrained JSON preview renderer (Python bindings)
12
+ Keywords: json,preview,summarize,cli,bindings
13
+ Requires-Python: >=3.8
14
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
15
+
16
+ # headson
17
+
18
+ Head/tail for JSON — but structure‑aware. Get a compact preview that shows both the shape and representative values of your data, all within a strict character budget.
19
+
20
+ Available as:
21
+ - CLI (see [Usage](#usage))
22
+ - Python library (see [Python Bindings](#python-bindings))
23
+
24
+ ## Install
25
+
26
+ Using Cargo:
27
+
28
+ cargo install headson
29
+
30
+ From source:
31
+
32
+ cargo build --release
33
+ target/release/headson --help
34
+
35
+
36
+ ## Features
37
+
38
+ - *Budgeted output*: specify exactly how much JSON you want to see
39
+ - *Multiple output formats* : `json` (machine‑readable), `pseudo` (human‑friendly), `js` (valid JavaScript, most detailed metadata).
40
+ - *Multiple inputs*: preview many files at once with a shared or per‑file budget.
41
+ - *Fast*: can process gigabyte-scale files in seconds (mostly disk-constrained)
42
+ - *Available as a CLI app and as a Python library*
43
+
44
+ ## Fits into command line workflows
45
+
46
+ If you’re comfortable with tools like `head` and `tail`, use `headson` when you want a quick, structured peek into a JSON file without dumping the entire thing.
47
+
48
+ - `head`/`tail` operate on bytes/lines - their output is not optimized for tree structures
49
+ - `jq` you need to craft filters to preview large JSON files
50
+ - `headson` is like head/tail for trees: zero config but it keeps structure and represents content as much as possible
51
+
52
+ ## Usage
53
+
54
+ headson [FLAGS] [INPUT...]
55
+
56
+ - INPUT (optional, repeatable): file path(s). If omitted, reads JSON from stdin. Multiple input files are supported.
57
+ - Prints the preview to stdout. On parse errors, exits non‑zero and prints an error to stderr.
58
+
59
+ Common flags:
60
+
61
+ - `-n, --budget <BYTES>`: per‑file output budget. When multiple input files are provided, the total budget equals `<BYTES> * number_of_inputs`.
62
+ - `-N, --global-budget <BYTES>`: total output budget across all inputs. Useful when you want a fixed-size preview across many files (may omit entire files). Mutually exclusive with `--budget`.
63
+ - `-f, --template <json|pseudo|js>`: output style (default: `pseudo`)
64
+ - `-m, --compact`: no indentation, no spaces, no newlines
65
+ - `--no-newline`: single line output
66
+ - `--no-space`: no space after `:` in objects
67
+ - `--indent <STR>`: indentation unit (default: two spaces)
68
+ - `--string-cap <N>`: max graphemes to consider per string (default: 500)
69
+ - `--tail`: prefer the end of arrays when truncating. Strings are unaffected. In `pseudo`/`js` templates the omission marker appears at the start; `json` remains strict JSON with no annotations.
70
+
71
+ Notes:
72
+
73
+ - With multiple input files:
74
+ - JSON template outputs a single JSON object keyed by the input file paths.
75
+ - Pseudo and JS templates render file sections with human-readable headers.
76
+ - Using `--global-budget` may truncate or omit entire files to respect the total budget.
77
+ - The tool finds the largest preview that fits the budget; if even the tiniest preview exceeds it, you still get a minimal, valid preview.
78
+ - When passing file paths, directories and binary files are ignored; a notice is printed to stderr for each (e.g., `Ignored binary file: ./path/to/file`). Stdin mode reads the stream as-is.
79
+
80
+ Quick one‑liners:
81
+
82
+ - Peek a big JSON stream (keeps structure):
83
+
84
+ zstdcat huge.json.zst | headson -n 800 -f pseudo
85
+
86
+ - Many files with a fixed overall size:
87
+
88
+ headson -N 1200 -f json logs/*.json
89
+
90
+ - Glance at a file, JavaScript‑style comments for omissions:
91
+
92
+ headson -n 400 -f js data.json
93
+
94
+ Show help:
95
+
96
+ headson --help
97
+
98
+ ## Examples: head vs headson
99
+
100
+ Input:
101
+
102
+ ```json
103
+ {"users":[{"id":1,"name":"Ana","roles":["admin","dev"]},{"id":2,"name":"Bo"}],"meta":{"count":2,"source":"db"}}
104
+ ```
105
+
106
+ Naive cut (can break mid‑token):
107
+
108
+ ```bash
109
+ jq -c . users.json | head -c 80
110
+ # {"users":[{"id":1,"name":"Ana","roles":["admin","dev"]},{"id":2,"name":"Bo"}],"me
111
+ ```
112
+
113
+ Structured preview with headson (pseudo):
114
+
115
+ ```bash
116
+ headson -n 120 -f pseudo users.json
117
+ # {
118
+ # users: [
119
+ # { id: 1, name: "Ana", roles: [ "admin", … ] },
120
+ # …
121
+ # ]
122
+ # meta: { count: 2, … }
123
+ # }
124
+ ```
125
+
126
+ Machine‑readable preview (json):
127
+
128
+ ```bash
129
+ headson -n 120 -f json users.json
130
+ # {"users":[{"id":1,"name":"Ana","roles":["admin"]}],"meta":{"count":2}}
131
+ ```
132
+
133
+ ## Python Bindings
134
+
135
+ A thin Python extension module is available on PyPI as `headson`.
136
+
137
+ - Install: `pip install headson` (prebuilt wheels for CPython 3.10–3.12 on Linux/macOS/Windows). Older/newer Python versions may build from source if Rust is installed.
138
+ - API:
139
+ - `headson.summarize(text: str, *, template: str = "pseudo", character_budget: int | None = None, tail: bool = False) -> str`
140
+ - `template`: one of `"json" | "pseudo" | "js"`
141
+ - `character_budget`: maximum output size in characters (default: 500)
142
+ - `tail`: prefer the end of arrays when truncating; strings unaffected. Affects only display templates (`pseudo`/`js`); `json` remains strict.
143
+
144
+ Example:
145
+
146
+ ```python
147
+ import json
148
+ import headson
149
+
150
+ data = {"foo": [1, 2, 3], "bar": {"x": "y"}}
151
+ preview = headson.summarize(json.dumps(data), template="json", character_budget=200)
152
+ print(preview)
153
+
154
+ # Prefer the tail of arrays (annotations show in pseudo/js only)
155
+ print(
156
+ headson.summarize(
157
+ json.dumps(list(range(100))),
158
+ template="pseudo",
159
+ character_budget=80,
160
+ tail=True,
161
+ )
162
+ )
163
+ ```
164
+
165
+ ## License
166
+
167
+ MIT
168
+
@@ -0,0 +1,152 @@
1
+ # headson
2
+
3
+ Head/tail for JSON — but structure‑aware. Get a compact preview that shows both the shape and representative values of your data, all within a strict character budget.
4
+
5
+ Available as:
6
+ - CLI (see [Usage](#usage))
7
+ - Python library (see [Python Bindings](#python-bindings))
8
+
9
+ ## Install
10
+
11
+ Using Cargo:
12
+
13
+ cargo install headson
14
+
15
+ From source:
16
+
17
+ cargo build --release
18
+ target/release/headson --help
19
+
20
+
21
+ ## Features
22
+
23
+ - *Budgeted output*: specify exactly how much JSON you want to see
24
+ - *Multiple output formats* : `json` (machine‑readable), `pseudo` (human‑friendly), `js` (valid JavaScript, most detailed metadata).
25
+ - *Multiple inputs*: preview many files at once with a shared or per‑file budget.
26
+ - *Fast*: can process gigabyte-scale files in seconds (mostly disk-constrained)
27
+ - *Available as a CLI app and as a Python library*
28
+
29
+ ## Fits into command line workflows
30
+
31
+ If you’re comfortable with tools like `head` and `tail`, use `headson` when you want a quick, structured peek into a JSON file without dumping the entire thing.
32
+
33
+ - `head`/`tail` operate on bytes/lines - their output is not optimized for tree structures
34
+ - `jq` you need to craft filters to preview large JSON files
35
+ - `headson` is like head/tail for trees: zero config but it keeps structure and represents content as much as possible
36
+
37
+ ## Usage
38
+
39
+ headson [FLAGS] [INPUT...]
40
+
41
+ - INPUT (optional, repeatable): file path(s). If omitted, reads JSON from stdin. Multiple input files are supported.
42
+ - Prints the preview to stdout. On parse errors, exits non‑zero and prints an error to stderr.
43
+
44
+ Common flags:
45
+
46
+ - `-n, --budget <BYTES>`: per‑file output budget. When multiple input files are provided, the total budget equals `<BYTES> * number_of_inputs`.
47
+ - `-N, --global-budget <BYTES>`: total output budget across all inputs. Useful when you want a fixed-size preview across many files (may omit entire files). Mutually exclusive with `--budget`.
48
+ - `-f, --template <json|pseudo|js>`: output style (default: `pseudo`)
49
+ - `-m, --compact`: no indentation, no spaces, no newlines
50
+ - `--no-newline`: single line output
51
+ - `--no-space`: no space after `:` in objects
52
+ - `--indent <STR>`: indentation unit (default: two spaces)
53
+ - `--string-cap <N>`: max graphemes to consider per string (default: 500)
54
+ - `--tail`: prefer the end of arrays when truncating. Strings are unaffected. In `pseudo`/`js` templates the omission marker appears at the start; `json` remains strict JSON with no annotations.
55
+
56
+ Notes:
57
+
58
+ - With multiple input files:
59
+ - JSON template outputs a single JSON object keyed by the input file paths.
60
+ - Pseudo and JS templates render file sections with human-readable headers.
61
+ - Using `--global-budget` may truncate or omit entire files to respect the total budget.
62
+ - The tool finds the largest preview that fits the budget; if even the tiniest preview exceeds it, you still get a minimal, valid preview.
63
+ - When passing file paths, directories and binary files are ignored; a notice is printed to stderr for each (e.g., `Ignored binary file: ./path/to/file`). Stdin mode reads the stream as-is.
64
+
65
+ Quick one‑liners:
66
+
67
+ - Peek a big JSON stream (keeps structure):
68
+
69
+ zstdcat huge.json.zst | headson -n 800 -f pseudo
70
+
71
+ - Many files with a fixed overall size:
72
+
73
+ headson -N 1200 -f json logs/*.json
74
+
75
+ - Glance at a file, JavaScript‑style comments for omissions:
76
+
77
+ headson -n 400 -f js data.json
78
+
79
+ Show help:
80
+
81
+ headson --help
82
+
83
+ ## Examples: head vs headson
84
+
85
+ Input:
86
+
87
+ ```json
88
+ {"users":[{"id":1,"name":"Ana","roles":["admin","dev"]},{"id":2,"name":"Bo"}],"meta":{"count":2,"source":"db"}}
89
+ ```
90
+
91
+ Naive cut (can break mid‑token):
92
+
93
+ ```bash
94
+ jq -c . users.json | head -c 80
95
+ # {"users":[{"id":1,"name":"Ana","roles":["admin","dev"]},{"id":2,"name":"Bo"}],"me
96
+ ```
97
+
98
+ Structured preview with headson (pseudo):
99
+
100
+ ```bash
101
+ headson -n 120 -f pseudo users.json
102
+ # {
103
+ # users: [
104
+ # { id: 1, name: "Ana", roles: [ "admin", … ] },
105
+ # …
106
+ # ]
107
+ # meta: { count: 2, … }
108
+ # }
109
+ ```
110
+
111
+ Machine‑readable preview (json):
112
+
113
+ ```bash
114
+ headson -n 120 -f json users.json
115
+ # {"users":[{"id":1,"name":"Ana","roles":["admin"]}],"meta":{"count":2}}
116
+ ```
117
+
118
+ ## Python Bindings
119
+
120
+ A thin Python extension module is available on PyPI as `headson`.
121
+
122
+ - Install: `pip install headson` (prebuilt wheels for CPython 3.10–3.12 on Linux/macOS/Windows). Older/newer Python versions may build from source if Rust is installed.
123
+ - API:
124
+ - `headson.summarize(text: str, *, template: str = "pseudo", character_budget: int | None = None, tail: bool = False) -> str`
125
+ - `template`: one of `"json" | "pseudo" | "js"`
126
+ - `character_budget`: maximum output size in characters (default: 500)
127
+ - `tail`: prefer the end of arrays when truncating; strings unaffected. Affects only display templates (`pseudo`/`js`); `json` remains strict.
128
+
129
+ Example:
130
+
131
+ ```python
132
+ import json
133
+ import headson
134
+
135
+ data = {"foo": [1, 2, 3], "bar": {"x": "y"}}
136
+ preview = headson.summarize(json.dumps(data), template="json", character_budget=200)
137
+ print(preview)
138
+
139
+ # Prefer the tail of arrays (annotations show in pseudo/js only)
140
+ print(
141
+ headson.summarize(
142
+ json.dumps(list(range(100))),
143
+ template="pseudo",
144
+ character_budget=80,
145
+ tail=True,
146
+ )
147
+ )
148
+ ```
149
+
150
+ ## License
151
+
152
+ MIT
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "headson"
7
- version = "0.2.5"
7
+ version = "0.3.0"
8
8
  description = "Budget‑constrained JSON preview renderer (Python bindings)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -32,7 +32,7 @@ python-source = "python"
32
32
  dev = [
33
33
  "pytest>=8",
34
34
  "maturin>=1.7,<2",
35
- "ruff>=0.6",
35
+ "ruff==0.6.9",
36
36
  ]
37
37
 
38
38
  [tool.ruff]
@@ -42,5 +42,3 @@ src = ["python", "tests_py"]
42
42
 
43
43
  [tool.ruff.lint]
44
44
  select = ["E", "F"]
45
-
46
-
@@ -169,7 +169,7 @@ dependencies = [
169
169
 
170
170
  [[package]]
171
171
  name = "headson"
172
- version = "0.2.5"
172
+ version = "0.3.0"
173
173
  dependencies = [
174
174
  "anyhow",
175
175
  "clap",
@@ -182,7 +182,7 @@ dependencies = [
182
182
 
183
183
  [[package]]
184
184
  name = "headson-python"
185
- version = "0.2.5"
185
+ version = "0.3.0"
186
186
  dependencies = [
187
187
  "anyhow",
188
188
  "headson",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "headson-python"
3
- version = "0.2.5"
3
+ version = "0.3.0"
4
4
  edition = "2021"
5
5
  publish = false
6
6
  readme = "README.md"
@@ -13,9 +13,7 @@ fn to_template(s: &str) -> Result<OutputTemplate> {
13
13
  }
14
14
  }
15
15
 
16
- fn render_config(
17
- template: &str,
18
- ) -> Result<RenderConfig> {
16
+ fn render_config(template: &str, prefer_tail_arrays: bool) -> Result<RenderConfig> {
19
17
  let t = to_template(template)?;
20
18
  let space = " ".to_string();
21
19
  let newline = "\n".to_string();
@@ -25,13 +23,15 @@ fn render_config(
25
23
  indent_unit,
26
24
  space,
27
25
  newline,
26
+ prefer_tail_arrays,
28
27
  })
29
28
  }
30
29
 
31
- fn priority_config(per_file_budget: usize) -> PriorityConfig {
30
+ fn priority_config(per_file_budget: usize, prefer_tail_arrays: bool) -> PriorityConfig {
32
31
  PriorityConfig {
33
32
  max_string_graphemes: 500,
34
33
  array_max_items: (per_file_budget / 2).max(1),
34
+ prefer_tail_arrays,
35
35
  }
36
36
  }
37
37
 
@@ -40,17 +40,18 @@ fn to_pyerr(e: anyhow::Error) -> PyErr {
40
40
  }
41
41
 
42
42
  #[pyfunction]
43
- #[pyo3(signature = (text, *, template="pseudo", character_budget=None))]
43
+ #[pyo3(signature = (text, *, template="pseudo", character_budget=None, tail=false))]
44
44
  fn summarize(
45
45
  py: Python<'_>,
46
46
  text: &str,
47
47
  template: &str,
48
48
  character_budget: Option<usize>,
49
+ tail: bool,
49
50
  ) -> PyResult<String> {
50
- let cfg = render_config(template).map_err(to_pyerr)?;
51
+ let cfg = render_config(template, tail).map_err(to_pyerr)?;
51
52
  let budget = character_budget.unwrap_or(500);
52
53
  let per_file_for_priority = budget.max(1);
53
- let prio = priority_config(per_file_for_priority);
54
+ let prio = priority_config(per_file_for_priority, tail);
54
55
  let input = text.as_bytes().to_vec();
55
56
  py.detach(|| headson_core::headson(input, &cfg, &prio, budget).map_err(to_pyerr))
56
57
  }
@@ -53,6 +53,12 @@ struct Cli {
53
53
  help = "Total output budget across all inputs; useful to keep multiple files within a fixed overall output size (may omit entire files)."
54
54
  )]
55
55
  global_budget: Option<usize>,
56
+ #[arg(
57
+ long = "tail",
58
+ default_value_t = false,
59
+ help = "Prefer the end of arrays when truncating. Strings unaffected; JSON stays strict."
60
+ )]
61
+ tail: bool,
56
62
  #[arg(
57
63
  value_name = "INPUT",
58
64
  value_hint = clap::ValueHint::FilePath,
@@ -225,6 +231,7 @@ fn get_render_config_from(cli: &Cli) -> headson::RenderConfig {
225
231
  indent_unit,
226
232
  space,
227
233
  newline,
234
+ prefer_tail_arrays: cli.tail,
228
235
  }
229
236
  }
230
237
 
@@ -232,14 +239,9 @@ fn get_priority_config(
232
239
  per_file_budget: usize,
233
240
  cli: &Cli,
234
241
  ) -> headson::PriorityConfig {
235
- // Optimization: derive a conservative per‑array expansion cap from the output
236
- // budget to avoid allocating/walking items that could never appear in the
237
- // final preview. As a simple lower bound, an array of N items needs ~2*N
238
- // bytes to render (item plus comma), so we cap per‑array expansion at
239
- // budget/2. This prunes unnecessary work on large inputs without changing
240
- // output semantics.
241
242
  headson::PriorityConfig {
242
243
  max_string_graphemes: cli.string_cap,
243
244
  array_max_items: (per_file_budget / 2).max(1),
245
+ prefer_tail_arrays: cli.tail,
244
246
  }
245
247
  }
@@ -120,7 +120,13 @@ impl<'a> Scope<'a> {
120
120
  let child_kind = self.arena.nodes[child_arena_id].kind;
121
121
  let child_pq = *self.next_pq_id;
122
122
  *self.next_pq_id += 1;
123
- let extra = (i as u128).pow(3) * ARRAY_INDEX_CUBIC_WEIGHT;
123
+ let idx_for_priority: usize = if self.config.prefer_tail_arrays {
124
+ kept.saturating_sub(1).saturating_sub(i)
125
+ } else {
126
+ i
127
+ };
128
+ let ii = idx_for_priority as u128;
129
+ let extra = ii * ii * ii * ARRAY_INDEX_CUBIC_WEIGHT;
124
130
  let score = entry.score + ARRAY_CHILD_BASE_INCREMENT + extra;
125
131
  let child_node = &self.arena.nodes[child_arena_id];
126
132
  self.push_child_common(
@@ -4,6 +4,7 @@ use serde_json;
4
4
  pub struct PriorityConfig {
5
5
  pub max_string_graphemes: usize,
6
6
  pub array_max_items: usize,
7
+ pub prefer_tail_arrays: bool,
7
8
  }
8
9
 
9
10
  impl PriorityConfig {
@@ -11,6 +12,7 @@ impl PriorityConfig {
11
12
  Self {
12
13
  max_string_graphemes,
13
14
  array_max_items,
15
+ prefer_tail_arrays: false,
14
16
  }
15
17
  }
16
18
  }
@@ -19,6 +19,39 @@ pub(crate) struct RenderScope<'a> {
19
19
  }
20
20
 
21
21
  impl<'a> RenderScope<'a> {
22
+ fn render_has_newline(&self, s: &str) -> bool {
23
+ let nl = &self.config.newline;
24
+ if nl.is_empty() {
25
+ return false;
26
+ }
27
+ if nl == "\n" {
28
+ return s.as_bytes().contains(&b'\n');
29
+ }
30
+ s.contains(nl)
31
+ }
32
+
33
+ fn push_array_child_line(
34
+ &self,
35
+ out: &mut Vec<ArrayChildPair>,
36
+ index: usize,
37
+ child_kind: NodeKind,
38
+ depth: usize,
39
+ rendered: String,
40
+ ) {
41
+ if self.render_has_newline(&rendered) {
42
+ out.push((index, rendered));
43
+ return;
44
+ }
45
+ match child_kind {
46
+ NodeKind::Array | NodeKind::Object => {
47
+ out.push((index, rendered));
48
+ }
49
+ _ => {
50
+ let child_indent = indent(depth + 1, &self.config.indent_unit);
51
+ out.push((index, format!("{child_indent}{rendered}")));
52
+ }
53
+ }
54
+ }
22
55
  fn append_js_fileset_section(
23
56
  &mut self,
24
57
  out: &mut String,
@@ -172,6 +205,7 @@ impl<'a> RenderScope<'a> {
172
205
  indent_unit: &config.indent_unit,
173
206
  inline_open: inline,
174
207
  newline: &config.newline,
208
+ omitted_at_start: config.prefer_tail_arrays,
175
209
  };
176
210
  render_array(config.template, &ctx)
177
211
  }
@@ -278,7 +312,6 @@ impl<'a> RenderScope<'a> {
278
312
  id: usize,
279
313
  depth: usize,
280
314
  ) -> (Vec<ArrayChildPair>, usize) {
281
- let config = self.config;
282
315
  let mut children_pairs: Vec<ArrayChildPair> = Vec::new();
283
316
  let mut kept = 0usize;
284
317
  if let Some(children_ids) = self.pq.children.get(id) {
@@ -287,17 +320,16 @@ impl<'a> RenderScope<'a> {
287
320
  continue;
288
321
  }
289
322
  kept += 1;
323
+ let child_kind = self.pq.nodes[child_id.0].kind;
290
324
  let rendered =
291
325
  self.serialize_node(child_id.0, depth + 1, false);
292
- if !config.newline.is_empty()
293
- && rendered.contains(&config.newline)
294
- {
295
- children_pairs.push((i, rendered));
296
- } else {
297
- let child_indent = indent(depth + 1, &config.indent_unit);
298
- children_pairs
299
- .push((i, format!("{child_indent}{rendered}")));
300
- }
326
+ self.push_array_child_line(
327
+ &mut children_pairs,
328
+ i,
329
+ child_kind,
330
+ depth,
331
+ rendered,
332
+ );
301
333
  }
302
334
  }
303
335
  (children_pairs, kept)
@@ -463,11 +495,49 @@ mod tests {
463
495
  indent_unit: " ".to_string(),
464
496
  space: " ".to_string(),
465
497
  newline: "\n".to_string(),
498
+ prefer_tail_arrays: false,
466
499
  },
467
500
  );
468
501
  assert_snapshot!("arena_render_empty", out);
469
502
  }
470
503
 
504
+ #[test]
505
+ fn newline_detection_crlf_array_child() {
506
+ // Ensure we exercise the render_has_newline branch that checks
507
+ // arbitrary newline sequences (e.g., "\r\n") via s.contains(nl).
508
+ let arena = crate::json_ingest::build_json_tree_arena(
509
+ "[{\"a\":1,\"b\":2}]",
510
+ &crate::PriorityConfig::new(usize::MAX, usize::MAX),
511
+ )
512
+ .unwrap();
513
+ let build = build_order(
514
+ &arena,
515
+ &crate::PriorityConfig::new(usize::MAX, usize::MAX),
516
+ )
517
+ .unwrap();
518
+ let mut marks = vec![0u32; build.total_nodes];
519
+ let out = render_arena_with_marks(
520
+ &build,
521
+ usize::MAX,
522
+ &mut marks,
523
+ 1,
524
+ &crate::RenderConfig {
525
+ template: crate::OutputTemplate::Json,
526
+ indent_unit: " ".to_string(),
527
+ space: " ".to_string(),
528
+ // Use CRLF to force the contains(nl) path.
529
+ newline: "\r\n".to_string(),
530
+ prefer_tail_arrays: false,
531
+ },
532
+ );
533
+ // Sanity: output should contain CRLF newlines and render the object child across lines.
534
+ assert!(
535
+ out.contains("\r\n"),
536
+ "expected CRLF newlines in output: {out:?}"
537
+ );
538
+ assert!(out.starts_with("["));
539
+ }
540
+
471
541
  #[test]
472
542
  fn arena_render_single_string_array() {
473
543
  let arena = crate::json_ingest::build_json_tree_arena(
@@ -491,6 +561,7 @@ mod tests {
491
561
  indent_unit: " ".to_string(),
492
562
  space: " ".to_string(),
493
563
  newline: "\n".to_string(),
564
+ prefer_tail_arrays: false,
494
565
  },
495
566
  );
496
567
  assert_snapshot!("arena_render_single", out);
@@ -48,8 +48,13 @@ pub fn render_array_with<S: Style>(ctx: &ArrayCtx<'_>) -> String {
48
48
  out.push_str(open_indent);
49
49
  out.push('[');
50
50
  out.push_str(ctx.newline);
51
+ if ctx.omitted_at_start {
52
+ S::array_push_omitted(&mut out, ctx);
53
+ }
51
54
  push_array_items(&mut out, ctx);
52
- S::array_push_omitted(&mut out, ctx);
55
+ if !ctx.omitted_at_start {
56
+ S::array_push_omitted(&mut out, ctx);
57
+ }
53
58
  out.push_str(&base);
54
59
  out.push(']');
55
60
  out
@@ -18,10 +18,13 @@ impl Style for Js {
18
18
  fn array_push_omitted(out: &mut String, ctx: &ArrayCtx<'_>) {
19
19
  if ctx.omitted > 0 {
20
20
  out.push_str(&indent(ctx.depth + 1, ctx.indent_unit));
21
- out.push_str(&format!(
22
- "/* {} more items */{}",
23
- ctx.omitted, ctx.newline
24
- ));
21
+ out.push_str("/* ");
22
+ out.push_str(&ctx.omitted.to_string());
23
+ out.push_str(" more items */");
24
+ if ctx.children_len > 0 && ctx.omitted_at_start {
25
+ out.push(',');
26
+ }
27
+ out.push_str(ctx.newline);
25
28
  }
26
29
  }
27
30
 
@@ -13,6 +13,7 @@ pub struct ArrayCtx<'a> {
13
13
  pub indent_unit: &'a str,
14
14
  pub inline_open: bool,
15
15
  pub newline: &'a str,
16
+ pub omitted_at_start: bool,
16
17
  }
17
18
 
18
19
  pub struct ObjectCtx<'a> {
@@ -16,6 +16,9 @@ impl Style for Pseudo {
16
16
  if ctx.omitted > 0 {
17
17
  out.push_str(&indent(ctx.depth + 1, ctx.indent_unit));
18
18
  out.push('…');
19
+ if ctx.children_len > 0 && ctx.omitted_at_start {
20
+ out.push(',');
21
+ }
19
22
  out.push_str(ctx.newline);
20
23
  }
21
24
  }
@@ -13,4 +13,6 @@ pub struct RenderConfig {
13
13
  // Newline sequence to use in final output (e.g., "\n" or "").
14
14
  // Templates read this directly; no post-processing replacement.
15
15
  pub newline: String,
16
+ // When true, arrays prefer tail rendering (omission marker at start).
17
+ pub prefer_tail_arrays: bool,
16
18
  }
@@ -6,9 +6,8 @@ pub struct JsonTreeArena {
6
6
  pub children: Vec<usize>,
7
7
  pub obj_keys: Vec<String>,
8
8
  pub root_id: usize,
9
- // Marks that the root is a synthetic wrapper object representing a fileset
10
- // (multi-input ingest). Rendering remains standard JSON; this is an
11
- // internal marker for future behaviors.
9
+ // True when root is a synthetic wrapper object for multi-input ingest.
10
+ // Rendering remains standard JSON; used to select fileset-specific headers.
12
11
  pub is_fileset: bool,
13
12
  }
14
13
 
headson-0.2.5/PKG-INFO DELETED
@@ -1,99 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: headson
3
- Version: 0.2.5
4
- Classifier: Programming Language :: Python
5
- Classifier: Programming Language :: Python :: 3
6
- Classifier: Programming Language :: Rust
7
- Classifier: Operating System :: OS Independent
8
- Requires-Dist: pytest>=8 ; extra == 'test'
9
- Provides-Extra: test
10
- License-File: LICENSE
11
- Summary: Budget‑constrained JSON preview renderer (Python bindings)
12
- Keywords: json,preview,summarize,cli,bindings
13
- Requires-Python: >=3.8
14
- Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
15
-
16
- # headson
17
-
18
- Budget‑constrained JSON preview for the terminal.
19
-
20
- ## Install
21
-
22
- Using Cargo:
23
-
24
- cargo install headson
25
-
26
- From source:
27
-
28
- cargo build --release
29
- target/release/headson --help
30
-
31
- ## Usage
32
-
33
- headson [FLAGS] [INPUT...]
34
-
35
- - INPUT (optional, repeatable): file path(s). If omitted, reads JSON from stdin. Multiple input files are supported.
36
- - Prints the preview to stdout. On parse errors, exits non‑zero and prints an error to stderr.
37
-
38
- Common flags:
39
-
40
- - `-n, --budget <BYTES>`: per‑file output budget. When multiple input files are provided, the total budget equals `<BYTES> * number_of_inputs`.
41
- - `-N, --global-budget <BYTES>`: total output budget across all inputs. Useful when you want a fixed-size preview across many files (may omit entire files). Mutually exclusive with `--budget`.
42
- - `-f, --template <json|pseudo|js>`: output style (default: `pseudo`)
43
- - `-m, --compact`: no indentation, no spaces, no newlines
44
- - `--no-newline`: single line output
45
- - `--no-space`: no space after `:` in objects
46
- - `--indent <STR>`: indentation unit (default: two spaces)
47
- - `--string-cap <N>`: max graphemes to consider per string (default: 500)
48
-
49
- Notes:
50
-
51
- - With multiple input files:
52
- - JSON template outputs a single JSON object keyed by the input file paths.
53
- - Pseudo and JS templates render file sections with human-readable headers.
54
- - Using `--global-budget` may truncate or omit entire files to respect the total budget.
55
- - When passing file paths, directories and binary files are ignored; a notice is printed to stderr for each (e.g., `Ignored binary file: ./path/to/file`). Stdin mode reads the stream as-is.
56
-
57
- Examples:
58
-
59
- - Read from stdin with defaults:
60
-
61
- cat data.json | headson
62
-
63
- - Read from file, JS style, 200‑byte budget:
64
-
65
- headson -n 200 -f js data.json
66
-
67
- - JSON style, compact:
68
-
69
- headson -f json -m data.json
70
-
71
- - Multiple files (JSON template produces an object keyed by paths):
72
-
73
- headson -f json a.json b.json
74
-
75
- - Global limit across files (fixed total size across all files):
76
-
77
- headson -N 400 -f json a.json b.json
78
-
79
- Show help:
80
-
81
- headson --help
82
-
83
- ## Python package
84
-
85
- Headson is also available as a Python extension module built with PyO3/maturin.
86
-
87
- Install from PyPI:
88
-
89
- pip install headson
90
-
91
- Example:
92
-
93
- import json
94
- import headson
95
-
96
- data = {"foo": [1, 2, 3], "bar": {"x": "y"}}
97
- preview = headson.summarize(json.dumps(data), template="json", character_budget=200)
98
- print(preview)
99
-
headson-0.2.5/README.md DELETED
@@ -1,83 +0,0 @@
1
- # headson
2
-
3
- Budget‑constrained JSON preview for the terminal.
4
-
5
- ## Install
6
-
7
- Using Cargo:
8
-
9
- cargo install headson
10
-
11
- From source:
12
-
13
- cargo build --release
14
- target/release/headson --help
15
-
16
- ## Usage
17
-
18
- headson [FLAGS] [INPUT...]
19
-
20
- - INPUT (optional, repeatable): file path(s). If omitted, reads JSON from stdin. Multiple input files are supported.
21
- - Prints the preview to stdout. On parse errors, exits non‑zero and prints an error to stderr.
22
-
23
- Common flags:
24
-
25
- - `-n, --budget <BYTES>`: per‑file output budget. When multiple input files are provided, the total budget equals `<BYTES> * number_of_inputs`.
26
- - `-N, --global-budget <BYTES>`: total output budget across all inputs. Useful when you want a fixed-size preview across many files (may omit entire files). Mutually exclusive with `--budget`.
27
- - `-f, --template <json|pseudo|js>`: output style (default: `pseudo`)
28
- - `-m, --compact`: no indentation, no spaces, no newlines
29
- - `--no-newline`: single line output
30
- - `--no-space`: no space after `:` in objects
31
- - `--indent <STR>`: indentation unit (default: two spaces)
32
- - `--string-cap <N>`: max graphemes to consider per string (default: 500)
33
-
34
- Notes:
35
-
36
- - With multiple input files:
37
- - JSON template outputs a single JSON object keyed by the input file paths.
38
- - Pseudo and JS templates render file sections with human-readable headers.
39
- - Using `--global-budget` may truncate or omit entire files to respect the total budget.
40
- - When passing file paths, directories and binary files are ignored; a notice is printed to stderr for each (e.g., `Ignored binary file: ./path/to/file`). Stdin mode reads the stream as-is.
41
-
42
- Examples:
43
-
44
- - Read from stdin with defaults:
45
-
46
- cat data.json | headson
47
-
48
- - Read from file, JS style, 200‑byte budget:
49
-
50
- headson -n 200 -f js data.json
51
-
52
- - JSON style, compact:
53
-
54
- headson -f json -m data.json
55
-
56
- - Multiple files (JSON template produces an object keyed by paths):
57
-
58
- headson -f json a.json b.json
59
-
60
- - Global limit across files (fixed total size across all files):
61
-
62
- headson -N 400 -f json a.json b.json
63
-
64
- Show help:
65
-
66
- headson --help
67
-
68
- ## Python package
69
-
70
- Headson is also available as a Python extension module built with PyO3/maturin.
71
-
72
- Install from PyPI:
73
-
74
- pip install headson
75
-
76
- Example:
77
-
78
- import json
79
- import headson
80
-
81
- data = {"foo": [1, 2, 3], "bar": {"x": "y"}}
82
- preview = headson.summarize(json.dumps(data), template="json", character_budget=200)
83
- print(preview)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes