add variable support
This commit is contained in:
parent
0f09a5e22d
commit
c5301cfe63
38
src/class.rs
38
src/class.rs
|
@ -1,3 +1,5 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
media_queries::{ReducedMotion, Responsive},
|
media_queries::{ReducedMotion, Responsive},
|
||||||
modifiers::Modifiers,
|
modifiers::Modifiers,
|
||||||
|
@ -8,8 +10,7 @@ use crate::{
|
||||||
pub(crate) struct Class<'a> {
|
pub(crate) struct Class<'a> {
|
||||||
pub property: &'a str,
|
pub property: &'a str,
|
||||||
pub value: Option<&'a str>,
|
pub value: Option<&'a str>,
|
||||||
/// if true, no replacements will be done on `value`
|
pub value_type: ValueType,
|
||||||
pub value_literal: bool,
|
|
||||||
pub modifiers: Modifiers<'a>,
|
pub modifiers: Modifiers<'a>,
|
||||||
pub pseudo: Option<&'a str>,
|
pub pseudo: Option<&'a str>,
|
||||||
/// the original unparsed value
|
/// the original unparsed value
|
||||||
|
@ -17,6 +18,16 @@ pub(crate) struct Class<'a> {
|
||||||
pub original: &'a str,
|
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> {
|
impl<'a> Class<'a> {
|
||||||
pub(crate) fn selector(&self, z: &Zephyr) -> String {
|
pub(crate) fn selector(&self, z: &Zephyr) -> String {
|
||||||
let Class {
|
let Class {
|
||||||
|
@ -73,14 +84,12 @@ impl<'a> Class<'a> {
|
||||||
let selector = self.selector(z);
|
let selector = self.selector(z);
|
||||||
|
|
||||||
if let Some(val) = self.value {
|
if let Some(val) = self.value {
|
||||||
let val = if self.value_literal {
|
let val = match self.value_type {
|
||||||
val.to_string()
|
ValueType::Normal => {
|
||||||
} else {
|
replace_underscores(z.values.get(val).map(AsRef::as_ref).unwrap_or(val))
|
||||||
z.values
|
}
|
||||||
.get(val)
|
ValueType::Literal => val.into(),
|
||||||
.map(AsRef::as_ref)
|
ValueType::Variable => format!("var(--{val})").into(),
|
||||||
.unwrap_or(val)
|
|
||||||
.replace('_', " ")
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(fun) = z.specials.get(property) {
|
if let Some(fun) = z.specials.get(property) {
|
||||||
|
@ -109,3 +118,12 @@ impl<'a> Class<'a> {
|
||||||
Ok(css)
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
18
src/lib.rs
18
src/lib.rs
|
@ -72,9 +72,6 @@ impl Zephyr {
|
||||||
|
|
||||||
/// generates css rules for all the of the classes that parse correctly
|
/// 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 {
|
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)
|
// TODO we could return (css, seen_classes)
|
||||||
let mut seen_classes = vec![];
|
let mut seen_classes = vec![];
|
||||||
|
|
||||||
|
@ -139,7 +136,7 @@ mod tests {
|
||||||
modifiers: vec![].into(),
|
modifiers: vec![].into(),
|
||||||
pseudo: None,
|
pseudo: None,
|
||||||
original: "m[1rem]",
|
original: "m[1rem]",
|
||||||
value_literal: false,
|
value_type: class::ValueType::Normal,
|
||||||
};
|
};
|
||||||
let css = class.generate(&z).unwrap();
|
let css = class.generate(&z).unwrap();
|
||||||
assert_eq!(css, r#".m\[1rem\]{margin:1rem;}"#);
|
assert_eq!(css, r#".m\[1rem\]{margin:1rem;}"#);
|
||||||
|
@ -150,7 +147,7 @@ mod tests {
|
||||||
modifiers: vec!["focus"].into(),
|
modifiers: vec!["focus"].into(),
|
||||||
pseudo: None,
|
pseudo: None,
|
||||||
original: "m[1rem]focus",
|
original: "m[1rem]focus",
|
||||||
value_literal: false,
|
value_type: class::ValueType::Normal,
|
||||||
};
|
};
|
||||||
let css = class.generate(&z).unwrap();
|
let css = class.generate(&z).unwrap();
|
||||||
assert_eq!(css, r#".m\[1rem\]focus:focus{margin:1rem;}"#);
|
assert_eq!(css, r#".m\[1rem\]focus:focus{margin:1rem;}"#);
|
||||||
|
@ -161,7 +158,7 @@ mod tests {
|
||||||
modifiers: vec!["focus", "hover", "odd"].into(),
|
modifiers: vec!["focus", "hover", "odd"].into(),
|
||||||
pseudo: None,
|
pseudo: None,
|
||||||
original: "m[1rem]focus,hover,odd",
|
original: "m[1rem]focus,hover,odd",
|
||||||
value_literal: false,
|
value_type: class::ValueType::Normal,
|
||||||
};
|
};
|
||||||
let css = class.generate(&z).unwrap();
|
let css = class.generate(&z).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -251,4 +248,13 @@ mod tests {
|
||||||
r#"@media(min-width:640px){.m\[1rem\]sm{margin:1rem;}}"#
|
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);}"#);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
73
src/parse.rs
73
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<Class<'a>, ZephyrError> {
|
pub(crate) fn parse_class<'a>(original: &'a str) -> Result<Class<'a>, ZephyrError> {
|
||||||
// this code is kinda repetitive but idk
|
// 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(),
|
modifiers: mods.into(),
|
||||||
pseudo,
|
pseudo,
|
||||||
original,
|
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, '}')) {
|
match (pos(class, '{'), pos(class, '}')) {
|
||||||
(Some(start), Some(end)) if start <= end => {
|
(Some(start), Some(end)) if start <= end => {
|
||||||
let mods = if end + 1 == class.len() {
|
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(),
|
modifiers: mods.into(),
|
||||||
pseudo,
|
pseudo,
|
||||||
original,
|
original,
|
||||||
value_literal: true,
|
value_type: ValueType::Literal,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// go to [...] case
|
// 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, ']')) {
|
match (pos(class, '['), pos(class, ']')) {
|
||||||
(Some(start), Some(end)) if start <= end => {
|
(Some(start), Some(end)) if start <= end => {
|
||||||
let mods = if end + 1 == class.len() {
|
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(),
|
modifiers: mods.into(),
|
||||||
pseudo,
|
pseudo,
|
||||||
original,
|
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(),
|
modifiers: vec![].into(),
|
||||||
pseudo,
|
pseudo,
|
||||||
original,
|
original,
|
||||||
value_literal: false,
|
value_type: ValueType::Normal,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,13 +129,19 @@ mod tests {
|
||||||
modifiers: modifiers.into(),
|
modifiers: modifiers.into(),
|
||||||
pseudo,
|
pseudo,
|
||||||
original: class,
|
original: class,
|
||||||
value_literal: false,
|
value_type: ValueType::Normal,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
fn check_literal(
|
fn check_with_type(
|
||||||
class: &str,
|
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!(
|
assert_eq!(
|
||||||
parse_class(class),
|
parse_class(class),
|
||||||
|
@ -117,7 +151,7 @@ mod tests {
|
||||||
modifiers: modifiers.into(),
|
modifiers: modifiers.into(),
|
||||||
pseudo,
|
pseudo,
|
||||||
original: class,
|
original: class,
|
||||||
value_literal: true,
|
value_type,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -143,12 +177,29 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_literal_values() {
|
fn parse_literal_values() {
|
||||||
// testing out weird unicode stuffs
|
// testing out weird unicode stuffs
|
||||||
check_literal(
|
check_with_type(
|
||||||
"hello{hey_hello}",
|
"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]
|
#[test]
|
||||||
fn parse_modifier() {
|
fn parse_modifier() {
|
||||||
check("a[b]hover", ("a", Some("b"), vec!["hover"], None));
|
check("a[b]hover", ("a", Some("b"), vec!["hover"], None));
|
||||||
|
|
Loading…
Reference in New Issue