@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,252 @@
|
|
|
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::combinator::peek;
|
|
15
|
+
use nom::error::ParseError;
|
|
16
|
+
use nom::multi::{many0, many1};
|
|
17
|
+
use nom::sequence::{terminated, tuple};
|
|
18
|
+
use nom::{IResult, Parser};
|
|
19
|
+
|
|
20
|
+
use super::flat_ruleset::FlatRuleset;
|
|
21
|
+
use super::ruleset::{QualNestedRuleset, QualRule, QualRuleset, Rule, Ruleset, SelectorRuleset};
|
|
22
|
+
use super::selector::Selector;
|
|
23
|
+
use super::token::{comment0, sep0};
|
|
24
|
+
use crate::parser::*;
|
|
25
|
+
use crate::render::*;
|
|
26
|
+
use crate::transform::TransformCss;
|
|
27
|
+
|
|
28
|
+
/// A tree node which expresses a recursive `T` over `Ruleset<T>`. Using this
|
|
29
|
+
/// struct in place of `Rule` allows nested CSS selectors that can be later
|
|
30
|
+
/// flattened.
|
|
31
|
+
#[derive(Clone, Debug)]
|
|
32
|
+
pub enum TreeRule<'a> {
|
|
33
|
+
Rule(Rule<'a>),
|
|
34
|
+
Ruleset(TreeRuleset<'a>),
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
impl<'a> ParseCss<'a> for TreeRule<'a> {
|
|
38
|
+
fn parse<E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Self, E> {
|
|
39
|
+
let block = terminated(TreeRuleset::parse, sep0).map(TreeRule::Ruleset);
|
|
40
|
+
let rule = terminated(Rule::parse, sep0).map(TreeRule::Rule);
|
|
41
|
+
alt((block, rule))(input)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
impl<'a> RenderCss for TreeRule<'a> {
|
|
46
|
+
fn render(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
47
|
+
match self {
|
|
48
|
+
TreeRule::Rule(rule) => rule.render(f),
|
|
49
|
+
TreeRule::Ruleset(block) => block.render(f),
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
impl<'a> TransformCss<Rule<'a>> for TreeRule<'a> {
|
|
55
|
+
fn transform_each<F: FnMut(&mut Rule<'a>)>(&mut self, f: &mut F) {
|
|
56
|
+
match self {
|
|
57
|
+
TreeRule::Rule(rule) => f(rule),
|
|
58
|
+
TreeRule::Ruleset(ruleset) => ruleset.transform_each(f),
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
impl<'a> TransformCss<TreeRuleset<'a>> for TreeRule<'a> {
|
|
64
|
+
fn transform_each<F: FnMut(&mut TreeRuleset<'a>)>(&mut self, f: &mut F) {
|
|
65
|
+
match self {
|
|
66
|
+
TreeRule::Rule(_) => (),
|
|
67
|
+
TreeRule::Ruleset(ruleset) => ruleset.transform_each(f),
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
impl<'a> TransformCss<Vec<TreeRuleset<'a>>> for TreeRule<'a> {
|
|
73
|
+
fn transform_each<F: FnMut(&mut Vec<TreeRuleset<'a>>)>(&mut self, f: &mut F) {
|
|
74
|
+
match self {
|
|
75
|
+
TreeRule::Rule(_) => (),
|
|
76
|
+
TreeRule::Ruleset(ruleset) => ruleset.transform_each(f),
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// A nested recursive block, ala popular CSS tools and the CSS nesting
|
|
82
|
+
/// proposal.
|
|
83
|
+
///
|
|
84
|
+
/// ```css
|
|
85
|
+
/// div {
|
|
86
|
+
/// color: green;
|
|
87
|
+
/// &#my_elem {
|
|
88
|
+
/// color: red;
|
|
89
|
+
/// }
|
|
90
|
+
/// .sub_elem {
|
|
91
|
+
/// color: purple;
|
|
92
|
+
/// }
|
|
93
|
+
/// }
|
|
94
|
+
/// ```
|
|
95
|
+
pub type TreeRuleset<'a> = Ruleset<'a, TreeRule<'a>>;
|
|
96
|
+
|
|
97
|
+
impl<'a> ParseCss<'a> for TreeRuleset<'a> {
|
|
98
|
+
fn parse<E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Self, E> {
|
|
99
|
+
if let Ok((input, _)) = peek::<_, _, E, _>(tag("@"))(input) {
|
|
100
|
+
let (input, qual_rule) = QualRule::parse(input)?;
|
|
101
|
+
if let Ok((input, _)) = tag::<_, _, E>(";")(input) {
|
|
102
|
+
Ok((input, Ruleset::QualRule(qual_rule)))
|
|
103
|
+
} else {
|
|
104
|
+
let (input, _) = tuple((tag("{"), sep0))(input)?;
|
|
105
|
+
let (input, rules) = many1(TreeRule::parse::<E>)(input)?;
|
|
106
|
+
let (input, _) = tuple((comment0, tag("}")))(input)?;
|
|
107
|
+
Ok((input, Ruleset::QualRuleset(QualRuleset(qual_rule, rules))))
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
let (input, selector_ruleset) = SelectorRuleset::parse(input)?;
|
|
111
|
+
Ok((input, Ruleset::SelectorRuleset(selector_ruleset)))
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
impl<'a> TransformCss<TreeRuleset<'a>> for TreeRuleset<'a> {
|
|
117
|
+
fn transform_each<F: FnMut(&mut TreeRuleset<'a>)>(&mut self, f: &mut F) {
|
|
118
|
+
f(self);
|
|
119
|
+
match self {
|
|
120
|
+
Ruleset::QualRule(_) => (),
|
|
121
|
+
Ruleset::QualRuleset(_) => (),
|
|
122
|
+
Ruleset::QualNestedRuleset(..) => (),
|
|
123
|
+
Ruleset::SelectorRuleset(ruleset) => {
|
|
124
|
+
for rule in ruleset.1.iter_mut() {
|
|
125
|
+
rule.transform_each(f)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
impl<'a> TreeRuleset<'a> {
|
|
133
|
+
/// Flatten into a `FlatRuleset`, replacing this struct's inner `TreeRule`
|
|
134
|
+
/// recursive type with a regular `Rule`, removing arbitrary nesting of
|
|
135
|
+
/// `SelectorRuleset` variants.
|
|
136
|
+
pub fn flatten_tree(&self) -> Vec<FlatRuleset<'a>> {
|
|
137
|
+
match self {
|
|
138
|
+
Ruleset::SelectorRuleset(ruleset) => ruleset.flatten_tree(),
|
|
139
|
+
Ruleset::QualRule(x) => vec![Ruleset::QualRule(x.clone())],
|
|
140
|
+
Ruleset::QualRuleset(rules) => {
|
|
141
|
+
let mut new_rules: Vec<Rule<'a>> = vec![];
|
|
142
|
+
let mut new_rulesets: Vec<FlatRuleset<'a>> = vec![];
|
|
143
|
+
for rule in rules.1.iter() {
|
|
144
|
+
match rule {
|
|
145
|
+
TreeRule::Rule(rule) => new_rules.push(rule.clone()),
|
|
146
|
+
TreeRule::Ruleset(ruleset) => {
|
|
147
|
+
let sub_rules = ruleset.flatten_tree().into_iter();
|
|
148
|
+
new_rulesets.extend(sub_rules)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let mut ret = vec![];
|
|
154
|
+
if !new_rules.is_empty() {
|
|
155
|
+
let ruleset = QualRuleset(rules.0.clone(), new_rules);
|
|
156
|
+
ret.push(Ruleset::QualRuleset(ruleset));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if !new_rulesets.is_empty() {
|
|
160
|
+
ret.push(Ruleset::QualNestedRuleset(QualNestedRuleset(
|
|
161
|
+
rules.0.clone(),
|
|
162
|
+
new_rulesets,
|
|
163
|
+
)))
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
ret
|
|
167
|
+
}
|
|
168
|
+
Ruleset::QualNestedRuleset(ruleset) => {
|
|
169
|
+
vec![Ruleset::QualNestedRuleset(QualNestedRuleset(
|
|
170
|
+
ruleset.0.clone(),
|
|
171
|
+
ruleset.1.iter().flat_map(|x| x.flatten_tree()).collect(),
|
|
172
|
+
))]
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
type TreeSelectorRuleset<'a> = SelectorRuleset<'a, TreeRule<'a>>;
|
|
179
|
+
|
|
180
|
+
impl<'a> ParseCss<'a> for TreeSelectorRuleset<'a> {
|
|
181
|
+
fn parse<E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Self, E> {
|
|
182
|
+
let (input, selector) = Selector::parse(input)?;
|
|
183
|
+
let (input, _) = tuple((comment0, tag("{"), sep0))(input)?;
|
|
184
|
+
let (input, rules) = many0(TreeRule::parse)(input)?;
|
|
185
|
+
let (input, _) = tuple((comment0, tag("}")))(input)?;
|
|
186
|
+
Ok((input, SelectorRuleset(selector, rules)))
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
impl<'a> TransformCss<Vec<TreeRuleset<'a>>> for TreeRuleset<'a> {
|
|
191
|
+
fn transform_each<F: FnMut(&mut Vec<TreeRuleset<'a>>)>(&mut self, f: &mut F) {
|
|
192
|
+
match self {
|
|
193
|
+
Ruleset::SelectorRuleset(ruleset) => {
|
|
194
|
+
for rule in ruleset.1.iter_mut() {
|
|
195
|
+
rule.transform_each(f)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
Ruleset::QualRule(_) => (),
|
|
199
|
+
Ruleset::QualRuleset(_) => (),
|
|
200
|
+
Ruleset::QualNestedRuleset(ruleset) => {
|
|
201
|
+
for rule in ruleset.1.iter_mut() {
|
|
202
|
+
rule.transform_each(f)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
impl<'a> TreeSelectorRuleset<'a> {
|
|
210
|
+
/// Flatten a TreeRuleset's SelectorRuleset into a `FlatRuleset`m erging any
|
|
211
|
+
/// nested rulesets which are not allowed in the latter.
|
|
212
|
+
pub fn flatten_tree(&self) -> Vec<Ruleset<'a, Rule<'a>>> {
|
|
213
|
+
let mut new_rules: Vec<Rule<'a>> = vec![];
|
|
214
|
+
let mut new_rulesets: Vec<FlatRuleset<'a>> = vec![];
|
|
215
|
+
for rule in self.1.iter() {
|
|
216
|
+
match rule {
|
|
217
|
+
TreeRule::Rule(rule) => new_rules.push(rule.clone()),
|
|
218
|
+
TreeRule::Ruleset(ruleset) => {
|
|
219
|
+
if !new_rules.is_empty() {
|
|
220
|
+
let ruleset = SelectorRuleset(self.0.clone(), new_rules);
|
|
221
|
+
new_rulesets.push(Ruleset::SelectorRuleset(ruleset));
|
|
222
|
+
new_rules = vec![];
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
let sub_rules = ruleset
|
|
226
|
+
.flatten_tree()
|
|
227
|
+
.into_iter()
|
|
228
|
+
.map(|ruleset| self.join(ruleset));
|
|
229
|
+
new_rulesets.extend(sub_rules)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if !new_rules.is_empty() {
|
|
235
|
+
let ruleset = SelectorRuleset(self.0.clone(), new_rules);
|
|
236
|
+
new_rulesets.push(Ruleset::SelectorRuleset(ruleset));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
new_rulesets
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/// Join a new `Ruleset` as an extension of self's selector.
|
|
243
|
+
fn join(&self, rhs: Ruleset<'a, Rule<'a>>) -> Ruleset<'a, Rule<'a>> {
|
|
244
|
+
match rhs {
|
|
245
|
+
Ruleset::SelectorRuleset(inner_ruleset) => {
|
|
246
|
+
let joined_selector = self.0.join(&inner_ruleset.0);
|
|
247
|
+
Ruleset::SelectorRuleset(SelectorRuleset(joined_selector, inner_ruleset.1))
|
|
248
|
+
}
|
|
249
|
+
ruleset => ruleset,
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
package/src/ast.rs
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
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
|
+
//! The complete AST structs for [`Css`] and [`Tree`]. These top-level structs
|
|
13
|
+
//! are part of the [`crate::parse`] API, and their various components structs
|
|
14
|
+
//! are necessary for writing any transformations on these between parsing and
|
|
15
|
+
//! rendering.
|
|
16
|
+
|
|
17
|
+
mod flat_ruleset;
|
|
18
|
+
mod ruleset;
|
|
19
|
+
mod selector;
|
|
20
|
+
mod token;
|
|
21
|
+
mod tree_ruleset;
|
|
22
|
+
|
|
23
|
+
use std::cmp::Ordering;
|
|
24
|
+
|
|
25
|
+
use nom::branch::alt;
|
|
26
|
+
use nom::combinator::eof;
|
|
27
|
+
use nom::error::ParseError;
|
|
28
|
+
use nom::multi::many1;
|
|
29
|
+
use nom::sequence::terminated;
|
|
30
|
+
use nom::{IResult, Parser};
|
|
31
|
+
|
|
32
|
+
pub use self::flat_ruleset::FlatRuleset;
|
|
33
|
+
pub use self::ruleset::{QualNestedRuleset, QualRule, QualRuleset, Rule, Ruleset, SelectorRuleset};
|
|
34
|
+
pub use self::selector::{Combinator, Selector, SelectorAttr, SelectorPath, SelectorTerm};
|
|
35
|
+
pub use self::tree_ruleset::{TreeRule, TreeRuleset};
|
|
36
|
+
use crate::parser::*;
|
|
37
|
+
use crate::render::*;
|
|
38
|
+
use crate::transform::*;
|
|
39
|
+
use crate::transformers;
|
|
40
|
+
|
|
41
|
+
/// A non-nested "flat" CSS representation, suitable for browser output. The
|
|
42
|
+
/// [`Css`] AST is typically generated via the
|
|
43
|
+
/// [`crate::ast::Tree::flatten_tree`] method.
|
|
44
|
+
#[derive(Clone, Debug)]
|
|
45
|
+
pub struct Css<'a>(pub Vec<FlatRuleset<'a>>);
|
|
46
|
+
|
|
47
|
+
impl<'a> Css<'a> {
|
|
48
|
+
/// A mutable transform which walks this AST recursively, invoking `f` for
|
|
49
|
+
/// all nodes of type `T`.
|
|
50
|
+
///
|
|
51
|
+
/// # Example
|
|
52
|
+
///
|
|
53
|
+
/// ```
|
|
54
|
+
/// use procss::{ast, parse, RenderCss};
|
|
55
|
+
/// let mut css = parse("div{color: red;}").unwrap().flatten_tree();
|
|
56
|
+
/// css.transform(|rule: &mut ast::Rule| {
|
|
57
|
+
/// rule.value = "green".into();
|
|
58
|
+
/// });
|
|
59
|
+
///
|
|
60
|
+
/// assert_eq!(css.as_css_string(), "div{color:green;}");
|
|
61
|
+
/// ```
|
|
62
|
+
pub fn transform<T>(&mut self, mut f: impl FnMut(&mut T))
|
|
63
|
+
where
|
|
64
|
+
Self: TransformCss<T>,
|
|
65
|
+
{
|
|
66
|
+
self.transform_each(&mut f)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// Iterate over the immediate children of this Tree (non-recursive).
|
|
70
|
+
pub fn iter(&self) -> impl Iterator<Item = &'_ FlatRuleset<'a>> {
|
|
71
|
+
self.0.iter()
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
impl<'a, T> TransformCss<T> for Css<'a>
|
|
76
|
+
where
|
|
77
|
+
Ruleset<'a, Rule<'a>>: TransformCss<T>,
|
|
78
|
+
{
|
|
79
|
+
fn transform_each<F: FnMut(&mut T)>(&mut self, f: &mut F) {
|
|
80
|
+
for rule in self.0.iter_mut() {
|
|
81
|
+
rule.transform_each(f);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
impl<'a> RenderCss for Css<'a> {
|
|
87
|
+
fn render(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
88
|
+
for x in self.0.iter() {
|
|
89
|
+
x.render(f)?;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
Ok(())
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// A nested CSS representation yielded from parsing. [`Tree`] also implements
|
|
97
|
+
/// [`RenderCss`] if this is needed, though this output can't be read by
|
|
98
|
+
/// browsers and is not identical to the input since whitespace has been
|
|
99
|
+
/// discarded.
|
|
100
|
+
#[derive(Clone, Debug)]
|
|
101
|
+
pub struct Tree<'a>(pub Vec<TreeRuleset<'a>>);
|
|
102
|
+
|
|
103
|
+
impl<'a> Tree<'a> {
|
|
104
|
+
/// Flatten a nested [`Tree`] into a [`Css`], or in other words, convert the
|
|
105
|
+
/// result of a parse into something that can be rendered.
|
|
106
|
+
pub fn flatten_tree(&self) -> Css<'a> {
|
|
107
|
+
let mut rules = self
|
|
108
|
+
.0
|
|
109
|
+
.iter()
|
|
110
|
+
.flat_map(|x| x.flatten_tree())
|
|
111
|
+
.collect::<Vec<_>>();
|
|
112
|
+
|
|
113
|
+
rules.sort_by(|x, y| match (x, y) {
|
|
114
|
+
(Ruleset::QualRule(_), Ruleset::QualRule(_)) => Ordering::Equal,
|
|
115
|
+
(Ruleset::QualRule(_), _) => Ordering::Less,
|
|
116
|
+
(_, Ruleset::QualRule(_)) => Ordering::Greater,
|
|
117
|
+
_ => Ordering::Equal,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
let mut css = Css(rules);
|
|
121
|
+
transformers::flat_self(&mut css);
|
|
122
|
+
css
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/// A mutable transform which walks this AST recursively, invoking `f` for
|
|
126
|
+
/// all nodes of type `T`.
|
|
127
|
+
///
|
|
128
|
+
/// # Example
|
|
129
|
+
///
|
|
130
|
+
/// ```
|
|
131
|
+
/// use procss::{ast, parse, RenderCss};
|
|
132
|
+
/// let mut tree = parse("div{color: red;}").unwrap();
|
|
133
|
+
/// tree.transform(|rule: &mut ast::Rule| {
|
|
134
|
+
/// rule.value = "green".into();
|
|
135
|
+
/// });
|
|
136
|
+
///
|
|
137
|
+
/// let css = tree.flatten_tree().as_css_string();
|
|
138
|
+
/// assert_eq!(css, "div{color:green;}");
|
|
139
|
+
/// ```
|
|
140
|
+
pub fn transform<T>(&mut self, mut f: impl FnMut(&mut T))
|
|
141
|
+
where
|
|
142
|
+
Self: TransformCss<T>,
|
|
143
|
+
{
|
|
144
|
+
self.transform_each(&mut f)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/// Iterate over the immediate children of this Tree (non-recursive).
|
|
148
|
+
pub fn iter(&self) -> impl Iterator<Item = &'_ TreeRuleset<'a>> {
|
|
149
|
+
self.0.iter()
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
impl<'a> TransformCss<Rule<'a>> for Tree<'a> {
|
|
154
|
+
fn transform_each<F: FnMut(&mut Rule<'a>)>(&mut self, f: &mut F) {
|
|
155
|
+
for rule in self.0.iter_mut() {
|
|
156
|
+
rule.transform_each(f);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
impl<'a> TransformCss<TreeRuleset<'a>> for Tree<'a> {
|
|
162
|
+
fn transform_each<F: FnMut(&mut TreeRuleset<'a>)>(&mut self, f: &mut F) {
|
|
163
|
+
for rule in self.0.iter_mut() {
|
|
164
|
+
f(rule);
|
|
165
|
+
match rule {
|
|
166
|
+
Ruleset::SelectorRuleset(ruleset) => {
|
|
167
|
+
for rule in ruleset.1.iter_mut() {
|
|
168
|
+
rule.transform_each(f)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
Ruleset::QualRule(_) => (),
|
|
172
|
+
Ruleset::QualRuleset(_) => (),
|
|
173
|
+
Ruleset::QualNestedRuleset(ruleset) => {
|
|
174
|
+
for rule in ruleset.1.iter_mut() {
|
|
175
|
+
rule.transform_each(f)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
impl<'a> TransformCss<Vec<TreeRuleset<'a>>> for Tree<'a> {
|
|
184
|
+
fn transform_each<F: FnMut(&mut Vec<TreeRuleset<'a>>)>(&mut self, f: &mut F) {
|
|
185
|
+
f(&mut self.0);
|
|
186
|
+
for rule in self.0.iter_mut() {
|
|
187
|
+
// f(rule);
|
|
188
|
+
rule.transform_each(f);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
impl<'a> ParseCss<'a> for Tree<'a> {
|
|
194
|
+
fn parse<E>(input: &'a str) -> IResult<&'a str, Self, E>
|
|
195
|
+
where
|
|
196
|
+
E: ParseError<&'a str>,
|
|
197
|
+
{
|
|
198
|
+
let (input, _) = token::sep0(input)?;
|
|
199
|
+
let (input, x) = alt((
|
|
200
|
+
eof.map(|_| vec![]),
|
|
201
|
+
terminated(many1(terminated(TreeRuleset::parse, token::sep0)), eof),
|
|
202
|
+
))(input)?;
|
|
203
|
+
|
|
204
|
+
Ok((input, Tree(x)))
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
impl<'a> RenderCss for Tree<'a> {
|
|
209
|
+
fn render(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
210
|
+
for x in self.0.iter() {
|
|
211
|
+
x.render(f)?;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
Ok(())
|
|
215
|
+
}
|
|
216
|
+
}
|
package/src/builder.rs
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
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 std::collections::HashMap;
|
|
13
|
+
use std::path::PathBuf;
|
|
14
|
+
|
|
15
|
+
use anyhow::Context;
|
|
16
|
+
|
|
17
|
+
use crate::parser::{unwrap_parse_error, ParseCss};
|
|
18
|
+
use crate::render::RenderCss;
|
|
19
|
+
#[cfg(not(target_arch = "wasm32"))]
|
|
20
|
+
use crate::utils::fs;
|
|
21
|
+
#[cfg(feature = "iotest")]
|
|
22
|
+
use crate::utils::IoTestFs;
|
|
23
|
+
use crate::{ast, transformers, utils};
|
|
24
|
+
|
|
25
|
+
/// A CSS+ project build, comprising a collection of CSS+ files which may
|
|
26
|
+
/// reference eachother (via `@import`).
|
|
27
|
+
pub struct BuildCss<'a> {
|
|
28
|
+
paths: Vec<String>,
|
|
29
|
+
contents: HashMap<&'a str, String>,
|
|
30
|
+
trees: HashMap<&'a str, ast::Tree<'a>>,
|
|
31
|
+
css: HashMap<&'a str, ast::Css<'a>>,
|
|
32
|
+
rootdir: String,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// The compiled output of a [`BuildCss`] collection, obtained from
|
|
36
|
+
/// [`BuildCss::compile`].
|
|
37
|
+
pub struct CompiledCss<'a>(&'a BuildCss<'a>);
|
|
38
|
+
|
|
39
|
+
/// An incremental build struct for compiling a project's CSS sources.
|
|
40
|
+
///
|
|
41
|
+
/// # Example
|
|
42
|
+
///
|
|
43
|
+
/// ```no_run
|
|
44
|
+
/// let mut build = procss::BuildCss::new("./src");
|
|
45
|
+
/// build.add_file("app.scss");
|
|
46
|
+
/// build.compile().unwrap().write("./dist").unwrap();
|
|
47
|
+
/// ```
|
|
48
|
+
impl<'a> BuildCss<'a> {
|
|
49
|
+
/// Create a new [`BuildCss`] rooted at `rootdir`.
|
|
50
|
+
pub fn new<S: Into<String>>(rootdir: S) -> Self {
|
|
51
|
+
Self {
|
|
52
|
+
paths: Default::default(),
|
|
53
|
+
contents: Default::default(),
|
|
54
|
+
trees: Default::default(),
|
|
55
|
+
css: Default::default(),
|
|
56
|
+
rootdir: rootdir.into(),
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// Add a file `path` to this build.
|
|
61
|
+
#[cfg(not(target_arch = "wasm32"))]
|
|
62
|
+
pub fn add_file(&mut self, path: &'a str) {
|
|
63
|
+
self.paths.push(path.to_owned());
|
|
64
|
+
let inpath = PathBuf::from(&self.rootdir).join(path);
|
|
65
|
+
let txt = fs::read_to_string(inpath.as_path()).unwrap();
|
|
66
|
+
self.contents.insert(path, txt);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// Add a file `path` to this build.
|
|
70
|
+
pub fn add_content(&mut self, path: &'a str, scss: String) {
|
|
71
|
+
self.paths.push(path.to_owned());
|
|
72
|
+
self.contents.insert(path, scss);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// Compile this [`BuildCss`] start-to-finish, applying all transforms along
|
|
76
|
+
/// the way.
|
|
77
|
+
pub fn compile(&'a mut self) -> anyhow::Result<CompiledCss<'a>> {
|
|
78
|
+
for (path, contents) in &self.contents {
|
|
79
|
+
let tree = ast::Tree::parse(contents);
|
|
80
|
+
let (_, tree) = tree.map_err(|err| unwrap_parse_error(contents, err))?;
|
|
81
|
+
self.trees.insert(path, tree);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let dep_trees = self.trees.clone();
|
|
85
|
+
for (path, tree) in self.trees.iter_mut() {
|
|
86
|
+
transformers::apply_import(&dep_trees)(tree);
|
|
87
|
+
transformers::apply_mixin(tree);
|
|
88
|
+
transformers::apply_var(tree);
|
|
89
|
+
self.css.insert(path, tree.flatten_tree());
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
for (path, css) in self.css.iter_mut() {
|
|
93
|
+
let srcdir = utils::join_paths(&self.rootdir, path);
|
|
94
|
+
transformers::inline_url(&srcdir.to_string_lossy())(css);
|
|
95
|
+
transformers::dedupe(css);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
Ok(CompiledCss(self))
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
impl<'a> CompiledCss<'a> {
|
|
103
|
+
/// Write this struct's compiled data to `outdir`, preserving the relative
|
|
104
|
+
/// subdirectory structure of the `input` sources passed to
|
|
105
|
+
/// [`BuildCss::add`], relative to `outdir`.
|
|
106
|
+
#[cfg(not(target_arch = "wasm32"))]
|
|
107
|
+
pub fn write(self, outdir: &'static str) -> anyhow::Result<()> {
|
|
108
|
+
for (outfile, css, path) in self.iter_files().flatten() {
|
|
109
|
+
let outdir = utils::join_paths(outdir, path);
|
|
110
|
+
fs::create_dir_all(outdir.clone()).unwrap_or_default();
|
|
111
|
+
fs::write(outdir.join(outfile), css)?;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
Ok(())
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/// Render this struct's compiled data in memory as a `String`, preserving
|
|
118
|
+
/// the relative subdirectory structure of the `input` sources passed to
|
|
119
|
+
/// [`BuildCss::add`], relative to `outdir`.
|
|
120
|
+
pub fn as_strings(&self) -> anyhow::Result<HashMap<String, String>> {
|
|
121
|
+
let mut results = HashMap::default();
|
|
122
|
+
for (outfile, css, _) in self.iter_files().flatten() {
|
|
123
|
+
results.insert(outfile, css).unwrap_or_default();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
Ok(results)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
fn iter_files(&self) -> impl Iterator<Item = anyhow::Result<(String, String, &'_ str)>> {
|
|
130
|
+
self.0.css.iter().map(|(path, css)| {
|
|
131
|
+
let outpath = PathBuf::from(path);
|
|
132
|
+
let outfile = format!(
|
|
133
|
+
"{}.css",
|
|
134
|
+
outpath
|
|
135
|
+
.file_prefix()
|
|
136
|
+
.context("No Prefix")?
|
|
137
|
+
.to_string_lossy()
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
Ok((outfile, css.as_css_string(), *path))
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
#[cfg(all(test, feature = "iotest"))]
|
|
146
|
+
mod tests {
|
|
147
|
+
use std::cell::RefCell;
|
|
148
|
+
use std::path::*;
|
|
149
|
+
use std::rc::Rc;
|
|
150
|
+
|
|
151
|
+
use super::*;
|
|
152
|
+
|
|
153
|
+
#[test]
|
|
154
|
+
fn test_simple_build() {
|
|
155
|
+
let outputs = Rc::new(RefCell::new(vec![]));
|
|
156
|
+
let infiles = Rc::new(RefCell::new(vec![]));
|
|
157
|
+
let outfiles = Rc::new(RefCell::new(vec![]));
|
|
158
|
+
|
|
159
|
+
let ctx = fs::read_to_string_context();
|
|
160
|
+
let infiles2 = infiles.clone();
|
|
161
|
+
ctx.expect().times(1).returning_st(move |x: &Path| {
|
|
162
|
+
infiles2.borrow_mut().push(x.to_string_lossy().to_string());
|
|
163
|
+
Ok("div{.open{color:green}}".to_owned())
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
let ctx = fs::create_dir_all_context();
|
|
167
|
+
ctx.expect().times(1).returning(|_: PathBuf| Ok(()));
|
|
168
|
+
|
|
169
|
+
let ctx = fs::write_context();
|
|
170
|
+
let outputs2 = outputs.clone();
|
|
171
|
+
let outfiles2 = outfiles.clone();
|
|
172
|
+
ctx.expect().returning_st(move |x: PathBuf, y: String| {
|
|
173
|
+
outfiles2.borrow_mut().push(x.to_string_lossy().to_string());
|
|
174
|
+
outputs2.borrow_mut().push(y);
|
|
175
|
+
Ok(())
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
let mut build = BuildCss::new("./src".to_owned());
|
|
179
|
+
build.add_file("app/component.scss");
|
|
180
|
+
build.compile().unwrap().write("./dist").unwrap();
|
|
181
|
+
|
|
182
|
+
let outputs = outputs.borrow().clone();
|
|
183
|
+
assert_eq!(outputs, vec!["div .open{color:green;}".to_owned()]);
|
|
184
|
+
let infiles = infiles.borrow().clone();
|
|
185
|
+
assert_eq!(infiles, vec!["./src/app/component.scss".to_owned()]);
|
|
186
|
+
let outfiles = outfiles.borrow().clone();
|
|
187
|
+
assert_eq!(outfiles, vec!["./dist/app/component.css".to_owned()])
|
|
188
|
+
}
|
|
189
|
+
}
|