headson 0.2.3__tar.gz → 0.2.4__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 (47) hide show
  1. {headson-0.2.3 → headson-0.2.4}/Cargo.lock +101 -3
  2. {headson-0.2.3 → headson-0.2.4}/Cargo.toml +3 -1
  3. {headson-0.2.3 → headson-0.2.4}/PKG-INFO +2 -1
  4. {headson-0.2.3 → headson-0.2.4}/README.md +1 -0
  5. {headson-0.2.3 → headson-0.2.4}/pyproject.toml +1 -1
  6. {headson-0.2.3 → headson-0.2.4}/python/Cargo.lock +12 -2
  7. {headson-0.2.3 → headson-0.2.4}/python/Cargo.toml +1 -1
  8. headson-0.2.4/src/main.rs +245 -0
  9. {headson-0.2.3 → headson-0.2.4}/src/order/mod.rs +2 -1
  10. {headson-0.2.3 → headson-0.2.4}/src/serialization/mod.rs +1 -1
  11. {headson-0.2.3 → headson-0.2.4}/src/serialization/templates/js.rs +2 -2
  12. {headson-0.2.3 → headson-0.2.4}/src/serialization/templates/json.rs +2 -2
  13. {headson-0.2.3 → headson-0.2.4}/src/serialization/templates/mod.rs +4 -4
  14. {headson-0.2.3 → headson-0.2.4}/src/serialization/templates/pseudo.rs +2 -2
  15. headson-0.2.3/src/main.rs +0 -174
  16. {headson-0.2.3 → headson-0.2.4}/JSONTestSuite/LICENSE +0 -0
  17. {headson-0.2.3 → headson-0.2.4}/JSONTestSuite/README.md +0 -0
  18. {headson-0.2.3 → headson-0.2.4}/LICENSE +0 -0
  19. {headson-0.2.3 → headson-0.2.4}/python/README.md +0 -0
  20. {headson-0.2.3 → headson-0.2.4}/python/headson/__init__.py +0 -0
  21. {headson-0.2.3 → headson-0.2.4}/python/src/lib.rs +0 -0
  22. {headson-0.2.3 → headson-0.2.4}/src/json_ingest/builder.rs +0 -0
  23. {headson-0.2.3 → headson-0.2.4}/src/json_ingest/mod.rs +0 -0
  24. {headson-0.2.3 → headson-0.2.4}/src/lib.rs +0 -0
  25. {headson-0.2.3 → headson-0.2.4}/src/order/build.rs +0 -0
  26. {headson-0.2.3 → headson-0.2.4}/src/order/scoring.rs +0 -0
  27. {headson-0.2.3 → headson-0.2.4}/src/order/snapshots/headson__order__build__tests__order_empty_array_order.snap +0 -0
  28. {headson-0.2.3 → headson-0.2.4}/src/order/snapshots/headson__order__build__tests__order_single_string_array_order.snap +0 -0
  29. {headson-0.2.3 → headson-0.2.4}/src/order/types.rs +0 -0
  30. {headson-0.2.3 → headson-0.2.4}/src/serialization/snapshots/headson__serialization__tests__arena_render_empty.snap +0 -0
  31. {headson-0.2.3 → headson-0.2.4}/src/serialization/snapshots/headson__serialization__tests__arena_render_single.snap +0 -0
  32. {headson-0.2.3 → headson-0.2.4}/src/serialization/templates/core.rs +0 -0
  33. {headson-0.2.3 → headson-0.2.4}/src/serialization/types.rs +0 -0
  34. {headson-0.2.3 → headson-0.2.4}/src/snapshots/headson__order__tests__order_empty_array_order.snap +0 -0
  35. {headson-0.2.3 → headson-0.2.4}/src/snapshots/headson__order__tests__order_single_string_array_order.snap +0 -0
  36. {headson-0.2.3 → headson-0.2.4}/src/snapshots/headson__order__tests__pq_empty_array_queue.snap +0 -0
  37. {headson-0.2.3 → headson-0.2.4}/src/snapshots/headson__order__tests__pq_single_string_array_queue.snap +0 -0
  38. {headson-0.2.3 → headson-0.2.4}/src/snapshots/headson__queue__tests__pq_empty_array_queue.snap +0 -0
  39. {headson-0.2.3 → headson-0.2.4}/src/snapshots/headson__queue__tests__pq_single_string_array_queue.snap +0 -0
  40. {headson-0.2.3 → headson-0.2.4}/src/snapshots/headson__tree__tests__build_tree_empty.snap +0 -0
  41. {headson-0.2.3 → headson-0.2.4}/src/snapshots/headson__tree__tests__build_tree_single.snap +0 -0
  42. {headson-0.2.3 → headson-0.2.4}/src/utils/graph.rs +0 -0
  43. {headson-0.2.3 → headson-0.2.4}/src/utils/json.rs +0 -0
  44. {headson-0.2.3 → headson-0.2.4}/src/utils/mod.rs +0 -0
  45. {headson-0.2.3 → headson-0.2.4}/src/utils/search.rs +0 -0
  46. {headson-0.2.3 → headson-0.2.4}/src/utils/text.rs +0 -0
  47. {headson-0.2.3 → headson-0.2.4}/src/utils/tree_arena.rs +0 -0
