rusty_css/
lib.rs

1//! This crate can be used to convert rust structs into css, while maintaining
2//! the ability to access each field individually.
3
4//mod keywords;
5//use keywords::Pseudo;
6use bevy_reflect::{Reflect, Struct, ReflectMut, List};
7use std::num::ParseFloatError;
8use web_sys::{ Document };
9use substring::*;
10use regex::Regex;
11
12// add a smart way to extract the containing float value within a string 
13pub trait ExtractNums {
14
15    // tries to extract the numbers from whatever string is given.
16    fn try_extract_nums(&self) -> Option<String>;
17
18    // tries to cast any given string to an f64
19    fn try_to_f64(&self) -> Result<f64, ParseFloatError>;
20}
21
22impl ExtractNums for String {
23    fn try_extract_nums(&self) -> Option<String> {
24        Some( self.chars().filter(|c| c.is_digit(10) || c == &'.' || c == &'-').collect() )
25    }
26
27    fn try_to_f64(&self) -> Result<f64, ParseFloatError> {
28        self.try_extract_nums().unwrap().parse::<f64>()
29    }
30}
31
32// implementations for converting fields in a struct to a css string
33pub trait Style: Reflect + Struct {
34
35    // constructor
36    fn create() -> Self;
37
38    fn set_string_reflect(string_reflect: &mut dyn Reflect, value: &str) where Self: Sized {
39        *string_reflect.downcast_mut::<String>().unwrap() = value.to_string();
40    } 
41
42    fn set_list_reflect(list_reflect: &mut dyn List, value: &str) where Self: Sized {
43        if let Some(string_vec) = list_reflect.as_reflect_mut().downcast_mut::<Vec<String>>() {
44            let string_vec_value = value.split(",").map(|v| {
45                v.to_string()  
46            }).collect::<Vec<String>>();
47            *string_vec = string_vec_value;
48        } else {
49            // value = prop: Vec<Struct{r: Vec<str>, g: Vec<str>, b: <str>}> /=/ r(4,5,6) g(7,8,9) b(10,11,12), r(1,2,3) g(1,2,3) b(1,2,3)
50            // value = prop: Vec<Struct{a: Struct{aa: str ab: Vec<str>}} /=/  a(aa(10deg) ab(1,2,3)), a(aa(20deg) ab(3,5,6)) 
51            // only relevant if its a Vec<Struct>
52            let new_structs_values = value
53                .split_inclusive("),") // split value string into respective vec elements 
54                .map(|s| { s.strip_suffix(",").unwrap_or(s) }) // strip leading comma if its there
55                .collect::<Vec<&str>>();
56            for i in 0..list_reflect.len() {
57                match list_reflect.get_mut(i).unwrap().reflect_mut() {
58                    ReflectMut::Struct(struct_reflect) => {
59                        Self::set_struct_reflect(struct_reflect, new_structs_values[i]);
60                    }
61                    ReflectMut::TupleStruct(_) => todo!(),
62                    ReflectMut::Tuple(_) => todo!(),
63                    ReflectMut::List(_) => todo!(),
64                    ReflectMut::Array(_) => todo!(),
65                    ReflectMut::Map(_) => todo!(),
66                    ReflectMut::Enum(_) => todo!(),
67                    ReflectMut::Value(_) => todo!(),
68                }
69            }
70        }
71    }
72
73    // fn set_array_reflect(&mut self, field_name: &str, value: &str) where Self: Sized {
74    //     if let ReflectMut::Array(string_arr) = self.field_mut(field_name).unwrap().reflect_mut() {
75    //         let string_arr_value = value.split(",").map(|v| {
76    //             v.to_string()  
77    //         }).collect::<Vec<String>>();
78    //         *string_arr = Array::(string_arr_value);
79    //     } else {
80    //         println!("AAAAA");
81    //     }n
82    // }
83
84    fn set_struct_reflect(struct_reflect: &mut dyn Struct, value: &str) where Self: Sized {
85        // extract name of the first field of the struct from css
86        let value = value.replace(" ", ""); // "linear-gradient(rgb(1,2,3)),skewY(30deg)rgba(...)"
87        let func_rest= value.split_once("("); // i.e. 0: "linear-gradient", 1: "rgb(#dadafe)),skewY(30deg)"]
88        if func_rest.is_none() { 
89            let warn_msg = format!("    rusty-css:\n    Warning: parsing for the struct \n{:?}\n failed. \n    There might be some missplaced parentheses.", struct_reflect.get_type_info());
90            log::warn!("{}", warn_msg); println!("{}", warn_msg); 
91            return; 
92        }
93        let func_rest = func_rest.unwrap();
94        let func_name = &func_rest.0.replace("_","-");
95
96        if let Some(nested_struct_field) = struct_reflect.field_mut(func_name) {
97            let new_value: (&str, &str);
98            match nested_struct_field.reflect_mut() {
99                // Struct { Field: Nested_Struct { Nested_Field: Vec<String>, <Nested_field_n: String || Vec<String> }}
100                ReflectMut::List(nested_srtruct_field_list_ref) => {
101                    new_value = func_rest.1.split_once(")").unwrap(); // "10deg,20deg,30deg)skewY(...)" -> 0: "10deg, 20deg, 30deg", 1: "skewY(...)" 
102                    Self::set_list_reflect(nested_srtruct_field_list_ref, new_value.0);
103                }
104                ReflectMut::Struct(nested_srtruct_field_struct_ref) => {
105                    // func_rest.1 = "rgba(10deg,20deg,30deg))whatever(...)"
106                    let split_at = Regex::new(r"\)\)([a-zA-Z_-]|$)").unwrap(); //matches "))a, ))b, etc." or "))"
107                    let split_pos = split_at.find(func_rest.1).unwrap(); 
108                    let new_val_left = func_rest.1.split_at(split_pos.start() + 1_usize).0;
109                    let mut new_val_right = func_rest.1.split_at(split_pos.end() - 1_usize).1;
110                    if new_val_right == ")" { new_val_right = "" } // remove right side string if the previous value was at the end of the string
111                    new_value = (new_val_left, new_val_right); // "rgba(10deg,20deg,30deg))whatever(...)" -> 0: "rgba(10deg,20deg,30deg)", 1: "whatever(...)"
112                    Self::set_struct_reflect(nested_srtruct_field_struct_ref, new_value.0);
113                },
114                ReflectMut::TupleStruct(_) => todo!(),
115                ReflectMut::Tuple(_) => todo!(),
116                ReflectMut::Array(_) => todo!(),
117                ReflectMut::Map(_) => todo!(),
118                ReflectMut::Enum(_) => todo!(),
119                // Struct { Field: Nested_Struct { Nested_Field: String, <Nested_field_n: String || Vec<String>> }}
120                ReflectMut::Value(nested_srtruct_field_value_ref) => {
121                    new_value = func_rest.1.split_once(")").unwrap(); // "10deg)" -> 10deg
122                    *nested_srtruct_field_value_ref.downcast_mut::<String>().unwrap() = new_value.0.to_string();
123                }
124            }
125
126            // recurse if the css still has move funcs next to each other (e.g. skewX(20deg) >skewY(30deg)< ) 
127            if !new_value.1.is_empty() && !new_value.1.starts_with(",") {
128                Self::set_struct_reflect(struct_reflect, new_value.1);
129            }
130        }
131    }
132
133    // mutates a given objects fields to match a given inline css string
134    fn set_from_inline_string(&mut self, style: String) -> &Self where Self: Sized {
135        let prop_value = style.split(";"); //[" a: b", " c: d"]
136        prop_value.into_iter().for_each(|pv| {
137            let prop_value_vec = pv.split(":").collect::<Vec<&str>>();
138            let field_name = prop_value_vec[0].replace("-", "_").replace(" ", "").replace("\n", "");
139
140            // if the prop name corresponds to a field name
141            if let Some(field) = self.field_mut(&field_name) {
142                // if the field is of type String
143                match field.reflect_mut() {
144                    ReflectMut::Struct(struct_reflect) => {
145                        Self::set_struct_reflect(struct_reflect, prop_value_vec[1])
146                    },
147                    ReflectMut::List(list_reflect) => {
148                        Self::set_list_reflect(list_reflect, prop_value_vec[1])
149                    },
150                    ReflectMut::Array(_) => todo!(),
151                    ReflectMut::TupleStruct(_) => todo!(),
152                    ReflectMut::Tuple(_) => todo!(),
153                    ReflectMut::Map(_) => todo!(),
154                    ReflectMut::Enum(_) => todo!(),
155                    ReflectMut::Value(string_reflect) => {
156                        Self::set_string_reflect(string_reflect, prop_value_vec[1])
157                    }
158                }  
159            }
160
161            // // Simple String field
162            // if let Some(_field) = self.get_field::<String>(field_name.as_str()) {
163            //     self.set_string_reflect(field_name, prop_value_vec[1].to_string());
164            // } else 
165
166            // // Array
167            // if let Some(_field) = self.get_field::<DynamicArray>(field_name.as_str()) {
168            //     self.set_array_reflect(field_name, prop_value_vec[1].to_string());
169            // } else
170
171            // // Nested Struct
172            // if let Some(_field) = self.get_field::<DynamicStruct>(field_name.as_str()) {
173            //     self.set_struct_reflect(field_name, prop_value_vec[1].to_string());
174            // } else 
175            
176            // // Either it's not a part of Self or it's not a Struct, Array or String
177            // {
178            //     let field = self.field(field_name.as_str());
179            //     println!("!{:?}", field.unwrap().get_type_info());
180            // }
181        });
182        self
183    }
184
185    // creates a string in the form of an inline style css string
186    fn inline(&self) -> String where Self: Sized {
187        let mut style_string = "".to_owned();
188
189        //iterate over fields of the component
190        for (i, value_reflect) in self.iter_fields().enumerate() {
191
192            //get the name of the structs field as a String
193            let mut property_name = self.name_at(i).unwrap().to_owned();
194            
195            //++++++++++++++++++++++++++++++++++++++++++++
196            // horrendous way of implementing reserved keywords
197            // but I'm tired now so this will have to do
198            //++++++++++++++++++++++++++++++++++++++++++++
199            if property_name != "append" {
200                property_name = property_name.replace("_", "-");
201
202                //initialize the value to be given for the property in property_name (i.e. width, height, transform, etc) 
203                let value = Self::create_value_string(value_reflect);
204
205                style_string.push_str( &format!("{property}:{value}; ", property = property_name, value = value) );
206            }
207        }
208
209        style_string
210    }
211
212    // creates a string for the values behind the css property
213    fn create_value_string(reflect: &dyn Reflect) -> String {
214        let mut value = "".to_owned();
215
216        match &reflect.reflect_ref() {
217            //check if the field is a value type (i.e. String, i32, f32, etc.)
218            bevy_reflect::ReflectRef::Value(v) => {
219                let value_string = " ".to_owned() + v.downcast_ref::<String>().unwrap();
220                value.push_str( &value_string );
221            }
222
223            //check if the field is a nested struct (i.e. Transform, etc.)
224            bevy_reflect::ReflectRef::Struct(fields) => {
225
226                //loop over the nested struct                    
227                for (i, value_reflect) in fields.iter_fields().enumerate() {
228                    //function names like skewX, skewY, etc.
229                    let function_name = fields.name_at(i).unwrap();
230                    let function_param = Self::create_value_string(value_reflect);
231                    let value_string = format!(" {function}({parameter})", function = &function_name, parameter = &function_param);
232                    value.push_str(&value_string);
233                }
234            },
235            bevy_reflect::ReflectRef::Array(arr) => {
236                //loop over the vector                    
237                for (i, value_reflect) in arr.iter().enumerate() {
238                    // dont set the leading comma if its the first element
239                    let mut comma = "".to_string();
240                    if i != 0 {
241                        comma = ",".to_string();
242                    }
243
244                    let value_string = format!("{comma}{value}", value = Self::create_value_string(value_reflect), comma = comma);
245                    value.push_str(&value_string);
246                }
247            },
248            _ => {
249                log::warn!("The given Object is only allowed to have String fields or Structs with String fields. \nGot: {:?}", &reflect.get_type_info());
250            }
251        }
252
253        value
254    }
255
256
257    fn as_class_string(&self, class_name: &String) -> Result<String, &'static str> where Self: Sized {
258
259        let mut class_name_appended = class_name.clone();
260        // append pseudo-class name to the class name (i.e. .struct_ident:pseudo_class)
261        let append = self.field("append");
262        if !append.is_none() {
263            class_name_appended.push_str(
264                append.unwrap().downcast_ref::<String>().unwrap()
265            );
266        }
267           
268        Ok( format!(".{} {{ {}}}", class_name_appended, self.inline()) )
269    }
270
271    fn as_class(&self, document: &Document) -> Result<String, &'static str> where Self: Sized {
272        
273        // get struct name as class name
274        let class_name = self.get_struct_name().unwrap();
275
276        // create the string that is supposed to be inserted into the head as class
277        let class_string = self.as_class_string(&class_name).expect("Class string could not be created");
278
279        // insert the class
280        self.append_to_head(&document, &class_name, &class_string);
281        
282        // return just the class name
283        Ok(class_name)
284    }
285
286    fn append_to_head(&self, document: &Document, class_name: &String, class_string: &String) where Self: Sized {
287        let head = document.head().expect("No <head> element found in the document");
288        let new_style_element = document.create_element("style").expect("couldn't create <style> element in this document");
289        let style_id = format!("rusty-css-{}", class_name.replace(":", "_"));
290        new_style_element.set_attribute("id", &style_id ).expect("couldn't set attribute of internally created style tag");
291        new_style_element.set_text_content(Some(&class_string));
292
293        if let Some(existent_style) = head.query_selector(&format!("#{}", style_id) ).expect("an error occured while trying to fetch the element with id `rusty-css` in head") {
294            head.remove_child(&existent_style).expect("couldn't remove child element with id `rusty-css` in head");
295        }
296
297        head.append_child(&new_style_element).expect("couldn't append internally created `style` element with id `rusty-css` to head");
298    }
299
300    fn add_as_pseudo_class(&self, document: &Document) where Self: Sized {
301        
302        let mut class_name = self.get_struct_name().unwrap();
303        class_name = class_name.replace("_", ":");
304
305        let class_string = self.as_class_string(&class_name).expect("Class string could not be created");
306
307        self.append_to_head(document, &class_name, &class_string);
308    }
309
310    fn get_struct_name(&self) -> Result<String, &'static str> where Self: Sized {
311        
312        // cuts off the "arbitrary_caller_name::" from "arbitrary_caller_name::StructIdent and returns just the StructIdent"
313        let class_name_pos = self.type_name().rfind("::").expect("(Internal Error) couldn't find position of `::` in type_name");
314        let class_name_slice = self.type_name().substring(class_name_pos + 2, self.type_name().len());
315        if class_name_slice == "" { return Err("(Internal Error) couldn't strip arbitrary_caller_name:: prefix"); }
316        
317        Ok(class_name_slice.to_owned())
318    }
319
320    fn debug(self) -> Self where Self: Sized {
321        
322        wasm_logger::init(wasm_logger::Config::default());
323
324        for (_i, value_reflect) in self.iter_fields().enumerate() {
325            log::info!("{:?}", value_reflect.get_type_info());
326        }
327
328        self
329    }
330}