From 21b3134b9e5fb8afd7548fd70d54bdf64e06a6bb Mon Sep 17 00:00:00 2001 From: oyelowo Date: Sat, 7 Oct 2023 13:20:29 -0600 Subject: [PATCH 01/46] Base implementation of new classname parser --- Cargo.toml | 1 + tailwind/src/main.rs | 6 ++ tw-macro/Cargo.toml | 1 + tw-macro/src/lib.rs | 217 ++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 223 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 18f92d3..42443b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ tw-macro = { path = "tw-macro" } proc-macro2 = "1.0.66" quote = "1.0.33" syn = "2.0.29" +nom = "7.1.3" static_assertions = "1.1.0" serde = { version = "1.0.188", features = ["derive"] } serde_json = "1.0.105" diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index 2e67616..f0ffcc7 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -6,9 +6,15 @@ * Licensed under the MIT license */ use tw_macro::tw; +use tw_macro::tww; fn main() { + let _ = tww!("btn btn"); + let test = tww!("btn active:hover:collapse-arrow"); + println!("TEXT - {}", test); let _ = tw!("btn collapse-arrow"); + tww!("bg-gray-600 bg-sky-700 bg-midnight underline"); + // tww!("bg-gray-600 aria-checked:bg-sky-700 aria-asc:bg-midnight data-checked:underline"); let _classnames = tw!("bg-taxvhiti bg-tahiti-500 bg-tahiti bg-midnight bg-red-50"); let _classnames = tw!("bg-taxvhiti bg-tahiti-500 bg-tahiti bg-midnight bg-purple bg-red-50 bg-tahiti-800 border-s-tahiti-800"); let _classnames = tw!("md:text-red-50 text-slate-50 text-purple text-tahiti-500"); diff --git a/tw-macro/Cargo.toml b/tw-macro/Cargo.toml index 74eb8f4..e90da69 100644 --- a/tw-macro/Cargo.toml +++ b/tw-macro/Cargo.toml @@ -17,6 +17,7 @@ daisyui = [] syn = { workspace = true } proc-macro2 = { workspace = true } quote = { workspace = true } +nom = { workspace = true } static_assertions = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 363002e..8d58467 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -1,3 +1,11 @@ +use nom::{ + bytes::complete::{tag, take_while1}, + character::complete::{multispace0, multispace1, space0, space1}, + combinator::{all_consuming, opt, recognize}, + multi::separated_list0, + sequence::{delimited, tuple}, + IResult, +}; /* * Author: Oyelowo Oyedayo * Email: oyelowo.oss@gmail.com @@ -9,8 +17,8 @@ mod config; mod plugins; mod tailwind; use tailwind::{ - lengthy::LENGTHY, modifiers::get_modifiers, tailwind_config::CustomisableClasses, - valid_baseclass_names::VALID_BASECLASS_NAMES, + default_classnames::TAILWIND_CSS, lengthy::LENGTHY, modifiers::get_modifiers, + tailwind_config::CustomisableClasses, valid_baseclass_names::VALID_BASECLASS_NAMES, }; use config::{get_classes, noconfig::UNCONFIGURABLE, read_tailwind_config}; @@ -18,6 +26,8 @@ use proc_macro::TokenStream; use regex::{self, Regex}; use tailwind::signable::SIGNABLES; // use tailwindcss_core::parser::{Extractor, ExtractorOptions}; +// +// p-6 max-w-sm mx-auto bg-white rounded-xl shadow-lg flex items-center space-x-4 #[proc_macro] pub fn tw(raw_input: TokenStream) -> TokenStream { @@ -342,3 +352,206 @@ fn is_valid_string(s: &str) -> bool { let re = Regex::new(r"^[a-zA-Z0-9_-]*$").expect("Invalid regex"); re.is_match(s) && !s.is_empty() } + +// fn is_valid_id_char(c: char) -> bool { +// c.is_alphanumeric() || c == '_' || c == '-' +// } +// +// fn parse_identifier(input: &str) -> IResult<&str, &str> { +// take_while1(is_valid_id_char)(input) +// } + +// fn parse_record_inner(input: &str) -> IResult<&str, Vec<&str>> { +// let (input, _) = space0(input)?; +// let (input, _) = tag("<")(input)?; +// let (input, _) = space0(input)?; +// let (input, ref_tables) = +// separated_list0(tag("|"), tuple((space0, parse_identifier, space0)))(input)?; +// let (input, _) = space0(input)?; +// let (input, _) = tag(">")(input)?; +// let (input, _) = space0(input)?; +// Ok((input, ref_tables.iter().map(|t| t.1).collect())) +// } +// +// +// fn parse_record_type(input: &str) -> IResult<&str, FieldType> { +// let (input, _) = tag("record")(input)?; +// let (input, rt) = opt(parse_record_inner)(input)?; +// // let (input, rt) = cut(opt(parse_record_inner))(input)?; +// Ok(( +// input, +// FieldType::Record( +// rt.unwrap_or(vec![]) +// .into_iter() +// .map(|t| t.to_string().into()) +// .collect(), +// ), +// )) +// } +// + +fn get_classes_straight() -> Vec { + get_classes(&read_tailwind_config().unwrap()) + // get_classes +} +fn is_valid_classname2(class_name: &str) -> bool { + get_classes_straight().contains(&class_name.to_string()) +} + +fn is_valid_modifier2(modifier: &str) -> bool { + get_modifiers(&read_tailwind_config().unwrap()).contains(&modifier.to_string()) +} + +// fn parse_tw_full_classname(input: &str) -> IResult<&str, &str> { +// let (input, class_names) = (is_valid_classname)(input)?; +// Ok((input, "")) +// } + +fn parse_single_tw_classname(input: &str) -> IResult<&str, &str> { + let (input, class_name) = recognize(|i| { + // Assuming a Tailwind class consists of alphanumeric, dashes, and colons + nom::bytes::complete::is_a( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-:", + )(i) + })(input)?; + + if is_valid_classname2(class_name) { + Ok((input, class_name)) + } else { + Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))) + } +} + +// rules: colon(:) preceeded by either valid identifier or closed bracket +// // postceeded by either valid identifier or open bracket +// // e.g +fn modifier_separator(input: &str) -> IResult<&str, &str> { + let (input, _) = tag(":")(input)?; + Ok((input, "")) +} + +// [&:nth-child(3)]:underline +// lg:[&:nth-child(3)]:hover:underline +// [&_p]:mt-4 +// flex [@supports(display:grid)]:grid +// [@media(any-hover:hover){&:hover}]:opacity-100 +// group/edit invisible hover:bg-slate-200 group-hover/item:visible +// hidden group-[.is-published]:block +// group-[:nth-of-type(3)_&]:block +// peer-checked/published:text-sky-500 +// peer-[.is-dirty]:peer-required:block hidden +// hidden peer-[:nth-of-type(3)_&]:block +// after:content-['*'] after:ml-0.5 after:text-red-500 block text-sm font-medium text-slate-700 +// before:content-[''] before:block +// bg-black/75 supports-[backdrop-filter]:bg-black/25 supports-[backdrop-filter]:backdrop-blur +// aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg-[url('/img/up-arrow.svg')] +// group-aria-[sort=ascending]:rotate-0 group-aria-[sort=descending]:rotate-180 +// data-[size=large]:p-8 +// open:bg-white dark:open:bg-slate-900 open:ring-1 open:ring-black/5 dark:open:ring-white/10 open:shadow-lg p-6 rounded-lg +// lg:[&:nth-child(3)]:hover:underline +// min-[320px]:text-center max-[600px]:bg-sky-300 +// top-[117px] lg:top-[344px] +// bg-[#bada55] text-[22px] before:content-['Festivus'] +// grid grid-cols-[fit-content(theme(spacing.32))] +// bg-[--my-color] +// [mask-type:luminance] hover:[mask-type:alpha] +// [--scroll-offset:56px] lg:[--scroll-offset:44px] +// lg:[&:nth-child(3)]:hover:underline +// bg-[url('/what_a_rush.png')] +// before:content-['hello\_world'] +// text-[22px] +// text-[#bada55] +// text-[var(--my-var)] +// text-[length:var(--my-var)] +// text-[color:var(--my-var)] +// +fn modifier(input: &str) -> IResult<&str, &str> { + let (input, modifier) = recognize(|i| { + // Assuming a Tailwind class consists of alphanumeric, dashes, and colons + nom::bytes::complete::is_a( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-", + )(i) + })(input)?; + + if is_valid_modifier2(modifier) { + Ok((input, modifier)) + } else { + Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))) + } +} + +fn modifiers_chained(input: &str) -> IResult<&str, Vec<&str>> { + let (input, modifiers) = separated_list0(tag(":"), modifier)(input)?; + Ok((input, modifiers)) +} + +fn parse_tw_full_classname(input: &str) -> IResult<&str, Vec<&str>> { + // Parses one or more Tailwind class names separated by spaces, allowing optional spaces before and after each class name + // let (input, class_names) = delimited( + // multispace0, + // separated_list0(multispace1, parse_single_tw_classname), + // multispace0, + // )(input)?; + + let (input, class_names) = tuple(( + opt(tuple((modifiers_chained, tag(":")))), + parse_single_tw_classname, + ))(input)?; + + // Ok((input, class_names)) + Ok((input, vec![])) +} + +fn parse_class_names(input: &str) -> IResult<&str, Vec<&str>> { + // let (input, _) = space0(input)?; + // let (input, class_names) = separated_list0(space1, parse_tw_full_classname)(input)?; + // let (input, class_names) = separated_list0(space1, tag("btn"))(input)?; + let (input, class_names) = separated_list0(multispace1, parse_tw_full_classname)(input)?; + // let (input, _) = space0(input)?; + + Ok((input, vec![])) +} + +fn parse_top(input: &str) -> IResult<&str, Vec<&str>> { + parse_class_names(input) + // all_consuming(parse_class_names)(input) +} + +// p-6 max-w-sm mx-auto bg-white rounded-xl shadow-lg flex items-center space-x-4 +#[proc_macro] +pub fn tww(raw_input: TokenStream) -> TokenStream { + let r_input = raw_input.clone(); + let input = parse_macro_input!(r_input as LitStr); + let (modifiers, valid_class_names) = match setup(&input) { + Ok(value) => value, + Err(value) => { + return syn::Error::new_spanned(input, value) + .to_compile_error() + .into() + } + }; + let full_classnames = input.value(); + + let (input, class_names) = match parse_top(&full_classnames) { + Ok(value) => value, + Err(value) => { + return syn::Error::new_spanned(input, value) + .to_compile_error() + .into() + } + }; + + // for word in input.value().split_whitespace() { + + // raw_input + quote::quote! { + #input + } + .into() +} From a31122c9d0911a4d047edd5ae96a48c68ea528f2 Mon Sep 17 00:00:00 2001 From: oyelowo Date: Sun, 8 Oct 2023 12:58:45 -0600 Subject: [PATCH 02/46] SUpport kv arbitrary property --- tailwind/src/main.rs | 2 +- tw-macro/src/lib.rs | 83 ++++++++++++++++++++++++++++++-------------- 2 files changed, 57 insertions(+), 28 deletions(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index f0ffcc7..d64e3d7 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -10,7 +10,7 @@ use tw_macro::tww; fn main() { let _ = tww!("btn btn"); - let test = tww!("btn active:hover:collapse-arrow"); + let test = tww!("[mask-type:alpha] btn active:hover:collapse-arrow"); println!("TEXT - {}", test); let _ = tw!("btn collapse-arrow"); tww!("bg-gray-600 bg-sky-700 bg-midnight underline"); diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 8d58467..d8d03f0 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -1,4 +1,5 @@ use nom::{ + branch::alt, bytes::complete::{tag, take_while1}, character::complete::{multispace0, multispace1, space0, space1}, combinator::{all_consuming, opt, recognize}, @@ -407,32 +408,6 @@ fn is_valid_modifier2(modifier: &str) -> bool { // Ok((input, "")) // } -fn parse_single_tw_classname(input: &str) -> IResult<&str, &str> { - let (input, class_name) = recognize(|i| { - // Assuming a Tailwind class consists of alphanumeric, dashes, and colons - nom::bytes::complete::is_a( - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-:", - )(i) - })(input)?; - - if is_valid_classname2(class_name) { - Ok((input, class_name)) - } else { - Err(nom::Err::Error(nom::error::Error::new( - input, - nom::error::ErrorKind::Tag, - ))) - } -} - -// rules: colon(:) preceeded by either valid identifier or closed bracket -// // postceeded by either valid identifier or open bracket -// // e.g -fn modifier_separator(input: &str) -> IResult<&str, &str> { - let (input, _) = tag(":")(input)?; - Ok((input, "")) -} - // [&:nth-child(3)]:underline // lg:[&:nth-child(3)]:hover:underline // [&_p]:mt-4 @@ -467,7 +442,61 @@ fn modifier_separator(input: &str) -> IResult<&str, &str> { // text-[var(--my-var)] // text-[length:var(--my-var)] // text-[color:var(--my-var)] -// +fn parse_predefined_tw_classname(input: &str) -> IResult<&str, ()> { + let (input, class_name) = recognize(|i| { + // Assuming a Tailwind class consists of alphanumeric, dashes, and colons + nom::bytes::complete::is_a( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-:", + )(i) + })(input)?; + + if is_valid_classname2(class_name) { + // Ok((input, class_name)) + Ok((input, ())) + } else { + Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))) + } +} + +fn is_ident_char(c: char) -> bool { + c.is_alphanumeric() || c == '_' || c == '-' +} + +// e.g: [mask-type:alpha] +fn kv_pair_classname(input: &str) -> IResult<&str, ()> { + // let Ok((input, _)) = delimited( + // tag("["), + // tuple(( + // take_while1(is_ident_char), + // tag(":"), + // take_while1(is_ident_char), + // )), + // tag("]"), + // )(input); + // Ok((input, ())) + let (input, _) = tag("[")(input)?; + let (input, _) = take_while1(is_ident_char)(input)?; + let (input, _) = tag(":")(input)?; + let (input, _) = take_while1(is_ident_char)(input)?; + let (input, _) = tag("]")(input)?; + Ok((input, ())) +} + +fn parse_single_tw_classname(input: &str) -> IResult<&str, ()> { + alt((parse_predefined_tw_classname, kv_pair_classname))(input) +} + +// rules: colon(:) preceeded by either valid identifier or closed bracket +// // postceeded by either valid identifier or open bracket +// // e.g +fn modifier_separator(input: &str) -> IResult<&str, &str> { + let (input, _) = tag(":")(input)?; + Ok((input, "")) +} + fn modifier(input: &str) -> IResult<&str, &str> { let (input, modifier) = recognize(|i| { // Assuming a Tailwind class consists of alphanumeric, dashes, and colons From 4b466f0761bff9da624d418aee99c5b82ed8c8c9 Mon Sep 17 00:00:00 2001 From: oyelowo Date: Sun, 8 Oct 2023 17:05:05 -0600 Subject: [PATCH 03/46] Support lengthy arbitrary classname --- tailwind/src/main.rs | 2 +- tw-macro/src/lib.rs | 55 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index d64e3d7..bfe57a0 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -10,7 +10,7 @@ use tw_macro::tww; fn main() { let _ = tww!("btn btn"); - let test = tww!("[mask-type:alpha] btn active:hover:collapse-arrow"); + let test = tww!("[mask-type:alpha] text-[22cm] btn active:hover:collapse-arrow"); println!("TEXT - {}", test); let _ = tw!("btn collapse-arrow"); tww!("bg-gray-600 bg-sky-700 bg-midnight underline"); diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index d8d03f0..633d1d3 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -1,9 +1,10 @@ use nom::{ branch::alt, - bytes::complete::{tag, take_while1}, + bytes::complete::{tag, take_until, take_while1}, character::complete::{multispace0, multispace1, space0, space1}, combinator::{all_consuming, opt, recognize}, multi::separated_list0, + number, sequence::{delimited, tuple}, IResult, }; @@ -465,6 +466,52 @@ fn is_ident_char(c: char) -> bool { c.is_alphanumeric() || c == '_' || c == '-' } +fn is_lengthy_classname(class_name: &str) -> bool { + LENGTHY.contains(&class_name) +} + +/// text-[22px] +fn lengthy_arbitrary_classname(input: &str) -> IResult<&str, ()> { + let (input, class_name) = take_until("-[")(input)?; + let ((input, _)) = if is_lengthy_classname(class_name) { + // if is_lengthy_classname(class_name) { + // // Do something special for lengthy class names + // } + Ok((input, ())) + } else { + Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))) + }?; + + // arbitrary value + let (input, _) = tag("-")(input)?; + let (input, _) = tag("[")(input)?; + // is number + let (input, _) = number::complete::double(input)?; + let (input, _) = { + // px|em|rem|%|cm|mm|in|pt|pc|vh|vw|vmin|vmax + alt(( + tag("px"), + tag("em"), + tag("rem"), + tag("%"), + tag("cm"), + tag("mm"), + tag("in"), + tag("pt"), + tag("pc"), + tag("vh"), + tag("vw"), + tag("vmin"), + tag("vmax"), + )) + }(input)?; + let (input, _) = tag("]")(input)?; + Ok((input, ())) +} + // e.g: [mask-type:alpha] fn kv_pair_classname(input: &str) -> IResult<&str, ()> { // let Ok((input, _)) = delimited( @@ -486,7 +533,11 @@ fn kv_pair_classname(input: &str) -> IResult<&str, ()> { } fn parse_single_tw_classname(input: &str) -> IResult<&str, ()> { - alt((parse_predefined_tw_classname, kv_pair_classname))(input) + alt(( + parse_predefined_tw_classname, + kv_pair_classname, + lengthy_arbitrary_classname, + ))(input) } // rules: colon(:) preceeded by either valid identifier or closed bracket From 0582cf606158e8e29d08a505a5319932db0a073e Mon Sep 17 00:00:00 2001 From: oyelowo Date: Sun, 8 Oct 2023 23:52:51 -0600 Subject: [PATCH 04/46] Add colorful classnames --- tw-macro/src/tailwind/colorful.rs | 24 ++++++++++++++++++++++++ tw-macro/src/tailwind/mod.rs | 1 + 2 files changed, 25 insertions(+) create mode 100644 tw-macro/src/tailwind/colorful.rs diff --git a/tw-macro/src/tailwind/colorful.rs b/tw-macro/src/tailwind/colorful.rs new file mode 100644 index 0000000..4e5e4ac --- /dev/null +++ b/tw-macro/src/tailwind/colorful.rs @@ -0,0 +1,24 @@ +pub const COLORFUL_BASECLASSES: [&str; 22] = [ + "text", + "bg", + "border", + "border-x", + "border-y", + "border-s", + "border-e", + "border-t", + "border-r", + "border-b", + "border-l", + "divide", + "outline", + "ring", + "ring-offset", + "shadow", + "caret", + "accent", + "fill", + "stroke", + "placeholder", + "decoration", +]; diff --git a/tw-macro/src/tailwind/mod.rs b/tw-macro/src/tailwind/mod.rs index 2312602..3273428 100644 --- a/tw-macro/src/tailwind/mod.rs +++ b/tw-macro/src/tailwind/mod.rs @@ -4,6 +4,7 @@ * Copyright (c) 2023 Oyelowo Oyedayo * Licensed under the MIT license */ +pub mod colorful; pub mod default_classnames; pub mod lengthy; pub mod modifiers; From 9e0e2d430c67f82d1e765bfbc3e22e78701c4cca Mon Sep 17 00:00:00 2001 From: oyelowo Date: Mon, 9 Oct 2023 10:20:34 -0600 Subject: [PATCH 05/46] Validate arbitrary hex color --- tailwind/src/main.rs | 4 +++- tw-macro/src/lib.rs | 49 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index bfe57a0..9de6512 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -10,7 +10,9 @@ use tw_macro::tww; fn main() { let _ = tww!("btn btn"); - let test = tww!("[mask-type:alpha] text-[22cm] btn active:hover:collapse-arrow"); + let test = tww!( + "[mask-type:alpha] active:hover:text-[#bada55] text-[#bada55] text-[22cm] btn active:hover:collapse-arrow" + ); println!("TEXT - {}", test); let _ = tw!("btn collapse-arrow"); tww!("bg-gray-600 bg-sky-700 bg-midnight underline"); diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 633d1d3..78e0dc0 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -19,8 +19,9 @@ mod config; mod plugins; mod tailwind; use tailwind::{ - default_classnames::TAILWIND_CSS, lengthy::LENGTHY, modifiers::get_modifiers, - tailwind_config::CustomisableClasses, valid_baseclass_names::VALID_BASECLASS_NAMES, + colorful::COLORFUL_BASECLASSES, default_classnames::TAILWIND_CSS, lengthy::LENGTHY, + modifiers::get_modifiers, tailwind_config::CustomisableClasses, + valid_baseclass_names::VALID_BASECLASS_NAMES, }; use config::{get_classes, noconfig::UNCONFIGURABLE, read_tailwind_config}; @@ -470,7 +471,7 @@ fn is_lengthy_classname(class_name: &str) -> bool { LENGTHY.contains(&class_name) } -/// text-[22px] +// text-[22px] fn lengthy_arbitrary_classname(input: &str) -> IResult<&str, ()> { let (input, class_name) = take_until("-[")(input)?; let ((input, _)) = if is_lengthy_classname(class_name) { @@ -512,6 +513,47 @@ fn lengthy_arbitrary_classname(input: &str) -> IResult<&str, ()> { Ok((input, ())) } +fn is_hex_color(color: &str) -> bool { + let re = regex::Regex::new(r"^#[0-9a-fA-F]{3,6}$").expect("Invalid regex"); + re.is_match(color) +} + +fn is_colorful_baseclass(class_name: &str) -> bool { + COLORFUL_BASECLASSES.contains(&class_name) +} + +// text-[#bada55] +fn colorful_arbitrary_baseclass(input: &str) -> IResult<&str, ()> { + let (input, class_name) = take_until("-[")(input)?; + let ((input, _)) = if is_colorful_baseclass(class_name) { + Ok((input, ())) + } else { + Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))) + }?; + + // arbitrary value + let (input, _) = tag("-")(input)?; + let (input, _) = tag("[")(input)?; + // is hex color + // let (input, _) = tag("#")(input)?; + // let (input, color) = take_while1(|c: char| c.is_ascii_hexdigit())(input)?; + // should be length 3 or 6 + let (input, color) = take_until("]")(input)?; + let ((input, _)) = if is_hex_color(color) { + Ok((input, ())) + } else { + Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))) + }?; + + let (input, _) = tag("]")(input)?; + Ok((input, ())) +} // e.g: [mask-type:alpha] fn kv_pair_classname(input: &str) -> IResult<&str, ()> { // let Ok((input, _)) = delimited( @@ -537,6 +579,7 @@ fn parse_single_tw_classname(input: &str) -> IResult<&str, ()> { parse_predefined_tw_classname, kv_pair_classname, lengthy_arbitrary_classname, + colorful_arbitrary_baseclass, ))(input) } From 0964587785a5338776918f509c9826940384b064 Mon Sep 17 00:00:00 2001 From: oyelowo Date: Mon, 9 Oct 2023 10:30:25 -0600 Subject: [PATCH 06/46] Allow sacing in arbitrary length and color --- tailwind/src/main.rs | 3 ++- tw-macro/src/lib.rs | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index 9de6512..2faf821 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -11,7 +11,8 @@ use tw_macro::tww; fn main() { let _ = tww!("btn btn"); let test = tww!( - "[mask-type:alpha] active:hover:text-[#bada55] text-[#bada55] text-[22cm] btn active:hover:collapse-arrow" + "[mask-type:alpha] [ mask-type: alpha ] active:hover:text-[#bada55] active:hover:text-[ #bada55 ] text-[#bada55] hover:aria-checked:text-[22px] + text-[22.34e434cm] btn active:hover:collapse-arrow" ); println!("TEXT - {}", test); let _ = tw!("btn collapse-arrow"); diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 78e0dc0..f593760 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -489,6 +489,7 @@ fn lengthy_arbitrary_classname(input: &str) -> IResult<&str, ()> { // arbitrary value let (input, _) = tag("-")(input)?; let (input, _) = tag("[")(input)?; + let (input, _) = multispace0(input)?; // is number let (input, _) = number::complete::double(input)?; let (input, _) = { @@ -509,6 +510,7 @@ fn lengthy_arbitrary_classname(input: &str) -> IResult<&str, ()> { tag("vmax"), )) }(input)?; + let (input, _) = multispace0(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } @@ -537,12 +539,13 @@ fn colorful_arbitrary_baseclass(input: &str) -> IResult<&str, ()> { // arbitrary value let (input, _) = tag("-")(input)?; let (input, _) = tag("[")(input)?; + let (input, _) = multispace0(input)?; // is hex color // let (input, _) = tag("#")(input)?; // let (input, color) = take_while1(|c: char| c.is_ascii_hexdigit())(input)?; // should be length 3 or 6 let (input, color) = take_until("]")(input)?; - let ((input, _)) = if is_hex_color(color) { + let ((input, _)) = if is_hex_color(color.trim()) { Ok((input, ())) } else { Err(nom::Err::Error(nom::error::Error::new( @@ -567,9 +570,13 @@ fn kv_pair_classname(input: &str) -> IResult<&str, ()> { // )(input); // Ok((input, ())) let (input, _) = tag("[")(input)?; + let (input, _) = multispace0(input)?; let (input, _) = take_while1(is_ident_char)(input)?; + let (input, _) = multispace0(input)?; let (input, _) = tag(":")(input)?; + let (input, _) = multispace0(input)?; let (input, _) = take_while1(is_ident_char)(input)?; + let (input, _) = multispace0(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } From 496ce9a1624cae4bc94c672fc00254a2c46c175e Mon Sep 17 00:00:00 2001 From: oyelowo Date: Mon, 9 Oct 2023 15:58:10 -0600 Subject: [PATCH 07/46] Support arbitrary content --- tailwind/src/main.rs | 4 +++- tw-macro/src/lib.rs | 53 +++++++++----------------------------------- 2 files changed, 14 insertions(+), 43 deletions(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index 2faf821..07d0ed6 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -11,7 +11,9 @@ use tw_macro::tww; fn main() { let _ = tww!("btn btn"); let test = tww!( - "[mask-type:alpha] [ mask-type: alpha ] active:hover:text-[#bada55] active:hover:text-[ #bada55 ] text-[#bada55] hover:aria-checked:text-[22px] + "[mask-type:alpha] [ mask-type: alpha ] before:content-['rerer erer re rr r \re reFestivus'] +after:content-['I am a content'] after:content-['I am a content'] after:content-['I am a content'] + active:hover:text-[#bada55] active:hover:text-[ #bada55 ] text-[#bada55] hover:aria-checked:text-[22px] text-[22.34e434cm] btn active:hover:collapse-arrow" ); println!("TEXT - {}", test); diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index f593760..097a77a 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -32,6 +32,7 @@ use tailwind::signable::SIGNABLES; // // p-6 max-w-sm mx-auto bg-white rounded-xl shadow-lg flex items-center space-x-4 +// OLD IMPLEMENTATION #[proc_macro] pub fn tw(raw_input: TokenStream) -> TokenStream { let r_input = raw_input.clone(); @@ -356,43 +357,7 @@ fn is_valid_string(s: &str) -> bool { re.is_match(s) && !s.is_empty() } -// fn is_valid_id_char(c: char) -> bool { -// c.is_alphanumeric() || c == '_' || c == '-' -// } -// -// fn parse_identifier(input: &str) -> IResult<&str, &str> { -// take_while1(is_valid_id_char)(input) -// } - -// fn parse_record_inner(input: &str) -> IResult<&str, Vec<&str>> { -// let (input, _) = space0(input)?; -// let (input, _) = tag("<")(input)?; -// let (input, _) = space0(input)?; -// let (input, ref_tables) = -// separated_list0(tag("|"), tuple((space0, parse_identifier, space0)))(input)?; -// let (input, _) = space0(input)?; -// let (input, _) = tag(">")(input)?; -// let (input, _) = space0(input)?; -// Ok((input, ref_tables.iter().map(|t| t.1).collect())) -// } -// -// -// fn parse_record_type(input: &str) -> IResult<&str, FieldType> { -// let (input, _) = tag("record")(input)?; -// let (input, rt) = opt(parse_record_inner)(input)?; -// // let (input, rt) = cut(opt(parse_record_inner))(input)?; -// Ok(( -// input, -// FieldType::Record( -// rt.unwrap_or(vec![]) -// .into_iter() -// .map(|t| t.to_string().into()) -// .collect(), -// ), -// )) -// } -// - +// OLD IMPLEMENTATION fn get_classes_straight() -> Vec { get_classes(&read_tailwind_config().unwrap()) // get_classes @@ -405,11 +370,6 @@ fn is_valid_modifier2(modifier: &str) -> bool { get_modifiers(&read_tailwind_config().unwrap()).contains(&modifier.to_string()) } -// fn parse_tw_full_classname(input: &str) -> IResult<&str, &str> { -// let (input, class_names) = (is_valid_classname)(input)?; -// Ok((input, "")) -// } - // [&:nth-child(3)]:underline // lg:[&:nth-child(3)]:hover:underline // [&_p]:mt-4 @@ -581,12 +541,21 @@ fn kv_pair_classname(input: &str) -> IResult<&str, ()> { Ok((input, ())) } +// before:content-['Festivus'] +fn arbitrary_content(input: &str) -> IResult<&str, ()> { + let (input, _) = tag("content-['")(input)?; + let (input, _) = take_until("']")(input)?; + let (input, _) = tag("']")(input)?; + Ok((input, ())) +} + fn parse_single_tw_classname(input: &str) -> IResult<&str, ()> { alt(( parse_predefined_tw_classname, kv_pair_classname, lengthy_arbitrary_classname, colorful_arbitrary_baseclass, + arbitrary_content, ))(input) } From 59b83d6d96df886f9d1d3158b4ef4eb388cf4827 Mon Sep 17 00:00:00 2001 From: oyelowo Date: Mon, 9 Oct 2023 19:00:03 -0600 Subject: [PATCH 08/46] Support prefefined and arbitrary opacity --- tailwind/src/main.rs | 2 +- tw-macro/src/lib.rs | 66 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index 07d0ed6..f2ccac9 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -14,7 +14,7 @@ fn main() { "[mask-type:alpha] [ mask-type: alpha ] before:content-['rerer erer re rr r \re reFestivus'] after:content-['I am a content'] after:content-['I am a content'] after:content-['I am a content'] active:hover:text-[#bada55] active:hover:text-[ #bada55 ] text-[#bada55] hover:aria-checked:text-[22px] - text-[22.34e434cm] btn active:hover:collapse-arrow" + text-[22.34e434cm] btn bg-black/25 bg-black/[100] bg-black/[0.75] active:hover:collapse-arrow " ); println!("TEXT - {}", test); let _ = tw!("btn collapse-arrow"); diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 097a77a..9184cfc 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use nom::{ branch::alt, bytes::complete::{tag, take_until, take_while1}, @@ -549,8 +551,72 @@ fn arbitrary_content(input: &str) -> IResult<&str, ()> { Ok((input, ())) } +// bg-black/25 +fn predefined_colorful_opacity(input: &str) -> IResult<&str, ()> { + let input = if COLORFUL_BASECLASSES + .iter() + .any(|cb| input.trim().starts_with(cb)) + { + input + } else { + return Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))); + }; + let (input, _) = take_while1(|char| is_ident_char(char) && char != '/')(input)?; + // let (input, _) = take_until("/")(input)?; + let (input, _) = tag("/")(input)?; + + let (input, num) = number::complete::double(input)?; + let input = match num as u8 { + 0..=100 => input, + _ => { + return Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))) + } + }; + + Ok((input, ())) +} + +// bg-black/[27] +fn arbitrary_opacity(input: &str) -> IResult<&str, ()> { + let input = if COLORFUL_BASECLASSES + .iter() + .any(|cb| input.trim().starts_with(cb)) + { + input + } else { + return Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))); + }; + let (input, _) = take_while1(|char| is_ident_char(char) && char != '/')(input)?; + let (input, _) = tag("/")(input)?; + let (input, _) = tag("[")(input)?; + // 0-100 integer + let (input, num) = number::complete::double(input)?; + let input = match num as u8 { + 0..=100 => input, + _ => { + return Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))) + } + }; + let (input, _) = tag("]")(input)?; + Ok((input, ())) +} + fn parse_single_tw_classname(input: &str) -> IResult<&str, ()> { alt(( + predefined_colorful_opacity, + arbitrary_opacity, parse_predefined_tw_classname, kv_pair_classname, lengthy_arbitrary_classname, From e89ef6909f999445ceefe44f6f3755a580b501c1 Mon Sep 17 00:00:00 2001 From: oyelowo Date: Mon, 9 Oct 2023 19:05:19 -0600 Subject: [PATCH 09/46] Support url --- tailwind/src/main.rs | 5 ++++- tw-macro/src/lib.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index f2ccac9..12f1169 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -10,11 +10,14 @@ use tw_macro::tww; fn main() { let _ = tww!("btn btn"); + + // // aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg-[url('/img/up-arrow.svg')] + let test = tww!( "[mask-type:alpha] [ mask-type: alpha ] before:content-['rerer erer re rr r \re reFestivus'] after:content-['I am a content'] after:content-['I am a content'] after:content-['I am a content'] active:hover:text-[#bada55] active:hover:text-[ #bada55 ] text-[#bada55] hover:aria-checked:text-[22px] - text-[22.34e434cm] btn bg-black/25 bg-black/[100] bg-black/[0.75] active:hover:collapse-arrow " + text-[22.34e434cm] btn bg-[url('/img/down-arrow.svg')] bg-black/25 bg-black/[100] bg-black/[0.75] active:hover:collapse-arrow " ); println!("TEXT - {}", test); let _ = tw!("btn collapse-arrow"); diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 9184cfc..567d493 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -613,8 +613,34 @@ fn arbitrary_opacity(input: &str) -> IResult<&str, ()> { Ok((input, ())) } +// bg-[url('/img/down-arrow.svg')] +fn bg_arbitrary_url(input: &str) -> IResult<&str, ()> { + // prefixed by baseclass + let input = if COLORFUL_BASECLASSES + .iter() + .any(|cb| input.trim().starts_with(cb)) + { + input + } else { + return Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))); + }; + let (input, _) = take_while1(|char| is_ident_char(char) && char != '/')(input)?; + let (input, _) = tag("[")(input)?; + let (input, _) = multispace0(input)?; + let (input, _) = tag("url('")(input)?; + let (input, _) = take_until("')")(input)?; + let (input, _) = tag("')")(input)?; + let (input, _) = multispace0(input)?; + let (input, _) = tag("]")(input)?; + Ok((input, ())) +} + fn parse_single_tw_classname(input: &str) -> IResult<&str, ()> { alt(( + bg_arbitrary_url, predefined_colorful_opacity, arbitrary_opacity, parse_predefined_tw_classname, From af1f4f6bd8cedde5a6c224fb0ac3d3dea6b6822d Mon Sep 17 00:00:00 2001 From: oyelowo Date: Mon, 9 Oct 2023 19:11:49 -0600 Subject: [PATCH 10/46] Support arbitrary css value --- tailwind/src/main.rs | 5 ++++- tw-macro/src/lib.rs | 27 ++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index 12f1169..08050ca 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -17,7 +17,10 @@ fn main() { "[mask-type:alpha] [ mask-type: alpha ] before:content-['rerer erer re rr r \re reFestivus'] after:content-['I am a content'] after:content-['I am a content'] after:content-['I am a content'] active:hover:text-[#bada55] active:hover:text-[ #bada55 ] text-[#bada55] hover:aria-checked:text-[22px] - text-[22.34e434cm] btn bg-[url('/img/down-arrow.svg')] bg-black/25 bg-black/[100] bg-black/[0.75] active:hover:collapse-arrow " + text-[22.34e434cm] +grid grid-cols-[fit-content(theme(spacing.32))] + + btn bg-[url('/img/down-arrow.svg')] ring-white/10 bg-black/25 bg-black/[100] bg-black/[0.75] active:hover:collapse-arrow " ); println!("TEXT - {}", test); let _ = tw!("btn collapse-arrow"); diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 567d493..6afc15d 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -627,7 +627,7 @@ fn bg_arbitrary_url(input: &str) -> IResult<&str, ()> { nom::error::ErrorKind::Tag, ))); }; - let (input, _) = take_while1(|char| is_ident_char(char) && char != '/')(input)?; + let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; let (input, _) = tag("[")(input)?; let (input, _) = multispace0(input)?; let (input, _) = tag("url('")(input)?; @@ -638,6 +638,30 @@ fn bg_arbitrary_url(input: &str) -> IResult<&str, ()> { Ok((input, ())) } +// grid-cols-[fit-content(theme(spacing.32))] +fn arbitrary_css_value(input: &str) -> IResult<&str, ()> { + // is prefixed by valid base class + let input = if VALID_BASECLASS_NAMES + .iter() + .any(|cb| input.trim().starts_with(cb)) + { + input + } else { + return Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))); + }; + let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; + let (input, _) = tag("[")(input)?; + let (input, _) = multispace0(input)?; + // allow anything inthe brackets + let (input, _) = take_until("]")(input)?; + let (input, _) = multispace0(input)?; + let (input, _) = tag("]")(input)?; + Ok((input, ())) +} + fn parse_single_tw_classname(input: &str) -> IResult<&str, ()> { alt(( bg_arbitrary_url, @@ -648,6 +672,7 @@ fn parse_single_tw_classname(input: &str) -> IResult<&str, ()> { lengthy_arbitrary_classname, colorful_arbitrary_baseclass, arbitrary_content, + arbitrary_css_value, ))(input) } From 8a57273233f2f63e322e303ecc244cdc0a7c0dc1 Mon Sep 17 00:00:00 2001 From: oyelowo Date: Mon, 9 Oct 2023 19:19:38 -0600 Subject: [PATCH 11/46] Support arbitrary css variable --- tailwind/src/main.rs | 2 +- tw-macro/src/lib.rs | 38 +++++++++++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index 08050ca..346b8d7 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -19,7 +19,7 @@ after:content-['I am a content'] after:content-['I am a content'] after:content- active:hover:text-[#bada55] active:hover:text-[ #bada55 ] text-[#bada55] hover:aria-checked:text-[22px] text-[22.34e434cm] grid grid-cols-[fit-content(theme(spacing.32))] - +bg-[--my-color] btn bg-[url('/img/down-arrow.svg')] ring-white/10 bg-black/25 bg-black/[100] bg-black/[0.75] active:hover:collapse-arrow " ); println!("TEXT - {}", test); diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 6afc15d..800905e 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -4,7 +4,7 @@ use nom::{ branch::alt, bytes::complete::{tag, take_until, take_while1}, character::complete::{multispace0, multispace1, space0, space1}, - combinator::{all_consuming, opt, recognize}, + combinator::{all_consuming, not, opt, recognize}, multi::separated_list0, number, sequence::{delimited, tuple}, @@ -654,6 +654,7 @@ fn arbitrary_css_value(input: &str) -> IResult<&str, ()> { }; let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; let (input, _) = tag("[")(input)?; + let (input, _) = not(tag("--"))(input)?; let (input, _) = multispace0(input)?; // allow anything inthe brackets let (input, _) = take_until("]")(input)?; @@ -662,6 +663,39 @@ fn arbitrary_css_value(input: &str) -> IResult<&str, ()> { Ok((input, ())) } +// bg-[--my-color] +fn arbitrary_css_var(input: &str) -> IResult<&str, ()> { + // is prefixed by valid base class + let input = if VALID_BASECLASS_NAMES + .iter() + .any(|cb| input.trim().starts_with(cb)) + { + input + } else { + return Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))); + }; + let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; + let (input, _) = tag("[")(input)?; + let (input, _) = multispace0(input)?; + let (input, _) = tag("--")(input)?; + let (input, _) = take_while1(|char| is_ident_char(char) && char != ']')(input)?; + let (input, _) = tag("]")(input)?; + Ok((input, ())) +} + +// [mask-type:luminance] hover:[mask-type:alpha] +// [--scroll-offset:56px] lg:[--scroll-offset:44px] +// lg:[&:nth-child(3)]:hover:underline +// bg-[url('/what_a_rush.png')] +// before:content-['hello\_world'] +// text-[22px] +// text-[#bada55] +// text-[var(--my-var)] +// text-[length:var(--my-var)] +// text-[color:var(--my-var)] fn parse_single_tw_classname(input: &str) -> IResult<&str, ()> { alt(( bg_arbitrary_url, @@ -672,6 +706,8 @@ fn parse_single_tw_classname(input: &str) -> IResult<&str, ()> { lengthy_arbitrary_classname, colorful_arbitrary_baseclass, arbitrary_content, + // bg-[--my-color] + arbitrary_css_var, arbitrary_css_value, ))(input) } From 408c97dce87aeb41a840e3025e6f621d0cf06f14 Mon Sep 17 00:00:00 2001 From: oyelowo Date: Mon, 9 Oct 2023 19:27:12 -0600 Subject: [PATCH 12/46] Support arbitrary css variable 2 text-[var(--my-var)] --- tailwind/src/main.rs | 1 + tw-macro/src/lib.rs | 27 ++++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index 346b8d7..427f161 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -20,6 +20,7 @@ after:content-['I am a content'] after:content-['I am a content'] after:content- text-[22.34e434cm] grid grid-cols-[fit-content(theme(spacing.32))] bg-[--my-color] + text-[var(--my-var)] btn bg-[url('/img/down-arrow.svg')] ring-white/10 bg-black/25 bg-black/[100] bg-black/[0.75] active:hover:collapse-arrow " ); println!("TEXT - {}", test); diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 800905e..efefcd1 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -654,7 +654,8 @@ fn arbitrary_css_value(input: &str) -> IResult<&str, ()> { }; let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; let (input, _) = tag("[")(input)?; - let (input, _) = not(tag("--"))(input)?; + let (input, _) = not(alt((tag("--"), tag("var(--"))))(input)?; + let (input, _) = multispace0(input)?; // allow anything inthe brackets let (input, _) = take_until("]")(input)?; @@ -685,6 +686,28 @@ fn arbitrary_css_var(input: &str) -> IResult<&str, ()> { let (input, _) = tag("]")(input)?; Ok((input, ())) } +// text-[var(--my-var)] +fn arbitrary_css_var2(input: &str) -> IResult<&str, ()> { + // is prefixed by valid base class + let input = if VALID_BASECLASS_NAMES + .iter() + .any(|cb| input.trim().starts_with(cb)) + { + input + } else { + return Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))); + }; + let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; + let (input, _) = tag("[")(input)?; + let (input, _) = multispace0(input)?; + let (input, _) = tag("var(--")(input)?; + let (input, _) = take_while1(|char| is_ident_char(char) && char != ')')(input)?; + let (input, _) = tag(")]")(input)?; + Ok((input, ())) +} // [mask-type:luminance] hover:[mask-type:alpha] // [--scroll-offset:56px] lg:[--scroll-offset:44px] @@ -708,6 +731,8 @@ fn parse_single_tw_classname(input: &str) -> IResult<&str, ()> { arbitrary_content, // bg-[--my-color] arbitrary_css_var, + // text-[var(--my-var)] + arbitrary_css_var2, arbitrary_css_value, ))(input) } From e35cdc1a52a402e832e1fe485a3238c21e762577 Mon Sep 17 00:00:00 2001 From: oyelowo Date: Mon, 9 Oct 2023 19:34:32 -0600 Subject: [PATCH 13/46] Support css value variable ie text-[length:var(--my-var)] --- tailwind/src/main.rs | 1 + tw-macro/src/lib.rs | 39 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index 427f161..b8f06a7 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -21,6 +21,7 @@ after:content-['I am a content'] after:content-['I am a content'] after:content- grid grid-cols-[fit-content(theme(spacing.32))] bg-[--my-color] text-[var(--my-var)] + text-[length:var(--my-var)] btn bg-[url('/img/down-arrow.svg')] ring-white/10 bg-black/25 bg-black/[100] bg-black/[0.75] active:hover:collapse-arrow " ); println!("TEXT - {}", test); diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index efefcd1..3a6068e 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -654,7 +654,16 @@ fn arbitrary_css_value(input: &str) -> IResult<&str, ()> { }; let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; let (input, _) = tag("[")(input)?; - let (input, _) = not(alt((tag("--"), tag("var(--"))))(input)?; + let (input, _) = multispace0(input)?; + // ident, (, till ] + let (input, _) = take_while1(|char| is_ident_char(char) && char != '(')(input)?; + let (input, _) = not(alt(( + tag("--"), + tag("var(--"), + // :var(-- + )))(input)?; + let (input, _) = tag("(")(input)?; + let (input, _) = take_until(")]")(input)?; let (input, _) = multispace0(input)?; // allow anything inthe brackets @@ -709,6 +718,32 @@ fn arbitrary_css_var2(input: &str) -> IResult<&str, ()> { Ok((input, ())) } +// text-[length:var(--my-var)] +fn arbitrary_css_var3(input: &str) -> IResult<&str, ()> { + // is prefixed by valid base class + let input = if VALID_BASECLASS_NAMES + .iter() + .any(|cb| input.trim().starts_with(cb)) + { + input + } else { + return Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))); + }; + let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; + let (input, _) = tag("[")(input)?; + let (input, _) = multispace0(input)?; + let (input, _) = take_while1(|char| is_ident_char(char) && char != ':')(input)?; + let (input, _) = tag(":")(input)?; + let (input, _) = multispace0(input)?; + let (input, _) = tag("var(--")(input)?; + let (input, _) = take_while1(|char| is_ident_char(char) && char != ')')(input)?; + let (input, _) = tag(")]")(input)?; + Ok((input, ())) +} + // [mask-type:luminance] hover:[mask-type:alpha] // [--scroll-offset:56px] lg:[--scroll-offset:44px] // lg:[&:nth-child(3)]:hover:underline @@ -733,6 +768,8 @@ fn parse_single_tw_classname(input: &str) -> IResult<&str, ()> { arbitrary_css_var, // text-[var(--my-var)] arbitrary_css_var2, + // text-[length:var(--my-var)] + arbitrary_css_var3, arbitrary_css_value, ))(input) } From b0a4214c5a630964f8dbd6f29cf4449ae4b5adfa Mon Sep 17 00:00:00 2001 From: oyelowo Date: Mon, 9 Oct 2023 19:48:18 -0600 Subject: [PATCH 14/46] Refactor css value parser --- tailwind/src/main.rs | 7 +++++-- tw-macro/src/lib.rs | 14 +++++++------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index b8f06a7..16478ee 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -14,15 +14,18 @@ fn main() { // // aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg-[url('/img/up-arrow.svg')] let test = tww!( - "[mask-type:alpha] [ mask-type: alpha ] before:content-['rerer erer re rr r \re reFestivus'] + r#"[mask-type:alpha] [ mask-type: alpha ] before:content-['rerer erer re rr r \re reFestivus'] after:content-['I am a content'] after:content-['I am a content'] after:content-['I am a content'] active:hover:text-[#bada55] active:hover:text-[ #bada55 ] text-[#bada55] hover:aria-checked:text-[22px] text-[22.34e434cm] + before:content-['hello\_world'] grid grid-cols-[fit-content(theme(spacing.32))] bg-[--my-color] text-[var(--my-var)] text-[length:var(--my-var)] - btn bg-[url('/img/down-arrow.svg')] ring-white/10 bg-black/25 bg-black/[100] bg-black/[0.75] active:hover:collapse-arrow " + text-[color:var(--my-var)] + [--scroll-offset:56px] lg:[--scroll-offset:44px] + btn bg-[url('/img/down-arrow.svg')] ring-white/10 bg-black/25 bg-black/[100] bg-black/[0.75] active:hover:collapse-arrow "# ); println!("TEXT - {}", test); let _ = tw!("btn collapse-arrow"); diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 3a6068e..93a9711 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -641,27 +641,27 @@ fn bg_arbitrary_url(input: &str) -> IResult<&str, ()> { // grid-cols-[fit-content(theme(spacing.32))] fn arbitrary_css_value(input: &str) -> IResult<&str, ()> { // is prefixed by valid base class + // take until -[ + let (input, base_class) = take_until("-[")(input)?; let input = if VALID_BASECLASS_NAMES .iter() - .any(|cb| input.trim().starts_with(cb)) + .any(|cb| base_class.trim().eq(*cb)) { input } else { return Err(nom::Err::Error(nom::error::Error::new( - input, + base_class, nom::error::ErrorKind::Tag, ))); }; - let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; - let (input, _) = tag("[")(input)?; - let (input, _) = multispace0(input)?; - // ident, (, till ] - let (input, _) = take_while1(|char| is_ident_char(char) && char != '(')(input)?; + let (input, _) = tag("-[")(input)?; let (input, _) = not(alt(( tag("--"), tag("var(--"), // :var(-- )))(input)?; + let (input, _) = multispace0(input)?; + let (input, _) = take_while1(|char| is_ident_char(char) && char != '(')(input)?; let (input, _) = tag("(")(input)?; let (input, _) = take_until(")]")(input)?; From 1d31915b906636dd6e04daa4bd695172e612bcb0 Mon Sep 17 00:00:00 2001 From: oyelowo Date: Mon, 9 Oct 2023 20:06:57 -0600 Subject: [PATCH 15/46] Support arbitrary modiifer front and back selector --- tailwind/src/main.rs | 11 +++++-- tw-macro/src/lib.rs | 68 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 73 insertions(+), 6 deletions(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index 16478ee..3dfaead 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -14,7 +14,7 @@ fn main() { // // aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg-[url('/img/up-arrow.svg')] let test = tww!( - r#"[mask-type:alpha] [ mask-type: alpha ] before:content-['rerer erer re rr r \re reFestivus'] + r#"[mask-type:alpha] [ mask-type: alpha ] before:content-['rerer erer re rr r \re reFestivus'] after:content-['I am a content'] after:content-['I am a content'] after:content-['I am a content'] active:hover:text-[#bada55] active:hover:text-[ #bada55 ] text-[#bada55] hover:aria-checked:text-[22px] text-[22.34e434cm] @@ -25,7 +25,14 @@ bg-[--my-color] text-[length:var(--my-var)] text-[color:var(--my-var)] [--scroll-offset:56px] lg:[--scroll-offset:44px] - btn bg-[url('/img/down-arrow.svg')] ring-white/10 bg-black/25 bg-black/[100] bg-black/[0.75] active:hover:collapse-arrow "# + btn bg-[url('/img/down-arrow.svg')] ring-white/10 bg-black/25 bg-black/[100] bg-black/[0.75] active:hover:collapse-arrow + + + lg:[&:nth-child(3)]:hover:underline + group-[:nth-of-type(3)_&]:block + [&_p]:mt-4 + + "# ); println!("TEXT - {}", test); let _ = tw!("btn collapse-arrow"); diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 93a9711..2324750 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -782,7 +782,8 @@ fn modifier_separator(input: &str) -> IResult<&str, &str> { Ok((input, "")) } -fn modifier(input: &str) -> IResult<&str, &str> { +// hover:underline +fn predefined_modifier(input: &str) -> IResult<&str, ()> { let (input, modifier) = recognize(|i| { // Assuming a Tailwind class consists of alphanumeric, dashes, and colons nom::bytes::complete::is_a( @@ -791,7 +792,7 @@ fn modifier(input: &str) -> IResult<&str, &str> { })(input)?; if is_valid_modifier2(modifier) { - Ok((input, modifier)) + Ok((input, ())) } else { Err(nom::Err::Error(nom::error::Error::new( input, @@ -800,9 +801,68 @@ fn modifier(input: &str) -> IResult<&str, &str> { } } -fn modifiers_chained(input: &str) -> IResult<&str, Vec<&str>> { +// [&:nth-child(3)]:underline +// [&_p]:mt-4 +fn arbitrary_front_selector_modifier(input: &str) -> IResult<&str, ()> { + let (input, _) = tag("[&")(input)?; + let (input, _) = take_until("]")(input)?; + let (input, _) = tag("]")(input)?; + Ok((input, ())) +} + +// group-[:nth-of-type(3)_&]:block +fn arbitrary_back_selector_modifier(input: &str) -> IResult<&str, ()> { + let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; + let (input, _) = tag("[")(input)?; + let (input, _) = take_until("&]")(input)?; + let (input, _) = tag("&]")(input)?; + Ok((input, ())) +} + +// +// flex [@supports(display:grid)]:grid +// [@media(any-hover:hover){&:hover}]:opacity-100 +// group/edit invisible hover:bg-slate-200 group-hover/item:visible +// hidden group-[.is-published]:block +// group-[:nth-of-type(3)_&]:block +// peer-checked/published:text-sky-500 +// peer-[.is-dirty]:peer-required:block hidden +// hidden peer-[:nth-of-type(3)_&]:block +// after:content-['*'] after:ml-0.5 after:text-red-500 block text-sm font-medium text-slate-700 +// before:content-[''] before:block +// bg-black/75 supports-[backdrop-filter]:bg-black/25 supports-[backdrop-filter]:backdrop-blur +// aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg-[url('/img/up-arrow.svg')] +// group-aria-[sort=ascending]:rotate-0 group-aria-[sort=descending]:rotate-180 +// data-[size=large]:p-8 +// open:bg-white dark:open:bg-slate-900 open:ring-1 open:ring-black/5 dark:open:ring-white/10 open:shadow-lg p-6 rounded-lg +// lg:[&:nth-child(3)]:hover:underline +// min-[320px]:text-center max-[600px]:bg-sky-300 +// top-[117px] lg:top-[344px] +// bg-[#bada55] text-[22px] before:content-['Festivus'] +// grid grid-cols-[fit-content(theme(spacing.32))] +// bg-[--my-color] +// [mask-type:luminance] hover:[mask-type:alpha] +// [--scroll-offset:56px] lg:[--scroll-offset:44px] +// lg:[&:nth-child(3)]:hover:underline +// bg-[url('/what_a_rush.png')] +// before:content-['hello\_world'] +// text-[22px] +// text-[#bada55] +// text-[var(--my-var)] +// text-[length:var(--my-var)] +// text-[color:var(--my-var)] + +fn modifier(input: &str) -> IResult<&str, ()> { + alt(( + arbitrary_front_selector_modifier, + arbitrary_back_selector_modifier, + predefined_modifier, + ))(input) +} + +fn modifiers_chained(input: &str) -> IResult<&str, ()> { let (input, modifiers) = separated_list0(tag(":"), modifier)(input)?; - Ok((input, modifiers)) + Ok((input, ())) } fn parse_tw_full_classname(input: &str) -> IResult<&str, Vec<&str>> { From 219a918276cd65fddc069033deca7aec6e818d85 Mon Sep 17 00:00:00 2001 From: oyelowo Date: Mon, 9 Oct 2023 20:09:17 -0600 Subject: [PATCH 16/46] Support aribitrary @supports modifier e.g [@supports(display:grid)]:grid --- tailwind/src/main.rs | 1 + tw-macro/src/lib.rs | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index 3dfaead..0592397 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -32,6 +32,7 @@ bg-[--my-color] group-[:nth-of-type(3)_&]:block [&_p]:mt-4 + flex [@supports(display:grid)]:grid "# ); println!("TEXT - {}", test); diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 2324750..2466e14 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -819,8 +819,15 @@ fn arbitrary_back_selector_modifier(input: &str) -> IResult<&str, ()> { Ok((input, ())) } +// [@supports(display:grid)]:grid +fn arbitrary_at_supports_rule_modifier(input: &str) -> IResult<&str, ()> { + let (input, _) = tag("[@supports(")(input)?; + let (input, _) = take_until(")")(input)?; + let (input, _) = tag(")]")(input)?; + Ok((input, ())) +} + // -// flex [@supports(display:grid)]:grid // [@media(any-hover:hover){&:hover}]:opacity-100 // group/edit invisible hover:bg-slate-200 group-hover/item:visible // hidden group-[.is-published]:block @@ -856,6 +863,7 @@ fn modifier(input: &str) -> IResult<&str, ()> { alt(( arbitrary_front_selector_modifier, arbitrary_back_selector_modifier, + arbitrary_at_supports_rule_modifier, predefined_modifier, ))(input) } From 439d591a0725992d083ff11f49c5efb6cce906cc Mon Sep 17 00:00:00 2001 From: oyelowo Date: Mon, 9 Oct 2023 20:20:44 -0600 Subject: [PATCH 17/46] Supports arbitrary @media modifier --- tailwind/src/main.rs | 4 ++++ tw-macro/src/lib.rs | 13 +++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index 0592397..0673700 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -33,6 +33,10 @@ bg-[--my-color] [&_p]:mt-4 flex [@supports(display:grid)]:grid + flex active:hover:[@supports(display:grid)]:grid + + [@media(any-hover:hover){&:hover}]:opacity-100 + "# ); println!("TEXT - {}", test); diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 2466e14..812b3d6 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use nom::{ branch::alt, - bytes::complete::{tag, take_until, take_while1}, + bytes::complete::{tag, take_till, take_until, take_while1}, character::complete::{multispace0, multispace1, space0, space1}, combinator::{all_consuming, not, opt, recognize}, multi::separated_list0, @@ -827,8 +827,16 @@ fn arbitrary_at_supports_rule_modifier(input: &str) -> IResult<&str, ()> { Ok((input, ())) } -// // [@media(any-hover:hover){&:hover}]:opacity-100 +fn arbitrary_at_media_rule_modifier(input: &str) -> IResult<&str, ()> { + // starts with [@media and ends with ] + let (input, _) = tag("[@media(")(input)?; + let (input, _) = take_until("]")(input)?; + let (input, _) = tag("]")(input)?; + Ok((input, ())) +} + +// // group/edit invisible hover:bg-slate-200 group-hover/item:visible // hidden group-[.is-published]:block // group-[:nth-of-type(3)_&]:block @@ -864,6 +872,7 @@ fn modifier(input: &str) -> IResult<&str, ()> { arbitrary_front_selector_modifier, arbitrary_back_selector_modifier, arbitrary_at_supports_rule_modifier, + arbitrary_at_media_rule_modifier, predefined_modifier, ))(input) } From 610dddcf167b4a7b259155e09b074b38c6c83c16 Mon Sep 17 00:00:00 2001 From: oyelowo Date: Tue, 10 Oct 2023 08:00:03 -0600 Subject: [PATCH 18/46] Support group and peer modifier --- tailwind/src/main.rs | 5 +++++ tw-macro/src/lib.rs | 40 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index 0673700..f59f20f 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -37,6 +37,11 @@ bg-[--my-color] [@media(any-hover:hover){&:hover}]:opacity-100 + group/edit invisible hover:bg-slate-200 group-hover/item:visible + + peer-checked/published:text-sky-500 + + "# ); println!("TEXT - {}", test); diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 812b3d6..9032676 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -744,6 +744,18 @@ fn arbitrary_css_var3(input: &str) -> IResult<&str, ()> { Ok((input, ())) } +// group/edit +fn arbitrary_group_classname(input: &str) -> IResult<&str, ()> { + let (input, _) = alt(( + // tuple((tag("group-"), predefined_modifier)), + // tuple((tag("group"), |_| Ok(("", ())))), + tag("group"), + ))(input)?; + let (input, _) = tag("/")(input)?; + let (input, _) = take_while1(|char| is_ident_char(char))(input)?; + Ok((input, ())) +} + // [mask-type:luminance] hover:[mask-type:alpha] // [--scroll-offset:56px] lg:[--scroll-offset:44px] // lg:[&:nth-child(3)]:hover:underline @@ -756,13 +768,23 @@ fn arbitrary_css_var3(input: &str) -> IResult<&str, ()> { // text-[color:var(--my-var)] fn parse_single_tw_classname(input: &str) -> IResult<&str, ()> { alt(( + // bg-[url('/what_a_rush.png')] bg_arbitrary_url, + // bg-black/25 predefined_colorful_opacity, + // group/edit + arbitrary_group_classname, + // bg-black/[27] arbitrary_opacity, + // btn parse_predefined_tw_classname, + // [mask-type:luminance] [mask-type:alpha] kv_pair_classname, + // text-[22px] lengthy_arbitrary_classname, + // text-[#bada55] colorful_arbitrary_baseclass, + // before:content-['Festivus'] arbitrary_content, // bg-[--my-color] arbitrary_css_var, @@ -770,6 +792,7 @@ fn parse_single_tw_classname(input: &str) -> IResult<&str, ()> { arbitrary_css_var2, // text-[length:var(--my-var)] arbitrary_css_var3, + // grid-cols-[fit-content(theme(spacing.32))] arbitrary_css_value, ))(input) } @@ -836,11 +859,23 @@ fn arbitrary_at_media_rule_modifier(input: &str) -> IResult<&str, ()> { Ok((input, ())) } -// // group/edit invisible hover:bg-slate-200 group-hover/item:visible +fn group_peer_modifier(input: &str) -> IResult<&str, ()> { + let (input, _) = alt(( + tuple((tag("group-"), predefined_modifier)), + // https://tailwindcss.com/docs/hover-focus-and-other-states#differentiating-peers + // peer-checked/published:text-sky-500 + tuple((tag("peer-"), predefined_modifier)), + // tuple((tag("group"), |_| Ok(("", ())))), + ))(input)?; + let (input, _) = tag("/")(input)?; + let (input, _) = take_while1(|char| is_ident_char(char) && char != ':')(input)?; + Ok((input, ())) +} + +// // hidden group-[.is-published]:block // group-[:nth-of-type(3)_&]:block -// peer-checked/published:text-sky-500 // peer-[.is-dirty]:peer-required:block hidden // hidden peer-[:nth-of-type(3)_&]:block // after:content-['*'] after:ml-0.5 after:text-red-500 block text-sm font-medium text-slate-700 @@ -869,6 +904,7 @@ fn arbitrary_at_media_rule_modifier(input: &str) -> IResult<&str, ()> { fn modifier(input: &str) -> IResult<&str, ()> { alt(( + group_peer_modifier, arbitrary_front_selector_modifier, arbitrary_back_selector_modifier, arbitrary_at_supports_rule_modifier, From e1581ba23202e0b59da61d5ed70f53a61923985e Mon Sep 17 00:00:00 2001 From: oyelowo Date: Tue, 10 Oct 2023 10:53:26 -0600 Subject: [PATCH 19/46] Support group and peer modifier and selector --- tailwind/src/main.rs | 69 +++++++++++++++++++++++++------------------- tw-macro/src/lib.rs | 64 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 100 insertions(+), 33 deletions(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index f59f20f..778a020 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -9,44 +9,53 @@ use tw_macro::tw; use tw_macro::tww; fn main() { - let _ = tww!("btn btn"); + // let _ = tww!("btn btn"); // // aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg-[url('/img/up-arrow.svg')] let test = tww!( r#"[mask-type:alpha] [ mask-type: alpha ] before:content-['rerer erer re rr r \re reFestivus'] -after:content-['I am a content'] after:content-['I am a content'] after:content-['I am a content'] - active:hover:text-[#bada55] active:hover:text-[ #bada55 ] text-[#bada55] hover:aria-checked:text-[22px] - text-[22.34e434cm] - before:content-['hello\_world'] -grid grid-cols-[fit-content(theme(spacing.32))] -bg-[--my-color] - text-[var(--my-var)] - text-[length:var(--my-var)] - text-[color:var(--my-var)] - [--scroll-offset:56px] lg:[--scroll-offset:44px] - btn bg-[url('/img/down-arrow.svg')] ring-white/10 bg-black/25 bg-black/[100] bg-black/[0.75] active:hover:collapse-arrow - - - lg:[&:nth-child(3)]:hover:underline - group-[:nth-of-type(3)_&]:block - [&_p]:mt-4 - - flex [@supports(display:grid)]:grid - flex active:hover:[@supports(display:grid)]:grid - - [@media(any-hover:hover){&:hover}]:opacity-100 - - group/edit invisible hover:bg-slate-200 group-hover/item:visible - - peer-checked/published:text-sky-500 - - - "# + after:content-['I am a content'] after:content-['I am a content'] after:content-['I am a content'] + active:hover:text-[#bada55] active:hover:text-[ #bada55 ] text-[#bada55] hover:aria-checked:text-[22px] + text-[22.34e434cm] + before:content-['hello\_world'] + grid grid-cols-[fit-content(theme(spacing.32))] + bg-[--my-color] + text-[var(--my-var)] + text-[length:var(--my-var)] + text-[color:var(--my-var)] + [--scroll-offset:56px] lg:[--scroll-offset:44px] + btn bg-[url('/img/down-arrow.svg')] ring-white/10 bg-black/25 bg-black/[100] bg-black/[0.75] active:hover:collapse-arrow + + + lg:[&:nth-child(3)]:hover:underline + group-[:nth-of-type(3)_&]:block + [&_p]:mt-4 + + flex [@supports(display:grid)]:grid + flex active:hover:[@supports(display:grid)]:grid + + [@media(any-hover:hover){&:hover}]:opacity-100 + + hidden group-[.is-published]:block + group-[:nth-of-type(3)_&]:block + peer-[.is-dirty]:peer-required:block hidden + hidden peer-[:nth-of-type(3)_&]:block + + group/edit invisible hover:bg-slate-200 group-hover/item:visible + + peer-checked/published:text-sky-500 + + + + + "# ); + // let test = + // tww!("peer[.is-dirty]:peer-required:block hidden hidden peer-[:nth-of-type(3)_&]:block"); println!("TEXT - {}", test); let _ = tw!("btn collapse-arrow"); - tww!("bg-gray-600 bg-sky-700 bg-midnight underline"); + // tww!("bg-gray-600 bg-sky-700 bg-midnight underline"); // tww!("bg-gray-600 aria-checked:bg-sky-700 aria-asc:bg-midnight data-checked:underline"); let _classnames = tw!("bg-taxvhiti bg-tahiti-500 bg-tahiti bg-midnight bg-red-50"); let _classnames = tw!("bg-taxvhiti bg-tahiti-500 bg-tahiti bg-midnight bg-purple bg-red-50 bg-tahiti-800 border-s-tahiti-800"); diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 9032676..92a0960 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -416,6 +416,7 @@ fn parse_predefined_tw_classname(input: &str) -> IResult<&str, ()> { if is_valid_classname2(class_name) { // Ok((input, class_name)) + eprintln!("parse_predefined_tw_classname: {}", input); Ok((input, ())) } else { Err(nom::Err::Error(nom::error::Error::new( @@ -474,6 +475,7 @@ fn lengthy_arbitrary_classname(input: &str) -> IResult<&str, ()> { }(input)?; let (input, _) = multispace0(input)?; let (input, _) = tag("]")(input)?; + eprintln!("lengthy_arbitrary_classname: {}", input); Ok((input, ())) } @@ -517,6 +519,7 @@ fn colorful_arbitrary_baseclass(input: &str) -> IResult<&str, ()> { }?; let (input, _) = tag("]")(input)?; + eprintln!("colorful_arbitrary_baseclass: {}", input); Ok((input, ())) } // e.g: [mask-type:alpha] @@ -540,6 +543,7 @@ fn kv_pair_classname(input: &str) -> IResult<&str, ()> { let (input, _) = take_while1(is_ident_char)(input)?; let (input, _) = multispace0(input)?; let (input, _) = tag("]")(input)?; + eprintln!("kv_pair_classname: {}", input); Ok((input, ())) } @@ -548,6 +552,7 @@ fn arbitrary_content(input: &str) -> IResult<&str, ()> { let (input, _) = tag("content-['")(input)?; let (input, _) = take_until("']")(input)?; let (input, _) = tag("']")(input)?; + eprintln!("arbitrary_content: {}", input); Ok((input, ())) } @@ -578,6 +583,7 @@ fn predefined_colorful_opacity(input: &str) -> IResult<&str, ()> { ))) } }; + eprintln!("predefined_colorful_opacity: {}", input); Ok((input, ())) } @@ -610,6 +616,7 @@ fn arbitrary_opacity(input: &str) -> IResult<&str, ()> { } }; let (input, _) = tag("]")(input)?; + eprintln!("arbitrary_opacity: {}", input); Ok((input, ())) } @@ -635,6 +642,7 @@ fn bg_arbitrary_url(input: &str) -> IResult<&str, ()> { let (input, _) = tag("')")(input)?; let (input, _) = multispace0(input)?; let (input, _) = tag("]")(input)?; + eprintln!("bg_arbitrary_url: {}", input); Ok((input, ())) } @@ -670,6 +678,7 @@ fn arbitrary_css_value(input: &str) -> IResult<&str, ()> { let (input, _) = take_until("]")(input)?; let (input, _) = multispace0(input)?; let (input, _) = tag("]")(input)?; + eprintln!("arbitrary_css_value: {}", input); Ok((input, ())) } @@ -693,6 +702,7 @@ fn arbitrary_css_var(input: &str) -> IResult<&str, ()> { let (input, _) = tag("--")(input)?; let (input, _) = take_while1(|char| is_ident_char(char) && char != ']')(input)?; let (input, _) = tag("]")(input)?; + eprintln!("arbitrary_css_var: {}", input); Ok((input, ())) } // text-[var(--my-var)] @@ -715,6 +725,7 @@ fn arbitrary_css_var2(input: &str) -> IResult<&str, ()> { let (input, _) = tag("var(--")(input)?; let (input, _) = take_while1(|char| is_ident_char(char) && char != ')')(input)?; let (input, _) = tag(")]")(input)?; + eprintln!("arbitrary_css_var2: {}", input); Ok((input, ())) } @@ -741,6 +752,7 @@ fn arbitrary_css_var3(input: &str) -> IResult<&str, ()> { let (input, _) = tag("var(--")(input)?; let (input, _) = take_while1(|char| is_ident_char(char) && char != ')')(input)?; let (input, _) = tag(")]")(input)?; + eprintln!("arbitrary_css_var3: {}", input); Ok((input, ())) } @@ -753,6 +765,7 @@ fn arbitrary_group_classname(input: &str) -> IResult<&str, ()> { ))(input)?; let (input, _) = tag("/")(input)?; let (input, _) = take_while1(|char| is_ident_char(char))(input)?; + eprintln!("arbitrary_group_classname: {}", input); Ok((input, ())) } @@ -815,6 +828,7 @@ fn predefined_modifier(input: &str) -> IResult<&str, ()> { })(input)?; if is_valid_modifier2(modifier) { + eprintln!("predefined_modifier: {}", input); Ok((input, ())) } else { Err(nom::Err::Error(nom::error::Error::new( @@ -824,21 +838,34 @@ fn predefined_modifier(input: &str) -> IResult<&str, ()> { } } +// predefined special modifiers e.g peer-checked:p-4 group-hover:visible +fn predefined_special_modifier(input: &str) -> IResult<&str, ()> { + let (input, _) = alt(( + // peer-checked:p-4 + tuple((tag("peer-"), predefined_modifier)), + // group-hover:visible + tuple((tag("group-"), predefined_modifier)), + ))(input)?; + Ok((input, ())) +} + // [&:nth-child(3)]:underline // [&_p]:mt-4 fn arbitrary_front_selector_modifier(input: &str) -> IResult<&str, ()> { let (input, _) = tag("[&")(input)?; let (input, _) = take_until("]")(input)?; let (input, _) = tag("]")(input)?; + eprintln!("arbitrary_front_selector_modifier: {}", input); Ok((input, ())) } // group-[:nth-of-type(3)_&]:block fn arbitrary_back_selector_modifier(input: &str) -> IResult<&str, ()> { let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; - let (input, _) = tag("[")(input)?; + let (input, _) = tag("-[")(input)?; let (input, _) = take_until("&]")(input)?; let (input, _) = tag("&]")(input)?; + eprintln!("arbitrary_back_selector_modifier: {}", input); Ok((input, ())) } @@ -847,6 +874,7 @@ fn arbitrary_at_supports_rule_modifier(input: &str) -> IResult<&str, ()> { let (input, _) = tag("[@supports(")(input)?; let (input, _) = take_until(")")(input)?; let (input, _) = tag(")]")(input)?; + eprintln!("arbitrary_at_supports_rule_modifier: {}", input); Ok((input, ())) } @@ -856,6 +884,7 @@ fn arbitrary_at_media_rule_modifier(input: &str) -> IResult<&str, ()> { let (input, _) = tag("[@media(")(input)?; let (input, _) = take_until("]")(input)?; let (input, _) = tag("]")(input)?; + eprintln!("arbitrary_at_media_rule_modifier: {}", input); Ok((input, ())) } @@ -869,15 +898,25 @@ fn group_peer_modifier(input: &str) -> IResult<&str, ()> { // tuple((tag("group"), |_| Ok(("", ())))), ))(input)?; let (input, _) = tag("/")(input)?; - let (input, _) = take_while1(|char| is_ident_char(char) && char != ':')(input)?; + let (input, _) = take_while1(is_ident_char)(input)?; + eprintln!("group_peer_modifier: {}", input); Ok((input, ())) } -// // hidden group-[.is-published]:block // group-[:nth-of-type(3)_&]:block // peer-[.is-dirty]:peer-required:block hidden // hidden peer-[:nth-of-type(3)_&]:block +fn group_modifier_selector(input: &str) -> IResult<&str, ()> { + let (input, _) = alt((tag("group"), tag("peer")))(input)?; + let (input, _) = tag("-[")(input)?; + let (input, _) = take_until("]")(input)?; + let (input, _) = tag("]")(input)?; + eprintln!("group_modifier_selector: {}", input); + Ok((input, ())) +} + +// // after:content-['*'] after:ml-0.5 after:text-red-500 block text-sm font-medium text-slate-700 // before:content-[''] before:block // bg-black/75 supports-[backdrop-filter]:bg-black/25 supports-[backdrop-filter]:backdrop-blur @@ -902,9 +941,28 @@ fn group_peer_modifier(input: &str) -> IResult<&str, ()> { // text-[length:var(--my-var)] // text-[color:var(--my-var)] +fn all_consuming_segment<'a, F, O>(parser: F) -> impl Fn(&'a str) -> IResult<&'a str, O> +where + F: Fn(&'a str) -> IResult<&'a str, O>, +{ + move |input: &str| { + let (input, output) = parser(input)?; + if input.is_empty() { + Ok((input, output)) + } else { + Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))) + } + } +} + fn modifier(input: &str) -> IResult<&str, ()> { alt(( + group_modifier_selector, group_peer_modifier, + predefined_special_modifier, arbitrary_front_selector_modifier, arbitrary_back_selector_modifier, arbitrary_at_supports_rule_modifier, From 3c35d65293b64ba8eeed70fd22d6ade0e1053bfe Mon Sep 17 00:00:00 2001 From: oyelowo Date: Tue, 10 Oct 2023 11:54:15 -0600 Subject: [PATCH 20/46] Support arbitrary rgb and rbga colors --- tailwind/src/main.rs | 2 +- tw-macro/src/lib.rs | 98 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 82 insertions(+), 18 deletions(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index 778a020..27ac78d 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -16,7 +16,7 @@ fn main() { let test = tww!( r#"[mask-type:alpha] [ mask-type: alpha ] before:content-['rerer erer re rr r \re reFestivus'] after:content-['I am a content'] after:content-['I am a content'] after:content-['I am a content'] - active:hover:text-[#bada55] active:hover:text-[ #bada55 ] text-[#bada55] hover:aria-checked:text-[22px] + active:hover:text-[#bada55] active:hover:text-[ #ba5 ] text-[#bada55] hover:aria-checked:text-[22px] text-[22.34e434cm] before:content-['hello\_world'] grid grid-cols-[fit-content(theme(spacing.32))] diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 92a0960..f6b5bfc 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -479,9 +479,84 @@ fn lengthy_arbitrary_classname(input: &str) -> IResult<&str, ()> { Ok((input, ())) } -fn is_hex_color(color: &str) -> bool { - let re = regex::Regex::new(r"^#[0-9a-fA-F]{3,6}$").expect("Invalid regex"); - re.is_match(color) +// fn is_hex_color(color: &str) -> bool { +// let re = regex::Regex::new(r"^#[0-9a-fA-F]{3,6}$").expect("Invalid regex"); +// re.is_match(color) +// } + +// #bada55 +fn parse_hex_color(input: &str) -> IResult<&str, String> { + let (input, _) = tag("#")(input)?; + let (input, color) = take_while1(|c: char| c.is_ascii_hexdigit())(input)?; + let ((input, _)) = if color.chars().count() == 3 || color.chars().count() == 6 { + Ok((input, ())) + } else { + Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))) + }?; + eprintln!("parse_hex_color: {}", input); + let color = format!("#{}", color); + Ok((input, color)) +} + +fn parse_u8(input: &str) -> IResult<&str, u8> { + let (input, num) = number::complete::double(input)?; + let input = match num as u32 { + 0..=255 => input, + _ => { + return Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))) + } + }; + Ok((input, num as u8)) +} + +// rgb(255, 255, 255) +fn parse_rgb_color(input: &str) -> IResult<&str, String> { + let (input, _) = tag("rgb(")(input)?; + let (input, _) = multispace0(input)?; + let (input, r) = parse_u8(input)?; + let (input, _) = multispace0(input)?; + let (input, _) = tag(",")(input)?; + let (input, _) = multispace0(input)?; + let (input, g) = parse_u8(input)?; + let (input, _) = multispace0(input)?; + let (input, _) = tag(",")(input)?; + let (input, _) = multispace0(input)?; + let (input, b) = parse_u8(input)?; + let (input, _) = multispace0(input)?; + let (input, _) = tag(")")(input)?; + eprintln!("parse_rgb_color: {}", input); + let color = format!("rgb({}, {}, {})", r, g, b); + Ok((input, color)) +} + +// rgba(255, 255, 255, 0.5) +fn parse_rgba_color(input: &str) -> IResult<&str, String> { + let (input, _) = tag("rgba(")(input)?; + let (input, _) = multispace0(input)?; + let (input, r) = parse_u8(input)?; + let (input, _) = multispace0(input)?; + let (input, _) = tag(",")(input)?; + let (input, _) = multispace0(input)?; + let (input, g) = parse_u8(input)?; + let (input, _) = multispace0(input)?; + let (input, _) = tag(",")(input)?; + let (input, _) = multispace0(input)?; + let (input, b) = parse_u8(input)?; + let (input, _) = multispace0(input)?; + let (input, _) = tag(",")(input)?; + let (input, _) = multispace0(input)?; + let (input, a) = number::complete::double(input)?; + let (input, _) = multispace0(input)?; + let (input, _) = tag(")")(input)?; + eprintln!("parse_rgba_color: {}", input); + let color = format!("rgba({}, {}, {}, {})", r, g, b, a); + Ok((input, color)) } fn is_colorful_baseclass(class_name: &str) -> bool { @@ -504,24 +579,13 @@ fn colorful_arbitrary_baseclass(input: &str) -> IResult<&str, ()> { let (input, _) = tag("-")(input)?; let (input, _) = tag("[")(input)?; let (input, _) = multispace0(input)?; - // is hex color - // let (input, _) = tag("#")(input)?; - // let (input, color) = take_while1(|c: char| c.is_ascii_hexdigit())(input)?; - // should be length 3 or 6 - let (input, color) = take_until("]")(input)?; - let ((input, _)) = if is_hex_color(color.trim()) { - Ok((input, ())) - } else { - Err(nom::Err::Error(nom::error::Error::new( - input, - nom::error::ErrorKind::Tag, - ))) - }?; - + let (input, _) = alt((parse_hex_color, parse_rgb_color, parse_rgba_color))(input)?; + let (input, _) = multispace0(input)?; let (input, _) = tag("]")(input)?; eprintln!("colorful_arbitrary_baseclass: {}", input); Ok((input, ())) } + // e.g: [mask-type:alpha] fn kv_pair_classname(input: &str) -> IResult<&str, ()> { // let Ok((input, _)) = delimited( From 5c2053ee7222ac0fef70f078708d51077aa18e48 Mon Sep 17 00:00:00 2001 From: oyelowo Date: Tue, 10 Oct 2023 12:18:58 -0600 Subject: [PATCH 21/46] Support supports arbitrary --- tailwind/src/main.rs | 6 ++++++ tw-macro/src/lib.rs | 16 +++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index 27ac78d..681f386 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -45,7 +45,13 @@ fn main() { group/edit invisible hover:bg-slate-200 group-hover/item:visible peer-checked/published:text-sky-500 + +after:content-['*'] after:ml-0.5 after:text-red-500 block text-sm font-medium text-slate-700 + before:content-[''] before:block + + + bg-black/75 supports-[backdrop-filter]:bg-black/25 supports-[backdrop-filter]:backdrop-blur diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index f6b5bfc..b9d903e 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -410,7 +410,7 @@ fn parse_predefined_tw_classname(input: &str) -> IResult<&str, ()> { let (input, class_name) = recognize(|i| { // Assuming a Tailwind class consists of alphanumeric, dashes, and colons nom::bytes::complete::is_a( - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-:", + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-.", )(i) })(input)?; @@ -980,9 +980,18 @@ fn group_modifier_selector(input: &str) -> IResult<&str, ()> { Ok((input, ())) } +// supports-[backdrop-filter] +fn supports_arbitrary(input: &str) -> IResult<&str, ()> { + let (input, _) = tag("supports-[")(input)?; + let (input, _) = take_until("]")(input)?; + let (input, _) = tag("]")(input)?; + eprintln!("supports-arbitrary: {}", input); + Ok((input, ())) +} + +// +// // -// after:content-['*'] after:ml-0.5 after:text-red-500 block text-sm font-medium text-slate-700 -// before:content-[''] before:block // bg-black/75 supports-[backdrop-filter]:bg-black/25 supports-[backdrop-filter]:backdrop-blur // aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg-[url('/img/up-arrow.svg')] // group-aria-[sort=ascending]:rotate-0 group-aria-[sort=descending]:rotate-180 @@ -1032,6 +1041,7 @@ fn modifier(input: &str) -> IResult<&str, ()> { arbitrary_at_supports_rule_modifier, arbitrary_at_media_rule_modifier, predefined_modifier, + supports_arbitrary, ))(input) } From 27667156d287e24b9e1a88ea811c577568c31632 Mon Sep 17 00:00:00 2001 From: oyelowo Date: Tue, 10 Oct 2023 12:25:05 -0600 Subject: [PATCH 22/46] Support aria arbitrary --- tailwind/src/main.rs | 2 +- tw-macro/src/lib.rs | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index 681f386..978ff15 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -53,7 +53,7 @@ after:content-['*'] after:ml-0.5 after:text-red-500 block text-sm font-medium te bg-black/75 supports-[backdrop-filter]:bg-black/25 supports-[backdrop-filter]:backdrop-blur - +aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg-[url('/img/up-arrow.svg')] "# ); diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index b9d903e..eec51bb 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -989,10 +989,20 @@ fn supports_arbitrary(input: &str) -> IResult<&str, ()> { Ok((input, ())) } +// aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] +// aria-[sort=descending]:bg-[url('/img/up-arrow.svg')] +fn aria_arbitrary(input: &str) -> IResult<&str, ()> { + let (input, _) = tag("aria-[")(input)?; + let (input, _) = take_while1(is_ident_char)(input)?; + let (input, _) = tag("=")(input)?; + let (input, _) = take_while1(is_ident_char)(input)?; + let (input, _) = tag("]")(input)?; + Ok((input, ())) +} + // // // -// bg-black/75 supports-[backdrop-filter]:bg-black/25 supports-[backdrop-filter]:backdrop-blur // aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg-[url('/img/up-arrow.svg')] // group-aria-[sort=ascending]:rotate-0 group-aria-[sort=descending]:rotate-180 // data-[size=large]:p-8 @@ -1042,6 +1052,7 @@ fn modifier(input: &str) -> IResult<&str, ()> { arbitrary_at_media_rule_modifier, predefined_modifier, supports_arbitrary, + aria_arbitrary, ))(input) } From 3cbfed2b66a11649bd89cb7ff39a78f0700b82bf Mon Sep 17 00:00:00 2001 From: oyelowo Date: Tue, 10 Oct 2023 12:27:30 -0600 Subject: [PATCH 23/46] Support group-aria --- tailwind/src/main.rs | 5 +++++ tw-macro/src/lib.rs | 4 +--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index 978ff15..09dfd60 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -55,6 +55,11 @@ after:content-['*'] after:ml-0.5 after:text-red-500 block text-sm font-medium te aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg-[url('/img/up-arrow.svg')] + + group-aria-[sort=ascending]:rotate-0 group-aria-[sort=descending]:rotate-180 + + data-[size=large]:p-8 + "# ); // let test = diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index eec51bb..1457907 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -992,6 +992,7 @@ fn supports_arbitrary(input: &str) -> IResult<&str, ()> { // aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] // aria-[sort=descending]:bg-[url('/img/up-arrow.svg')] fn aria_arbitrary(input: &str) -> IResult<&str, ()> { + let (input, _) = opt(tag("group-"))(input)?; let (input, _) = tag("aria-[")(input)?; let (input, _) = take_while1(is_ident_char)(input)?; let (input, _) = tag("=")(input)?; @@ -1003,9 +1004,6 @@ fn aria_arbitrary(input: &str) -> IResult<&str, ()> { // // // -// aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg-[url('/img/up-arrow.svg')] -// group-aria-[sort=ascending]:rotate-0 group-aria-[sort=descending]:rotate-180 -// data-[size=large]:p-8 // open:bg-white dark:open:bg-slate-900 open:ring-1 open:ring-black/5 dark:open:ring-white/10 open:shadow-lg p-6 rounded-lg // lg:[&:nth-child(3)]:hover:underline // min-[320px]:text-center max-[600px]:bg-sky-300 From f6cdcc8814f1ac8c8e2f8caafa518d5f6258325f Mon Sep 17 00:00:00 2001 From: oyelowo Date: Tue, 10 Oct 2023 12:29:20 -0600 Subject: [PATCH 24/46] Support data arbitrary --- tw-macro/src/lib.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 1457907..e56390f 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -1001,6 +1001,16 @@ fn aria_arbitrary(input: &str) -> IResult<&str, ()> { Ok((input, ())) } +// data-[size=large]:p-8 +fn data_arbitrary(input: &str) -> IResult<&str, ()> { + let (input, _) = tag("data-[")(input)?; + let (input, _) = take_while1(is_ident_char)(input)?; + let (input, _) = tag("=")(input)?; + let (input, _) = take_while1(is_ident_char)(input)?; + let (input, _) = tag("]")(input)?; + Ok((input, ())) +} + // // // @@ -1051,6 +1061,7 @@ fn modifier(input: &str) -> IResult<&str, ()> { predefined_modifier, supports_arbitrary, aria_arbitrary, + data_arbitrary, ))(input) } From 4c2946862d9b969f022d0aded037dce4261868d5 Mon Sep 17 00:00:00 2001 From: oyelowo Date: Tue, 10 Oct 2023 12:35:15 -0600 Subject: [PATCH 25/46] Support min max simple arbitrary mediaqueries --- tailwind/src/main.rs | 7 ++++++ tw-macro/src/lib.rs | 58 ++++++++++++++++++++++++++------------------ 2 files changed, 42 insertions(+), 23 deletions(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index 09dfd60..fb33a0a 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -60,6 +60,13 @@ aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg- data-[size=large]:p-8 + open:bg-white dark:open:bg-slate-900 open:ring-1 open:ring-black/5 dark:open:ring-white/10 open:shadow-lg p-6 rounded-lg + + lg:[&:nth-child(3)]:hover:underline + + min-[320px]:text-center max-[600px]:bg-sky-300 + + "# ); // let test = diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index e56390f..8ef0849 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -434,6 +434,29 @@ fn is_lengthy_classname(class_name: &str) -> bool { LENGTHY.contains(&class_name) } +fn parse_length_unit(input: &str) -> IResult<&str, String> { + let (input, number) = number::complete::double(input)?; + let (input, unit) = { + // px|em|rem|%|cm|mm|in|pt|pc|vh|vw|vmin|vmax + alt(( + tag("px"), + tag("em"), + tag("rem"), + tag("%"), + tag("cm"), + tag("mm"), + tag("in"), + tag("pt"), + tag("pc"), + tag("vh"), + tag("vw"), + tag("vmin"), + tag("vmax"), + )) + }(input)?; + Ok((input, format!("{}{}", number, unit))) +} + // text-[22px] fn lengthy_arbitrary_classname(input: &str) -> IResult<&str, ()> { let (input, class_name) = take_until("-[")(input)?; @@ -454,25 +477,7 @@ fn lengthy_arbitrary_classname(input: &str) -> IResult<&str, ()> { let (input, _) = tag("[")(input)?; let (input, _) = multispace0(input)?; // is number - let (input, _) = number::complete::double(input)?; - let (input, _) = { - // px|em|rem|%|cm|mm|in|pt|pc|vh|vw|vmin|vmax - alt(( - tag("px"), - tag("em"), - tag("rem"), - tag("%"), - tag("cm"), - tag("mm"), - tag("in"), - tag("pt"), - tag("pc"), - tag("vh"), - tag("vw"), - tag("vmin"), - tag("vmax"), - )) - }(input)?; + let (input, _) = parse_length_unit(input)?; let (input, _) = multispace0(input)?; let (input, _) = tag("]")(input)?; eprintln!("lengthy_arbitrary_classname: {}", input); @@ -1011,12 +1016,18 @@ fn data_arbitrary(input: &str) -> IResult<&str, ()> { Ok((input, ())) } +// min-[320px]:text-center max-[600px]:bg-sky-300 +fn min_max_arbitrary_modifier(input: &str) -> IResult<&str, ()> { + let (input, _) = alt((tag("min-"), tag("max-")))(input)?; + let (input, _) = tag("[")(input)?; + let (input, _) = parse_length_unit(input)?; + let (input, _) = tag("]")(input)?; + eprintln!("min_max_arbitrary_modifier: {}", input); + Ok((input, ())) +} + // // -// -// open:bg-white dark:open:bg-slate-900 open:ring-1 open:ring-black/5 dark:open:ring-white/10 open:shadow-lg p-6 rounded-lg -// lg:[&:nth-child(3)]:hover:underline -// min-[320px]:text-center max-[600px]:bg-sky-300 // top-[117px] lg:top-[344px] // bg-[#bada55] text-[22px] before:content-['Festivus'] // grid grid-cols-[fit-content(theme(spacing.32))] @@ -1062,6 +1073,7 @@ fn modifier(input: &str) -> IResult<&str, ()> { supports_arbitrary, aria_arbitrary, data_arbitrary, + min_max_arbitrary_modifier, ))(input) } From 00b1830d5437798f72b876b26aaf12bae219d686 Mon Sep 17 00:00:00 2001 From: oyelowo Date: Tue, 10 Oct 2023 12:46:56 -0600 Subject: [PATCH 26/46] Complete modifier classnames support --- tailwind/src/main.rs | 22 +++++++++++++++++++++- tw-macro/src/lib.rs | 17 ----------------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index fb33a0a..909b21d 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -64,9 +64,29 @@ aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg- lg:[&:nth-child(3)]:hover:underline - min-[320px]:text-center max-[600px]:bg-sky-300 + min-[320rem]:text-center max-[600px]:bg-sky-300 + top-[117px] lg:top-[344px] + bg-[#bada55] text-[22px] before:content-['Festivus'] + + + grid grid-cols-[fit-content(theme(spacing.32))] + + bg-[--my-color] + + [mask-type:luminance] hover:[mask-type:alpha] + + [--scroll-offset:56px] lg:[--scroll-offset:44px] + + lg:[&:nth-child(3)]:hover:underline + bg-[url('/what_a_rush.png')] + before:content-['hello\_world'] + text-[22px] + text-[#bada55] + text-[var(--my-var)] + text-[length:var(--my-var)] + text-[color:var(--my-var)] "# ); // let test = diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 8ef0849..7b9dd91 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -1026,23 +1026,6 @@ fn min_max_arbitrary_modifier(input: &str) -> IResult<&str, ()> { Ok((input, ())) } -// -// -// top-[117px] lg:top-[344px] -// bg-[#bada55] text-[22px] before:content-['Festivus'] -// grid grid-cols-[fit-content(theme(spacing.32))] -// bg-[--my-color] -// [mask-type:luminance] hover:[mask-type:alpha] -// [--scroll-offset:56px] lg:[--scroll-offset:44px] -// lg:[&:nth-child(3)]:hover:underline -// bg-[url('/what_a_rush.png')] -// before:content-['hello\_world'] -// text-[22px] -// text-[#bada55] -// text-[var(--my-var)] -// text-[length:var(--my-var)] -// text-[color:var(--my-var)] - fn all_consuming_segment<'a, F, O>(parser: F) -> impl Fn(&'a str) -> IResult<&'a str, O> where F: Fn(&'a str) -> IResult<&'a str, O>, From 706a38659b3083a35a8481516cfeb8f950534a4e Mon Sep 17 00:00:00 2001 From: oyelowo Date: Tue, 10 Oct 2023 14:28:31 -0600 Subject: [PATCH 27/46] Switch to new tw macro parser --- tailwind/src/lib.rs | 14 +- tailwind/src/main.rs | 12 +- tw-macro/src/lib.rs | 313 ++----------------------------------------- 3 files changed, 31 insertions(+), 308 deletions(-) diff --git a/tailwind/src/lib.rs b/tailwind/src/lib.rs index 41e0565..4210587 100644 --- a/tailwind/src/lib.rs +++ b/tailwind/src/lib.rs @@ -169,7 +169,7 @@ fn _happy_paths() { let _classnames = tw!("px-[-45px]"); let _classnames = tw!("px-[-45cm]"); let _classnames = tw!("px-[-45rem]"); - let _classnames = tw!("px-[-45em]"); + // let _classnames = tw!("px-[-45em]"); let _classnames = tw!("px-[-45%]"); let _classnames = tw!("px-[-45in]"); let _classnames = tw!("px-[-45vh]"); @@ -178,20 +178,24 @@ fn _happy_paths() { let _classnames = tw!("px-[-45vmax]"); let _classnames = tw!("px-[-45mm]"); let _classnames = tw!("px-[-45pc]"); - let _classnames = tw!("px-[0]"); + let _classnames = tw!("px-[0px]"); + // let _classnames = tw!("px-[0]"); let _classnames = tw!("px-[45px]"); let _classnames = tw!("px-[45cm]"); let _classnames = tw!("px-[45rem]"); - let _classnames = tw!("px-[45em]"); + tw!("bg-taxvhiti"); + + // let _classnames = tw!("px-[45em]"); let _classnames = tw!("px-[45%]"); let _classnames = tw!("px-[45in]"); let _classnames = tw!("px-[45vh]"); let _classnames = tw!("px-[45vw]"); let _classnames = tw!("px-[45vmin]"); let _classnames = tw!("px-[45vmax]"); - let _classnames = tw!("px-[45mm]"); + let _classnames = tw!("px-[45.5mm]"); + let _classnames = tw!("px-[45pc]"); + // let _classnames = tw!("py-[0]"); let _classnames = tw!("px-[45pc]"); - let _classnames = tw!("py-[0]"); let _classnames = tw!("-px-[45pc]"); let _classnames = tw!("hover:[mask-type:alpha]"); let _classnames = tw!( diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index 909b21d..b235e18 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -6,14 +6,13 @@ * Licensed under the MIT license */ use tw_macro::tw; -use tw_macro::tww; fn main() { // let _ = tww!("btn btn"); // // aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg-[url('/img/up-arrow.svg')] - let test = tww!( + let test = tw!( r#"[mask-type:alpha] [ mask-type: alpha ] before:content-['rerer erer re rr r \re reFestivus'] after:content-['I am a content'] after:content-['I am a content'] after:content-['I am a content'] active:hover:text-[#bada55] active:hover:text-[ #ba5 ] text-[#bada55] hover:aria-checked:text-[22px] @@ -87,8 +86,13 @@ aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg- text-[var(--my-var)] text-[length:var(--my-var)] text-[color:var(--my-var)] + + + p-6 max-w-sm mx-auto bg-white rounded-xl shadow-lg flex items-center space-x-4 + "# ); + tw!("bg-taxvhiti"); // let test = // tww!("peer[.is-dirty]:peer-required:block hidden hidden peer-[:nth-of-type(3)_&]:block"); println!("TEXT - {}", test); @@ -143,7 +147,7 @@ aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg- let _classnames = tw!("px-[-45px]"); let _classnames = tw!("px-[-45cm]"); let _classnames = tw!("px-[-45rem]"); - let _classnames = tw!("px-[-45em]"); + // let _classnames = tw!("px-[-45em]"); let _classnames = tw!("px-[-45%]"); let _classnames = tw!("px-[-45in]"); let _classnames = tw!("px-[-45vh]"); @@ -156,7 +160,7 @@ aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg- let _classnames = tw!("px-[45px]"); let _classnames = tw!("px-[45cm]"); let _classnames = tw!("px-[45rem]"); - let _classnames = tw!("px-[45em]"); + // let _classnames = tw!("px-[45em]"); let _classnames = tw!("px-[45%]"); let _classnames = tw!("px-[45in]"); let _classnames = tw!("px-[45vh]"); diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 7b9dd91..0c0dddd 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -34,102 +34,6 @@ use tailwind::signable::SIGNABLES; // // p-6 max-w-sm mx-auto bg-white rounded-xl shadow-lg flex items-center space-x-4 -// OLD IMPLEMENTATION -#[proc_macro] -pub fn tw(raw_input: TokenStream) -> TokenStream { - let r_input = raw_input.clone(); - let input = parse_macro_input!(r_input as LitStr); - let (modifiers, valid_class_names) = match setup(&input) { - Ok(value) => value, - Err(value) => { - return syn::Error::new_spanned(input, value) - .to_compile_error() - .into() - } - }; - - for word in input.value().split_whitespace() { - let (last_word_signed, last_word_unsigned) = get_last_word_types(word); - - // modifiers e.g hover: in - // hover:[mask-type:alpha] - let is_valid_arb_prop = is_valid_arb_prop(word, &modifiers); - - let is_valid_class = - is_valid_class(is_valid_arb_prop, &valid_class_names, last_word_unsigned); - - let (base_classname, arbitrary_value_with_bracket) = - last_word_unsigned.split_once("-[").unwrap_or_default(); - - let is_valid_negative_baseclass = is_valid_negative_baseclass( - &valid_class_names, - last_word_unsigned, - last_word_signed, - is_valid_arb_prop, - ); - - let prefix_is_valid_tailwind_keyword = VALID_BASECLASS_NAMES.contains(&base_classname); - let is_arbitrary_value = - prefix_is_valid_tailwind_keyword && arbitrary_value_with_bracket.ends_with(']'); - - let arbitrary_value = arbitrary_value_with_bracket.trim_end_matches(']'); - let is_lengthy_class = LENGTHY.contains(&base_classname); - let is_valid_length = is_arbitrary_value - && is_lengthy_class - && (is_valid_length(arbitrary_value) || is_valid_calc(arbitrary_value)); - - let has_arb_variant = has_arb_variant(word); - - let is_valid_opacity = is_valid_opacity(last_word_unsigned, &valid_class_names); - - if (is_valid_class && is_valid_modifier(word, &modifiers)) - || is_valid_negative_baseclass - || (!is_lengthy_class && is_arbitrary_value) - || is_valid_length - || is_valid_arb_prop - || has_arb_variant - || is_valid_opacity - || is_valid_group_classname(last_word_unsigned) - || is_validate_modifier_or_group(word, &modifiers, &valid_class_names) - { - // if check_word(word, false).is_empty() { - // return syn::Error::new_spanned(input, format!("Invalid string: {}", word)) - // .to_compile_error() - // .into(); - // } - } else { - return syn::Error::new_spanned(input, format!("Invalid string: {word}")) - .to_compile_error() - .into(); - } - } - - raw_input -} - -// fn check_word(input: &str, loose: bool) -> Vec<&str> { -// Extractor::unique_ord( -// input.as_bytes(), -// ExtractorOptions { -// preserve_spaces_in_arbitrary: loose, -// }, -// ) -// .into_iter() -// .map(|s| unsafe { std::str::from_utf8_unchecked(s) }) -// .collect() -// } - -fn is_valid_length(value: &str) -> bool { - let re = regex::Regex::new(r"^(-?\d+(\.?\d+)?(px|em|rem|%|cm|mm|in|pt|pc|vh|vw|vmin|vmax)|0)$") - .expect("Invalid regex"); - re.is_match(value) -} - -fn is_valid_calc(value: &str) -> bool { - let re = regex::Regex::new(r"^calc\([^)]+\)$").expect("Invalid regex"); - re.is_match(value) -} - fn setup(input: &LitStr) -> Result<(Vec, Vec), TokenStream> { let config = &(match read_tailwind_config() { Ok(config) => config, @@ -162,204 +66,6 @@ fn setup(input: &LitStr) -> Result<(Vec, Vec), TokenStream> { Ok((modifiers, valid_class_names)) } -fn get_last_word_types(word: &str) -> (&str, &str) { - let modifiers_and_class = word.split(':'); - - // let is_arbitrary_property = word.starts_with('[') && word.ends_with(']'); - let last_word_signed = modifiers_and_class.clone().last().unwrap_or_default(); - let last_word_unsigned = last_word_signed - .strip_prefix('-') - .unwrap_or(last_word_signed); - - (last_word_signed, last_word_unsigned) -} - -fn is_valid_modifier(word: &str, modifiers: &[String]) -> bool { - let modifiers_and_class = word.split(':'); - let modifiers_from_word = modifiers_and_class - .clone() - .take(modifiers_and_class.count() - 1) - .collect::>(); - modifiers_from_word - .iter() - .all(|modifier| modifiers.contains(&modifier.to_string())) -} - -fn is_valid_opacity(last_word_unsigned: &str, valid_class_names: &[String]) -> bool { - let is_valid_opacity = { - let (class_name, opacity_raw) = last_word_unsigned.split_once('/').unwrap_or_default(); - let opacity_arb = opacity_raw - .trim_start_matches('[') - .trim_end_matches(']') - .parse::(); - let is_valid_number = - opacity_arb.is_ok_and(|opacity_num| (0.0..=100.0).contains(&opacity_num)); - valid_class_names.contains(&class_name.to_string()) && is_valid_number - }; - is_valid_opacity -} - -fn has_arb_variant(word: &str) -> bool { - // lg:[&:nth-child(3)]:hover:underline - // [&_p]:mt-4 - // flex [@supports(display:grid)]:grid - // [@media(any-hover:hover){&:hover}]:opacity-100 - let has_arb_variant = { - // lg:[&:nth-child(3)]:hover:underline => :nth-child(3) - // [&_p]:mt-4 => _p - let mut ampersand_variant_selector = - word.split("[@").last().unwrap_or_default().split("]:"); - let and_variant_selector = word.split("[&").last().unwrap_or_default().split("]:"); - let is_valid_arbitrary_variant_selector = ampersand_variant_selector.clone().count() >= 2 - && !ampersand_variant_selector - .next() - .unwrap_or_default() - .is_empty(); - let is_valid_arbitrary_variant_queries = and_variant_selector.clone().count() >= 2 - && !and_variant_selector - .clone() - .last() - .unwrap_or_default() - .split("]:") - .next() - .unwrap_or_default() - .is_empty(); - let is_query = word.starts_with("[@"); - - is_valid_arbitrary_variant_selector || is_valid_arbitrary_variant_queries || is_query - // && - // ((!is_query && !word.split("[&").next().unwrap_or_default().is_empty() && word.split(":[&").count() >= 2) || is_query) - }; - has_arb_variant -} - -fn is_valid_negative_baseclass( - valid_class_names: &[String], - last_word_unsigned: &str, - last_word_signed: &str, - is_valid_arb_prop: bool, -) -> bool { - let is_valid_negative_baseclass = { - // tw!("-m-4 p-4 p-4"); - (valid_class_names.contains(&last_word_unsigned.to_string()) - && last_word_signed.starts_with('-') - && SIGNABLES - .iter() - .any(|s| (last_word_unsigned.starts_with(s)))) - || (is_valid_arb_prop - && last_word_signed.starts_with('-') - && SIGNABLES.iter().any(|s| last_word_unsigned.starts_with(s))) - }; - is_valid_negative_baseclass -} - -fn is_valid_class( - is_valid_arb_prop: bool, - valid_class_names: &[String], - last_word_unsigned: &str, -) -> bool { - !is_valid_arb_prop && valid_class_names.contains(&last_word_unsigned.to_string()) -} - -fn is_valid_arb_prop(word: &str, modifiers: &[String]) -> bool { - // TODO: check the first and the last character are not open and close brackets - // respectively i.e arbitrary property e.g [mask_type:aplha]; - // hover:[mask-type:alpha]; - let mut word_for_arb_prop = word.split(":["); - - word_for_arb_prop - .next() - // e.g for hover:[mask-type:alpha], this will be hover, - // for [mask-type:alpha], this will be [mask-type:alpha] - .is_some_and(|modifiers_or_full_arb_prop| { - let is_arbitrary_property = modifiers_or_full_arb_prop.starts_with('[') && modifiers_or_full_arb_prop.ends_with(']'); - - let is_valid = if is_arbitrary_property { - modifiers_or_full_arb_prop.matches('[').count() == 1 && - modifiers_or_full_arb_prop.matches(']').count() == 1 && - modifiers_or_full_arb_prop - .trim_start_matches('[') - .trim_end_matches(']') - .split(':') - .count() == 2 - } else { - // e.g mask-type:alpha] in hover:[mask-type:alpha] - let full_arb_prop = word_for_arb_prop.next().unwrap_or_default(); - // e.g for single, hover in hover:[mask-type:alpha] - // for multiple, hover:first:last, in hover:first:last:[mask-type:alpha] - modifiers_or_full_arb_prop - .split(':') - .all(|modifier| modifiers.contains(&modifier.to_string())) && - full_arb_prop.matches(']').count() == 1 && - full_arb_prop - .trim_end_matches(']') - .split(':') - .count() == 2 - - }; - is_valid - }) - || - // value e.g [mask-type:alpha] in hover:[mask-type:alpha] - // potential addition checks(probably not a good idea. Imagine a new css property, we would - // have to open a PR for every new or esoteric css property.) - word_for_arb_prop.next().is_some_and(|value| { - value.ends_with(']') - && value.split(':').count() == 2 - // We had already split by ":[", so there should be no "[" anymore - && value.matches('[').count() == 0 - && value.matches(']').count() == 1 - }) -} - -fn is_valid_group_pattern(modifier: &str, valid_modifiers: &[String]) -> bool { - let parts: Vec<&str> = modifier.split('/').collect(); - let group_modifier = parts[0]; - parts.len() == 2 - && valid_modifiers.contains(&group_modifier.to_string()) - && group_modifier.starts_with("group") -} - -// tw!("group/edit invisible hover:bg-slate-200 group-hover/item:visible"); -// tw!("group-[:nth-of-type(3)_&]:block group-hover/edit:text-gray-700 group-[:nth-of-type(3)_&]:block"); -fn is_validate_modifier_or_group( - word: &str, - valid_modifiers: &[String], - valid_class_names: &[String], -) -> bool { - let valid_arb_group = word.split(':').collect::>(); - let modifiers = &valid_arb_group[..valid_arb_group.len() - 1]; - let last_word = valid_arb_group.last().unwrap_or(&""); - let is_valid_last_word = - is_valid_string(last_word) && valid_class_names.contains(&last_word.to_string()); - - for modifier in modifiers { - if modifier.starts_with("group") { - if !is_valid_group_pattern(modifier, valid_modifiers) && is_valid_last_word { - return false; - } - } else if !valid_modifiers.contains(&modifier.to_string()) && is_valid_last_word { - return false; - } - } - - is_valid_last_word -} - -fn is_valid_group_classname(class_name: &str) -> bool { - !class_name.contains(':') - && !class_name.contains('[') - && !class_name.contains(']') - && class_name.starts_with("group/") -} - -fn is_valid_string(s: &str) -> bool { - // Matches strings that contain only alphanumeric characters, underscores, and hyphens. - let re = Regex::new(r"^[a-zA-Z0-9_-]*$").expect("Invalid regex"); - re.is_match(s) && !s.is_empty() -} - -// OLD IMPLEMENTATION fn get_classes_straight() -> Vec { get_classes(&read_tailwind_config().unwrap()) // get_classes @@ -414,7 +120,7 @@ fn parse_predefined_tw_classname(input: &str) -> IResult<&str, ()> { )(i) })(input)?; - if is_valid_classname2(class_name) { + if is_valid_classname2(class_name.trim_start_matches("-")) { // Ok((input, class_name)) eprintln!("parse_predefined_tw_classname: {}", input); Ok((input, ())) @@ -431,10 +137,20 @@ fn is_ident_char(c: char) -> bool { } fn is_lengthy_classname(class_name: &str) -> bool { - LENGTHY.contains(&class_name) + LENGTHY.contains(&class_name.trim_start_matches("-")) +} + +fn parse_till_em(input: &str) -> IResult<&str, &str> { + let (input, unit) = take_until("em")(input)?; + Ok((input, unit)) +} +fn parse_till_rem(input: &str) -> IResult<&str, &str> { + let (input, unit) = take_until("rem")(input)?; + Ok((input, unit)) } fn parse_length_unit(input: &str) -> IResult<&str, String> { + // let (input, numeric) = alt((parse_till_em, parse_till_rem))(input)?; let (input, number) = number::complete::double(input)?; let (input, unit) = { // px|em|rem|%|cm|mm|in|pt|pc|vh|vw|vmin|vmax @@ -1093,13 +809,12 @@ fn parse_class_names(input: &str) -> IResult<&str, Vec<&str>> { } fn parse_top(input: &str) -> IResult<&str, Vec<&str>> { - parse_class_names(input) - // all_consuming(parse_class_names)(input) + all_consuming(parse_class_names)(input) } // p-6 max-w-sm mx-auto bg-white rounded-xl shadow-lg flex items-center space-x-4 #[proc_macro] -pub fn tww(raw_input: TokenStream) -> TokenStream { +pub fn tw(raw_input: TokenStream) -> TokenStream { let r_input = raw_input.clone(); let input = parse_macro_input!(r_input as LitStr); let (modifiers, valid_class_names) = match setup(&input) { From 2868f6ebbeef70b18da725e40a7580bde5ab81a2 Mon Sep 17 00:00:00 2001 From: oyelowo Date: Tue, 10 Oct 2023 16:34:34 -0600 Subject: [PATCH 28/46] Parsing leading and ending spaces --- tailwind/src/main.rs | 180 +++++++++++++++++++++---------------------- tw-macro/src/lib.rs | 5 +- 2 files changed, 94 insertions(+), 91 deletions(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index b235e18..d4638c3 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -92,95 +92,95 @@ aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg- "# ); - tw!("bg-taxvhiti"); - // let test = - // tww!("peer[.is-dirty]:peer-required:block hidden hidden peer-[:nth-of-type(3)_&]:block"); + // tw!("bg-taxvhiti"); + // // let test = + // // tww!("peer[.is-dirty]:peer-required:block hidden hidden peer-[:nth-of-type(3)_&]:block"); println!("TEXT - {}", test); - let _ = tw!("btn collapse-arrow"); - // tww!("bg-gray-600 bg-sky-700 bg-midnight underline"); - // tww!("bg-gray-600 aria-checked:bg-sky-700 aria-asc:bg-midnight data-checked:underline"); - let _classnames = tw!("bg-taxvhiti bg-tahiti-500 bg-tahiti bg-midnight bg-red-50"); - let _classnames = tw!("bg-taxvhiti bg-tahiti-500 bg-tahiti bg-midnight bg-purple bg-red-50 bg-tahiti-800 border-s-tahiti-800"); - let _classnames = tw!("md:text-red-50 text-slate-50 text-purple text-tahiti-500"); - let _classnames = tw!("py-sm md:py-md tablet:py-sm lg:py-lg xl:py-xl"); - let _classnames = tw!("group"); - let _classnames = tw!("text-sm font-medium text-slate-300 group-hover:text-white"); - let _classnames = tw!("text-sm font-medium text-slate-500 group-hover:text-slate-300"); - let _classnames = tw!("hover:-translate-y-0.5 transition motion-reduce:hover:translate-y-0 motion-reduce:transition-none"); - let _classnames = tw!("motion-safe:hover:-translate-x-0.5 motion-safe:transition"); - - let _classnames = - tw!("group/edit block invisible md:hover:bg-slate-200 group-hover/item:visible"); - let _classnames = tw!("group-[:nth-of-type(3)_&]:block group-hover/edit:text-gray-700 group-[:nth-of-type(3)_&]:block"); - - let _classnames = tw!("tracking-widest text-xs title-font font-medium text-gray-400 mb-1"); - - let _classnames = - tw!("bg-gray-600 aria-checked:bg-sky-700 aria-asc:bg-midnight data-checked:underline"); - let _classnames = tw!("scroll-m-14 flex supports-grid:grid supports-[display:grid]:grid"); - let _classnames = tw!("scroll-m-sm group-aria-[sort=ascending]:rotate-0"); - let _classnames = tw!("scroll-mx-sm"); - let _classnames = tw!("scroll-mx-md"); - let _classnames = tw!("scroll-my-md"); - let _classnames = tw!("px-sm pt-sm pb-sm pr-sm pl-sm"); - let _classnames = tw!("px-md pt-md pb-md pr-md pl-md"); - let _classnames = tw!("scroll-m-14 scroll-mx-14"); - let _classnames = tw!("m-4 p-4 p-4"); - let _classnames = tw!("-m-[4px] p-4 p-4"); - let _classnames = tw!("-m-4 p-4 p-4"); - let _classnames = tw!("lg:[&:nth-child(3)]:hover:underline"); - let _classnames = // tw!("[0]"); - // tw!("[color:red]/dark"); - // tw!("![feature(slice_as_chunks)]"); - // tw!("!-[feature(slice_as_chunks)]"); - tw!("[@supports(display:grid)]:grid"); - let _classnames = tw!("[@media(any-hover:hover){&:hover}]:opacity-100"); - let _classnames = tw!("underline-offset-[3px]"); - - let _classnames = tw!("[&_p]:mt-4"); - // tw!("-[&_p]:mt-4"); - let _classnames = tw!("lg:[&:nth-child(3)]:hover:underline"); - let _classnames = tw!("outline-blue-500/50"); - let _classnames = tw!("text-blue-600/[.07]"); - - // tw!("[something]"); - let _classnames = tw!("px-[-45px]"); - let _classnames = tw!("px-[-45cm]"); - let _classnames = tw!("px-[-45rem]"); - // let _classnames = tw!("px-[-45em]"); - let _classnames = tw!("px-[-45%]"); - let _classnames = tw!("px-[-45in]"); - let _classnames = tw!("px-[-45vh]"); - let _classnames = tw!("px-[-45vw]"); - let _classnames = tw!("px-[-45vmin]"); - let _classnames = tw!("px-[-45vmax]"); - let _classnames = tw!("px-[-45mm]"); - let _classnames = tw!("px-[-45pc]"); - let _classnames = tw!("px-[0]"); - let _classnames = tw!("px-[45px]"); - let _classnames = tw!("px-[45cm]"); - let _classnames = tw!("px-[45rem]"); - // let _classnames = tw!("px-[45em]"); - let _classnames = tw!("px-[45%]"); - let _classnames = tw!("px-[45in]"); - let _classnames = tw!("px-[45vh]"); - let _classnames = tw!("px-[45vw]"); - let _classnames = tw!("px-[45vmin]"); - let _classnames = tw!("px-[45vmax]"); - let _classnames = tw!("px-[45mm]"); - let _classnames = tw!("px-[45pc]"); - let _classnames = tw!("py-[0]"); - let _classnames = tw!("-px-[45pc]"); - let _classnames = tw!("hover:[mask-type:alpha]"); - let _classnames = tw!( - "m-4 last:first:invalid:last:first:p-4 last:m-4 pb-[calc(100%-34px)] pb-[23px] [mask-type:luminance] - [mask-type:luminance] hover:[mask-type:alpha] lg:[--scroll-offset:44px] oyelowo oyedayo break-after-avoid" - ); - let _classnames = tw!("p-4 md:w-1/3"); - - let _classnames = tw!("opacity-50 md:opacity-100 hover:opacity-100"); - let _classnames = tw!("tracking-widest text-xs font-medium text-gray-400 mb-1"); - // border color is overriden here in tailwind.config.json - let _classnames = - tw!("h-full border-2 border-mana-53 border-opacity-60 rounded-lg overflow-hidden"); + // let _ = tw!("btn collapse-arrow"); + // // tww!("bg-gray-600 bg-sky-700 bg-midnight underline"); + // // tww!("bg-gray-600 aria-checked:bg-sky-700 aria-asc:bg-midnight data-checked:underline"); + // let _classnames = tw!("bg-taxvhiti bg-tahiti-500 bg-tahiti bg-midnight bg-red-50"); + // let _classnames = tw!("bg-taxvhiti bg-tahiti-500 bg-tahiti bg-midnight bg-purple bg-red-50 bg-tahiti-800 border-s-tahiti-800"); + // let _classnames = tw!("md:text-red-50 text-slate-50 text-purple text-tahiti-500"); + // let _classnames = tw!("py-sm md:py-md tablet:py-sm lg:py-lg xl:py-xl"); + // let _classnames = tw!("group"); + // let _classnames = tw!("text-sm font-medium text-slate-300 group-hover:text-white"); + // let _classnames = tw!("text-sm font-medium text-slate-500 group-hover:text-slate-300"); + // let _classnames = tw!("hover:-translate-y-0.5 transition motion-reduce:hover:translate-y-0 motion-reduce:transition-none"); + // let _classnames = tw!("motion-safe:hover:-translate-x-0.5 motion-safe:transition"); + // + // let _classnames = + // tw!("group/edit block invisible md:hover:bg-slate-200 group-hover/item:visible"); + // let _classnames = tw!("group-[:nth-of-type(3)_&]:block group-hover/edit:text-gray-700 group-[:nth-of-type(3)_&]:block"); + // + // let _classnames = tw!("tracking-widest text-xs title-font font-medium text-gray-400 mb-1"); + // + // let _classnames = + // tw!("bg-gray-600 aria-checked:bg-sky-700 aria-asc:bg-midnight data-checked:underline"); + // let _classnames = tw!("scroll-m-14 flex supports-grid:grid supports-[display:grid]:grid"); + // let _classnames = tw!("scroll-m-sm group-aria-[sort=ascending]:rotate-0"); + // let _classnames = tw!("scroll-mx-sm"); + // let _classnames = tw!("scroll-mx-md"); + // let _classnames = tw!("scroll-my-md"); + // let _classnames = tw!("px-sm pt-sm pb-sm pr-sm pl-sm"); + // let _classnames = tw!("px-md pt-md pb-md pr-md pl-md"); + // let _classnames = tw!("scroll-m-14 scroll-mx-14"); + // let _classnames = tw!("m-4 p-4 p-4"); + // let _classnames = tw!("-m-[4px] p-4 p-4"); + // let _classnames = tw!("-m-4 p-4 p-4"); + // let _classnames = tw!("lg:[&:nth-child(3)]:hover:underline"); + // let _classnames = // tw!("[0]"); + // // tw!("[color:red]/dark"); + // // tw!("![feature(slice_as_chunks)]"); + // // tw!("!-[feature(slice_as_chunks)]"); + // tw!("[@supports(display:grid)]:grid"); + // let _classnames = tw!("[@media(any-hover:hover){&:hover}]:opacity-100"); + // let _classnames = tw!("underline-offset-[3px]"); + // + // let _classnames = tw!("[&_p]:mt-4"); + // // tw!("-[&_p]:mt-4"); + // let _classnames = tw!("lg:[&:nth-child(3)]:hover:underline"); + // let _classnames = tw!("outline-blue-500/50"); + // let _classnames = tw!("text-blue-600/[.07]"); + // + // // tw!("[something]"); + // let _classnames = tw!("px-[-45px]"); + // let _classnames = tw!("px-[-45cm]"); + // let _classnames = tw!("px-[-45rem]"); + // // let _classnames = tw!("px-[-45em]"); + // let _classnames = tw!("px-[-45%]"); + // let _classnames = tw!("px-[-45in]"); + // let _classnames = tw!("px-[-45vh]"); + // let _classnames = tw!("px-[-45vw]"); + // let _classnames = tw!("px-[-45vmin]"); + // let _classnames = tw!("px-[-45vmax]"); + // let _classnames = tw!("px-[-45mm]"); + // let _classnames = tw!("px-[-45pc]"); + // // let _classnames = tw!("px-[0]"); + // let _classnames = tw!("px-[45px]"); + // let _classnames = tw!("px-[45cm]"); + // let _classnames = tw!("px-[45rem]"); + // // let _classnames = tw!("px-[45em]"); + // let _classnames = tw!("px-[45%]"); + // let _classnames = tw!("px-[45in]"); + // let _classnames = tw!("px-[45vh]"); + // let _classnames = tw!("px-[45vw]"); + // let _classnames = tw!("px-[45vmin]"); + // let _classnames = tw!("px-[45vmax]"); + // let _classnames = tw!("px-[45mm]"); + // let _classnames = tw!("px-[45pc]"); + // // let _classnames = tw!("py-[0]"); + // let _classnames = tw!("-px-[45pc]"); + // let _classnames = tw!("hover:[mask-type:alpha]"); + // let _classnames = tw!( + // "m-4 last:first:invalid:last:first:p-4 last:m-4 pb-[calc(100%-34px)] pb-[23px] [mask-type:luminance] + // [mask-type:luminance] hover:[mask-type:alpha] lg:[--scroll-offset:44px] oyelowo oyedayo break-after-avoid" + // ); + // let _classnames = tw!("p-4 md:w-1/3"); + // + // let _classnames = tw!("opacity-50 md:opacity-100 hover:opacity-100"); + // let _classnames = tw!("tracking-widest text-xs font-medium text-gray-400 mb-1"); + // // border color is overriden here in tailwind.config.json + // let _classnames = + // tw!("h-full border-2 border-mana-53 border-opacity-60 rounded-lg overflow-hidden"); } diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 0c0dddd..40d2ecc 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -116,7 +116,7 @@ fn parse_predefined_tw_classname(input: &str) -> IResult<&str, ()> { let (input, class_name) = recognize(|i| { // Assuming a Tailwind class consists of alphanumeric, dashes, and colons nom::bytes::complete::is_a( - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-.", + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-./", )(i) })(input)?; @@ -802,7 +802,9 @@ fn parse_class_names(input: &str) -> IResult<&str, Vec<&str>> { // let (input, _) = space0(input)?; // let (input, class_names) = separated_list0(space1, parse_tw_full_classname)(input)?; // let (input, class_names) = separated_list0(space1, tag("btn"))(input)?; + let (input, _) = multispace0(input)?; let (input, class_names) = separated_list0(multispace1, parse_tw_full_classname)(input)?; + let (input, _) = multispace0(input)?; // let (input, _) = space0(input)?; Ok((input, vec![])) @@ -810,6 +812,7 @@ fn parse_class_names(input: &str) -> IResult<&str, Vec<&str>> { fn parse_top(input: &str) -> IResult<&str, Vec<&str>> { all_consuming(parse_class_names)(input) + // parse_class_names(input) } // p-6 max-w-sm mx-auto bg-white rounded-xl shadow-lg flex items-center space-x-4 From f32610cec9850322f1a4e798f14ea561bad1d9d8 Mon Sep 17 00:00:00 2001 From: oyelowo Date: Tue, 10 Oct 2023 16:40:26 -0600 Subject: [PATCH 29/46] Parse arbitrary unitless metric --- tailwind/src/lib.rs | 4 ++-- tw-macro/src/lib.rs | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/tailwind/src/lib.rs b/tailwind/src/lib.rs index 4210587..1d8ea22 100644 --- a/tailwind/src/lib.rs +++ b/tailwind/src/lib.rs @@ -179,7 +179,7 @@ fn _happy_paths() { let _classnames = tw!("px-[-45mm]"); let _classnames = tw!("px-[-45pc]"); let _classnames = tw!("px-[0px]"); - // let _classnames = tw!("px-[0]"); + let _classnames = tw!("px-[0]"); let _classnames = tw!("px-[45px]"); let _classnames = tw!("px-[45cm]"); let _classnames = tw!("px-[45rem]"); @@ -194,7 +194,7 @@ fn _happy_paths() { let _classnames = tw!("px-[45vmax]"); let _classnames = tw!("px-[45.5mm]"); let _classnames = tw!("px-[45pc]"); - // let _classnames = tw!("py-[0]"); + let _classnames = tw!("py-[0]"); let _classnames = tw!("px-[45pc]"); let _classnames = tw!("-px-[45pc]"); let _classnames = tw!("hover:[mask-type:alpha]"); diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 40d2ecc..a88d529 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use nom::{ branch::alt, bytes::complete::{tag, take_till, take_until, take_while1}, - character::complete::{multispace0, multispace1, space0, space1}, + character::complete::{digit1, multispace0, multispace1, space0, space1}, combinator::{all_consuming, not, opt, recognize}, multi::separated_list0, number, @@ -173,6 +173,11 @@ fn parse_length_unit(input: &str) -> IResult<&str, String> { Ok((input, format!("{}{}", number, unit))) } +fn parse_number(input: &str) -> IResult<&str, String> { + let (input, number) = digit1(input)?; + Ok((input, number.to_string())) +} + // text-[22px] fn lengthy_arbitrary_classname(input: &str) -> IResult<&str, ()> { let (input, class_name) = take_until("-[")(input)?; @@ -193,7 +198,7 @@ fn lengthy_arbitrary_classname(input: &str) -> IResult<&str, ()> { let (input, _) = tag("[")(input)?; let (input, _) = multispace0(input)?; // is number - let (input, _) = parse_length_unit(input)?; + let (input, _) = alt((parse_length_unit, parse_number))(input)?; let (input, _) = multispace0(input)?; let (input, _) = tag("]")(input)?; eprintln!("lengthy_arbitrary_classname: {}", input); From f32d5a5bb37c608363d25b822015e369bf0570d1 Mon Sep 17 00:00:00 2001 From: oyelowo Date: Tue, 10 Oct 2023 16:57:52 -0600 Subject: [PATCH 30/46] Allow unitless metric values --- tw-macro/src/lib.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index a88d529..6100744 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -168,6 +168,10 @@ fn parse_length_unit(input: &str) -> IResult<&str, String> { tag("vw"), tag("vmin"), tag("vmax"), + // TODO: Should i allow unitless values? Would need something like this in caller + // location if so: + // let (input, _) = alt((parse_length_unit, parse_number))(input)?; + tag(""), )) }(input)?; Ok((input, format!("{}{}", number, unit))) @@ -198,7 +202,8 @@ fn lengthy_arbitrary_classname(input: &str) -> IResult<&str, ()> { let (input, _) = tag("[")(input)?; let (input, _) = multispace0(input)?; // is number - let (input, _) = alt((parse_length_unit, parse_number))(input)?; + // let (input, _) = alt((parse_length_unit, parse_number))(input)?; + let (input, _) = parse_length_unit(input)?; let (input, _) = multispace0(input)?; let (input, _) = tag("]")(input)?; eprintln!("lengthy_arbitrary_classname: {}", input); From 10dc111cd2e2d1b818cea7c7a6136e3d49bb40b5 Mon Sep 17 00:00:00 2001 From: oyelowo Date: Tue, 10 Oct 2023 17:19:55 -0600 Subject: [PATCH 31/46] Implement custom parser for float and number to disambiguate scientific notation and unit in 45em --- tailwind/src/lib.rs | 5 +++-- tailwind/src/main.rs | 2 +- tw-macro/src/lib.rs | 25 ++++++++++++++++++++++--- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/tailwind/src/lib.rs b/tailwind/src/lib.rs index 1d8ea22..56fbe25 100644 --- a/tailwind/src/lib.rs +++ b/tailwind/src/lib.rs @@ -166,10 +166,11 @@ fn _happy_paths() { let _classnames = tw!("text-blue-600/[.07]"); // tw!("[something]"); - let _classnames = tw!("px-[-45px]"); + let _classnames = tw!("px-[45.43px]"); let _classnames = tw!("px-[-45cm]"); let _classnames = tw!("px-[-45rem]"); - // let _classnames = tw!("px-[-45em]"); + let _classnames = tw!("px-[-45em]"); + let _classnames = tw!("px-[45em]"); let _classnames = tw!("px-[-45%]"); let _classnames = tw!("px-[-45in]"); let _classnames = tw!("px-[-45vh]"); diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index d4638c3..2e7a7b5 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -16,7 +16,7 @@ fn main() { r#"[mask-type:alpha] [ mask-type: alpha ] before:content-['rerer erer re rr r \re reFestivus'] after:content-['I am a content'] after:content-['I am a content'] after:content-['I am a content'] active:hover:text-[#bada55] active:hover:text-[ #ba5 ] text-[#bada55] hover:aria-checked:text-[22px] - text-[22.34e434cm] + text-[22.34434cm] before:content-['hello\_world'] grid grid-cols-[fit-content(theme(spacing.32))] bg-[--my-color] diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 6100744..97029aa 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -7,7 +7,7 @@ use nom::{ combinator::{all_consuming, not, opt, recognize}, multi::separated_list0, number, - sequence::{delimited, tuple}, + sequence::{delimited, preceded, tuple}, IResult, }; /* @@ -148,10 +148,29 @@ fn parse_till_rem(input: &str) -> IResult<&str, &str> { let (input, unit) = take_until("rem")(input)?; Ok((input, unit)) } +// Custom number parser that handles optional decimals and signs +fn float_strict(input: &str) -> IResult<&str, f64> { + let (input, sign) = opt(tag("-"))(input)?; + // let sign = sign.unwrap_or("+"); + let sign = sign.unwrap_or_default(); + + let (input, integer) = digit1(input)?; + let (input, decimals) = opt(preceded(tag("."), digit1))(input)?; + + let float_str = if let Some(decimals) = decimals { + format!("{}{}.{}", sign, integer, decimals) + } else { + format!("{}{}", sign, integer) + }; + + let float_val: f64 = float_str.parse().unwrap(); + Ok((input, float_val)) +} fn parse_length_unit(input: &str) -> IResult<&str, String> { - // let (input, numeric) = alt((parse_till_em, parse_till_rem))(input)?; - let (input, number) = number::complete::double(input)?; + // let (input, number) = number::complete::double(input)?; + let (input, number) = float_strict(input)?; + // let (input, number) = parse_number(input)?; let (input, unit) = { // px|em|rem|%|cm|mm|in|pt|pc|vh|vw|vmin|vmax alt(( From ec2a2df8a1acb01c9b6022207dbd479490399ede Mon Sep 17 00:00:00 2001 From: oyelowo Date: Tue, 10 Oct 2023 17:20:21 -0600 Subject: [PATCH 32/46] Improve disambiguation --- tailwind/src/main.rs | 2 +- tw-macro/src/lib.rs | 26 ++++++++++++-------------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index 2e7a7b5..d4638c3 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -16,7 +16,7 @@ fn main() { r#"[mask-type:alpha] [ mask-type: alpha ] before:content-['rerer erer re rr r \re reFestivus'] after:content-['I am a content'] after:content-['I am a content'] after:content-['I am a content'] active:hover:text-[#bada55] active:hover:text-[ #ba5 ] text-[#bada55] hover:aria-checked:text-[22px] - text-[22.34434cm] + text-[22.34e434cm] before:content-['hello\_world'] grid grid-cols-[fit-content(theme(spacing.32))] bg-[--my-color] diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 97029aa..3206e03 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -148,22 +148,20 @@ fn parse_till_rem(input: &str) -> IResult<&str, &str> { let (input, unit) = take_until("rem")(input)?; Ok((input, unit)) } -// Custom number parser that handles optional decimals and signs +// Custom number parser that handles optional decimals and signs, and scientific notation fn float_strict(input: &str) -> IResult<&str, f64> { - let (input, sign) = opt(tag("-"))(input)?; - // let sign = sign.unwrap_or("+"); - let sign = sign.unwrap_or_default(); - - let (input, integer) = digit1(input)?; - let (input, decimals) = opt(preceded(tag("."), digit1))(input)?; - - let float_str = if let Some(decimals) = decimals { - format!("{}{}.{}", sign, integer, decimals) - } else { - format!("{}{}", sign, integer) - }; + let (input, number) = recognize(tuple(( + opt(alt((tag("-"), tag("+")))), + digit1, + opt(preceded(tag("."), digit1)), + opt(tuple(( + alt((tag("e"), tag("E"))), + opt(alt((tag("-"), tag("+")))), + digit1, + ))), + )))(input)?; - let float_val: f64 = float_str.parse().unwrap(); + let float_val: f64 = number.parse().unwrap(); Ok((input, float_val)) } From 8ddf8b417a8af45cd4c3d3fadd63a95bc4b77ded Mon Sep 17 00:00:00 2001 From: oyelowo Date: Tue, 10 Oct 2023 17:43:46 -0600 Subject: [PATCH 33/46] Cleanup parsers --- tailwind/src/main.rs | 183 +++++++++++++++++++++---------------------- tw-macro/src/lib.rs | 176 ++++++++++------------------------------- 2 files changed, 133 insertions(+), 226 deletions(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index d4638c3..325859a 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -8,9 +8,7 @@ use tw_macro::tw; fn main() { - // let _ = tww!("btn btn"); - - // // aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg-[url('/img/up-arrow.svg')] + let _ = tw!("btn btn"); let test = tw!( r#"[mask-type:alpha] [ mask-type: alpha ] before:content-['rerer erer re rr r \re reFestivus'] @@ -92,95 +90,94 @@ aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg- "# ); - // tw!("bg-taxvhiti"); - // // let test = - // // tww!("peer[.is-dirty]:peer-required:block hidden hidden peer-[:nth-of-type(3)_&]:block"); + // let test = + // tw!("peer[.is-dirty]:peer-required:block hidden hidden peer-[:nth-of-type(3)_&]:block"); println!("TEXT - {}", test); - // let _ = tw!("btn collapse-arrow"); - // // tww!("bg-gray-600 bg-sky-700 bg-midnight underline"); - // // tww!("bg-gray-600 aria-checked:bg-sky-700 aria-asc:bg-midnight data-checked:underline"); - // let _classnames = tw!("bg-taxvhiti bg-tahiti-500 bg-tahiti bg-midnight bg-red-50"); - // let _classnames = tw!("bg-taxvhiti bg-tahiti-500 bg-tahiti bg-midnight bg-purple bg-red-50 bg-tahiti-800 border-s-tahiti-800"); - // let _classnames = tw!("md:text-red-50 text-slate-50 text-purple text-tahiti-500"); - // let _classnames = tw!("py-sm md:py-md tablet:py-sm lg:py-lg xl:py-xl"); - // let _classnames = tw!("group"); - // let _classnames = tw!("text-sm font-medium text-slate-300 group-hover:text-white"); - // let _classnames = tw!("text-sm font-medium text-slate-500 group-hover:text-slate-300"); - // let _classnames = tw!("hover:-translate-y-0.5 transition motion-reduce:hover:translate-y-0 motion-reduce:transition-none"); - // let _classnames = tw!("motion-safe:hover:-translate-x-0.5 motion-safe:transition"); - // - // let _classnames = - // tw!("group/edit block invisible md:hover:bg-slate-200 group-hover/item:visible"); - // let _classnames = tw!("group-[:nth-of-type(3)_&]:block group-hover/edit:text-gray-700 group-[:nth-of-type(3)_&]:block"); - // - // let _classnames = tw!("tracking-widest text-xs title-font font-medium text-gray-400 mb-1"); - // - // let _classnames = - // tw!("bg-gray-600 aria-checked:bg-sky-700 aria-asc:bg-midnight data-checked:underline"); - // let _classnames = tw!("scroll-m-14 flex supports-grid:grid supports-[display:grid]:grid"); - // let _classnames = tw!("scroll-m-sm group-aria-[sort=ascending]:rotate-0"); - // let _classnames = tw!("scroll-mx-sm"); - // let _classnames = tw!("scroll-mx-md"); - // let _classnames = tw!("scroll-my-md"); - // let _classnames = tw!("px-sm pt-sm pb-sm pr-sm pl-sm"); - // let _classnames = tw!("px-md pt-md pb-md pr-md pl-md"); - // let _classnames = tw!("scroll-m-14 scroll-mx-14"); - // let _classnames = tw!("m-4 p-4 p-4"); - // let _classnames = tw!("-m-[4px] p-4 p-4"); - // let _classnames = tw!("-m-4 p-4 p-4"); - // let _classnames = tw!("lg:[&:nth-child(3)]:hover:underline"); - // let _classnames = // tw!("[0]"); - // // tw!("[color:red]/dark"); - // // tw!("![feature(slice_as_chunks)]"); - // // tw!("!-[feature(slice_as_chunks)]"); - // tw!("[@supports(display:grid)]:grid"); - // let _classnames = tw!("[@media(any-hover:hover){&:hover}]:opacity-100"); - // let _classnames = tw!("underline-offset-[3px]"); - // - // let _classnames = tw!("[&_p]:mt-4"); - // // tw!("-[&_p]:mt-4"); - // let _classnames = tw!("lg:[&:nth-child(3)]:hover:underline"); - // let _classnames = tw!("outline-blue-500/50"); - // let _classnames = tw!("text-blue-600/[.07]"); - // - // // tw!("[something]"); - // let _classnames = tw!("px-[-45px]"); - // let _classnames = tw!("px-[-45cm]"); - // let _classnames = tw!("px-[-45rem]"); - // // let _classnames = tw!("px-[-45em]"); - // let _classnames = tw!("px-[-45%]"); - // let _classnames = tw!("px-[-45in]"); - // let _classnames = tw!("px-[-45vh]"); - // let _classnames = tw!("px-[-45vw]"); - // let _classnames = tw!("px-[-45vmin]"); - // let _classnames = tw!("px-[-45vmax]"); - // let _classnames = tw!("px-[-45mm]"); - // let _classnames = tw!("px-[-45pc]"); - // // let _classnames = tw!("px-[0]"); - // let _classnames = tw!("px-[45px]"); - // let _classnames = tw!("px-[45cm]"); - // let _classnames = tw!("px-[45rem]"); - // // let _classnames = tw!("px-[45em]"); - // let _classnames = tw!("px-[45%]"); - // let _classnames = tw!("px-[45in]"); - // let _classnames = tw!("px-[45vh]"); - // let _classnames = tw!("px-[45vw]"); - // let _classnames = tw!("px-[45vmin]"); - // let _classnames = tw!("px-[45vmax]"); - // let _classnames = tw!("px-[45mm]"); - // let _classnames = tw!("px-[45pc]"); - // // let _classnames = tw!("py-[0]"); - // let _classnames = tw!("-px-[45pc]"); - // let _classnames = tw!("hover:[mask-type:alpha]"); - // let _classnames = tw!( - // "m-4 last:first:invalid:last:first:p-4 last:m-4 pb-[calc(100%-34px)] pb-[23px] [mask-type:luminance] - // [mask-type:luminance] hover:[mask-type:alpha] lg:[--scroll-offset:44px] oyelowo oyedayo break-after-avoid" - // ); - // let _classnames = tw!("p-4 md:w-1/3"); - // - // let _classnames = tw!("opacity-50 md:opacity-100 hover:opacity-100"); - // let _classnames = tw!("tracking-widest text-xs font-medium text-gray-400 mb-1"); - // // border color is overriden here in tailwind.config.json - // let _classnames = - // tw!("h-full border-2 border-mana-53 border-opacity-60 rounded-lg overflow-hidden"); + let _ = tw!("btn collapse-arrow"); + tw!("bg-gray-600 bg-sky-700 bg-midnight underline"); + tw!("bg-gray-600 aria-checked:bg-sky-700 aria-asc:bg-midnight data-checked:underline"); + let _classnames = tw!("bg-taxvhiti bg-tahiti-500 bg-tahiti bg-midnight bg-red-50"); + let _classnames = tw!("bg-taxvhiti bg-tahiti-500 bg-tahiti bg-midnight bg-purple bg-red-50 bg-tahiti-800 border-s-tahiti-800"); + let _classnames = tw!("md:text-red-50 text-slate-50 text-purple text-tahiti-500"); + let _classnames = tw!("py-sm md:py-md tablet:py-sm lg:py-lg xl:py-xl"); + let _classnames = tw!("group"); + let _classnames = tw!("text-sm font-medium text-slate-300 group-hover:text-white"); + let _classnames = tw!("text-sm font-medium text-slate-500 group-hover:text-slate-300"); + let _classnames = tw!("hover:-translate-y-0.5 transition motion-reduce:hover:translate-y-0 motion-reduce:transition-none"); + let _classnames = tw!("motion-safe:hover:-translate-x-0.5 motion-safe:transition"); + + let _classnames = + tw!("group/edit block invisible md:hover:bg-slate-200 group-hover/item:visible"); + let _classnames = tw!("group-[:nth-of-type(3)_&]:block group-hover/edit:text-gray-700 group-[:nth-of-type(3)_&]:block"); + + let _classnames = tw!("tracking-widest text-xs title-font font-medium text-gray-400 mb-1"); + + let _classnames = + tw!("bg-gray-600 aria-checked:bg-sky-700 aria-asc:bg-midnight data-checked:underline"); + let _classnames = tw!("scroll-m-14 flex supports-grid:grid supports-[display:grid]:grid"); + let _classnames = tw!("scroll-m-sm group-aria-[sort=ascending]:rotate-0"); + let _classnames = tw!("scroll-mx-sm"); + let _classnames = tw!("scroll-mx-md"); + let _classnames = tw!("scroll-my-md"); + let _classnames = tw!("px-sm pt-sm pb-sm pr-sm pl-sm"); + let _classnames = tw!("px-md pt-md pb-md pr-md pl-md"); + let _classnames = tw!("scroll-m-14 scroll-mx-14"); + let _classnames = tw!("m-4 p-4 p-4"); + let _classnames = tw!("-m-[4px] p-4 p-4"); + let _classnames = tw!("-m-4 p-4 p-4"); + let _classnames = tw!("lg:[&:nth-child(3)]:hover:underline"); + let _classnames = // tw!("[0]"); + // tw!("[color:red]/dark"); + // tw!("![feature(slice_as_chunks)]"); + // tw!("!-[feature(slice_as_chunks)]"); + tw!("[@supports(display:grid)]:grid"); + let _classnames = tw!("[@media(any-hover:hover){&:hover}]:opacity-100"); + let _classnames = tw!("underline-offset-[3px]"); + + let _classnames = tw!("[&_p]:mt-4"); + // tw!("-[&_p]:mt-4"); + let _classnames = tw!("lg:[&:nth-child(3)]:hover:underline"); + let _classnames = tw!("outline-blue-500/50"); + let _classnames = tw!("text-blue-600/[.07]"); + + // tw!("[something]"); + let _classnames = tw!("px-[-45px]"); + let _classnames = tw!("px-[-45cm]"); + let _classnames = tw!("px-[-45rem]"); + let _classnames = tw!("px-[-45em]"); + let _classnames = tw!("px-[-45%]"); + let _classnames = tw!("px-[-45in]"); + let _classnames = tw!("px-[-45vh]"); + let _classnames = tw!("px-[-45vw]"); + let _classnames = tw!("px-[-45vmin]"); + let _classnames = tw!("px-[-45vmax]"); + let _classnames = tw!("px-[-45mm]"); + let _classnames = tw!("px-[-45pc]"); + let _classnames = tw!("px-[0]"); + let _classnames = tw!("px-[45px]"); + let _classnames = tw!("px-[45cm]"); + let _classnames = tw!("px-[45rem]"); + let _classnames = tw!("px-[45em]"); + let _classnames = tw!("px-[45%]"); + let _classnames = tw!("px-[45in]"); + let _classnames = tw!("px-[45vh]"); + let _classnames = tw!("px-[45vw]"); + let _classnames = tw!("px-[45vmin]"); + let _classnames = tw!("px-[45vmax]"); + let _classnames = tw!("px-[45mm]"); + let _classnames = tw!("px-[45pc]"); + let _classnames = tw!("py-[0]"); + let _classnames = tw!("-px-[45pc]"); + let _classnames = tw!("hover:[mask-type:alpha]"); + let _classnames = tw!( + "m-4 last:first:invalid:last:first:p-4 last:m-4 pb-[calc(100%-34px)] pb-[23px] [mask-type:luminance] + [mask-type:luminance] hover:[mask-type:alpha] lg:[--scroll-offset:44px] oyelowo oyedayo break-after-avoid" + ); + let _classnames = tw!("p-4 md:w-1/3"); + + let _classnames = tw!("opacity-50 md:opacity-100 hover:opacity-100"); + let _classnames = tw!("tracking-widest text-xs font-medium text-gray-400 mb-1"); + // border color is overriden here in tailwind.config.json + let _classnames = + tw!("h-full border-2 border-mana-53 border-opacity-60 rounded-lg overflow-hidden"); } diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 3206e03..e00e4af 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -1,3 +1,9 @@ +/* + * Author: Oyelowo Oyedayo + * Email: oyelowo.oss@gmail.com + * Copyright (c) 2023 Oyelowo Oyedayo + * Licensed under the MIT license + */ use std::str::FromStr; use nom::{ @@ -10,12 +16,6 @@ use nom::{ sequence::{delimited, preceded, tuple}, IResult, }; -/* - * Author: Oyelowo Oyedayo - * Email: oyelowo.oss@gmail.com - * Copyright (c) 2023 Oyelowo Oyedayo - * Licensed under the MIT license - */ use syn::{parse_macro_input, LitStr}; mod config; mod plugins; @@ -31,8 +31,6 @@ use proc_macro::TokenStream; use regex::{self, Regex}; use tailwind::signable::SIGNABLES; // use tailwindcss_core::parser::{Extractor, ExtractorOptions}; -// -// p-6 max-w-sm mx-auto bg-white rounded-xl shadow-lg flex items-center space-x-4 fn setup(input: &LitStr) -> Result<(Vec, Vec), TokenStream> { let config = &(match read_tailwind_config() { @@ -78,40 +76,6 @@ fn is_valid_modifier2(modifier: &str) -> bool { get_modifiers(&read_tailwind_config().unwrap()).contains(&modifier.to_string()) } -// [&:nth-child(3)]:underline -// lg:[&:nth-child(3)]:hover:underline -// [&_p]:mt-4 -// flex [@supports(display:grid)]:grid -// [@media(any-hover:hover){&:hover}]:opacity-100 -// group/edit invisible hover:bg-slate-200 group-hover/item:visible -// hidden group-[.is-published]:block -// group-[:nth-of-type(3)_&]:block -// peer-checked/published:text-sky-500 -// peer-[.is-dirty]:peer-required:block hidden -// hidden peer-[:nth-of-type(3)_&]:block -// after:content-['*'] after:ml-0.5 after:text-red-500 block text-sm font-medium text-slate-700 -// before:content-[''] before:block -// bg-black/75 supports-[backdrop-filter]:bg-black/25 supports-[backdrop-filter]:backdrop-blur -// aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg-[url('/img/up-arrow.svg')] -// group-aria-[sort=ascending]:rotate-0 group-aria-[sort=descending]:rotate-180 -// data-[size=large]:p-8 -// open:bg-white dark:open:bg-slate-900 open:ring-1 open:ring-black/5 dark:open:ring-white/10 open:shadow-lg p-6 rounded-lg -// lg:[&:nth-child(3)]:hover:underline -// min-[320px]:text-center max-[600px]:bg-sky-300 -// top-[117px] lg:top-[344px] -// bg-[#bada55] text-[22px] before:content-['Festivus'] -// grid grid-cols-[fit-content(theme(spacing.32))] -// bg-[--my-color] -// [mask-type:luminance] hover:[mask-type:alpha] -// [--scroll-offset:56px] lg:[--scroll-offset:44px] -// lg:[&:nth-child(3)]:hover:underline -// bg-[url('/what_a_rush.png')] -// before:content-['hello\_world'] -// text-[22px] -// text-[#bada55] -// text-[var(--my-var)] -// text-[length:var(--my-var)] -// text-[color:var(--my-var)] fn parse_predefined_tw_classname(input: &str) -> IResult<&str, ()> { let (input, class_name) = recognize(|i| { // Assuming a Tailwind class consists of alphanumeric, dashes, and colons @@ -121,7 +85,6 @@ fn parse_predefined_tw_classname(input: &str) -> IResult<&str, ()> { })(input)?; if is_valid_classname2(class_name.trim_start_matches("-")) { - // Ok((input, class_name)) eprintln!("parse_predefined_tw_classname: {}", input); Ok((input, ())) } else { @@ -140,14 +103,6 @@ fn is_lengthy_classname(class_name: &str) -> bool { LENGTHY.contains(&class_name.trim_start_matches("-")) } -fn parse_till_em(input: &str) -> IResult<&str, &str> { - let (input, unit) = take_until("em")(input)?; - Ok((input, unit)) -} -fn parse_till_rem(input: &str) -> IResult<&str, &str> { - let (input, unit) = take_until("rem")(input)?; - Ok((input, unit)) -} // Custom number parser that handles optional decimals and signs, and scientific notation fn float_strict(input: &str) -> IResult<&str, f64> { let (input, number) = recognize(tuple(( @@ -166,9 +121,7 @@ fn float_strict(input: &str) -> IResult<&str, f64> { } fn parse_length_unit(input: &str) -> IResult<&str, String> { - // let (input, number) = number::complete::double(input)?; let (input, number) = float_strict(input)?; - // let (input, number) = parse_number(input)?; let (input, unit) = { // px|em|rem|%|cm|mm|in|pt|pc|vh|vw|vmin|vmax alt(( @@ -194,11 +147,6 @@ fn parse_length_unit(input: &str) -> IResult<&str, String> { Ok((input, format!("{}{}", number, unit))) } -fn parse_number(input: &str) -> IResult<&str, String> { - let (input, number) = digit1(input)?; - Ok((input, number.to_string())) -} - // text-[22px] fn lengthy_arbitrary_classname(input: &str) -> IResult<&str, ()> { let (input, class_name) = take_until("-[")(input)?; @@ -219,7 +167,6 @@ fn lengthy_arbitrary_classname(input: &str) -> IResult<&str, ()> { let (input, _) = tag("[")(input)?; let (input, _) = multispace0(input)?; // is number - // let (input, _) = alt((parse_length_unit, parse_number))(input)?; let (input, _) = parse_length_unit(input)?; let (input, _) = multispace0(input)?; let (input, _) = tag("]")(input)?; @@ -227,11 +174,6 @@ fn lengthy_arbitrary_classname(input: &str) -> IResult<&str, ()> { Ok((input, ())) } -// fn is_hex_color(color: &str) -> bool { -// let re = regex::Regex::new(r"^#[0-9a-fA-F]{3,6}$").expect("Invalid regex"); -// re.is_match(color) -// } - // #bada55 fn parse_hex_color(input: &str) -> IResult<&str, String> { let (input, _) = tag("#")(input)?; @@ -336,16 +278,6 @@ fn colorful_arbitrary_baseclass(input: &str) -> IResult<&str, ()> { // e.g: [mask-type:alpha] fn kv_pair_classname(input: &str) -> IResult<&str, ()> { - // let Ok((input, _)) = delimited( - // tag("["), - // tuple(( - // take_while1(is_ident_char), - // tag(":"), - // take_while1(is_ident_char), - // )), - // tag("]"), - // )(input); - // Ok((input, ())) let (input, _) = tag("[")(input)?; let (input, _) = multispace0(input)?; let (input, _) = take_while1(is_ident_char)(input)?; @@ -570,27 +502,13 @@ fn arbitrary_css_var3(input: &str) -> IResult<&str, ()> { // group/edit fn arbitrary_group_classname(input: &str) -> IResult<&str, ()> { - let (input, _) = alt(( - // tuple((tag("group-"), predefined_modifier)), - // tuple((tag("group"), |_| Ok(("", ())))), - tag("group"), - ))(input)?; + let (input, _) = alt((tag("group"),))(input)?; let (input, _) = tag("/")(input)?; let (input, _) = take_while1(|char| is_ident_char(char))(input)?; eprintln!("arbitrary_group_classname: {}", input); Ok((input, ())) } -// [mask-type:luminance] hover:[mask-type:alpha] -// [--scroll-offset:56px] lg:[--scroll-offset:44px] -// lg:[&:nth-child(3)]:hover:underline -// bg-[url('/what_a_rush.png')] -// before:content-['hello\_world'] -// text-[22px] -// text-[#bada55] -// text-[var(--my-var)] -// text-[length:var(--my-var)] -// text-[color:var(--my-var)] fn parse_single_tw_classname(input: &str) -> IResult<&str, ()> { alt(( // bg-[url('/what_a_rush.png')] @@ -622,14 +540,6 @@ fn parse_single_tw_classname(input: &str) -> IResult<&str, ()> { ))(input) } -// rules: colon(:) preceeded by either valid identifier or closed bracket -// // postceeded by either valid identifier or open bracket -// // e.g -fn modifier_separator(input: &str) -> IResult<&str, &str> { - let (input, _) = tag(":")(input)?; - Ok((input, "")) -} - // hover:underline fn predefined_modifier(input: &str) -> IResult<&str, ()> { let (input, modifier) = recognize(|i| { @@ -707,7 +617,6 @@ fn group_peer_modifier(input: &str) -> IResult<&str, ()> { // https://tailwindcss.com/docs/hover-focus-and-other-states#differentiating-peers // peer-checked/published:text-sky-500 tuple((tag("peer-"), predefined_modifier)), - // tuple((tag("group"), |_| Ok(("", ())))), ))(input)?; let (input, _) = tag("/")(input)?; let (input, _) = take_while1(is_ident_char)(input)?; @@ -769,23 +678,6 @@ fn min_max_arbitrary_modifier(input: &str) -> IResult<&str, ()> { Ok((input, ())) } -fn all_consuming_segment<'a, F, O>(parser: F) -> impl Fn(&'a str) -> IResult<&'a str, O> -where - F: Fn(&'a str) -> IResult<&'a str, O>, -{ - move |input: &str| { - let (input, output) = parser(input)?; - if input.is_empty() { - Ok((input, output)) - } else { - Err(nom::Err::Error(nom::error::Error::new( - input, - nom::error::ErrorKind::Tag, - ))) - } - } -} - fn modifier(input: &str) -> IResult<&str, ()> { alt(( group_modifier_selector, @@ -804,45 +696,66 @@ fn modifier(input: &str) -> IResult<&str, ()> { } fn modifiers_chained(input: &str) -> IResult<&str, ()> { - let (input, modifiers) = separated_list0(tag(":"), modifier)(input)?; + let (input, _modifiers) = separated_list0(tag(":"), modifier)(input)?; Ok((input, ())) } fn parse_tw_full_classname(input: &str) -> IResult<&str, Vec<&str>> { - // Parses one or more Tailwind class names separated by spaces, allowing optional spaces before and after each class name - // let (input, class_names) = delimited( - // multispace0, - // separated_list0(multispace1, parse_single_tw_classname), - // multispace0, - // )(input)?; - let (input, class_names) = tuple(( opt(tuple((modifiers_chained, tag(":")))), parse_single_tw_classname, ))(input)?; - // Ok((input, class_names)) Ok((input, vec![])) } +// Edge cases +// [&:nth-child(3)]:underline +// lg:[&:nth-child(3)]:hover:underline +// [&_p]:mt-4 +// flex [@supports(display:grid)]:grid +// [@media(any-hover:hover){&:hover}]:opacity-100 +// group/edit invisible hover:bg-slate-200 group-hover/item:visible +// hidden group-[.is-published]:block +// group-[:nth-of-type(3)_&]:block +// peer-checked/published:text-sky-500 +// peer-[.is-dirty]:peer-required:block hidden +// hidden peer-[:nth-of-type(3)_&]:block +// after:content-['*'] after:ml-0.5 after:text-red-500 block text-sm font-medium text-slate-700 +// before:content-[''] before:block +// bg-black/75 supports-[backdrop-filter]:bg-black/25 supports-[backdrop-filter]:backdrop-blur +// aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg-[url('/img/up-arrow.svg')] +// group-aria-[sort=ascending]:rotate-0 group-aria-[sort=descending]:rotate-180 +// data-[size=large]:p-8 +// open:bg-white dark:open:bg-slate-900 open:ring-1 open:ring-black/5 dark:open:ring-white/10 open:shadow-lg p-6 rounded-lg +// lg:[&:nth-child(3)]:hover:underline +// min-[320px]:text-center max-[600px]:bg-sky-300 +// top-[117px] lg:top-[344px] +// bg-[#bada55] text-[22px] before:content-['Festivus'] +// grid grid-cols-[fit-content(theme(spacing.32))] +// bg-[--my-color] +// [mask-type:luminance] hover:[mask-type:alpha] +// [--scroll-offset:56px] lg:[--scroll-offset:44px] +// lg:[&:nth-child(3)]:hover:underline +// bg-[url('/what_a_rush.png')] +// before:content-['hello\_world'] +// text-[22px] +// text-[#bada55] +// text-[var(--my-var)] +// text-[length:var(--my-var)] +// text-[color:var(--my-var)] fn parse_class_names(input: &str) -> IResult<&str, Vec<&str>> { - // let (input, _) = space0(input)?; - // let (input, class_names) = separated_list0(space1, parse_tw_full_classname)(input)?; - // let (input, class_names) = separated_list0(space1, tag("btn"))(input)?; let (input, _) = multispace0(input)?; let (input, class_names) = separated_list0(multispace1, parse_tw_full_classname)(input)?; let (input, _) = multispace0(input)?; - // let (input, _) = space0(input)?; Ok((input, vec![])) } fn parse_top(input: &str) -> IResult<&str, Vec<&str>> { all_consuming(parse_class_names)(input) - // parse_class_names(input) } -// p-6 max-w-sm mx-auto bg-white rounded-xl shadow-lg flex items-center space-x-4 #[proc_macro] pub fn tw(raw_input: TokenStream) -> TokenStream { let r_input = raw_input.clone(); @@ -866,9 +779,6 @@ pub fn tw(raw_input: TokenStream) -> TokenStream { } }; - // for word in input.value().split_whitespace() { - - // raw_input quote::quote! { #input } From 836d6382325ba2aab0f62c2bfddc921094d7226b Mon Sep 17 00:00:00 2001 From: oyelowo Date: Tue, 10 Oct 2023 17:46:57 -0600 Subject: [PATCH 34/46] Cleanup debug printing --- tw-macro/src/lib.rs | 41 ++++++----------------------------------- 1 file changed, 6 insertions(+), 35 deletions(-) diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index e00e4af..9c3f7f9 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -66,26 +66,25 @@ fn setup(input: &LitStr) -> Result<(Vec, Vec), TokenStream> { fn get_classes_straight() -> Vec { get_classes(&read_tailwind_config().unwrap()) - // get_classes } -fn is_valid_classname2(class_name: &str) -> bool { + +fn is_valid_classname(class_name: &str) -> bool { get_classes_straight().contains(&class_name.to_string()) } -fn is_valid_modifier2(modifier: &str) -> bool { +fn is_valid_modifier(modifier: &str) -> bool { get_modifiers(&read_tailwind_config().unwrap()).contains(&modifier.to_string()) } fn parse_predefined_tw_classname(input: &str) -> IResult<&str, ()> { let (input, class_name) = recognize(|i| { - // Assuming a Tailwind class consists of alphanumeric, dashes, and colons + // Considering a Tailwind class consists of alphanumeric, dashes, and slash nom::bytes::complete::is_a( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-./", )(i) })(input)?; - if is_valid_classname2(class_name.trim_start_matches("-")) { - eprintln!("parse_predefined_tw_classname: {}", input); + if is_valid_classname(class_name.trim_start_matches("-")) { Ok((input, ())) } else { Err(nom::Err::Error(nom::error::Error::new( @@ -151,9 +150,6 @@ fn parse_length_unit(input: &str) -> IResult<&str, String> { fn lengthy_arbitrary_classname(input: &str) -> IResult<&str, ()> { let (input, class_name) = take_until("-[")(input)?; let ((input, _)) = if is_lengthy_classname(class_name) { - // if is_lengthy_classname(class_name) { - // // Do something special for lengthy class names - // } Ok((input, ())) } else { Err(nom::Err::Error(nom::error::Error::new( @@ -170,7 +166,6 @@ fn lengthy_arbitrary_classname(input: &str) -> IResult<&str, ()> { let (input, _) = parse_length_unit(input)?; let (input, _) = multispace0(input)?; let (input, _) = tag("]")(input)?; - eprintln!("lengthy_arbitrary_classname: {}", input); Ok((input, ())) } @@ -186,7 +181,6 @@ fn parse_hex_color(input: &str) -> IResult<&str, String> { nom::error::ErrorKind::Tag, ))) }?; - eprintln!("parse_hex_color: {}", input); let color = format!("#{}", color); Ok((input, color)) } @@ -220,7 +214,6 @@ fn parse_rgb_color(input: &str) -> IResult<&str, String> { let (input, b) = parse_u8(input)?; let (input, _) = multispace0(input)?; let (input, _) = tag(")")(input)?; - eprintln!("parse_rgb_color: {}", input); let color = format!("rgb({}, {}, {})", r, g, b); Ok((input, color)) } @@ -244,7 +237,6 @@ fn parse_rgba_color(input: &str) -> IResult<&str, String> { let (input, a) = number::complete::double(input)?; let (input, _) = multispace0(input)?; let (input, _) = tag(")")(input)?; - eprintln!("parse_rgba_color: {}", input); let color = format!("rgba({}, {}, {}, {})", r, g, b, a); Ok((input, color)) } @@ -272,7 +264,6 @@ fn colorful_arbitrary_baseclass(input: &str) -> IResult<&str, ()> { let (input, _) = alt((parse_hex_color, parse_rgb_color, parse_rgba_color))(input)?; let (input, _) = multispace0(input)?; let (input, _) = tag("]")(input)?; - eprintln!("colorful_arbitrary_baseclass: {}", input); Ok((input, ())) } @@ -287,7 +278,6 @@ fn kv_pair_classname(input: &str) -> IResult<&str, ()> { let (input, _) = take_while1(is_ident_char)(input)?; let (input, _) = multispace0(input)?; let (input, _) = tag("]")(input)?; - eprintln!("kv_pair_classname: {}", input); Ok((input, ())) } @@ -296,7 +286,6 @@ fn arbitrary_content(input: &str) -> IResult<&str, ()> { let (input, _) = tag("content-['")(input)?; let (input, _) = take_until("']")(input)?; let (input, _) = tag("']")(input)?; - eprintln!("arbitrary_content: {}", input); Ok((input, ())) } @@ -327,7 +316,6 @@ fn predefined_colorful_opacity(input: &str) -> IResult<&str, ()> { ))) } }; - eprintln!("predefined_colorful_opacity: {}", input); Ok((input, ())) } @@ -360,7 +348,6 @@ fn arbitrary_opacity(input: &str) -> IResult<&str, ()> { } }; let (input, _) = tag("]")(input)?; - eprintln!("arbitrary_opacity: {}", input); Ok((input, ())) } @@ -386,7 +373,6 @@ fn bg_arbitrary_url(input: &str) -> IResult<&str, ()> { let (input, _) = tag("')")(input)?; let (input, _) = multispace0(input)?; let (input, _) = tag("]")(input)?; - eprintln!("bg_arbitrary_url: {}", input); Ok((input, ())) } @@ -422,7 +408,6 @@ fn arbitrary_css_value(input: &str) -> IResult<&str, ()> { let (input, _) = take_until("]")(input)?; let (input, _) = multispace0(input)?; let (input, _) = tag("]")(input)?; - eprintln!("arbitrary_css_value: {}", input); Ok((input, ())) } @@ -446,7 +431,6 @@ fn arbitrary_css_var(input: &str) -> IResult<&str, ()> { let (input, _) = tag("--")(input)?; let (input, _) = take_while1(|char| is_ident_char(char) && char != ']')(input)?; let (input, _) = tag("]")(input)?; - eprintln!("arbitrary_css_var: {}", input); Ok((input, ())) } // text-[var(--my-var)] @@ -469,7 +453,6 @@ fn arbitrary_css_var2(input: &str) -> IResult<&str, ()> { let (input, _) = tag("var(--")(input)?; let (input, _) = take_while1(|char| is_ident_char(char) && char != ')')(input)?; let (input, _) = tag(")]")(input)?; - eprintln!("arbitrary_css_var2: {}", input); Ok((input, ())) } @@ -496,7 +479,6 @@ fn arbitrary_css_var3(input: &str) -> IResult<&str, ()> { let (input, _) = tag("var(--")(input)?; let (input, _) = take_while1(|char| is_ident_char(char) && char != ')')(input)?; let (input, _) = tag(")]")(input)?; - eprintln!("arbitrary_css_var3: {}", input); Ok((input, ())) } @@ -505,7 +487,6 @@ fn arbitrary_group_classname(input: &str) -> IResult<&str, ()> { let (input, _) = alt((tag("group"),))(input)?; let (input, _) = tag("/")(input)?; let (input, _) = take_while1(|char| is_ident_char(char))(input)?; - eprintln!("arbitrary_group_classname: {}", input); Ok((input, ())) } @@ -549,8 +530,7 @@ fn predefined_modifier(input: &str) -> IResult<&str, ()> { )(i) })(input)?; - if is_valid_modifier2(modifier) { - eprintln!("predefined_modifier: {}", input); + if is_valid_modifier(modifier) { Ok((input, ())) } else { Err(nom::Err::Error(nom::error::Error::new( @@ -577,8 +557,6 @@ fn arbitrary_front_selector_modifier(input: &str) -> IResult<&str, ()> { let (input, _) = tag("[&")(input)?; let (input, _) = take_until("]")(input)?; let (input, _) = tag("]")(input)?; - eprintln!("arbitrary_front_selector_modifier: {}", input); - Ok((input, ())) } // group-[:nth-of-type(3)_&]:block @@ -587,7 +565,6 @@ fn arbitrary_back_selector_modifier(input: &str) -> IResult<&str, ()> { let (input, _) = tag("-[")(input)?; let (input, _) = take_until("&]")(input)?; let (input, _) = tag("&]")(input)?; - eprintln!("arbitrary_back_selector_modifier: {}", input); Ok((input, ())) } @@ -596,7 +573,6 @@ fn arbitrary_at_supports_rule_modifier(input: &str) -> IResult<&str, ()> { let (input, _) = tag("[@supports(")(input)?; let (input, _) = take_until(")")(input)?; let (input, _) = tag(")]")(input)?; - eprintln!("arbitrary_at_supports_rule_modifier: {}", input); Ok((input, ())) } @@ -606,7 +582,6 @@ fn arbitrary_at_media_rule_modifier(input: &str) -> IResult<&str, ()> { let (input, _) = tag("[@media(")(input)?; let (input, _) = take_until("]")(input)?; let (input, _) = tag("]")(input)?; - eprintln!("arbitrary_at_media_rule_modifier: {}", input); Ok((input, ())) } @@ -620,7 +595,6 @@ fn group_peer_modifier(input: &str) -> IResult<&str, ()> { ))(input)?; let (input, _) = tag("/")(input)?; let (input, _) = take_while1(is_ident_char)(input)?; - eprintln!("group_peer_modifier: {}", input); Ok((input, ())) } @@ -633,7 +607,6 @@ fn group_modifier_selector(input: &str) -> IResult<&str, ()> { let (input, _) = tag("-[")(input)?; let (input, _) = take_until("]")(input)?; let (input, _) = tag("]")(input)?; - eprintln!("group_modifier_selector: {}", input); Ok((input, ())) } @@ -642,7 +615,6 @@ fn supports_arbitrary(input: &str) -> IResult<&str, ()> { let (input, _) = tag("supports-[")(input)?; let (input, _) = take_until("]")(input)?; let (input, _) = tag("]")(input)?; - eprintln!("supports-arbitrary: {}", input); Ok((input, ())) } @@ -674,7 +646,6 @@ fn min_max_arbitrary_modifier(input: &str) -> IResult<&str, ()> { let (input, _) = tag("[")(input)?; let (input, _) = parse_length_unit(input)?; let (input, _) = tag("]")(input)?; - eprintln!("min_max_arbitrary_modifier: {}", input); Ok((input, ())) } From 7f13a2588442b6b50feaee65934f2aa54596cff5 Mon Sep 17 00:00:00 2001 From: oyelowo Date: Tue, 10 Oct 2023 17:54:03 -0600 Subject: [PATCH 35/46] Handle signable values better --- tailwind/src/main.rs | 1 + tw-macro/src/lib.rs | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index 325859a..a8c66c2 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -24,6 +24,7 @@ fn main() { [--scroll-offset:56px] lg:[--scroll-offset:44px] btn bg-[url('/img/down-arrow.svg')] ring-white/10 bg-black/25 bg-black/[100] bg-black/[0.75] active:hover:collapse-arrow + -mt-4 lg:[&:nth-child(3)]:hover:underline group-[:nth-of-type(3)_&]:block diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 9c3f7f9..ffa17be 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -84,7 +84,7 @@ fn parse_predefined_tw_classname(input: &str) -> IResult<&str, ()> { )(i) })(input)?; - if is_valid_classname(class_name.trim_start_matches("-")) { + if is_valid_classname(class_name.strip_prefix("-").unwrap_or(class_name)) { Ok((input, ())) } else { Err(nom::Err::Error(nom::error::Error::new( @@ -99,7 +99,7 @@ fn is_ident_char(c: char) -> bool { } fn is_lengthy_classname(class_name: &str) -> bool { - LENGTHY.contains(&class_name.trim_start_matches("-")) + LENGTHY.contains(&class_name.strip_prefix("-").unwrap_or(class_name)) } // Custom number parser that handles optional decimals and signs, and scientific notation @@ -557,6 +557,7 @@ fn arbitrary_front_selector_modifier(input: &str) -> IResult<&str, ()> { let (input, _) = tag("[&")(input)?; let (input, _) = take_until("]")(input)?; let (input, _) = tag("]")(input)?; + Ok((input, ())) } // group-[:nth-of-type(3)_&]:block From 3465f578bb8d924fbd0e13e6c67e7f12cc57ef83 Mon Sep 17 00:00:00 2001 From: oyelowo Date: Tue, 10 Oct 2023 18:40:58 -0600 Subject: [PATCH 36/46] Take into consideration if classname is signable before allowing signs --- tw-macro/src/lib.rs | 11 ++++++++++- tw-macro/src/tailwind/signable.rs | 3 +++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index ffa17be..1c42977 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -84,7 +84,16 @@ fn parse_predefined_tw_classname(input: &str) -> IResult<&str, ()> { )(i) })(input)?; - if is_valid_classname(class_name.strip_prefix("-").unwrap_or(class_name)) { + let is_signable = SIGNABLES.iter().any(|s| { + class_name + .strip_prefix("-") + .unwrap_or(class_name) + .starts_with(s) + }); + + if is_signable && is_valid_classname(class_name.strip_prefix("-").unwrap_or(class_name)) { + Ok((input, ())) + } else if !is_signable && is_valid_classname(class_name) { Ok((input, ())) } else { Err(nom::Err::Error(nom::error::Error::new( diff --git a/tw-macro/src/tailwind/signable.rs b/tw-macro/src/tailwind/signable.rs index 78a448a..fb03cca 100644 --- a/tw-macro/src/tailwind/signable.rs +++ b/tw-macro/src/tailwind/signable.rs @@ -45,4 +45,7 @@ pub const SIGNABLES: [&str; 40] = [ "grid-auto-columns", "z", "order", + // "scroll-mx", + // "scroll-my", + // "scroll-m", ]; From c7fac02810a567710ca1e62020efd55ff9ad341e Mon Sep 17 00:00:00 2001 From: oyelowo Date: Tue, 10 Oct 2023 18:48:07 -0600 Subject: [PATCH 37/46] Allow unitless value in test --- tailwind/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tailwind/src/lib.rs b/tailwind/src/lib.rs index 56fbe25..357a78d 100644 --- a/tailwind/src/lib.rs +++ b/tailwind/src/lib.rs @@ -82,9 +82,9 @@ fn _unsupported_media_query() {} /// /// ```compile_fail /// use tw_macro::tw; -/// tw!("px-[45]"); +/// tw!("px-45]"); /// ``` -fn _missing_unit_after_arbitrary_value() {} +fn malformed_arbitrary_value() {} /// Invalid group usage. /// From 288c29b519a55733aade71d2de6e3c8101b14826 Mon Sep 17 00:00:00 2001 From: oyelowo Date: Tue, 10 Oct 2023 21:20:28 -0600 Subject: [PATCH 38/46] Allow underscore for rgb and rbga --- tw-macro/src/lib.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 1c42977..a4eb1c9 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -208,17 +208,17 @@ fn parse_u8(input: &str) -> IResult<&str, u8> { Ok((input, num as u8)) } -// rgb(255, 255, 255) +// rgb(255, 255, 255) rgb(255_255_255) fn parse_rgb_color(input: &str) -> IResult<&str, String> { let (input, _) = tag("rgb(")(input)?; let (input, _) = multispace0(input)?; let (input, r) = parse_u8(input)?; let (input, _) = multispace0(input)?; - let (input, _) = tag(",")(input)?; + let (input, _) = alt((tag(","), tag("_")))(input)?; let (input, _) = multispace0(input)?; let (input, g) = parse_u8(input)?; let (input, _) = multispace0(input)?; - let (input, _) = tag(",")(input)?; + let (input, _) = alt((tag(","), tag("_")))(input)?; let (input, _) = multispace0(input)?; let (input, b) = parse_u8(input)?; let (input, _) = multispace0(input)?; @@ -227,21 +227,21 @@ fn parse_rgb_color(input: &str) -> IResult<&str, String> { Ok((input, color)) } -// rgba(255, 255, 255, 0.5) +// rgba(255, 255, 255, 0.5) rgba(255_255_255_0.5) fn parse_rgba_color(input: &str) -> IResult<&str, String> { let (input, _) = tag("rgba(")(input)?; let (input, _) = multispace0(input)?; let (input, r) = parse_u8(input)?; let (input, _) = multispace0(input)?; - let (input, _) = tag(",")(input)?; + let (input, _) = alt((tag(","), tag("_")))(input)?; let (input, _) = multispace0(input)?; let (input, g) = parse_u8(input)?; let (input, _) = multispace0(input)?; - let (input, _) = tag(",")(input)?; + let (input, _) = alt((tag(","), tag("_")))(input)?; let (input, _) = multispace0(input)?; let (input, b) = parse_u8(input)?; let (input, _) = multispace0(input)?; - let (input, _) = tag(",")(input)?; + let (input, _) = alt((tag(","), tag("_")))(input)?; let (input, _) = multispace0(input)?; let (input, a) = number::complete::double(input)?; let (input, _) = multispace0(input)?; @@ -356,6 +356,7 @@ fn arbitrary_opacity(input: &str) -> IResult<&str, ()> { ))) } }; + let (input, _) = opt(tag("%"))(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } From c1754a727f29368870398de0312427b1cc074e47 Mon Sep 17 00:00:00 2001 From: oyelowo Date: Tue, 10 Oct 2023 21:23:19 -0600 Subject: [PATCH 39/46] Disallow spacing within rgba and rbga --- tw-macro/src/lib.rs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index a4eb1c9..8f791e3 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -211,17 +211,11 @@ fn parse_u8(input: &str) -> IResult<&str, u8> { // rgb(255, 255, 255) rgb(255_255_255) fn parse_rgb_color(input: &str) -> IResult<&str, String> { let (input, _) = tag("rgb(")(input)?; - let (input, _) = multispace0(input)?; let (input, r) = parse_u8(input)?; - let (input, _) = multispace0(input)?; let (input, _) = alt((tag(","), tag("_")))(input)?; - let (input, _) = multispace0(input)?; let (input, g) = parse_u8(input)?; - let (input, _) = multispace0(input)?; let (input, _) = alt((tag(","), tag("_")))(input)?; - let (input, _) = multispace0(input)?; let (input, b) = parse_u8(input)?; - let (input, _) = multispace0(input)?; let (input, _) = tag(")")(input)?; let color = format!("rgb({}, {}, {})", r, g, b); Ok((input, color)) @@ -230,21 +224,13 @@ fn parse_rgb_color(input: &str) -> IResult<&str, String> { // rgba(255, 255, 255, 0.5) rgba(255_255_255_0.5) fn parse_rgba_color(input: &str) -> IResult<&str, String> { let (input, _) = tag("rgba(")(input)?; - let (input, _) = multispace0(input)?; let (input, r) = parse_u8(input)?; - let (input, _) = multispace0(input)?; let (input, _) = alt((tag(","), tag("_")))(input)?; - let (input, _) = multispace0(input)?; let (input, g) = parse_u8(input)?; - let (input, _) = multispace0(input)?; let (input, _) = alt((tag(","), tag("_")))(input)?; - let (input, _) = multispace0(input)?; let (input, b) = parse_u8(input)?; - let (input, _) = multispace0(input)?; let (input, _) = alt((tag(","), tag("_")))(input)?; - let (input, _) = multispace0(input)?; let (input, a) = number::complete::double(input)?; - let (input, _) = multispace0(input)?; let (input, _) = tag(")")(input)?; let color = format!("rgba({}, {}, {}, {})", r, g, b, a); Ok((input, color)) From 822b81cf38d18ce16dc945630139b348f171adf5 Mon Sep 17 00:00:00 2001 From: oyelowo Date: Tue, 10 Oct 2023 21:29:06 -0600 Subject: [PATCH 40/46] Support arbitrary contetnt with arrows --- tailwind/src/main.rs | 25 +++++++++++++++++++++++-- tw-macro/src/lib.rs | 12 ++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index a8c66c2..b5f72a9 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -47,8 +47,9 @@ fn main() { after:content-['*'] after:ml-0.5 after:text-red-500 block text-sm font-medium text-slate-700 before:content-[''] before:block - - +content-[>] +content-[<] + bg-black/75 supports-[backdrop-filter]:bg-black/25 supports-[backdrop-filter]:backdrop-blur aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg-[url('/img/up-arrow.svg')] @@ -91,6 +92,26 @@ aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg- "# ); + // 'content-[>]', + // // ^ + // 'content-[<]', + // // ^ + // + // // With functions and math expressions + // 'px-[calc(100%-1rem)]', + // 'px-[theme(spacing.1)]', + // 'px-[theme(spacing[1.5])]', + // + // // With spaces (replaced by `_`) + // 'bg-[rgb(255_0_0)]', + // + // // Examples with combinations + // 'w-[calc(100%_-_theme("spacing[1.5]))"]', + // 'fill-[oklab(59.69%_0.1007_0.1191_/_0.5)]/[33.7%]', + // 'fill-[color:oklab(59.69%_0.1007_0.1191_/_0.5)]/[33.7%]', + // 'shadow-[inset_0_-3em_3em_rgba(0,_0,_0,_0.1),_0_0_0_2px_rgb(255,_255,_255),_0.3em_0.3em_1em_rgba(0,_0,_0,_0.3)]' + // + // let test = // tw!("peer[.is-dirty]:peer-required:block hidden hidden peer-[:nth-of-type(3)_&]:block"); println!("TEXT - {}", test); diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 8f791e3..381f247 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -284,6 +284,16 @@ fn arbitrary_content(input: &str) -> IResult<&str, ()> { Ok((input, ())) } +// content-[>] content-[<] +fn arbitrary_with_arrow(input: &str) -> IResult<&str, ()> { + let (input, _) = take_while1(is_ident_char)(input)?; + let (input, _) = tag("[")(input)?; + let (input, _) = alt((tag(">"), tag("<")))(input)?; + let (input, _) = take_until("]")(input)?; + let (input, _) = tag("]")(input)?; + Ok((input, ())) +} + // bg-black/25 fn predefined_colorful_opacity(input: &str) -> IResult<&str, ()> { let input = if COLORFUL_BASECLASSES @@ -506,6 +516,8 @@ fn parse_single_tw_classname(input: &str) -> IResult<&str, ()> { colorful_arbitrary_baseclass, // before:content-['Festivus'] arbitrary_content, + // content-[>] content-[<] + arbitrary_with_arrow, // bg-[--my-color] arbitrary_css_var, // text-[var(--my-var)] From 52944972324aa0adaa4bb2f28ffa5efa35399990 Mon Sep 17 00:00:00 2001 From: oyelowo Date: Tue, 10 Oct 2023 21:40:04 -0600 Subject: [PATCH 41/46] Disallow spacing within each modifier_classnames --- tailwind/src/main.rs | 6 +++--- tw-macro/src/lib.rs | 21 ++------------------- 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index b5f72a9..b41f315 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -11,9 +11,9 @@ fn main() { let _ = tw!("btn btn"); let test = tw!( - r#"[mask-type:alpha] [ mask-type: alpha ] before:content-['rerer erer re rr r \re reFestivus'] + r#"[mask-type:alpha] [mask-type:alpha] before:content-['rerer erer re rr r \re reFestivus'] after:content-['I am a content'] after:content-['I am a content'] after:content-['I am a content'] - active:hover:text-[#bada55] active:hover:text-[ #ba5 ] text-[#bada55] hover:aria-checked:text-[22px] + active:hover:text-[#bada55] active:hover:text-[#fa5] text-[#bada55] hover:aria-checked:text-[22px] text-[22.34e434cm] before:content-['hello\_world'] grid grid-cols-[fit-content(theme(spacing.32))] @@ -22,7 +22,7 @@ fn main() { text-[length:var(--my-var)] text-[color:var(--my-var)] [--scroll-offset:56px] lg:[--scroll-offset:44px] - btn bg-[url('/img/down-arrow.svg')] ring-white/10 bg-black/25 bg-black/[100] bg-black/[0.75] active:hover:collapse-arrow + btn bg-[url('/img/down-arrow.svg')] ring-white/10 bg-black/25 bg-black/[80%] bg-black/[100] bg-black/[0.75] active:hover:collapse-arrow -mt-4 diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 381f247..e6f4ffc 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -170,10 +170,8 @@ fn lengthy_arbitrary_classname(input: &str) -> IResult<&str, ()> { // arbitrary value let (input, _) = tag("-")(input)?; let (input, _) = tag("[")(input)?; - let (input, _) = multispace0(input)?; // is number let (input, _) = parse_length_unit(input)?; - let (input, _) = multispace0(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } @@ -255,9 +253,7 @@ fn colorful_arbitrary_baseclass(input: &str) -> IResult<&str, ()> { // arbitrary value let (input, _) = tag("-")(input)?; let (input, _) = tag("[")(input)?; - let (input, _) = multispace0(input)?; let (input, _) = alt((parse_hex_color, parse_rgb_color, parse_rgba_color))(input)?; - let (input, _) = multispace0(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } @@ -265,13 +261,9 @@ fn colorful_arbitrary_baseclass(input: &str) -> IResult<&str, ()> { // e.g: [mask-type:alpha] fn kv_pair_classname(input: &str) -> IResult<&str, ()> { let (input, _) = tag("[")(input)?; - let (input, _) = multispace0(input)?; let (input, _) = take_while1(is_ident_char)(input)?; - let (input, _) = multispace0(input)?; let (input, _) = tag(":")(input)?; - let (input, _) = multispace0(input)?; let (input, _) = take_while1(is_ident_char)(input)?; - let (input, _) = multispace0(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } @@ -325,7 +317,7 @@ fn predefined_colorful_opacity(input: &str) -> IResult<&str, ()> { Ok((input, ())) } -// bg-black/[27] +// bg-black/[27] bg-black/[27%] fn arbitrary_opacity(input: &str) -> IResult<&str, ()> { let input = if COLORFUL_BASECLASSES .iter() @@ -373,11 +365,9 @@ fn bg_arbitrary_url(input: &str) -> IResult<&str, ()> { }; let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; let (input, _) = tag("[")(input)?; - let (input, _) = multispace0(input)?; let (input, _) = tag("url('")(input)?; let (input, _) = take_until("')")(input)?; let (input, _) = tag("')")(input)?; - let (input, _) = multispace0(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } @@ -404,15 +394,12 @@ fn arbitrary_css_value(input: &str) -> IResult<&str, ()> { tag("var(--"), // :var(-- )))(input)?; - let (input, _) = multispace0(input)?; let (input, _) = take_while1(|char| is_ident_char(char) && char != '(')(input)?; let (input, _) = tag("(")(input)?; let (input, _) = take_until(")]")(input)?; - let (input, _) = multispace0(input)?; // allow anything inthe brackets let (input, _) = take_until("]")(input)?; - let (input, _) = multispace0(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } @@ -433,7 +420,6 @@ fn arbitrary_css_var(input: &str) -> IResult<&str, ()> { }; let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; let (input, _) = tag("[")(input)?; - let (input, _) = multispace0(input)?; let (input, _) = tag("--")(input)?; let (input, _) = take_while1(|char| is_ident_char(char) && char != ']')(input)?; let (input, _) = tag("]")(input)?; @@ -455,7 +441,6 @@ fn arbitrary_css_var2(input: &str) -> IResult<&str, ()> { }; let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; let (input, _) = tag("[")(input)?; - let (input, _) = multispace0(input)?; let (input, _) = tag("var(--")(input)?; let (input, _) = take_while1(|char| is_ident_char(char) && char != ')')(input)?; let (input, _) = tag(")]")(input)?; @@ -478,10 +463,8 @@ fn arbitrary_css_var3(input: &str) -> IResult<&str, ()> { }; let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; let (input, _) = tag("[")(input)?; - let (input, _) = multispace0(input)?; let (input, _) = take_while1(|char| is_ident_char(char) && char != ':')(input)?; let (input, _) = tag(":")(input)?; - let (input, _) = multispace0(input)?; let (input, _) = tag("var(--")(input)?; let (input, _) = take_while1(|char| is_ident_char(char) && char != ')')(input)?; let (input, _) = tag(")]")(input)?; @@ -726,7 +709,7 @@ fn parse_tw_full_classname(input: &str) -> IResult<&str, Vec<&str>> { // text-[color:var(--my-var)] fn parse_class_names(input: &str) -> IResult<&str, Vec<&str>> { let (input, _) = multispace0(input)?; - let (input, class_names) = separated_list0(multispace1, parse_tw_full_classname)(input)?; + let (input, _class_names) = separated_list0(multispace1, parse_tw_full_classname)(input)?; let (input, _) = multispace0(input)?; Ok((input, vec![])) From 3012b03c9257f8e4431054ee773c0514aa98614e Mon Sep 17 00:00:00 2001 From: oyelowo Date: Tue, 10 Oct 2023 21:42:42 -0600 Subject: [PATCH 42/46] Support more character types in arbitrary kv values --- tailwind/src/main.rs | 6 ++++++ tw-macro/src/lib.rs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index b41f315..a38ac34 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -24,6 +24,12 @@ fn main() { [--scroll-offset:56px] lg:[--scroll-offset:44px] btn bg-[url('/img/down-arrow.svg')] ring-white/10 bg-black/25 bg-black/[80%] bg-black/[100] bg-black/[0.75] active:hover:collapse-arrow + [mask-image:linear-gradient(180deg,white,rgba(255,255,255,0))] + + pt-8 text-base font-semibold leading-7 + + bg-[rgb(0,0,3)] absolute inset-0 bg-center + -mt-4 lg:[&:nth-child(3)]:hover:underline diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index e6f4ffc..0a64ff7 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -263,7 +263,7 @@ fn kv_pair_classname(input: &str) -> IResult<&str, ()> { let (input, _) = tag("[")(input)?; let (input, _) = take_while1(is_ident_char)(input)?; let (input, _) = tag(":")(input)?; - let (input, _) = take_while1(is_ident_char)(input)?; + let (input, _) = take_until("]")(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } From 5f0afba25f7719045db26e7c9419a2e996dfef65 Mon Sep 17 00:00:00 2001 From: oyelowo Date: Tue, 10 Oct 2023 21:45:20 -0600 Subject: [PATCH 43/46] Add more examples --- tailwind/src/main.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index a38ac34..93974db 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -96,6 +96,11 @@ aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg- p-6 max-w-sm mx-auto bg-white rounded-xl shadow-lg flex items-center space-x-4 + + w-[calc(100%_-_theme("spacing[1.5]))"] + shadow-[inset_0_-3em_3em_rgba(0,_0,_0,_0.1),_0_0_0_2px_rgb(255,_255,_255),_0.3em_0.3em_1em_rgba(0,_0,_0,_0.3)] + + "# ); // 'content-[>]', @@ -112,10 +117,6 @@ aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg- // 'bg-[rgb(255_0_0)]', // // // Examples with combinations - // 'w-[calc(100%_-_theme("spacing[1.5]))"]', - // 'fill-[oklab(59.69%_0.1007_0.1191_/_0.5)]/[33.7%]', - // 'fill-[color:oklab(59.69%_0.1007_0.1191_/_0.5)]/[33.7%]', - // 'shadow-[inset_0_-3em_3em_rgba(0,_0,_0,_0.1),_0_0_0_2px_rgb(255,_255,_255),_0.3em_0.3em_1em_rgba(0,_0,_0,_0.3)]' // // let test = From 9d296a1aae2e2a1435199f10df7673df0423937d Mon Sep 17 00:00:00 2001 From: oyelowo Date: Tue, 10 Oct 2023 21:52:56 -0600 Subject: [PATCH 44/46] Fix and format --- tailwind/src/lib.rs | 2 +- tw-macro/src/lib.rs | 37 +++++++++++++++++-------------------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/tailwind/src/lib.rs b/tailwind/src/lib.rs index 357a78d..c6ddf90 100644 --- a/tailwind/src/lib.rs +++ b/tailwind/src/lib.rs @@ -84,7 +84,7 @@ fn _unsupported_media_query() {} /// use tw_macro::tw; /// tw!("px-45]"); /// ``` -fn malformed_arbitrary_value() {} +fn _malformed_arbitrary_value() {} /// Invalid group usage. /// diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 0a64ff7..2df4cf2 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -4,16 +4,15 @@ * Copyright (c) 2023 Oyelowo Oyedayo * Licensed under the MIT license */ -use std::str::FromStr; use nom::{ branch::alt, - bytes::complete::{tag, take_till, take_until, take_while1}, - character::complete::{digit1, multispace0, multispace1, space0, space1}, + bytes::complete::{tag, take_until, take_while1}, + character::complete::{digit1, multispace0, multispace1}, combinator::{all_consuming, not, opt, recognize}, multi::separated_list0, number, - sequence::{delimited, preceded, tuple}, + sequence::{preceded, tuple}, IResult, }; use syn::{parse_macro_input, LitStr}; @@ -21,14 +20,12 @@ mod config; mod plugins; mod tailwind; use tailwind::{ - colorful::COLORFUL_BASECLASSES, default_classnames::TAILWIND_CSS, lengthy::LENGTHY, - modifiers::get_modifiers, tailwind_config::CustomisableClasses, - valid_baseclass_names::VALID_BASECLASS_NAMES, + colorful::COLORFUL_BASECLASSES, lengthy::LENGTHY, modifiers::get_modifiers, + tailwind_config::CustomisableClasses, valid_baseclass_names::VALID_BASECLASS_NAMES, }; use config::{get_classes, noconfig::UNCONFIGURABLE, read_tailwind_config}; use proc_macro::TokenStream; -use regex::{self, Regex}; use tailwind::signable::SIGNABLES; // use tailwindcss_core::parser::{Extractor, ExtractorOptions}; @@ -86,14 +83,14 @@ fn parse_predefined_tw_classname(input: &str) -> IResult<&str, ()> { let is_signable = SIGNABLES.iter().any(|s| { class_name - .strip_prefix("-") + .strip_prefix('-') .unwrap_or(class_name) .starts_with(s) }); - if is_signable && is_valid_classname(class_name.strip_prefix("-").unwrap_or(class_name)) { - Ok((input, ())) - } else if !is_signable && is_valid_classname(class_name) { + if is_signable && is_valid_classname(class_name.strip_prefix('-').unwrap_or(class_name)) + || !is_signable && is_valid_classname(class_name) + { Ok((input, ())) } else { Err(nom::Err::Error(nom::error::Error::new( @@ -108,7 +105,7 @@ fn is_ident_char(c: char) -> bool { } fn is_lengthy_classname(class_name: &str) -> bool { - LENGTHY.contains(&class_name.strip_prefix("-").unwrap_or(class_name)) + LENGTHY.contains(&class_name.strip_prefix('-').unwrap_or(class_name)) } // Custom number parser that handles optional decimals and signs, and scientific notation @@ -158,7 +155,7 @@ fn parse_length_unit(input: &str) -> IResult<&str, String> { // text-[22px] fn lengthy_arbitrary_classname(input: &str) -> IResult<&str, ()> { let (input, class_name) = take_until("-[")(input)?; - let ((input, _)) = if is_lengthy_classname(class_name) { + let (input, _) = if is_lengthy_classname(class_name) { Ok((input, ())) } else { Err(nom::Err::Error(nom::error::Error::new( @@ -180,7 +177,7 @@ fn lengthy_arbitrary_classname(input: &str) -> IResult<&str, ()> { fn parse_hex_color(input: &str) -> IResult<&str, String> { let (input, _) = tag("#")(input)?; let (input, color) = take_while1(|c: char| c.is_ascii_hexdigit())(input)?; - let ((input, _)) = if color.chars().count() == 3 || color.chars().count() == 6 { + let (input, _) = if color.chars().count() == 3 || color.chars().count() == 6 { Ok((input, ())) } else { Err(nom::Err::Error(nom::error::Error::new( @@ -241,7 +238,7 @@ fn is_colorful_baseclass(class_name: &str) -> bool { // text-[#bada55] fn colorful_arbitrary_baseclass(input: &str) -> IResult<&str, ()> { let (input, class_name) = take_until("-[")(input)?; - let ((input, _)) = if is_colorful_baseclass(class_name) { + let (input, _) = if is_colorful_baseclass(class_name) { Ok((input, ())) } else { Err(nom::Err::Error(nom::error::Error::new( @@ -475,7 +472,7 @@ fn arbitrary_css_var3(input: &str) -> IResult<&str, ()> { fn arbitrary_group_classname(input: &str) -> IResult<&str, ()> { let (input, _) = alt((tag("group"),))(input)?; let (input, _) = tag("/")(input)?; - let (input, _) = take_while1(|char| is_ident_char(char))(input)?; + let (input, _) = take_while1(is_ident_char)(input)?; Ok((input, ())) } @@ -664,7 +661,7 @@ fn modifiers_chained(input: &str) -> IResult<&str, ()> { } fn parse_tw_full_classname(input: &str) -> IResult<&str, Vec<&str>> { - let (input, class_names) = tuple(( + let (input, _class_names) = tuple(( opt(tuple((modifiers_chained, tag(":")))), parse_single_tw_classname, ))(input)?; @@ -723,7 +720,7 @@ fn parse_top(input: &str) -> IResult<&str, Vec<&str>> { pub fn tw(raw_input: TokenStream) -> TokenStream { let r_input = raw_input.clone(); let input = parse_macro_input!(r_input as LitStr); - let (modifiers, valid_class_names) = match setup(&input) { + let (_modifiers, _valid_class_names) = match setup(&input) { Ok(value) => value, Err(value) => { return syn::Error::new_spanned(input, value) @@ -733,7 +730,7 @@ pub fn tw(raw_input: TokenStream) -> TokenStream { }; let full_classnames = input.value(); - let (input, class_names) = match parse_top(&full_classnames) { + let (input, _class_names) = match parse_top(&full_classnames) { Ok(value) => value, Err(value) => { return syn::Error::new_spanned(input, value) From d63540fa2ec1d3ec3e207a215b8cf17d5cbede57 Mon Sep 17 00:00:00 2001 From: oyelowo Date: Tue, 10 Oct 2023 22:32:14 -0600 Subject: [PATCH 45/46] Use hashset for classses for 0(1) class/modifier names lookups --- sss.md | 1181 ++++++++++++++++++++++++++++ tw-macro/src/lib.rs | 13 +- tw-macro/src/tailwind/modifiers.rs | 2 +- 3 files changed, 1192 insertions(+), 4 deletions(-) create mode 100644 sss.md diff --git a/sss.md b/sss.md new file mode 100644 index 0000000..36c36bd --- /dev/null +++ b/sss.md @@ -0,0 +1,1181 @@ +Create a PR for these changes: + +Skip to content +Oyelowo +/ +tailwind-rust + +Type / to search + +Code +Issues +1 +Pull requests +Actions +Projects +Wiki +Security +Insights +Settings +Open a pull request +Create a new pull request by comparing changes across two branches. If you need to, you can also . + +... + + Able to merge. These branches can be automatically merged. +@Oyelowo +5 use a more robust parsing for tailwind classes + + +Leave a comment +No file chosen +Attach files by dragging & dropping, selecting or pasting them. +Remember, contributions to this repository should follow our GitHub Community Guidelines. +Reviewers +No reviews—at least 1 approving review is required. +Assignees +No one— +Labels +None yet +Projects +None yet +Milestone +No milestone +Development +Use Closing keywords in the description to automatically close issues + +Helpful resources +GitHub Community Guidelines + 1 contributor + Commits 44 + Files changed 8 +Showing with 836 additions and 283 deletions. + 1 change: 1 addition & 0 deletions1 +Cargo.toml +@@ -26,6 +26,7 @@ tw-macro = { path = "tw-macro" } +proc-macro2 = "1.0.66" +quote = "1.0.33" +syn = "2.0.29" +nom = "7.1.3" +static_assertions = "1.1.0" +serde = { version = "1.0.188", features = ["derive"] } +serde_json = "1.0.105" + 15 changes: 10 additions & 5 deletions15 +tailwind/src/lib.rs +@@ -82,9 +82,9 @@ fn _unsupported_media_query() {} +/// +/// ```compile_fail +/// use tw_macro::tw; +/// tw!("px-[45]"); +/// tw!("px-45]"); +/// ``` +fn _missing_unit_after_arbitrary_value() {} +fn _malformed_arbitrary_value() {} + +/// Invalid group usage. +/// +@@ -166,10 +166,11 @@ fn _happy_paths() { + let _classnames = tw!("text-blue-600/[.07]"); + + // tw!("[something]"); + let _classnames = tw!("px-[-45px]"); + let _classnames = tw!("px-[45.43px]"); + let _classnames = tw!("px-[-45cm]"); + let _classnames = tw!("px-[-45rem]"); + let _classnames = tw!("px-[-45em]"); + let _classnames = tw!("px-[45em]"); + let _classnames = tw!("px-[-45%]"); + let _classnames = tw!("px-[-45in]"); + let _classnames = tw!("px-[-45vh]"); +@@ -178,20 +179,24 @@ fn _happy_paths() { + let _classnames = tw!("px-[-45vmax]"); + let _classnames = tw!("px-[-45mm]"); + let _classnames = tw!("px-[-45pc]"); + let _classnames = tw!("px-[0px]"); + let _classnames = tw!("px-[0]"); + let _classnames = tw!("px-[45px]"); + let _classnames = tw!("px-[45cm]"); + let _classnames = tw!("px-[45rem]"); + let _classnames = tw!("px-[45em]"); + tw!("bg-taxvhiti"); + + // let _classnames = tw!("px-[45em]"); + let _classnames = tw!("px-[45%]"); + let _classnames = tw!("px-[45in]"); + let _classnames = tw!("px-[45vh]"); + let _classnames = tw!("px-[45vw]"); + let _classnames = tw!("px-[45vmin]"); + let _classnames = tw!("px-[45vmax]"); + let _classnames = tw!("px-[45mm]"); + let _classnames = tw!("px-[45.5mm]"); + let _classnames = tw!("px-[45pc]"); + let _classnames = tw!("py-[0]"); + let _classnames = tw!("px-[45pc]"); + let _classnames = tw!("-px-[45pc]"); + let _classnames = tw!("hover:[mask-type:alpha]"); + let _classnames = tw!( + 116 changes: 116 additions & 0 deletions116 +tailwind/src/main.rs + 1 change: 1 addition & 0 deletions1 +tw-macro/Cargo.toml + 958 changes: 680 additions & 278 deletions958 +tw-macro/src/lib.rs +@@ -4,116 +4,31 @@ + * Copyright (c) 2023 Oyelowo Oyedayo + * Licensed under the MIT license + */ + +use nom::{ + branch::alt, + bytes::complete::{tag, take_until, take_while1}, + character::complete::{digit1, multispace0, multispace1}, + combinator::{all_consuming, not, opt, recognize}, + multi::separated_list0, + number, + sequence::{preceded, tuple}, + IResult, +}; +use syn::{parse_macro_input, LitStr}; +mod config; +mod plugins; +mod tailwind; +use tailwind::{ + lengthy::LENGTHY, modifiers::get_modifiers, tailwind_config::CustomisableClasses, + valid_baseclass_names::VALID_BASECLASS_NAMES, + colorful::COLORFUL_BASECLASSES, lengthy::LENGTHY, modifiers::get_modifiers, + tailwind_config::CustomisableClasses, valid_baseclass_names::VALID_BASECLASS_NAMES, +}; + +use config::{get_classes, noconfig::UNCONFIGURABLE, read_tailwind_config}; +use proc_macro::TokenStream; +use regex::{self, Regex}; +use tailwind::signable::SIGNABLES; +// use tailwindcss_core::parser::{Extractor, ExtractorOptions}; + +#[proc_macro] +pub fn tw(raw_input: TokenStream) -> TokenStream { + let r_input = raw_input.clone(); + let input = parse_macro_input!(r_input as LitStr); + let (modifiers, valid_class_names) = match setup(&input) { + Ok(value) => value, + Err(value) => { + return syn::Error::new_spanned(input, value) + .to_compile_error() + .into() + } + }; + + for word in input.value().split_whitespace() { + let (last_word_signed, last_word_unsigned) = get_last_word_types(word); + + // modifiers e.g hover: in + // hover:[mask-type:alpha] + let is_valid_arb_prop = is_valid_arb_prop(word, &modifiers); + + let is_valid_class = + is_valid_class(is_valid_arb_prop, &valid_class_names, last_word_unsigned); + + let (base_classname, arbitrary_value_with_bracket) = + last_word_unsigned.split_once("-[").unwrap_or_default(); + + let is_valid_negative_baseclass = is_valid_negative_baseclass( + &valid_class_names, + last_word_unsigned, + last_word_signed, + is_valid_arb_prop, + ); + + let prefix_is_valid_tailwind_keyword = VALID_BASECLASS_NAMES.contains(&base_classname); + let is_arbitrary_value = + prefix_is_valid_tailwind_keyword && arbitrary_value_with_bracket.ends_with(']'); + + let arbitrary_value = arbitrary_value_with_bracket.trim_end_matches(']'); + let is_lengthy_class = LENGTHY.contains(&base_classname); + let is_valid_length = is_arbitrary_value + && is_lengthy_class + && (is_valid_length(arbitrary_value) || is_valid_calc(arbitrary_value)); + + let has_arb_variant = has_arb_variant(word); + + let is_valid_opacity = is_valid_opacity(last_word_unsigned, &valid_class_names); + + if (is_valid_class && is_valid_modifier(word, &modifiers)) + || is_valid_negative_baseclass + || (!is_lengthy_class && is_arbitrary_value) + || is_valid_length + || is_valid_arb_prop + || has_arb_variant + || is_valid_opacity + || is_valid_group_classname(last_word_unsigned) + || is_validate_modifier_or_group(word, &modifiers, &valid_class_names) + { + // if check_word(word, false).is_empty() { + // return syn::Error::new_spanned(input, format!("Invalid string: {}", word)) + // .to_compile_error() + // .into(); + // } + } else { + return syn::Error::new_spanned(input, format!("Invalid string: {word}")) + .to_compile_error() + .into(); + } + } + + raw_input +} + +// fn check_word(input: &str, loose: bool) -> Vec<&str> { +// Extractor::unique_ord( +// input.as_bytes(), +// ExtractorOptions { +// preserve_spaces_in_arbitrary: loose, +// }, +// ) +// .into_iter() +// .map(|s| unsafe { std::str::from_utf8_unchecked(s) }) +// .collect() +// } + +fn is_valid_length(value: &str) -> bool { + let re = regex::Regex::new(r"^(-?\d+(\.?\d+)?(px|em|rem|%|cm|mm|in|pt|pc|vh|vw|vmin|vmax)|0)$") + .expect("Invalid regex"); + re.is_match(value) +} + +fn is_valid_calc(value: &str) -> bool { + let re = regex::Regex::new(r"^calc\([^)]+\)$").expect("Invalid regex"); + re.is_match(value) +} + +fn setup(input: &LitStr) -> Result<(Vec, Vec), TokenStream> { + let config = &(match read_tailwind_config() { + Ok(config) => config, +@@ -146,199 +61,686 @@ fn setup(input: &LitStr) -> Result<(Vec, Vec), TokenStream> { + Ok((modifiers, valid_class_names)) +} + +fn get_last_word_types(word: &str) -> (&str, &str) { + let modifiers_and_class = word.split(':'); +fn get_classes_straight() -> Vec { + get_classes(&read_tailwind_config().unwrap()) +} + +fn is_valid_classname(class_name: &str) -> bool { + get_classes_straight().contains(&class_name.to_string()) +} + +fn is_valid_modifier(modifier: &str) -> bool { + get_modifiers(&read_tailwind_config().unwrap()).contains(&modifier.to_string()) +} + +fn parse_predefined_tw_classname(input: &str) -> IResult<&str, ()> { + let (input, class_name) = recognize(|i| { + // Considering a Tailwind class consists of alphanumeric, dashes, and slash + nom::bytes::complete::is_a( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-./", + )(i) + })(input)?; + + let is_signable = SIGNABLES.iter().any(|s| { + class_name + .strip_prefix('-') + .unwrap_or(class_name) + .starts_with(s) + }); + + if is_signable && is_valid_classname(class_name.strip_prefix('-').unwrap_or(class_name)) + || !is_signable && is_valid_classname(class_name) + { + Ok((input, ())) + } else { + Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))) + } +} + +fn is_ident_char(c: char) -> bool { + c.is_alphanumeric() || c == '_' || c == '-' +} + +fn is_lengthy_classname(class_name: &str) -> bool { + LENGTHY.contains(&class_name.strip_prefix('-').unwrap_or(class_name)) +} + +// Custom number parser that handles optional decimals and signs, and scientific notation +fn float_strict(input: &str) -> IResult<&str, f64> { + let (input, number) = recognize(tuple(( + opt(alt((tag("-"), tag("+")))), + digit1, + opt(preceded(tag("."), digit1)), + opt(tuple(( + alt((tag("e"), tag("E"))), + opt(alt((tag("-"), tag("+")))), + digit1, + ))), + )))(input)?; + + let float_val: f64 = number.parse().unwrap(); + Ok((input, float_val)) +} + +fn parse_length_unit(input: &str) -> IResult<&str, String> { + let (input, number) = float_strict(input)?; + let (input, unit) = { + // px|em|rem|%|cm|mm|in|pt|pc|vh|vw|vmin|vmax + alt(( + tag("px"), + tag("em"), + tag("rem"), + tag("%"), + tag("cm"), + tag("mm"), + tag("in"), + tag("pt"), + tag("pc"), + tag("vh"), + tag("vw"), + tag("vmin"), + tag("vmax"), + // TODO: Should i allow unitless values? Would need something like this in caller + // location if so: + // let (input, _) = alt((parse_length_unit, parse_number))(input)?; + tag(""), + )) + }(input)?; + Ok((input, format!("{}{}", number, unit))) +} + + // let is_arbitrary_property = word.starts_with('[') && word.ends_with(']'); + let last_word_signed = modifiers_and_class.clone().last().unwrap_or_default(); + let last_word_unsigned = last_word_signed + .strip_prefix('-') + .unwrap_or(last_word_signed); +// text-[22px] +fn lengthy_arbitrary_classname(input: &str) -> IResult<&str, ()> { + let (input, class_name) = take_until("-[")(input)?; + let (input, _) = if is_lengthy_classname(class_name) { + Ok((input, ())) + } else { + Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))) + }?; + + // arbitrary value + let (input, _) = tag("-")(input)?; + let (input, _) = tag("[")(input)?; + // is number + let (input, _) = parse_length_unit(input)?; + let (input, _) = tag("]")(input)?; + Ok((input, ())) +} + + (last_word_signed, last_word_unsigned) +// #bada55 +fn parse_hex_color(input: &str) -> IResult<&str, String> { + let (input, _) = tag("#")(input)?; + let (input, color) = take_while1(|c: char| c.is_ascii_hexdigit())(input)?; + let (input, _) = if color.chars().count() == 3 || color.chars().count() == 6 { + Ok((input, ())) + } else { + Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))) + }?; + let color = format!("#{}", color); + Ok((input, color)) +} + +fn is_valid_modifier(word: &str, modifiers: &[String]) -> bool { + let modifiers_and_class = word.split(':'); + let modifiers_from_word = modifiers_and_class + .clone() + .take(modifiers_and_class.count() - 1) + .collect::>(); + modifiers_from_word +fn parse_u8(input: &str) -> IResult<&str, u8> { + let (input, num) = number::complete::double(input)?; + let input = match num as u32 { + 0..=255 => input, + _ => { + return Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))) + } + }; + Ok((input, num as u8)) +} + +// rgb(255, 255, 255) rgb(255_255_255) +fn parse_rgb_color(input: &str) -> IResult<&str, String> { + let (input, _) = tag("rgb(")(input)?; + let (input, r) = parse_u8(input)?; + let (input, _) = alt((tag(","), tag("_")))(input)?; + let (input, g) = parse_u8(input)?; + let (input, _) = alt((tag(","), tag("_")))(input)?; + let (input, b) = parse_u8(input)?; + let (input, _) = tag(")")(input)?; + let color = format!("rgb({}, {}, {})", r, g, b); + Ok((input, color)) +} + +// rgba(255, 255, 255, 0.5) rgba(255_255_255_0.5) +fn parse_rgba_color(input: &str) -> IResult<&str, String> { + let (input, _) = tag("rgba(")(input)?; + let (input, r) = parse_u8(input)?; + let (input, _) = alt((tag(","), tag("_")))(input)?; + let (input, g) = parse_u8(input)?; + let (input, _) = alt((tag(","), tag("_")))(input)?; + let (input, b) = parse_u8(input)?; + let (input, _) = alt((tag(","), tag("_")))(input)?; + let (input, a) = number::complete::double(input)?; + let (input, _) = tag(")")(input)?; + let color = format!("rgba({}, {}, {}, {})", r, g, b, a); + Ok((input, color)) +} + +fn is_colorful_baseclass(class_name: &str) -> bool { + COLORFUL_BASECLASSES.contains(&class_name) +} + +// text-[#bada55] +fn colorful_arbitrary_baseclass(input: &str) -> IResult<&str, ()> { + let (input, class_name) = take_until("-[")(input)?; + let (input, _) = if is_colorful_baseclass(class_name) { + Ok((input, ())) + } else { + Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))) + }?; + + // arbitrary value + let (input, _) = tag("-")(input)?; + let (input, _) = tag("[")(input)?; + let (input, _) = alt((parse_hex_color, parse_rgb_color, parse_rgba_color))(input)?; + let (input, _) = tag("]")(input)?; + Ok((input, ())) +} + +// e.g: [mask-type:alpha] +fn kv_pair_classname(input: &str) -> IResult<&str, ()> { + let (input, _) = tag("[")(input)?; + let (input, _) = take_while1(is_ident_char)(input)?; + let (input, _) = tag(":")(input)?; + let (input, _) = take_until("]")(input)?; + let (input, _) = tag("]")(input)?; + Ok((input, ())) +} + +// before:content-['Festivus'] +fn arbitrary_content(input: &str) -> IResult<&str, ()> { + let (input, _) = tag("content-['")(input)?; + let (input, _) = take_until("']")(input)?; + let (input, _) = tag("']")(input)?; + Ok((input, ())) +} + +// content-[>] content-[<] +fn arbitrary_with_arrow(input: &str) -> IResult<&str, ()> { + let (input, _) = take_while1(is_ident_char)(input)?; + let (input, _) = tag("[")(input)?; + let (input, _) = alt((tag(">"), tag("<")))(input)?; + let (input, _) = take_until("]")(input)?; + let (input, _) = tag("]")(input)?; + Ok((input, ())) +} + +// bg-black/25 +fn predefined_colorful_opacity(input: &str) -> IResult<&str, ()> { + let input = if COLORFUL_BASECLASSES + .iter() + .all(|modifier| modifiers.contains(&modifier.to_string())) +} + +fn is_valid_opacity(last_word_unsigned: &str, valid_class_names: &[String]) -> bool { + let is_valid_opacity = { + let (class_name, opacity_raw) = last_word_unsigned.split_once('/').unwrap_or_default(); + let opacity_arb = opacity_raw + .trim_start_matches('[') + .trim_end_matches(']') + .parse::(); + let is_valid_number = + opacity_arb.is_ok_and(|opacity_num| (0.0..=100.0).contains(&opacity_num)); + valid_class_names.contains(&class_name.to_string()) && is_valid_number + .any(|cb| input.trim().starts_with(cb)) + { + input + } else { + return Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))); + }; + is_valid_opacity +} + +fn has_arb_variant(word: &str) -> bool { + // lg:[&:nth-child(3)]:hover:underline + // [&_p]:mt-4 + // flex [@supports(display:grid)]:grid + // [@media(any-hover:hover){&:hover}]:opacity-100 + let has_arb_variant = { + // lg:[&:nth-child(3)]:hover:underline => :nth-child(3) + // [&_p]:mt-4 => _p + let mut ampersand_variant_selector = + word.split("[@").last().unwrap_or_default().split("]:"); + let and_variant_selector = word.split("[&").last().unwrap_or_default().split("]:"); + let is_valid_arbitrary_variant_selector = ampersand_variant_selector.clone().count() >= 2 + && !ampersand_variant_selector + .next() + .unwrap_or_default() + .is_empty(); + let is_valid_arbitrary_variant_queries = and_variant_selector.clone().count() >= 2 + && !and_variant_selector + .clone() + .last() + .unwrap_or_default() + .split("]:") + .next() + .unwrap_or_default() + .is_empty(); + let is_query = word.starts_with("[@"); + + is_valid_arbitrary_variant_selector || is_valid_arbitrary_variant_queries || is_query + // && + // ((!is_query && !word.split("[&").next().unwrap_or_default().is_empty() && word.split(":[&").count() >= 2) || is_query) + let (input, _) = take_while1(|char| is_ident_char(char) && char != '/')(input)?; + // let (input, _) = take_until("/")(input)?; + let (input, _) = tag("/")(input)?; + + let (input, num) = number::complete::double(input)?; + let input = match num as u8 { + 0..=100 => input, + _ => { + return Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))) + } + }; + has_arb_variant +} + +fn is_valid_negative_baseclass( + valid_class_names: &[String], + last_word_unsigned: &str, + last_word_signed: &str, + is_valid_arb_prop: bool, +) -> bool { + let is_valid_negative_baseclass = { + // tw!("-m-4 p-4 p-4"); + (valid_class_names.contains(&last_word_unsigned.to_string()) + && last_word_signed.starts_with('-') + && SIGNABLES + .iter() + .any(|s| (last_word_unsigned.starts_with(s)))) + || (is_valid_arb_prop + && last_word_signed.starts_with('-') + && SIGNABLES.iter().any(|s| last_word_unsigned.starts_with(s))) + + Ok((input, ())) +} + +// bg-black/[27] bg-black/[27%] +fn arbitrary_opacity(input: &str) -> IResult<&str, ()> { + let input = if COLORFUL_BASECLASSES + .iter() + .any(|cb| input.trim().starts_with(cb)) + { + input + } else { + return Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))); + }; + is_valid_negative_baseclass +} + +fn is_valid_class( + is_valid_arb_prop: bool, + valid_class_names: &[String], + last_word_unsigned: &str, +) -> bool { + !is_valid_arb_prop && valid_class_names.contains(&last_word_unsigned.to_string()) +} + +fn is_valid_arb_prop(word: &str, modifiers: &[String]) -> bool { + // TODO: check the first and the last character are not open and close brackets + // respectively i.e arbitrary property e.g [mask_type:aplha]; + // hover:[mask-type:alpha]; + let mut word_for_arb_prop = word.split(":["); + + word_for_arb_prop + .next() + // e.g for hover:[mask-type:alpha], this will be hover, + // for [mask-type:alpha], this will be [mask-type:alpha] + .is_some_and(|modifiers_or_full_arb_prop| { + let is_arbitrary_property = modifiers_or_full_arb_prop.starts_with('[') && modifiers_or_full_arb_prop.ends_with(']'); + + let is_valid = if is_arbitrary_property { + modifiers_or_full_arb_prop.matches('[').count() == 1 && + modifiers_or_full_arb_prop.matches(']').count() == 1 && + modifiers_or_full_arb_prop + .trim_start_matches('[') + .trim_end_matches(']') + .split(':') + .count() == 2 + } else { + // e.g mask-type:alpha] in hover:[mask-type:alpha] + let full_arb_prop = word_for_arb_prop.next().unwrap_or_default(); + // e.g for single, hover in hover:[mask-type:alpha] + // for multiple, hover:first:last, in hover:first:last:[mask-type:alpha] + modifiers_or_full_arb_prop + .split(':') + .all(|modifier| modifiers.contains(&modifier.to_string())) && + full_arb_prop.matches(']').count() == 1 && + full_arb_prop + .trim_end_matches(']') + .split(':') + .count() == 2 + + }; + is_valid + }) + || + // value e.g [mask-type:alpha] in hover:[mask-type:alpha] + // potential addition checks(probably not a good idea. Imagine a new css property, we would + // have to open a PR for every new or esoteric css property.) + word_for_arb_prop.next().is_some_and(|value| { + value.ends_with(']') + && value.split(':').count() == 2 + // We had already split by ":[", so there should be no "[" anymore + && value.matches('[').count() == 0 + && value.matches(']').count() == 1 + }) +} + +fn is_valid_group_pattern(modifier: &str, valid_modifiers: &[String]) -> bool { + let parts: Vec<&str> = modifier.split('/').collect(); + let group_modifier = parts[0]; + parts.len() == 2 + && valid_modifiers.contains(&group_modifier.to_string()) + && group_modifier.starts_with("group") +} + +// tw!("group/edit invisible hover:bg-slate-200 group-hover/item:visible"); +// tw!("group-[:nth-of-type(3)_&]:block group-hover/edit:text-gray-700 group-[:nth-of-type(3)_&]:block"); +fn is_validate_modifier_or_group( + word: &str, + valid_modifiers: &[String], + valid_class_names: &[String], +) -> bool { + let valid_arb_group = word.split(':').collect::>(); + let modifiers = &valid_arb_group[..valid_arb_group.len() - 1]; + let last_word = valid_arb_group.last().unwrap_or(&""); + let is_valid_last_word = + is_valid_string(last_word) && valid_class_names.contains(&last_word.to_string()); + + for modifier in modifiers { + if modifier.starts_with("group") { + if !is_valid_group_pattern(modifier, valid_modifiers) && is_valid_last_word { + return false; + } + } else if !valid_modifiers.contains(&modifier.to_string()) && is_valid_last_word { + return false; + let (input, _) = take_while1(|char| is_ident_char(char) && char != '/')(input)?; + let (input, _) = tag("/")(input)?; + let (input, _) = tag("[")(input)?; + // 0-100 integer + let (input, num) = number::complete::double(input)?; + let input = match num as u8 { + 0..=100 => input, + _ => { + return Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))) + } + }; + let (input, _) = opt(tag("%"))(input)?; + let (input, _) = tag("]")(input)?; + Ok((input, ())) +} + +// bg-[url('/img/down-arrow.svg')] +fn bg_arbitrary_url(input: &str) -> IResult<&str, ()> { + // prefixed by baseclass + let input = if COLORFUL_BASECLASSES + .iter() + .any(|cb| input.trim().starts_with(cb)) + { + input + } else { + return Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))); + }; + let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; + let (input, _) = tag("[")(input)?; + let (input, _) = tag("url('")(input)?; + let (input, _) = take_until("')")(input)?; + let (input, _) = tag("')")(input)?; + let (input, _) = tag("]")(input)?; + Ok((input, ())) +} + +// grid-cols-[fit-content(theme(spacing.32))] +fn arbitrary_css_value(input: &str) -> IResult<&str, ()> { + // is prefixed by valid base class + // take until -[ + let (input, base_class) = take_until("-[")(input)?; + let input = if VALID_BASECLASS_NAMES + .iter() + .any(|cb| base_class.trim().eq(*cb)) + { + input + } else { + return Err(nom::Err::Error(nom::error::Error::new( + base_class, + nom::error::ErrorKind::Tag, + ))); + }; + let (input, _) = tag("-[")(input)?; + let (input, _) = not(alt(( + tag("--"), + tag("var(--"), + // :var(-- + )))(input)?; + let (input, _) = take_while1(|char| is_ident_char(char) && char != '(')(input)?; + let (input, _) = tag("(")(input)?; + let (input, _) = take_until(")]")(input)?; + + // allow anything inthe brackets + let (input, _) = take_until("]")(input)?; + let (input, _) = tag("]")(input)?; + Ok((input, ())) +} + +// bg-[--my-color] +fn arbitrary_css_var(input: &str) -> IResult<&str, ()> { + // is prefixed by valid base class + let input = if VALID_BASECLASS_NAMES + .iter() + .any(|cb| input.trim().starts_with(cb)) + { + input + } else { + return Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))); + }; + let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; + let (input, _) = tag("[")(input)?; + let (input, _) = tag("--")(input)?; + let (input, _) = take_while1(|char| is_ident_char(char) && char != ']')(input)?; + let (input, _) = tag("]")(input)?; + Ok((input, ())) +} +// text-[var(--my-var)] +fn arbitrary_css_var2(input: &str) -> IResult<&str, ()> { + // is prefixed by valid base class + let input = if VALID_BASECLASS_NAMES + .iter() + .any(|cb| input.trim().starts_with(cb)) + { + input + } else { + return Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))); + }; + let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; + let (input, _) = tag("[")(input)?; + let (input, _) = tag("var(--")(input)?; + let (input, _) = take_while1(|char| is_ident_char(char) && char != ')')(input)?; + let (input, _) = tag(")]")(input)?; + Ok((input, ())) +} + +// text-[length:var(--my-var)] +fn arbitrary_css_var3(input: &str) -> IResult<&str, ()> { + // is prefixed by valid base class + let input = if VALID_BASECLASS_NAMES + .iter() + .any(|cb| input.trim().starts_with(cb)) + { + input + } else { + return Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))); + }; + let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; + let (input, _) = tag("[")(input)?; + let (input, _) = take_while1(|char| is_ident_char(char) && char != ':')(input)?; + let (input, _) = tag(":")(input)?; + let (input, _) = tag("var(--")(input)?; + let (input, _) = take_while1(|char| is_ident_char(char) && char != ')')(input)?; + let (input, _) = tag(")]")(input)?; + Ok((input, ())) +} + +// group/edit +fn arbitrary_group_classname(input: &str) -> IResult<&str, ()> { + let (input, _) = alt((tag("group"),))(input)?; + let (input, _) = tag("/")(input)?; + let (input, _) = take_while1(is_ident_char)(input)?; + Ok((input, ())) +} + +fn parse_single_tw_classname(input: &str) -> IResult<&str, ()> { + alt(( + // bg-[url('/what_a_rush.png')] + bg_arbitrary_url, + // bg-black/25 + predefined_colorful_opacity, + // group/edit + arbitrary_group_classname, + // bg-black/[27] + arbitrary_opacity, + // btn + parse_predefined_tw_classname, + // [mask-type:luminance] [mask-type:alpha] + kv_pair_classname, + // text-[22px] + lengthy_arbitrary_classname, + // text-[#bada55] + colorful_arbitrary_baseclass, + // before:content-['Festivus'] + arbitrary_content, + // content-[>] content-[<] + arbitrary_with_arrow, + // bg-[--my-color] + arbitrary_css_var, + // text-[var(--my-var)] + arbitrary_css_var2, + // text-[length:var(--my-var)] + arbitrary_css_var3, + // grid-cols-[fit-content(theme(spacing.32))] + arbitrary_css_value, + ))(input) +} + +// hover:underline +fn predefined_modifier(input: &str) -> IResult<&str, ()> { + let (input, modifier) = recognize(|i| { + // Assuming a Tailwind class consists of alphanumeric, dashes, and colons + nom::bytes::complete::is_a( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-", + )(i) + })(input)?; + + if is_valid_modifier(modifier) { + Ok((input, ())) + } else { + Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))) + } +} + +// predefined special modifiers e.g peer-checked:p-4 group-hover:visible +fn predefined_special_modifier(input: &str) -> IResult<&str, ()> { + let (input, _) = alt(( + // peer-checked:p-4 + tuple((tag("peer-"), predefined_modifier)), + // group-hover:visible + tuple((tag("group-"), predefined_modifier)), + ))(input)?; + Ok((input, ())) +} + +// [&:nth-child(3)]:underline +// [&_p]:mt-4 +fn arbitrary_front_selector_modifier(input: &str) -> IResult<&str, ()> { + let (input, _) = tag("[&")(input)?; + let (input, _) = take_until("]")(input)?; + let (input, _) = tag("]")(input)?; + Ok((input, ())) +} + +// group-[:nth-of-type(3)_&]:block +fn arbitrary_back_selector_modifier(input: &str) -> IResult<&str, ()> { + let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; + let (input, _) = tag("-[")(input)?; + let (input, _) = take_until("&]")(input)?; + let (input, _) = tag("&]")(input)?; + Ok((input, ())) +} + +// [@supports(display:grid)]:grid +fn arbitrary_at_supports_rule_modifier(input: &str) -> IResult<&str, ()> { + let (input, _) = tag("[@supports(")(input)?; + let (input, _) = take_until(")")(input)?; + let (input, _) = tag(")]")(input)?; + Ok((input, ())) +} + +// [@media(any-hover:hover){&:hover}]:opacity-100 +fn arbitrary_at_media_rule_modifier(input: &str) -> IResult<&str, ()> { + // starts with [@media and ends with ] + let (input, _) = tag("[@media(")(input)?; + let (input, _) = take_until("]")(input)?; + let (input, _) = tag("]")(input)?; + Ok((input, ())) +} + +// group/edit invisible hover:bg-slate-200 group-hover/item:visible +fn group_peer_modifier(input: &str) -> IResult<&str, ()> { + let (input, _) = alt(( + tuple((tag("group-"), predefined_modifier)), + // https://tailwindcss.com/docs/hover-focus-and-other-states#differentiating-peers + // peer-checked/published:text-sky-500 + tuple((tag("peer-"), predefined_modifier)), + ))(input)?; + let (input, _) = tag("/")(input)?; + let (input, _) = take_while1(is_ident_char)(input)?; + Ok((input, ())) +} + +// hidden group-[.is-published]:block +// group-[:nth-of-type(3)_&]:block +// peer-[.is-dirty]:peer-required:block hidden +// hidden peer-[:nth-of-type(3)_&]:block +fn group_modifier_selector(input: &str) -> IResult<&str, ()> { + let (input, _) = alt((tag("group"), tag("peer")))(input)?; + let (input, _) = tag("-[")(input)?; + let (input, _) = take_until("]")(input)?; + let (input, _) = tag("]")(input)?; + Ok((input, ())) +} + + is_valid_last_word +// supports-[backdrop-filter] +fn supports_arbitrary(input: &str) -> IResult<&str, ()> { + let (input, _) = tag("supports-[")(input)?; + let (input, _) = take_until("]")(input)?; + let (input, _) = tag("]")(input)?; + Ok((input, ())) +} + +fn is_valid_group_classname(class_name: &str) -> bool { + !class_name.contains(':') + && !class_name.contains('[') + && !class_name.contains(']') + && class_name.starts_with("group/") +// aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] +// aria-[sort=descending]:bg-[url('/img/up-arrow.svg')] +fn aria_arbitrary(input: &str) -> IResult<&str, ()> { + let (input, _) = opt(tag("group-"))(input)?; + let (input, _) = tag("aria-[")(input)?; + let (input, _) = take_while1(is_ident_char)(input)?; + let (input, _) = tag("=")(input)?; + let (input, _) = take_while1(is_ident_char)(input)?; + let (input, _) = tag("]")(input)?; + Ok((input, ())) +} + +fn is_valid_string(s: &str) -> bool { + // Matches strings that contain only alphanumeric characters, underscores, and hyphens. + let re = Regex::new(r"^[a-zA-Z0-9_-]*$").expect("Invalid regex"); + re.is_match(s) && !s.is_empty() +// data-[size=large]:p-8 +fn data_arbitrary(input: &str) -> IResult<&str, ()> { + let (input, _) = tag("data-[")(input)?; + let (input, _) = take_while1(is_ident_char)(input)?; + let (input, _) = tag("=")(input)?; + let (input, _) = take_while1(is_ident_char)(input)?; + let (input, _) = tag("]")(input)?; + Ok((input, ())) +} + +// min-[320px]:text-center max-[600px]:bg-sky-300 +fn min_max_arbitrary_modifier(input: &str) -> IResult<&str, ()> { + let (input, _) = alt((tag("min-"), tag("max-")))(input)?; + let (input, _) = tag("[")(input)?; + let (input, _) = parse_length_unit(input)?; + let (input, _) = tag("]")(input)?; + Ok((input, ())) +} + +fn modifier(input: &str) -> IResult<&str, ()> { + alt(( + group_modifier_selector, + group_peer_modifier, + predefined_special_modifier, + arbitrary_front_selector_modifier, + arbitrary_back_selector_modifier, + arbitrary_at_supports_rule_modifier, + arbitrary_at_media_rule_modifier, + predefined_modifier, + supports_arbitrary, + aria_arbitrary, + data_arbitrary, + min_max_arbitrary_modifier, + ))(input) +} + +fn modifiers_chained(input: &str) -> IResult<&str, ()> { + let (input, _modifiers) = separated_list0(tag(":"), modifier)(input)?; + Ok((input, ())) +} + +fn parse_tw_full_classname(input: &str) -> IResult<&str, Vec<&str>> { + let (input, _class_names) = tuple(( + opt(tuple((modifiers_chained, tag(":")))), + parse_single_tw_classname, + ))(input)?; + + Ok((input, vec![])) +} + +// Edge cases +// [&:nth-child(3)]:underline +// lg:[&:nth-child(3)]:hover:underline +// [&_p]:mt-4 +// flex [@supports(display:grid)]:grid +// [@media(any-hover:hover){&:hover}]:opacity-100 +// group/edit invisible hover:bg-slate-200 group-hover/item:visible +// hidden group-[.is-published]:block +// group-[:nth-of-type(3)_&]:block +// peer-checked/published:text-sky-500 +// peer-[.is-dirty]:peer-required:block hidden +// hidden peer-[:nth-of-type(3)_&]:block +// after:content-['*'] after:ml-0.5 after:text-red-500 block text-sm font-medium text-slate-700 +// before:content-[''] before:block +// bg-black/75 supports-[backdrop-filter]:bg-black/25 supports-[backdrop-filter]:backdrop-blur +// aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg-[url('/img/up-arrow.svg')] +// group-aria-[sort=ascending]:rotate-0 group-aria-[sort=descending]:rotate-180 +// data-[size=large]:p-8 +// open:bg-white dark:open:bg-slate-900 open:ring-1 open:ring-black/5 dark:open:ring-white/10 open:shadow-lg p-6 rounded-lg +// lg:[&:nth-child(3)]:hover:underline +// min-[320px]:text-center max-[600px]:bg-sky-300 +// top-[117px] lg:top-[344px] +// bg-[#bada55] text-[22px] before:content-['Festivus'] +// grid grid-cols-[fit-content(theme(spacing.32))] +// bg-[--my-color] +// [mask-type:luminance] hover:[mask-type:alpha] +// [--scroll-offset:56px] lg:[--scroll-offset:44px] +// lg:[&:nth-child(3)]:hover:underline +// bg-[url('/what_a_rush.png')] +// before:content-['hello\_world'] +// text-[22px] +// text-[#bada55] +// text-[var(--my-var)] +// text-[length:var(--my-var)] +// text-[color:var(--my-var)] +fn parse_class_names(input: &str) -> IResult<&str, Vec<&str>> { + let (input, _) = multispace0(input)?; + let (input, _class_names) = separated_list0(multispace1, parse_tw_full_classname)(input)?; + let (input, _) = multispace0(input)?; + + Ok((input, vec![])) +} + +fn parse_top(input: &str) -> IResult<&str, Vec<&str>> { + all_consuming(parse_class_names)(input) +} + +#[proc_macro] +pub fn tw(raw_input: TokenStream) -> TokenStream { + let r_input = raw_input.clone(); + let input = parse_macro_input!(r_input as LitStr); + let (_modifiers, _valid_class_names) = match setup(&input) { + Ok(value) => value, + Err(value) => { + return syn::Error::new_spanned(input, value) + .to_compile_error() + .into() + } + }; + let full_classnames = input.value(); + + let (input, _class_names) = match parse_top(&full_classnames) { + Ok(value) => value, + Err(value) => { + return syn::Error::new_spanned(input, value) + .to_compile_error() + .into() + } + }; + + quote::quote! { + #input + } + .into() +} + 24 changes: 24 additions & 0 deletions24 +tw-macro/src/tailwind/colorful.rs +@@ -0,0 +1,24 @@ +pub const COLORFUL_BASECLASSES: [&str; 22] = [ + "text", + "bg", + "border", + "border-x", + "border-y", + "border-s", + "border-e", + "border-t", + "border-r", + "border-b", + "border-l", + "divide", + "outline", + "ring", + "ring-offset", + "shadow", + "caret", + "accent", + "fill", + "stroke", + "placeholder", + "decoration", +]; + 1 change: 1 addition & 0 deletions1 +tw-macro/src/tailwind/mod.rs +@@ -4,6 +4,7 @@ + * Copyright (c) 2023 Oyelowo Oyedayo + * Licensed under the MIT license + */ +pub mod colorful; +pub mod default_classnames; +pub mod lengthy; +pub mod modifiers; + 3 changes: 3 additions & 0 deletions3 +tw-macro/src/tailwind/signable.rs +@@ -45,4 +45,7 @@ pub const SIGNABLES: [&str; 40] = [ + "grid-auto-columns", + "z", + "order", + // "scroll-mx", + // "scroll-my", + // "scroll-m", +]; +Footer +© 2023 GitHub, Inc. +Footer navigation +Terms +Privacy +Security +Status +Docs +Contact GitHub +Pricing +API +Training +Blog +About +@@ -4,116 +4,31 @@ * Copyright (c) 2023 Oyelowo Oyedayo * Licensed under the MIT license */ use nom::{ branch::alt, bytes::complete::{tag, take_until, take_while1}, character::complete::{digit1, multispace0, multispace1}, combinator::{all_consuming, not, opt, recognize}, multi::separated_list0, number, sequence::{preceded, tuple}, IResult, }; use syn::{parse_macro_input, LitStr}; mod config; mod plugins; mod tailwind; use tailwind::{ lengthy::LENGTHY, modifiers::get_modifiers, tailwind_config::CustomisableClasses, valid_baseclass_names::VALID_BASECLASS_NAMES, colorful::COLORFUL_BASECLASSES, lengthy::LENGTHY, modifiers::get_modifiers, tailwind_config::CustomisableClasses, valid_baseclass_names::VALID_BASECLASS_NAMES, }; use config::{get_classes, noconfig::UNCONFIGURABLE, read_tailwind_config}; use proc_macro::TokenStream; use regex::{self, Regex}; use tailwind::signable::SIGNABLES; // use tailwindcss_core::parser::{Extractor, ExtractorOptions}; #[proc_macro] pub fn tw(raw_input: TokenStream) -> TokenStream { let r_input = raw_input.clone(); let input = parse_macro_input!(r_input as LitStr); let (modifiers, valid_class_names) = match setup(&input) { Ok(value) => value, Err(value) => { return syn::Error::new_spanned(input, value) .to_compile_error() .into() } }; for word in input.value().split_whitespace() { let (last_word_signed, last_word_unsigned) = get_last_word_types(word); // modifiers e.g hover: in // hover:[mask-type:alpha] let is_valid_arb_prop = is_valid_arb_prop(word, &modifiers); let is_valid_class = is_valid_class(is_valid_arb_prop, &valid_class_names, last_word_unsigned); let (base_classname, arbitrary_value_with_bracket) = last_word_unsigned.split_once("-[").unwrap_or_default(); let is_valid_negative_baseclass = is_valid_negative_baseclass( &valid_class_names, last_word_unsigned, last_word_signed, is_valid_arb_prop, ); let prefix_is_valid_tailwind_keyword = VALID_BASECLASS_NAMES.contains(&base_classname); let is_arbitrary_value = prefix_is_valid_tailwind_keyword && arbitrary_value_with_bracket.ends_with(']'); let arbitrary_value = arbitrary_value_with_bracket.trim_end_matches(']'); let is_lengthy_class = LENGTHY.contains(&base_classname); let is_valid_length = is_arbitrary_value && is_lengthy_class && (is_valid_length(arbitrary_value) || is_valid_calc(arbitrary_value)); let has_arb_variant = has_arb_variant(word); let is_valid_opacity = is_valid_opacity(last_word_unsigned, &valid_class_names); if (is_valid_class && is_valid_modifier(word, &modifiers)) || is_valid_negative_baseclass || (!is_lengthy_class && is_arbitrary_value) || is_valid_length || is_valid_arb_prop || has_arb_variant || is_valid_opacity || is_valid_group_classname(last_word_unsigned) || is_validate_modifier_or_group(word, &modifiers, &valid_class_names) { // if check_word(word, false).is_empty() { // return syn::Error::new_spanned(input, format!("Invalid string: {}", word)) // .to_compile_error() // .into(); // } } else { return syn::Error::new_spanned(input, format!("Invalid string: {word}")) .to_compile_error() .into(); } } raw_input } // fn check_word(input: &str, loose: bool) -> Vec<&str> { // Extractor::unique_ord( // input.as_bytes(), // ExtractorOptions { // preserve_spaces_in_arbitrary: loose, // }, // ) // .into_iter() // .map(|s| unsafe { std::str::from_utf8_unchecked(s) }) // .collect() // } fn is_valid_length(value: &str) -> bool { let re = regex::Regex::new(r"^(-?\d+(\.?\d+)?(px|em|rem|%|cm|mm|in|pt|pc|vh|vw|vmin|vmax)|0)$") .expect("Invalid regex"); re.is_match(value) } fn is_valid_calc(value: &str) -> bool { let re = regex::Regex::new(r"^calc\([^)]+\)$").expect("Invalid regex"); re.is_match(value) } fn setup(input: &LitStr) -> Result<(Vec, Vec), TokenStream> { let config = &(match read_tailwind_config() { Ok(config) => config, @@ -146,199 +61,686 @@ fn setup(input: &LitStr) -> Result<(Vec, Vec), TokenStream> { Ok((modifiers, valid_class_names)) } fn get_last_word_types(word: &str) -> (&str, &str) { let modifiers_and_class = word.split(':'); fn get_classes_straight() -> Vec { get_classes(&read_tailwind_config().unwrap()) } fn is_valid_classname(class_name: &str) -> bool { get_classes_straight().contains(&class_name.to_string()) } fn is_valid_modifier(modifier: &str) -> bool { get_modifiers(&read_tailwind_config().unwrap()).contains(&modifier.to_string()) } fn parse_predefined_tw_classname(input: &str) -> IResult<&str, ()> { let (input, class_name) = recognize(|i| { // Considering a Tailwind class consists of alphanumeric, dashes, and slash nom::bytes::complete::is_a( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-./", )(i) })(input)?; let is_signable = SIGNABLES.iter().any(|s| { class_name .strip_prefix('-') .unwrap_or(class_name) .starts_with(s) }); if is_signable && is_valid_classname(class_name.strip_prefix('-').unwrap_or(class_name)) || !is_signable && is_valid_classname(class_name) { Ok((input, ())) } else { Err(nom::Err::Error(nom::error::Error::new( input, nom::error::ErrorKind::Tag, ))) } } fn is_ident_char(c: char) -> bool { c.is_alphanumeric() || c == '_' || c == '-' } fn is_lengthy_classname(class_name: &str) -> bool { LENGTHY.contains(&class_name.strip_prefix('-').unwrap_or(class_name)) } // Custom number parser that handles optional decimals and signs, and scientific notation fn float_strict(input: &str) -> IResult<&str, f64> { let (input, number) = recognize(tuple(( opt(alt((tag("-"), tag("+")))), digit1, opt(preceded(tag("."), digit1)), opt(tuple(( alt((tag("e"), tag("E"))), opt(alt((tag("-"), tag("+")))), digit1, ))), )))(input)?; let float_val: f64 = number.parse().unwrap(); Ok((input, float_val)) } fn parse_length_unit(input: &str) -> IResult<&str, String> { let (input, number) = float_strict(input)?; let (input, unit) = { // px|em|rem|%|cm|mm|in|pt|pc|vh|vw|vmin|vmax alt(( tag("px"), tag("em"), tag("rem"), tag("%"), tag("cm"), tag("mm"), tag("in"), tag("pt"), tag("pc"), tag("vh"), tag("vw"), tag("vmin"), tag("vmax"), // TODO: Should i allow unitless values? Would need something like this in caller // location if so: // let (input, _) = alt((parse_length_unit, parse_number))(input)?; tag(""), )) }(input)?; Ok((input, format!("{}{}", number, unit))) } // let is_arbitrary_property = word.starts_with('[') && word.ends_with(']'); let last_word_signed = modifiers_and_class.clone().last().unwrap_or_default(); let last_word_unsigned = last_word_signed .strip_prefix('-') .unwrap_or(last_word_signed); // text-[22px] fn lengthy_arbitrary_classname(input: &str) -> IResult<&str, ()> { let (input, class_name) = take_until("-[")(input)?; let (input, _) = if is_lengthy_classname(class_name) { Ok((input, ())) } else { Err(nom::Err::Error(nom::error::Error::new( input, nom::error::ErrorKind::Tag, ))) }?; // arbitrary value let (input, _) = tag("-")(input)?; let (input, _) = tag("[")(input)?; // is number let (input, _) = parse_length_unit(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } (last_word_signed, last_word_unsigned) // #bada55 fn parse_hex_color(input: &str) -> IResult<&str, String> { let (input, _) = tag("#")(input)?; let (input, color) = take_while1(|c: char| c.is_ascii_hexdigit())(input)?; let (input, _) = if color.chars().count() == 3 || color.chars().count() == 6 { Ok((input, ())) } else { Err(nom::Err::Error(nom::error::Error::new( input, nom::error::ErrorKind::Tag, ))) }?; let color = format!("#{}", color); Ok((input, color)) } fn is_valid_modifier(word: &str, modifiers: &[String]) -> bool { let modifiers_and_class = word.split(':'); let modifiers_from_word = modifiers_and_class .clone() .take(modifiers_and_class.count() - 1) .collect::>(); modifiers_from_word fn parse_u8(input: &str) -> IResult<&str, u8> { let (input, num) = number::complete::double(input)?; let input = match num as u32 { 0..=255 => input, _ => { return Err(nom::Err::Error(nom::error::Error::new( input, nom::error::ErrorKind::Tag, ))) } }; Ok((input, num as u8)) } // rgb(255, 255, 255) rgb(255_255_255) fn parse_rgb_color(input: &str) -> IResult<&str, String> { let (input, _) = tag("rgb(")(input)?; let (input, r) = parse_u8(input)?; let (input, _) = alt((tag(","), tag("_")))(input)?; let (input, g) = parse_u8(input)?; let (input, _) = alt((tag(","), tag("_")))(input)?; let (input, b) = parse_u8(input)?; let (input, _) = tag(")")(input)?; let color = format!("rgb({}, {}, {})", r, g, b); Ok((input, color)) } // rgba(255, 255, 255, 0.5) rgba(255_255_255_0.5) fn parse_rgba_color(input: &str) -> IResult<&str, String> { let (input, _) = tag("rgba(")(input)?; let (input, r) = parse_u8(input)?; let (input, _) = alt((tag(","), tag("_")))(input)?; let (input, g) = parse_u8(input)?; let (input, _) = alt((tag(","), tag("_")))(input)?; let (input, b) = parse_u8(input)?; let (input, _) = alt((tag(","), tag("_")))(input)?; let (input, a) = number::complete::double(input)?; let (input, _) = tag(")")(input)?; let color = format!("rgba({}, {}, {}, {})", r, g, b, a); Ok((input, color)) } fn is_colorful_baseclass(class_name: &str) -> bool { COLORFUL_BASECLASSES.contains(&class_name) } // text-[#bada55] fn colorful_arbitrary_baseclass(input: &str) -> IResult<&str, ()> { let (input, class_name) = take_until("-[")(input)?; let (input, _) = if is_colorful_baseclass(class_name) { Ok((input, ())) } else { Err(nom::Err::Error(nom::error::Error::new( input, nom::error::ErrorKind::Tag, ))) }?; // arbitrary value let (input, _) = tag("-")(input)?; let (input, _) = tag("[")(input)?; let (input, _) = alt((parse_hex_color, parse_rgb_color, parse_rgba_color))(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } // e.g: [mask-type:alpha] fn kv_pair_classname(input: &str) -> IResult<&str, ()> { let (input, _) = tag("[")(input)?; let (input, _) = take_while1(is_ident_char)(input)?; let (input, _) = tag(":")(input)?; let (input, _) = take_until("]")(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } // before:content-['Festivus'] fn arbitrary_content(input: &str) -> IResult<&str, ()> { let (input, _) = tag("content-['")(input)?; let (input, _) = take_until("']")(input)?; let (input, _) = tag("']")(input)?; Ok((input, ())) } // content-[>] content-[<] fn arbitrary_with_arrow(input: &str) -> IResult<&str, ()> { let (input, _) = take_while1(is_ident_char)(input)?; let (input, _) = tag("[")(input)?; let (input, _) = alt((tag(">"), tag("<")))(input)?; let (input, _) = take_until("]")(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } // bg-black/25 fn predefined_colorful_opacity(input: &str) -> IResult<&str, ()> { let input = if COLORFUL_BASECLASSES .iter() .all(|modifier| modifiers.contains(&modifier.to_string())) } fn is_valid_opacity(last_word_unsigned: &str, valid_class_names: &[String]) -> bool { let is_valid_opacity = { let (class_name, opacity_raw) = last_word_unsigned.split_once('/').unwrap_or_default(); let opacity_arb = opacity_raw .trim_start_matches('[') .trim_end_matches(']') .parse::(); let is_valid_number = opacity_arb.is_ok_and(|opacity_num| (0.0..=100.0).contains(&opacity_num)); valid_class_names.contains(&class_name.to_string()) && is_valid_number .any(|cb| input.trim().starts_with(cb)) { input } else { return Err(nom::Err::Error(nom::error::Error::new( input, nom::error::ErrorKind::Tag, ))); }; is_valid_opacity } fn has_arb_variant(word: &str) -> bool { // lg:[&:nth-child(3)]:hover:underline // [&_p]:mt-4 // flex [@supports(display:grid)]:grid // [@media(any-hover:hover){&:hover}]:opacity-100 let has_arb_variant = { // lg:[&:nth-child(3)]:hover:underline => :nth-child(3) // [&_p]:mt-4 => _p let mut ampersand_variant_selector = word.split("[@").last().unwrap_or_default().split("]:"); let and_variant_selector = word.split("[&").last().unwrap_or_default().split("]:"); let is_valid_arbitrary_variant_selector = ampersand_variant_selector.clone().count() >= 2 && !ampersand_variant_selector .next() .unwrap_or_default() .is_empty(); let is_valid_arbitrary_variant_queries = and_variant_selector.clone().count() >= 2 && !and_variant_selector .clone() .last() .unwrap_or_default() .split("]:") .next() .unwrap_or_default() .is_empty(); let is_query = word.starts_with("[@"); is_valid_arbitrary_variant_selector || is_valid_arbitrary_variant_queries || is_query // && // ((!is_query && !word.split("[&").next().unwrap_or_default().is_empty() && word.split(":[&").count() >= 2) || is_query) let (input, _) = take_while1(|char| is_ident_char(char) && char != '/')(input)?; // let (input, _) = take_until("/")(input)?; let (input, _) = tag("/")(input)?; let (input, num) = number::complete::double(input)?; let input = match num as u8 { 0..=100 => input, _ => { return Err(nom::Err::Error(nom::error::Error::new( input, nom::error::ErrorKind::Tag, ))) } }; has_arb_variant } fn is_valid_negative_baseclass( valid_class_names: &[String], last_word_unsigned: &str, last_word_signed: &str, is_valid_arb_prop: bool, ) -> bool { let is_valid_negative_baseclass = { // tw!("-m-4 p-4 p-4"); (valid_class_names.contains(&last_word_unsigned.to_string()) && last_word_signed.starts_with('-') && SIGNABLES .iter() .any(|s| (last_word_unsigned.starts_with(s)))) || (is_valid_arb_prop && last_word_signed.starts_with('-') && SIGNABLES.iter().any(|s| last_word_unsigned.starts_with(s))) Ok((input, ())) } // bg-black/[27] bg-black/[27%] fn arbitrary_opacity(input: &str) -> IResult<&str, ()> { let input = if COLORFUL_BASECLASSES .iter() .any(|cb| input.trim().starts_with(cb)) { input } else { return Err(nom::Err::Error(nom::error::Error::new( input, nom::error::ErrorKind::Tag, ))); }; is_valid_negative_baseclass } fn is_valid_class( is_valid_arb_prop: bool, valid_class_names: &[String], last_word_unsigned: &str, ) -> bool { !is_valid_arb_prop && valid_class_names.contains(&last_word_unsigned.to_string()) } fn is_valid_arb_prop(word: &str, modifiers: &[String]) -> bool { // TODO: check the first and the last character are not open and close brackets // respectively i.e arbitrary property e.g [mask_type:aplha]; // hover:[mask-type:alpha]; let mut word_for_arb_prop = word.split(":["); word_for_arb_prop .next() // e.g for hover:[mask-type:alpha], this will be hover, // for [mask-type:alpha], this will be [mask-type:alpha] .is_some_and(|modifiers_or_full_arb_prop| { let is_arbitrary_property = modifiers_or_full_arb_prop.starts_with('[') && modifiers_or_full_arb_prop.ends_with(']'); let is_valid = if is_arbitrary_property { modifiers_or_full_arb_prop.matches('[').count() == 1 && modifiers_or_full_arb_prop.matches(']').count() == 1 && modifiers_or_full_arb_prop .trim_start_matches('[') .trim_end_matches(']') .split(':') .count() == 2 } else { // e.g mask-type:alpha] in hover:[mask-type:alpha] let full_arb_prop = word_for_arb_prop.next().unwrap_or_default(); // e.g for single, hover in hover:[mask-type:alpha] // for multiple, hover:first:last, in hover:first:last:[mask-type:alpha] modifiers_or_full_arb_prop .split(':') .all(|modifier| modifiers.contains(&modifier.to_string())) && full_arb_prop.matches(']').count() == 1 && full_arb_prop .trim_end_matches(']') .split(':') .count() == 2 }; is_valid }) || // value e.g [mask-type:alpha] in hover:[mask-type:alpha] // potential addition checks(probably not a good idea. Imagine a new css property, we would // have to open a PR for every new or esoteric css property.) word_for_arb_prop.next().is_some_and(|value| { value.ends_with(']') && value.split(':').count() == 2 // We had already split by ":[", so there should be no "[" anymore && value.matches('[').count() == 0 && value.matches(']').count() == 1 }) } fn is_valid_group_pattern(modifier: &str, valid_modifiers: &[String]) -> bool { let parts: Vec<&str> = modifier.split('/').collect(); let group_modifier = parts[0]; parts.len() == 2 && valid_modifiers.contains(&group_modifier.to_string()) && group_modifier.starts_with("group") } // tw!("group/edit invisible hover:bg-slate-200 group-hover/item:visible"); // tw!("group-[:nth-of-type(3)_&]:block group-hover/edit:text-gray-700 group-[:nth-of-type(3)_&]:block"); fn is_validate_modifier_or_group( word: &str, valid_modifiers: &[String], valid_class_names: &[String], ) -> bool { let valid_arb_group = word.split(':').collect::>(); let modifiers = &valid_arb_group[..valid_arb_group.len() - 1]; let last_word = valid_arb_group.last().unwrap_or(&""); let is_valid_last_word = is_valid_string(last_word) && valid_class_names.contains(&last_word.to_string()); for modifier in modifiers { if modifier.starts_with("group") { if !is_valid_group_pattern(modifier, valid_modifiers) && is_valid_last_word { return false; } } else if !valid_modifiers.contains(&modifier.to_string()) && is_valid_last_word { return false; let (input, _) = take_while1(|char| is_ident_char(char) && char != '/')(input)?; let (input, _) = tag("/")(input)?; let (input, _) = tag("[")(input)?; // 0-100 integer let (input, num) = number::complete::double(input)?; let input = match num as u8 { 0..=100 => input, _ => { return Err(nom::Err::Error(nom::error::Error::new( input, nom::error::ErrorKind::Tag, ))) } }; let (input, _) = opt(tag("%"))(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } // bg-[url('/img/down-arrow.svg')] fn bg_arbitrary_url(input: &str) -> IResult<&str, ()> { // prefixed by baseclass let input = if COLORFUL_BASECLASSES .iter() .any(|cb| input.trim().starts_with(cb)) { input } else { return Err(nom::Err::Error(nom::error::Error::new( input, nom::error::ErrorKind::Tag, ))); }; let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; let (input, _) = tag("[")(input)?; let (input, _) = tag("url('")(input)?; let (input, _) = take_until("')")(input)?; let (input, _) = tag("')")(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } // grid-cols-[fit-content(theme(spacing.32))] fn arbitrary_css_value(input: &str) -> IResult<&str, ()> { // is prefixed by valid base class // take until -[ let (input, base_class) = take_until("-[")(input)?; let input = if VALID_BASECLASS_NAMES .iter() .any(|cb| base_class.trim().eq(*cb)) { input } else { return Err(nom::Err::Error(nom::error::Error::new( base_class, nom::error::ErrorKind::Tag, ))); }; let (input, _) = tag("-[")(input)?; let (input, _) = not(alt(( tag("--"), tag("var(--"), // :var(-- )))(input)?; let (input, _) = take_while1(|char| is_ident_char(char) && char != '(')(input)?; let (input, _) = tag("(")(input)?; let (input, _) = take_until(")]")(input)?; // allow anything inthe brackets let (input, _) = take_until("]")(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } // bg-[--my-color] fn arbitrary_css_var(input: &str) -> IResult<&str, ()> { // is prefixed by valid base class let input = if VALID_BASECLASS_NAMES .iter() .any(|cb| input.trim().starts_with(cb)) { input } else { return Err(nom::Err::Error(nom::error::Error::new( input, nom::error::ErrorKind::Tag, ))); }; let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; let (input, _) = tag("[")(input)?; let (input, _) = tag("--")(input)?; let (input, _) = take_while1(|char| is_ident_char(char) && char != ']')(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } // text-[var(--my-var)] fn arbitrary_css_var2(input: &str) -> IResult<&str, ()> { // is prefixed by valid base class let input = if VALID_BASECLASS_NAMES .iter() .any(|cb| input.trim().starts_with(cb)) { input } else { return Err(nom::Err::Error(nom::error::Error::new( input, nom::error::ErrorKind::Tag, ))); }; let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; let (input, _) = tag("[")(input)?; let (input, _) = tag("var(--")(input)?; let (input, _) = take_while1(|char| is_ident_char(char) && char != ')')(input)?; let (input, _) = tag(")]")(input)?; Ok((input, ())) } // text-[length:var(--my-var)] fn arbitrary_css_var3(input: &str) -> IResult<&str, ()> { // is prefixed by valid base class let input = if VALID_BASECLASS_NAMES .iter() .any(|cb| input.trim().starts_with(cb)) { input } else { return Err(nom::Err::Error(nom::error::Error::new( input, nom::error::ErrorKind::Tag, ))); }; let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; let (input, _) = tag("[")(input)?; let (input, _) = take_while1(|char| is_ident_char(char) && char != ':')(input)?; let (input, _) = tag(":")(input)?; let (input, _) = tag("var(--")(input)?; let (input, _) = take_while1(|char| is_ident_char(char) && char != ')')(input)?; let (input, _) = tag(")]")(input)?; Ok((input, ())) } // group/edit fn arbitrary_group_classname(input: &str) -> IResult<&str, ()> { let (input, _) = alt((tag("group"),))(input)?; let (input, _) = tag("/")(input)?; let (input, _) = take_while1(is_ident_char)(input)?; Ok((input, ())) } fn parse_single_tw_classname(input: &str) -> IResult<&str, ()> { alt(( // bg-[url('/what_a_rush.png')] bg_arbitrary_url, // bg-black/25 predefined_colorful_opacity, // group/edit arbitrary_group_classname, // bg-black/[27] arbitrary_opacity, // btn parse_predefined_tw_classname, // [mask-type:luminance] [mask-type:alpha] kv_pair_classname, // text-[22px] lengthy_arbitrary_classname, // text-[#bada55] colorful_arbitrary_baseclass, // before:content-['Festivus'] arbitrary_content, // content-[>] content-[<] arbitrary_with_arrow, // bg-[--my-color] arbitrary_css_var, // text-[var(--my-var)] arbitrary_css_var2, // text-[length:var(--my-var)] arbitrary_css_var3, // grid-cols-[fit-content(theme(spacing.32))] arbitrary_css_value, ))(input) } // hover:underline fn predefined_modifier(input: &str) -> IResult<&str, ()> { let (input, modifier) = recognize(|i| { // Assuming a Tailwind class consists of alphanumeric, dashes, and colons nom::bytes::complete::is_a( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-", )(i) })(input)?; if is_valid_modifier(modifier) { Ok((input, ())) } else { Err(nom::Err::Error(nom::error::Error::new( input, nom::error::ErrorKind::Tag, ))) } } // predefined special modifiers e.g peer-checked:p-4 group-hover:visible fn predefined_special_modifier(input: &str) -> IResult<&str, ()> { let (input, _) = alt(( // peer-checked:p-4 tuple((tag("peer-"), predefined_modifier)), // group-hover:visible tuple((tag("group-"), predefined_modifier)), ))(input)?; Ok((input, ())) } // [&:nth-child(3)]:underline // [&_p]:mt-4 fn arbitrary_front_selector_modifier(input: &str) -> IResult<&str, ()> { let (input, _) = tag("[&")(input)?; let (input, _) = take_until("]")(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } // group-[:nth-of-type(3)_&]:block fn arbitrary_back_selector_modifier(input: &str) -> IResult<&str, ()> { let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; let (input, _) = tag("-[")(input)?; let (input, _) = take_until("&]")(input)?; let (input, _) = tag("&]")(input)?; Ok((input, ())) } // [@supports(display:grid)]:grid fn arbitrary_at_supports_rule_modifier(input: &str) -> IResult<&str, ()> { let (input, _) = tag("[@supports(")(input)?; let (input, _) = take_until(")")(input)?; let (input, _) = tag(")]")(input)?; Ok((input, ())) } // [@media(any-hover:hover){&:hover}]:opacity-100 fn arbitrary_at_media_rule_modifier(input: &str) -> IResult<&str, ()> { // starts with [@media and ends with ] let (input, _) = tag("[@media(")(input)?; let (input, _) = take_until("]")(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } // group/edit invisible hover:bg-slate-200 group-hover/item:visible fn group_peer_modifier(input: &str) -> IResult<&str, ()> { let (input, _) = alt(( tuple((tag("group-"), predefined_modifier)), // https://tailwindcss.com/docs/hover-focus-and-other-states#differentiating-peers // peer-checked/published:text-sky-500 tuple((tag("peer-"), predefined_modifier)), ))(input)?; let (input, _) = tag("/")(input)?; let (input, _) = take_while1(is_ident_char)(input)?; Ok((input, ())) } // hidden group-[.is-published]:block // group-[:nth-of-type(3)_&]:block // peer-[.is-dirty]:peer-required:block hidden // hidden peer-[:nth-of-type(3)_&]:block fn group_modifier_selector(input: &str) -> IResult<&str, ()> { let (input, _) = alt((tag("group"), tag("peer")))(input)?; let (input, _) = tag("-[")(input)?; let (input, _) = take_until("]")(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } is_valid_last_word // supports-[backdrop-filter] fn supports_arbitrary(input: &str) -> IResult<&str, ()> { let (input, _) = tag("supports-[")(input)?; let (input, _) = take_until("]")(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } fn is_valid_group_classname(class_name: &str) -> bool { !class_name.contains(':') && !class_name.contains('[') && !class_name.contains(']') && class_name.starts_with("group/") // aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] // aria-[sort=descending]:bg-[url('/img/up-arrow.svg')] fn aria_arbitrary(input: &str) -> IResult<&str, ()> { let (input, _) = opt(tag("group-"))(input)?; let (input, _) = tag("aria-[")(input)?; let (input, _) = take_while1(is_ident_char)(input)?; let (input, _) = tag("=")(input)?; let (input, _) = take_while1(is_ident_char)(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } fn is_valid_string(s: &str) -> bool { // Matches strings that contain only alphanumeric characters, underscores, and hyphens. let re = Regex::new(r"^[a-zA-Z0-9_-]*$").expect("Invalid regex"); re.is_match(s) && !s.is_empty() // data-[size=large]:p-8 fn data_arbitrary(input: &str) -> IResult<&str, ()> { let (input, _) = tag("data-[")(input)?; let (input, _) = take_while1(is_ident_char)(input)?; let (input, _) = tag("=")(input)?; let (input, _) = take_while1(is_ident_char)(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } // min-[320px]:text-center max-[600px]:bg-sky-300 fn min_max_arbitrary_modifier(input: &str) -> IResult<&str, ()> { let (input, _) = alt((tag("min-"), tag("max-")))(input)?; let (input, _) = tag("[")(input)?; let (input, _) = parse_length_unit(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } fn modifier(input: &str) -> IResult<&str, ()> { alt(( group_modifier_selector, group_peer_modifier, predefined_special_modifier, arbitrary_front_selector_modifier, arbitrary_back_selector_modifier, arbitrary_at_supports_rule_modifier, arbitrary_at_media_rule_modifier, predefined_modifier, supports_arbitrary, aria_arbitrary, data_arbitrary, min_max_arbitrary_modifier, ))(input) } fn modifiers_chained(input: &str) -> IResult<&str, ()> { let (input, _modifiers) = separated_list0(tag(":"), modifier)(input)?; Ok((input, ())) } fn parse_tw_full_classname(input: &str) -> IResult<&str, Vec<&str>> { let (input, _class_names) = tuple(( opt(tuple((modifiers_chained, tag(":")))), parse_single_tw_classname, ))(input)?; Ok((input, vec![])) } // Edge cases // [&:nth-child(3)]:underline // lg:[&:nth-child(3)]:hover:underline // [&_p]:mt-4 // flex [@supports(display:grid)]:grid // [@media(any-hover:hover){&:hover}]:opacity-100 // group/edit invisible hover:bg-slate-200 group-hover/item:visible // hidden group-[.is-published]:block // group-[:nth-of-type(3)_&]:block // peer-checked/published:text-sky-500 // peer-[.is-dirty]:peer-required:block hidden // hidden peer-[:nth-of-type(3)_&]:block // after:content-['*'] after:ml-0.5 after:text-red-500 block text-sm font-medium text-slate-700 // before:content-[''] before:block // bg-black/75 supports-[backdrop-filter]:bg-black/25 supports-[backdrop-filter]:backdrop-blur // aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg-[url('/img/up-arrow.svg')] // group-aria-[sort=ascending]:rotate-0 group-aria-[sort=descending]:rotate-180 // data-[size=large]:p-8 // open:bg-white dark:open:bg-slate-900 open:ring-1 open:ring-black/5 dark:open:ring-white/10 open:shadow-lg p-6 rounded-lg // lg:[&:nth-child(3)]:hover:underline // min-[320px]:text-center max-[600px]:bg-sky-300 // top-[117px] lg:top-[344px] // bg-[#bada55] text-[22px] before:content-['Festivus'] // grid grid-cols-[fit-content(theme(spacing.32))] // bg-[--my-color] // [mask-type:luminance] hover:[mask-type:alpha] // [--scroll-offset:56px] lg:[--scroll-offset:44px] // lg:[&:nth-child(3)]:hover:underline // bg-[url('/what_a_rush.png')] // before:content-['hello\_world'] // text-[22px] // text-[#bada55] // text-[var(--my-var)] // text-[length:var(--my-var)] // text-[color:var(--my-var)] fn parse_class_names(input: &str) -> IResult<&str, Vec<&str>> { let (input, _) = multispace0(input)?; let (input, _class_names) = separated_list0(multispace1, parse_tw_full_classname)(input)?; let (input, _) = multispace0(input)?; Ok((input, vec![])) } fn parse_top(input: &str) -> IResult<&str, Vec<&str>> { all_consuming(parse_class_names)(input) } #[proc_macro] pub fn tw(raw_input: TokenStream) -> TokenStream { let r_input = raw_input.clone(); let input = parse_macro_input!(r_input as LitStr); let (_modifiers, _valid_class_names) = match setup(&input) { Ok(value) => value, Err(value) => { return syn::Error::new_spanned(input, value) .to_compile_error() .into() } }; let full_classnames = input.value(); let (input, _class_names) = match parse_top(&full_classnames) { Ok(value) => value, Err(value) => { return syn::Error::new_spanned(input, value) .to_compile_error() .into() } }; quote::quote! { #input } .into() } \ No newline at end of file diff --git a/tw-macro/src/lib.rs b/tw-macro/src/lib.rs index 2df4cf2..12877d1 100644 --- a/tw-macro/src/lib.rs +++ b/tw-macro/src/lib.rs @@ -5,6 +5,8 @@ * Licensed under the MIT license */ +use std::collections::HashSet; + use nom::{ branch::alt, bytes::complete::{tag, take_until, take_while1}, @@ -61,8 +63,10 @@ fn setup(input: &LitStr) -> Result<(Vec, Vec), TokenStream> { Ok((modifiers, valid_class_names)) } -fn get_classes_straight() -> Vec { - get_classes(&read_tailwind_config().unwrap()) +fn get_classes_straight() -> HashSet { + HashSet::from_iter(get_classes( + &read_tailwind_config().expect("Problem getting classes"), + )) } fn is_valid_classname(class_name: &str) -> bool { @@ -70,7 +74,10 @@ fn is_valid_classname(class_name: &str) -> bool { } fn is_valid_modifier(modifier: &str) -> bool { - get_modifiers(&read_tailwind_config().unwrap()).contains(&modifier.to_string()) + let modifiers: HashSet = HashSet::from_iter(get_modifiers( + &read_tailwind_config().expect("Problem getting modifiers"), + )); + modifiers.contains(&modifier.to_string()) } fn parse_predefined_tw_classname(input: &str) -> IResult<&str, ()> { diff --git a/tw-macro/src/tailwind/modifiers.rs b/tw-macro/src/tailwind/modifiers.rs index e986c71..724cd53 100644 --- a/tw-macro/src/tailwind/modifiers.rs +++ b/tw-macro/src/tailwind/modifiers.rs @@ -153,7 +153,7 @@ pub const ARIA_DEFAULT: [&str; 8] = [ pub fn get_modifiers(config: &TailwindConfig) -> Vec { let mut modifiers = Vec::new(); - modifiers.extend(MODIFIERS.iter().map(|x| x.to_string())); + modifiers.extend(MODIFIERS.iter().map(ToString::to_string)); let mut default_screens = vec![ "sm", "md", "lg", "xl", "2xl", // "min-[…]", "max-sm", "max-md", "max-lg", "max-xl", "max-2xl", From 6039a52d8532962d0ee8e45d515384f432a68466 Mon Sep 17 00:00:00 2001 From: oyelowo Date: Tue, 10 Oct 2023 23:16:39 -0600 Subject: [PATCH 46/46] Rename crate to from tailwind-rust to twust --- Cargo.toml | 2 +- README.md | 38 +- examples/leptos-demo/Cargo.toml | 2 +- examples/leptos-demo/src/app.rs | 2 +- sss.md | 1181 ------------------------------- tailwind/Cargo.toml | 2 +- tailwind/src/lib.rs | 2 +- tailwind/src/main.rs | 2 +- tw-macro/Cargo.toml | 2 +- 9 files changed, 26 insertions(+), 1207 deletions(-) delete mode 100644 sss.md diff --git a/Cargo.toml b/Cargo.toml index 42443b9..88736b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ documentation = "https://codebreather.com/oyelowo" [workspace.dependencies] -tw-macro = { path = "tw-macro" } +twust = { path = "tw-macro" } # tailwind = { path = "tailwind" } proc-macro2 = "1.0.66" diff --git a/README.md b/README.md index a50c1ef..eb97e13 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ -# `tw-macro` +# `twust` -A powerful Rust macro to validate TailwindCSS class names at compile-time. +Twust is a powerful static checker in rust for TailwindCSS class names at +compile-time. - - Screenshot 2023-09-09 at 19 51 09 - +Screenshot 2023-09-09 at 19 51 09 ## Table of Contents @@ -25,18 +25,18 @@ A powerful Rust macro to validate TailwindCSS class names at compile-time. ## Overview -`tw-macro` is a Rust procedural macro that provides compile-time validation for -TailwindCSS class names. Leveraging the power of Rust's macro system, `tw-macro` +`twust` is a Rust procedural macro that provides compile-time validation for +TailwindCSS class names. Leveraging the power of Rust's macro system, `twust` ensures that you only use valid TailwindCSS class names, preventing runtime errors and promoting a more robust development experience. ## Installation -Add `tw-macro` to your `Cargo.toml`: +Add `twust` to your `Cargo.toml`: ```toml [dependencies] -tw-macro = "0.1.0" +twust = "0.1.0" ``` ## Usage @@ -92,24 +92,24 @@ applications. However, its flexibility can also lead to potential pitfalls: ## Solution -`tw-macro` addresses these challenges by offering: +`twust` addresses these challenges by offering: - **Compile-time Validation:** By checking the validity of TailwindCSS class - names at compile time, `tw-macro` prevents invalid class names from making + names at compile time, `twust` prevents invalid class names from making their way into the production code. -- **Seamless Integration:** As a Rust macro, `tw-macro` integrates seamlessly +- **Seamless Integration:** As a Rust macro, `twust` integrates seamlessly into your Rust workflow, offering immediate feedback without the need for external tools or manual validation. -- **Plugin Support:** With tw-macro, you can easily integrate popular plugins +- **Plugin Support:** With twust, you can easily integrate popular plugins like daisyui by merely specifying them as a feature, ensuring a consistent and extended development experience. - **Effortless Code Reusability:** The ability to copy-paste and reuse your TailwindCSS code without any manual mappings or transformations. Just wrap your code with the macro, and you're set. -- **Optimized Builds:** By ensuring only valid class names are used, `tw-macro` +- **Optimized Builds:** By ensuring only valid class names are used, `twust` helps in reducing the unnecessary bloat in the final CSS bundle. ## Features @@ -168,16 +168,16 @@ nonexistent classes and code completion for available classes. adds an external dependency, which might not be suitable for all projects, especially those that want to minimize their dependency tree. -### Approach with `tw-macro` +### Approach with `twust` -Our solution with `tw-macro` offers a more streamlined and integrated approach: +Our solution with `twust` offers a more streamlined and integrated approach: - **Simpler Setup:** Just add the macro to your project and start using it. No need for external tools or additional configuration steps. - **Real-time Validation:** Instead of generating static Rust code from - TailwindCSS, `tw-macro` validates class names in real-time during the + TailwindCSS, `twust` validates class names in real-time during the compilation process. -- **No External Dependencies:** `tw-macro` is self-contained, meaning you don't +- **No External Dependencies:** `twust` is self-contained, meaning you don't need any external tools like the `tailwindcss` CLI. - **Extensive Coverage:** We support all standard TailwindCSS class names, @@ -231,5 +231,5 @@ to contribute to the code, please open an issue or pull request. ## License -`tw-macro` is licensed under the MIT license. See the `LICENSE` file for +`twust` is licensed under the MIT license. See the `LICENSE` file for details. diff --git a/examples/leptos-demo/Cargo.toml b/examples/leptos-demo/Cargo.toml index a4c1671..0b0dfba 100644 --- a/examples/leptos-demo/Cargo.toml +++ b/examples/leptos-demo/Cargo.toml @@ -11,7 +11,7 @@ leptos_meta = { version = "0.5.0-beta2", features = ["csr", "nightly"] } leptos_router = { version = "0.5.0-beta2", features = ["csr", "nightly"] } log = "0.4" gloo-net = { version = "0.2", features = ["http"] } -tw-macro = { git = "https://github.com/oyelowo/tailwind-rust", features = ["daisyui"]} +twust = { git = "https://github.com/oyelowo/twust", features = ["daisyui"] } # dependecies for client (enable when csr or hydrate set) diff --git a/examples/leptos-demo/src/app.rs b/examples/leptos-demo/src/app.rs index 5c596a7..3f21728 100644 --- a/examples/leptos-demo/src/app.rs +++ b/examples/leptos-demo/src/app.rs @@ -1,7 +1,7 @@ use leptos::*; use leptos_meta::*; use leptos_router::*; -use tw_macro::tw; +use twust::tw; #[component] pub fn App() -> impl IntoView { diff --git a/sss.md b/sss.md deleted file mode 100644 index 36c36bd..0000000 --- a/sss.md +++ /dev/null @@ -1,1181 +0,0 @@ -Create a PR for these changes: - -Skip to content -Oyelowo -/ -tailwind-rust - -Type / to search - -Code -Issues -1 -Pull requests -Actions -Projects -Wiki -Security -Insights -Settings -Open a pull request -Create a new pull request by comparing changes across two branches. If you need to, you can also . - -... - - Able to merge. These branches can be automatically merged. -@Oyelowo -5 use a more robust parsing for tailwind classes - - -Leave a comment -No file chosen -Attach files by dragging & dropping, selecting or pasting them. -Remember, contributions to this repository should follow our GitHub Community Guidelines. -Reviewers -No reviews—at least 1 approving review is required. -Assignees -No one— -Labels -None yet -Projects -None yet -Milestone -No milestone -Development -Use Closing keywords in the description to automatically close issues - -Helpful resources -GitHub Community Guidelines - 1 contributor - Commits 44 - Files changed 8 -Showing with 836 additions and 283 deletions. - 1 change: 1 addition & 0 deletions1 -Cargo.toml -@@ -26,6 +26,7 @@ tw-macro = { path = "tw-macro" } -proc-macro2 = "1.0.66" -quote = "1.0.33" -syn = "2.0.29" -nom = "7.1.3" -static_assertions = "1.1.0" -serde = { version = "1.0.188", features = ["derive"] } -serde_json = "1.0.105" - 15 changes: 10 additions & 5 deletions15 -tailwind/src/lib.rs -@@ -82,9 +82,9 @@ fn _unsupported_media_query() {} -/// -/// ```compile_fail -/// use tw_macro::tw; -/// tw!("px-[45]"); -/// tw!("px-45]"); -/// ``` -fn _missing_unit_after_arbitrary_value() {} -fn _malformed_arbitrary_value() {} - -/// Invalid group usage. -/// -@@ -166,10 +166,11 @@ fn _happy_paths() { - let _classnames = tw!("text-blue-600/[.07]"); - - // tw!("[something]"); - let _classnames = tw!("px-[-45px]"); - let _classnames = tw!("px-[45.43px]"); - let _classnames = tw!("px-[-45cm]"); - let _classnames = tw!("px-[-45rem]"); - let _classnames = tw!("px-[-45em]"); - let _classnames = tw!("px-[45em]"); - let _classnames = tw!("px-[-45%]"); - let _classnames = tw!("px-[-45in]"); - let _classnames = tw!("px-[-45vh]"); -@@ -178,20 +179,24 @@ fn _happy_paths() { - let _classnames = tw!("px-[-45vmax]"); - let _classnames = tw!("px-[-45mm]"); - let _classnames = tw!("px-[-45pc]"); - let _classnames = tw!("px-[0px]"); - let _classnames = tw!("px-[0]"); - let _classnames = tw!("px-[45px]"); - let _classnames = tw!("px-[45cm]"); - let _classnames = tw!("px-[45rem]"); - let _classnames = tw!("px-[45em]"); - tw!("bg-taxvhiti"); - - // let _classnames = tw!("px-[45em]"); - let _classnames = tw!("px-[45%]"); - let _classnames = tw!("px-[45in]"); - let _classnames = tw!("px-[45vh]"); - let _classnames = tw!("px-[45vw]"); - let _classnames = tw!("px-[45vmin]"); - let _classnames = tw!("px-[45vmax]"); - let _classnames = tw!("px-[45mm]"); - let _classnames = tw!("px-[45.5mm]"); - let _classnames = tw!("px-[45pc]"); - let _classnames = tw!("py-[0]"); - let _classnames = tw!("px-[45pc]"); - let _classnames = tw!("-px-[45pc]"); - let _classnames = tw!("hover:[mask-type:alpha]"); - let _classnames = tw!( - 116 changes: 116 additions & 0 deletions116 -tailwind/src/main.rs - 1 change: 1 addition & 0 deletions1 -tw-macro/Cargo.toml - 958 changes: 680 additions & 278 deletions958 -tw-macro/src/lib.rs -@@ -4,116 +4,31 @@ - * Copyright (c) 2023 Oyelowo Oyedayo - * Licensed under the MIT license - */ - -use nom::{ - branch::alt, - bytes::complete::{tag, take_until, take_while1}, - character::complete::{digit1, multispace0, multispace1}, - combinator::{all_consuming, not, opt, recognize}, - multi::separated_list0, - number, - sequence::{preceded, tuple}, - IResult, -}; -use syn::{parse_macro_input, LitStr}; -mod config; -mod plugins; -mod tailwind; -use tailwind::{ - lengthy::LENGTHY, modifiers::get_modifiers, tailwind_config::CustomisableClasses, - valid_baseclass_names::VALID_BASECLASS_NAMES, - colorful::COLORFUL_BASECLASSES, lengthy::LENGTHY, modifiers::get_modifiers, - tailwind_config::CustomisableClasses, valid_baseclass_names::VALID_BASECLASS_NAMES, -}; - -use config::{get_classes, noconfig::UNCONFIGURABLE, read_tailwind_config}; -use proc_macro::TokenStream; -use regex::{self, Regex}; -use tailwind::signable::SIGNABLES; -// use tailwindcss_core::parser::{Extractor, ExtractorOptions}; - -#[proc_macro] -pub fn tw(raw_input: TokenStream) -> TokenStream { - let r_input = raw_input.clone(); - let input = parse_macro_input!(r_input as LitStr); - let (modifiers, valid_class_names) = match setup(&input) { - Ok(value) => value, - Err(value) => { - return syn::Error::new_spanned(input, value) - .to_compile_error() - .into() - } - }; - - for word in input.value().split_whitespace() { - let (last_word_signed, last_word_unsigned) = get_last_word_types(word); - - // modifiers e.g hover: in - // hover:[mask-type:alpha] - let is_valid_arb_prop = is_valid_arb_prop(word, &modifiers); - - let is_valid_class = - is_valid_class(is_valid_arb_prop, &valid_class_names, last_word_unsigned); - - let (base_classname, arbitrary_value_with_bracket) = - last_word_unsigned.split_once("-[").unwrap_or_default(); - - let is_valid_negative_baseclass = is_valid_negative_baseclass( - &valid_class_names, - last_word_unsigned, - last_word_signed, - is_valid_arb_prop, - ); - - let prefix_is_valid_tailwind_keyword = VALID_BASECLASS_NAMES.contains(&base_classname); - let is_arbitrary_value = - prefix_is_valid_tailwind_keyword && arbitrary_value_with_bracket.ends_with(']'); - - let arbitrary_value = arbitrary_value_with_bracket.trim_end_matches(']'); - let is_lengthy_class = LENGTHY.contains(&base_classname); - let is_valid_length = is_arbitrary_value - && is_lengthy_class - && (is_valid_length(arbitrary_value) || is_valid_calc(arbitrary_value)); - - let has_arb_variant = has_arb_variant(word); - - let is_valid_opacity = is_valid_opacity(last_word_unsigned, &valid_class_names); - - if (is_valid_class && is_valid_modifier(word, &modifiers)) - || is_valid_negative_baseclass - || (!is_lengthy_class && is_arbitrary_value) - || is_valid_length - || is_valid_arb_prop - || has_arb_variant - || is_valid_opacity - || is_valid_group_classname(last_word_unsigned) - || is_validate_modifier_or_group(word, &modifiers, &valid_class_names) - { - // if check_word(word, false).is_empty() { - // return syn::Error::new_spanned(input, format!("Invalid string: {}", word)) - // .to_compile_error() - // .into(); - // } - } else { - return syn::Error::new_spanned(input, format!("Invalid string: {word}")) - .to_compile_error() - .into(); - } - } - - raw_input -} - -// fn check_word(input: &str, loose: bool) -> Vec<&str> { -// Extractor::unique_ord( -// input.as_bytes(), -// ExtractorOptions { -// preserve_spaces_in_arbitrary: loose, -// }, -// ) -// .into_iter() -// .map(|s| unsafe { std::str::from_utf8_unchecked(s) }) -// .collect() -// } - -fn is_valid_length(value: &str) -> bool { - let re = regex::Regex::new(r"^(-?\d+(\.?\d+)?(px|em|rem|%|cm|mm|in|pt|pc|vh|vw|vmin|vmax)|0)$") - .expect("Invalid regex"); - re.is_match(value) -} - -fn is_valid_calc(value: &str) -> bool { - let re = regex::Regex::new(r"^calc\([^)]+\)$").expect("Invalid regex"); - re.is_match(value) -} - -fn setup(input: &LitStr) -> Result<(Vec, Vec), TokenStream> { - let config = &(match read_tailwind_config() { - Ok(config) => config, -@@ -146,199 +61,686 @@ fn setup(input: &LitStr) -> Result<(Vec, Vec), TokenStream> { - Ok((modifiers, valid_class_names)) -} - -fn get_last_word_types(word: &str) -> (&str, &str) { - let modifiers_and_class = word.split(':'); -fn get_classes_straight() -> Vec { - get_classes(&read_tailwind_config().unwrap()) -} - -fn is_valid_classname(class_name: &str) -> bool { - get_classes_straight().contains(&class_name.to_string()) -} - -fn is_valid_modifier(modifier: &str) -> bool { - get_modifiers(&read_tailwind_config().unwrap()).contains(&modifier.to_string()) -} - -fn parse_predefined_tw_classname(input: &str) -> IResult<&str, ()> { - let (input, class_name) = recognize(|i| { - // Considering a Tailwind class consists of alphanumeric, dashes, and slash - nom::bytes::complete::is_a( - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-./", - )(i) - })(input)?; - - let is_signable = SIGNABLES.iter().any(|s| { - class_name - .strip_prefix('-') - .unwrap_or(class_name) - .starts_with(s) - }); - - if is_signable && is_valid_classname(class_name.strip_prefix('-').unwrap_or(class_name)) - || !is_signable && is_valid_classname(class_name) - { - Ok((input, ())) - } else { - Err(nom::Err::Error(nom::error::Error::new( - input, - nom::error::ErrorKind::Tag, - ))) - } -} - -fn is_ident_char(c: char) -> bool { - c.is_alphanumeric() || c == '_' || c == '-' -} - -fn is_lengthy_classname(class_name: &str) -> bool { - LENGTHY.contains(&class_name.strip_prefix('-').unwrap_or(class_name)) -} - -// Custom number parser that handles optional decimals and signs, and scientific notation -fn float_strict(input: &str) -> IResult<&str, f64> { - let (input, number) = recognize(tuple(( - opt(alt((tag("-"), tag("+")))), - digit1, - opt(preceded(tag("."), digit1)), - opt(tuple(( - alt((tag("e"), tag("E"))), - opt(alt((tag("-"), tag("+")))), - digit1, - ))), - )))(input)?; - - let float_val: f64 = number.parse().unwrap(); - Ok((input, float_val)) -} - -fn parse_length_unit(input: &str) -> IResult<&str, String> { - let (input, number) = float_strict(input)?; - let (input, unit) = { - // px|em|rem|%|cm|mm|in|pt|pc|vh|vw|vmin|vmax - alt(( - tag("px"), - tag("em"), - tag("rem"), - tag("%"), - tag("cm"), - tag("mm"), - tag("in"), - tag("pt"), - tag("pc"), - tag("vh"), - tag("vw"), - tag("vmin"), - tag("vmax"), - // TODO: Should i allow unitless values? Would need something like this in caller - // location if so: - // let (input, _) = alt((parse_length_unit, parse_number))(input)?; - tag(""), - )) - }(input)?; - Ok((input, format!("{}{}", number, unit))) -} - - // let is_arbitrary_property = word.starts_with('[') && word.ends_with(']'); - let last_word_signed = modifiers_and_class.clone().last().unwrap_or_default(); - let last_word_unsigned = last_word_signed - .strip_prefix('-') - .unwrap_or(last_word_signed); -// text-[22px] -fn lengthy_arbitrary_classname(input: &str) -> IResult<&str, ()> { - let (input, class_name) = take_until("-[")(input)?; - let (input, _) = if is_lengthy_classname(class_name) { - Ok((input, ())) - } else { - Err(nom::Err::Error(nom::error::Error::new( - input, - nom::error::ErrorKind::Tag, - ))) - }?; - - // arbitrary value - let (input, _) = tag("-")(input)?; - let (input, _) = tag("[")(input)?; - // is number - let (input, _) = parse_length_unit(input)?; - let (input, _) = tag("]")(input)?; - Ok((input, ())) -} - - (last_word_signed, last_word_unsigned) -// #bada55 -fn parse_hex_color(input: &str) -> IResult<&str, String> { - let (input, _) = tag("#")(input)?; - let (input, color) = take_while1(|c: char| c.is_ascii_hexdigit())(input)?; - let (input, _) = if color.chars().count() == 3 || color.chars().count() == 6 { - Ok((input, ())) - } else { - Err(nom::Err::Error(nom::error::Error::new( - input, - nom::error::ErrorKind::Tag, - ))) - }?; - let color = format!("#{}", color); - Ok((input, color)) -} - -fn is_valid_modifier(word: &str, modifiers: &[String]) -> bool { - let modifiers_and_class = word.split(':'); - let modifiers_from_word = modifiers_and_class - .clone() - .take(modifiers_and_class.count() - 1) - .collect::>(); - modifiers_from_word -fn parse_u8(input: &str) -> IResult<&str, u8> { - let (input, num) = number::complete::double(input)?; - let input = match num as u32 { - 0..=255 => input, - _ => { - return Err(nom::Err::Error(nom::error::Error::new( - input, - nom::error::ErrorKind::Tag, - ))) - } - }; - Ok((input, num as u8)) -} - -// rgb(255, 255, 255) rgb(255_255_255) -fn parse_rgb_color(input: &str) -> IResult<&str, String> { - let (input, _) = tag("rgb(")(input)?; - let (input, r) = parse_u8(input)?; - let (input, _) = alt((tag(","), tag("_")))(input)?; - let (input, g) = parse_u8(input)?; - let (input, _) = alt((tag(","), tag("_")))(input)?; - let (input, b) = parse_u8(input)?; - let (input, _) = tag(")")(input)?; - let color = format!("rgb({}, {}, {})", r, g, b); - Ok((input, color)) -} - -// rgba(255, 255, 255, 0.5) rgba(255_255_255_0.5) -fn parse_rgba_color(input: &str) -> IResult<&str, String> { - let (input, _) = tag("rgba(")(input)?; - let (input, r) = parse_u8(input)?; - let (input, _) = alt((tag(","), tag("_")))(input)?; - let (input, g) = parse_u8(input)?; - let (input, _) = alt((tag(","), tag("_")))(input)?; - let (input, b) = parse_u8(input)?; - let (input, _) = alt((tag(","), tag("_")))(input)?; - let (input, a) = number::complete::double(input)?; - let (input, _) = tag(")")(input)?; - let color = format!("rgba({}, {}, {}, {})", r, g, b, a); - Ok((input, color)) -} - -fn is_colorful_baseclass(class_name: &str) -> bool { - COLORFUL_BASECLASSES.contains(&class_name) -} - -// text-[#bada55] -fn colorful_arbitrary_baseclass(input: &str) -> IResult<&str, ()> { - let (input, class_name) = take_until("-[")(input)?; - let (input, _) = if is_colorful_baseclass(class_name) { - Ok((input, ())) - } else { - Err(nom::Err::Error(nom::error::Error::new( - input, - nom::error::ErrorKind::Tag, - ))) - }?; - - // arbitrary value - let (input, _) = tag("-")(input)?; - let (input, _) = tag("[")(input)?; - let (input, _) = alt((parse_hex_color, parse_rgb_color, parse_rgba_color))(input)?; - let (input, _) = tag("]")(input)?; - Ok((input, ())) -} - -// e.g: [mask-type:alpha] -fn kv_pair_classname(input: &str) -> IResult<&str, ()> { - let (input, _) = tag("[")(input)?; - let (input, _) = take_while1(is_ident_char)(input)?; - let (input, _) = tag(":")(input)?; - let (input, _) = take_until("]")(input)?; - let (input, _) = tag("]")(input)?; - Ok((input, ())) -} - -// before:content-['Festivus'] -fn arbitrary_content(input: &str) -> IResult<&str, ()> { - let (input, _) = tag("content-['")(input)?; - let (input, _) = take_until("']")(input)?; - let (input, _) = tag("']")(input)?; - Ok((input, ())) -} - -// content-[>] content-[<] -fn arbitrary_with_arrow(input: &str) -> IResult<&str, ()> { - let (input, _) = take_while1(is_ident_char)(input)?; - let (input, _) = tag("[")(input)?; - let (input, _) = alt((tag(">"), tag("<")))(input)?; - let (input, _) = take_until("]")(input)?; - let (input, _) = tag("]")(input)?; - Ok((input, ())) -} - -// bg-black/25 -fn predefined_colorful_opacity(input: &str) -> IResult<&str, ()> { - let input = if COLORFUL_BASECLASSES - .iter() - .all(|modifier| modifiers.contains(&modifier.to_string())) -} - -fn is_valid_opacity(last_word_unsigned: &str, valid_class_names: &[String]) -> bool { - let is_valid_opacity = { - let (class_name, opacity_raw) = last_word_unsigned.split_once('/').unwrap_or_default(); - let opacity_arb = opacity_raw - .trim_start_matches('[') - .trim_end_matches(']') - .parse::(); - let is_valid_number = - opacity_arb.is_ok_and(|opacity_num| (0.0..=100.0).contains(&opacity_num)); - valid_class_names.contains(&class_name.to_string()) && is_valid_number - .any(|cb| input.trim().starts_with(cb)) - { - input - } else { - return Err(nom::Err::Error(nom::error::Error::new( - input, - nom::error::ErrorKind::Tag, - ))); - }; - is_valid_opacity -} - -fn has_arb_variant(word: &str) -> bool { - // lg:[&:nth-child(3)]:hover:underline - // [&_p]:mt-4 - // flex [@supports(display:grid)]:grid - // [@media(any-hover:hover){&:hover}]:opacity-100 - let has_arb_variant = { - // lg:[&:nth-child(3)]:hover:underline => :nth-child(3) - // [&_p]:mt-4 => _p - let mut ampersand_variant_selector = - word.split("[@").last().unwrap_or_default().split("]:"); - let and_variant_selector = word.split("[&").last().unwrap_or_default().split("]:"); - let is_valid_arbitrary_variant_selector = ampersand_variant_selector.clone().count() >= 2 - && !ampersand_variant_selector - .next() - .unwrap_or_default() - .is_empty(); - let is_valid_arbitrary_variant_queries = and_variant_selector.clone().count() >= 2 - && !and_variant_selector - .clone() - .last() - .unwrap_or_default() - .split("]:") - .next() - .unwrap_or_default() - .is_empty(); - let is_query = word.starts_with("[@"); - - is_valid_arbitrary_variant_selector || is_valid_arbitrary_variant_queries || is_query - // && - // ((!is_query && !word.split("[&").next().unwrap_or_default().is_empty() && word.split(":[&").count() >= 2) || is_query) - let (input, _) = take_while1(|char| is_ident_char(char) && char != '/')(input)?; - // let (input, _) = take_until("/")(input)?; - let (input, _) = tag("/")(input)?; - - let (input, num) = number::complete::double(input)?; - let input = match num as u8 { - 0..=100 => input, - _ => { - return Err(nom::Err::Error(nom::error::Error::new( - input, - nom::error::ErrorKind::Tag, - ))) - } - }; - has_arb_variant -} - -fn is_valid_negative_baseclass( - valid_class_names: &[String], - last_word_unsigned: &str, - last_word_signed: &str, - is_valid_arb_prop: bool, -) -> bool { - let is_valid_negative_baseclass = { - // tw!("-m-4 p-4 p-4"); - (valid_class_names.contains(&last_word_unsigned.to_string()) - && last_word_signed.starts_with('-') - && SIGNABLES - .iter() - .any(|s| (last_word_unsigned.starts_with(s)))) - || (is_valid_arb_prop - && last_word_signed.starts_with('-') - && SIGNABLES.iter().any(|s| last_word_unsigned.starts_with(s))) - - Ok((input, ())) -} - -// bg-black/[27] bg-black/[27%] -fn arbitrary_opacity(input: &str) -> IResult<&str, ()> { - let input = if COLORFUL_BASECLASSES - .iter() - .any(|cb| input.trim().starts_with(cb)) - { - input - } else { - return Err(nom::Err::Error(nom::error::Error::new( - input, - nom::error::ErrorKind::Tag, - ))); - }; - is_valid_negative_baseclass -} - -fn is_valid_class( - is_valid_arb_prop: bool, - valid_class_names: &[String], - last_word_unsigned: &str, -) -> bool { - !is_valid_arb_prop && valid_class_names.contains(&last_word_unsigned.to_string()) -} - -fn is_valid_arb_prop(word: &str, modifiers: &[String]) -> bool { - // TODO: check the first and the last character are not open and close brackets - // respectively i.e arbitrary property e.g [mask_type:aplha]; - // hover:[mask-type:alpha]; - let mut word_for_arb_prop = word.split(":["); - - word_for_arb_prop - .next() - // e.g for hover:[mask-type:alpha], this will be hover, - // for [mask-type:alpha], this will be [mask-type:alpha] - .is_some_and(|modifiers_or_full_arb_prop| { - let is_arbitrary_property = modifiers_or_full_arb_prop.starts_with('[') && modifiers_or_full_arb_prop.ends_with(']'); - - let is_valid = if is_arbitrary_property { - modifiers_or_full_arb_prop.matches('[').count() == 1 && - modifiers_or_full_arb_prop.matches(']').count() == 1 && - modifiers_or_full_arb_prop - .trim_start_matches('[') - .trim_end_matches(']') - .split(':') - .count() == 2 - } else { - // e.g mask-type:alpha] in hover:[mask-type:alpha] - let full_arb_prop = word_for_arb_prop.next().unwrap_or_default(); - // e.g for single, hover in hover:[mask-type:alpha] - // for multiple, hover:first:last, in hover:first:last:[mask-type:alpha] - modifiers_or_full_arb_prop - .split(':') - .all(|modifier| modifiers.contains(&modifier.to_string())) && - full_arb_prop.matches(']').count() == 1 && - full_arb_prop - .trim_end_matches(']') - .split(':') - .count() == 2 - - }; - is_valid - }) - || - // value e.g [mask-type:alpha] in hover:[mask-type:alpha] - // potential addition checks(probably not a good idea. Imagine a new css property, we would - // have to open a PR for every new or esoteric css property.) - word_for_arb_prop.next().is_some_and(|value| { - value.ends_with(']') - && value.split(':').count() == 2 - // We had already split by ":[", so there should be no "[" anymore - && value.matches('[').count() == 0 - && value.matches(']').count() == 1 - }) -} - -fn is_valid_group_pattern(modifier: &str, valid_modifiers: &[String]) -> bool { - let parts: Vec<&str> = modifier.split('/').collect(); - let group_modifier = parts[0]; - parts.len() == 2 - && valid_modifiers.contains(&group_modifier.to_string()) - && group_modifier.starts_with("group") -} - -// tw!("group/edit invisible hover:bg-slate-200 group-hover/item:visible"); -// tw!("group-[:nth-of-type(3)_&]:block group-hover/edit:text-gray-700 group-[:nth-of-type(3)_&]:block"); -fn is_validate_modifier_or_group( - word: &str, - valid_modifiers: &[String], - valid_class_names: &[String], -) -> bool { - let valid_arb_group = word.split(':').collect::>(); - let modifiers = &valid_arb_group[..valid_arb_group.len() - 1]; - let last_word = valid_arb_group.last().unwrap_or(&""); - let is_valid_last_word = - is_valid_string(last_word) && valid_class_names.contains(&last_word.to_string()); - - for modifier in modifiers { - if modifier.starts_with("group") { - if !is_valid_group_pattern(modifier, valid_modifiers) && is_valid_last_word { - return false; - } - } else if !valid_modifiers.contains(&modifier.to_string()) && is_valid_last_word { - return false; - let (input, _) = take_while1(|char| is_ident_char(char) && char != '/')(input)?; - let (input, _) = tag("/")(input)?; - let (input, _) = tag("[")(input)?; - // 0-100 integer - let (input, num) = number::complete::double(input)?; - let input = match num as u8 { - 0..=100 => input, - _ => { - return Err(nom::Err::Error(nom::error::Error::new( - input, - nom::error::ErrorKind::Tag, - ))) - } - }; - let (input, _) = opt(tag("%"))(input)?; - let (input, _) = tag("]")(input)?; - Ok((input, ())) -} - -// bg-[url('/img/down-arrow.svg')] -fn bg_arbitrary_url(input: &str) -> IResult<&str, ()> { - // prefixed by baseclass - let input = if COLORFUL_BASECLASSES - .iter() - .any(|cb| input.trim().starts_with(cb)) - { - input - } else { - return Err(nom::Err::Error(nom::error::Error::new( - input, - nom::error::ErrorKind::Tag, - ))); - }; - let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; - let (input, _) = tag("[")(input)?; - let (input, _) = tag("url('")(input)?; - let (input, _) = take_until("')")(input)?; - let (input, _) = tag("')")(input)?; - let (input, _) = tag("]")(input)?; - Ok((input, ())) -} - -// grid-cols-[fit-content(theme(spacing.32))] -fn arbitrary_css_value(input: &str) -> IResult<&str, ()> { - // is prefixed by valid base class - // take until -[ - let (input, base_class) = take_until("-[")(input)?; - let input = if VALID_BASECLASS_NAMES - .iter() - .any(|cb| base_class.trim().eq(*cb)) - { - input - } else { - return Err(nom::Err::Error(nom::error::Error::new( - base_class, - nom::error::ErrorKind::Tag, - ))); - }; - let (input, _) = tag("-[")(input)?; - let (input, _) = not(alt(( - tag("--"), - tag("var(--"), - // :var(-- - )))(input)?; - let (input, _) = take_while1(|char| is_ident_char(char) && char != '(')(input)?; - let (input, _) = tag("(")(input)?; - let (input, _) = take_until(")]")(input)?; - - // allow anything inthe brackets - let (input, _) = take_until("]")(input)?; - let (input, _) = tag("]")(input)?; - Ok((input, ())) -} - -// bg-[--my-color] -fn arbitrary_css_var(input: &str) -> IResult<&str, ()> { - // is prefixed by valid base class - let input = if VALID_BASECLASS_NAMES - .iter() - .any(|cb| input.trim().starts_with(cb)) - { - input - } else { - return Err(nom::Err::Error(nom::error::Error::new( - input, - nom::error::ErrorKind::Tag, - ))); - }; - let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; - let (input, _) = tag("[")(input)?; - let (input, _) = tag("--")(input)?; - let (input, _) = take_while1(|char| is_ident_char(char) && char != ']')(input)?; - let (input, _) = tag("]")(input)?; - Ok((input, ())) -} -// text-[var(--my-var)] -fn arbitrary_css_var2(input: &str) -> IResult<&str, ()> { - // is prefixed by valid base class - let input = if VALID_BASECLASS_NAMES - .iter() - .any(|cb| input.trim().starts_with(cb)) - { - input - } else { - return Err(nom::Err::Error(nom::error::Error::new( - input, - nom::error::ErrorKind::Tag, - ))); - }; - let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; - let (input, _) = tag("[")(input)?; - let (input, _) = tag("var(--")(input)?; - let (input, _) = take_while1(|char| is_ident_char(char) && char != ')')(input)?; - let (input, _) = tag(")]")(input)?; - Ok((input, ())) -} - -// text-[length:var(--my-var)] -fn arbitrary_css_var3(input: &str) -> IResult<&str, ()> { - // is prefixed by valid base class - let input = if VALID_BASECLASS_NAMES - .iter() - .any(|cb| input.trim().starts_with(cb)) - { - input - } else { - return Err(nom::Err::Error(nom::error::Error::new( - input, - nom::error::ErrorKind::Tag, - ))); - }; - let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; - let (input, _) = tag("[")(input)?; - let (input, _) = take_while1(|char| is_ident_char(char) && char != ':')(input)?; - let (input, _) = tag(":")(input)?; - let (input, _) = tag("var(--")(input)?; - let (input, _) = take_while1(|char| is_ident_char(char) && char != ')')(input)?; - let (input, _) = tag(")]")(input)?; - Ok((input, ())) -} - -// group/edit -fn arbitrary_group_classname(input: &str) -> IResult<&str, ()> { - let (input, _) = alt((tag("group"),))(input)?; - let (input, _) = tag("/")(input)?; - let (input, _) = take_while1(is_ident_char)(input)?; - Ok((input, ())) -} - -fn parse_single_tw_classname(input: &str) -> IResult<&str, ()> { - alt(( - // bg-[url('/what_a_rush.png')] - bg_arbitrary_url, - // bg-black/25 - predefined_colorful_opacity, - // group/edit - arbitrary_group_classname, - // bg-black/[27] - arbitrary_opacity, - // btn - parse_predefined_tw_classname, - // [mask-type:luminance] [mask-type:alpha] - kv_pair_classname, - // text-[22px] - lengthy_arbitrary_classname, - // text-[#bada55] - colorful_arbitrary_baseclass, - // before:content-['Festivus'] - arbitrary_content, - // content-[>] content-[<] - arbitrary_with_arrow, - // bg-[--my-color] - arbitrary_css_var, - // text-[var(--my-var)] - arbitrary_css_var2, - // text-[length:var(--my-var)] - arbitrary_css_var3, - // grid-cols-[fit-content(theme(spacing.32))] - arbitrary_css_value, - ))(input) -} - -// hover:underline -fn predefined_modifier(input: &str) -> IResult<&str, ()> { - let (input, modifier) = recognize(|i| { - // Assuming a Tailwind class consists of alphanumeric, dashes, and colons - nom::bytes::complete::is_a( - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-", - )(i) - })(input)?; - - if is_valid_modifier(modifier) { - Ok((input, ())) - } else { - Err(nom::Err::Error(nom::error::Error::new( - input, - nom::error::ErrorKind::Tag, - ))) - } -} - -// predefined special modifiers e.g peer-checked:p-4 group-hover:visible -fn predefined_special_modifier(input: &str) -> IResult<&str, ()> { - let (input, _) = alt(( - // peer-checked:p-4 - tuple((tag("peer-"), predefined_modifier)), - // group-hover:visible - tuple((tag("group-"), predefined_modifier)), - ))(input)?; - Ok((input, ())) -} - -// [&:nth-child(3)]:underline -// [&_p]:mt-4 -fn arbitrary_front_selector_modifier(input: &str) -> IResult<&str, ()> { - let (input, _) = tag("[&")(input)?; - let (input, _) = take_until("]")(input)?; - let (input, _) = tag("]")(input)?; - Ok((input, ())) -} - -// group-[:nth-of-type(3)_&]:block -fn arbitrary_back_selector_modifier(input: &str) -> IResult<&str, ()> { - let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; - let (input, _) = tag("-[")(input)?; - let (input, _) = take_until("&]")(input)?; - let (input, _) = tag("&]")(input)?; - Ok((input, ())) -} - -// [@supports(display:grid)]:grid -fn arbitrary_at_supports_rule_modifier(input: &str) -> IResult<&str, ()> { - let (input, _) = tag("[@supports(")(input)?; - let (input, _) = take_until(")")(input)?; - let (input, _) = tag(")]")(input)?; - Ok((input, ())) -} - -// [@media(any-hover:hover){&:hover}]:opacity-100 -fn arbitrary_at_media_rule_modifier(input: &str) -> IResult<&str, ()> { - // starts with [@media and ends with ] - let (input, _) = tag("[@media(")(input)?; - let (input, _) = take_until("]")(input)?; - let (input, _) = tag("]")(input)?; - Ok((input, ())) -} - -// group/edit invisible hover:bg-slate-200 group-hover/item:visible -fn group_peer_modifier(input: &str) -> IResult<&str, ()> { - let (input, _) = alt(( - tuple((tag("group-"), predefined_modifier)), - // https://tailwindcss.com/docs/hover-focus-and-other-states#differentiating-peers - // peer-checked/published:text-sky-500 - tuple((tag("peer-"), predefined_modifier)), - ))(input)?; - let (input, _) = tag("/")(input)?; - let (input, _) = take_while1(is_ident_char)(input)?; - Ok((input, ())) -} - -// hidden group-[.is-published]:block -// group-[:nth-of-type(3)_&]:block -// peer-[.is-dirty]:peer-required:block hidden -// hidden peer-[:nth-of-type(3)_&]:block -fn group_modifier_selector(input: &str) -> IResult<&str, ()> { - let (input, _) = alt((tag("group"), tag("peer")))(input)?; - let (input, _) = tag("-[")(input)?; - let (input, _) = take_until("]")(input)?; - let (input, _) = tag("]")(input)?; - Ok((input, ())) -} - - is_valid_last_word -// supports-[backdrop-filter] -fn supports_arbitrary(input: &str) -> IResult<&str, ()> { - let (input, _) = tag("supports-[")(input)?; - let (input, _) = take_until("]")(input)?; - let (input, _) = tag("]")(input)?; - Ok((input, ())) -} - -fn is_valid_group_classname(class_name: &str) -> bool { - !class_name.contains(':') - && !class_name.contains('[') - && !class_name.contains(']') - && class_name.starts_with("group/") -// aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] -// aria-[sort=descending]:bg-[url('/img/up-arrow.svg')] -fn aria_arbitrary(input: &str) -> IResult<&str, ()> { - let (input, _) = opt(tag("group-"))(input)?; - let (input, _) = tag("aria-[")(input)?; - let (input, _) = take_while1(is_ident_char)(input)?; - let (input, _) = tag("=")(input)?; - let (input, _) = take_while1(is_ident_char)(input)?; - let (input, _) = tag("]")(input)?; - Ok((input, ())) -} - -fn is_valid_string(s: &str) -> bool { - // Matches strings that contain only alphanumeric characters, underscores, and hyphens. - let re = Regex::new(r"^[a-zA-Z0-9_-]*$").expect("Invalid regex"); - re.is_match(s) && !s.is_empty() -// data-[size=large]:p-8 -fn data_arbitrary(input: &str) -> IResult<&str, ()> { - let (input, _) = tag("data-[")(input)?; - let (input, _) = take_while1(is_ident_char)(input)?; - let (input, _) = tag("=")(input)?; - let (input, _) = take_while1(is_ident_char)(input)?; - let (input, _) = tag("]")(input)?; - Ok((input, ())) -} - -// min-[320px]:text-center max-[600px]:bg-sky-300 -fn min_max_arbitrary_modifier(input: &str) -> IResult<&str, ()> { - let (input, _) = alt((tag("min-"), tag("max-")))(input)?; - let (input, _) = tag("[")(input)?; - let (input, _) = parse_length_unit(input)?; - let (input, _) = tag("]")(input)?; - Ok((input, ())) -} - -fn modifier(input: &str) -> IResult<&str, ()> { - alt(( - group_modifier_selector, - group_peer_modifier, - predefined_special_modifier, - arbitrary_front_selector_modifier, - arbitrary_back_selector_modifier, - arbitrary_at_supports_rule_modifier, - arbitrary_at_media_rule_modifier, - predefined_modifier, - supports_arbitrary, - aria_arbitrary, - data_arbitrary, - min_max_arbitrary_modifier, - ))(input) -} - -fn modifiers_chained(input: &str) -> IResult<&str, ()> { - let (input, _modifiers) = separated_list0(tag(":"), modifier)(input)?; - Ok((input, ())) -} - -fn parse_tw_full_classname(input: &str) -> IResult<&str, Vec<&str>> { - let (input, _class_names) = tuple(( - opt(tuple((modifiers_chained, tag(":")))), - parse_single_tw_classname, - ))(input)?; - - Ok((input, vec![])) -} - -// Edge cases -// [&:nth-child(3)]:underline -// lg:[&:nth-child(3)]:hover:underline -// [&_p]:mt-4 -// flex [@supports(display:grid)]:grid -// [@media(any-hover:hover){&:hover}]:opacity-100 -// group/edit invisible hover:bg-slate-200 group-hover/item:visible -// hidden group-[.is-published]:block -// group-[:nth-of-type(3)_&]:block -// peer-checked/published:text-sky-500 -// peer-[.is-dirty]:peer-required:block hidden -// hidden peer-[:nth-of-type(3)_&]:block -// after:content-['*'] after:ml-0.5 after:text-red-500 block text-sm font-medium text-slate-700 -// before:content-[''] before:block -// bg-black/75 supports-[backdrop-filter]:bg-black/25 supports-[backdrop-filter]:backdrop-blur -// aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg-[url('/img/up-arrow.svg')] -// group-aria-[sort=ascending]:rotate-0 group-aria-[sort=descending]:rotate-180 -// data-[size=large]:p-8 -// open:bg-white dark:open:bg-slate-900 open:ring-1 open:ring-black/5 dark:open:ring-white/10 open:shadow-lg p-6 rounded-lg -// lg:[&:nth-child(3)]:hover:underline -// min-[320px]:text-center max-[600px]:bg-sky-300 -// top-[117px] lg:top-[344px] -// bg-[#bada55] text-[22px] before:content-['Festivus'] -// grid grid-cols-[fit-content(theme(spacing.32))] -// bg-[--my-color] -// [mask-type:luminance] hover:[mask-type:alpha] -// [--scroll-offset:56px] lg:[--scroll-offset:44px] -// lg:[&:nth-child(3)]:hover:underline -// bg-[url('/what_a_rush.png')] -// before:content-['hello\_world'] -// text-[22px] -// text-[#bada55] -// text-[var(--my-var)] -// text-[length:var(--my-var)] -// text-[color:var(--my-var)] -fn parse_class_names(input: &str) -> IResult<&str, Vec<&str>> { - let (input, _) = multispace0(input)?; - let (input, _class_names) = separated_list0(multispace1, parse_tw_full_classname)(input)?; - let (input, _) = multispace0(input)?; - - Ok((input, vec![])) -} - -fn parse_top(input: &str) -> IResult<&str, Vec<&str>> { - all_consuming(parse_class_names)(input) -} - -#[proc_macro] -pub fn tw(raw_input: TokenStream) -> TokenStream { - let r_input = raw_input.clone(); - let input = parse_macro_input!(r_input as LitStr); - let (_modifiers, _valid_class_names) = match setup(&input) { - Ok(value) => value, - Err(value) => { - return syn::Error::new_spanned(input, value) - .to_compile_error() - .into() - } - }; - let full_classnames = input.value(); - - let (input, _class_names) = match parse_top(&full_classnames) { - Ok(value) => value, - Err(value) => { - return syn::Error::new_spanned(input, value) - .to_compile_error() - .into() - } - }; - - quote::quote! { - #input - } - .into() -} - 24 changes: 24 additions & 0 deletions24 -tw-macro/src/tailwind/colorful.rs -@@ -0,0 +1,24 @@ -pub const COLORFUL_BASECLASSES: [&str; 22] = [ - "text", - "bg", - "border", - "border-x", - "border-y", - "border-s", - "border-e", - "border-t", - "border-r", - "border-b", - "border-l", - "divide", - "outline", - "ring", - "ring-offset", - "shadow", - "caret", - "accent", - "fill", - "stroke", - "placeholder", - "decoration", -]; - 1 change: 1 addition & 0 deletions1 -tw-macro/src/tailwind/mod.rs -@@ -4,6 +4,7 @@ - * Copyright (c) 2023 Oyelowo Oyedayo - * Licensed under the MIT license - */ -pub mod colorful; -pub mod default_classnames; -pub mod lengthy; -pub mod modifiers; - 3 changes: 3 additions & 0 deletions3 -tw-macro/src/tailwind/signable.rs -@@ -45,4 +45,7 @@ pub const SIGNABLES: [&str; 40] = [ - "grid-auto-columns", - "z", - "order", - // "scroll-mx", - // "scroll-my", - // "scroll-m", -]; -Footer -© 2023 GitHub, Inc. -Footer navigation -Terms -Privacy -Security -Status -Docs -Contact GitHub -Pricing -API -Training -Blog -About -@@ -4,116 +4,31 @@ * Copyright (c) 2023 Oyelowo Oyedayo * Licensed under the MIT license */ use nom::{ branch::alt, bytes::complete::{tag, take_until, take_while1}, character::complete::{digit1, multispace0, multispace1}, combinator::{all_consuming, not, opt, recognize}, multi::separated_list0, number, sequence::{preceded, tuple}, IResult, }; use syn::{parse_macro_input, LitStr}; mod config; mod plugins; mod tailwind; use tailwind::{ lengthy::LENGTHY, modifiers::get_modifiers, tailwind_config::CustomisableClasses, valid_baseclass_names::VALID_BASECLASS_NAMES, colorful::COLORFUL_BASECLASSES, lengthy::LENGTHY, modifiers::get_modifiers, tailwind_config::CustomisableClasses, valid_baseclass_names::VALID_BASECLASS_NAMES, }; use config::{get_classes, noconfig::UNCONFIGURABLE, read_tailwind_config}; use proc_macro::TokenStream; use regex::{self, Regex}; use tailwind::signable::SIGNABLES; // use tailwindcss_core::parser::{Extractor, ExtractorOptions}; #[proc_macro] pub fn tw(raw_input: TokenStream) -> TokenStream { let r_input = raw_input.clone(); let input = parse_macro_input!(r_input as LitStr); let (modifiers, valid_class_names) = match setup(&input) { Ok(value) => value, Err(value) => { return syn::Error::new_spanned(input, value) .to_compile_error() .into() } }; for word in input.value().split_whitespace() { let (last_word_signed, last_word_unsigned) = get_last_word_types(word); // modifiers e.g hover: in // hover:[mask-type:alpha] let is_valid_arb_prop = is_valid_arb_prop(word, &modifiers); let is_valid_class = is_valid_class(is_valid_arb_prop, &valid_class_names, last_word_unsigned); let (base_classname, arbitrary_value_with_bracket) = last_word_unsigned.split_once("-[").unwrap_or_default(); let is_valid_negative_baseclass = is_valid_negative_baseclass( &valid_class_names, last_word_unsigned, last_word_signed, is_valid_arb_prop, ); let prefix_is_valid_tailwind_keyword = VALID_BASECLASS_NAMES.contains(&base_classname); let is_arbitrary_value = prefix_is_valid_tailwind_keyword && arbitrary_value_with_bracket.ends_with(']'); let arbitrary_value = arbitrary_value_with_bracket.trim_end_matches(']'); let is_lengthy_class = LENGTHY.contains(&base_classname); let is_valid_length = is_arbitrary_value && is_lengthy_class && (is_valid_length(arbitrary_value) || is_valid_calc(arbitrary_value)); let has_arb_variant = has_arb_variant(word); let is_valid_opacity = is_valid_opacity(last_word_unsigned, &valid_class_names); if (is_valid_class && is_valid_modifier(word, &modifiers)) || is_valid_negative_baseclass || (!is_lengthy_class && is_arbitrary_value) || is_valid_length || is_valid_arb_prop || has_arb_variant || is_valid_opacity || is_valid_group_classname(last_word_unsigned) || is_validate_modifier_or_group(word, &modifiers, &valid_class_names) { // if check_word(word, false).is_empty() { // return syn::Error::new_spanned(input, format!("Invalid string: {}", word)) // .to_compile_error() // .into(); // } } else { return syn::Error::new_spanned(input, format!("Invalid string: {word}")) .to_compile_error() .into(); } } raw_input } // fn check_word(input: &str, loose: bool) -> Vec<&str> { // Extractor::unique_ord( // input.as_bytes(), // ExtractorOptions { // preserve_spaces_in_arbitrary: loose, // }, // ) // .into_iter() // .map(|s| unsafe { std::str::from_utf8_unchecked(s) }) // .collect() // } fn is_valid_length(value: &str) -> bool { let re = regex::Regex::new(r"^(-?\d+(\.?\d+)?(px|em|rem|%|cm|mm|in|pt|pc|vh|vw|vmin|vmax)|0)$") .expect("Invalid regex"); re.is_match(value) } fn is_valid_calc(value: &str) -> bool { let re = regex::Regex::new(r"^calc\([^)]+\)$").expect("Invalid regex"); re.is_match(value) } fn setup(input: &LitStr) -> Result<(Vec, Vec), TokenStream> { let config = &(match read_tailwind_config() { Ok(config) => config, @@ -146,199 +61,686 @@ fn setup(input: &LitStr) -> Result<(Vec, Vec), TokenStream> { Ok((modifiers, valid_class_names)) } fn get_last_word_types(word: &str) -> (&str, &str) { let modifiers_and_class = word.split(':'); fn get_classes_straight() -> Vec { get_classes(&read_tailwind_config().unwrap()) } fn is_valid_classname(class_name: &str) -> bool { get_classes_straight().contains(&class_name.to_string()) } fn is_valid_modifier(modifier: &str) -> bool { get_modifiers(&read_tailwind_config().unwrap()).contains(&modifier.to_string()) } fn parse_predefined_tw_classname(input: &str) -> IResult<&str, ()> { let (input, class_name) = recognize(|i| { // Considering a Tailwind class consists of alphanumeric, dashes, and slash nom::bytes::complete::is_a( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-./", )(i) })(input)?; let is_signable = SIGNABLES.iter().any(|s| { class_name .strip_prefix('-') .unwrap_or(class_name) .starts_with(s) }); if is_signable && is_valid_classname(class_name.strip_prefix('-').unwrap_or(class_name)) || !is_signable && is_valid_classname(class_name) { Ok((input, ())) } else { Err(nom::Err::Error(nom::error::Error::new( input, nom::error::ErrorKind::Tag, ))) } } fn is_ident_char(c: char) -> bool { c.is_alphanumeric() || c == '_' || c == '-' } fn is_lengthy_classname(class_name: &str) -> bool { LENGTHY.contains(&class_name.strip_prefix('-').unwrap_or(class_name)) } // Custom number parser that handles optional decimals and signs, and scientific notation fn float_strict(input: &str) -> IResult<&str, f64> { let (input, number) = recognize(tuple(( opt(alt((tag("-"), tag("+")))), digit1, opt(preceded(tag("."), digit1)), opt(tuple(( alt((tag("e"), tag("E"))), opt(alt((tag("-"), tag("+")))), digit1, ))), )))(input)?; let float_val: f64 = number.parse().unwrap(); Ok((input, float_val)) } fn parse_length_unit(input: &str) -> IResult<&str, String> { let (input, number) = float_strict(input)?; let (input, unit) = { // px|em|rem|%|cm|mm|in|pt|pc|vh|vw|vmin|vmax alt(( tag("px"), tag("em"), tag("rem"), tag("%"), tag("cm"), tag("mm"), tag("in"), tag("pt"), tag("pc"), tag("vh"), tag("vw"), tag("vmin"), tag("vmax"), // TODO: Should i allow unitless values? Would need something like this in caller // location if so: // let (input, _) = alt((parse_length_unit, parse_number))(input)?; tag(""), )) }(input)?; Ok((input, format!("{}{}", number, unit))) } // let is_arbitrary_property = word.starts_with('[') && word.ends_with(']'); let last_word_signed = modifiers_and_class.clone().last().unwrap_or_default(); let last_word_unsigned = last_word_signed .strip_prefix('-') .unwrap_or(last_word_signed); // text-[22px] fn lengthy_arbitrary_classname(input: &str) -> IResult<&str, ()> { let (input, class_name) = take_until("-[")(input)?; let (input, _) = if is_lengthy_classname(class_name) { Ok((input, ())) } else { Err(nom::Err::Error(nom::error::Error::new( input, nom::error::ErrorKind::Tag, ))) }?; // arbitrary value let (input, _) = tag("-")(input)?; let (input, _) = tag("[")(input)?; // is number let (input, _) = parse_length_unit(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } (last_word_signed, last_word_unsigned) // #bada55 fn parse_hex_color(input: &str) -> IResult<&str, String> { let (input, _) = tag("#")(input)?; let (input, color) = take_while1(|c: char| c.is_ascii_hexdigit())(input)?; let (input, _) = if color.chars().count() == 3 || color.chars().count() == 6 { Ok((input, ())) } else { Err(nom::Err::Error(nom::error::Error::new( input, nom::error::ErrorKind::Tag, ))) }?; let color = format!("#{}", color); Ok((input, color)) } fn is_valid_modifier(word: &str, modifiers: &[String]) -> bool { let modifiers_and_class = word.split(':'); let modifiers_from_word = modifiers_and_class .clone() .take(modifiers_and_class.count() - 1) .collect::>(); modifiers_from_word fn parse_u8(input: &str) -> IResult<&str, u8> { let (input, num) = number::complete::double(input)?; let input = match num as u32 { 0..=255 => input, _ => { return Err(nom::Err::Error(nom::error::Error::new( input, nom::error::ErrorKind::Tag, ))) } }; Ok((input, num as u8)) } // rgb(255, 255, 255) rgb(255_255_255) fn parse_rgb_color(input: &str) -> IResult<&str, String> { let (input, _) = tag("rgb(")(input)?; let (input, r) = parse_u8(input)?; let (input, _) = alt((tag(","), tag("_")))(input)?; let (input, g) = parse_u8(input)?; let (input, _) = alt((tag(","), tag("_")))(input)?; let (input, b) = parse_u8(input)?; let (input, _) = tag(")")(input)?; let color = format!("rgb({}, {}, {})", r, g, b); Ok((input, color)) } // rgba(255, 255, 255, 0.5) rgba(255_255_255_0.5) fn parse_rgba_color(input: &str) -> IResult<&str, String> { let (input, _) = tag("rgba(")(input)?; let (input, r) = parse_u8(input)?; let (input, _) = alt((tag(","), tag("_")))(input)?; let (input, g) = parse_u8(input)?; let (input, _) = alt((tag(","), tag("_")))(input)?; let (input, b) = parse_u8(input)?; let (input, _) = alt((tag(","), tag("_")))(input)?; let (input, a) = number::complete::double(input)?; let (input, _) = tag(")")(input)?; let color = format!("rgba({}, {}, {}, {})", r, g, b, a); Ok((input, color)) } fn is_colorful_baseclass(class_name: &str) -> bool { COLORFUL_BASECLASSES.contains(&class_name) } // text-[#bada55] fn colorful_arbitrary_baseclass(input: &str) -> IResult<&str, ()> { let (input, class_name) = take_until("-[")(input)?; let (input, _) = if is_colorful_baseclass(class_name) { Ok((input, ())) } else { Err(nom::Err::Error(nom::error::Error::new( input, nom::error::ErrorKind::Tag, ))) }?; // arbitrary value let (input, _) = tag("-")(input)?; let (input, _) = tag("[")(input)?; let (input, _) = alt((parse_hex_color, parse_rgb_color, parse_rgba_color))(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } // e.g: [mask-type:alpha] fn kv_pair_classname(input: &str) -> IResult<&str, ()> { let (input, _) = tag("[")(input)?; let (input, _) = take_while1(is_ident_char)(input)?; let (input, _) = tag(":")(input)?; let (input, _) = take_until("]")(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } // before:content-['Festivus'] fn arbitrary_content(input: &str) -> IResult<&str, ()> { let (input, _) = tag("content-['")(input)?; let (input, _) = take_until("']")(input)?; let (input, _) = tag("']")(input)?; Ok((input, ())) } // content-[>] content-[<] fn arbitrary_with_arrow(input: &str) -> IResult<&str, ()> { let (input, _) = take_while1(is_ident_char)(input)?; let (input, _) = tag("[")(input)?; let (input, _) = alt((tag(">"), tag("<")))(input)?; let (input, _) = take_until("]")(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } // bg-black/25 fn predefined_colorful_opacity(input: &str) -> IResult<&str, ()> { let input = if COLORFUL_BASECLASSES .iter() .all(|modifier| modifiers.contains(&modifier.to_string())) } fn is_valid_opacity(last_word_unsigned: &str, valid_class_names: &[String]) -> bool { let is_valid_opacity = { let (class_name, opacity_raw) = last_word_unsigned.split_once('/').unwrap_or_default(); let opacity_arb = opacity_raw .trim_start_matches('[') .trim_end_matches(']') .parse::(); let is_valid_number = opacity_arb.is_ok_and(|opacity_num| (0.0..=100.0).contains(&opacity_num)); valid_class_names.contains(&class_name.to_string()) && is_valid_number .any(|cb| input.trim().starts_with(cb)) { input } else { return Err(nom::Err::Error(nom::error::Error::new( input, nom::error::ErrorKind::Tag, ))); }; is_valid_opacity } fn has_arb_variant(word: &str) -> bool { // lg:[&:nth-child(3)]:hover:underline // [&_p]:mt-4 // flex [@supports(display:grid)]:grid // [@media(any-hover:hover){&:hover}]:opacity-100 let has_arb_variant = { // lg:[&:nth-child(3)]:hover:underline => :nth-child(3) // [&_p]:mt-4 => _p let mut ampersand_variant_selector = word.split("[@").last().unwrap_or_default().split("]:"); let and_variant_selector = word.split("[&").last().unwrap_or_default().split("]:"); let is_valid_arbitrary_variant_selector = ampersand_variant_selector.clone().count() >= 2 && !ampersand_variant_selector .next() .unwrap_or_default() .is_empty(); let is_valid_arbitrary_variant_queries = and_variant_selector.clone().count() >= 2 && !and_variant_selector .clone() .last() .unwrap_or_default() .split("]:") .next() .unwrap_or_default() .is_empty(); let is_query = word.starts_with("[@"); is_valid_arbitrary_variant_selector || is_valid_arbitrary_variant_queries || is_query // && // ((!is_query && !word.split("[&").next().unwrap_or_default().is_empty() && word.split(":[&").count() >= 2) || is_query) let (input, _) = take_while1(|char| is_ident_char(char) && char != '/')(input)?; // let (input, _) = take_until("/")(input)?; let (input, _) = tag("/")(input)?; let (input, num) = number::complete::double(input)?; let input = match num as u8 { 0..=100 => input, _ => { return Err(nom::Err::Error(nom::error::Error::new( input, nom::error::ErrorKind::Tag, ))) } }; has_arb_variant } fn is_valid_negative_baseclass( valid_class_names: &[String], last_word_unsigned: &str, last_word_signed: &str, is_valid_arb_prop: bool, ) -> bool { let is_valid_negative_baseclass = { // tw!("-m-4 p-4 p-4"); (valid_class_names.contains(&last_word_unsigned.to_string()) && last_word_signed.starts_with('-') && SIGNABLES .iter() .any(|s| (last_word_unsigned.starts_with(s)))) || (is_valid_arb_prop && last_word_signed.starts_with('-') && SIGNABLES.iter().any(|s| last_word_unsigned.starts_with(s))) Ok((input, ())) } // bg-black/[27] bg-black/[27%] fn arbitrary_opacity(input: &str) -> IResult<&str, ()> { let input = if COLORFUL_BASECLASSES .iter() .any(|cb| input.trim().starts_with(cb)) { input } else { return Err(nom::Err::Error(nom::error::Error::new( input, nom::error::ErrorKind::Tag, ))); }; is_valid_negative_baseclass } fn is_valid_class( is_valid_arb_prop: bool, valid_class_names: &[String], last_word_unsigned: &str, ) -> bool { !is_valid_arb_prop && valid_class_names.contains(&last_word_unsigned.to_string()) } fn is_valid_arb_prop(word: &str, modifiers: &[String]) -> bool { // TODO: check the first and the last character are not open and close brackets // respectively i.e arbitrary property e.g [mask_type:aplha]; // hover:[mask-type:alpha]; let mut word_for_arb_prop = word.split(":["); word_for_arb_prop .next() // e.g for hover:[mask-type:alpha], this will be hover, // for [mask-type:alpha], this will be [mask-type:alpha] .is_some_and(|modifiers_or_full_arb_prop| { let is_arbitrary_property = modifiers_or_full_arb_prop.starts_with('[') && modifiers_or_full_arb_prop.ends_with(']'); let is_valid = if is_arbitrary_property { modifiers_or_full_arb_prop.matches('[').count() == 1 && modifiers_or_full_arb_prop.matches(']').count() == 1 && modifiers_or_full_arb_prop .trim_start_matches('[') .trim_end_matches(']') .split(':') .count() == 2 } else { // e.g mask-type:alpha] in hover:[mask-type:alpha] let full_arb_prop = word_for_arb_prop.next().unwrap_or_default(); // e.g for single, hover in hover:[mask-type:alpha] // for multiple, hover:first:last, in hover:first:last:[mask-type:alpha] modifiers_or_full_arb_prop .split(':') .all(|modifier| modifiers.contains(&modifier.to_string())) && full_arb_prop.matches(']').count() == 1 && full_arb_prop .trim_end_matches(']') .split(':') .count() == 2 }; is_valid }) || // value e.g [mask-type:alpha] in hover:[mask-type:alpha] // potential addition checks(probably not a good idea. Imagine a new css property, we would // have to open a PR for every new or esoteric css property.) word_for_arb_prop.next().is_some_and(|value| { value.ends_with(']') && value.split(':').count() == 2 // We had already split by ":[", so there should be no "[" anymore && value.matches('[').count() == 0 && value.matches(']').count() == 1 }) } fn is_valid_group_pattern(modifier: &str, valid_modifiers: &[String]) -> bool { let parts: Vec<&str> = modifier.split('/').collect(); let group_modifier = parts[0]; parts.len() == 2 && valid_modifiers.contains(&group_modifier.to_string()) && group_modifier.starts_with("group") } // tw!("group/edit invisible hover:bg-slate-200 group-hover/item:visible"); // tw!("group-[:nth-of-type(3)_&]:block group-hover/edit:text-gray-700 group-[:nth-of-type(3)_&]:block"); fn is_validate_modifier_or_group( word: &str, valid_modifiers: &[String], valid_class_names: &[String], ) -> bool { let valid_arb_group = word.split(':').collect::>(); let modifiers = &valid_arb_group[..valid_arb_group.len() - 1]; let last_word = valid_arb_group.last().unwrap_or(&""); let is_valid_last_word = is_valid_string(last_word) && valid_class_names.contains(&last_word.to_string()); for modifier in modifiers { if modifier.starts_with("group") { if !is_valid_group_pattern(modifier, valid_modifiers) && is_valid_last_word { return false; } } else if !valid_modifiers.contains(&modifier.to_string()) && is_valid_last_word { return false; let (input, _) = take_while1(|char| is_ident_char(char) && char != '/')(input)?; let (input, _) = tag("/")(input)?; let (input, _) = tag("[")(input)?; // 0-100 integer let (input, num) = number::complete::double(input)?; let input = match num as u8 { 0..=100 => input, _ => { return Err(nom::Err::Error(nom::error::Error::new( input, nom::error::ErrorKind::Tag, ))) } }; let (input, _) = opt(tag("%"))(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } // bg-[url('/img/down-arrow.svg')] fn bg_arbitrary_url(input: &str) -> IResult<&str, ()> { // prefixed by baseclass let input = if COLORFUL_BASECLASSES .iter() .any(|cb| input.trim().starts_with(cb)) { input } else { return Err(nom::Err::Error(nom::error::Error::new( input, nom::error::ErrorKind::Tag, ))); }; let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; let (input, _) = tag("[")(input)?; let (input, _) = tag("url('")(input)?; let (input, _) = take_until("')")(input)?; let (input, _) = tag("')")(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } // grid-cols-[fit-content(theme(spacing.32))] fn arbitrary_css_value(input: &str) -> IResult<&str, ()> { // is prefixed by valid base class // take until -[ let (input, base_class) = take_until("-[")(input)?; let input = if VALID_BASECLASS_NAMES .iter() .any(|cb| base_class.trim().eq(*cb)) { input } else { return Err(nom::Err::Error(nom::error::Error::new( base_class, nom::error::ErrorKind::Tag, ))); }; let (input, _) = tag("-[")(input)?; let (input, _) = not(alt(( tag("--"), tag("var(--"), // :var(-- )))(input)?; let (input, _) = take_while1(|char| is_ident_char(char) && char != '(')(input)?; let (input, _) = tag("(")(input)?; let (input, _) = take_until(")]")(input)?; // allow anything inthe brackets let (input, _) = take_until("]")(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } // bg-[--my-color] fn arbitrary_css_var(input: &str) -> IResult<&str, ()> { // is prefixed by valid base class let input = if VALID_BASECLASS_NAMES .iter() .any(|cb| input.trim().starts_with(cb)) { input } else { return Err(nom::Err::Error(nom::error::Error::new( input, nom::error::ErrorKind::Tag, ))); }; let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; let (input, _) = tag("[")(input)?; let (input, _) = tag("--")(input)?; let (input, _) = take_while1(|char| is_ident_char(char) && char != ']')(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } // text-[var(--my-var)] fn arbitrary_css_var2(input: &str) -> IResult<&str, ()> { // is prefixed by valid base class let input = if VALID_BASECLASS_NAMES .iter() .any(|cb| input.trim().starts_with(cb)) { input } else { return Err(nom::Err::Error(nom::error::Error::new( input, nom::error::ErrorKind::Tag, ))); }; let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; let (input, _) = tag("[")(input)?; let (input, _) = tag("var(--")(input)?; let (input, _) = take_while1(|char| is_ident_char(char) && char != ')')(input)?; let (input, _) = tag(")]")(input)?; Ok((input, ())) } // text-[length:var(--my-var)] fn arbitrary_css_var3(input: &str) -> IResult<&str, ()> { // is prefixed by valid base class let input = if VALID_BASECLASS_NAMES .iter() .any(|cb| input.trim().starts_with(cb)) { input } else { return Err(nom::Err::Error(nom::error::Error::new( input, nom::error::ErrorKind::Tag, ))); }; let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; let (input, _) = tag("[")(input)?; let (input, _) = take_while1(|char| is_ident_char(char) && char != ':')(input)?; let (input, _) = tag(":")(input)?; let (input, _) = tag("var(--")(input)?; let (input, _) = take_while1(|char| is_ident_char(char) && char != ')')(input)?; let (input, _) = tag(")]")(input)?; Ok((input, ())) } // group/edit fn arbitrary_group_classname(input: &str) -> IResult<&str, ()> { let (input, _) = alt((tag("group"),))(input)?; let (input, _) = tag("/")(input)?; let (input, _) = take_while1(is_ident_char)(input)?; Ok((input, ())) } fn parse_single_tw_classname(input: &str) -> IResult<&str, ()> { alt(( // bg-[url('/what_a_rush.png')] bg_arbitrary_url, // bg-black/25 predefined_colorful_opacity, // group/edit arbitrary_group_classname, // bg-black/[27] arbitrary_opacity, // btn parse_predefined_tw_classname, // [mask-type:luminance] [mask-type:alpha] kv_pair_classname, // text-[22px] lengthy_arbitrary_classname, // text-[#bada55] colorful_arbitrary_baseclass, // before:content-['Festivus'] arbitrary_content, // content-[>] content-[<] arbitrary_with_arrow, // bg-[--my-color] arbitrary_css_var, // text-[var(--my-var)] arbitrary_css_var2, // text-[length:var(--my-var)] arbitrary_css_var3, // grid-cols-[fit-content(theme(spacing.32))] arbitrary_css_value, ))(input) } // hover:underline fn predefined_modifier(input: &str) -> IResult<&str, ()> { let (input, modifier) = recognize(|i| { // Assuming a Tailwind class consists of alphanumeric, dashes, and colons nom::bytes::complete::is_a( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-", )(i) })(input)?; if is_valid_modifier(modifier) { Ok((input, ())) } else { Err(nom::Err::Error(nom::error::Error::new( input, nom::error::ErrorKind::Tag, ))) } } // predefined special modifiers e.g peer-checked:p-4 group-hover:visible fn predefined_special_modifier(input: &str) -> IResult<&str, ()> { let (input, _) = alt(( // peer-checked:p-4 tuple((tag("peer-"), predefined_modifier)), // group-hover:visible tuple((tag("group-"), predefined_modifier)), ))(input)?; Ok((input, ())) } // [&:nth-child(3)]:underline // [&_p]:mt-4 fn arbitrary_front_selector_modifier(input: &str) -> IResult<&str, ()> { let (input, _) = tag("[&")(input)?; let (input, _) = take_until("]")(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } // group-[:nth-of-type(3)_&]:block fn arbitrary_back_selector_modifier(input: &str) -> IResult<&str, ()> { let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; let (input, _) = tag("-[")(input)?; let (input, _) = take_until("&]")(input)?; let (input, _) = tag("&]")(input)?; Ok((input, ())) } // [@supports(display:grid)]:grid fn arbitrary_at_supports_rule_modifier(input: &str) -> IResult<&str, ()> { let (input, _) = tag("[@supports(")(input)?; let (input, _) = take_until(")")(input)?; let (input, _) = tag(")]")(input)?; Ok((input, ())) } // [@media(any-hover:hover){&:hover}]:opacity-100 fn arbitrary_at_media_rule_modifier(input: &str) -> IResult<&str, ()> { // starts with [@media and ends with ] let (input, _) = tag("[@media(")(input)?; let (input, _) = take_until("]")(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } // group/edit invisible hover:bg-slate-200 group-hover/item:visible fn group_peer_modifier(input: &str) -> IResult<&str, ()> { let (input, _) = alt(( tuple((tag("group-"), predefined_modifier)), // https://tailwindcss.com/docs/hover-focus-and-other-states#differentiating-peers // peer-checked/published:text-sky-500 tuple((tag("peer-"), predefined_modifier)), ))(input)?; let (input, _) = tag("/")(input)?; let (input, _) = take_while1(is_ident_char)(input)?; Ok((input, ())) } // hidden group-[.is-published]:block // group-[:nth-of-type(3)_&]:block // peer-[.is-dirty]:peer-required:block hidden // hidden peer-[:nth-of-type(3)_&]:block fn group_modifier_selector(input: &str) -> IResult<&str, ()> { let (input, _) = alt((tag("group"), tag("peer")))(input)?; let (input, _) = tag("-[")(input)?; let (input, _) = take_until("]")(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } is_valid_last_word // supports-[backdrop-filter] fn supports_arbitrary(input: &str) -> IResult<&str, ()> { let (input, _) = tag("supports-[")(input)?; let (input, _) = take_until("]")(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } fn is_valid_group_classname(class_name: &str) -> bool { !class_name.contains(':') && !class_name.contains('[') && !class_name.contains(']') && class_name.starts_with("group/") // aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] // aria-[sort=descending]:bg-[url('/img/up-arrow.svg')] fn aria_arbitrary(input: &str) -> IResult<&str, ()> { let (input, _) = opt(tag("group-"))(input)?; let (input, _) = tag("aria-[")(input)?; let (input, _) = take_while1(is_ident_char)(input)?; let (input, _) = tag("=")(input)?; let (input, _) = take_while1(is_ident_char)(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } fn is_valid_string(s: &str) -> bool { // Matches strings that contain only alphanumeric characters, underscores, and hyphens. let re = Regex::new(r"^[a-zA-Z0-9_-]*$").expect("Invalid regex"); re.is_match(s) && !s.is_empty() // data-[size=large]:p-8 fn data_arbitrary(input: &str) -> IResult<&str, ()> { let (input, _) = tag("data-[")(input)?; let (input, _) = take_while1(is_ident_char)(input)?; let (input, _) = tag("=")(input)?; let (input, _) = take_while1(is_ident_char)(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } // min-[320px]:text-center max-[600px]:bg-sky-300 fn min_max_arbitrary_modifier(input: &str) -> IResult<&str, ()> { let (input, _) = alt((tag("min-"), tag("max-")))(input)?; let (input, _) = tag("[")(input)?; let (input, _) = parse_length_unit(input)?; let (input, _) = tag("]")(input)?; Ok((input, ())) } fn modifier(input: &str) -> IResult<&str, ()> { alt(( group_modifier_selector, group_peer_modifier, predefined_special_modifier, arbitrary_front_selector_modifier, arbitrary_back_selector_modifier, arbitrary_at_supports_rule_modifier, arbitrary_at_media_rule_modifier, predefined_modifier, supports_arbitrary, aria_arbitrary, data_arbitrary, min_max_arbitrary_modifier, ))(input) } fn modifiers_chained(input: &str) -> IResult<&str, ()> { let (input, _modifiers) = separated_list0(tag(":"), modifier)(input)?; Ok((input, ())) } fn parse_tw_full_classname(input: &str) -> IResult<&str, Vec<&str>> { let (input, _class_names) = tuple(( opt(tuple((modifiers_chained, tag(":")))), parse_single_tw_classname, ))(input)?; Ok((input, vec![])) } // Edge cases // [&:nth-child(3)]:underline // lg:[&:nth-child(3)]:hover:underline // [&_p]:mt-4 // flex [@supports(display:grid)]:grid // [@media(any-hover:hover){&:hover}]:opacity-100 // group/edit invisible hover:bg-slate-200 group-hover/item:visible // hidden group-[.is-published]:block // group-[:nth-of-type(3)_&]:block // peer-checked/published:text-sky-500 // peer-[.is-dirty]:peer-required:block hidden // hidden peer-[:nth-of-type(3)_&]:block // after:content-['*'] after:ml-0.5 after:text-red-500 block text-sm font-medium text-slate-700 // before:content-[''] before:block // bg-black/75 supports-[backdrop-filter]:bg-black/25 supports-[backdrop-filter]:backdrop-blur // aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg-[url('/img/up-arrow.svg')] // group-aria-[sort=ascending]:rotate-0 group-aria-[sort=descending]:rotate-180 // data-[size=large]:p-8 // open:bg-white dark:open:bg-slate-900 open:ring-1 open:ring-black/5 dark:open:ring-white/10 open:shadow-lg p-6 rounded-lg // lg:[&:nth-child(3)]:hover:underline // min-[320px]:text-center max-[600px]:bg-sky-300 // top-[117px] lg:top-[344px] // bg-[#bada55] text-[22px] before:content-['Festivus'] // grid grid-cols-[fit-content(theme(spacing.32))] // bg-[--my-color] // [mask-type:luminance] hover:[mask-type:alpha] // [--scroll-offset:56px] lg:[--scroll-offset:44px] // lg:[&:nth-child(3)]:hover:underline // bg-[url('/what_a_rush.png')] // before:content-['hello\_world'] // text-[22px] // text-[#bada55] // text-[var(--my-var)] // text-[length:var(--my-var)] // text-[color:var(--my-var)] fn parse_class_names(input: &str) -> IResult<&str, Vec<&str>> { let (input, _) = multispace0(input)?; let (input, _class_names) = separated_list0(multispace1, parse_tw_full_classname)(input)?; let (input, _) = multispace0(input)?; Ok((input, vec![])) } fn parse_top(input: &str) -> IResult<&str, Vec<&str>> { all_consuming(parse_class_names)(input) } #[proc_macro] pub fn tw(raw_input: TokenStream) -> TokenStream { let r_input = raw_input.clone(); let input = parse_macro_input!(r_input as LitStr); let (_modifiers, _valid_class_names) = match setup(&input) { Ok(value) => value, Err(value) => { return syn::Error::new_spanned(input, value) .to_compile_error() .into() } }; let full_classnames = input.value(); let (input, _class_names) = match parse_top(&full_classnames) { Ok(value) => value, Err(value) => { return syn::Error::new_spanned(input, value) .to_compile_error() .into() } }; quote::quote! { #input } .into() } \ No newline at end of file diff --git a/tailwind/Cargo.toml b/tailwind/Cargo.toml index 9658225..2328c5c 100644 --- a/tailwind/Cargo.toml +++ b/tailwind/Cargo.toml @@ -9,7 +9,7 @@ documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -tw-macro = { workspace = true, features = ["daisyui"] } +twust = { workspace = true, features = ["daisyui"] } serde = "1.0.188" diff --git a/tailwind/src/lib.rs b/tailwind/src/lib.rs index c6ddf90..a9326f3 100644 --- a/tailwind/src/lib.rs +++ b/tailwind/src/lib.rs @@ -4,7 +4,7 @@ * Copyright (c) 2023 Oyelowo Oyedayo * Licensed under the MIT license */ -use tw_macro::tw; +use twust::tw; /// Invalid character in class name. /// diff --git a/tailwind/src/main.rs b/tailwind/src/main.rs index 93974db..4cbb02a 100644 --- a/tailwind/src/main.rs +++ b/tailwind/src/main.rs @@ -5,7 +5,7 @@ * Copyright (c) 2023 Oyelowo Oyedayo * Licensed under the MIT license */ -use tw_macro::tw; +use twust::tw; fn main() { let _ = tw!("btn btn"); diff --git a/tw-macro/Cargo.toml b/tw-macro/Cargo.toml index e90da69..d1444ab 100644 --- a/tw-macro/Cargo.toml +++ b/tw-macro/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "tw-macro" +name = "twust" version = { workspace = true } edition = { workspace = true } authors = { workspace = true }