microcad_lang/model/attribute/
mod.rs

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