headson 0.7.0__tar.gz → 0.7.1__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 (74) hide show
  1. {headson-0.7.0 → headson-0.7.1}/Cargo.lock +1 -1
  2. {headson-0.7.0 → headson-0.7.1}/Cargo.toml +1 -1
  3. {headson-0.7.0 → headson-0.7.1}/PKG-INFO +4 -2
  4. {headson-0.7.0 → headson-0.7.1}/README.md +3 -1
  5. headson-0.7.1/docs/assets/tapes/demo.gif +0 -0
  6. {headson-0.7.0 → headson-0.7.1}/pyproject.toml +1 -1
  7. {headson-0.7.0 → headson-0.7.1}/python/Cargo.lock +2 -2
  8. {headson-0.7.0 → headson-0.7.1}/python/Cargo.toml +1 -1
  9. {headson-0.7.0 → headson-0.7.1}/python/src/lib.rs +1 -0
  10. {headson-0.7.0 → headson-0.7.1}/src/ingest/formats/text/mod.rs +1 -0
  11. {headson-0.7.0 → headson-0.7.1}/src/lib.rs +59 -44
  12. {headson-0.7.0 → headson-0.7.1}/src/main.rs +7 -0
  13. {headson-0.7.0 → headson-0.7.1}/src/serialization/fileset.rs +50 -15
  14. {headson-0.7.0 → headson-0.7.1}/src/serialization/mod.rs +19 -0
  15. {headson-0.7.0 → headson-0.7.1}/src/serialization/types.rs +2 -0
  16. {headson-0.7.0 → headson-0.7.1}/src/utils/measure.rs +15 -3
  17. headson-0.7.0/docs/assets/tapes/demo.gif +0 -0
  18. {headson-0.7.0 → headson-0.7.1}/docs/assets/algorithm.svg +0 -0
  19. {headson-0.7.0 → headson-0.7.1}/docs/assets/logo.png +0 -0
  20. {headson-0.7.0 → headson-0.7.1}/docs/assets/logo.svg +0 -0
  21. {headson-0.7.0 → headson-0.7.1}/python/README.md +0 -0
  22. {headson-0.7.0 → headson-0.7.1}/python/headson/__init__.py +0 -0
  23. {headson-0.7.0 → headson-0.7.1}/src/debug.rs +0 -0
  24. {headson-0.7.0 → headson-0.7.1}/src/format.rs +0 -0
  25. {headson-0.7.0 → headson-0.7.1}/src/ingest/fileset.rs +0 -0
  26. {headson-0.7.0 → headson-0.7.1}/src/ingest/formats/json/builder.rs +0 -0
  27. {headson-0.7.0 → headson-0.7.1}/src/ingest/formats/json/mod.rs +0 -0
  28. {headson-0.7.0 → headson-0.7.1}/src/ingest/formats/json/samplers/default.rs +0 -0
  29. {headson-0.7.0 → headson-0.7.1}/src/ingest/formats/json/samplers/head.rs +0 -0
  30. {headson-0.7.0 → headson-0.7.1}/src/ingest/formats/json/samplers/mod.rs +0 -0
  31. {headson-0.7.0 → headson-0.7.1}/src/ingest/formats/json/samplers/tail.rs +0 -0
  32. {headson-0.7.0 → headson-0.7.1}/src/ingest/formats/mod.rs +0 -0
  33. {headson-0.7.0 → headson-0.7.1}/src/ingest/formats/yaml/mod.rs +0 -0
  34. {headson-0.7.0 → headson-0.7.1}/src/ingest/mod.rs +0 -0
  35. {headson-0.7.0 → headson-0.7.1}/src/ingest/sampling/mod.rs +0 -0
  36. {headson-0.7.0 → headson-0.7.1}/src/order/build.rs +0 -0
  37. {headson-0.7.0 → headson-0.7.1}/src/order/mod.rs +0 -0
  38. {headson-0.7.0 → headson-0.7.1}/src/order/scoring.rs +0 -0
  39. {headson-0.7.0 → headson-0.7.1}/src/order/snapshots/headson__order__build__tests__order_empty_array_order.snap +0 -0
  40. {headson-0.7.0 → headson-0.7.1}/src/order/snapshots/headson__order__build__tests__order_single_string_array_order.snap +0 -0
  41. {headson-0.7.0 → headson-0.7.1}/src/order/types.rs +0 -0
  42. {headson-0.7.0 → headson-0.7.1}/src/serialization/color.rs +0 -0
  43. {headson-0.7.0 → headson-0.7.1}/src/serialization/highlight.rs +0 -0
  44. {headson-0.7.0 → headson-0.7.1}/src/serialization/output.rs +0 -0
  45. {headson-0.7.0 → headson-0.7.1}/src/serialization/snapshots/headson__serialization__tests__arena_render_empty.snap +0 -0
  46. {headson-0.7.0 → headson-0.7.1}/src/serialization/snapshots/headson__serialization__tests__arena_render_empty_yaml.snap +0 -0
  47. {headson-0.7.0 → headson-0.7.1}/src/serialization/snapshots/headson__serialization__tests__arena_render_single.snap +0 -0
  48. {headson-0.7.0 → headson-0.7.1}/src/serialization/snapshots/headson__serialization__tests__arena_render_single_yaml.snap +0 -0
  49. {headson-0.7.0 → headson-0.7.1}/src/serialization/snapshots/headson__serialization__tests__array_internal_gaps_yaml.snap +0 -0
  50. {headson-0.7.0 → headson-0.7.1}/src/serialization/snapshots/headson__serialization__tests__array_omitted_js_head.snap +0 -0
  51. {headson-0.7.0 → headson-0.7.1}/src/serialization/snapshots/headson__serialization__tests__array_omitted_js_tail.snap +0 -0
  52. {headson-0.7.0 → headson-0.7.1}/src/serialization/snapshots/headson__serialization__tests__array_omitted_pseudo_head.snap +0 -0
  53. {headson-0.7.0 → headson-0.7.1}/src/serialization/snapshots/headson__serialization__tests__array_omitted_pseudo_tail.snap +0 -0
  54. {headson-0.7.0 → headson-0.7.1}/src/serialization/snapshots/headson__serialization__tests__array_omitted_yaml_head.snap +0 -0
  55. {headson-0.7.0 → headson-0.7.1}/src/serialization/snapshots/headson__serialization__tests__array_omitted_yaml_tail.snap +0 -0
  56. {headson-0.7.0 → headson-0.7.1}/src/serialization/snapshots/headson__serialization__tests__inline_open_array_in_object_json.snap +0 -0
  57. {headson-0.7.0 → headson-0.7.1}/src/serialization/snapshots/headson__serialization__tests__inline_open_array_in_object_yaml.snap +0 -0
  58. {headson-0.7.0 → headson-0.7.1}/src/serialization/templates/code.rs +0 -0
  59. {headson-0.7.0 → headson-0.7.1}/src/serialization/templates/core.rs +0 -0
  60. {headson-0.7.0 → headson-0.7.1}/src/serialization/templates/js.rs +0 -0
  61. {headson-0.7.0 → headson-0.7.1}/src/serialization/templates/json.rs +0 -0
  62. {headson-0.7.0 → headson-0.7.1}/src/serialization/templates/mod.rs +0 -0
  63. {headson-0.7.0 → headson-0.7.1}/src/serialization/templates/pseudo.rs +0 -0
  64. {headson-0.7.0 → headson-0.7.1}/src/serialization/templates/text.rs +0 -0
  65. {headson-0.7.0 → headson-0.7.1}/src/serialization/templates/yaml.rs +0 -0
  66. {headson-0.7.0 → headson-0.7.1}/src/utils/extensions.rs +0 -0
  67. {headson-0.7.0 → headson-0.7.1}/src/utils/graph.rs +0 -0
  68. {headson-0.7.0 → headson-0.7.1}/src/utils/json.rs +0 -0
  69. {headson-0.7.0 → headson-0.7.1}/src/utils/mod.rs +0 -0
  70. {headson-0.7.0 → headson-0.7.1}/src/utils/search.rs +0 -0
  71. {headson-0.7.0 → headson-0.7.1}/src/utils/text.rs +0 -0
  72. {headson-0.7.0 → headson-0.7.1}/src/utils/tree_arena.rs +0 -0
  73. {headson-0.7.0 → headson-0.7.1}/tests/fixtures/json/JSONTestSuite/LICENSE +0 -0
  74. {headson-0.7.0 → headson-0.7.1}/tests/fixtures/json/JSONTestSuite/README.md +0 -0
