orbiton/templates/
components.rs1use liquid::model::{ArrayView, DisplayCow, ObjectView, ScalarCow, Value, ValueView};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::fmt::Debug;
5use std::fmt::Display;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub enum PropType {
9 String,
10 Number,
11 Boolean,
12 Array(Box<PropType>),
13 Object(HashMap<String, PropType>),
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct Component {
18 pub name: String,
19 pub template: String,
20 pub props: HashMap<String, PropType>,
21 pub description: String,
22}
23
24#[allow(dead_code)]
25impl Component {
26 pub fn new(
27 name: String,
28 template: String,
29 props: HashMap<String, PropType>,
30 description: String,
31 ) -> Self {
32 Self {
33 name,
34 template,
35 props,
36 description,
37 }
38 }
39
40 pub fn add_prop(&mut self, name: String, prop_type: PropType) {
41 self.props.insert(name, prop_type);
42 }
43
44 pub fn generate_code(&self) -> String {
45 format!(
47 "Component {{ name: {}, props: [{}] }}",
48 self.name,
49 self.props.keys().cloned().collect::<Vec<_>>().join(", ")
50 )
51 }
52}
53
54impl Display for Component {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 write!(f, "{} ({})", self.name, self.description)
57 }
58}
59
60impl ValueView for Component {
61 fn as_debug(&self) -> &dyn Debug {
62 self
63 }
64
65 fn render(&self) -> DisplayCow<'_> {
66 DisplayCow::Owned(Box::new(self.to_string()))
67 }
68
69 fn source(&self) -> DisplayCow<'_> {
70 DisplayCow::Owned(Box::new(self.to_string()))
71 }
72
73 fn type_name(&self) -> &'static str {
74 "component"
75 }
76
77 fn query_state(&self, state: liquid::model::State) -> bool {
78 match state {
79 liquid::model::State::Truthy => true,
80 liquid::model::State::DefaultValue => false,
81 liquid::model::State::Empty => false,
82 _ => false,
83 }
84 }
85
86 fn to_kstr(&self) -> liquid::model::KStringCow<'_> {
87 liquid::model::KStringCow::from_string(self.to_string())
88 }
89
90 fn to_value(&self) -> Value {
91 Value::Nil
92 }
93
94 fn as_object(&self) -> Option<&dyn ObjectView> {
95 None
96 }
97
98 fn as_array(&self) -> Option<&dyn ArrayView> {
99 None
100 }
101
102 fn as_scalar(&self) -> Option<ScalarCow<'_>> {
103 Some(ScalarCow::new(self.name.as_str()))
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn test_simple_component() {
113 let mut component = Component::new(
114 "Button".to_string(),
115 "button".to_string(),
116 HashMap::new(),
117 "A simple button component".to_string(),
118 );
119 component.add_prop("disabled".to_string(), PropType::Boolean);
120 component.add_prop("label".to_string(), PropType::String);
121
122 let code = component.generate_code();
123 assert!(code.contains("disabled"));
124 assert!(code.contains("label"));
125 }
126
127 #[test]
128 fn test_complex_component() {
129 let mut user_type = HashMap::new();
130 user_type.insert("name".to_string(), PropType::String);
131 user_type.insert("age".to_string(), PropType::Number);
132
133 let mut component = Component::new(
134 "UserCard".to_string(),
135 "user-card".to_string(),
136 HashMap::new(),
137 "A user card component".to_string(),
138 );
139 component.add_prop(
140 "tags".to_string(),
141 PropType::Array(Box::new(PropType::String)),
142 );
143 component.add_prop("user".to_string(), PropType::Object(user_type));
144
145 let code = component.generate_code();
146 assert!(code.contains("tags"));
147 assert!(code.contains("user"));
148 }
149}