@@ -98,6 +98,12 @@ version = "1.5.0"
98
98
  source = "registry+https://github.com/rust-lang/crates.io-index"
99
99
  checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
100
100
 
101
+ [[package]]
102
+ name = "bitflags"
103
+ version = "2.10.0"
104
+ source = "registry+https://github.com/rust-lang/crates.io-index"
105
+ checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
106
+
101
107
  [[package]]
102
108
  name = "bstr"
103
109
  version = "1.12.0"
@@ -179,6 +185,15 @@ dependencies = [
179
185
  "windows-sys 0.59.0",
180
186
  ]
181
187
 
188
+ [[package]]
189
+ name = "content_inspector"
190
+ version = "0.2.4"
191
+ source = "registry+https://github.com/rust-lang/crates.io-index"
192
+ checksum = "b7bda66e858c683005a53a9a60c69a4aca7eeaa45d124526e389f7aec8e62f38"
193
+ dependencies = [
194
+ "memchr",
195
+ ]
196
+
182
197
  [[package]]
183
198
  name = "deunicode"
184
199
  version = "1.6.2"
@@ -203,6 +218,16 @@ version = "1.0.0"
203
218
  source = "registry+https://github.com/rust-lang/crates.io-index"
204
219
  checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
205
220
 
221
+ [[package]]
222
+ name = "errno"
223
+ version = "0.3.14"
224
+ source = "registry+https://github.com/rust-lang/crates.io-index"
225
+ checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
226
+ dependencies = [
227
+ "libc",
228
+ "windows-sys 0.60.2",
229
+ ]
230
+
206
231
  [[package]]
207
232
  name = "fake"
208
233
  version = "2.10.0"
@@ -213,6 +238,12 @@ dependencies = [
213
238
  "rand",
214
239
  ]
215
240
 
241
+ [[package]]
242
+ name = "fastrand"
243
+ version = "2.3.0"
244
+ source = "registry+https://github.com/rust-lang/crates.io-index"
245
+ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
246
+
216
247
  [[package]]
217
248
  name = "float-cmp"
218
249
  version = "0.9.0"
@@ -235,6 +266,18 @@ dependencies = [
235
266
  "wasm-bindgen",
236
267
  ]
237
268
 
269
+ [[package]]
270
+ name = "getrandom"
271
+ version = "0.3.4"
272
+ source = "registry+https://github.com/rust-lang/crates.io-index"
273
+ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
274
+ dependencies = [
275
+ "cfg-if",
276
+ "libc",
277
+ "r-efi",
278
+ "wasip2",
279
+ ]
280
+
238
281
  [[package]]
239
282
  name = "halfbrown"
240
283
  version = "0.2.5"
