Skip to main content

altium_format/
dump.rs

1//! Pretty-printing utilities for dumping Altium file structures.
2//!
3//! Provides a tree-structured dump format for visualizing PCB and schematic
4//! library contents in a human-readable way.
5
6use std::fmt::Write;
7
8use crate::types::ParameterCollection;
9
10/// Options for controlling dump output.
11#[derive(Debug, Clone, Default)]
12pub struct DumpOptions {
13    /// Show all parameter keys in the output.
14    pub show_keys: bool,
15}
16
17/// A builder for creating tree-structured output.
18#[derive(Default)]
19pub struct TreeBuilder {
20    output: String,
21    indent_stack: Vec<bool>, // true = has more siblings after, false = last in group
22    options: DumpOptions,
23}
24
25impl TreeBuilder {
26    /// Create a new tree builder.
27    pub fn new() -> Self {
28        Self::default()
29    }
30
31    /// Create a new tree builder with options.
32    pub fn with_options(options: DumpOptions) -> Self {
33        Self {
34            options,
35            ..Self::default()
36        }
37    }
38
39    /// Get the dump options.
40    pub fn options(&self) -> &DumpOptions {
41        &self.options
42    }
43
44    /// Check if show_keys is enabled.
45    pub fn show_keys(&self) -> bool {
46        self.options.show_keys
47    }
48
49    /// Get the current indentation prefix.
50    fn prefix(&self) -> String {
51        let mut s = String::new();
52        for (i, &has_more) in self.indent_stack.iter().enumerate() {
53            if i == self.indent_stack.len() - 1 {
54                if has_more {
55                    s.push_str("├── ");
56                } else {
57                    s.push_str("└── ");
58                }
59            } else if has_more {
60                s.push_str("│   ");
61            } else {
62                s.push_str("    ");
63            }
64        }
65        s
66    }
67
68    /// Get prefix for continuation lines (properties).
69    fn continuation_prefix(&self) -> String {
70        let mut s = String::new();
71        for &has_more in &self.indent_stack {
72            if has_more {
73                s.push_str("│   ");
74            } else {
75                s.push_str("    ");
76            }
77        }
78        s
79    }
80
81    /// Add a node with a title (creates a branch point).
82    pub fn begin_node(&mut self, title: &str) {
83        let prefix = self.prefix();
84        writeln!(&mut self.output, "{}{}", prefix, title).expect("writing to String cannot fail");
85    }
86
87    /// Add a leaf node with properties.
88    pub fn add_leaf(&mut self, title: &str, props: &[(&str, String)]) {
89        let prefix = self.prefix();
90        if props.is_empty() {
91            writeln!(&mut self.output, "{}{}", prefix, title)
92                .expect("writing to String cannot fail");
93        } else {
94            writeln!(&mut self.output, "{}{}", prefix, title)
95                .expect("writing to String cannot fail");
96            let cont = self.continuation_prefix();
97            for (name, value) in props {
98                writeln!(&mut self.output, "{}    {} = {}", cont, name, value)
99                    .expect("writing to String cannot fail");
100            }
101        }
102    }
103
104    /// Add a leaf node with properties and optional params (for --show-keys debug).
105    ///
106    /// When `show_keys` option is enabled, dumps all parameters from the collection.
107    /// Otherwise behaves like `add_leaf`.
108    pub fn add_leaf_with_params(
109        &mut self,
110        title: &str,
111        props: &[(&str, String)],
112        params: Option<&ParameterCollection>,
113    ) {
114        let prefix = self.prefix();
115        writeln!(&mut self.output, "{}{}", prefix, title).expect("writing to String cannot fail");
116        let cont = self.continuation_prefix();
117
118        // Always show the standard props
119        for (name, value) in props {
120            writeln!(&mut self.output, "{}    {} = {}", cont, name, value)
121                .expect("writing to String cannot fail");
122        }
123
124        // If show_keys is enabled and we have params, dump all keys
125        if self.options.show_keys {
126            if let Some(params) = params {
127                writeln!(&mut self.output, "{}    [all keys]", cont)
128                    .expect("writing to String cannot fail");
129                for (key, value) in params.iter() {
130                    writeln!(
131                        &mut self.output,
132                        "{}        {} = {}",
133                        cont,
134                        key,
135                        value.as_str()
136                    )
137                    .expect("writing to String cannot fail");
138                }
139            }
140        }
141    }
142
143    /// Push into a child group (for nesting).
144    pub fn push(&mut self, has_more_siblings: bool) {
145        self.indent_stack.push(has_more_siblings);
146    }
147
148    /// Pop out of a child group.
149    pub fn pop(&mut self) {
150        self.indent_stack.pop();
151    }
152
153    /// Add a root node (no prefix).
154    pub fn root(&mut self, title: &str) {
155        writeln!(&mut self.output, "{}", title).expect("writing to String cannot fail");
156    }
157
158    /// Consume and return the built string.
159    pub fn finish(self) -> String {
160        self.output
161    }
162}
163
164/// Trait for types that can dump themselves as a tree structure.
165pub trait DumpTree {
166    /// Dump this item to the tree builder.
167    fn dump(&self, tree: &mut TreeBuilder);
168
169    /// Convenience method to dump to a string.
170    fn dump_to_string(&self) -> String {
171        let mut tree = TreeBuilder::new();
172        self.dump(&mut tree);
173        tree.finish()
174    }
175
176    /// Convenience method to dump to a string with options.
177    fn dump_to_string_with_options(&self, options: DumpOptions) -> String {
178        let mut tree = TreeBuilder::with_options(options);
179        self.dump(&mut tree);
180        tree.finish()
181    }
182}
183
184/// Format a coordinate value nicely (in mils).
185pub fn fmt_coord(raw: i32) -> String {
186    let mils = raw as f64 / 10000.0;
187    if mils == mils.trunc() {
188        format!("{:.0} mil", mils)
189    } else {
190        format!("{:.2} mil", mils)
191    }
192}
193
194/// Format a coordinate point.
195pub fn fmt_point(x: i32, y: i32) -> String {
196    format!("({}, {})", fmt_coord(x), fmt_coord(y))
197}
198
199/// Format a coordinate point from Coord types.
200pub fn fmt_coord_point(pt: &crate::types::CoordPoint) -> String {
201    fmt_point(pt.x.to_raw(), pt.y.to_raw())
202}
203
204/// Format a single Coord value.
205pub fn fmt_coord_val(c: &crate::types::Coord) -> String {
206    fmt_coord(c.to_raw())
207}
208
209/// Format an angle in degrees.
210pub fn fmt_angle(degrees: f64) -> String {
211    format!("{:.1}°", degrees)
212}
213
214/// Format a layer.
215pub fn fmt_layer(layer: &crate::types::Layer) -> String {
216    layer.name().to_string()
217}
218
219/// Format a boolean as yes/no.
220pub fn fmt_bool(b: bool) -> String {
221    if b {
222        "yes".to_string()
223    } else {
224        "no".to_string()
225    }
226}