Skip to main content

vize_musea/
lib.rs

1//! # vize_musea
2//!
3//! Musea - Component gallery and documentation for Vize.
4//!
5//! ## Name Origin
6//!
7//! **Musea** (plural of museum) represents a gallery space where art is
8//! displayed and documented. Similarly, `vize_musea` provides a gallery
9//! for Vue components, allowing developers to view and interact with
10//! components in isolation - similar to Storybook.
11//!
12//! ## Concepts
13//!
14//! - **Art**: A component variation/state (replaces "story")
15//! - **Art file** (`*.art.vue`): File defining arts
16//! - **Gallery**: Display area for arts
17//! - **Palette**: Interactive controls panel
18//!
19//! ## Performance
20//!
21//! This crate is optimized for high performance:
22//! - **Zero-copy parsing**: All strings are borrowed from source
23//! - **Arena allocation**: Uses `vize_carton::Bump` for fast allocation
24//! - **Minimal allocations**: Only allocates when absolutely necessary
25//! - **Fast byte-level parsing**: Uses `memchr` and `memmem` for O(n) search
26//!
27//! ## Usage
28//!
29//! ```rust
30//! use vize_carton::Bump;
31//! use vize_musea::{parse_art, transform_to_csf};
32//! use vize_musea::types::ArtParseOptions;
33//!
34//! let allocator = Bump::new();
35//! let source = r#"
36//! <script setup lang="ts">
37//! defineArt("./Button.vue", { title: "Button" });
38//! </script>
39//!
40//! <art>
41//!   <variant name="Primary" default>
42//!     <Button variant="primary">Click me</Button>
43//!   </variant>
44//!   <variant name="Secondary">
45//!     <Button variant="secondary">Click me</Button>
46//!   </variant>
47//! </art>
48//! "#;
49//!
50//! // Parse Art file with arena allocator
51//! let art = parse_art(&allocator, source, ArtParseOptions::default()).unwrap();
52//!
53//! // Transform to Storybook CSF
54//! let csf = transform_to_csf(&art);
55//! println!("Generated: {}", csf.filename);
56//! ```
57//!
58//! ## Features
59//!
60//! - Zero-copy parsing of `*.art.vue` files
61//! - Type-safe variant definitions
62//! - Storybook CSF 3.0 export
63//! - Visual Regression Testing (VRT) support
64//! - Interactive props palette
65
66pub mod autogen;
67pub mod docs;
68pub mod palette;
69pub mod parse;
70pub mod tokens;
71pub mod transform;
72pub mod types;
73pub mod vrt;
74
75// Re-exports for convenience
76pub use parse::parse_art;
77pub use tokens::{
78    build_token_map, find_dependent_tokens, flatten_token_categories, generate_tokens_markdown,
79    parse_tokens_from_json, parse_tokens_from_path, parse_tokens_from_value,
80    resolve_token_categories, validate_reference,
81};
82pub use transform::{transform_to_csf, transform_to_vue};
83pub use types::{
84    ArtDescriptor, ArtDescriptorOwned, ArtMetadata, ArtMetadataOwned, ArtParseError,
85    ArtParseOptions, ArtParseResult, ArtScriptBlock, ArtScriptBlockOwned, ArtStatus, ArtStyleBlock,
86    ArtStyleBlockOwned, ArtVariant, ArtVariantOwned, CsfOutput, SourceLocation, ViewportConfig,
87};
88
89// Re-export vize_carton::Bump for convenience
90pub use vize_carton::Bump;
91
92/// Start the Musea component gallery server.
93///
94/// This function starts a development server that serves the component gallery UI.
95pub fn serve() {
96    todo!("Component gallery server for Vue SFC - implement with Vite plugin")
97}
98
99#[cfg(test)]
100mod tests {
101    use super::{
102        ArtDescriptorOwned, ArtParseOptions, Bump, parse_art, transform_to_csf, transform_to_vue,
103    };
104
105    #[test]
106    fn test_full_workflow() {
107        let allocator = Bump::new();
108        let source = r#"
109<script setup lang="ts">
110defineArt("./Button.vue", {
111  title: "Button",
112  description: "A versatile button component",
113  category: "atoms",
114  tags: ["ui", "input"],
115});
116</script>
117
118<art>
119  <variant name="Primary" default>
120    <Button variant="primary">Primary Button</Button>
121  </variant>
122  <variant name="Secondary">
123    <Button variant="secondary">Secondary Button</Button>
124  </variant>
125  <variant name="With Icon">
126    <Button variant="primary" icon="plus">Add Item</Button>
127  </variant>
128</art>
129
130<style scoped>
131.art-container {
132  padding: 20px;
133}
134</style>
135"#;
136
137        // Parse with arena allocator
138        let art = parse_art(
139            &allocator,
140            source,
141            ArtParseOptions {
142                filename: "Button.art.vue".into(),
143            },
144        )
145        .unwrap();
146
147        assert_eq!(art.metadata.title, "Button");
148        assert_eq!(
149            art.metadata.description,
150            Some("A versatile button component")
151        );
152        assert_eq!(art.metadata.category, Some("atoms"));
153        assert_eq!(art.metadata.tags.len(), 2);
154        assert_eq!(art.variants.len(), 3);
155        assert!(art.script_setup.is_some());
156        assert_eq!(art.styles.len(), 1);
157
158        // Transform to CSF
159        let csf = transform_to_csf(&art);
160        insta::assert_debug_snapshot!(csf);
161
162        // Transform to Vue
163        let vue = transform_to_vue(&art);
164        insta::assert_debug_snapshot!(vue);
165    }
166
167    #[test]
168    fn test_default_variant() {
169        let allocator = Bump::new();
170        let source = r#"
171<art title="Test">
172  <variant name="First">
173    <div>First</div>
174  </variant>
175  <variant name="Second" default>
176    <div>Second</div>
177  </variant>
178</art>
179"#;
180
181        let art = parse_art(&allocator, source, ArtParseOptions::default()).unwrap();
182        let default = art.default_variant().unwrap();
183        assert_eq!(default.name, "Second");
184    }
185
186    #[test]
187    fn test_into_owned() {
188        let allocator = Bump::new();
189        let source = r#"
190<art title="Button" component="./Button.vue">
191  <variant name="Primary">
192    <Button>Click</Button>
193  </variant>
194</art>
195"#;
196
197        let art = parse_art(&allocator, source, ArtParseOptions::default()).unwrap();
198        let owned: ArtDescriptorOwned = art.into_owned();
199
200        assert_eq!(owned.metadata.title, "Button");
201        assert_eq!(owned.variants.len(), 1);
202    }
203
204    #[test]
205    fn test_arena_efficiency() {
206        // Test that multiple parses can share an allocator
207        let allocator = Bump::new();
208
209        let sources = [
210            r#"<art title="A"><variant name="V1"><div>1</div></variant></art>"#,
211            r#"<art title="B"><variant name="V2"><div>2</div></variant></art>"#,
212            r#"<art title="C"><variant name="V3"><div>3</div></variant></art>"#,
213        ];
214
215        for source in sources {
216            let art = parse_art(&allocator, source, ArtParseOptions::default()).unwrap();
217            assert!(!art.metadata.title.is_empty());
218        }
219
220        // All allocations in single arena - efficient memory usage
221        // Arena is dropped when allocator goes out of scope
222    }
223}