@@ -257,11 +300,12 @@ dependencies = [
257
300
 
258
301
  [[package]]
259
302
  name = "headson"
260
- version = "0.2.3"
303
+ version = "0.2.4"
261
304
  dependencies = [
262
305
  "anyhow",
263
306
  "assert_cmd",
264
307
  "clap",
308
+ "content_inspector",
265
309
  "fake",
266
310
  "insta",
267
311
  "rand",
@@ -269,6 +313,7 @@ dependencies = [
269
313
  "serde",
270
314
  "serde_json",
271
315
  "simd-json",
316
+ "tempfile",
272
317
  "test_each_file",
273
318
  "unicode-segmentation",
274
319
  ]
@@ -375,6 +420,12 @@ version = "0.2.177"
375
420
  source = "registry+https://github.com/rust-lang/crates.io-index"
376
421
  checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
377
422
 
423
+ [[package]]
424
+ name = "linux-raw-sys"
425
+ version = "0.11.0"
426
+ source = "registry+https://github.com/rust-lang/crates.io-index"
427
+ checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
428
+
378
429
  [[package]]
379
430
  name = "log"
380
431
  version = "0.4.28"
@@ -462,6 +513,12 @@ dependencies = [
462
513
  "proc-macro2",
463
514
  ]
464
515
 
516
+ [[package]]
517
+ name = "r-efi"
518
+ version = "5.3.0"
519
+ source = "registry+https://github.com/rust-lang/crates.io-index"
520
+ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
521
+
465
522
  [[package]]
466
523
  name = "rand"
467
524
  version = "0.8.5"
@@ -489,7 +546,7 @@ version = "0.6.4"
489
546
  source = "registry+https://github.com/rust-lang/crates.io-index"
490
547
  checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
491
548
  dependencies = [
492
- "getrandom",
549
+ "getrandom 0.2.16",
493
550
  ]
494
551
 
495
552
  [[package]]
@@ -518,6 +575,19 @@ version = "0.4.13"
518
575
  source = "registry+https://github.com/rust-lang/crates.io-index"
519
576
  checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
520
577
 
578
+ [[package]]
579
+ name = "rustix"
580
+ version = "1.1.2"
581
+ source = "registry+https://github.com/rust-lang/crates.io-index"
582
+ checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
583
+ dependencies = [
584
+ "bitflags",
585
+ "errno",
586
+ "libc",
587
+ "linux-raw-sys",
588
+ "windows-sys 0.60.2",
589
+ ]
590
+
521
591
  [[package]]
522
592
  name = "rustversion"
523
593
  version = "1.0.22"
@@ -579,7 +649,7 @@ version = "0.13.11"
579
649
  source = "registry+https://github.com/rust-lang/crates.io-index"
580
650
  checksum = "a0228a564470f81724e30996bbc2b171713b37b15254a6440c7e2d5449b95691"
581
651
  dependencies = [
582
- "getrandom",
652
+ "getrandom 0.2.16",
583
653
  "halfbrown",
584
654
  "lexical-core",
585
655
  "ref-cast",
@@ -618,6 +688,19 @@ dependencies = [
618
688
  "unicode-ident",
619
689
  ]
620
690
 
691
+ [[package]]
692
+ name = "tempfile"
693
+ version = "3.23.0"
694
+ source = "registry+https://github.com/rust-lang/crates.io-index"
695
+ checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
696
+ dependencies = [
697
+ "fastrand",
698
+ "getrandom 0.3.4",
699
+ "once_cell",
700
+ "rustix",
701
+ "windows-sys 0.60.2",
702
+ ]
703
+
621
704
  [[package]]
622
705
  name = "termtree"
623
706
  version = "0.5.1"
@@ -687,6 +770,15 @@ version = "0.11.1+wasi-snapshot-preview1"
687
770
  source = "registry+https://github.com/rust-lang/crates.io-index"
688
771
  checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
689
772
 
773
+ [[package]]
774
+ name = "wasip2"
775
+ version = "1.0.1+wasi-0.2.4"
776
+ source = "registry+https://github.com/rust-lang/crates.io-index"
777
+ checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
778
+ dependencies = [
779
+ "wit-bindgen",
780
+ ]
781
+
690
782
  [[package]]
691
783
  name = "wasm-bindgen"
692
784
  version = "0.2.104"
@@ -899,6 +991,12 @@ version = "0.53.1"
899
991
  source = "registry+https://github.com/rust-lang/crates.io-index"
900
992
  checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
901
993
 
994
+ [[package]]
995
+ name = "wit-bindgen"
996
+ version = "0.46.0"
997
+ source = "registry+https://github.com/rust-lang/crates.io-index"
998
+ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
999
+
902
1000
  [[package]]
903
1001
  name = "zerocopy"
904
1002
  version = "0.8.27"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "headson"
3
- version = "0.2.3"
3
+ version = "0.2.4"
4
4
  edition = "2024"
5
5
  description = "Budget‑constrained JSON preview renderer"
6
6
  readme = "README.md"
@@ -25,6 +25,7 @@ serde = { version = "1.0.228", features = ["derive"] }
25
25
  serde_json = "1.0.145"
26
26
  unicode-segmentation = "1.12.0"
27
27
  simd-json = { version = "0.13", features = ["serde_impl"] }
28
+ content_inspector = "0.2"
28
29
 
29
30
  [dev-dependencies]
30
31
  insta = "1.40.0"
@@ -33,6 +34,7 @@ test_each_file = "0.3"
33
34
  fake = "2"
34
35
  rand = "0.8"
35
36
  rand_chacha = "0.3"
37
+ tempfile = "3"
36
38
 
37
39
  [profile.release]
38
40
  # Prioritize runtime speed for large inputs
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: headson
3
- Version: 0.2.3
3
+ Version: 0.2.4
4
4
  Classifier: Programming Language :: Python
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Classifier: Programming Language :: Rust
@@ -52,6 +52,7 @@ Notes:
52
52
  - JSON template outputs a single JSON object keyed by the input file paths.
53
53
  - Pseudo and JS templates render file sections with human-readable headers.
54
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.
55
56
 
56
57
  Examples:
57
58
 
@@ -37,6 +37,7 @@ Notes:
37
37
  - JSON template outputs a single JSON object keyed by the input file paths.
38
38
  - Pseudo and JS templates render file sections with human-readable headers.
39
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.
40
41
 
41
42
  Examples:
42
43
 
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "headson"
7
- version = "0.2.3"
7
+ version = "0.2.4"
8
8
  description = "Budget‑constrained JSON preview renderer (Python bindings)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -140,6 +140,15 @@ version = "1.0.4"
140
140
  source = "registry+https://github.com/rust-lang/crates.io-index"
141
141
  checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
142
142
 
143
+ [[package]]
144
+ name = "content_inspector"
145
+ version = "0.2.4"
146
+ source = "registry+https://github.com/rust-lang/crates.io-index"
147
+ checksum = "b7bda66e858c683005a53a9a60c69a4aca7eeaa45d124526e389f7aec8e62f38"
148
+ dependencies = [
149
+ "memchr",
150
+ ]
151
+
143
152
  [[package]]
144
153
  name = "float-cmp"
145
154
  version = "0.9.0"
@@ -184,10 +193,11 @@ dependencies = [
184
193
 
185
194
  [[package]]
186
195
  name = "headson"
187
- version = "0.2.3"
196
+ version = "0.2.4"
188
197
  dependencies = [
189
198
  "anyhow",
190
199
  "clap",
200
+ "content_inspector",
191
201
  "serde",
192
202
  "serde_json",
193
203
  "simd-json",
@@ -196,7 +206,7 @@ dependencies = [
196
206
 
197
207
  [[package]]
198
208
  name = "headson-python"
199
- version = "0.2.3"
209
+ version = "0.2.4"
200
210
  dependencies = [
201
211
  "anyhow",
202
212
  "headson",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "headson-python"
3
- version = "0.2.3"
3
+ version = "0.2.4"
4
4
  edition = "2021"
5
5
  publish = false
6
6
  readme = "README.md"
@@ -0,0 +1,245 @@
1
+ use std::fs::File;
2
+ use std::io::{self, Read};
3
+ use std::path::{Path, PathBuf};
4
+
5
+ use anyhow::{Context, Result};
6
+ use clap::{Parser, ValueEnum};
7
+ use content_inspector::{ContentType, inspect};
8
+
9
+ type InputEntry = (String, Vec<u8>);
10
+ type InputEntries = Vec<InputEntry>;
11
+ type IgnoreNotices = Vec<String>;
12
+
13
+ #[derive(Parser, Debug)]
14
+ #[command(
15
+ name = "headson",
16
+ version,
17
+ about = "Get a small but useful preview of a JSON file"
18
+ )]
19
+ struct Cli {
20
+ #[arg(short = 'n', long = "budget", conflicts_with = "global_budget")]
21
+ budget: Option<usize>,
22
+ #[arg(short = 'f', long = "template", value_enum, default_value_t = Template::Pseudo)]
23
+ template: Template,
24
+ #[arg(long = "indent", default_value = " ")]
25
+ indent: String,
26
+ #[arg(long = "no-space", default_value_t = false)]
27
+ no_space: bool,
28
+ #[arg(
29
+ long = "no-newline",
30
+ default_value_t = false,
31
+ help = "Do not add newlines in the output"
32
+ )]
33
+ no_newline: bool,
34
+ #[arg(
35
+ short = 'm',
36
+ long = "compact",
37
+ default_value_t = false,
38
+ conflicts_with_all = ["no_space", "no_newline", "indent"],
39
+ help = "Compact output with no added whitespace. Not very human-readable."
40
+ )]
41
+ compact: bool,
42
+ #[arg(
43
+ long = "string-cap",
44
+ default_value_t = 500,
45
+ help = "Maximum string length to display"
46
+ )]
47
+ string_cap: usize,
48
+ #[arg(
49
+ short = 'N',
50
+ long = "global-budget",
51
+ value_name = "BYTES",
52
+ conflicts_with = "budget",
53
+ help = "Total output budget across all inputs; useful to keep multiple files within a fixed overall output size (may omit entire files)."
54
+ )]
55
+ global_budget: Option<usize>,
56
+ #[arg(
57
+ value_name = "INPUT",
58
+ value_hint = clap::ValueHint::FilePath,
59
+ num_args = 0..,
60
+ help = "Optional file paths. If omitted, reads JSON from stdin. Multiple input files are supported. Directories and binary files are ignored with a notice on stderr."
61
+ )]
62
+ inputs: Vec<PathBuf>,
63
+ }
64
+
65
+ #[derive(Copy, Clone, Debug, ValueEnum)]
66
+ enum Template {
67
+ Json,
68
+ Pseudo,
69
+ Js,
70
+ }
71
+
72
+ fn main() -> Result<()> {
73
+ let cli = Cli::parse();
74
+
75
+ let render_cfg = get_render_config_from(&cli);
76
+ let (output, ignore_notices) = if cli.inputs.is_empty() {
77
+ (run_from_stdin(&cli, &render_cfg)?, Vec::new())
78
+ } else {
79
+ run_from_paths(&cli, &render_cfg)?
80
+ };
81
+ println!("{output}");
82
+
83
+ for notice in ignore_notices {
84
+ eprintln!("{notice}");
85
+ }
86
+
87
+ Ok(())
88
+ }
89
+
90
+ fn compute_effective_budget(cli: &Cli, input_count: usize) -> usize {
91
+ if let Some(g) = cli.global_budget {
92
+ g
93
+ } else {
94
+ let per_file = cli.budget.unwrap_or(500);
95
+ per_file.saturating_mul(input_count)
96
+ }
97
+ }
98
+
99
+ fn compute_priority(
100
+ cli: &Cli,
101
+ effective_budget: usize,
102
+ input_count: usize,
103
+ ) -> headson::PriorityConfig {
104
+ let per_file_for_priority = (effective_budget / input_count.max(1)).max(1);
105
+ get_priority_config(per_file_for_priority, cli)
106
+ }
107
+
108
+ fn run_from_stdin(
109
+ cli: &Cli,
110
+ render_cfg: &headson::RenderConfig,
111
+ ) -> Result<String> {
112
+ let input_bytes = read_stdin()?;
113
+ let input_count = 1usize;
114
+ let eff = compute_effective_budget(cli, input_count);
115
+ let prio = compute_priority(cli, eff, input_count);
116
+ headson::headson(input_bytes, render_cfg, &prio, eff)
117
+ }
118
+
119
+ fn run_from_paths(
120
+ cli: &Cli,
121
+ render_cfg: &headson::RenderConfig,
122
+ ) -> Result<(String, IgnoreNotices)> {
123
+ let (entries, ignored) = ingest_paths(&cli.inputs)?;
124
+ let included = entries.len();
125
+ let input_count = included.max(1);
126
+ let eff = compute_effective_budget(cli, input_count);
127
+ let prio = compute_priority(cli, eff, input_count);
128
+ if cli.inputs.len() > 1 {
129
+ let out = headson::headson_many(entries, render_cfg, &prio, eff)?;
130
+ Ok((out, ignored))
131
+ } else if included == 0 {
132
+ Ok((String::new(), ignored))
133
+ } else {
134
+ let bytes = entries.into_iter().next().unwrap().1;
135
+ let out = headson::headson(bytes, render_cfg, &prio, eff)?;
136
+ Ok((out, ignored))
137
+ }
138
+ }
139
+
140
+ fn read_stdin() -> Result<Vec<u8>> {
141
+ let mut buf = Vec::new();
142
+ io::stdin()
143
+ .read_to_end(&mut buf)
144
+ .context("failed to read from stdin")?;
145
+ Ok(buf)
146
+ }
147
+
148
+ fn sniff_then_read_text(path: &Path) -> Result<Option<Vec<u8>>> {
149
+ // Inspect the first chunk with content_inspector; if it looks binary, skip.
150
+ // Otherwise, read the remainder without further inspection for speed.
151
+ const CHUNK: usize = 64 * 1024;
152
+ let file = File::open(path).with_context(|| {
153
+ format!("failed to open input file: {}", path.display())
154
+ })?;
155
+ let meta_len = file.metadata().ok().map(|m| m.len());
156
+ let mut reader = io::BufReader::with_capacity(CHUNK, file);
157
+
158
+ let mut first = [0u8; CHUNK];
159
+ let n = reader.read(&mut first).with_context(|| {
160
+ format!("failed to read input file: {}", path.display())
161
+ })?;
162
+ if n == 0 {
163
+ return Ok(Some(Vec::new()));
164
+ }
165
+ if matches!(inspect(&first[..n]), ContentType::BINARY) {
166
+ return Ok(None);
167
+ }
168
+
169
+ // Preallocate buffer: first chunk + estimated remainder (capped)
170
+ let mut buf = Vec::with_capacity(
171
+ n + meta_len
172
+ .map(|m| m.saturating_sub(n as u64) as usize)
173
+ .unwrap_or(0)
174
+ .min(8 * 1024 * 1024),
175
+ );
176
+ buf.extend_from_slice(&first[..n]);
177
+ reader.read_to_end(&mut buf).with_context(|| {
178
+ format!("failed to read input file: {}", path.display())
179
+ })?;
180
+ Ok(Some(buf))
181
+ }
182
+
183
+ fn ingest_paths(paths: &[PathBuf]) -> Result<(InputEntries, IgnoreNotices)> {
184
+ let mut out: InputEntries = Vec::with_capacity(paths.len());
185
+ let mut ignored: IgnoreNotices = Vec::new();
186
+ for path in paths.iter() {
187
+ let display = path.display().to_string();
188
+ if let Ok(meta) = std::fs::metadata(path) {
189
+ if meta.is_dir() {
190
+ ignored.push(format!("Ignored directory: {display}"));
191
+ continue;
192
+ }
193
+ }
194
+ if let Some(bytes) = sniff_then_read_text(path)? {
195
+ out.push((display, bytes))
196
+ } else {
197
+ ignored.push(format!("Ignored binary file: {display}"));
198
+ continue;
199
+ }
200
+ }
201
+ Ok((out, ignored))
202
+ }
203
+
204
+ fn get_render_config_from(cli: &Cli) -> headson::RenderConfig {
205
+ let template = match cli.template {
206
+ Template::Json => headson::OutputTemplate::Json,
207
+ Template::Pseudo => headson::OutputTemplate::Pseudo,
208
+ Template::Js => headson::OutputTemplate::Js,
209
+ };
210
+ let space = if cli.compact || cli.no_space { "" } else { " " }.to_string();
211
+ let newline = if cli.compact || cli.no_newline {
212
+ ""
213
+ } else {
214
+ "\n"
215
+ }
216
+ .to_string();
217
+ let indent_unit = if cli.compact {
218
+ String::new()
219
+ } else {
220
+ cli.indent.clone()
221
+ };
222
+
223
+ headson::RenderConfig {
224
+ template,
225
+ indent_unit,
226
+ space,
227
+ newline,
228
+ }
229
+ }
230
+
231
+ fn get_priority_config(
232
+ per_file_budget: usize,
233
+ cli: &Cli,
234
+ ) -> 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
+ headson::PriorityConfig {
242
+ max_string_graphemes: cli.string_cap,
243
+ array_max_items: (per_file_budget / 2).max(1),
244
+ }
245
+ }
@@ -4,5 +4,6 @@ pub mod types;
4
4
 
