1#![allow(dead_code)]
43
44use crate::Path;
45use dcbor::prelude::*;
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum PathElementFormat {
50 DiagnosticSummary(Option<usize>),
52 DiagnosticFlat(Option<usize>),
55}
56
57impl Default for PathElementFormat {
58 fn default() -> Self { PathElementFormat::DiagnosticSummary(None) }
59}
60
61#[derive(Debug, Clone)]
63pub struct FormatPathsOpts {
64 indent: bool,
67
68 element_format: PathElementFormat,
71
72 last_element_only: bool,
76}
77
78impl Default for FormatPathsOpts {
79 fn default() -> Self {
84 Self {
85 indent: true,
86 element_format: PathElementFormat::default(),
87 last_element_only: false,
88 }
89 }
90}
91
92impl FormatPathsOpts {
93 pub fn new() -> Self { Self::default() }
95
96 pub fn indent(mut self, indent: bool) -> Self {
99 self.indent = indent;
100 self
101 }
102
103 pub fn element_format(mut self, format: PathElementFormat) -> Self {
106 self.element_format = format;
107 self
108 }
109
110 pub fn last_element_only(mut self, last_element_only: bool) -> Self {
114 self.last_element_only = last_element_only;
115 self
116 }
117}
118
119impl AsRef<FormatPathsOpts> for FormatPathsOpts {
120 fn as_ref(&self) -> &FormatPathsOpts { self }
121}
122
123fn format_cbor_element(
125 cbor: &CBOR,
126 format: PathElementFormat,
127) -> String {
128 match format {
129 PathElementFormat::DiagnosticSummary(max_length) => {
130 let diagnostic = cbor.summary();
131 truncate_with_ellipsis(&diagnostic, max_length)
132 }
133 PathElementFormat::DiagnosticFlat(max_length) => {
134 let diagnostic = cbor.diagnostic_flat();
135 truncate_with_ellipsis(&diagnostic, max_length)
136 }
137 }
138}
139
140fn truncate_with_ellipsis(s: &str, max_length: Option<usize>) -> String {
143 match max_length {
144 Some(max_len) if s.len() > max_len => {
145 if max_len > 1 {
146 format!("{}…", &s[0..(max_len - 1)])
147 } else {
148 "…".to_string()
149 }
150 }
151 _ => s.to_string(),
152 }
153}
154
155pub fn format_path_opt(
158 path: &Path,
159 opts: impl AsRef<FormatPathsOpts>,
160) -> String {
161 let opts = opts.as_ref();
162
163 if opts.last_element_only {
164 if let Some(element) = path.iter().last() {
166 format_cbor_element(element, opts.element_format)
167 } else {
168 String::new()
169 }
170 } else {
171 match opts.element_format {
172 PathElementFormat::DiagnosticSummary(_)
173 | PathElementFormat::DiagnosticFlat(_) => {
174 let mut lines = Vec::new();
176 for (index, element) in path.iter().enumerate() {
177 let indent = if opts.indent {
178 " ".repeat(index * 4)
179 } else {
180 String::new()
181 };
182
183 let content =
184 format_cbor_element(element, opts.element_format);
185 lines.push(format!("{}{}", indent, content));
186 }
187 lines.join("\n")
188 }
189 }
190 }
191}
192
193pub fn format_path(path: &Path) -> String {
196 format_path_opt(path, FormatPathsOpts::default())
197}
198
199pub fn format_paths_with_captures(
203 paths: &[Path],
204 captures: &std::collections::HashMap<String, Vec<Path>>,
205 opts: impl AsRef<FormatPathsOpts>,
206) -> String {
207 let opts = opts.as_ref();
208 let mut result = Vec::new();
209
210 let mut capture_names: Vec<&String> = captures.keys().collect();
212 capture_names.sort();
213
214 for capture_name in capture_names {
215 if let Some(capture_paths) = captures.get(capture_name) {
216 result.push(format!("@{}", capture_name));
217 for path in capture_paths {
218 let formatted_path = format_path_opt(path, opts);
219 for line in formatted_path.split('\n') {
221 if !line.is_empty() {
222 result.push(format!(" {}", line));
223 }
224 }
225 }
226 }
227 }
228
229 for path in paths {
231 let formatted_path = format_path_opt(path, opts);
232 for line in formatted_path.split('\n') {
233 if !line.is_empty() {
234 result.push(line.to_string());
235 }
236 }
237 }
238
239 result.join("\n")
240}
241
242pub fn format_paths_opt(
244 paths: &[Path],
245 opts: impl AsRef<FormatPathsOpts>,
246) -> String {
247 format_paths_with_captures(paths, &std::collections::HashMap::new(), opts)
249}
250
251pub fn format_paths(paths: &[Path]) -> String {
253 format_paths_opt(paths, FormatPathsOpts::default())
254}
255
256#[cfg(test)]
257mod tests {
258 use dcbor::prelude::*;
259
260 use super::*;
261
262 fn create_test_path() -> Path {
263 vec![
264 CBOR::from(42),
265 CBOR::from("test"),
266 CBOR::from(vec![1, 2, 3]),
267 ]
268 }
269
270 #[test]
271 fn test_format_path_default() {
272 let path = create_test_path();
273 let formatted = format_path(&path);
274
275 assert!(formatted.contains("42"));
277 assert!(formatted.contains("\"test\""));
278 assert!(formatted.contains("[1, 2, 3]"));
279 }
280
281 #[test]
282 fn test_format_path_flat() {
283 let path = create_test_path();
284 let opts = FormatPathsOpts::new()
285 .element_format(PathElementFormat::DiagnosticFlat(None));
286 let formatted = format_path_opt(&path, opts);
287
288 assert!(formatted.contains("42"));
290 assert!(formatted.contains("\"test\""));
291 assert!(formatted.contains("[1, 2, 3]"));
292 }
293
294 #[test]
295 fn test_format_path_last_element_only() {
296 let path = create_test_path();
297 let opts = FormatPathsOpts::new().last_element_only(true);
298 let formatted = format_path_opt(&path, opts);
299
300 assert!(!formatted.contains("42"));
302 assert!(!formatted.contains("\"test\""));
303 assert!(formatted.contains("[1, 2, 3]"));
304 }
305
306 #[test]
307 fn test_truncate_with_ellipsis() {
308 assert_eq!(truncate_with_ellipsis("hello", None), "hello");
309 assert_eq!(truncate_with_ellipsis("hello", Some(10)), "hello");
310 assert_eq!(truncate_with_ellipsis("hello world", Some(5)), "hell…");
311 assert_eq!(truncate_with_ellipsis("hello", Some(1)), "…");
312 }
313
314 #[test]
315 fn test_format_paths_multiple() {
316 let path1 = vec![CBOR::from(1)];
317 let path2 = vec![CBOR::from(2)];
318 let paths = vec![path1, path2];
319
320 let formatted = format_paths(&paths);
321 let lines: Vec<&str> = formatted.split('\n').collect();
322
323 assert_eq!(lines.len(), 2);
324 assert!(lines[0].contains("1"));
325 assert!(lines[1].contains("2"));
326 }
327
328 #[test]
329 fn test_format_paths_with_captures() {
330 use std::collections::HashMap;
331
332 let path1 = vec![CBOR::from(1)];
333 let path2 = vec![CBOR::from(2)];
334 let paths = vec![path1.clone(), path2.clone()];
335
336 let mut captures = HashMap::new();
337 captures.insert("capture1".to_string(), vec![path1]);
338 captures.insert("capture2".to_string(), vec![path2]);
339
340 let formatted = format_paths_with_captures(
341 &paths,
342 &captures,
343 FormatPathsOpts::default(),
344 );
345 let lines: Vec<&str> = formatted.split('\n').collect();
346
347 assert!(lines[0] == "@capture1");
349 assert!(lines[1].contains(" 1")); assert!(lines[2] == "@capture2");
351 assert!(lines[3].contains(" 2")); assert!(lines[4].contains("1")); assert!(lines[5].contains("2")); }
355
356 #[test]
357 fn test_format_paths_with_empty_captures() {
358 use std::collections::HashMap;
359
360 let path1 = vec![CBOR::from(1)];
361 let path2 = vec![CBOR::from(2)];
362 let paths = vec![path1, path2];
363
364 let captures = HashMap::new();
365 let formatted = format_paths_with_captures(
366 &paths,
367 &captures,
368 FormatPathsOpts::default(),
369 );
370
371 let expected = format_paths(&paths);
373 assert_eq!(formatted, expected);
374 }
375
376 #[test]
377 fn test_capture_names_sorted() {
378 use std::collections::HashMap;
379
380 let path1 = vec![CBOR::from(1)];
381 let path2 = vec![CBOR::from(2)];
382 let path3 = vec![CBOR::from(3)];
383 let paths = vec![];
384
385 let mut captures = HashMap::new();
386 captures.insert("zebra".to_string(), vec![path1]);
387 captures.insert("alpha".to_string(), vec![path2]);
388 captures.insert("beta".to_string(), vec![path3]);
389
390 let formatted = format_paths_with_captures(
391 &paths,
392 &captures,
393 FormatPathsOpts::default(),
394 );
395 let lines: Vec<&str> = formatted.split('\n').collect();
396
397 assert!(lines[0] == "@alpha");
399 assert!(lines[2] == "@beta");
400 assert!(lines[4] == "@zebra");
401 }
402}