From c5301cfe63773ba7d65d6f806196632e3a5ee114 Mon Sep 17 00:00:00 2001 From: annieversary Date: Mon, 11 Jul 2022 16:43:43 +0100 Subject: [PATCH] add variable support --- src/class.rs | 38 ++++++++++++++++++++------- src/lib.rs | 18 ++++++++----- src/parse.rs | 73 ++++++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 102 insertions(+), 27 deletions(-) diff --git a/src/class.rs b/src/class.rs index 2c85483..b544c51 100644 --- a/src/class.rs +++ b/src/class.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use crate::{ media_queries::{ReducedMotion, Responsive}, modifiers::Modifiers, @@ -8,8 +10,7 @@ use crate::{ pub(crate) struct Class<'a> { pub property: &'a str, pub value: Option<&'a str>, - /// if true, no replacements will be done on `value` - pub value_literal: bool, + pub value_type: ValueType, pub modifiers: Modifiers<'a>, pub pseudo: Option<&'a str>, /// the original unparsed value @@ -17,6 +18,16 @@ pub(crate) struct Class<'a> { pub original: &'a str, } +#[derive(PartialEq, Debug)] +pub(crate) enum ValueType { + /// replacements will be performed + Normal, + /// no replacements will be done. value will be output as-is + Literal, + /// value will be output as `var(--value)`, without any replacements + Variable, +} + impl<'a> Class<'a> { pub(crate) fn selector(&self, z: &Zephyr) -> String { let Class { @@ -73,14 +84,12 @@ impl<'a> Class<'a> { let selector = self.selector(z); if let Some(val) = self.value { - let val = if self.value_literal { - val.to_string() - } else { - z.values - .get(val) - .map(AsRef::as_ref) - .unwrap_or(val) - .replace('_', " ") + let val = match self.value_type { + ValueType::Normal => { + replace_underscores(z.values.get(val).map(AsRef::as_ref).unwrap_or(val)) + } + ValueType::Literal => val.into(), + ValueType::Variable => format!("var(--{val})").into(), }; if let Some(fun) = z.specials.get(property) { @@ -109,3 +118,12 @@ impl<'a> Class<'a> { Ok(css) } } + +/// replaces underscores with spaces +fn replace_underscores<'a>(s: &'a str) -> Cow<'a, str> { + if s.contains('_') { + s.replace('_', " ").into() + } else { + s.into() + } +} diff --git a/src/lib.rs b/src/lib.rs index fac5fdf..8f4c837 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,9 +72,6 @@ impl Zephyr { /// generates css rules for all the of the classes that parse correctly pub fn generate_classes<'a>(&self, classes: impl IntoIterator) -> String { - // TODO when we have media queries, we can do something to group them by the query, - // and then emit those together - // TODO we could return (css, seen_classes) let mut seen_classes = vec![]; @@ -139,7 +136,7 @@ mod tests { modifiers: vec![].into(), pseudo: None, original: "m[1rem]", - value_literal: false, + value_type: class::ValueType::Normal, }; let css = class.generate(&z).unwrap(); assert_eq!(css, r#".m\[1rem\]{margin:1rem;}"#); @@ -150,7 +147,7 @@ mod tests { modifiers: vec!["focus"].into(), pseudo: None, original: "m[1rem]focus", - value_literal: false, + value_type: class::ValueType::Normal, }; let css = class.generate(&z).unwrap(); assert_eq!(css, r#".m\[1rem\]focus:focus{margin:1rem;}"#); @@ -161,7 +158,7 @@ mod tests { modifiers: vec!["focus", "hover", "odd"].into(), pseudo: None, original: "m[1rem]focus,hover,odd", - value_literal: false, + value_type: class::ValueType::Normal, }; let css = class.generate(&z).unwrap(); assert_eq!( @@ -251,4 +248,13 @@ mod tests { r#"@media(min-width:640px){.m\[1rem\]sm{margin:1rem;}}"# ); } + + #[test] + fn generate_variable() { + let z = Zephyr::new(); + + // the curly brackets indicate that the value should not go through replacements + let classes = z.generate_classes(["m(my-margin)"]); + assert_eq!(classes, r#".m\(my-margin\){margin:var(--my-margin);}"#); + } } diff --git a/src/parse.rs b/src/parse.rs index 8ef27ff..ac3f0e0 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -1,4 +1,7 @@ -use crate::{class::Class, ZephyrError}; +use crate::{ + class::{Class, ValueType}, + ZephyrError, +}; pub(crate) fn parse_class<'a>(original: &'a str) -> Result, ZephyrError> { // this code is kinda repetitive but idk @@ -22,10 +25,13 @@ pub(crate) fn parse_class<'a>(original: &'a str) -> Result, ZephyrErro modifiers: mods.into(), pseudo, original, - value_literal: false, + value_type: ValueType::Normal, }); } + // first try braces, then parenthesis, then square brakcets + // i know it's kinda ugly and repetitive, but it works for now + match (pos(class, '{'), pos(class, '}')) { (Some(start), Some(end)) if start <= end => { let mods = if end + 1 == class.len() { @@ -40,7 +46,7 @@ pub(crate) fn parse_class<'a>(original: &'a str) -> Result, ZephyrErro modifiers: mods.into(), pseudo, original, - value_literal: true, + value_type: ValueType::Literal, }); } // go to [...] case @@ -51,6 +57,28 @@ pub(crate) fn parse_class<'a>(original: &'a str) -> Result, ZephyrErro } }; + 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 Ok(Class { + property: &class[0..start], + value: Some(&class[start + 1..end]), + modifiers: mods.into(), + pseudo, + original, + value_type: ValueType::Variable, + }); + } + _ => { + // do nothing + } + } + match (pos(class, '['), pos(class, ']')) { (Some(start), Some(end)) if start <= end => { let mods = if end + 1 == class.len() { @@ -65,7 +93,7 @@ pub(crate) fn parse_class<'a>(original: &'a str) -> Result, ZephyrErro modifiers: mods.into(), pseudo, original, - value_literal: false, + value_type: ValueType::Normal, }); } _ => { @@ -75,7 +103,7 @@ pub(crate) fn parse_class<'a>(original: &'a str) -> Result, ZephyrErro modifiers: vec![].into(), pseudo, original, - value_literal: false, + value_type: ValueType::Normal, }); } } @@ -101,13 +129,19 @@ mod tests { modifiers: modifiers.into(), pseudo, original: class, - value_literal: false, + value_type: ValueType::Normal, }) ); } - fn check_literal( + fn check_with_type( class: &str, - (property, value, modifiers, pseudo): (&str, Option<&str>, Vec<&str>, Option<&str>), + (property, value, modifiers, pseudo, value_type): ( + &str, + Option<&str>, + Vec<&str>, + Option<&str>, + ValueType, + ), ) { assert_eq!( parse_class(class), @@ -117,7 +151,7 @@ mod tests { modifiers: modifiers.into(), pseudo, original: class, - value_literal: true, + value_type, }) ); } @@ -143,12 +177,29 @@ mod tests { #[test] fn parse_literal_values() { // testing out weird unicode stuffs - check_literal( + check_with_type( "hello{hey_hello}", - ("hello", Some("hey_hello"), vec![], None), + ("hello", Some("hey_hello"), vec![], None, ValueType::Literal), ); } + #[test] + fn parse_variable_values() { + // testing out weird unicode stuffs + check_with_type( + "hello(hey_hello)", + ( + "hello", + Some("hey_hello"), + vec![], + None, + ValueType::Variable, + ), + ); + } + + // TODO add more tests for interactions between all of those + #[test] fn parse_modifier() { check("a[b]hover", ("a", Some("b"), vec!["hover"], None));