5
5
  pub use build::build_order;
6
6
  pub use types::{
7
- NodeId, NodeKind, PriorityConfig, PriorityOrder, ROOT_PQ_ID, RankedNode,
7
+ NodeId, NodeKind, ObjectType, PriorityConfig, PriorityOrder, ROOT_PQ_ID,
8
+ RankedNode,
8
9
  };
@@ -1,4 +1,4 @@
1
- use crate::order::types::ObjectType;
1
+ use crate::order::ObjectType;
2
2
  use crate::order::{NodeKind, PriorityOrder, ROOT_PQ_ID};
3
3
  pub mod templates;
4
4
  pub mod types;
@@ -60,10 +60,10 @@ impl Style for Js {
60
60
  }
61
61
  }
62
62
 
63
- pub fn render_array(ctx: &ArrayCtx<'_>) -> String {
63
+ pub(super) fn render_array(ctx: &ArrayCtx<'_>) -> String {
64
64
  render_array_with::<Js>(ctx)
65
65
  }
66
66
 
67
- pub fn render_object(ctx: &ObjectCtx<'_>) -> String {
67
+ pub(super) fn render_object(ctx: &ObjectCtx<'_>) -> String {
68
68
  render_object_with::<Js>(ctx)
69
69
  }
@@ -13,10 +13,10 @@ impl Style for Json {
13
13
  }
14
14
  }
15
15
 
16
- pub fn render_array(ctx: &ArrayCtx<'_>) -> String {
16
+ pub(super) fn render_array(ctx: &ArrayCtx<'_>) -> String {
17
17
  render_array_with::<Json>(ctx)
18
18
  }
19
19
 
20
- pub fn render_object(ctx: &ObjectCtx<'_>) -> String {
20
+ pub(super) fn render_object(ctx: &ObjectCtx<'_>) -> String {
21
21
  render_object_with::<Json>(ctx)
22
22
  }
@@ -1,9 +1,9 @@
1
1
  use crate::OutputTemplate;
2
2
 
3
- pub mod core;
4
- pub mod js;
5
- pub mod json;
6
- pub mod pseudo;
3
+ mod core;
4
+ mod js;
5
+ mod json;
6
+ mod pseudo;
7
7
 
8
8
  pub struct ArrayCtx<'a> {
9
9
  pub children: Vec<(usize, String)>,
@@ -39,10 +39,10 @@ impl Style for Pseudo {
39
39
  }
40
40
  }
41
41
 
42
- pub fn render_array(ctx: &ArrayCtx<'_>) -> String {
42
+ pub(super) fn render_array(ctx: &ArrayCtx<'_>) -> String {
43
43
  render_array_with::<Pseudo>(ctx)
44
44
  }
45
45
 
46
- pub fn render_object(ctx: &ObjectCtx<'_>) -> String {
46
+ pub(super) fn render_object(ctx: &ObjectCtx<'_>) -> String {
47
47
  render_object_with::<Pseudo>(ctx)
48
48
  }
headson-0.2.3/src/main.rs DELETED
@@ -1,174 +0,0 @@
1
- use std::io::{self, Read};
2
- use std::path::PathBuf;
3
-
4
- use anyhow::{Context, Result};
5
- use clap::{Parser, ValueEnum};
6
-
7
- #[derive(Parser, Debug)]
8
- #[command(
9
- name = "headson",
10
- version,
11
- about = "Get a small but useful preview of a JSON file"
12
- )]
13
- struct Cli {
14
- #[arg(short = 'n', long = "budget", conflicts_with = "global_budget")]
15
- budget: Option<usize>,
16
- #[arg(short = 'f', long = "template", value_enum, default_value_t = Template::Pseudo)]
17
- template: Template,
18
- #[arg(long = "indent", default_value = " ")]
19
- indent: String,
20
- #[arg(long = "no-space", default_value_t = false)]
21
- no_space: bool,
22
- #[arg(
23
- long = "no-newline",
24
- default_value_t = false,
25
- help = "Do not add newlines in the output"
26
- )]
27
- no_newline: bool,
28
- #[arg(
29
- short = 'm',
30
- long = "compact",
31
- default_value_t = false,
32
- conflicts_with_all = ["no_space", "no_newline", "indent"],
33
- help = "Compact output with no added whitespace. Not very human-readable."
34
- )]
35
- compact: bool,
36
- #[arg(
37
- long = "string-cap",
38
- default_value_t = 500,
39
- help = "Maximum string length to display"
40
- )]
41
- string_cap: usize,
42
- #[arg(
43
- short = 'N',
44
- long = "global-budget",
45
- value_name = "BYTES",
46
- conflicts_with = "budget",
47
- help = "Total output budget across all inputs; useful to keep multiple files within a fixed overall output size (may omit entire files)."
48
- )]
49
- global_budget: Option<usize>,
50
- #[arg(
51
- value_name = "INPUT",
52
- value_hint = clap::ValueHint::FilePath,
53
- num_args = 0..,
54
- help = "Optional file paths. If omitted, reads JSON from stdin. Multiple input files are supported."
55
- )]
56
- inputs: Vec<PathBuf>,
57
- }
58
-
59
- #[derive(Copy, Clone, Debug, ValueEnum)]
60
- enum Template {
61
- Json,
62
- Pseudo,
63
- Js,
64
- }
65
-
66
- fn main() -> Result<()> {
67
- let cli = Cli::parse();
68
-
69
- let render_cfg = get_render_config_from(&cli);
70
- let input_count = if cli.inputs.is_empty() {
71
- 1
72
- } else {
73
- cli.inputs.len()
74
- };
75
- let effective_budget = if let Some(g) = cli.global_budget {
76
- g
77
- } else {
78
- let per_file = cli.budget.unwrap_or(500);
79
- per_file.saturating_mul(input_count)
80
- };
81
- // Derive a per-file baseline for priority heuristics to avoid over-pruning.
82
- let per_file_for_priority = (effective_budget / input_count).max(1);
83
- let priority_cfg = get_priority_config(per_file_for_priority, &cli);
84
-
85
- let output = if cli.inputs.len() <= 1 {
86
- let input_bytes = get_input_single(&cli.inputs)?;
87
- headson::headson(
88
- input_bytes,
89
- &render_cfg,
90
- &priority_cfg,
91
- effective_budget,
92
- )?
93
- } else {
94
- let inputs = get_input_many(&cli.inputs)?;
95
- headson::headson_many(
96
- inputs,
97
- &render_cfg,
98
- &priority_cfg,
99
- effective_budget,
100
- )?
101
- };
102
- println!("{output}");
103
-
104
- Ok(())
105
- }
106
-
107
- fn get_input_single(paths: &[PathBuf]) -> Result<Vec<u8>> {
108
- // Read input from first file path when provided, otherwise from stdin.
109
- if let Some(path) = paths.first() {
110
- std::fs::read(path).with_context(|| {
111
- format!("failed to read input file: {}", path.display())
112
- })
113
- } else {
114
- let mut buf = Vec::new();
115
- io::stdin()
116
- .read_to_end(&mut buf)
117
- .context("failed to read from stdin")?;
118
- Ok(buf)
119
- }
120
- }
121
-
122
- fn get_input_many(paths: &[PathBuf]) -> Result<Vec<(String, Vec<u8>)>> {
123
- let mut out: Vec<(String, Vec<u8>)> = Vec::with_capacity(paths.len());
124
- for path in paths.iter() {
125
- let bytes = std::fs::read(path).with_context(|| {
126
- format!("failed to read input file: {}", path.display())
127
- })?;
128
- out.push((path.display().to_string(), bytes));
129
- }
130
- Ok(out)
131
- }
132
-
133
- fn get_render_config_from(cli: &Cli) -> headson::RenderConfig {
134
- let template = match cli.template {
135
- Template::Json => headson::OutputTemplate::Json,
136
- Template::Pseudo => headson::OutputTemplate::Pseudo,
137
- Template::Js => headson::OutputTemplate::Js,
138
- };
139
- let space = if cli.compact || cli.no_space { "" } else { " " }.to_string();
140
- let newline = if cli.compact || cli.no_newline {
141
- ""
142
- } else {
143
- "\n"
144
- }
145
- .to_string();
146
- let indent_unit = if cli.compact {
147
- String::new()
148
- } else {
149
- cli.indent.clone()
150
- };
151
-
152
- headson::RenderConfig {
153
- template,
154
- indent_unit,
155
- space,
156
- newline,
157
- }
158
- }
159
-
160
- fn get_priority_config(
161
- per_file_budget: usize,
162
- cli: &Cli,
163
- ) -> headson::PriorityConfig {
164
- // Optimization: derive a conservative per‑array expansion cap from the output
165
- // budget to avoid allocating/walking items that could never appear in the
166
- // final preview. As a simple lower bound, an array of N items needs ~2*N
167
- // bytes to render (item plus comma), so we cap per‑array expansion at
168
- // budget/2. This prunes unnecessary work on large inputs without changing
169
- // output semantics.
170
- headson::PriorityConfig {
171
- max_string_graphemes: cli.string_cap,
172
- array_max_items: (per_file_budget / 2).max(1),
173
- }
174
- }
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
File without changes
File without changes
File without changes