@@ -369,7 +369,7 @@ dependencies = [
369
369
 
370
370
  [[package]]
371
371
  name = "headson"
372
- version = "0.7.0"
372
+ version = "0.7.1"
373
373
  dependencies = [
374
374
  "anyhow",
375
375
  "assert_cmd",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "headson"
3
- version = "0.7.0"
3
+ version = "0.7.1"
4
4
  edition = "2024"
5
5
  description = "Budget‑constrained JSON preview renderer"
6
6
  readme = "README.md"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: headson
3
- Version: 0.7.0
3
+ Version: 0.7.1
4
4
  Classifier: Programming Language :: Python
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Classifier: Programming Language :: Rust
@@ -83,6 +83,7 @@ Common flags:
83
83
  - `-i, --input-format <json|yaml|text>`: ingestion format (default: `json`). For filesets in `auto` format, ingestion is chosen by extensions.
84
84
  - `-m, --compact`: no indentation, no spaces, no newlines
85
85
  - `--no-newline`: single line output
86
+ - `--no-header`: suppress fileset section headers (useful when embedding output in scripts)
86
87
  - `--no-space`: no space after `:` in objects
87
88
  - `--indent <STR>`: indentation unit (default: two spaces)
88
89
  - `--string-cap <N>`: max graphemes to consider per string (default: 500)
@@ -92,7 +93,7 @@ Common flags:
92
93
  Notes:
93
94
 
94
95
  - Multiple inputs:
95
- - With newlines enabled, file sections are rendered with human‑readable headers. In compact/single‑line modes, headers are omitted.
96
+ - With newlines enabled, file sections are rendered with human‑readable headers (pass `--no-header` to suppress them). In compact/single‑line modes, headers are omitted.
96
97
  - In `--format auto`, each file uses its own best format: JSON family for `.json`, YAML for `.yaml`/`.yml`.
97
98
  - Unknown extensions are treated as Text (raw lines) — safe for logs and `.txt` files.
98
99
  - `--global-bytes` may truncate or omit entire files to respect the total budget.
@@ -114,6 +115,7 @@ Notes:
114
115
  - Caps the number of lines in the output.
115
116
  - Incompatible with `--no-newline`.
116
117
  - Multiple inputs: defaults to `<LINES> * number_of_inputs`; `--global-lines` caps the total.
118
+ - Fileset headers, blank separators, and summary lines do not count toward the line cap; only actual content lines are considered.
117
119
 
118
120
  - Interactions and precedence
119
121
  - All active budgets are enforced simultaneously. The render must satisfy all of: bytes (if set), chars (if set), and lines (if set). The strictest cap wins.
@@ -69,6 +69,7 @@ Common flags:
69
69
  - `-i, --input-format <json|yaml|text>`: ingestion format (default: `json`). For filesets in `auto` format, ingestion is chosen by extensions.
70
70
  - `-m, --compact`: no indentation, no spaces, no newlines
71
71
  - `--no-newline`: single line output
72
+ - `--no-header`: suppress fileset section headers (useful when embedding output in scripts)
72
73
  - `--no-space`: no space after `:` in objects
73
74
  - `--indent <STR>`: indentation unit (default: two spaces)
74
75
  - `--string-cap <N>`: max graphemes to consider per string (default: 500)
@@ -78,7 +79,7 @@ Common flags:
78
79
  Notes:
79
80
 
80
81
  - Multiple inputs:
81
- - With newlines enabled, file sections are rendered with human‑readable headers. In compact/single‑line modes, headers are omitted.
82
+ - With newlines enabled, file sections are rendered with human‑readable headers (pass `--no-header` to suppress them). In compact/single‑line modes, headers are omitted.
82
83
  - In `--format auto`, each file uses its own best format: JSON family for `.json`, YAML for `.yaml`/`.yml`.
83
84
  - Unknown extensions are treated as Text (raw lines) — safe for logs and `.txt` files.
84
85
  - `--global-bytes` may truncate or omit entire files to respect the total budget.
@@ -100,6 +101,7 @@ Notes:
100
101
  - Caps the number of lines in the output.
101
102
  - Incompatible with `--no-newline`.
102
103
  - Multiple inputs: defaults to `<LINES> * number_of_inputs`; `--global-lines` caps the total.
104
+ - Fileset headers, blank separators, and summary lines do not count toward the line cap; only actual content lines are considered.
103
105
 
104
106
  - Interactions and precedence
105
107
  - All active budgets are enforced simultaneously. The render must satisfy all of: bytes (if set), chars (if set), and lines (if set). The strictest cap wins.
Binary file
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "headson"
7
- version = "0.7.0"
7
+ version = "0.7.1"
8
8
  description = "Budget‑constrained JSON preview renderer (Python bindings)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -291,7 +291,7 @@ dependencies = [
291
291
 
292
292
  [[package]]
293
293
  name = "headson"
294
- version = "0.7.0"
294
+ version = "0.7.1"
295
295
  dependencies = [
296
296
  "anyhow",
297
297
  "clap",
@@ -307,7 +307,7 @@ dependencies = [
307
307
 
308
308
  [[package]]
309
309
  name = "headson-python"
310
- version = "0.7.0"
310
+ version = "0.7.1"
311
311
  dependencies = [
312
312
  "anyhow",
313
313
  "headson",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "headson-python"
3
- version = "0.7.0"
3
+ version = "0.7.1"
4
4
  edition = "2021"
5
5
  publish = false
6
6
  readme = "README.md"
@@ -63,6 +63,7 @@ fn render_config_with_sampler(
63
63
  string_free_prefix_graphemes: None,
64
64
  debug: false,
65
65
  primary_source_name: None,
66
+ show_fileset_headers: true,
66
67
  })
67
68
  }
68
69
 
@@ -494,6 +494,7 @@ mod tests {
494
494
  string_free_prefix_graphemes: None,
495
495
  debug: false,
496
496
  primary_source_name: None,
497
+ show_fileset_headers: true,
497
498
  };
498
499
  let prio = PriorityConfig::new(100, 100);
499
500
  (cfg, prio)
@@ -187,52 +187,20 @@ fn find_largest_render_under_budgets(
187
187
  if total == 0 {
188
188
  return String::new();
189
189
  }
190
- // Each included node contributes at least some output; cap hi by budget.
191
- let lo = 1usize;
192
- // For the upper bound, when a byte budget is present, we can safely cap by it;
193
- // otherwise, cap by total.
194
- let hi = match budgets.byte_budget {
195
- Some(c) => total.min(c.max(1)),
196
- None => total,
197
- };
198
- // Reuse render-inclusion flags across render attempts to avoid clearing the vector.
199
- // A node participates in the current render attempt when inclusion_flags[id] == render_set_id.
200
- let mut inclusion_flags: Vec<u32> = vec![0; total];
201
- // Each render attempt bumps this non-zero identifier to create a fresh inclusion set.
202
- let mut render_set_id: u32 = 1;
203
- // Measure length without color so ANSI escapes do not count toward the
204
- // byte budget. Then render once more with the requested color setting.
205
- let mut best_k: Option<usize> = None;
190
+ let root_is_fileset = order_build
191
+ .object_type
192
+ .get(crate::order::ROOT_PQ_ID)
193
+ .is_some_and(|t| *t == crate::order::ObjectType::Fileset);
206
194
  let mut measure_cfg = config.clone();
207
195
  measure_cfg.color_enabled = false;
208
-
209
- let _ = crate::utils::search::binary_search_max(lo, hi, |mid| {
210
- let s = crate::serialization::render_top_k(
211
- order_build,
212
- mid,
213
- &mut inclusion_flags,
214
- render_set_id,
215
- &measure_cfg,
216
- );
217
- render_set_id = render_set_id.wrapping_add(1).max(1);
218
- // Measure output using a unified stats helper and enforce
219
- // all provided caps (chars and/or lines).
220
- let stats = crate::utils::measure::count_output_stats(
221
- &s,
222
- budgets.char_budget.is_some(),
223
- );
224
- let fits_bytes = budgets.byte_budget.is_none_or(|c| stats.bytes <= c);
225
- let fits_chars = budgets.char_budget.is_none_or(|c| stats.chars <= c);
226
- let fits_lines = budgets.line_budget.is_none_or(|l| stats.lines <= l);
227
- if fits_bytes && fits_chars && fits_lines {
228
- best_k = Some(mid);
229
- true
230
- } else {
231
- false
232
- }
233
- });
234
-
235
- let k = best_k.unwrap_or(1);
196
+ if budgets.line_budget.is_some()
197
+ && config.show_fileset_headers
198
+ && root_is_fileset
199
+ {
200
+ measure_cfg.show_fileset_headers = false;
201
+ }
202
+ let (k, mut inclusion_flags, render_set_id) =
203
+ select_best_k(order_build, &measure_cfg, budgets);
236
204
 
237
205
  // Prepare final inclusion set once and optionally build debug JSON.
238
206
  crate::serialization::prepare_render_set_top_k_and_ancestors(
@@ -295,6 +263,53 @@ fn find_largest_render_under_budgets(
295
263
  )
296
264
  }
297
265
 
266
+ fn select_best_k(
267
+ order_build: &PriorityOrder,
268
+ measure_cfg: &RenderConfig,
269
+ budgets: Budgets,
270
+ ) -> (usize, Vec<u32>, u32) {
271
+ let total = order_build.total_nodes;
272
+ // Each included node contributes at least some output; cap hi by budget.
273
+ let lo = 1usize;
274
+ // For the upper bound, when a byte budget is present, we can safely cap by it;
275
+ // otherwise, cap by total.
276
+ let hi = match budgets.byte_budget {
277
+ Some(c) => total.min(c.max(1)),
278
+ None => total,
279
+ };
280
+ // Reuse render-inclusion flags across render attempts to avoid clearing the vector.
281
+ let mut inclusion_flags: Vec<u32> = vec![0; total];
282
+ // Each render attempt bumps this non-zero identifier to create a fresh inclusion set.
283
+ let mut render_set_id: u32 = 1;
284
+ let mut best_k: Option<usize> = None;
285
+ let measure_chars = budgets.char_budget.is_some();
286
+ let _ = crate::utils::search::binary_search_max(lo, hi, |mid| {
287
+ let current_render_id = render_set_id;
288
+ let s = crate::serialization::render_top_k(
289
+ order_build,
290
+ mid,
291
+ &mut inclusion_flags,
292
+ current_render_id,
293
+ measure_cfg,
294
+ );
295
+ let stats =
296
+ crate::utils::measure::count_output_stats(&s, measure_chars);
297
+ let fits_bytes = budgets.byte_budget.is_none_or(|c| stats.bytes <= c);
298
+ let fits_chars = budgets.char_budget.is_none_or(|c| stats.chars <= c);
299
+ let fits_lines =
300
+ budgets.line_budget.is_none_or(|cap| stats.lines <= cap);
301
+ render_set_id = render_set_id.wrapping_add(1).max(1);
302
+ if fits_bytes && fits_chars && fits_lines {
303
+ best_k = Some(mid);
304
+ true
305
+ } else {
306
+ false
307
+ }
308
+ });
309
+ let k = best_k.unwrap_or(1);
310
+ (k, inclusion_flags, render_set_id)
311
+ }
312
+
298
313
  // (removed) render_final helper was inlined to centralize optional debug dump
299
314
 
300
315
  // Optional new public API that accepts both budgets explicitly.
@@ -64,6 +64,12 @@ struct Cli {
64
64
  help = "Do not add newlines in the output. Incompatible with --lines/--global-lines."
65
65
  )]
66
66
  no_newline: bool,
67
+ #[arg(
68
+ long = "no-header",
69
+ default_value_t = false,
70
+ help = "Suppress fileset section headers in the output"
71
+ )]
72
+ no_header: bool,
67
73
  #[arg(
68
74
  short = 'm',
69
75
  long = "compact",
@@ -525,6 +531,7 @@ fn get_render_config_from(cli: &Cli) -> headson::RenderConfig {
525
531
  string_free_prefix_graphemes: None,
526
532
  debug: cli.debug,
527
533
  primary_source_name: None,
534
+ show_fileset_headers: !cli.no_header,
528
535
  }
529
536
  }
