Skip to main content

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
82pub mod ast;
83mod builder;
84#[cfg(target_arch = "wasm32")]
85mod js_builder;
86mod parser;
87mod render;
88mod transform;
89pub mod transformers;
90#[cfg(feature = "iotest")]
91pub mod utils;
92
93#[cfg(not(feature = "iotest"))]
94pub mod utils;
95
96use self::ast::Tree;
97pub use self::builder::BuildCss;
98use self::parser::{unwrap_parse_error, ParseCss};
99pub use self::render::RenderCss;
100
101/// Parse CSS text to a [`Tree`] (where it can be further manipulated),
102/// capturing detailed error reporting for a moderate performance impact (using
103/// [`nom::error::VerboseError`]).
104///
105/// # Example
106///
107/// ```rust
108/// let ast = procss::parse("div { .open { color: red; }}").unwrap();
109/// ```
110pub fn parse(input: &str) -> anyhow::Result<Tree<'_>> {
111    let (_, tree) = Tree::parse(input).map_err(|err| unwrap_parse_error(input, err))?;
112    Ok(tree)
113}
114
115/// Parse CSS text to a [`Tree`], without capturing error details, for maximum
116/// performance without any error details when parsing fails.
117///
118/// # Example
119///
120/// ```rust
121/// let ast = procss::parse_unchecked("div { .open { color: red; }}").unwrap();
122/// ```
123pub fn parse_unchecked(input: &str) -> anyhow::Result<Tree<'_>> {
124    let (_, tree) = Tree::parse::<()>(input)?;
125    Ok(tree)
126}
127
128#[cfg(test)]
129mod tests {
130    use std::assert_matches::assert_matches;
131
132    use super::*;
133
134    #[test]
135    fn test_verbose_error() {
136        assert_matches!(
137            parse("div{color:red").map(|x| x.as_css_string()).as_deref(),
138            Err(_)
139        )
140    }
141
142    #[test]
143    fn test_parse_unchecked() {
144        assert_matches!(
145            parse_unchecked("div{color:red}")
146                .map(|x| x.as_css_string())
147                .as_deref(),
148            Ok("div{color:red;}")
149        )
150    }
151}
152
153// `iotest` feature flag stubs out disk-accessing and other performance
154// neutering function
155#[cfg(all(not(feature = "iotest"), test))]
156compile_error!("Feature 'iotest' must be enabled, rerun with:\n\n> cargo xtest");