From 3322e62d07007be63781fe6fa89c4c136ffaa60a Mon Sep 17 00:00:00 2001 From: yiffyrusdev Date: Fri, 13 Dec 2024 10:34:23 +0500 Subject: [PATCH] feature: implement :glob() --- sabry_intrnl/src/scoper/mod.rs | 89 +++++++++++++++++++++---- sabry_intrnl/src/syntax/mod.rs | 25 ++++++- sabry_procmacro_impl/src/impls/styly.rs | 3 +- 3 files changed, 100 insertions(+), 17 deletions(-) diff --git a/sabry_intrnl/src/scoper/mod.rs b/sabry_intrnl/src/scoper/mod.rs index 112ffe3..24696d3 100644 --- a/sabry_intrnl/src/scoper/mod.rs +++ b/sabry_intrnl/src/scoper/mod.rs @@ -2,7 +2,7 @@ use std::fmt::Debug; use cfg_if::cfg_if; use hash::ScopeHash; -use raffia::Spanned; +use raffia::{Span, Spanned}; use regex::Regex; use crate::{ @@ -109,7 +109,33 @@ impl<'s> HashedScope<'s> { .filter_map(|t| t.as_tag_name()) .map(|c| HashedSelector::from_tag(&hash, c)); - let mut hashed_selectors = classes.chain(ids).chain(tags).collect::>(); + // get global selectors + let globs = scope.adapter().glob_modified_selectors(); + let globs = globs + .iter() + .filter_map(|g| { + // we do ignore the latter selectors from :global(a, b) because there's no clear decision: + // how should we treat :glob(a, b) c {} + // as `a c {}` `b c {}` + // or as `a, b c {}` ? + g.arg.clone().and_then(|a| { + if let raffia::ast::PseudoClassSelectorArgKind::SelectorList(list) = a.kind { + list.selectors + .first() + .cloned() + .map(|fcl| (g.span.clone(), fcl.span.clone())) + } else { + None + } + }) + }) + .map(|c| HashedSelector::from_glob_mod(&hash, c, scope.adapter().source())); + + let mut hashed_selectors = classes + .chain(ids) + .chain(tags) + .chain(globs) + .collect::>(); // sorting by span start is important because of how hashed code construction works hashed_selectors.sort_by(|a, b| { @@ -151,6 +177,18 @@ pub struct HashedSelector { } impl HashedSelector { + pub fn from_glob_mod(hash: &ScopeHash, sel: (Span, Span), source: &str) -> Self { + let sel = ScopedSelector::from_glob_mod(sel, source); + let css_ident = Self::make_hashed_css(&sel, hash); + let html_ident = Self::make_hashed_html(&sel, hash); + + Self { + sel, + css_ident, + html_ident, + } + } + /// Construct the [HashedSelector], with given hash and parsed class selector pub fn from_class(hash: &ScopeHash, sel: &raffia::ast::ClassSelector) -> Self { let sel = ScopedSelector::from_class(sel); @@ -220,6 +258,7 @@ impl HashedSelector { } } } + ScopedSelector::Glob { raw, .. } => raw.clone(), } } @@ -254,6 +293,7 @@ impl HashedSelector { } } ScopedSelector::Tag(_) => None, + ScopedSelector::Glob { .. } => None, } } } @@ -264,7 +304,7 @@ pub struct ArbitrarySelector { /// CSS-ish identifier pub ident: String, /// Span in original CSS code where it comes from - pub span: raffia::Span, + pub span: Span, } /// Any unary CSS-ish selector, that's able to be scoped, and then hashed @@ -273,6 +313,11 @@ pub enum ScopedSelector { Class(ArbitrarySelector), Id(ArbitrarySelector), Tag(ArbitrarySelector), + Glob { + origin: ArbitrarySelector, + inner_span: Span, + raw: String, + }, } impl ScopedSelector { @@ -282,24 +327,40 @@ impl ScopedSelector { Self::Class(a) => a, Self::Id(a) => a, Self::Tag(a) => a, + Self::Glob { origin, .. } => origin, } } /// Generate rusty member ident based on selector type and CSS ident - pub fn gen_rusty_ident(&self) -> syn::Ident { - let basic = apply_basic_rusty_member_gen_rules(&self.as_arbitrary().ident); + pub fn gen_rusty_ident(&self) -> Option { + let arb = &self.as_arbitrary().ident; + let basic = apply_basic_rusty_member_gen_rules(arb); let ready = match self { - Self::Class(_) => basic, - Self::Id(_) => { - format!("the{basic}") - } - Self::Tag(_) => { - format!("any{basic}") - } + Self::Class(_) => Some(basic), + Self::Id(_) => Some(format!("the{basic}")), + Self::Tag(_) => Some(format!("any{basic}")), + Self::Glob { .. } => None, }; - syn::parse_str::(&ready).expect("BUG: formed ident is not Rust Ident") + ready.map(|rs| { + syn::parse_str::(&rs).expect("BUG: formed ident is not Rust Ident") + }) + } + + /// Construct this from glob complex selector + pub fn from_glob_mod(s: (Span, Span), source: &str) -> Self { + let inner_span = s.1; + let origin_ident = source[s.0.start..s.0.end].to_owned(); + + Self::Glob { + origin: ArbitrarySelector { + span: s.0, + ident: origin_ident, + }, + raw: source[inner_span.start..inner_span.end].to_owned(), + inner_span, + } } /// Construct this from class selector @@ -482,7 +543,7 @@ mod test { .hashed_selectors .iter() .map(|s| s.sel.gen_rusty_ident()) - .map(|i| i.to_string()) + .flat_map(|i| i.map(|is| is.to_string())) .collect::>(); assert_eq!(expect_ident_names, sels); diff --git a/sabry_intrnl/src/syntax/mod.rs b/sabry_intrnl/src/syntax/mod.rs index ba85336..9feccd4 100644 --- a/sabry_intrnl/src/syntax/mod.rs +++ b/sabry_intrnl/src/syntax/mod.rs @@ -1,8 +1,8 @@ use ostrta::OneSyntaxToRuleThemAll; use raffia::{ ast::{ - ClassSelector, CompoundSelector, IdSelector, NestingSelector, SimpleSelector, Statement, - Stylesheet, TypeSelector, + ClassSelector, CompoundSelector, IdSelector, NestingSelector, PseudoClassSelector, + SimpleSelector, Statement, Stylesheet, TypeSelector, }, ParserBuilder, }; @@ -56,6 +56,27 @@ impl<'s> StylesheetAdapter<'s> { self.selectors_by(|c| c.as_nesting().cloned()) } + /// special selector case searching for :g(whatever), :glob(whatever), :global(whatever) + pub fn glob_modified_selectors(&self) -> Vec> { + self.selectors_by(|c| { + let cps = match c.as_pseudo_class() { + Some(s) => s.clone(), + _ => return None, + }; + + let ident = match cps.name.as_literal().map(|r| r.raw) { + Some(i) => i, + _ => return None, + }; + + if ident == "global" { + Some(cps) + } else { + None + } + }) + } + /// Return collected [Vec] of owned values modulo `T`, filtered by `F`. /// /// This is preferred way to apply custom filtering on selectors over diff --git a/sabry_procmacro_impl/src/impls/styly.rs b/sabry_procmacro_impl/src/impls/styly.rs index 5d896d3..e49a577 100644 --- a/sabry_procmacro_impl/src/impls/styly.rs +++ b/sabry_procmacro_impl/src/impls/styly.rs @@ -119,13 +119,14 @@ pub fn styly_macro_impl(input: TokenStream, source_path: Option) -> Tok ScopedSelector::Class(_) => "class", ScopedSelector::Id(_) => "id", ScopedSelector::Tag(_) => "tagname", + ScopedSelector::Glob { .. } => "glob", }; (name, hs.sel.gen_rusty_ident(), html, hs.css_ident.clone()) }) }) .map(|(name, ident, html, css)| { - let docs = format!("'{}' {}. CSS selector '{}'", ident, name, css); + let docs = format!("'{:?}' {}. CSS selector '{}'", ident, name, css); quote! { #[doc = #docs] #[allow(non_upper_case_globals)]