530
537
 
@@ -1,6 +1,6 @@
1
1
  use super::RenderScope;
2
2
  use crate::format::Format;
3
- use crate::order::{ObjectType, ROOT_PQ_ID};
3
+ use crate::order::{NodeId, ObjectType, ROOT_PQ_ID};
4
4
  use crate::serialization::types::OutputTemplate;
5
5
 
6
6
  impl<'a> RenderScope<'a> {
@@ -19,26 +19,68 @@ impl<'a> RenderScope<'a> {
19
19
  }
20
20
 
21
21
  fn render_fileset_sections(&mut self, depth: usize) -> String {
22
- let mut out = String::new();
23
22
  let Some(children_ids) = self.order.children.get(ROOT_PQ_ID) else {
24
- return out;
23
+ return String::new();
25
24
  };
25
+ let show_headers = self.should_render_fileset_headers();
26
+ let mut out = String::new();
27
+ let kept = self.render_fileset_children(
28
+ children_ids,
29
+ depth,
30
+ show_headers,
31
+ &mut out,
32
+ );
33
+ if show_headers {
34
+ self.render_fileset_summary(children_ids, depth, kept, &mut out);
35
+ }
36
+ out
37
+ }
38
+
39
+ fn fileset_push_section_gap(&self, out: &mut String) {
40
+ let nl = &self.config.newline;
41
+ out.push_str(nl);
42
+ out.push_str(nl);
43
+ }
44
+
45
+ fn should_render_fileset_headers(&self) -> bool {
46
+ self.config.show_fileset_headers && !self.config.newline.is_empty()
47
+ }
48
+
49
+ fn render_fileset_children(
50
+ &mut self,
51
+ children_ids: &[NodeId],
52
+ depth: usize,
53
+ show_headers: bool,
54
+ out: &mut String,
55
+ ) -> usize {
26
56
  let mut kept = 0usize;
27
- for &child_id in children_ids.iter() {
57
+ for &child_id in children_ids {
28
58
  if self.inclusion_flags[child_id.0] != self.render_set_id {
29
59
  continue;
30
60
  }
31
- if kept > 0 {
32
- self.fileset_push_section_gap(&mut out);
61
+ if kept > 0 && show_headers {
62
+ self.fileset_push_section_gap(out);
33
63
  }
34
64
  kept += 1;
35
65
  let raw_key =
36
66
  self.order.nodes[child_id.0].key_in_object().unwrap_or("");
37
- out.push_str(&self.fileset_header_line(depth, raw_key));
67
+ if show_headers {
68
+ out.push_str(&self.fileset_header_line(depth, raw_key));
69
+ }
38
70
  let rendered =
39
71
  self.fileset_render_child(child_id.0, depth, raw_key);
40
72
  out.push_str(&rendered);
41
73
  }
74
+ kept
75
+ }
76
+
77
+ fn render_fileset_summary(
78
+ &self,
79
+ children_ids: &[NodeId],
80
+ depth: usize,
81
+ kept: usize,
82
+ out: &mut String,
83
+ ) {
42
84
  let total = self
43
85
  .order
44
86
  .metrics
@@ -46,16 +88,9 @@ impl<'a> RenderScope<'a> {
46
88
  .and_then(|m| m.object_len)
47
89
  .unwrap_or(children_ids.len());
48
90
  if total > kept && !self.config.newline.is_empty() {
49
- self.fileset_push_section_gap(&mut out);
91
+ self.fileset_push_section_gap(out);
50
92
  out.push_str(&self.fileset_summary_line(depth, total - kept));
51
93
  }
52
- out
53
- }
54
-
55
- fn fileset_push_section_gap(&self, out: &mut String) {
56
- let nl = &self.config.newline;
57
- out.push_str(nl);
58
- out.push_str(nl);
59
94
  }
60
95
 
61
96
  fn fileset_header_line(&self, depth: usize, key: &str) -> String {
@@ -936,6 +936,7 @@ mod tests {
936
936
  string_free_prefix_graphemes: None,
937
937
  debug: false,
938
938
  primary_source_name: None,
939
+ show_fileset_headers: true,
939
940
  },
940
941
  );
941
942
  assert_snapshot!("arena_render_empty", out);
@@ -974,6 +975,7 @@ mod tests {
974
975
  string_free_prefix_graphemes: None,
975
976
  debug: false,
976
977
  primary_source_name: None,
978
+ show_fileset_headers: true,
977
979
  },
978
980
  );
979
981
  // Sanity: output should contain CRLF newlines and render the object child across lines.
@@ -1014,6 +1016,7 @@ mod tests {
1014
1016
  string_free_prefix_graphemes: None,
1015
1017
  debug: false,
1016
1018
  primary_source_name: None,
1019
+ show_fileset_headers: true,
1017
1020
  },
1018
1021
  );
1019
1022
  assert_snapshot!("arena_render_single", out);
@@ -1055,6 +1058,7 @@ mod tests {
1055
1058
  string_free_prefix_graphemes: None,
1056
1059
  debug: false,
1057
1060
  primary_source_name: None,
1061
+ show_fileset_headers: true,
1058
1062
  },
1059
1063
  );
1060
1064
  assert_snapshot!("array_omitted_pseudo_head", out_head);
@@ -1077,6 +1081,7 @@ mod tests {
1077
1081
  string_free_prefix_graphemes: None,
1078
1082
  debug: false,
1079
1083
  primary_source_name: None,
1084
+ show_fileset_headers: true,
1080
1085
  },
1081
1086
  );
