feature: implement :glob()
This commit is contained in:
parent
74c32ec1f9
commit
3322e62d07
2 changed files with 100 additions and 17 deletions
|
|
@ -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::<Vec<_>>();
|
||||
// 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::<Vec<_>>();
|
||||
|
||||
// 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<syn::Ident> {
|
||||
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::<syn::Ident>(&ready).expect("BUG: formed ident is not Rust Ident")
|
||||
ready.map(|rs| {
|
||||
syn::parse_str::<syn::Ident>(&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::<HashSet<_>>();
|
||||
|
||||
assert_eq!(expect_ident_names, sels);
|
||||
|
|
|
|||
|
|
@ -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<PseudoClassSelector<'s>> {
|
||||
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
|
||||
|
|
|
|||
Loading…
Reference in a new issue