1use serde::{Deserialize, Serialize};
9use vize_carton::{Bump, FxHashMap, String, ToCompactString, Vec as BumpVec};
10
11#[derive(Debug)]
16pub struct ArtDescriptor<'a> {
17 pub filename: &'a str,
19
20 pub source: &'a str,
22
23 pub metadata: ArtMetadata<'a>,
25
26 pub variants: BumpVec<'a, ArtVariant<'a>>,
28
29 pub script_setup: Option<ArtScriptBlock<'a>>,
31
32 pub script: Option<ArtScriptBlock<'a>>,
34
35 pub styles: BumpVec<'a, ArtStyleBlock<'a>>,
37}
38
39#[derive(Debug)]
41pub struct ArtMetadata<'a> {
42 pub title: &'a str,
44
45 pub description: Option<&'a str>,
47
48 pub component: Option<&'a str>,
50
51 pub category: Option<&'a str>,
53
54 pub tags: BumpVec<'a, &'a str>,
56
57 pub status: ArtStatus,
59
60 pub order: Option<u32>,
62}
63
64#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
66#[serde(rename_all = "lowercase")]
67pub enum ArtStatus {
68 Draft,
70 #[default]
72 Ready,
73 Deprecated,
75}
76
77#[derive(Debug)]
79pub struct ArtVariant<'a> {
80 pub name: &'a str,
82
83 pub template: &'a str,
85
86 pub is_default: bool,
88
89 pub args: FxHashMap<&'a str, serde_json::Value>,
91
92 pub viewport: Option<ViewportConfig>,
94
95 pub skip_vrt: bool,
97
98 pub loc: Option<SourceLocation>,
100}
101
102#[derive(Debug)]
104pub struct ArtScriptBlock<'a> {
105 pub content: &'a str,
107
108 pub lang: Option<&'a str>,
110
111 pub setup: bool,
113
114 pub loc: Option<SourceLocation>,
116}
117
118#[derive(Debug)]
120pub struct ArtStyleBlock<'a> {
121 pub content: &'a str,
123
124 pub lang: Option<&'a str>,
126
127 pub scoped: bool,
129
130 pub loc: Option<SourceLocation>,
132}
133
134#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
136#[serde(rename_all = "camelCase")]
137pub struct ViewportConfig {
138 pub width: u32,
140 pub height: u32,
142 pub device_scale_factor: Option<f32>,
144}
145
146#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
148pub struct SourceLocation {
149 pub start: u32,
151 pub end: u32,
153 pub start_line: u32,
155 pub start_column: u32,
157}
158
159#[derive(Debug, Clone, Default)]
161pub struct ArtParseOptions {
162 pub filename: String,
164}
165
166pub type ArtParseResult<'a> = Result<ArtDescriptor<'a>, ArtParseError>;
168
169#[derive(Debug, Clone, thiserror::Error)]
171pub enum ArtParseError {
172 #[error("Missing required 'title' attribute in <art> block")]
173 MissingTitle,
174
175 #[error("Missing required 'name' attribute in <variant> block at line {line}")]
176 MissingVariantName { line: u32 },
177
178 #[error("No <art> block found in file")]
179 NoArtBlock,
180
181 #[error("Invalid attribute value for '{attr}': {message}")]
182 InvalidAttribute { attr: String, message: String },
183
184 #[error("Parse error at line {line}: {message}")]
185 ParseError { line: u32, message: String },
186}
187
188#[derive(Debug, Clone, Serialize, Deserialize)]
190#[serde(rename_all = "camelCase")]
191pub struct CsfOutput {
192 pub code: String,
194 pub filename: String,
196}
197
198impl<'a> ArtDescriptor<'a> {
199 #[inline]
201 pub fn new(allocator: &'a Bump, filename: &'a str, source: &'a str) -> Self {
202 Self {
203 filename,
204 source,
205 metadata: ArtMetadata::new(allocator),
206 variants: BumpVec::new_in(allocator),
207 script_setup: None,
208 script: None,
209 styles: BumpVec::new_in(allocator),
210 }
211 }
212
213 #[inline]
215 pub fn default_variant(&self) -> Option<&ArtVariant<'a>> {
216 self.variants
217 .iter()
218 .find(|v| v.is_default)
219 .or_else(|| self.variants.first())
220 }
221}
222
223impl<'a> ArtMetadata<'a> {
224 #[inline]
226 pub fn new(allocator: &'a Bump) -> Self {
227 Self {
228 title: "",
229 description: None,
230 component: None,
231 category: None,
232 tags: BumpVec::new_in(allocator),
233 status: ArtStatus::default(),
234 order: None,
235 }
236 }
237}
238
239impl<'a> ArtVariant<'a> {
240 #[inline]
242 pub fn new(name: &'a str, template: &'a str) -> Self {
243 Self {
244 name,
245 template,
246 is_default: false,
247 args: FxHashMap::default(),
248 viewport: None,
249 skip_vrt: false,
250 loc: None,
251 }
252 }
253}
254
255impl Default for ViewportConfig {
256 #[inline]
257 fn default() -> Self {
258 Self {
259 width: 1280,
260 height: 720,
261 device_scale_factor: Some(1.0),
262 }
263 }
264}
265
266impl SourceLocation {
267 #[inline]
269 pub const fn new(start: u32, end: u32, start_line: u32, start_column: u32) -> Self {
270 Self {
271 start,
272 end,
273 start_line,
274 start_column,
275 }
276 }
277}
278
279#[derive(Debug, Clone, Serialize, Deserialize)]
285#[serde(rename_all = "camelCase")]
286pub struct ArtDescriptorOwned {
287 pub filename: String,
288 pub source: String,
289 pub metadata: ArtMetadataOwned,
290 pub variants: Vec<ArtVariantOwned>,
291 pub script_setup: Option<ArtScriptBlockOwned>,
292 pub script: Option<ArtScriptBlockOwned>,
293 pub styles: Vec<ArtStyleBlockOwned>,
294}
295
296#[derive(Debug, Clone, Serialize, Deserialize)]
297#[serde(rename_all = "camelCase")]
298pub struct ArtMetadataOwned {
299 pub title: String,
300 pub description: Option<String>,
301 pub component: Option<String>,
302 pub category: Option<String>,
303 pub tags: Vec<String>,
304 pub status: ArtStatus,
305 pub order: Option<u32>,
306}
307
308#[derive(Debug, Clone, Serialize, Deserialize)]
309#[serde(rename_all = "camelCase")]
310pub struct ArtVariantOwned {
311 pub name: String,
312 pub template: String,
313 pub is_default: bool,
314 pub args: FxHashMap<String, serde_json::Value>,
315 pub viewport: Option<ViewportConfig>,
316 pub skip_vrt: bool,
317 pub loc: Option<SourceLocation>,
318}
319
320#[derive(Debug, Clone, Serialize, Deserialize)]
321#[serde(rename_all = "camelCase")]
322pub struct ArtScriptBlockOwned {
323 pub content: String,
324 pub lang: Option<String>,
325 pub setup: bool,
326 pub loc: Option<SourceLocation>,
327}
328
329#[derive(Debug, Clone, Serialize, Deserialize)]
330#[serde(rename_all = "camelCase")]
331pub struct ArtStyleBlockOwned {
332 pub content: String,
333 pub lang: Option<String>,
334 pub scoped: bool,
335 pub loc: Option<SourceLocation>,
336}
337
338impl<'a> ArtDescriptor<'a> {
339 pub fn into_owned(self) -> ArtDescriptorOwned {
341 ArtDescriptorOwned {
342 filename: self.filename.to_compact_string(),
343 source: self.source.to_compact_string(),
344 metadata: self.metadata.into_owned(),
345 variants: self.variants.into_iter().map(|v| v.into_owned()).collect(),
346 script_setup: self.script_setup.map(|s| s.into_owned()),
347 script: self.script.map(|s| s.into_owned()),
348 styles: self.styles.into_iter().map(|s| s.into_owned()).collect(),
349 }
350 }
351}
352
353impl<'a> ArtMetadata<'a> {
354 pub fn into_owned(self) -> ArtMetadataOwned {
356 ArtMetadataOwned {
357 title: self.title.to_compact_string(),
358 description: self.description.map(|s| s.to_compact_string()),
359 component: self.component.map(|s| s.to_compact_string()),
360 category: self.category.map(|s| s.to_compact_string()),
361 tags: self
362 .tags
363 .into_iter()
364 .map(|s| s.to_compact_string())
365 .collect(),
366 status: self.status,
367 order: self.order,
368 }
369 }
370}
371
372impl<'a> ArtVariant<'a> {
373 pub fn into_owned(self) -> ArtVariantOwned {
375 ArtVariantOwned {
376 name: self.name.to_compact_string(),
377 template: self.template.to_compact_string(),
378 is_default: self.is_default,
379 args: self
380 .args
381 .into_iter()
382 .map(|(k, v)| (k.to_compact_string(), v))
383 .collect(),
384 viewport: self.viewport,
385 skip_vrt: self.skip_vrt,
386 loc: self.loc,
387 }
388 }
389}
390
391impl<'a> ArtScriptBlock<'a> {
392 pub fn into_owned(self) -> ArtScriptBlockOwned {
394 ArtScriptBlockOwned {
395 content: self.content.to_compact_string(),
396 lang: self.lang.map(|s| s.to_compact_string()),
397 setup: self.setup,
398 loc: self.loc,
399 }
400 }
401}
402
403impl<'a> ArtStyleBlock<'a> {
404 pub fn into_owned(self) -> ArtStyleBlockOwned {
406 ArtStyleBlockOwned {
407 content: self.content.to_compact_string(),
408 lang: self.lang.map(|s| s.to_compact_string()),
409 scoped: self.scoped,
410 loc: self.loc,
411 }
412 }
413}
414
415#[cfg(test)]
416mod tests {
417 use super::{ArtDescriptor, ArtStatus, ViewportConfig};
418 use vize_carton::Bump;
419
420 #[test]
421 fn test_art_descriptor_new() {
422 let allocator = Bump::new();
423 let desc = ArtDescriptor::new(&allocator, "test.art.vue", "<art></art>");
424 assert_eq!(desc.filename, "test.art.vue");
425 assert!(desc.variants.is_empty());
426 }
427
428 #[test]
429 fn test_art_status_default() {
430 assert_eq!(ArtStatus::default(), ArtStatus::Ready);
431 }
432
433 #[test]
434 fn test_viewport_default() {
435 let vp = ViewportConfig::default();
436 assert_eq!(vp.width, 1280);
437 assert_eq!(vp.height, 720);
438 }
439}