255 lines
6.9 KiB
Rust
255 lines
6.9 KiB
Rust
use ostrta::OneSyntaxToRuleThemAll;
|
|
use raffia::{
|
|
ast::{
|
|
ClassSelector, CompoundSelector, IdSelector, NestingSelector, PseudoClassSelector,
|
|
SimpleSelector, Statement, Stylesheet, TypeSelector,
|
|
},
|
|
ParserBuilder,
|
|
};
|
|
|
|
pub mod ostrta;
|
|
|
|
/// Convenience wrapper for [Stylesheet]
|
|
pub struct StylesheetAdapter<'s> {
|
|
pub syntax: OneSyntaxToRuleThemAll,
|
|
source: &'s str,
|
|
stylesheet: Stylesheet<'s>,
|
|
}
|
|
|
|
impl<'s> StylesheetAdapter<'s> {
|
|
pub fn new(
|
|
source: &'s str,
|
|
syntax: OneSyntaxToRuleThemAll,
|
|
) -> Result<Self, raffia::error::Error> {
|
|
Ok(Self {
|
|
stylesheet: ParserBuilder::new(source)
|
|
.ignore_comments()
|
|
.syntax(syntax.into())
|
|
.build()
|
|
.parse()?,
|
|
syntax,
|
|
source,
|
|
})
|
|
}
|
|
|
|
pub fn source(&self) -> &str {
|
|
self.source
|
|
}
|
|
|
|
pub fn selectors(&self) -> Vec<CompoundSelector<'s>> {
|
|
Self::selectors_of(self.stylesheet.statements.iter().cloned()).collect()
|
|
}
|
|
|
|
pub fn class_selectors(&self) -> Vec<ClassSelector<'s>> {
|
|
self.selectors_by(|c| c.as_class().cloned())
|
|
}
|
|
|
|
pub fn id_selectors(&self) -> Vec<IdSelector<'s>> {
|
|
self.selectors_by(|c| c.as_id().cloned())
|
|
}
|
|
|
|
pub fn type_selectors(&self) -> Vec<TypeSelector<'s>> {
|
|
self.selectors_by(|c| c.as_type().cloned())
|
|
}
|
|
|
|
pub fn nesting_selectors(&self) -> Vec<NestingSelector<'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
|
|
/// `selectors().iter().filter_map()`
|
|
/// as it does not allocate intermediate vector
|
|
pub fn selectors_by<T, F>(&self, form: F) -> Vec<T>
|
|
where
|
|
F: FnMut(SimpleSelector<'s>) -> Option<T>,
|
|
{
|
|
Self::selectors_of(self.stylesheet.statements.iter().cloned())
|
|
.flat_map(|cs| cs.children)
|
|
.filter_map(form)
|
|
.collect()
|
|
}
|
|
|
|
fn selectors_of(
|
|
c: impl Iterator<Item = Statement<'s>>,
|
|
) -> impl Iterator<Item = CompoundSelector<'s>> {
|
|
// Hello. Its Dan.
|
|
// Im not proud of this,
|
|
// but more effective implementation
|
|
// was SO unreadable and SO infected with lifetimes
|
|
// I suck at those things
|
|
|
|
// For everyone trying to make this MORE OPTIMAL:
|
|
// increment the hour-counter and post your name here:
|
|
// 4 - Danik
|
|
// 5 - Yiffy
|
|
// 8 - Danik
|
|
c.filter_map(|s| s.as_qualified_rule().cloned())
|
|
.flat_map(|q| {
|
|
let thissels = q
|
|
.selector
|
|
.selectors
|
|
.iter()
|
|
.flat_map(|sel| &sel.children)
|
|
.filter_map(|sc| sc.as_compound_selector())
|
|
.cloned();
|
|
|
|
let blockstmt = q.block.statements.iter().cloned();
|
|
Self::selectors_of(blockstmt)
|
|
.chain(thissels)
|
|
.collect::<Vec<_>>()
|
|
})
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use std::collections::HashSet;
|
|
|
|
use raffia::Spanned;
|
|
|
|
use crate::syntax::ostrta::OneSyntaxToRuleThemAll;
|
|
|
|
use super::StylesheetAdapter;
|
|
|
|
#[test]
|
|
fn sels() {
|
|
let source_scss = "
|
|
.r1{
|
|
.r2.r3 {
|
|
color: red;
|
|
}
|
|
&-rod {
|
|
color: red;
|
|
&[value] {
|
|
color: red;
|
|
}
|
|
}
|
|
}
|
|
div#id2 {
|
|
& > span.seled {
|
|
color: red;
|
|
& + ul#id2[attr] {
|
|
color: red;
|
|
}
|
|
& + .uled[attr] {
|
|
color: red;
|
|
}
|
|
}
|
|
&::after{
|
|
color: red;
|
|
}
|
|
}
|
|
";
|
|
let source_sass = "
|
|
.r1
|
|
.r2.r3
|
|
color: red
|
|
&-rod
|
|
color: red
|
|
&[value]
|
|
color: red
|
|
div#id2
|
|
& > span.seled
|
|
color: red
|
|
& + ul#id2[attr]
|
|
color: red
|
|
& + .uled[attr]
|
|
color: red
|
|
&::after
|
|
color: red
|
|
";
|
|
let expect_classes = HashSet::from(["r2", "r3", "r1", "uled", "seled"]);
|
|
let expect_ids = HashSet::from(["id2", "id2"]);
|
|
let expect_tags = HashSet::from(["ul", "div", "span"]);
|
|
|
|
//scss
|
|
let adp = StylesheetAdapter::new(source_scss, OneSyntaxToRuleThemAll::Scss).unwrap();
|
|
|
|
let classes = adp
|
|
.class_selectors()
|
|
.iter()
|
|
.filter_map(|c| c.name.as_literal().map(|l| l.raw))
|
|
.collect::<HashSet<_>>();
|
|
|
|
let ids = adp
|
|
.id_selectors()
|
|
.iter()
|
|
.filter_map(|i| i.name.as_literal().map(|l| l.raw))
|
|
.collect::<HashSet<_>>();
|
|
|
|
let tags = adp
|
|
.type_selectors()
|
|
.iter()
|
|
.filter_map(|t| t.as_tag_name())
|
|
.filter_map(|t| t.name.name.as_literal().map(|l| l.raw))
|
|
.collect::<HashSet<_>>();
|
|
|
|
assert_eq!(expect_classes, classes);
|
|
assert_eq!(expect_ids, ids);
|
|
assert_eq!(expect_tags, tags);
|
|
|
|
//sass
|
|
let adp = StylesheetAdapter::new(source_sass, OneSyntaxToRuleThemAll::Sass).unwrap();
|
|
|
|
let classes = adp
|
|
.class_selectors()
|
|
.iter()
|
|
.filter_map(|c| c.name.as_literal().map(|l| l.raw))
|
|
.collect::<HashSet<_>>();
|
|
|
|
let ids = adp
|
|
.id_selectors()
|
|
.iter()
|
|
.filter_map(|i| i.name.as_literal().map(|l| l.raw))
|
|
.collect::<HashSet<_>>();
|
|
|
|
let tags = adp
|
|
.type_selectors()
|
|
.iter()
|
|
.filter_map(|t| t.as_tag_name())
|
|
.filter_map(|t| t.name.name.as_literal().map(|l| l.raw))
|
|
.collect::<HashSet<_>>();
|
|
|
|
assert_eq!(expect_classes, classes);
|
|
assert_eq!(expect_ids, ids);
|
|
assert_eq!(expect_tags, tags);
|
|
}
|
|
|
|
#[test]
|
|
fn rename_class() {
|
|
let scss = ".cls1{color:red; .cls2{color:green;}}";
|
|
let newname = "cls203-jg7ihgjhftyfhjh";
|
|
let expect = ".cls1{color:red; .cls203-jg7ihgjhftyfhjh{color:green;}}";
|
|
|
|
let adp = StylesheetAdapter::new(scss, OneSyntaxToRuleThemAll::Scss).unwrap();
|
|
let classes = adp.class_selectors();
|
|
let target_class = classes[0].clone();
|
|
|
|
let span = target_class.name.span();
|
|
let renamed = format!("{}{}{}", &scss[..span.start], newname, &scss[span.end..]);
|
|
assert_eq!(renamed, expect);
|
|
}
|
|
}
|