#![allow(dead_code)]
use dcbor::prelude::*;
use crate::Path;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PathElementFormat {
DiagnosticSummary(Option<usize>),
DiagnosticFlat(Option<usize>),
}
impl Default for PathElementFormat {
fn default() -> Self { PathElementFormat::DiagnosticSummary(None) }
}
#[derive(Debug, Clone)]
pub struct FormatPathsOpts {
indent: bool,
element_format: PathElementFormat,
last_element_only: bool,
}
impl Default for FormatPathsOpts {
fn default() -> Self {
Self {
indent: true,
element_format: PathElementFormat::default(),
last_element_only: false,
}
}
}
impl FormatPathsOpts {
pub fn new() -> Self { Self::default() }
pub fn indent(mut self, indent: bool) -> Self {
self.indent = indent;
self
}
pub fn element_format(mut self, format: PathElementFormat) -> Self {
self.element_format = format;
self
}
pub fn last_element_only(mut self, last_element_only: bool) -> Self {
self.last_element_only = last_element_only;
self
}
}
impl AsRef<FormatPathsOpts> for FormatPathsOpts {
fn as_ref(&self) -> &FormatPathsOpts { self }
}
fn format_cbor_element(cbor: &CBOR, format: PathElementFormat) -> String {
match format {
PathElementFormat::DiagnosticSummary(max_length) => {
let diagnostic = cbor.summary();
truncate_with_ellipsis(&diagnostic, max_length)
}
PathElementFormat::DiagnosticFlat(max_length) => {
let diagnostic = cbor.diagnostic_flat();
truncate_with_ellipsis(&diagnostic, max_length)
}
}
}
fn truncate_with_ellipsis(s: &str, max_length: Option<usize>) -> String {
match max_length {
Some(max_len) if s.len() > max_len => {
if max_len > 1 {
format!("{}…", &s[0..(max_len - 1)])
} else {
"…".to_string()
}
}
_ => s.to_string(),
}
}
pub fn format_path_opt(
path: &Path,
opts: impl AsRef<FormatPathsOpts>,
) -> String {
let opts = opts.as_ref();
if opts.last_element_only {
if let Some(element) = path.iter().last() {
format_cbor_element(element, opts.element_format)
} else {
String::new()
}
} else {
match opts.element_format {
PathElementFormat::DiagnosticSummary(_)
| PathElementFormat::DiagnosticFlat(_) => {
let mut lines = Vec::new();
for (index, element) in path.iter().enumerate() {
let indent = if opts.indent {
" ".repeat(index * 4)
} else {
String::new()
};
let content =
format_cbor_element(element, opts.element_format);
lines.push(format!("{}{}", indent, content));
}
lines.join("\n")
}
}
}
}
pub fn format_path(path: &Path) -> String {
format_path_opt(path, FormatPathsOpts::default())
}
pub fn format_paths_with_captures(
paths: &[Path],
captures: &std::collections::HashMap<String, Vec<Path>>,
opts: impl AsRef<FormatPathsOpts>,
) -> String {
let opts = opts.as_ref();
let mut result = Vec::new();
let mut capture_names: Vec<&String> = captures.keys().collect();
capture_names.sort();
for capture_name in capture_names {
if let Some(capture_paths) = captures.get(capture_name) {
result.push(format!("@{}", capture_name));
for path in capture_paths {
let formatted_path = format_path_opt(path, opts);
for line in formatted_path.split('\n') {
if !line.is_empty() {
result.push(format!(" {}", line));
}
}
}
}
}
for path in paths {
let formatted_path = format_path_opt(path, opts);
for line in formatted_path.split('\n') {
if !line.is_empty() {
result.push(line.to_string());
}
}
}
result.join("\n")
}
pub fn format_paths_opt(
paths: &[Path],
opts: impl AsRef<FormatPathsOpts>,
) -> String {
format_paths_with_captures(paths, &std::collections::HashMap::new(), opts)
}
pub fn format_paths(paths: &[Path]) -> String {
format_paths_opt(paths, FormatPathsOpts::default())
}
#[cfg(test)]
mod tests {
use dcbor::prelude::*;
use super::*;
fn create_test_path() -> Path {
vec![
CBOR::from(42),
CBOR::from("test"),
CBOR::from(vec![1, 2, 3]),
]
}
#[test]
fn test_format_path_default() {
let path = create_test_path();
let formatted = format_path(&path);
assert!(formatted.contains("42"));
assert!(formatted.contains("\"test\""));
assert!(formatted.contains("[1, 2, 3]"));
}
#[test]
fn test_format_path_flat() {
let path = create_test_path();
let opts = FormatPathsOpts::new()
.element_format(PathElementFormat::DiagnosticFlat(None));
let formatted = format_path_opt(&path, opts);
assert!(formatted.contains("42"));
assert!(formatted.contains("\"test\""));
assert!(formatted.contains("[1, 2, 3]"));
}
#[test]
fn test_format_path_last_element_only() {
let path = create_test_path();
let opts = FormatPathsOpts::new().last_element_only(true);
let formatted = format_path_opt(&path, opts);
assert!(!formatted.contains("42"));
assert!(!formatted.contains("\"test\""));
assert!(formatted.contains("[1, 2, 3]"));
}
#[test]
fn test_truncate_with_ellipsis() {
assert_eq!(truncate_with_ellipsis("hello", None), "hello");
assert_eq!(truncate_with_ellipsis("hello", Some(10)), "hello");
assert_eq!(truncate_with_ellipsis("hello world", Some(5)), "hell…");
assert_eq!(truncate_with_ellipsis("hello", Some(1)), "…");
}
#[test]
fn test_format_paths_multiple() {
let path1 = vec![CBOR::from(1)];
let path2 = vec![CBOR::from(2)];
let paths = vec![path1, path2];
let formatted = format_paths(&paths);
let lines: Vec<&str> = formatted.split('\n').collect();
assert_eq!(lines.len(), 2);
assert!(lines[0].contains("1"));
assert!(lines[1].contains("2"));
}
#[test]
fn test_format_paths_with_captures() {
use std::collections::HashMap;
let path1 = vec![CBOR::from(1)];
let path2 = vec![CBOR::from(2)];
let paths = vec![path1.clone(), path2.clone()];
let mut captures = HashMap::new();
captures.insert("capture1".to_string(), vec![path1]);
captures.insert("capture2".to_string(), vec![path2]);
let formatted = format_paths_with_captures(
&paths,
&captures,
FormatPathsOpts::default(),
);
let lines: Vec<&str> = formatted.split('\n').collect();
assert!(lines[0] == "@capture1");
assert!(lines[1].contains(" 1")); assert!(lines[2] == "@capture2");
assert!(lines[3].contains(" 2")); assert!(lines[4].contains("1")); assert!(lines[5].contains("2")); }
#[test]
fn test_format_paths_with_empty_captures() {
use std::collections::HashMap;
let path1 = vec![CBOR::from(1)];
let path2 = vec![CBOR::from(2)];
let paths = vec![path1, path2];
let captures = HashMap::new();
let formatted = format_paths_with_captures(
&paths,
&captures,
FormatPathsOpts::default(),
);
let expected = format_paths(&paths);
assert_eq!(formatted, expected);
}
#[test]
fn test_capture_names_sorted() {
use std::collections::HashMap;
let path1 = vec![CBOR::from(1)];
let path2 = vec![CBOR::from(2)];
let path3 = vec![CBOR::from(3)];
let paths = vec![];
let mut captures = HashMap::new();
captures.insert("zebra".to_string(), vec![path1]);
captures.insert("alpha".to_string(), vec![path2]);
captures.insert("beta".to_string(), vec![path3]);
let formatted = format_paths_with_captures(
&paths,
&captures,
FormatPathsOpts::default(),
);
let lines: Vec<&str> = formatted.split('\n').collect();
assert!(lines[0] == "@alpha");
assert!(lines[2] == "@beta");
assert!(lines[4] == "@zebra");
}
}