add variable support

This commit is contained in:
annieversary 2022-07-11 16:43:43 +01:00
parent 0f09a5e22d
commit c5301cfe63
3 changed files with 102 additions and 27 deletions

View File

@ -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()
}
}

View File

@ -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<Item = &'a str>) -> 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);}"#);
}
}

View File

@ -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<Class<'a>, ZephyrError> {
// this code is kinda repetitive but idk
@ -22,10 +25,13 @@ pub(crate) fn parse_class<'a>(original: &'a str) -> Result<Class<'a>, 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<Class<'a>, 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<Class<'a>, 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<Class<'a>, 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<Class<'a>, 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));