@prospective.co/procss 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +45 -0
- package/package.json +31 -0
- package/src/ast/flat_ruleset.rs +52 -0
- package/src/ast/ruleset/mod.rs +229 -0
- package/src/ast/ruleset/rule.rs +114 -0
- package/src/ast/selector/attribute.rs +90 -0
- package/src/ast/selector/combinator.rs +58 -0
- package/src/ast/selector/mod.rs +115 -0
- package/src/ast/selector/selector_path.rs +230 -0
- package/src/ast/selector/selector_term.rs +325 -0
- package/src/ast/token/mod.rs +18 -0
- package/src/ast/token/space.rs +157 -0
- package/src/ast/token/string.rs +59 -0
- package/src/ast/token/symbol.rs +32 -0
- package/src/ast/tree_ruleset.rs +252 -0
- package/src/ast.rs +216 -0
- package/src/builder.rs +189 -0
- package/src/js_builder.rs +59 -0
- package/src/lib.rs +151 -0
- package/src/main.rs +40 -0
- package/src/parser.rs +36 -0
- package/src/render.rs +84 -0
- package/src/transform.rs +14 -0
- package/src/transformers/apply_import.rs +53 -0
- package/src/transformers/apply_mixin.rs +73 -0
- package/src/transformers/apply_var.rs +41 -0
- package/src/transformers/dedupe.rs +35 -0
- package/src/transformers/filter_refs.rs +27 -0
- package/src/transformers/flat_self.rs +30 -0
- package/src/transformers/inline_url.rs +52 -0
- package/src/transformers/mod.rs +43 -0
- package/src/utils.rs +90 -0
- package/target/cjs/procss.d.ts +22 -0
- package/target/cjs/procss.js +217 -0
- package/target/cjs/procss_bg.wasm +0 -0
- package/target/cjs/procss_bg.wasm.d.ts +12 -0
- package/target/esm/procss.d.ts +58 -0
- package/target/esm/procss.js +283 -0
- package/target/esm/procss_bg.wasm +0 -0
- package/target/esm/procss_bg.wasm.d.ts +12 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// ┌───────────────────────────────────────────────────────────────────────────┐
|
|
2
|
+
// │ │
|
|
3
|
+
// │ ██████╗ ██████╗ ██████╗ Copyright (C) 2022, The Prospective Company │
|
|
4
|
+
// │ ██╔══██╗██╔══██╗██╔═══██╗ │
|
|
5
|
+
// │ ██████╔╝██████╔╝██║ ██║ This file is part of the Procss library, │
|
|
6
|
+
// │ ██╔═══╝ ██╔══██╗██║ ██║ distributed under the terms of the │
|
|
7
|
+
// │ ██║ ██║ ██║╚██████╔╝ Apache License 2.0. The full license can │
|
|
8
|
+
// │ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ be found in the LICENSE file. │
|
|
9
|
+
// │ │
|
|
10
|
+
// └───────────────────────────────────────────────────────────────────────────┘
|
|
11
|
+
|
|
12
|
+
use nom::bytes::complete::{is_not, tag};
|
|
13
|
+
use nom::combinator::opt;
|
|
14
|
+
use nom::error::ParseError;
|
|
15
|
+
use nom::sequence::{preceded, tuple};
|
|
16
|
+
use nom::IResult;
|
|
17
|
+
|
|
18
|
+
use crate::ast::token::*;
|
|
19
|
+
use crate::parser::*;
|
|
20
|
+
|
|
21
|
+
/// A selector which matches attributes, optionally against their value as well.
|
|
22
|
+
/// TODO doesn't support comma-separated multiple selectors.
|
|
23
|
+
///
|
|
24
|
+
/// # Example
|
|
25
|
+
///
|
|
26
|
+
/// ```css
|
|
27
|
+
/// div[name=test] {}
|
|
28
|
+
/// div[disabled,data-value="red"] {}
|
|
29
|
+
/// ```
|
|
30
|
+
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
|
31
|
+
pub struct SelectorAttr<'a> {
|
|
32
|
+
pub name: &'a str,
|
|
33
|
+
pub value: Option<&'a str>,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
impl<'a> ParseCss<'a> for SelectorAttr<'a> {
|
|
37
|
+
fn parse<E>(input: &'a str) -> IResult<&'a str, Self, E>
|
|
38
|
+
where
|
|
39
|
+
E: ParseError<&'a str>,
|
|
40
|
+
{
|
|
41
|
+
let (rest, (_, name, value, _)) = tuple((
|
|
42
|
+
tag("["),
|
|
43
|
+
parse_symbol,
|
|
44
|
+
opt(preceded(tag("="), is_not("]"))),
|
|
45
|
+
tag("]"),
|
|
46
|
+
))(input)?;
|
|
47
|
+
Ok((rest, SelectorAttr { name, value }))
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#[cfg(test)]
|
|
52
|
+
mod tests {
|
|
53
|
+
use std::assert_matches::assert_matches;
|
|
54
|
+
|
|
55
|
+
use super::*;
|
|
56
|
+
|
|
57
|
+
#[test]
|
|
58
|
+
fn test_bool() {
|
|
59
|
+
assert_matches!(
|
|
60
|
+
SelectorAttr::parse::<()>("[disabled]"),
|
|
61
|
+
Ok(("", SelectorAttr {
|
|
62
|
+
name: "disabled",
|
|
63
|
+
value: None
|
|
64
|
+
}))
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
#[test]
|
|
69
|
+
fn test_value_quotes() {
|
|
70
|
+
assert_matches!(
|
|
71
|
+
SelectorAttr::parse::<()>("[data-value=\"red\"]"),
|
|
72
|
+
Ok(("", SelectorAttr {
|
|
73
|
+
name: "data-value",
|
|
74
|
+
value: Some("\"red\"")
|
|
75
|
+
}))
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
#[ignore]
|
|
80
|
+
#[test]
|
|
81
|
+
fn test_multiple() {
|
|
82
|
+
assert_matches!(
|
|
83
|
+
SelectorAttr::parse::<()>("[disabled,data-value=\"red\"]"),
|
|
84
|
+
Ok(("", SelectorAttr {
|
|
85
|
+
name: "data-value",
|
|
86
|
+
value: Some("\"red\"")
|
|
87
|
+
}))
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// ┌───────────────────────────────────────────────────────────────────────────┐
|
|
2
|
+
// │ │
|
|
3
|
+
// │ ██████╗ ██████╗ ██████╗ Copyright (C) 2022, The Prospective Company │
|
|
4
|
+
// │ ██╔══██╗██╔══██╗██╔═══██╗ │
|
|
5
|
+
// │ ██████╔╝██████╔╝██║ ██║ This file is part of the Procss library, │
|
|
6
|
+
// │ ██╔═══╝ ██╔══██╗██║ ██║ distributed under the terms of the │
|
|
7
|
+
// │ ██║ ██║ ██║╚██████╔╝ Apache License 2.0. The full license can │
|
|
8
|
+
// │ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ be found in the LICENSE file. │
|
|
9
|
+
// │ │
|
|
10
|
+
// └───────────────────────────────────────────────────────────────────────────┘
|
|
11
|
+
|
|
12
|
+
use nom::branch::alt;
|
|
13
|
+
use nom::bytes::complete::tag;
|
|
14
|
+
use nom::error::ParseError;
|
|
15
|
+
use nom::sequence::delimited;
|
|
16
|
+
use nom::{IResult, Parser};
|
|
17
|
+
|
|
18
|
+
use crate::ast::token::*;
|
|
19
|
+
use crate::parser::*;
|
|
20
|
+
use crate::render::*;
|
|
21
|
+
|
|
22
|
+
/// A selector combinator, used to combine a list of selectors.
|
|
23
|
+
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
|
24
|
+
pub enum Combinator {
|
|
25
|
+
Null,
|
|
26
|
+
Sibling,
|
|
27
|
+
AdjSibling,
|
|
28
|
+
Desc,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
impl RenderCss for Combinator {
|
|
32
|
+
fn render(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
33
|
+
match self {
|
|
34
|
+
Combinator::Null => write!(f, " "),
|
|
35
|
+
Combinator::Sibling => write!(f, "~"),
|
|
36
|
+
Combinator::AdjSibling => write!(f, "+"),
|
|
37
|
+
Combinator::Desc => write!(f, ">"),
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
impl<'a> ParseCss<'a> for Combinator {
|
|
43
|
+
fn parse<E>(input: &'a str) -> IResult<&'a str, Self, E>
|
|
44
|
+
where
|
|
45
|
+
E: ParseError<&'a str>,
|
|
46
|
+
{
|
|
47
|
+
delimited(
|
|
48
|
+
comment0,
|
|
49
|
+
alt((
|
|
50
|
+
tag("+").map(|_| Combinator::AdjSibling),
|
|
51
|
+
tag(">").map(|_| Combinator::Desc),
|
|
52
|
+
tag("~").map(|_| Combinator::Sibling),
|
|
53
|
+
comment0.map(|_| Combinator::Null),
|
|
54
|
+
)),
|
|
55
|
+
comment0,
|
|
56
|
+
)(input)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// ┌───────────────────────────────────────────────────────────────────────────┐
|
|
2
|
+
// │ │
|
|
3
|
+
// │ ██████╗ ██████╗ ██████╗ Copyright (C) 2022, The Prospective Company │
|
|
4
|
+
// │ ██╔══██╗██╔══██╗██╔═══██╗ │
|
|
5
|
+
// │ ██████╔╝██████╔╝██║ ██║ This file is part of the Procss library, │
|
|
6
|
+
// │ ██╔═══╝ ██╔══██╗██║ ██║ distributed under the terms of the │
|
|
7
|
+
// │ ██║ ██║ ██║╚██████╔╝ Apache License 2.0. The full license can │
|
|
8
|
+
// │ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ be found in the LICENSE file. │
|
|
9
|
+
// │ │
|
|
10
|
+
// └───────────────────────────────────────────────────────────────────────────┘
|
|
11
|
+
|
|
12
|
+
mod attribute;
|
|
13
|
+
mod combinator;
|
|
14
|
+
mod selector_path;
|
|
15
|
+
mod selector_term;
|
|
16
|
+
|
|
17
|
+
use std::ops::Deref;
|
|
18
|
+
|
|
19
|
+
use nom::bytes::complete::tag;
|
|
20
|
+
use nom::error::ParseError;
|
|
21
|
+
use nom::multi::many0;
|
|
22
|
+
use nom::sequence::{delimited, preceded};
|
|
23
|
+
use nom::IResult;
|
|
24
|
+
|
|
25
|
+
pub use self::attribute::SelectorAttr;
|
|
26
|
+
pub use self::combinator::Combinator;
|
|
27
|
+
pub use self::selector_path::SelectorPath;
|
|
28
|
+
pub use self::selector_term::SelectorTerm;
|
|
29
|
+
use super::token::comment0;
|
|
30
|
+
use crate::parser::*;
|
|
31
|
+
use crate::transform::TransformCss;
|
|
32
|
+
use crate::utils::*;
|
|
33
|
+
|
|
34
|
+
/// A set of selector alternatives separated by `,`, for example `div, span`.
|
|
35
|
+
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
|
36
|
+
pub struct Selector<'a>(MinVec<SelectorPath<'a>, 1>);
|
|
37
|
+
|
|
38
|
+
impl<'a> Default for Selector<'a> {
|
|
39
|
+
fn default() -> Self {
|
|
40
|
+
//Selector::parse::<()>("&").unwrap().1
|
|
41
|
+
Selector(MinVec::new(
|
|
42
|
+
[SelectorPath::PartialCons(
|
|
43
|
+
SelectorTerm {
|
|
44
|
+
tag: (),
|
|
45
|
+
..SelectorTerm::default()
|
|
46
|
+
},
|
|
47
|
+
vec![],
|
|
48
|
+
)],
|
|
49
|
+
vec![],
|
|
50
|
+
))
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
impl<'a> Deref for Selector<'a> {
|
|
55
|
+
type Target = MinVec<SelectorPath<'a>, 1>;
|
|
56
|
+
|
|
57
|
+
fn deref(&self) -> &Self::Target {
|
|
58
|
+
&self.0
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
impl<'a> ParseCss<'a> for Selector<'a> {
|
|
63
|
+
fn parse<E>(input: &'a str) -> IResult<&'a str, Self, E>
|
|
64
|
+
where
|
|
65
|
+
E: ParseError<&'a str>,
|
|
66
|
+
{
|
|
67
|
+
let (input, selector) = SelectorPath::parse(input)?;
|
|
68
|
+
let (input, extra) = many0(preceded(
|
|
69
|
+
delimited(comment0, tag(","), comment0),
|
|
70
|
+
SelectorPath::parse,
|
|
71
|
+
))(input)?;
|
|
72
|
+
|
|
73
|
+
Ok((input, Selector(MinVec::new([selector], extra))))
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
impl<'a> TransformCss<SelectorPath<'a>> for Selector<'a> {
|
|
78
|
+
fn transform_each<F: FnMut(&mut SelectorPath<'a>)>(&mut self, f: &mut F) {
|
|
79
|
+
for list in self.0.iter_mut() {
|
|
80
|
+
f(list)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
impl<'a> Selector<'a> {
|
|
86
|
+
/// Create a new `SelectorGroup` from an `Iterator`, which will fail if
|
|
87
|
+
/// there aren't enough elements. This should asserted by the caller.
|
|
88
|
+
fn new(mut iter: impl Iterator<Item = SelectorPath<'a>>) -> Option<Self> {
|
|
89
|
+
Some(Selector(MinVec::new([iter.next()?], iter.collect())))
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// `SelectorGroup` uses the underlying `join()` method of the
|
|
93
|
+
/// `SelectorList`, combined via the product of the two
|
|
94
|
+
/// `SelectorGroup`'s items. For example:
|
|
95
|
+
///
|
|
96
|
+
/// ```css
|
|
97
|
+
/// div, span {
|
|
98
|
+
/// .opened, :hover {
|
|
99
|
+
/// color: red;
|
|
100
|
+
/// }
|
|
101
|
+
/// }
|
|
102
|
+
/// ```
|
|
103
|
+
///
|
|
104
|
+
/// becomes
|
|
105
|
+
///
|
|
106
|
+
/// ```css
|
|
107
|
+
/// div .opened, div :hover, span .opened, span :hover {
|
|
108
|
+
/// color: red;
|
|
109
|
+
/// }
|
|
110
|
+
/// ```
|
|
111
|
+
pub fn join(&self, other: &Selector<'a>) -> Selector<'a> {
|
|
112
|
+
let iter = self.0.iter().flat_map(|x| other.iter().map(|y| x.join(y)));
|
|
113
|
+
Self::new(iter).unwrap()
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// ┌───────────────────────────────────────────────────────────────────────────┐
|
|
2
|
+
// │ │
|
|
3
|
+
// │ ██████╗ ██████╗ ██████╗ Copyright (C) 2022, The Prospective Company │
|
|
4
|
+
// │ ██╔══██╗██╔══██╗██╔═══██╗ │
|
|
5
|
+
// │ ██████╔╝██████╔╝██║ ██║ This file is part of the Procss library, │
|
|
6
|
+
// │ ██╔═══╝ ██╔══██╗██║ ██║ distributed under the terms of the │
|
|
7
|
+
// │ ██║ ██║ ██║╚██████╔╝ Apache License 2.0. The full license can │
|
|
8
|
+
// │ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ be found in the LICENSE file. │
|
|
9
|
+
// │ │
|
|
10
|
+
// └───────────────────────────────────────────────────────────────────────────┘
|
|
11
|
+
|
|
12
|
+
use nom::branch::alt;
|
|
13
|
+
use nom::error::ParseError;
|
|
14
|
+
use nom::multi::many0;
|
|
15
|
+
use nom::sequence::tuple;
|
|
16
|
+
use nom::IResult;
|
|
17
|
+
|
|
18
|
+
use super::combinator::*;
|
|
19
|
+
use super::selector_term::*;
|
|
20
|
+
use crate::parser::*;
|
|
21
|
+
use crate::render::*;
|
|
22
|
+
|
|
23
|
+
/// A linked-list-like data structure representing CSS selector lists, which are
|
|
24
|
+
/// selectors separated by combinators like `>`, `+` or most commonly just
|
|
25
|
+
/// whitespace. When the first selector in a `SelectorList` is the special
|
|
26
|
+
/// selector `&`, as special case `PartialCons` elimantes `tag` as a field from
|
|
27
|
+
/// this first Selector, preventing the `SelectorList` from being serialized
|
|
28
|
+
/// before being flattened.
|
|
29
|
+
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
|
30
|
+
pub enum SelectorPath<'a> {
|
|
31
|
+
Cons(
|
|
32
|
+
SelectorTerm<'a, Option<&'a str>>,
|
|
33
|
+
Vec<(Combinator, SelectorTerm<'a, Option<&'a str>>)>,
|
|
34
|
+
),
|
|
35
|
+
PartialCons(
|
|
36
|
+
SelectorTerm<'a, ()>,
|
|
37
|
+
Vec<(Combinator, SelectorTerm<'a, Option<&'a str>>)>,
|
|
38
|
+
),
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
use SelectorPath::*;
|
|
42
|
+
|
|
43
|
+
impl<'a> SelectorPath<'a> {
|
|
44
|
+
/// Utility method for cons-ing a `Selector` to the head of a
|
|
45
|
+
/// `SelectorList`.
|
|
46
|
+
fn cons(
|
|
47
|
+
&self,
|
|
48
|
+
selector: &SelectorTerm<'a, ()>,
|
|
49
|
+
tail: Vec<(Combinator, SelectorTerm<'a, Option<&'a str>>)>,
|
|
50
|
+
) -> Self {
|
|
51
|
+
match self {
|
|
52
|
+
//Nil => Nil,
|
|
53
|
+
Cons(x, _) => Cons(x.join(selector), tail),
|
|
54
|
+
PartialCons(x, _) => PartialCons(x.join(selector), tail),
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Utility method for accessing the tail of a `SelectorList`.
|
|
59
|
+
fn tail(&self) -> Vec<(Combinator, SelectorTerm<'a, Option<&'a str>>)> {
|
|
60
|
+
match self {
|
|
61
|
+
// Nil => vec![],
|
|
62
|
+
Cons(_, tail) => tail.clone(),
|
|
63
|
+
PartialCons(_, tail) => tail.clone(),
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// Append two `SelectorList`, properly merging `&` references along the
|
|
68
|
+
/// way. For example:
|
|
69
|
+
///
|
|
70
|
+
/// ```css
|
|
71
|
+
/// div {
|
|
72
|
+
/// &.enabled {
|
|
73
|
+
/// color: red;
|
|
74
|
+
/// }
|
|
75
|
+
/// }
|
|
76
|
+
/// ```
|
|
77
|
+
///
|
|
78
|
+
/// becomes
|
|
79
|
+
///
|
|
80
|
+
/// ```css
|
|
81
|
+
/// div.enabled {
|
|
82
|
+
/// color: red;
|
|
83
|
+
/// }
|
|
84
|
+
/// ```
|
|
85
|
+
pub fn join(&self, other: &Self) -> Self {
|
|
86
|
+
match (&self, other) {
|
|
87
|
+
(head, Cons(selector, tail)) => {
|
|
88
|
+
let mut new_tail = head.tail();
|
|
89
|
+
new_tail.push((Combinator::Null, selector.clone()));
|
|
90
|
+
new_tail.append(&mut tail.clone());
|
|
91
|
+
head.cons(&SelectorTerm::default(), new_tail)
|
|
92
|
+
}
|
|
93
|
+
(head, PartialCons(selector2, tail2)) => {
|
|
94
|
+
let mut new_tail = head.tail();
|
|
95
|
+
match new_tail.pop() {
|
|
96
|
+
Some((c, last)) => {
|
|
97
|
+
new_tail.push((c, last.join(selector2)));
|
|
98
|
+
new_tail.extend(tail2.iter().cloned());
|
|
99
|
+
head.cons(&SelectorTerm::default(), new_tail)
|
|
100
|
+
}
|
|
101
|
+
None => {
|
|
102
|
+
new_tail.append(&mut tail2.clone());
|
|
103
|
+
head.cons(selector2, new_tail)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
impl<'a> RenderCss for SelectorPath<'a> {
|
|
112
|
+
fn render(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
113
|
+
match &self {
|
|
114
|
+
Cons(selector, rest) => {
|
|
115
|
+
selector.render(f)?;
|
|
116
|
+
rest.render(f)?;
|
|
117
|
+
}
|
|
118
|
+
PartialCons(selector, rest) => {
|
|
119
|
+
write!(f, "&")?;
|
|
120
|
+
selector.render(f)?;
|
|
121
|
+
rest.render(f)?;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
Ok(())
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
impl<'a> ParseCss<'a> for SelectorPath<'a> {
|
|
130
|
+
fn parse<E>(input: &'a str) -> IResult<&'a str, Self, E>
|
|
131
|
+
where
|
|
132
|
+
E: ParseError<&'a str>,
|
|
133
|
+
{
|
|
134
|
+
alt((parse_selector_self_list, parse_selector_list))(input)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/// Parse a selector list without a leading `&` tag, see note on
|
|
139
|
+
/// `parse_selector_self_list`.
|
|
140
|
+
fn parse_selector_list<'a, E>(input: &'a str) -> IResult<&'a str, SelectorPath<'a>, E>
|
|
141
|
+
where
|
|
142
|
+
E: ParseError<&'a str>,
|
|
143
|
+
{
|
|
144
|
+
let (rest, x) = SelectorTerm::parse(input)?;
|
|
145
|
+
let (rest, combinators) = many0(tuple((Combinator::parse, SelectorTerm::parse)))(rest)?;
|
|
146
|
+
Ok((rest, Cons(x, combinators)))
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/// Parse a selector list starting with the `&` special tag via the
|
|
150
|
+
/// `Selector<()>` impl for the `Parser` trait, which is chosen here by the
|
|
151
|
+
/// compiler due to type inference, from the return binding later used as an
|
|
152
|
+
/// argument to the `PartialCons` variant constructor.
|
|
153
|
+
fn parse_selector_self_list<'a, E>(input: &'a str) -> IResult<&'a str, SelectorPath<'a>, E>
|
|
154
|
+
where
|
|
155
|
+
E: ParseError<&'a str>,
|
|
156
|
+
{
|
|
157
|
+
let (rest, x) = SelectorTerm::parse(input)?;
|
|
158
|
+
let (rest, combinators) = many0(tuple((Combinator::parse, SelectorTerm::parse)))(rest)?;
|
|
159
|
+
Ok((rest, PartialCons(x, combinators)))
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
#[cfg(test)]
|
|
163
|
+
mod tests {
|
|
164
|
+
use std::assert_matches::assert_matches;
|
|
165
|
+
|
|
166
|
+
use super::*;
|
|
167
|
+
|
|
168
|
+
#[test]
|
|
169
|
+
fn test_null() {
|
|
170
|
+
assert_matches!(
|
|
171
|
+
SelectorPath::parse::<()>("div img"),
|
|
172
|
+
Ok((
|
|
173
|
+
"",
|
|
174
|
+
SelectorPath::Cons(
|
|
175
|
+
SelectorTerm {
|
|
176
|
+
tag: Some("div"),
|
|
177
|
+
..
|
|
178
|
+
},
|
|
179
|
+
xs
|
|
180
|
+
)
|
|
181
|
+
)) if xs.len() == 1 && xs[0].1.tag == Some("img")
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
#[test]
|
|
186
|
+
fn test_desc() {
|
|
187
|
+
assert_matches!(
|
|
188
|
+
SelectorPath::parse::<()>("div > img"),
|
|
189
|
+
Ok((
|
|
190
|
+
"",
|
|
191
|
+
SelectorPath::Cons(
|
|
192
|
+
SelectorTerm {
|
|
193
|
+
tag: Some("div"),
|
|
194
|
+
..
|
|
195
|
+
},
|
|
196
|
+
xs
|
|
197
|
+
)
|
|
198
|
+
)) if xs.len() == 1 && xs[0].1.tag == Some("img")
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
#[test]
|
|
203
|
+
fn test_self() {
|
|
204
|
+
assert_matches!(
|
|
205
|
+
SelectorPath::parse::<()>("& > img"),
|
|
206
|
+
Ok((
|
|
207
|
+
"",
|
|
208
|
+
SelectorPath::PartialCons(
|
|
209
|
+
_,
|
|
210
|
+
xs
|
|
211
|
+
)
|
|
212
|
+
)) if xs.len() == 1 && xs[0].1.tag == Some("img")
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
#[ignore]
|
|
217
|
+
#[test]
|
|
218
|
+
fn test_inner_self() {
|
|
219
|
+
assert_matches!(
|
|
220
|
+
SelectorPath::parse::<()>("@nest div & img"),
|
|
221
|
+
Ok((
|
|
222
|
+
"",
|
|
223
|
+
SelectorPath::PartialCons(
|
|
224
|
+
_,
|
|
225
|
+
xs
|
|
226
|
+
)
|
|
227
|
+
)) if xs.len() == 1 && xs[0].1.tag == Some("img")
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
}
|