1082
1087
  assert_snapshot!("array_omitted_pseudo_tail", out_tail);
@@ -1116,6 +1121,7 @@ mod tests {
1116
1121
  string_free_prefix_graphemes: None,
1117
1122
  debug: false,
1118
1123
  primary_source_name: None,
1124
+ show_fileset_headers: true,
1119
1125
  },
1120
1126
  );
1121
1127
  assert_snapshot!("array_omitted_js_head", out_head);
@@ -1137,6 +1143,7 @@ mod tests {
1137
1143
  string_free_prefix_graphemes: None,
1138
1144
  debug: false,
1139
1145
  primary_source_name: None,
1146
+ show_fileset_headers: true,
1140
1147
  },
1141
1148
  );
1142
1149
  assert_snapshot!("array_omitted_js_tail", out_tail);
@@ -1176,6 +1183,7 @@ mod tests {
1176
1183
  string_free_prefix_graphemes: None,
1177
1184
  debug: false,
1178
1185
  primary_source_name: None,
1186
+ show_fileset_headers: true,
1179
1187
  },
1180
1188
  );
1181
1189
  assert_yaml_valid(&out_head);
@@ -1198,6 +1206,7 @@ mod tests {
1198
1206
  string_free_prefix_graphemes: None,
1199
1207
  debug: false,
1200
1208
  primary_source_name: None,
1209
+ show_fileset_headers: true,
1201
1210
  },
