From 8613e712e7e01a0812e1c9a5fb9147f1bda887e6 Mon Sep 17 00:00:00 2001 From: annieversary Date: Sat, 2 Jul 2022 11:01:06 +0100 Subject: [PATCH] rework, add non-value rules --- examples/html.rs | 3 +- src/class.rs | 59 ++++++++++++++++++ src/defaults.rs | 15 +++++ src/lib.rs | 90 +++++++++++++++++++--------- src/parse.rs | 152 +++++++++++++++++++++++++---------------------- src/rules.rs | 41 ------------- 6 files changed, 220 insertions(+), 140 deletions(-) create mode 100644 src/class.rs create mode 100644 src/defaults.rs delete mode 100644 src/rules.rs diff --git a/examples/html.rs b/examples/html.rs index 92ec0fb..be266e2 100644 --- a/examples/html.rs +++ b/examples/html.rs @@ -10,7 +10,8 @@ fn main() { "color[red]$after", ]; - let css = dbg!(zephyr::generate_css(&classes)); + let z = zephyr::Zephyr::new(); + let css = z.generate_css(&classes); let html = format!( r#" diff --git a/src/class.rs b/src/class.rs new file mode 100644 index 0000000..e2d27ff --- /dev/null +++ b/src/class.rs @@ -0,0 +1,59 @@ +use crate::{modifiers::Modifiers, name::Name, Zephyr}; + +#[derive(PartialEq, Debug)] +pub(crate) struct Class<'a> { + pub name: Name<'a>, + pub value: Option<&'a str>, + pub modifiers: Modifiers<'a>, + pub pseudo: Option<&'a str>, + /// the original unparsed value + /// needed to generate the css selector + pub original: &'a str, +} + +impl<'a> Class<'a> { + pub(crate) fn selector(&self) -> String { + let Class { + modifiers, + pseudo, + original, + .. + } = self; + + let mut rest = if let Some(mods) = modifiers.get() { + format!(":{mods}") + } else { + "".to_string() + }; + if let Some(pseudo) = pseudo { + rest.push_str("::"); + rest.push_str(pseudo); + } + + format!(".{original}{rest}") + .replace('[', "\\[") + .replace(']', "\\]") + .replace('|', "\\|") + .replace('(', "\\(") + .replace(')', "\\)") + .replace('#', "\\#") + .replace('$', "\\$") + .replace('\'', "\\'") + .replace('*', "\\*") + .replace('%', "\\%") + } + + /// TODO return result + pub(crate) fn generate(&self, z: &Zephyr) -> String { + let name = self.name.as_str(); + if let Some(val) = self.value { + let selector = self.selector(); + format!("{selector} {{ {name}: {val}; }}",) + } else if let Some(v) = z.rules.get(name) { + let selector = self.selector(); + format!("{selector} {{ {v} }}",) + } else { + panic!("{name} is not a no-variable rule, and no variables were provided"); + } + } +} diff --git a/src/defaults.rs b/src/defaults.rs new file mode 100644 index 0000000..e3ae070 --- /dev/null +++ b/src/defaults.rs @@ -0,0 +1,15 @@ +use std::collections::HashMap; + +pub(crate) fn default_rules() -> HashMap { + vec![ + ("flex", "display: flex;"), + ("flex-row", "display: flex; flex-direction: row;"), + ("flex-col", "display: flex; flex-direction: column;"), + ("items-center", "align-items: center"), + ("justify-center", "justify-content: center"), + // TODO + ] + .into_iter() + .map(|(a, b)| (a.to_string(), b.to_string())) + .collect() +} diff --git a/src/lib.rs b/src/lib.rs index 16a2bfc..d0f29c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,68 +1,91 @@ -use crate::{parse::*, rules::*}; -use std::path::Path; +use std::collections::HashMap; +use defaults::default_rules; + +use crate::parse::*; + +mod class; +mod defaults; mod modifiers; mod name; mod parse; -mod rules; -pub fn generate_and_write(classes: &[&str], path: impl AsRef) -> Result<(), std::io::Error> { - let out = generate_css(classes); - std::fs::write(path, out)?; +// pub fn generate_and_write(classes: &[&str], path: impl AsRef) -> Result<(), std::io::Error> { +// let out = generate_css(classes); +// std::fs::write(path, out)?; - Ok(()) +// Ok(()) +// } + +pub struct Zephyr { + pub rules: HashMap, } -pub fn generate_css(classes: &[&str]) -> String { - classes - .into_iter() - .flat_map(|c| generate_class(c)) - .collect::>() - .join("") -} +impl Zephyr { + /// builds a `Zephyr` with the default ruleset + pub fn new() -> Self { + Self { + rules: default_rules(), + } + } -pub fn generate_class(class: &str) -> Option { - let class = parse_class(class)?; - let rule = RULES - .get(&class.name.as_str()) - .unwrap_or(&(&General as &dyn Rule)); - Some(rule.generate(&class)) + /// builds a `Zephyr` without the default ruleset + pub fn new_without_rules() -> Self { + Self { + rules: HashMap::new(), + } + } + + pub fn generate_css(&self, classes: &[&str]) -> String { + classes + .into_iter() + .flat_map(|c| self.generate_class(c)) + .collect::>() + .join("") + } + + pub fn generate_class(&self, class: &str) -> Option { + parse_class(class).map(|c| c.generate(self)) + } } #[cfg(test)] mod tests { use super::*; + use class::Class; #[test] fn generate_margin_works() { + let z = Zephyr::new(); + let class = Class { name: "m".into(), - value: "1rem", + value: Some("1rem"), modifiers: vec![].into(), pseudo: None, original: "m[1rem]", }; - let css = General.generate(&class); + let css = class.generate(&z); assert_eq!(css, r#".m\[1rem\] { margin: 1rem; }"#); let class = Class { name: "m".into(), - value: "1rem", + value: Some("1rem"), modifiers: vec!["focus"].into(), pseudo: None, original: "m[1rem]focus", }; - let css = General.generate(&class); + let css = class.generate(&z); assert_eq!(css, r#".m\[1rem\]focus:focus { margin: 1rem; }"#); let class = Class { name: "m".into(), - value: "1rem", + value: Some("1rem"), modifiers: vec!["focus", "hover", "odd"].into(), pseudo: None, original: "m[1rem]focus,hover,odd", }; - let css = General.generate(&class); + let css = class.generate(&z); assert_eq!( css, r#".m\[1rem\]focus,hover,odd:focus:hover:nth-child\(odd\) { margin: 1rem; }"# @@ -71,11 +94,24 @@ mod tests { #[test] fn generate_classes_works() { - let classes = generate_css(&["m[3rem]hover,focus$placeholder"]); + let z = Zephyr::new(); + let classes = z.generate_css(&["flex-row"]); + assert_eq!( + classes, + r#".flex-row { display: flex; flex-direction: row; }"# + ); + + let classes = z.generate_css(&["m[3rem]hover,focus$placeholder"]); assert_eq!( classes, r#".m\[3rem\]hover,focus\$placeholder:hover:focus::placeholder { margin: 3rem; }"# ); + + let classes = z.generate_css(&["flex|hover,focus$placeholder"]); + assert_eq!( + classes, + r#".flex\|hover,focus\$placeholder:hover:focus::placeholder { display: flex; }"# + ); } } diff --git a/src/parse.rs b/src/parse.rs index f0caabb..19f9ee8 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -1,4 +1,4 @@ -use crate::{modifiers::Modifiers, name::Name}; +use crate::{class::Class, name::Name}; pub(crate) fn parse_class<'a>(original: &'a str) -> Option> { let (class, pseudo) = if let Some((class, pseudo)) = original.split_once('$') { @@ -7,68 +7,47 @@ pub(crate) fn parse_class<'a>(original: &'a str) -> Option> { (original, None) }; - let start = pos(class, '[')?; - let end = pos(class, ']')?; + if let Some(p) = pos(class, '|') { + let mods = if p + 1 == class.len() { + vec![] + } else { + class[p + 1..].split(',').collect() + }; - if start > end { - return None; - } - - let mods = if end + 1 == class.len() { - vec![] - } else { - class[end + 1..].split(',').collect() - }; - - Some(Class { - name: Name::new(&class[0..start]), - value: &class[start + 1..end], - modifiers: mods.into(), - pseudo, - original, - }) -} - -#[derive(PartialEq, Debug)] -pub(crate) struct Class<'a> { - pub name: Name<'a>, - pub value: &'a str, - pub modifiers: Modifiers<'a>, - pub pseudo: Option<&'a str>, - /// the original unparsed value - /// needed to generate the css selector - pub original: &'a str, -} - -impl<'a> Class<'a> { - pub(crate) fn selector(&self) -> String { - let Class { - modifiers, + return Some(Class { + name: Name::new(&class[0..p]), + value: None, + modifiers: mods.into(), pseudo, original, - .. - } = self; + }); + } - let mut rest = if let Some(mods) = modifiers.get() { - format!(":{mods}") - } else { - "".to_string() - }; - if let Some(pseudo) = pseudo { - rest.push_str("::"); - rest.push_str(pseudo); + match (pos(class, '['), pos(class, ']')) { + (Some(start), Some(end)) if start <= end => { + let mods = if end + 1 == class.len() { + vec![] + } else { + class[end + 1..].split(',').collect() + }; + + return Some(Class { + name: Name::new(&class[0..start]), + value: Some(&class[start + 1..end]), + modifiers: mods.into(), + pseudo, + original, + }); + } + _ => { + return Some(Class { + name: Name::new(&class[0..]), + value: None, + modifiers: vec![].into(), + pseudo, + original, + }); } - - format!(".{original}{rest}") - .replace('[', "\\[") - .replace(']', "\\]") - .replace('(', "\\(") - .replace(')', "\\)") - .replace('#', "\\#") - .replace('$', "\\$") - .replace('\'', "\\'") - .replace('*', "\\*") - .replace('%', "\\%") } } @@ -80,7 +59,10 @@ fn pos(s: &str, c: char) -> Option { mod tests { use super::*; - fn check(class: &str, (name, value, modifiers, pseudo): (&str, &str, Vec<&str>, Option<&str>)) { + fn check( + class: &str, + (name, value, modifiers, pseudo): (&str, Option<&str>, Vec<&str>, Option<&str>), + ) { assert_eq!( parse_class(class), Some(Class { @@ -95,19 +77,28 @@ mod tests { #[test] fn parse_works() { - check("m[1rem]", ("m", "1rem", vec![], None)); - check("text-align[center]", ("text-align", "center", vec![], None)); - check("something[one:two]", ("something", "one:two", vec![], None)); + check("m[1rem]", ("m", Some("1rem"), vec![], None)); + check( + "text-align[center]", + ("text-align", Some("center"), vec![], None), + ); + check( + "something[one:two]", + ("something", Some("one:two"), vec![], None), + ); // testing out weird unicode stuffs - check("hešŸ„°llo[one:two]", ("hešŸ„°llo", "one:two", vec![], None)); + check( + "hešŸ„°llo[one:two]", + ("hešŸ„°llo", Some("one:two"), vec![], None), + ); } #[test] fn parse_modifier() { - check("a[b]hover", ("a", "b", vec!["hover"], None)); + check("a[b]hover", ("a", Some("b"), vec!["hover"], None)); check( "text-align[center]focus", - ("text-align", "center", vec!["focus"], None), + ("text-align", Some("center"), vec!["focus"], None), ); } @@ -115,7 +106,7 @@ mod tests { fn parse_multiple_modifiers() { check( "a[b]hover,focus,odd", - ("a", "b", vec!["hover", "focus", "odd"], None), + ("a", Some("b"), vec!["hover", "focus", "odd"], None), ); } @@ -123,17 +114,36 @@ mod tests { fn parse_pseudo() { check( "a[b]hover,focus,odd$before", - ("a", "b", vec!["hover", "focus", "odd"], Some("before")), + ( + "a", + Some("b"), + vec!["hover", "focus", "odd"], + Some("before"), + ), ); check( "a[b]hover$before$after", - ("a", "b", vec!["hover"], Some("before$after")), + ("a", Some("b"), vec!["hover"], Some("before$after")), ); } #[test] - fn out_of_order_is_none() { - assert_eq!(parse_class("a]b["), None); - assert_eq!(parse_class("a]b[c]"), None); + fn closing_before_opening_means_no_value() { + check("a]b[", ("a]b[", None, vec![], None)); + check("a]b[c]", ("a]b[c]", None, vec![], None)); + } + + #[test] + fn parse_no_value() { + check("meow", ("meow", None, vec![], None)); + check( + "a|hover$before$after", + ("a", None, vec!["hover"], Some("before$after")), + ); + // no value has preference over value + check( + "a[hey]hi|hover$before$after", + ("a[hey]hi", None, vec!["hover"], Some("before$after")), + ); } } diff --git a/src/rules.rs b/src/rules.rs deleted file mode 100644 index 20acf99..0000000 --- a/src/rules.rs +++ /dev/null @@ -1,41 +0,0 @@ -use once_cell::sync::Lazy; -use std::collections::HashMap; - -use crate::parse::Class; - -pub(crate) static RULES: Lazy> = Lazy::new(|| { - let mut m = HashMap::new(); - m.insert("flex", &Flex as &dyn Rule); - m -}); - -pub(crate) trait Rule: Sync { - fn generate<'a>(&self, class: &Class<'a>) -> String; -} - -/// fallback general replacer -/// this is the one that will be used the most, as it emits a css rule with a single property -pub(crate) struct General; -impl Rule for General { - fn generate<'a>(&self, class: &Class<'a>) -> String { - format!( - "{selector} {{ {name}: {value}; }}", - selector = class.selector(), - name = class.name.as_str(), - value = class.value - ) - } -} - -// the rest of the rules go here -// these ones are not a simple replacement - -struct Flex; -impl Rule for Flex { - fn generate<'a>(&self, class: &Class<'a>) -> String { - format!( - "{selector} {{ display: flex; }}", - selector = class.selector(), - ) - } -}