procss/lib.rs
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//! A simple CSS parsing and transformation framework. Procss can be used to
13//! quickly bundle a collection of CSS+ files, or write your own custom
14//! transforms.
15//!
16//! # Usage
17//!
18//! Procss's parser understands a nested superset of CSS (which we refer to as
19//! CSS+), similar to the [CSS nesting proposal](https://www.w3.org/TR/css-nesting-1/),
20//! or languages like [Sass](https://sass-lang.com). Start with source CSS+
21//! as a [`str`], use [crate::parse] or [crate::parse_unchecked] to generate
22//! an [`ast::Tree`].
23//!
24//! ```
25//! use procss::{ast, parse};
26//!
27//! let ast = procss::parse("div{.open{color:red;}}").unwrap();
28//! ```
29//!
30//! The resulting [`ast::Tree`] can be converted to a de-nested [`ast::Css`]
31//! with the [`ast::Tree::flatten_tree`] method, which itself can then be
32//! rendered as a plain browser-readable CSS string via the
33//! [`RenderCss::as_css_string`] trait method.
34//!
35//! ```
36//! # use procss::{parse, ast};
37//! # let ast = procss::parse("div{.open{color:red;}}").unwrap();
38//! use procss::RenderCss;
39//!
40//! let flat: ast::Css = ast.flatten_tree();
41//! let css: String = flat.as_css_string();
42//! assert_eq!(css, "div .open{color:red;}");
43//! ```
44//!
45//! Intermediate structs [`ast::Css::transform`] amd [`ast::Tree::transform`]
46//! can be used to recursively mutate a tree for a variety of node structs in
47//! the [`ast`] module. Some useful Example of such transforms can be
48//! found in the [`transformers`] module.
49//!
50//! ```
51//! # use procss::{parse, RenderCss};
52//! use procss::transformers;
53//!
54//! let test = "
55//! @mixin test {color: red;}
56//! div {@include test;}
57//! ";
58//!
59//! let mut ast = procss::parse(test).unwrap();
60//! transformers::apply_mixin(&mut ast);
61//! let mut flat = ast.flatten_tree();
62//! transformers::remove_mixin(&mut flat);
63//! let css = flat.as_css_string();
64//! assert_eq!(css, "div{color:red;}");
65//! ```
66//!
67//! For coordinating large builds on a tree of CSS files, the [`BuildCss`]
68//! struct can parse and minify, applying all transforms (including
69//! [`transformers::apply_import`]) as the compilation is left-folded over the
70//! inputs.
71//!
72//! ```no_run
73//! let mut build = procss::BuildCss::new("./src");
74//! build.add_file("controls/menu.scss");
75//! build.add_file("logout.scss"); // imports "controls/menu.scss"
76//! build.add_file("my_app.scss"); // imports "controls/menu.scss" and "logout.scss"
77//! build.compile().unwrap().write("./dist").unwrap();
78//! ```
79
80#![feature(assert_matches)]
81#![feature(path_file_prefix)]
82
83pub mod ast;
84mod builder;
85#[cfg(target_arch = "wasm32")]
86mod js_builder;
87mod parser;
88mod render;
89mod transform;
90pub mod transformers;
91#[cfg(feature = "iotest")]
92pub mod utils;
93
94#[cfg(not(feature = "iotest"))]
95pub mod utils;
96
97use self::ast::Tree;
98pub use self::builder::BuildCss;
99use self::parser::{unwrap_parse_error, ParseCss};
100pub use self::render::RenderCss;
101
102/// Parse CSS text to a [`Tree`] (where it can be further manipulated),
103/// capturing detailed error reporting for a moderate performance impact (using
104/// [`nom::error::VerboseError`]).
105///
106/// # Example
107///
108/// ```rust
109/// let ast = procss::parse("div { .open { color: red; }}").unwrap();
110/// ```
111pub fn parse(input: &str) -> anyhow::Result<Tree<'_>> {
112 let (_, tree) = Tree::parse(input).map_err(|err| unwrap_parse_error(input, err))?;
113 Ok(tree)
114}
115
116/// Parse CSS text to a [`Tree`], without capturing error details, for maximum
117/// performance without any error details when parsing fails.
118///
119/// # Example
120///
121/// ```rust
122/// let ast = procss::parse_unchecked("div { .open { color: red; }}").unwrap();
123/// ```
124pub fn parse_unchecked(input: &str) -> anyhow::Result<Tree<'_>> {
125 let (_, tree) = Tree::parse::<()>(input)?;
126 Ok(tree)
127}
128
129#[cfg(test)]
130mod tests {
131 use std::assert_matches::assert_matches;
132
133 use super::*;
134
135 #[test]
136 fn test_verbose_error() {
137 assert_matches!(
138 parse("div{color:red").map(|x| x.as_css_string()).as_deref(),
139 Err(_)
140 )
141 }
142
143 #[test]
144 fn test_parse_unchecked() {
145 assert_matches!(
146 parse_unchecked("div{color:red}")
147 .map(|x| x.as_css_string())
148 .as_deref(),
149 Ok("div{color:red;}")
150 )
151 }
152}
153
154// `iotest` feature flag stubs out disk-accessing and other performance
155// neutering function
156#[cfg(all(not(feature = "iotest"), test))]
157compile_error!("Feature 'iotest' must be enabled, rerun with:\n\n> cargo xtest");