headson 0.3.0__tar.gz → 0.4.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.
- {headson-0.3.0 → headson-0.4.0}/Cargo.lock +1 -1
- {headson-0.3.0 → headson-0.4.0}/Cargo.toml +1 -1
- {headson-0.3.0 → headson-0.4.0}/PKG-INFO +6 -5
- {headson-0.3.0 → headson-0.4.0}/README.md +4 -3
- {headson-0.3.0 → headson-0.4.0}/pyproject.toml +3 -3
- {headson-0.3.0 → headson-0.4.0}/python/Cargo.lock +2 -2
- {headson-0.3.0 → headson-0.4.0}/python/Cargo.toml +2 -2
- {headson-0.3.0 → headson-0.4.0}/python/README.md +10 -2
- {headson-0.3.0 → headson-0.4.0}/src/lib.rs +12 -10
- {headson-0.3.0 → headson-0.4.0}/src/order/build.rs +36 -34
- {headson-0.3.0 → headson-0.4.0}/src/order/types.rs +1 -1
- {headson-0.3.0 → headson-0.4.0}/src/serialization/mod.rs +123 -49
- headson-0.4.0/src/utils/graph.rs +61 -0
- headson-0.3.0/src/utils/graph.rs +0 -54
- {headson-0.3.0 → headson-0.4.0}/JSONTestSuite/LICENSE +0 -0
- {headson-0.3.0 → headson-0.4.0}/JSONTestSuite/README.md +0 -0
- {headson-0.3.0 → headson-0.4.0}/LICENSE +0 -0
- {headson-0.3.0 → headson-0.4.0}/python/headson/__init__.py +0 -0
- {headson-0.3.0 → headson-0.4.0}/python/src/lib.rs +0 -0
- {headson-0.3.0 → headson-0.4.0}/src/json_ingest/builder.rs +0 -0
- {headson-0.3.0 → headson-0.4.0}/src/json_ingest/mod.rs +0 -0
- {headson-0.3.0 → headson-0.4.0}/src/main.rs +0 -0
- {headson-0.3.0 → headson-0.4.0}/src/order/mod.rs +0 -0
- {headson-0.3.0 → headson-0.4.0}/src/order/scoring.rs +0 -0
- {headson-0.3.0 → headson-0.4.0}/src/order/snapshots/headson__order__build__tests__order_empty_array_order.snap +0 -0
- {headson-0.3.0 → headson-0.4.0}/src/order/snapshots/headson__order__build__tests__order_single_string_array_order.snap +0 -0
- {headson-0.3.0 → headson-0.4.0}/src/serialization/snapshots/headson__serialization__tests__arena_render_empty.snap +0 -0
- {headson-0.3.0 → headson-0.4.0}/src/serialization/snapshots/headson__serialization__tests__arena_render_single.snap +0 -0
- {headson-0.3.0 → headson-0.4.0}/src/serialization/templates/core.rs +0 -0
- {headson-0.3.0 → headson-0.4.0}/src/serialization/templates/js.rs +0 -0
- {headson-0.3.0 → headson-0.4.0}/src/serialization/templates/json.rs +0 -0
- {headson-0.3.0 → headson-0.4.0}/src/serialization/templates/mod.rs +0 -0
- {headson-0.3.0 → headson-0.4.0}/src/serialization/templates/pseudo.rs +0 -0
- {headson-0.3.0 → headson-0.4.0}/src/serialization/types.rs +0 -0
- {headson-0.3.0 → headson-0.4.0}/src/snapshots/headson__order__tests__order_empty_array_order.snap +0 -0
- {headson-0.3.0 → headson-0.4.0}/src/snapshots/headson__order__tests__order_single_string_array_order.snap +0 -0
- {headson-0.3.0 → headson-0.4.0}/src/snapshots/headson__order__tests__pq_empty_array_queue.snap +0 -0
- {headson-0.3.0 → headson-0.4.0}/src/snapshots/headson__order__tests__pq_single_string_array_queue.snap +0 -0
- {headson-0.3.0 → headson-0.4.0}/src/snapshots/headson__queue__tests__pq_empty_array_queue.snap +0 -0
- {headson-0.3.0 → headson-0.4.0}/src/snapshots/headson__queue__tests__pq_single_string_array_queue.snap +0 -0
- {headson-0.3.0 → headson-0.4.0}/src/snapshots/headson__tree__tests__build_tree_empty.snap +0 -0
- {headson-0.3.0 → headson-0.4.0}/src/snapshots/headson__tree__tests__build_tree_single.snap +0 -0
- {headson-0.3.0 → headson-0.4.0}/src/utils/json.rs +0 -0
- {headson-0.3.0 → headson-0.4.0}/src/utils/mod.rs +0 -0
- {headson-0.3.0 → headson-0.4.0}/src/utils/search.rs +0 -0
- {headson-0.3.0 → headson-0.4.0}/src/utils/text.rs +0 -0
- {headson-0.3.0 → headson-0.4.0}/src/utils/tree_arena.rs +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: headson
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Classifier: Programming Language :: Python
|
|
5
5
|
Classifier: Programming Language :: Python :: 3
|
|
6
6
|
Classifier: Programming Language :: Rust
|
|
@@ -10,7 +10,7 @@ Provides-Extra: test
|
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Summary: Budget‑constrained JSON preview renderer (Python bindings)
|
|
12
12
|
Keywords: json,preview,summarize,cli,bindings
|
|
13
|
-
Requires-Python: >=3.
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
14
|
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
15
15
|
|
|
16
16
|
# headson
|
|
@@ -72,7 +72,8 @@ Notes:
|
|
|
72
72
|
|
|
73
73
|
- With multiple input files:
|
|
74
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.
|
|
75
|
+
- Pseudo and JS templates render file sections with human-readable headers when newlines are enabled.
|
|
76
|
+
- If you use `--compact` or `--no-newline` (both disable newlines), fileset output falls back to standard inline rendering (no per-file headers) to remain compact.
|
|
76
77
|
- Using `--global-budget` may truncate or omit entire files to respect the total budget.
|
|
77
78
|
- 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
79
|
- 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.
|
|
@@ -134,12 +135,12 @@ headson -n 120 -f json users.json
|
|
|
134
135
|
|
|
135
136
|
A thin Python extension module is available on PyPI as `headson`.
|
|
136
137
|
|
|
137
|
-
- Install: `pip install headson` (
|
|
138
|
+
- Install: `pip install headson` (ABI3 wheels for Python 3.10+ on Linux/macOS/Windows).
|
|
138
139
|
- API:
|
|
139
140
|
- `headson.summarize(text: str, *, template: str = "pseudo", character_budget: int | None = None, tail: bool = False) -> str`
|
|
140
141
|
- `template`: one of `"json" | "pseudo" | "js"`
|
|
141
142
|
- `character_budget`: maximum output size in characters (default: 500)
|
|
142
|
-
|
|
143
|
+
- `tail`: prefer the end of arrays when truncating; strings unaffected. Affects only display templates (`pseudo`/`js`); `json` remains strict.
|
|
143
144
|
|
|
144
145
|
Example:
|
|
145
146
|
|
|
@@ -57,7 +57,8 @@ Notes:
|
|
|
57
57
|
|
|
58
58
|
- With multiple input files:
|
|
59
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.
|
|
60
|
+
- Pseudo and JS templates render file sections with human-readable headers when newlines are enabled.
|
|
61
|
+
- If you use `--compact` or `--no-newline` (both disable newlines), fileset output falls back to standard inline rendering (no per-file headers) to remain compact.
|
|
61
62
|
- Using `--global-budget` may truncate or omit entire files to respect the total budget.
|
|
62
63
|
- 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
64
|
- 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.
|
|
@@ -119,12 +120,12 @@ headson -n 120 -f json users.json
|
|
|
119
120
|
|
|
120
121
|
A thin Python extension module is available on PyPI as `headson`.
|
|
121
122
|
|
|
122
|
-
- Install: `pip install headson` (
|
|
123
|
+
- Install: `pip install headson` (ABI3 wheels for Python 3.10+ on Linux/macOS/Windows).
|
|
123
124
|
- API:
|
|
124
125
|
- `headson.summarize(text: str, *, template: str = "pseudo", character_budget: int | None = None, tail: bool = False) -> str`
|
|
125
126
|
- `template`: one of `"json" | "pseudo" | "js"`
|
|
126
127
|
- `character_budget`: maximum output size in characters (default: 500)
|
|
127
|
-
|
|
128
|
+
- `tail`: prefer the end of arrays when truncating; strings unaffected. Affects only display templates (`pseudo`/`js`); `json` remains strict.
|
|
128
129
|
|
|
129
130
|
Example:
|
|
130
131
|
|
|
@@ -4,10 +4,10 @@ build-backend = "maturin"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "headson"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "Budget‑constrained JSON preview renderer (Python bindings)"
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
11
|
classifiers = [
|
|
12
12
|
"Programming Language :: Python",
|
|
13
13
|
"Programming Language :: Python :: 3",
|
|
@@ -32,7 +32,7 @@ python-source = "python"
|
|
|
32
32
|
dev = [
|
|
33
33
|
"pytest>=8",
|
|
34
34
|
"maturin>=1.7,<2",
|
|
35
|
-
"ruff==0.
|
|
35
|
+
"ruff==0.14.2",
|
|
36
36
|
]
|
|
37
37
|
|
|
38
38
|
[tool.ruff]
|
|
@@ -169,7 +169,7 @@ dependencies = [
|
|
|
169
169
|
|
|
170
170
|
[[package]]
|
|
171
171
|
name = "headson"
|
|
172
|
-
version = "0.
|
|
172
|
+
version = "0.4.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.
|
|
185
|
+
version = "0.4.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.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
publish = false
|
|
6
6
|
readme = "README.md"
|
|
@@ -11,5 +11,5 @@ crate-type = ["cdylib"]
|
|
|
11
11
|
|
|
12
12
|
[dependencies]
|
|
13
13
|
anyhow = "1"
|
|
14
|
-
pyo3 = { version = "0.27", features = ["extension-module"] }
|
|
14
|
+
pyo3 = { version = "0.27", features = ["extension-module", "abi3-py310"] }
|
|
15
15
|
headson_core = { package = "headson", path = ".." }
|
|
@@ -4,19 +4,27 @@ Minimal Python API for the `headson` JSON preview renderer.
|
|
|
4
4
|
|
|
5
5
|
Currently exported function:
|
|
6
6
|
|
|
7
|
-
- `headson.summarize(text: str, *, template: str = "pseudo", character_budget: int | None = None) -> str`
|
|
7
|
+
- `headson.summarize(text: str, *, template: str = "pseudo", character_budget: int | None = None, tail: bool = False) -> str`
|
|
8
8
|
- `template`: one of `"json" | "pseudo" | "js"`.
|
|
9
9
|
- `character_budget`: maximum output size in characters (defaults to 500 if not set).
|
|
10
|
+
- `tail`: prefer the end of arrays when truncating; strings are unaffected. Only affects display templates (`pseudo`/`js`); `json` remains strict JSON with no annotations.
|
|
10
11
|
|
|
11
12
|
Examples:
|
|
12
13
|
|
|
13
14
|
```python
|
|
14
15
|
import headson
|
|
15
16
|
|
|
17
|
+
# Pseudo template with a small budget (structure-aware preview)
|
|
16
18
|
print(headson.summarize('{"a": 1, "b": [1,2,3]}', template="pseudo", character_budget=80))
|
|
19
|
+
|
|
20
|
+
# Strict JSON template preserves valid JSON output
|
|
17
21
|
print(headson.summarize('{"a": 1, "b": {"c": 2}}', template="json", character_budget=10_000))
|
|
22
|
+
|
|
23
|
+
# JS template with tail preference: prefer the end of arrays when truncating
|
|
18
24
|
arr = ','.join(str(i) for i in range(100))
|
|
19
|
-
print(headson.summarize('{"arr": [' + arr + ']}', template="js", character_budget=60))
|
|
25
|
+
print(headson.summarize('{"arr": [' + arr + ']}', template="js", character_budget=60, tail=True))
|
|
26
|
+
|
|
27
|
+
# Note: tail mode affects only pseudo/js display templates; the json template stays strict.
|
|
20
28
|
```
|
|
21
29
|
|
|
22
30
|
Install for development:
|
|
@@ -70,20 +70,22 @@ fn find_largest_render_under_budget(
|
|
|
70
70
|
// Each included node contributes at least some output; cap hi by budget.
|
|
71
71
|
let lo = 1usize;
|
|
72
72
|
let hi = total.min(char_budget.max(1));
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
let mut
|
|
73
|
+
// Reuse render-inclusion flags across render attempts to avoid clearing the vector.
|
|
74
|
+
// A node participates in the current render attempt when inclusion_flags[id] == render_set_id.
|
|
75
|
+
let mut inclusion_flags: Vec<u32> = vec![0; total];
|
|
76
|
+
// Each render attempt bumps this non-zero identifier to create a fresh inclusion set.
|
|
77
|
+
let mut render_set_id: u32 = 1;
|
|
76
78
|
let mut best_str: Option<String> = None;
|
|
77
79
|
|
|
78
80
|
let _ = crate::utils::search::binary_search_max(lo, hi, |mid| {
|
|
79
|
-
let s = crate::serialization::
|
|
81
|
+
let s = crate::serialization::render_top_k(
|
|
80
82
|
order_build,
|
|
81
83
|
mid,
|
|
82
|
-
&mut
|
|
83
|
-
|
|
84
|
+
&mut inclusion_flags,
|
|
85
|
+
render_set_id,
|
|
84
86
|
config,
|
|
85
87
|
);
|
|
86
|
-
|
|
88
|
+
render_set_id = render_set_id.wrapping_add(1).max(1);
|
|
87
89
|
if s.len() <= char_budget {
|
|
88
90
|
best_str = Some(s);
|
|
89
91
|
true
|
|
@@ -97,11 +99,11 @@ fn find_largest_render_under_budget(
|
|
|
97
99
|
} else {
|
|
98
100
|
// Fallback: always render a single node (k=1) to produce the
|
|
99
101
|
// shortest possible preview, even if it exceeds the byte budget.
|
|
100
|
-
crate::serialization::
|
|
102
|
+
crate::serialization::render_top_k(
|
|
101
103
|
order_build,
|
|
102
104
|
1,
|
|
103
|
-
&mut
|
|
104
|
-
|
|
105
|
+
&mut inclusion_flags,
|
|
106
|
+
render_set_id,
|
|
105
107
|
config,
|
|
106
108
|
)
|
|
107
109
|
}
|
|
@@ -10,15 +10,17 @@ use crate::utils::tree_arena::JsonTreeArena;
|
|
|
10
10
|
#[derive(Clone)]
|
|
11
11
|
struct Entry {
|
|
12
12
|
score: u128,
|
|
13
|
-
|
|
13
|
+
// Index into the priority-ordered nodes (0..total_nodes)
|
|
14
|
+
priority_index: usize,
|
|
14
15
|
depth: usize,
|
|
15
|
-
// When present, we can read kind from the arena node.
|
|
16
|
+
// When present, we can read kind from the arena (parsed JSON) node.
|
|
16
17
|
// When None, this is a synthetic entry (currently only string grapheme).
|
|
17
|
-
|
|
18
|
+
arena_index: Option<usize>,
|
|
18
19
|
}
|
|
19
20
|
impl PartialEq for Entry {
|
|
20
21
|
fn eq(&self, other: &Self) -> bool {
|
|
21
|
-
self.score == other.score
|
|
22
|
+
self.score == other.score
|
|
23
|
+
&& self.priority_index == other.priority_index
|
|
22
24
|
}
|
|
23
25
|
}
|
|
24
26
|
impl Eq for Entry {}
|
|
@@ -31,7 +33,7 @@ impl Ord for Entry {
|
|
|
31
33
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
32
34
|
self.score
|
|
33
35
|
.cmp(&other.score)
|
|
34
|
-
.then_with(|| self.
|
|
36
|
+
.then_with(|| self.priority_index.cmp(&other.priority_index))
|
|
35
37
|
}
|
|
36
38
|
}
|
|
37
39
|
|
|
@@ -52,12 +54,12 @@ impl<'a> Scope<'a> {
|
|
|
52
54
|
fn push_child_common(
|
|
53
55
|
&mut self,
|
|
54
56
|
entry: &Entry,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
child_priority_index: usize,
|
|
58
|
+
arena_index: Option<usize>,
|
|
57
59
|
score: u128,
|
|
58
60
|
ranked: RankedNode,
|
|
59
61
|
) {
|
|
60
|
-
let id = entry.
|
|
62
|
+
let id = entry.priority_index;
|
|
61
63
|
self.parent.push(Some(NodeId(id)));
|
|
62
64
|
self.children.push(Vec::new());
|
|
63
65
|
self.metrics.push(NodeMetrics::default());
|
|
@@ -65,12 +67,12 @@ impl<'a> Scope<'a> {
|
|
|
65
67
|
// Children created from parsing regular JSON are standard objects/arrays/etc.
|
|
66
68
|
// If child is an object, default to Object type.
|
|
67
69
|
self.object_type.push(ObjectType::Object);
|
|
68
|
-
self.children[id].push(NodeId(
|
|
70
|
+
self.children[id].push(NodeId(child_priority_index));
|
|
69
71
|
self.heap.push(Reverse(Entry {
|
|
70
72
|
score,
|
|
71
|
-
|
|
73
|
+
priority_index: child_priority_index,
|
|
72
74
|
depth: entry.depth + 1,
|
|
73
|
-
|
|
75
|
+
arena_index,
|
|
74
76
|
}));
|
|
75
77
|
}
|
|
76
78
|
fn record_array_metrics(&mut self, id: usize, arena_id: usize) {
|
|
@@ -118,7 +120,7 @@ impl<'a> Scope<'a> {
|
|
|
118
120
|
for i in 0..kept {
|
|
119
121
|
let child_arena_id = self.arena.children[node.children_start + i];
|
|
120
122
|
let child_kind = self.arena.nodes[child_arena_id].kind;
|
|
121
|
-
let
|
|
123
|
+
let child_priority_index = *self.next_pq_id;
|
|
122
124
|
*self.next_pq_id += 1;
|
|
123
125
|
let idx_for_priority: usize = if self.config.prefer_tail_arrays {
|
|
124
126
|
kept.saturating_sub(1).saturating_sub(i)
|
|
@@ -131,11 +133,11 @@ impl<'a> Scope<'a> {
|
|
|
131
133
|
let child_node = &self.arena.nodes[child_arena_id];
|
|
132
134
|
self.push_child_common(
|
|
133
135
|
entry,
|
|
134
|
-
|
|
136
|
+
child_priority_index,
|
|
135
137
|
Some(child_arena_id),
|
|
136
138
|
score,
|
|
137
139
|
RankedNode {
|
|
138
|
-
node_id: NodeId(
|
|
140
|
+
node_id: NodeId(child_priority_index),
|
|
139
141
|
kind: child_kind,
|
|
140
142
|
key_in_object: None,
|
|
141
143
|
number_value: child_node.number_value.clone(),
|
|
@@ -163,17 +165,17 @@ impl<'a> Scope<'a> {
|
|
|
163
165
|
});
|
|
164
166
|
for (key_idx, child_arena_id) in items {
|
|
165
167
|
let child_kind = self.arena.nodes[child_arena_id].kind;
|
|
166
|
-
let
|
|
168
|
+
let child_priority_index = *self.next_pq_id;
|
|
167
169
|
*self.next_pq_id += 1;
|
|
168
170
|
let score = entry.score + OBJECT_CHILD_BASE_INCREMENT;
|
|
169
171
|
let child_node = &self.arena.nodes[child_arena_id];
|
|
170
172
|
self.push_child_common(
|
|
171
173
|
entry,
|
|
172
|
-
|
|
174
|
+
child_priority_index,
|
|
173
175
|
Some(child_arena_id),
|
|
174
176
|
score,
|
|
175
177
|
RankedNode {
|
|
176
|
-
node_id: NodeId(
|
|
178
|
+
node_id: NodeId(child_priority_index),
|
|
177
179
|
kind: child_kind,
|
|
178
180
|
key_in_object: Some(self.arena.obj_keys[key_idx].clone()),
|
|
179
181
|
number_value: child_node.number_value.clone(),
|
|
@@ -188,13 +190,13 @@ impl<'a> Scope<'a> {
|
|
|
188
190
|
}
|
|
189
191
|
|
|
190
192
|
fn expand_string_children(&mut self, entry: &Entry) {
|
|
191
|
-
let id = entry.
|
|
193
|
+
let id = entry.priority_index;
|
|
192
194
|
let full = self.nodes[id].string_value.as_deref().unwrap_or("");
|
|
193
195
|
let count = UnicodeSegmentation::graphemes(full, true)
|
|
194
196
|
.take(self.config.max_string_graphemes)
|
|
195
197
|
.count();
|
|
196
198
|
for i in 0..count {
|
|
197
|
-
let
|
|
199
|
+
let child_priority_index = *self.next_pq_id;
|
|
198
200
|
*self.next_pq_id += 1;
|
|
199
201
|
let extra = if i > STRING_INDEX_INFLECTION {
|
|
200
202
|
let d = (i - STRING_INDEX_INFLECTION) as u128;
|
|
@@ -208,11 +210,11 @@ impl<'a> Scope<'a> {
|
|
|
208
210
|
+ extra;
|
|
209
211
|
self.push_child_common(
|
|
210
212
|
entry,
|
|
211
|
-
|
|
213
|
+
child_priority_index,
|
|
212
214
|
None,
|
|
213
215
|
score,
|
|
214
216
|
RankedNode {
|
|
215
|
-
node_id: NodeId(
|
|
217
|
+
node_id: NodeId(child_priority_index),
|
|
216
218
|
kind: NodeKind::String,
|
|
217
219
|
key_in_object: None,
|
|
218
220
|
number_value: None,
|
|
@@ -227,7 +229,7 @@ impl<'a> Scope<'a> {
|
|
|
227
229
|
}
|
|
228
230
|
|
|
229
231
|
fn resolve_kind(&self, entry: &Entry) -> NodeKind {
|
|
230
|
-
if let Some(ar_id) = entry.
|
|
232
|
+
if let Some(ar_id) = entry.arena_index {
|
|
231
233
|
self.arena.nodes[ar_id].kind
|
|
232
234
|
} else {
|
|
233
235
|
NodeKind::String
|
|
@@ -237,12 +239,12 @@ impl<'a> Scope<'a> {
|
|
|
237
239
|
fn expand_for(&mut self, entry: &Entry, kind: NodeKind) {
|
|
238
240
|
match kind {
|
|
239
241
|
NodeKind::Array => {
|
|
240
|
-
if let Some(ar_id) = entry.
|
|
242
|
+
if let Some(ar_id) = entry.arena_index {
|
|
241
243
|
self.expand_array_children(entry, ar_id);
|
|
242
244
|
}
|
|
243
245
|
}
|
|
244
246
|
NodeKind::Object => {
|
|
245
|
-
if let Some(ar_id) = entry.
|
|
247
|
+
if let Some(ar_id) = entry.arena_index {
|
|
246
248
|
self.expand_object_children(entry, ar_id);
|
|
247
249
|
}
|
|
248
250
|
}
|
|
@@ -256,10 +258,10 @@ impl<'a> Scope<'a> {
|
|
|
256
258
|
entry: &Entry,
|
|
257
259
|
ids_by_order: &mut Vec<NodeId>,
|
|
258
260
|
) {
|
|
259
|
-
let id = entry.
|
|
261
|
+
let id = entry.priority_index;
|
|
260
262
|
ids_by_order.push(NodeId(id));
|
|
261
263
|
let kind = self.resolve_kind(entry);
|
|
262
|
-
if let Some(ar_id) = entry.
|
|
264
|
+
if let Some(ar_id) = entry.arena_index {
|
|
263
265
|
self.record_metrics_for(id, kind, ar_id);
|
|
264
266
|
}
|
|
265
267
|
self.expand_for(entry, kind);
|
|
@@ -282,14 +284,14 @@ pub fn build_order(
|
|
|
282
284
|
// Seed root from arena
|
|
283
285
|
let root_ar = arena.root_id;
|
|
284
286
|
let root_kind = arena.nodes[root_ar].kind;
|
|
285
|
-
let
|
|
287
|
+
let root_priority_index = next_pq_id;
|
|
286
288
|
next_pq_id += 1;
|
|
287
289
|
parent.push(None);
|
|
288
290
|
children.push(Vec::new());
|
|
289
291
|
metrics.push(NodeMetrics::default());
|
|
290
292
|
let n = &arena.nodes[root_ar];
|
|
291
293
|
nodes.push(RankedNode {
|
|
292
|
-
node_id: NodeId(
|
|
294
|
+
node_id: NodeId(root_priority_index),
|
|
293
295
|
kind: root_kind,
|
|
294
296
|
key_in_object: None,
|
|
295
297
|
number_value: n.number_value.clone(),
|
|
@@ -305,9 +307,9 @@ pub fn build_order(
|
|
|
305
307
|
object_type.push(root_ot);
|
|
306
308
|
heap.push(Reverse(Entry {
|
|
307
309
|
score: ROOT_BASE_SCORE,
|
|
308
|
-
|
|
310
|
+
priority_index: root_priority_index,
|
|
309
311
|
depth: 0,
|
|
310
|
-
|
|
312
|
+
arena_index: Some(root_ar),
|
|
311
313
|
}));
|
|
312
314
|
|
|
313
315
|
while let Some(Reverse(entry)) = heap.pop() {
|
|
@@ -335,7 +337,7 @@ pub fn build_order(
|
|
|
335
337
|
nodes,
|
|
336
338
|
parent,
|
|
337
339
|
children,
|
|
338
|
-
order,
|
|
340
|
+
by_priority: order,
|
|
339
341
|
total_nodes: total,
|
|
340
342
|
object_type,
|
|
341
343
|
})
|
|
@@ -359,9 +361,9 @@ mod tests {
|
|
|
359
361
|
)
|
|
360
362
|
.unwrap();
|
|
361
363
|
let mut items_sorted: Vec<_> = build.nodes.clone();
|
|
362
|
-
// Build a transient mapping from id ->
|
|
364
|
+
// Build a transient mapping from id -> by_priority index
|
|
363
365
|
let mut order_index = vec![usize::MAX; build.total_nodes];
|
|
364
|
-
for (idx, &pid) in build.
|
|
366
|
+
for (idx, &pid) in build.by_priority.iter().enumerate() {
|
|
365
367
|
let pidx = pid.0;
|
|
366
368
|
if pidx < build.total_nodes {
|
|
367
369
|
order_index[pidx] = idx;
|
|
@@ -391,7 +393,7 @@ mod tests {
|
|
|
391
393
|
.unwrap();
|
|
392
394
|
let mut items_sorted: Vec<_> = build.nodes.clone();
|
|
393
395
|
let mut order_index = vec![usize::MAX; build.total_nodes];
|
|
394
|
-
for (idx, &pid) in build.
|
|
396
|
+
for (idx, &pid) in build.by_priority.iter().enumerate() {
|
|
395
397
|
let pidx = pid.0;
|
|
396
398
|
if pidx < build.total_nodes {
|
|
397
399
|
order_index[pidx] = idx;
|
|
@@ -62,7 +62,7 @@ pub struct PriorityOrder {
|
|
|
62
62
|
// They correspond to `NodeId.0` in `RankedNode` for convenience when indexing.
|
|
63
63
|
pub parent: Vec<Option<NodeId>>, // parent[id] = parent id (PQ id)
|
|
64
64
|
pub children: Vec<Vec<NodeId>>, // children[id] = children ids (PQ ids)
|
|
65
|
-
pub
|
|
65
|
+
pub by_priority: Vec<NodeId>, // ids sorted by ascending priority (PQ ids)
|
|
66
66
|
pub total_nodes: usize,
|
|
67
67
|
pub object_type: Vec<ObjectType>,
|
|
68
68
|
}
|
|
@@ -12,9 +12,15 @@ type ArrayChildPair = (usize, String);
|
|
|
12
12
|
type ObjectChildPair = (usize, (String, String));
|
|
13
13
|
|
|
14
14
|
pub(crate) struct RenderScope<'a> {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
// Priority-ordered view of the parsed JSON tree.
|
|
16
|
+
order: &'a PriorityOrder,
|
|
17
|
+
// Per-node inclusion flag: a node is included in the current render attempt
|
|
18
|
+
// when inclusion_flags[node_id] == render_set_id. This avoids clearing the
|
|
19
|
+
// vector between render attempts by bumping render_set_id each time.
|
|
20
|
+
inclusion_flags: &'a [u32],
|
|
21
|
+
// Identifier for the current inclusion set (render pass).
|
|
22
|
+
render_set_id: u32,
|
|
23
|
+
// Rendering configuration (template, whitespace, etc.).
|
|
18
24
|
config: &'a crate::RenderConfig,
|
|
19
25
|
}
|
|
20
26
|
|
|
@@ -59,7 +65,7 @@ impl<'a> RenderScope<'a> {
|
|
|
59
65
|
child_pq_id: usize,
|
|
60
66
|
nl: &str,
|
|
61
67
|
) {
|
|
62
|
-
let raw_key = self.
|
|
68
|
+
let raw_key = self.order.nodes[child_pq_id]
|
|
63
69
|
.key_in_object
|
|
64
70
|
.as_deref()
|
|
65
71
|
.unwrap_or("");
|
|
@@ -99,7 +105,7 @@ impl<'a> RenderScope<'a> {
|
|
|
99
105
|
child_pq_id: usize,
|
|
100
106
|
nl: &str,
|
|
101
107
|
) {
|
|
102
|
-
let raw_key = self.
|
|
108
|
+
let raw_key = self.order.nodes[child_pq_id]
|
|
103
109
|
.key_in_object
|
|
104
110
|
.as_deref()
|
|
105
111
|
.unwrap_or("");
|
|
@@ -132,10 +138,10 @@ impl<'a> RenderScope<'a> {
|
|
|
132
138
|
}
|
|
133
139
|
}
|
|
134
140
|
fn count_kept_children(&self, id: usize) -> usize {
|
|
135
|
-
if let Some(kids) = self.
|
|
141
|
+
if let Some(kids) = self.order.children.get(id) {
|
|
136
142
|
let mut kept = 0usize;
|
|
137
143
|
for &cid in kids {
|
|
138
|
-
if self.
|
|
144
|
+
if self.inclusion_flags[cid.0] == self.render_set_id {
|
|
139
145
|
kept += 1;
|
|
140
146
|
}
|
|
141
147
|
}
|
|
@@ -146,7 +152,7 @@ impl<'a> RenderScope<'a> {
|
|
|
146
152
|
}
|
|
147
153
|
|
|
148
154
|
fn omitted_for_string(&self, id: usize, kept: usize) -> Option<usize> {
|
|
149
|
-
let m = &self.
|
|
155
|
+
let m = &self.order.metrics[id];
|
|
150
156
|
if let Some(orig) = m.string_len {
|
|
151
157
|
if orig > kept {
|
|
152
158
|
return Some(orig - kept);
|
|
@@ -170,13 +176,13 @@ impl<'a> RenderScope<'a> {
|
|
|
170
176
|
) -> Option<usize> {
|
|
171
177
|
match kind {
|
|
172
178
|
NodeKind::Array => {
|
|
173
|
-
self.
|
|
179
|
+
self.order.metrics[id].array_len.and_then(|orig| {
|
|
174
180
|
if orig > kept { Some(orig - kept) } else { None }
|
|
175
181
|
})
|
|
176
182
|
}
|
|
177
183
|
NodeKind::String => self.omitted_for_string(id, kept),
|
|
178
184
|
NodeKind::Object => {
|
|
179
|
-
self.
|
|
185
|
+
self.order.metrics[id].object_len.and_then(|orig| {
|
|
180
186
|
if orig > kept { Some(orig - kept) } else { None }
|
|
181
187
|
})
|
|
182
188
|
}
|
|
@@ -192,7 +198,7 @@ impl<'a> RenderScope<'a> {
|
|
|
192
198
|
) -> String {
|
|
193
199
|
let config = self.config;
|
|
194
200
|
let (children_pairs, kept) = self.gather_array_children(id, depth);
|
|
195
|
-
let node = &self.
|
|
201
|
+
let node = &self.order.nodes[id];
|
|
196
202
|
let omitted = self.omitted_for(id, node.kind, kept).unwrap_or(0);
|
|
197
203
|
if kept == 0 && omitted == 0 {
|
|
198
204
|
return "[]".to_string();
|
|
@@ -219,7 +225,7 @@ impl<'a> RenderScope<'a> {
|
|
|
219
225
|
let config = self.config;
|
|
220
226
|
// Special-case: fileset root in Pseudo/JS templates → head-style sections
|
|
221
227
|
if id == ROOT_PQ_ID
|
|
222
|
-
&& self.
|
|
228
|
+
&& self.order.object_type.get(id) == Some(&ObjectType::Fileset)
|
|
223
229
|
&& !config.newline.is_empty()
|
|
224
230
|
{
|
|
225
231
|
match config.template {
|
|
@@ -233,7 +239,7 @@ impl<'a> RenderScope<'a> {
|
|
|
233
239
|
}
|
|
234
240
|
}
|
|
235
241
|
let (children_pairs, kept) = self.gather_object_children(id, depth);
|
|
236
|
-
let node = &self.
|
|
242
|
+
let node = &self.order.nodes[id];
|
|
237
243
|
let omitted = self.omitted_for(id, node.kind, kept).unwrap_or(0);
|
|
238
244
|
if kept == 0 && omitted == 0 {
|
|
239
245
|
return "{}".to_string();
|
|
@@ -248,14 +254,15 @@ impl<'a> RenderScope<'a> {
|
|
|
248
254
|
space: &config.space,
|
|
249
255
|
newline: &config.newline,
|
|
250
256
|
fileset_root: id == ROOT_PQ_ID
|
|
251
|
-
&& self.
|
|
257
|
+
&& self.order.object_type.get(id)
|
|
258
|
+
== Some(&ObjectType::Fileset),
|
|
252
259
|
};
|
|
253
260
|
render_object(config.template, &ctx)
|
|
254
261
|
}
|
|
255
262
|
|
|
256
263
|
fn serialize_string(&mut self, id: usize) -> String {
|
|
257
264
|
let kept = self.count_kept_children(id);
|
|
258
|
-
let node = &self.
|
|
265
|
+
let node = &self.order.nodes[id];
|
|
259
266
|
let omitted = self.omitted_for(id, node.kind, kept).unwrap_or(0);
|
|
260
267
|
let full: &str = node.string_value.as_deref().unwrap_or("");
|
|
261
268
|
if omitted == 0 {
|
|
@@ -267,7 +274,7 @@ impl<'a> RenderScope<'a> {
|
|
|
267
274
|
}
|
|
268
275
|
|
|
269
276
|
fn serialize_number(&self, id: usize) -> String {
|
|
270
|
-
let it = &self.
|
|
277
|
+
let it = &self.order.nodes[id];
|
|
271
278
|
if let Some(n) = it.number_value.as_ref() {
|
|
272
279
|
if let Some(i) = n.as_i64() {
|
|
273
280
|
return i.to_string();
|
|
@@ -283,7 +290,7 @@ impl<'a> RenderScope<'a> {
|
|
|
283
290
|
}
|
|
284
291
|
|
|
285
292
|
fn serialize_bool(&self, id: usize) -> String {
|
|
286
|
-
let it = &self.
|
|
293
|
+
let it = &self.order.nodes[id];
|
|
287
294
|
match it.bool_value {
|
|
288
295
|
Some(true) => "true".to_string(),
|
|
289
296
|
Some(false) | None => "false".to_string(),
|
|
@@ -296,7 +303,7 @@ impl<'a> RenderScope<'a> {
|
|
|
296
303
|
depth: usize,
|
|
297
304
|
inline: bool,
|
|
298
305
|
) -> String {
|
|
299
|
-
let it = &self.
|
|
306
|
+
let it = &self.order.nodes[id];
|
|
300
307
|
match it.kind {
|
|
301
308
|
NodeKind::Array => self.serialize_array(id, depth, inline),
|
|
302
309
|
NodeKind::Object => self.serialize_object(id, depth, inline),
|
|
@@ -314,13 +321,13 @@ impl<'a> RenderScope<'a> {
|
|
|
314
321
|
) -> (Vec<ArrayChildPair>, usize) {
|
|
315
322
|
let mut children_pairs: Vec<ArrayChildPair> = Vec::new();
|
|
316
323
|
let mut kept = 0usize;
|
|
317
|
-
if let Some(children_ids) = self.
|
|
324
|
+
if let Some(children_ids) = self.order.children.get(id) {
|
|
318
325
|
for (i, &child_id) in children_ids.iter().enumerate() {
|
|
319
|
-
if self.
|
|
326
|
+
if self.inclusion_flags[child_id.0] != self.render_set_id {
|
|
320
327
|
continue;
|
|
321
328
|
}
|
|
322
329
|
kept += 1;
|
|
323
|
-
let child_kind = self.
|
|
330
|
+
let child_kind = self.order.nodes[child_id.0].kind;
|
|
324
331
|
let rendered =
|
|
325
332
|
self.serialize_node(child_id.0, depth + 1, false);
|
|
326
333
|
self.push_array_child_line(
|
|
@@ -342,13 +349,13 @@ impl<'a> RenderScope<'a> {
|
|
|
342
349
|
) -> (Vec<ObjectChildPair>, usize) {
|
|
343
350
|
let mut children_pairs: Vec<ObjectChildPair> = Vec::new();
|
|
344
351
|
let mut kept = 0usize;
|
|
345
|
-
if let Some(children_ids) = self.
|
|
352
|
+
if let Some(children_ids) = self.order.children.get(id) {
|
|
346
353
|
for (i, &child_id) in children_ids.iter().enumerate() {
|
|
347
|
-
if self.
|
|
354
|
+
if self.inclusion_flags[child_id.0] != self.render_set_id {
|
|
348
355
|
continue;
|
|
349
356
|
}
|
|
350
357
|
kept += 1;
|
|
351
|
-
let child = &self.
|
|
358
|
+
let child = &self.order.nodes[child_id.0];
|
|
352
359
|
let raw_key = child.key_in_object.as_deref().unwrap_or("");
|
|
353
360
|
let key = crate::utils::json::json_string(raw_key);
|
|
354
361
|
let val = self.serialize_node(child_id.0, depth + 1, true);
|
|
@@ -362,10 +369,10 @@ impl<'a> RenderScope<'a> {
|
|
|
362
369
|
fn serialize_fileset_root_pseudo(&mut self, depth: usize) -> String {
|
|
363
370
|
let nl = &self.config.newline;
|
|
364
371
|
let mut out = String::new();
|
|
365
|
-
if let Some(children_ids) = self.
|
|
372
|
+
if let Some(children_ids) = self.order.children.get(ROOT_PQ_ID) {
|
|
366
373
|
let mut kept = 0usize;
|
|
367
374
|
for &child_id in children_ids.iter() {
|
|
368
|
-
if self.
|
|
375
|
+
if self.inclusion_flags[child_id.0] != self.render_set_id {
|
|
369
376
|
continue;
|
|
370
377
|
}
|
|
371
378
|
if kept > 0 {
|
|
@@ -380,7 +387,7 @@ impl<'a> RenderScope<'a> {
|
|
|
380
387
|
);
|
|
381
388
|
}
|
|
382
389
|
let total = self
|
|
383
|
-
.
|
|
390
|
+
.order
|
|
384
391
|
.metrics
|
|
385
392
|
.get(ROOT_PQ_ID)
|
|
386
393
|
.and_then(|m| m.object_len)
|
|
@@ -398,13 +405,13 @@ impl<'a> RenderScope<'a> {
|
|
|
398
405
|
fn serialize_fileset_root_js(&mut self, depth: usize) -> String {
|
|
399
406
|
let nl = &self.config.newline;
|
|
400
407
|
let mut out = String::new();
|
|
401
|
-
let Some(children_ids) = self.
|
|
408
|
+
let Some(children_ids) = self.order.children.get(ROOT_PQ_ID) else {
|
|
402
409
|
return out;
|
|
403
410
|
};
|
|
404
411
|
let kept =
|
|
405
412
|
self.render_js_fileset_sections(&mut out, depth, children_ids, nl);
|
|
406
413
|
let total = self
|
|
407
|
-
.
|
|
414
|
+
.order
|
|
408
415
|
.metrics
|
|
409
416
|
.get(ROOT_PQ_ID)
|
|
410
417
|
.and_then(|m| m.object_len)
|
|
@@ -422,7 +429,7 @@ impl<'a> RenderScope<'a> {
|
|
|
422
429
|
) -> usize {
|
|
423
430
|
let mut kept = 0usize;
|
|
424
431
|
for &child_id in children_ids.iter() {
|
|
425
|
-
if self.
|
|
432
|
+
if self.inclusion_flags[child_id.0] != self.render_set_id {
|
|
426
433
|
continue;
|
|
427
434
|
}
|
|
428
435
|
if kept > 0 {
|
|
@@ -435,37 +442,63 @@ impl<'a> RenderScope<'a> {
|
|
|
435
442
|
}
|
|
436
443
|
}
|
|
437
444
|
|
|
438
|
-
///
|
|
439
|
-
|
|
445
|
+
/// Prepare a render set by including the first `top_k` nodes by priority
|
|
446
|
+
/// and all of their ancestors so the output remains structurally valid.
|
|
447
|
+
pub fn prepare_render_set_top_k_and_ancestors(
|
|
440
448
|
order_build: &PriorityOrder,
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
)
|
|
446
|
-
|
|
447
|
-
marks.resize(order_build.total_nodes, 0);
|
|
449
|
+
top_k: usize,
|
|
450
|
+
inclusion_flags: &mut Vec<u32>,
|
|
451
|
+
render_id: u32,
|
|
452
|
+
) {
|
|
453
|
+
if inclusion_flags.len() < order_build.total_nodes {
|
|
454
|
+
inclusion_flags.resize(order_build.total_nodes, 0);
|
|
448
455
|
}
|
|
449
|
-
|
|
450
|
-
let k = budget.min(order_build.total_nodes);
|
|
456
|
+
let k = top_k.min(order_build.total_nodes);
|
|
451
457
|
crate::utils::graph::mark_top_k_and_ancestors(
|
|
452
458
|
order_build,
|
|
453
459
|
k,
|
|
454
|
-
|
|
455
|
-
|
|
460
|
+
inclusion_flags,
|
|
461
|
+
render_id,
|
|
456
462
|
);
|
|
463
|
+
}
|
|
457
464
|
|
|
465
|
+
/// Render using a previously prepared render set (inclusion flags matching `render_id`).
|
|
466
|
+
pub fn render_from_render_set(
|
|
467
|
+
order_build: &PriorityOrder,
|
|
468
|
+
inclusion_flags: &[u32],
|
|
469
|
+
render_id: u32,
|
|
470
|
+
config: &crate::RenderConfig,
|
|
471
|
+
) -> String {
|
|
458
472
|
// Root PQ id is a fixed invariant (0).
|
|
459
473
|
let root_id = ROOT_PQ_ID;
|
|
460
474
|
let mut scope = RenderScope {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
475
|
+
order: order_build,
|
|
476
|
+
inclusion_flags,
|
|
477
|
+
render_set_id: render_id,
|
|
464
478
|
config,
|
|
465
479
|
};
|
|
466
480
|
scope.serialize_node(root_id, 0, false)
|
|
467
481
|
}
|
|
468
482
|
|
|
483
|
+
/// Convenience: prepare the render set for `top_k` nodes and render in one call.
|
|
484
|
+
pub fn render_top_k(
|
|
485
|
+
order_build: &PriorityOrder,
|
|
486
|
+
top_k: usize,
|
|
487
|
+
inclusion_flags: &mut Vec<u32>,
|
|
488
|
+
render_id: u32,
|
|
489
|
+
config: &crate::RenderConfig,
|
|
490
|
+
) -> String {
|
|
491
|
+
prepare_render_set_top_k_and_ancestors(
|
|
492
|
+
order_build,
|
|
493
|
+
top_k,
|
|
494
|
+
inclusion_flags,
|
|
495
|
+
render_id,
|
|
496
|
+
);
|
|
497
|
+
render_from_render_set(order_build, inclusion_flags, render_id, config)
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
//
|
|
501
|
+
|
|
469
502
|
#[cfg(test)]
|
|
470
503
|
mod tests {
|
|
471
504
|
use super::*;
|
|
@@ -485,7 +518,7 @@ mod tests {
|
|
|
485
518
|
)
|
|
486
519
|
.unwrap();
|
|
487
520
|
let mut marks = vec![0u32; build.total_nodes];
|
|
488
|
-
let out =
|
|
521
|
+
let out = render_top_k(
|
|
489
522
|
&build,
|
|
490
523
|
10,
|
|
491
524
|
&mut marks,
|
|
@@ -516,7 +549,7 @@ mod tests {
|
|
|
516
549
|
)
|
|
517
550
|
.unwrap();
|
|
518
551
|
let mut marks = vec![0u32; build.total_nodes];
|
|
519
|
-
let out =
|
|
552
|
+
let out = render_top_k(
|
|
520
553
|
&build,
|
|
521
554
|
usize::MAX,
|
|
522
555
|
&mut marks,
|
|
@@ -551,7 +584,7 @@ mod tests {
|
|
|
551
584
|
)
|
|
552
585
|
.unwrap();
|
|
553
586
|
let mut marks = vec![0u32; build.total_nodes];
|
|
554
|
-
let out =
|
|
587
|
+
let out = render_top_k(
|
|
555
588
|
&build,
|
|
556
589
|
10,
|
|
557
590
|
&mut marks,
|
|
@@ -566,4 +599,45 @@ mod tests {
|
|
|
566
599
|
);
|
|
567
600
|
assert_snapshot!("arena_render_single", out);
|
|
568
601
|
}
|
|
602
|
+
|
|
603
|
+
#[test]
|
|
604
|
+
fn arena_render_object_partial_js() {
|
|
605
|
+
// Object with three properties; render top_k small so only one child is kept.
|
|
606
|
+
let arena = crate::json_ingest::build_json_tree_arena(
|
|
607
|
+
"{\"a\":1,\"b\":2,\"c\":3}",
|
|
608
|
+
&crate::PriorityConfig::new(usize::MAX, usize::MAX),
|
|
609
|
+
)
|
|
610
|
+
.unwrap();
|
|
611
|
+
let build = build_order(
|
|
612
|
+
&arena,
|
|
613
|
+
&crate::PriorityConfig::new(usize::MAX, usize::MAX),
|
|
614
|
+
)
|
|
615
|
+
.unwrap();
|
|
616
|
+
let mut flags = vec![0u32; build.total_nodes];
|
|
617
|
+
// top_k=2 → root object + first property
|
|
618
|
+
let out = render_top_k(
|
|
619
|
+
&build,
|
|
620
|
+
2,
|
|
621
|
+
&mut flags,
|
|
622
|
+
1,
|
|
623
|
+
&crate::RenderConfig {
|
|
624
|
+
template: crate::OutputTemplate::Js,
|
|
625
|
+
indent_unit: " ".to_string(),
|
|
626
|
+
space: " ".to_string(),
|
|
627
|
+
newline: "\n".to_string(),
|
|
628
|
+
prefer_tail_arrays: false,
|
|
629
|
+
},
|
|
630
|
+
);
|
|
631
|
+
// Should be a valid JS object with one property and an omitted summary.
|
|
632
|
+
assert!(out.starts_with("{\n"));
|
|
633
|
+
assert!(
|
|
634
|
+
out.contains("/* 2 more properties */"),
|
|
635
|
+
"missing omitted summary: {out:?}"
|
|
636
|
+
);
|
|
637
|
+
assert!(
|
|
638
|
+
out.contains("\"a\": 1")
|
|
639
|
+
|| out.contains("\"b\": 2")
|
|
640
|
+
|| out.contains("\"c\": 3")
|
|
641
|
+
);
|
|
642
|
+
}
|
|
569
643
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
use crate::order::{NodeId, PriorityOrder};
|
|
2
|
+
|
|
3
|
+
/// Seed the work stack by including the first `k` nodes in global priority order.
|
|
4
|
+
fn seed_stack_with_top_k(
|
|
5
|
+
order: &PriorityOrder,
|
|
6
|
+
k: usize,
|
|
7
|
+
inclusion_flags: &mut [u32],
|
|
8
|
+
render_id: u32,
|
|
9
|
+
work_stack: &mut Vec<NodeId>,
|
|
10
|
+
) {
|
|
11
|
+
for &id in order.by_priority.iter().take(k) {
|
|
12
|
+
let idx = id.0;
|
|
13
|
+
if inclusion_flags[idx] != render_id {
|
|
14
|
+
inclusion_flags[idx] = render_id;
|
|
15
|
+
work_stack.push(id);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// Pop from the work stack; for each node include its parent; continue until empty.
|
|
21
|
+
fn propagate_marks_to_ancestors(
|
|
22
|
+
parent: &[Option<NodeId>],
|
|
23
|
+
inclusion_flags: &mut [u32],
|
|
24
|
+
render_id: u32,
|
|
25
|
+
work_stack: &mut Vec<NodeId>,
|
|
26
|
+
) {
|
|
27
|
+
while let Some(id) = work_stack.pop() {
|
|
28
|
+
let idx = id.0;
|
|
29
|
+
match parent[idx] {
|
|
30
|
+
Some(parent) if inclusion_flags[parent.0] != render_id => {
|
|
31
|
+
inclusion_flags[parent.0] = render_id;
|
|
32
|
+
work_stack.push(parent);
|
|
33
|
+
}
|
|
34
|
+
_ => {}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// Include the first `k` nodes by global priority order and all of their ancestors
|
|
40
|
+
/// in the current render set (identified by `render_id`).
|
|
41
|
+
pub(crate) fn mark_top_k_and_ancestors(
|
|
42
|
+
order: &PriorityOrder,
|
|
43
|
+
k: usize,
|
|
44
|
+
inclusion_flags: &mut [u32],
|
|
45
|
+
render_id: u32,
|
|
46
|
+
) {
|
|
47
|
+
let mut work_stack: Vec<NodeId> = Vec::new();
|
|
48
|
+
seed_stack_with_top_k(
|
|
49
|
+
order,
|
|
50
|
+
k,
|
|
51
|
+
inclusion_flags,
|
|
52
|
+
render_id,
|
|
53
|
+
&mut work_stack,
|
|
54
|
+
);
|
|
55
|
+
propagate_marks_to_ancestors(
|
|
56
|
+
&order.parent,
|
|
57
|
+
inclusion_flags,
|
|
58
|
+
render_id,
|
|
59
|
+
&mut work_stack,
|
|
60
|
+
);
|
|
61
|
+
}
|
headson-0.3.0/src/utils/graph.rs
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
use crate::order::{NodeId, PriorityOrder};
|
|
2
|
-
|
|
3
|
-
/// Seed the work stack by marking the first `k` nodes in global order.
|
|
4
|
-
fn seed_stack_with_top_k(
|
|
5
|
-
order: &PriorityOrder,
|
|
6
|
-
k: usize,
|
|
7
|
-
marks: &mut [u32],
|
|
8
|
-
mark_gen: u32,
|
|
9
|
-
work_stack: &mut Vec<NodeId>,
|
|
10
|
-
) {
|
|
11
|
-
for &id in order.order.iter().take(k) {
|
|
12
|
-
let idx = id.0;
|
|
13
|
-
if marks[idx] != mark_gen {
|
|
14
|
-
marks[idx] = mark_gen;
|
|
15
|
-
work_stack.push(id);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/// Pop from the work stack; for each node mark its parent; continue until empty.
|
|
21
|
-
fn propagate_marks_to_ancestors(
|
|
22
|
-
parent: &[Option<NodeId>],
|
|
23
|
-
marks: &mut [u32],
|
|
24
|
-
mark_gen: u32,
|
|
25
|
-
work_stack: &mut Vec<NodeId>,
|
|
26
|
-
) {
|
|
27
|
-
while let Some(id) = work_stack.pop() {
|
|
28
|
-
let idx = id.0;
|
|
29
|
-
match parent[idx] {
|
|
30
|
-
Some(parent) if marks[parent.0] != mark_gen => {
|
|
31
|
-
marks[parent.0] = mark_gen;
|
|
32
|
-
work_stack.push(parent);
|
|
33
|
-
}
|
|
34
|
-
_ => {}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/// Mark the first `k` nodes by global order and all of their ancestors.
|
|
40
|
-
pub(crate) fn mark_top_k_and_ancestors(
|
|
41
|
-
order: &PriorityOrder,
|
|
42
|
-
k: usize,
|
|
43
|
-
marks: &mut [u32],
|
|
44
|
-
mark_gen: u32,
|
|
45
|
-
) {
|
|
46
|
-
let mut work_stack: Vec<NodeId> = Vec::new();
|
|
47
|
-
seed_stack_with_top_k(order, k, marks, mark_gen, &mut work_stack);
|
|
48
|
-
propagate_marks_to_ancestors(
|
|
49
|
-
&order.parent,
|
|
50
|
-
marks,
|
|
51
|
-
mark_gen,
|
|
52
|
-
&mut work_stack,
|
|
53
|
-
);
|
|
54
|
-
}
|
|
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
|
|
File without changes
|
|
File without changes
|
{headson-0.3.0 → headson-0.4.0}/src/snapshots/headson__order__tests__order_empty_array_order.snap
RENAMED
|
File without changes
|
|
File without changes
|
{headson-0.3.0 → headson-0.4.0}/src/snapshots/headson__order__tests__pq_empty_array_queue.snap
RENAMED
|
File without changes
|
|
File without changes
|
{headson-0.3.0 → headson-0.4.0}/src/snapshots/headson__queue__tests__pq_empty_array_queue.snap
RENAMED
|
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
|