1202
1211
  );
1203
1212
  assert_yaml_valid(&out_tail);
@@ -1234,6 +1243,7 @@ mod tests {
1234
1243
  string_free_prefix_graphemes: None,
1235
1244
  debug: false,
1236
1245
  primary_source_name: None,
1246
+ show_fileset_headers: true,
1237
1247
  },
1238
1248
  );
1239
1249
  assert_yaml_valid(&out);
@@ -1270,6 +1280,7 @@ mod tests {
1270
1280
  string_free_prefix_graphemes: None,
1271
1281
  debug: false,
1272
1282
  primary_source_name: None,
1283
+ show_fileset_headers: true,
1273
1284
  },
1274
1285
  );
1275
1286
  assert_yaml_valid(&out);
@@ -1304,6 +1315,7 @@ mod tests {
1304
1315
  string_free_prefix_graphemes: None,
1305
1316
  debug: false,
1306
1317
  primary_source_name: None,
1318
+ show_fileset_headers: true,
1307
1319
  },
1308
1320
  );
1309
1321
  assert_yaml_valid(&out);
@@ -1366,6 +1378,7 @@ mod tests {
1366
1378
  string_free_prefix_graphemes: None,
1367
1379
  debug: false,
1368
1380
  primary_source_name: None,
1381
+ show_fileset_headers: true,
1369
1382
  },
