rework, add non-value rules

This commit is contained in:
annieversary 2022-07-02 11:01:06 +01:00
parent dd9620ef9e
commit 8613e712e7
6 changed files with 220 additions and 140 deletions

View File

@ -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#"

59
src/class.rs Normal file
View File

@ -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");
}
}
}

15
src/defaults.rs Normal file
View File

@ -0,0 +1,15 @@
use std::collections::HashMap;
pub(crate) fn default_rules() -> HashMap<String, String> {
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()
}

View File

@ -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<Path>) -> Result<(), std::io::Error> {
let out = generate_css(classes);
std::fs::write(path, out)?;
// pub fn generate_and_write(classes: &[&str], path: impl AsRef<Path>) -> Result<(), std::io::Error> {
// let out = generate_css(classes);
// std::fs::write(path, out)?;
Ok(())
// Ok(())
// }
pub struct Zephyr {
pub rules: HashMap<String, String>,
}
pub fn generate_css(classes: &[&str]) -> String {
classes
.into_iter()
.flat_map(|c| generate_class(c))
.collect::<Vec<_>>()
.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<String> {
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::<Vec<_>>()
.join("")
}
pub fn generate_class(&self, class: &str) -> Option<String> {
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; }"#
);
}
}

View File

@ -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<Class<'a>> {
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<Class<'a>> {
(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<usize> {
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")),
);
}
}

View File

@ -1,41 +0,0 @@
use once_cell::sync::Lazy;
use std::collections::HashMap;
use crate::parse::Class;
pub(crate) static RULES: Lazy<HashMap<&str, &dyn Rule>> = 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(),
)
}
}