bc_envelope_pattern/
format.rs

1#![allow(dead_code)]
2
3use bc_envelope::{
4    base::envelope::EnvelopeCase, format::EnvelopeSummary, prelude::*,
5};
6
7use crate::Path;
8
9/// A builder that provides formatting options for each path element.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum PathElementFormat {
12    /// Summary format, with optional maximum length for truncation.
13    Summary(Option<usize>),
14    EnvelopeUR,
15    DigestUR,
16}
17
18impl Default for PathElementFormat {
19    fn default() -> Self { PathElementFormat::Summary(None) }
20}
21
22/// Options for formatting paths.
23#[derive(Debug, Clone)]
24pub struct FormatPathsOpts {
25    /// Whether to indent each path element.
26    /// If true, each element will be indented by 4 spaces per level.
27    indent: bool,
28
29    /// Format for each path element.
30    /// Default is `PathElementFormat::Summary(None)`.
31    element_format: PathElementFormat,
32
33    /// If true, only the last element of each path will be formatted.
34    /// This is useful for displaying only the final destination of a path.
35    /// If false, all elements will be formatted.
36    last_element_only: bool,
37}
38
39impl Default for FormatPathsOpts {
40    /// Returns the default formatting options:
41    /// - `indent`: true
42    /// - `element_format`: PathElementFormat::Summary(None)
43    /// - `last_element_only`: false
44    fn default() -> Self {
45        Self {
46            indent: true,
47            element_format: PathElementFormat::default(),
48            last_element_only: false,
49        }
50    }
51}
52
53impl FormatPathsOpts {
54    /// Creates a new FormatPathsOpts with default values.
55    pub fn new() -> Self { Self::default() }
56
57    /// Sets whether to indent each path element.
58    /// If true, each element will be indented by 4 spaces per level.
59    pub fn indent(mut self, indent: bool) -> Self {
60        self.indent = indent;
61        self
62    }
63
64    /// Sets the format for each path element.
65    /// Default is `PathElementFormat::Summary(None)`.
66    pub fn element_format(mut self, format: PathElementFormat) -> Self {
67        self.element_format = format;
68        self
69    }
70
71    /// Sets whether to format only the last element of each path.
72    /// If true, only the last element will be formatted.
73    /// If false, all elements will be formatted.
74    pub fn last_element_only(mut self, last_element_only: bool) -> Self {
75        self.last_element_only = last_element_only;
76        self
77    }
78}
79
80impl AsRef<FormatPathsOpts> for FormatPathsOpts {
81    fn as_ref(&self) -> &FormatPathsOpts { self }
82}
83
84pub fn envelope_summary(env: &Envelope) -> String {
85    let id = env.short_id(DigestDisplayFormat::Short);
86    let summary = match env.case() {
87        EnvelopeCase::Node { .. } => {
88            format!("NODE {}", env.format_flat())
89        }
90        EnvelopeCase::Leaf { cbor, .. } => {
91            format!(
92                "LEAF {}",
93                cbor.envelope_summary(usize::MAX, &FormatContextOpt::default())
94                    .unwrap_or_else(|_| "ERROR".to_string())
95            )
96        }
97        EnvelopeCase::Wrapped { .. } => {
98            format!("WRAPPED {}", env.format_flat())
99        }
100        EnvelopeCase::Assertion(_) => {
101            format!("ASSERTION {}", env.format_flat())
102        }
103        EnvelopeCase::Elided(_) => "ELIDED".to_string(),
104        EnvelopeCase::KnownValue { value, .. } => {
105            let content = with_format_context!(|ctx: &FormatContext| {
106                let known_value = KnownValuesStore::known_value_for_raw_value(
107                    value.value(),
108                    Some(ctx.known_values()),
109                );
110                format!("'{}'", known_value)
111            });
112            format!("KNOWN_VALUE {}", content)
113        }
114        EnvelopeCase::Encrypted(_) => "ENCRYPTED".to_string(),
115        EnvelopeCase::Compressed(_) => "COMPRESSED".to_string(),
116    };
117    format!("{} {}", id, summary)
118}
119
120/// Truncates a string to the specified maximum length, appending an ellipsis if
121/// truncated. If `max_length` is None, returns the original string.
122fn truncate_with_ellipsis(s: &str, max_length: Option<usize>) -> String {
123    match max_length {
124        Some(max_len) if s.len() > max_len => {
125            if max_len > 1 {
126                format!("{}…", &s[0..(max_len - 1)])
127            } else {
128                "…".to_string()
129            }
130        }
131        _ => s.to_string(),
132    }
133}
134
135/// Format each path element on its own line, each line successively indented by
136/// 4 spaces. Options can be provided to customize the formatting.
137pub fn format_path_opt(
138    path: &Path,
139    opts: impl AsRef<FormatPathsOpts>,
140) -> String {
141    let opts = opts.as_ref();
142
143    if opts.last_element_only {
144        // Only format the last element, no indentation.
145        if let Some(element) = path.iter().last() {
146            match opts.element_format {
147                PathElementFormat::Summary(max_length) => {
148                    let summary = envelope_summary(element);
149                    truncate_with_ellipsis(&summary, max_length)
150                }
151                PathElementFormat::EnvelopeUR => element.ur_string(),
152                PathElementFormat::DigestUR => element.digest().ur_string(),
153            }
154        } else {
155            String::new()
156        }
157    } else {
158        match opts.element_format {
159            PathElementFormat::Summary(max_length) => {
160            // Multi-line output with indentation for summaries.
161            let mut lines = Vec::new();
162            for (index, element) in path.iter().enumerate() {
163                let indent = if opts.indent {
164                " ".repeat(index * 4)
165                } else {
166                String::new()
167                };
168
169                let summary = envelope_summary(element);
170                let content = truncate_with_ellipsis(&summary, max_length);
171
172                lines.push(format!("{}{}", indent, content));
173            }
174            lines.join("\n")
175            }
176            PathElementFormat::EnvelopeUR => {
177            // Single-line, space-separated envelope URs.
178            path.iter()
179                .map(|element| element.ur_string())
180                .collect::<Vec<_>>()
181                .join(" ")
182            }
183            PathElementFormat::DigestUR => {
184            // Single-line, space-separated digest URs.
185            path.iter()
186                .map(|element| element.digest().ur_string())
187                .collect::<Vec<_>>()
188                .join(" ")
189            }
190        }
191    }
192}
193
194/// Format each path element on its own line, each line successively indented by
195/// 4 spaces.
196pub fn format_path(path: &Path) -> String {
197    format_path_opt(path, FormatPathsOpts::default())
198}
199
200/// Format multiple paths with custom formatting options.
201pub fn format_paths_opt(
202    paths: &[Path],
203    opts: impl AsRef<FormatPathsOpts>,
204) -> String {
205    let opts = opts.as_ref();
206    match opts.element_format {
207        PathElementFormat::EnvelopeUR | PathElementFormat::DigestUR => {
208            // Join all formatted paths with a space for UR formats.
209            paths
210                .iter()
211                .map(|path| format_path_opt(path, opts))
212                .collect::<Vec<_>>()
213                .join(" ")
214        }
215        PathElementFormat::Summary(_) => {
216            // Join all formatted paths with a newline for summary format.
217            paths
218                .iter()
219                .map(|path| format_path_opt(path, opts))
220                .collect::<Vec<_>>()
221                .join("\n")
222        }
223    }
224}
225
226/// Format multiple paths with default options.
227pub fn format_paths(paths: &[Path]) -> String {
228    format_paths_opt(paths, FormatPathsOpts::default())
229}