1370
1383
  );
1371
1384
  assert_yaml_valid(&out);
@@ -1439,6 +1452,7 @@ mod tests {
1439
1452
  string_free_prefix_graphemes: None,
1440
1453
  debug: false,
1441
1454
  primary_source_name: None,
1455
+ show_fileset_headers: true,
1442
1456
  },
1443
1457
  );
1444
1458
  // Expect the first 5 characters plus an ellipsis, as a valid JSON string literal.
@@ -1475,6 +1489,7 @@ mod tests {
1475
1489
  string_free_prefix_graphemes: None,
1476
1490
  debug: false,
1477
1491
  primary_source_name: None,
1492
+ show_fileset_headers: true,
1478
1493
  },
1479
1494
  );
1480
1495
  assert_yaml_valid(&out);
@@ -1518,6 +1533,7 @@ mod tests {
1518
1533
  string_free_prefix_graphemes: None,
1519
1534
  debug: false,
1520
1535
  primary_source_name: None,
1536
+ show_fileset_headers: true,
1521
1537
  };
1522
1538
  let scope = RenderScope {
1523
1539
  order: &build,
@@ -1560,6 +1576,7 @@ mod tests {
1560
1576
  string_free_prefix_graphemes: None,
1561
1577
  debug: false,
1562
1578
  primary_source_name: None,
1579
+ show_fileset_headers: true,
1563
1580
  },
1564
1581
  );
