rework, add non-value rules
This commit is contained in:
parent
dd9620ef9e
commit
8613e712e7
|
@ -10,7 +10,8 @@ fn main() {
|
||||||
"color[red]$after",
|
"color[red]$after",
|
||||||
];
|
];
|
||||||
|
|
||||||
let css = dbg!(zephyr::generate_css(&classes));
|
let z = zephyr::Zephyr::new();
|
||||||
|
let css = z.generate_css(&classes);
|
||||||
|
|
||||||
let html = format!(
|
let html = format!(
|
||||||
r#"
|
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::collections::HashMap;
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
|
use defaults::default_rules;
|
||||||
|
|
||||||
|
use crate::parse::*;
|
||||||
|
|
||||||
|
mod class;
|
||||||
|
mod defaults;
|
||||||
mod modifiers;
|
mod modifiers;
|
||||||
mod name;
|
mod name;
|
||||||
mod parse;
|
mod parse;
|
||||||
mod rules;
|
|
||||||
|
|
||||||
pub fn generate_and_write(classes: &[&str], path: impl AsRef<Path>) -> Result<(), std::io::Error> {
|
// pub fn generate_and_write(classes: &[&str], path: impl AsRef<Path>) -> Result<(), std::io::Error> {
|
||||||
let out = generate_css(classes);
|
// let out = generate_css(classes);
|
||||||
std::fs::write(path, out)?;
|
// std::fs::write(path, out)?;
|
||||||
|
|
||||||
Ok(())
|
// Ok(())
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub struct Zephyr {
|
||||||
|
pub rules: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_css(classes: &[&str]) -> String {
|
impl Zephyr {
|
||||||
classes
|
/// builds a `Zephyr` with the default ruleset
|
||||||
.into_iter()
|
pub fn new() -> Self {
|
||||||
.flat_map(|c| generate_class(c))
|
Self {
|
||||||
.collect::<Vec<_>>()
|
rules: default_rules(),
|
||||||
.join("")
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_class(class: &str) -> Option<String> {
|
/// builds a `Zephyr` without the default ruleset
|
||||||
let class = parse_class(class)?;
|
pub fn new_without_rules() -> Self {
|
||||||
let rule = RULES
|
Self {
|
||||||
.get(&class.name.as_str())
|
rules: HashMap::new(),
|
||||||
.unwrap_or(&(&General as &dyn Rule));
|
}
|
||||||
Some(rule.generate(&class))
|
}
|
||||||
|
|
||||||
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use class::Class;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn generate_margin_works() {
|
fn generate_margin_works() {
|
||||||
|
let z = Zephyr::new();
|
||||||
|
|
||||||
let class = Class {
|
let class = Class {
|
||||||
name: "m".into(),
|
name: "m".into(),
|
||||||
value: "1rem",
|
value: Some("1rem"),
|
||||||
modifiers: vec![].into(),
|
modifiers: vec![].into(),
|
||||||
pseudo: None,
|
pseudo: None,
|
||||||
original: "m[1rem]",
|
original: "m[1rem]",
|
||||||
};
|
};
|
||||||
let css = General.generate(&class);
|
let css = class.generate(&z);
|
||||||
assert_eq!(css, r#".m\[1rem\] { margin: 1rem; }"#);
|
assert_eq!(css, r#".m\[1rem\] { margin: 1rem; }"#);
|
||||||
|
|
||||||
let class = Class {
|
let class = Class {
|
||||||
name: "m".into(),
|
name: "m".into(),
|
||||||
value: "1rem",
|
value: Some("1rem"),
|
||||||
modifiers: vec!["focus"].into(),
|
modifiers: vec!["focus"].into(),
|
||||||
pseudo: None,
|
pseudo: None,
|
||||||
original: "m[1rem]focus",
|
original: "m[1rem]focus",
|
||||||
};
|
};
|
||||||
let css = General.generate(&class);
|
let css = class.generate(&z);
|
||||||
assert_eq!(css, r#".m\[1rem\]focus:focus { margin: 1rem; }"#);
|
assert_eq!(css, r#".m\[1rem\]focus:focus { margin: 1rem; }"#);
|
||||||
|
|
||||||
let class = Class {
|
let class = Class {
|
||||||
name: "m".into(),
|
name: "m".into(),
|
||||||
value: "1rem",
|
value: Some("1rem"),
|
||||||
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",
|
||||||
};
|
};
|
||||||
let css = General.generate(&class);
|
let css = class.generate(&z);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
css,
|
css,
|
||||||
r#".m\[1rem\]focus,hover,odd:focus:hover:nth-child\(odd\) { margin: 1rem; }"#
|
r#".m\[1rem\]focus,hover,odd:focus:hover:nth-child\(odd\) { margin: 1rem; }"#
|
||||||
|
@ -71,11 +94,24 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn generate_classes_works() {
|
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!(
|
assert_eq!(
|
||||||
classes,
|
classes,
|
||||||
r#".m\[3rem\]hover,focus\$placeholder:hover:focus::placeholder { margin: 3rem; }"#
|
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>> {
|
pub(crate) fn parse_class<'a>(original: &'a str) -> Option<Class<'a>> {
|
||||||
let (class, pseudo) = if let Some((class, pseudo)) = original.split_once('$') {
|
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)
|
(original, None)
|
||||||
};
|
};
|
||||||
|
|
||||||
let start = pos(class, '[')?;
|
if let Some(p) = pos(class, '|') {
|
||||||
let end = pos(class, ']')?;
|
let mods = if p + 1 == class.len() {
|
||||||
|
vec![]
|
||||||
|
} else {
|
||||||
|
class[p + 1..].split(',').collect()
|
||||||
|
};
|
||||||
|
|
||||||
if start > end {
|
return Some(Class {
|
||||||
return None;
|
name: Name::new(&class[0..p]),
|
||||||
}
|
value: None,
|
||||||
|
modifiers: mods.into(),
|
||||||
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,
|
|
||||||
pseudo,
|
pseudo,
|
||||||
original,
|
original,
|
||||||
..
|
});
|
||||||
} = self;
|
}
|
||||||
|
|
||||||
let mut rest = if let Some(mods) = modifiers.get() {
|
match (pos(class, '['), pos(class, ']')) {
|
||||||
format!(":{mods}")
|
(Some(start), Some(end)) if start <= end => {
|
||||||
} else {
|
let mods = if end + 1 == class.len() {
|
||||||
"".to_string()
|
vec![]
|
||||||
};
|
} else {
|
||||||
if let Some(pseudo) = pseudo {
|
class[end + 1..].split(',').collect()
|
||||||
rest.push_str("::");
|
};
|
||||||
rest.push_str(pseudo);
|
|
||||||
|
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 {
|
mod tests {
|
||||||
use super::*;
|
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!(
|
assert_eq!(
|
||||||
parse_class(class),
|
parse_class(class),
|
||||||
Some(Class {
|
Some(Class {
|
||||||
|
@ -95,19 +77,28 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_works() {
|
fn parse_works() {
|
||||||
check("m[1rem]", ("m", "1rem", vec![], None));
|
check("m[1rem]", ("m", Some("1rem"), vec![], None));
|
||||||
check("text-align[center]", ("text-align", "center", vec![], None));
|
check(
|
||||||
check("something[one:two]", ("something", "one:two", vec![], None));
|
"text-align[center]",
|
||||||
|
("text-align", Some("center"), vec![], None),
|
||||||
|
);
|
||||||
|
check(
|
||||||
|
"something[one:two]",
|
||||||
|
("something", Some("one:two"), vec![], None),
|
||||||
|
);
|
||||||
// testing out weird unicode stuffs
|
// 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]
|
#[test]
|
||||||
fn parse_modifier() {
|
fn parse_modifier() {
|
||||||
check("a[b]hover", ("a", "b", vec!["hover"], None));
|
check("a[b]hover", ("a", Some("b"), vec!["hover"], None));
|
||||||
check(
|
check(
|
||||||
"text-align[center]focus",
|
"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() {
|
fn parse_multiple_modifiers() {
|
||||||
check(
|
check(
|
||||||
"a[b]hover,focus,odd",
|
"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() {
|
fn parse_pseudo() {
|
||||||
check(
|
check(
|
||||||
"a[b]hover,focus,odd$before",
|
"a[b]hover,focus,odd$before",
|
||||||
("a", "b", vec!["hover", "focus", "odd"], Some("before")),
|
(
|
||||||
|
"a",
|
||||||
|
Some("b"),
|
||||||
|
vec!["hover", "focus", "odd"],
|
||||||
|
Some("before"),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
check(
|
check(
|
||||||
"a[b]hover$before$after",
|
"a[b]hover$before$after",
|
||||||
("a", "b", vec!["hover"], Some("before$after")),
|
("a", Some("b"), vec!["hover"], Some("before$after")),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn out_of_order_is_none() {
|
fn closing_before_opening_means_no_value() {
|
||||||
assert_eq!(parse_class("a]b["), None);
|
check("a]b[", ("a]b[", None, vec![], None));
|
||||||
assert_eq!(parse_class("a]b[c]"), 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