Skip to main content

microcad_lang/model/attribute/
mod.rs

1// Copyright © 2025-2026 The µcad authors <info@microcad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! Model attributes.
5
6mod attributes;
7mod export_command;
8mod layer;
9mod measure_command;
10mod resolution_attribute;
11
12pub use attributes::Attributes;
13pub use export_command::ExportCommand;
14pub use layer::Layer;
15pub use measure_command::MeasureCommand;
16pub use resolution_attribute::ResolutionAttribute;
17
18use crate::{syntax::*, value::*};
19
20use microcad_core::{Color, Size2};
21
22/// A custom command attribute from an exporter, e.g.: `svg = (style = "fill:none")`
23#[derive(Clone, Debug)]
24pub struct CustomCommand {
25    /// Attribute id.
26    id: Identifier,
27    /// Argument tuple.
28    pub arguments: Box<Tuple>,
29}
30
31impl CustomCommand {
32    pub(crate) fn new(id: Identifier, arguments: Tuple) -> CustomCommand {
33        CustomCommand {
34            id,
35            arguments: arguments.into(),
36        }
37    }
38}
39
40impl Identifiable for CustomCommand {
41    fn id_ref(&self) -> &Identifier {
42        &self.id
43    }
44}
45
46impl std::fmt::Display for CustomCommand {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        write!(f, "{} = {}", self.id, self.arguments)
49    }
50}
51
52/// An attribute for a model.
53#[derive(Clone, Debug)]
54pub enum Attribute {
55    /// Color attribute: `color = "red"`
56    Color(Color),
57    /// Render resolution attribute: `resolution = 200%`.
58    Resolution(ResolutionAttribute),
59    /// Size attribute: `size = std::A4`.
60    Size(Size2),
61    /// Export command: `export = "test.svg"`.
62    Export(ExportCommand),
63    /// Measure command: `measure = width`
64    Measure(MeasureCommand),
65    /// Custom non-builtin attribute with tuples: svg = (fill = "color"))
66    Custom(CustomCommand),
67}
68
69impl Attribute {
70    /// Return an id for the attribute.
71    fn id(&self) -> Identifier {
72        match &self {
73            Attribute::Color(_) => Identifier::no_ref("color"),
74            Attribute::Resolution(_) => Identifier::no_ref("resolution"),
75            Attribute::Size(_) => Identifier::no_ref("size"),
76            Attribute::Export(_) => Identifier::no_ref("export"),
77            Attribute::Measure(_) => Identifier::no_ref("measure"),
78            Attribute::Custom(attr) => attr.id(),
79        }
80    }
81
82    /// If this method returns true, the attribute can only be set once.
83    pub fn is_unique(&self) -> bool {
84        matches!(
85            self,
86            Attribute::Color(_) | Attribute::Resolution(_) | Attribute::Size(_)
87        )
88    }
89}
90
91impl std::fmt::Display for Attribute {
92    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93        write!(
94            f,
95            "#[{id} = {value}]",
96            id = self.id(),
97            value = match &self {
98                Attribute::Color(color) => format!("{color}"),
99                Attribute::Resolution(resolution) => format!("{resolution}"),
100                Attribute::Size(size) => format!("{size}"),
101                Attribute::Export(export) => format!("{export}"),
102                Attribute::Measure(measure) => format!("{measure}"),
103                Attribute::Custom(command) => format!("{command}"),
104            }
105        )
106    }
107}
108
109/// This trait implementation is used to access values from an attribute.
110impl From<Attribute> for Value {
111    fn from(value: Attribute) -> Self {
112        match value {
113            Attribute::Color(color) => Value::Tuple(Box::new(color.into())),
114            Attribute::Resolution(resolution_attribute) => resolution_attribute.into(),
115            Attribute::Size(size) => size.into(),
116            Attribute::Export(e) => e.into(),
117            Attribute::Measure(m) => m.into(),
118            Attribute::Custom(attr) => Value::Tuple(attr.arguments.clone()),
119        }
120    }
121}
122
123impl PartialEq for Attribute {
124    fn eq(&self, other: &Self) -> bool {
125        self.id() == other.id()
126    }
127}
128
129/// Access an attributes value by id.
130pub trait AttributesAccess {
131    /// Get a value attribute by id.
132    fn get_attributes_by_id(&self, id: &Identifier) -> Vec<Attribute>;
133
134    /// Get a single attributes.
135    fn get_single_attribute(&self, id: &Identifier) -> Option<Attribute> {
136        let attributes = self.get_attributes_by_id(id);
137        match attributes.len() {
138            1 => attributes.first().cloned(),
139            _ => None,
140        }
141    }
142
143    /// Get single attribute as value.
144    fn get_attribute_value(&self, id: &Identifier) -> Value {
145        match self.get_single_attribute(id) {
146            Some(attribute) => attribute.into(),
147            None => Value::None,
148        }
149    }
150
151    /// Get resolution attribute.
152    fn get_resolution(&self) -> Option<ResolutionAttribute> {
153        match self.get_single_attribute(&Identifier::no_ref("resolution")) {
154            Some(value) => match value {
155                Attribute::Resolution(resolution) => Some(resolution),
156                _ => unreachable!(),
157            },
158            None => None,
159        }
160    }
161
162    /// Color (builtin attribute).
163    fn get_color(&self) -> Option<Color> {
164        match self.get_single_attribute(&Identifier::no_ref("color")) {
165            Some(value) => match value {
166                Attribute::Color(color) => Some(color),
167                _ => unreachable!(),
168            },
169            None => None,
170        }
171    }
172
173    /// Get size.
174    fn get_size(&self) -> Option<Size2> {
175        self.get_single_attribute(&Identifier::no_ref("size"))
176            .map(|attr| match attr {
177                Attribute::Size(size) => size,
178                _ => unreachable!(),
179            })
180    }
181
182    /// Get all export commands.
183    fn get_exports(&self) -> Vec<ExportCommand> {
184        self.get_attributes_by_id(&Identifier::no_ref("export"))
185            .into_iter()
186            .fold(Vec::new(), |mut exports, command| {
187                match command {
188                    Attribute::Export(export_command) => exports.push(export_command.clone()),
189                    _ => unreachable!(),
190                }
191                exports
192            })
193    }
194
195    /// Get all measure commands.
196    fn get_measures(&self) -> Vec<MeasureCommand> {
197        self.get_attributes_by_id(&Identifier::no_ref("measure"))
198            .iter()
199            .fold(Vec::new(), |mut measures, attribute| {
200                match attribute {
201                    Attribute::Measure(measure_command) => measures.push(measure_command.clone()),
202                    _ => unreachable!(),
203                }
204                measures
205            })
206    }
207
208    /// Get custom attributes.
209    fn get_custom_attributes(&self, id: &Identifier) -> Vec<Tuple> {
210        self.get_attributes_by_id(id)
211            .iter()
212            .fold(Vec::new(), |mut attributes, attribute| {
213                match attribute {
214                    Attribute::Custom(attr) => attributes.push(attr.arguments.as_ref().clone()),
215                    _ => unreachable!(),
216                }
217                attributes
218            })
219    }
220}