1565
1582
  assert_snapshot!("inline_open_array_in_object_json", out);
@@ -1597,6 +1614,7 @@ mod tests {
1597
1614
  string_free_prefix_graphemes: None,
1598
1615
  debug: false,
1599
1616
  primary_source_name: None,
1617
+ show_fileset_headers: true,
1600
1618
  },
1601
1619
  );
1602
1620
  // Should be a valid JS object with one property and an omitted summary.
@@ -1649,6 +1667,7 @@ mod tests {
1649
1667
  string_free_prefix_graphemes: None,
1650
1668
  debug: false,
1651
1669
  primary_source_name: None,
1670
+ show_fileset_headers: true,
1652
1671
  }
1653
1672
  }
1654
1673
 
@@ -43,6 +43,8 @@ pub struct RenderConfig {
43
43
  // a single logical input outside of filesets. Used by code-specific
44
44
  // features such as syntax highlighting.
45
45
  pub primary_source_name: Option<String>,
46
+ // When false, suppress fileset section headers and summary lines.
47
+ pub show_fileset_headers: bool,
46
48
  }
47
49
 
48
50
  #[derive(Copy, Clone, Debug, Eq, PartialEq)]
@@ -10,8 +10,16 @@ fn count_lines_from_bytes(b: &[u8]) -> usize {
10
10
  if b.is_empty() {
11
11
  return 0;
12
12
  }
13
- let mut i = 0usize;
13
+ let mut lines = count_line_breaks(b).saturating_add(1);
14
+ if ends_with_break(b) && lines > 0 {
15
+ lines -= 1;
16
+ }
17
+ lines
18
+ }
19
+
20
+ fn count_line_breaks(b: &[u8]) -> usize {
14
21
  let mut breaks = 0usize;
22
+ let mut i = 0usize;
15
23
  while i < b.len() {
16
24
  match b[i] {
17
25
  b'\n' => {
@@ -21,7 +29,7 @@ fn count_lines_from_bytes(b: &[u8]) -> usize {
21
29
  b'\r' => {
22
30
  breaks += 1;
23
31
  if i + 1 < b.len() && b[i + 1] == b'\n' {
24
- i += 2; // treat CRLF as a single break
32
+ i += 2;
25
33
  } else {
26
34
  i += 1;
27
35
  }
@@ -29,7 +37,11 @@ fn count_lines_from_bytes(b: &[u8]) -> usize {
29
37
  _ => i += 1,
30
38
  }
31
39
  }
32
- breaks + 1
40
+ breaks
41
+ }
42
+
43
+ fn ends_with_break(b: &[u8]) -> bool {
44
+ b.ends_with(b"\n") || (b.ends_with(b"\r") && !b.ends_with(b"\r\n"))
33
45
  }
34
46
 
35
47
  /// Count bytes and logical lines in a string, normalizing CRLF/CR/LF.
Binary file
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
File without changes
File without changes