rework, add non-value rules
This commit is contained in:
parent
dd9620ef9e
commit
8613e712e7
|
@ -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#"
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
90
src/lib.rs
90
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<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; }"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
152
src/parse.rs
152
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<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")),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
41
src/rules.rs
41
src/rules.rs
|
@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue