1#![doc = include_str!("../README.md")]
2#![deny(
3 clippy::unwrap_used,
4 clippy::expect_used,
5 clippy::print_stdout,
6 clippy::print_stderr
7)]
8#![allow(
9 clippy::multiple_crate_versions,
10 reason = "Dependency graph pulls distinct versions (e.g., yaml-rust2)."
11)]
12#![cfg_attr(
13 test,
14 allow(
15 clippy::unwrap_used,
16 clippy::expect_used,
17 reason = "tests may use unwrap/expect for brevity"
18 )
19)]
20
21use anyhow::Result;
22
23mod format;
24mod ingest;
25mod order;
26mod serialization;
27mod utils;
28pub use order::types::{ArrayBias, ArraySamplerStrategy};
29pub use order::{
30 NodeId, NodeKind, PriorityConfig, PriorityOrder, RankedNode, build_order,
31};
32
33pub use serialization::color::resolve_color_enabled;
34pub use serialization::types::{
35 ColorMode, OutputTemplate, RenderConfig, Style,
36};
37
38#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
39pub struct Budgets {
40 pub byte_budget: Option<usize>,
41 pub line_budget: Option<usize>,
42}
43
44pub fn headson(
45 input: Vec<u8>,
46 config: &RenderConfig,
47 priority_cfg: &PriorityConfig,
48 budget: usize,
49) -> Result<String> {
50 let arena = crate::ingest::parse_json_one(input, priority_cfg)?;
51 let order_build = order::build_order(&arena, priority_cfg)?;
52 let out = find_largest_render_under_budgets(
53 &order_build,
54 config,
55 Budgets {
56 byte_budget: Some(budget),
57 line_budget: None,
58 },
59 );
60 Ok(out)
61}
62
63pub fn headson_many(
64 inputs: Vec<(String, Vec<u8>)>,
65 config: &RenderConfig,
66 priority_cfg: &PriorityConfig,
67 budget: usize,
68) -> Result<String> {
69 let arena = crate::ingest::parse_json_many(inputs, priority_cfg)?;
70 let order_build = order::build_order(&arena, priority_cfg)?;
71 let out = find_largest_render_under_budgets(
72 &order_build,
73 config,
74 Budgets {
75 byte_budget: Some(budget),
76 line_budget: None,
77 },
78 );
79 Ok(out)
80}
81
82pub fn headson_yaml(
84 input: Vec<u8>,
85 config: &RenderConfig,
86 priority_cfg: &PriorityConfig,
87 budget: usize,
88) -> Result<String> {
89 let arena = crate::ingest::parse_yaml_one(input, priority_cfg)?;
90 let order_build = order::build_order(&arena, priority_cfg)?;
91 let out = find_largest_render_under_budgets(
92 &order_build,
93 config,
94 Budgets {
95 byte_budget: Some(budget),
96 line_budget: None,
97 },
98 );
99 Ok(out)
100}
101
102pub fn headson_many_yaml(
104 inputs: Vec<(String, Vec<u8>)>,
105 config: &RenderConfig,
106 priority_cfg: &PriorityConfig,
107 budget: usize,
108) -> Result<String> {
109 let arena = crate::ingest::parse_yaml_many(inputs, priority_cfg)?;
110 let order_build = order::build_order(&arena, priority_cfg)?;
111 let out = find_largest_render_under_budgets(
112 &order_build,
113 config,
114 Budgets {
115 byte_budget: Some(budget),
116 line_budget: None,
117 },
118 );
119 Ok(out)
120}
121
122pub fn headson_text(
124 input: Vec<u8>,
125 config: &RenderConfig,
126 priority_cfg: &PriorityConfig,
127 budget: usize,
128) -> Result<String> {
129 let arena = crate::ingest::parse_text_one(input, priority_cfg)?;
130 let order_build = order::build_order(&arena, priority_cfg)?;
131 let out = find_largest_render_under_budgets(
132 &order_build,
133 config,
134 Budgets {
135 byte_budget: Some(budget),
136 line_budget: None,
137 },
138 );
139 Ok(out)
140}
141
142pub fn headson_many_text(
144 inputs: Vec<(String, Vec<u8>)>,
145 config: &RenderConfig,
146 priority_cfg: &PriorityConfig,
147 budget: usize,
148) -> Result<String> {
149 let arena = crate::ingest::parse_text_many(inputs, priority_cfg)?;
150 let order_build = order::build_order(&arena, priority_cfg)?;
151 let out = find_largest_render_under_budgets(
152 &order_build,
153 config,
154 Budgets {
155 byte_budget: Some(budget),
156 line_budget: None,
157 },
158 );
159 Ok(out)
160}
161
162fn find_largest_render_under_budgets(
164 order_build: &PriorityOrder,
165 config: &RenderConfig,
166 budgets: Budgets,
167) -> String {
168 let total = order_build.total_nodes;
171 if total == 0 {
172 return String::new();
173 }
174 let lo = 1usize;
176 let hi = match budgets.byte_budget {
179 Some(c) => total.min(c.max(1)),
180 None => total,
181 };
182 let mut inclusion_flags: Vec<u32> = vec![0; total];
185 let mut render_set_id: u32 = 1;
187 let mut best_k: Option<usize> = None;
190 let mut measure_cfg = config.clone();
191 measure_cfg.color_enabled = false;
192
193 let _ = crate::utils::search::binary_search_max(lo, hi, |mid| {
194 let s = crate::serialization::render_top_k(
195 order_build,
196 mid,
197 &mut inclusion_flags,
198 render_set_id,
199 &measure_cfg,
200 );
201 render_set_id = render_set_id.wrapping_add(1).max(1);
202 let stats = crate::utils::measure::count_output_stats(&s);
205 let fits_chars = budgets.byte_budget.is_none_or(|c| stats.bytes <= c);
206 let fits_lines = budgets.line_budget.is_none_or(|l| stats.lines <= l);
207 if fits_chars && fits_lines {
208 best_k = Some(mid);
209 true
210 } else {
211 false
212 }
213 });
214
215 if let Some(k) = best_k {
216 crate::serialization::render_top_k(
218 order_build,
219 k,
220 &mut inclusion_flags,
221 render_set_id,
222 config,
223 )
224 } else {
225 crate::serialization::render_top_k(
228 order_build,
229 1,
230 &mut inclusion_flags,
231 render_set_id,
232 config,
233 )
234 }
235}
236
237pub fn headson_with_budgets(
239 input: Vec<u8>,
240 config: &RenderConfig,
241 priority_cfg: &PriorityConfig,
242 budgets: Budgets,
243) -> Result<String> {
244 let arena = crate::ingest::parse_json_one(input, priority_cfg)?;
245 let order_build = order::build_order(&arena, priority_cfg)?;
246 Ok(find_largest_render_under_budgets(
247 &order_build,
248 config,
249 budgets,
250 ))
251}
252
253pub fn headson_many_with_budgets(
254 inputs: Vec<(String, Vec<u8>)>,
255 config: &RenderConfig,
256 priority_cfg: &PriorityConfig,
257 budgets: Budgets,
258) -> Result<String> {
259 let arena = crate::ingest::parse_json_many(inputs, priority_cfg)?;
260 let order_build = order::build_order(&arena, priority_cfg)?;
261 Ok(find_largest_render_under_budgets(
262 &order_build,
263 config,
264 budgets,
265 ))
266}
267
268pub fn headson_yaml_with_budgets(
269 input: Vec<u8>,
270 config: &RenderConfig,
271 priority_cfg: &PriorityConfig,
272 budgets: Budgets,
273) -> Result<String> {
274 let arena = crate::ingest::parse_yaml_one(input, priority_cfg)?;
275 let order_build = order::build_order(&arena, priority_cfg)?;
276 Ok(find_largest_render_under_budgets(
277 &order_build,
278 config,
279 budgets,
280 ))
281}
282
283pub fn headson_many_yaml_with_budgets(
284 inputs: Vec<(String, Vec<u8>)>,
285 config: &RenderConfig,
286 priority_cfg: &PriorityConfig,
287 budgets: Budgets,
288) -> Result<String> {
289 let arena = crate::ingest::parse_yaml_many(inputs, priority_cfg)?;
290 let order_build = order::build_order(&arena, priority_cfg)?;
291 Ok(find_largest_render_under_budgets(
292 &order_build,
293 config,
294 budgets,
295 ))
296}
297
298pub fn headson_text_with_budgets(
299 input: Vec<u8>,
300 config: &RenderConfig,
301 priority_cfg: &PriorityConfig,
302 budgets: Budgets,
303) -> Result<String> {
304 let arena = crate::ingest::parse_text_one(input, priority_cfg)?;
305 let order_build = order::build_order(&arena, priority_cfg)?;
306 Ok(find_largest_render_under_budgets(
307 &order_build,
308 config,
309 budgets,
310 ))
311}
312
313pub fn headson_many_text_with_budgets(
314 inputs: Vec<(String, Vec<u8>)>,
315 config: &RenderConfig,
316 priority_cfg: &PriorityConfig,
317 budgets: Budgets,
318) -> Result<String> {
319 let arena = crate::ingest::parse_text_many(inputs, priority_cfg)?;
320 let order_build = order::build_order(&arena, priority_cfg)?;
321 Ok(find_largest_render_under_budgets(
322 &order_build,
323 config,
324 budgets,
325 ))
326}