From 502d626cb71fe3120da3cf3c62a0547669681a6d Mon Sep 17 00:00:00 2001 From: annieversary Date: Fri, 13 May 2022 22:33:13 +0100 Subject: [PATCH] add name and stuff --- Cargo.toml | 1 - src/lib.rs | 100 +++++++++-------------------------------------- src/modifiers.rs | 53 +++++++++++-------------- src/name.rs | 49 +++++++++++++++++++++++ src/parse.rs | 9 +++-- src/rules.rs | 41 +++++++++++++++++++ 6 files changed, 135 insertions(+), 118 deletions(-) create mode 100644 src/name.rs create mode 100644 src/rules.rs diff --git a/Cargo.toml b/Cargo.toml index 3d8a947..2d49b59 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,3 @@ edition = "2021" [dependencies] once_cell = "1.10.0" -thiserror = "1.0.31" diff --git a/src/lib.rs b/src/lib.rs index 841d2df..16a2bfc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,12 @@ -use once_cell::sync::Lazy; -use std::{collections::HashMap, path::Path}; -use thiserror::Error; - -use crate::parse::*; +use crate::{parse::*, rules::*}; +use std::path::Path; mod modifiers; +mod name; mod parse; +mod rules; -pub fn generate_and_write(classes: &[&str], path: impl AsRef) -> Result<(), Error> { +pub fn generate_and_write(classes: &[&str], path: impl AsRef) -> Result<(), std::io::Error> { let out = generate_css(classes); std::fs::write(path, out)?; @@ -24,75 +23,12 @@ pub fn generate_css(classes: &[&str]) -> String { pub fn generate_class(class: &str) -> Option { let class = parse_class(class)?; - let rule = RULES.get(&class.name)?; + let rule = RULES + .get(&class.name.as_str()) + .unwrap_or(&(&General as &dyn Rule)); Some(rule.generate(&class)) } -static RULES: Lazy> = Lazy::new(|| { - let mut m = HashMap::new(); - m.insert("m", &Margin as &dyn Rule); - m.insert("mt", &MarginTop as &dyn Rule); - m.insert("color", &Color as &dyn Rule); - m.insert("content", &Content as &dyn Rule); - m -}); - -// TODO maybe we can skip rules and make it just be a general rewritter - -trait Rule: Sync { - fn generate<'a>(&self, class: &Class<'a>) -> String; -} - -struct Margin; -impl Rule for Margin { - fn generate<'a>(&self, class: &Class<'a>) -> String { - format!( - "{selector} {{ margin: {value}; }}", - selector = class.selector(), - value = class.value - ) - } -} - -struct MarginTop; -impl Rule for MarginTop { - fn generate<'a>(&self, class: &Class<'a>) -> String { - format!( - "{selector} {{ margin-top: {value}; }}", - selector = class.selector(), - value = class.value - ) - } -} - -struct Color; -impl Rule for Color { - fn generate<'a>(&self, class: &Class<'a>) -> String { - format!( - "{selector} {{ color: {value}; }}", - selector = class.selector(), - value = class.value - ) - } -} - -struct Content; -impl Rule for Content { - fn generate<'a>(&self, class: &Class<'a>) -> String { - format!( - "{selector} {{ content: {value}; }}", - selector = class.selector(), - value = class.value - ) - } -} - -#[derive(Error, Debug)] -pub enum Error { - #[error("io error")] - Disconnect(#[from] std::io::Error), -} - #[cfg(test)] mod tests { use super::*; @@ -100,36 +36,36 @@ mod tests { #[test] fn generate_margin_works() { let class = Class { - name: "m", + name: "m".into(), value: "1rem", modifiers: vec![].into(), pseudo: None, original: "m[1rem]", }; - let css = Margin.generate(&class); - assert_eq!(css, ".m[1rem] { margin: 1rem; }"); + let css = General.generate(&class); + assert_eq!(css, r#".m\[1rem\] { margin: 1rem; }"#); let class = Class { - name: "m", + name: "m".into(), value: "1rem", modifiers: vec!["focus"].into(), pseudo: None, original: "m[1rem]focus", }; - let css = Margin.generate(&class); - assert_eq!(css, ".m[1rem]focus:focus { margin: 1rem; }"); + let css = General.generate(&class); + assert_eq!(css, r#".m\[1rem\]focus:focus { margin: 1rem; }"#); let class = Class { - name: "m", + name: "m".into(), value: "1rem", modifiers: vec!["focus", "hover", "odd"].into(), pseudo: None, original: "m[1rem]focus,hover,odd", }; - let css = Margin.generate(&class); + let css = General.generate(&class); assert_eq!( css, - ".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; }"# ); } @@ -139,7 +75,7 @@ mod tests { assert_eq!( classes, - ".m[3rem]hover,focus$placeholder:hover:focus::placeholder { margin: 3rem; }" + r#".m\[3rem\]hover,focus\$placeholder:hover:focus::placeholder { margin: 3rem; }"# ); } } diff --git a/src/modifiers.rs b/src/modifiers.rs index 45484b1..3701ee6 100644 --- a/src/modifiers.rs +++ b/src/modifiers.rs @@ -1,3 +1,17 @@ +use once_cell::sync::Lazy; +use std::collections::HashMap; + +static REPLACEMENTS: Lazy> = Lazy::new(|| { + [ + ("odd", "nth-child(odd)"), + ("even", "nth-child(even)"), + ("first", "first-child"), + ("last", "last-child"), + ("only", "only-child"), + ] + .into() +}); + #[derive(Default, PartialEq, Debug)] pub(crate) struct Modifiers<'a>(Vec>); @@ -9,7 +23,7 @@ impl<'a> Modifiers<'a> { Some( self.0 .iter() - .map(Modifier::value) + .map(Modifier::as_str) .collect::>() .join(":"), ) @@ -27,10 +41,6 @@ impl<'a> From> for Modifiers<'a> { } } -// TODO something like this -// i wanna be able to have both replaced variables for common modifiers -// eg: odd -> :nth-child(odd) -// but i also wanna be able to keep it relaxed so you can type whatever #[derive(Debug, PartialEq)] enum Modifier<'a> { Converted { from: &'a str, to: &'static str }, @@ -39,36 +49,17 @@ enum Modifier<'a> { impl<'a> Modifier<'a> { fn new(s: &'a str) -> Self { - match s { - "odd" => Self::Converted { - from: s, - to: "nth-child(odd)", - }, - "even" => Self::Converted { - from: s, - to: "nth-child(even)", - }, - "first" => Self::Converted { - from: s, - to: "first-child", - }, - "last" => Self::Converted { - from: s, - to: "last-child", - }, - "only" => Self::Converted { - from: s, - to: "only-child", - }, - // TODO add more - _ => Self::Unknown(s), + if let Some(to) = REPLACEMENTS.get(s) { + Self::Converted { from: s, to } + } else { + Self::Unknown(s) } } - fn value(&self) -> &str { + fn as_str(&self) -> &str { match self { - Modifier::Converted { from, to } => to, - Modifier::Unknown(v) => v, + Self::Converted { to, .. } => to, + Self::Unknown(v) => v, } } } diff --git a/src/name.rs b/src/name.rs new file mode 100644 index 0000000..8dfa82c --- /dev/null +++ b/src/name.rs @@ -0,0 +1,49 @@ +use once_cell::sync::Lazy; +use std::collections::HashMap; + +static REPLACEMENTS: Lazy> = Lazy::new(|| { + [ + ("m", "margin"), + ("mt", "margin-top"), + ("mb", "margin-bottom"), + ("ml", "margin-left"), + ("mr", "margin-right"), + ("p", "padding"), + ("pt", "padding-top"), + ("pb", "padding-bottom"), + ("pl", "padding-left"), + ("pr", "padding-right"), + ("bg", "background"), + ("bgc", "background-color"), + ] + .into() +}); + +#[derive(Debug, PartialEq)] +pub(crate) enum Name<'a> { + Converted { from: &'a str, to: &'static str }, + Unknown(&'a str), +} + +impl<'a> Name<'a> { + pub(crate) fn new(s: &'a str) -> Self { + if let Some(to) = REPLACEMENTS.get(s) { + Self::Converted { from: s, to } + } else { + Self::Unknown(s) + } + } + + pub(crate) fn as_str(&self) -> &str { + match self { + Self::Converted { to, .. } => to, + Self::Unknown(v) => v, + } + } +} + +impl<'a> From<&'a str> for Name<'a> { + fn from(s: &'a str) -> Self { + Name::new(s) + } +} diff --git a/src/parse.rs b/src/parse.rs index c1d5555..f0caabb 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -1,4 +1,4 @@ -use crate::modifiers::Modifiers; +use crate::{modifiers::Modifiers, name::Name}; pub(crate) fn parse_class<'a>(original: &'a str) -> Option> { let (class, pseudo) = if let Some((class, pseudo)) = original.split_once('$') { @@ -21,7 +21,7 @@ pub(crate) fn parse_class<'a>(original: &'a str) -> Option> { }; Some(Class { - name: &class[0..start], + name: Name::new(&class[0..start]), value: &class[start + 1..end], modifiers: mods.into(), pseudo, @@ -31,7 +31,7 @@ pub(crate) fn parse_class<'a>(original: &'a str) -> Option> { #[derive(PartialEq, Debug)] pub(crate) struct Class<'a> { - pub name: &'a str, + pub name: Name<'a>, pub value: &'a str, pub modifiers: Modifiers<'a>, pub pseudo: Option<&'a str>, @@ -68,6 +68,7 @@ impl<'a> Class<'a> { .replace('$', "\\$") .replace('\'', "\\'") .replace('*', "\\*") + .replace('%', "\\%") } } @@ -83,7 +84,7 @@ mod tests { assert_eq!( parse_class(class), Some(Class { - name, + name: Name::new(name), value, modifiers: modifiers.into(), pseudo, diff --git a/src/rules.rs b/src/rules.rs new file mode 100644 index 0000000..20acf99 --- /dev/null +++ b/src/rules.rs @@ -0,0 +1,41 @@ +use once_cell::sync::Lazy; +use std::collections::HashMap; + +use crate::parse::Class; + +pub(crate) static RULES: Lazy> = 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(), + ) + } +}