add compile time macro
This commit is contained in:
parent
34bbe4cd32
commit
8095c2345e
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
|||||||
run: rustc --version; cargo --version;
|
run: rustc --version; cargo --version;
|
||||||
|
|
||||||
- name: Run all tests
|
- name: Run all tests
|
||||||
run: cargo test
|
run: cargo test --features=macro
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -9,6 +9,7 @@ Cargo.lock
|
|||||||
coverage
|
coverage
|
||||||
pkg
|
pkg
|
||||||
flamegraph.svg
|
flamegraph.svg
|
||||||
|
perf*
|
||||||
|
|
||||||
# editor specific
|
# editor specific
|
||||||
.idea
|
.idea
|
||||||
@ -28,3 +29,6 @@ uikit
|
|||||||
bourbon
|
bourbon
|
||||||
foundation-sites
|
foundation-sites
|
||||||
sassline
|
sassline
|
||||||
|
true
|
||||||
|
dart-sass
|
||||||
|
sass-fairy
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
# 0.12.1 (unreleased)
|
# 0.12.1 (unreleased)
|
||||||
|
|
||||||
|
- add `grass::include!` macro to make it easier to include CSS at compile time
|
||||||
- improve error message for complex units in calculations
|
- improve error message for complex units in calculations
|
||||||
- more accurate formatting of named arguments in arglists when passed to `inspect(..)`
|
- more accurate formatting of named arguments in arglists when passed to `inspect(..)`
|
||||||
- support `$whiteness` and `$blackness` as arguments to `scale-color(..)`
|
- support `$whiteness` and `$blackness` as arguments to `scale-color(..)`
|
||||||
|
27
Cargo.toml
27
Cargo.toml
@ -24,31 +24,20 @@ path = "src/lib.rs"
|
|||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
grass_internal = { path = "./grass_internal" }
|
||||||
|
include_sass = { path = "./include_sass", optional = true }
|
||||||
clap = { version = "2.34.0", optional = true }
|
clap = { version = "2.34.0", optional = true }
|
||||||
# todo: use lazy_static
|
|
||||||
once_cell = "1.15.0"
|
|
||||||
# todo: use xorshift for random numbers
|
|
||||||
rand = { version = "0.8", optional = true }
|
|
||||||
# todo: update to use asref<path>
|
|
||||||
# todo: update to expose more info (for eww)
|
|
||||||
# todo: update to use text_size::TextRange
|
|
||||||
codemap = "0.1.3"
|
|
||||||
wasm-bindgen = { version = "0.2.68", optional = true }
|
|
||||||
# todo: benchmark using phf for global functions
|
|
||||||
phf = { version = "0.10.1", features = ["macros"] }
|
|
||||||
indexmap = "1.9.0"
|
|
||||||
# todo: do we really need interning for things?
|
|
||||||
lasso = "0.6"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# todo: no commandline by default
|
# todo: no commandline by default
|
||||||
default = ["commandline", "random"]
|
default = ["commandline", "random"]
|
||||||
# Option (enabled by default): build a binary using clap
|
# Option (enabled by default): build a binary using clap
|
||||||
commandline = ["clap"]
|
commandline = ["clap"]
|
||||||
# Option (enabled by default): enable the builtin functions `random([$limit])` and `unique-id()`
|
random = ["grass_internal/random"]
|
||||||
random = ["rand"]
|
wasm-exports = ["grass_internal/wasm-exports"]
|
||||||
# Option: expose JavaScript-friendly WebAssembly exports
|
# Option: include the proc macro `include_sass!`
|
||||||
wasm-exports = ["wasm-bindgen"]
|
macro = ["include_sass"]
|
||||||
|
nightly = ["include_sass/nightly"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.3.0"
|
tempfile = "3.3.0"
|
||||||
@ -57,3 +46,5 @@ paste = "1.0.3"
|
|||||||
[profile.release]
|
[profile.release]
|
||||||
debug = true
|
debug = true
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
|
lto = true
|
||||||
|
codegen-units = 1
|
||||||
|
10
README.md
10
README.md
@ -45,6 +45,16 @@ compiled using wasm-bindgen. To use `grass` in your JavaScript projects, run
|
|||||||
In the future this feature will be removed when it is no longer necessary to rely on `rand` for
|
In the future this feature will be removed when it is no longer necessary to rely on `rand` for
|
||||||
random numbers.
|
random numbers.
|
||||||
|
|
||||||
|
### macro
|
||||||
|
|
||||||
|
(disabled by default): enable the macro `grass::include!` for compiling Sass to
|
||||||
|
CSS at compile time
|
||||||
|
|
||||||
|
### nightly
|
||||||
|
|
||||||
|
(disabled by default): currently only used by `grass::include!` to enable
|
||||||
|
[proc_macro::tracked_path](https://github.com/rust-lang/rust/issues/99515)
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
As much as possible this library attempts to follow the same [philosophy for testing as
|
As much as possible this library attempts to follow the same [philosophy for testing as
|
||||||
|
37
grass_internal/Cargo.toml
Normal file
37
grass_internal/Cargo.toml
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
[package]
|
||||||
|
name = "grass_internal"
|
||||||
|
version = "0.12.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "grass_internal"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
# crate-type = ["cdylib", "rlib"]
|
||||||
|
bench = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# todo: use lazy_static
|
||||||
|
once_cell = "1.15.0"
|
||||||
|
# todo: use xorshift for random numbers
|
||||||
|
rand = { version = "0.8", optional = true }
|
||||||
|
# todo: update to use asref<path>
|
||||||
|
# todo: update to expose more info (for eww)
|
||||||
|
# todo: update to use text_size::TextRange
|
||||||
|
codemap = "0.1.3"
|
||||||
|
wasm-bindgen = { version = "0.2.68", optional = true }
|
||||||
|
# todo: benchmark using phf for global functions
|
||||||
|
phf = { version = "0.10.1", features = ["macros"] }
|
||||||
|
indexmap = "1.9.0"
|
||||||
|
# todo: do we really need interning for things?
|
||||||
|
lasso = "0.6"
|
||||||
|
# include_sass = { path = "./include_sass", optional = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
# todo: no commandline by default
|
||||||
|
default = ["random"]
|
||||||
|
# Option (enabled by default): enable the builtin functions `random([$limit])` and `unique-id()`
|
||||||
|
random = ["rand"]
|
||||||
|
# Option: expose JavaScript-friendly WebAssembly exports
|
||||||
|
wasm-exports = ["wasm-bindgen"]
|
220
grass_internal/src/lib.rs
Normal file
220
grass_internal/src/lib.rs
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
/*!
|
||||||
|
This crate provides functionality for compiling [Sass](https://sass-lang.com/) to CSS.
|
||||||
|
|
||||||
|
This crate targets compatability with the reference implementation in Dart. If
|
||||||
|
upgrading from the [now deprecated](https://sass-lang.com/blog/libsass-is-deprecated)
|
||||||
|
`libsass`, one may have to modify their stylesheets. These changes will not differ
|
||||||
|
from those necessary to upgrade to `dart-sass`, and in general such changes should
|
||||||
|
be quite rare.
|
||||||
|
|
||||||
|
This crate is capable of compiling Bootstrap 4 and 5, bulma and bulma-scss, Bourbon,
|
||||||
|
as well as most other large Sass libraries with complete accuracy. For the vast
|
||||||
|
majority of use cases there should be no perceptible differences from the reference
|
||||||
|
implementation.
|
||||||
|
|
||||||
|
## Use as library
|
||||||
|
```
|
||||||
|
fn main() -> Result<(), Box<grass::Error>> {
|
||||||
|
let css = grass::from_string(
|
||||||
|
"a { b { color: &; } }".to_owned(),
|
||||||
|
&grass::Options::default()
|
||||||
|
)?;
|
||||||
|
assert_eq!(css, "a b {\n color: a b;\n}\n");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Use as binary
|
||||||
|
```bash
|
||||||
|
cargo install grass
|
||||||
|
grass input.scss
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
|
||||||
|
#![warn(clippy::all, clippy::cargo)]
|
||||||
|
#![deny(missing_debug_implementations)]
|
||||||
|
#![allow(
|
||||||
|
clippy::use_self,
|
||||||
|
clippy::missing_docs_in_private_items,
|
||||||
|
clippy::unreachable,
|
||||||
|
clippy::module_name_repetitions,
|
||||||
|
// filter isn't fallible
|
||||||
|
clippy::manual_filter_map,
|
||||||
|
clippy::new_ret_no_self,
|
||||||
|
renamed_and_removed_lints,
|
||||||
|
clippy::unknown_clippy_lints,
|
||||||
|
clippy::single_match,
|
||||||
|
clippy::unimplemented,
|
||||||
|
clippy::option_if_let_else,
|
||||||
|
clippy::branches_sharing_code,
|
||||||
|
clippy::derive_partial_eq_without_eq,
|
||||||
|
|
||||||
|
// temporarily allowed while under heavy development.
|
||||||
|
// eventually these allows should be refactored away
|
||||||
|
// to no longer be necessary
|
||||||
|
clippy::too_many_lines,
|
||||||
|
clippy::cast_possible_truncation,
|
||||||
|
clippy::single_match_else,
|
||||||
|
clippy::redundant_pub_crate,
|
||||||
|
// the api is changing too often to allot this
|
||||||
|
clippy::missing_errors_doc,
|
||||||
|
clippy::missing_const_for_fn,
|
||||||
|
clippy::multiple_crate_versions,
|
||||||
|
|
||||||
|
clippy::wrong_self_convention,
|
||||||
|
clippy::items_after_statements,
|
||||||
|
// this is only available on nightly
|
||||||
|
clippy::unnested_or_patterns,
|
||||||
|
clippy::uninlined_format_args,
|
||||||
|
|
||||||
|
// todo:
|
||||||
|
clippy::cast_sign_loss,
|
||||||
|
clippy::cast_lossless,
|
||||||
|
clippy::cast_precision_loss,
|
||||||
|
clippy::float_cmp,
|
||||||
|
clippy::wildcard_imports,
|
||||||
|
clippy::comparison_chain,
|
||||||
|
clippy::bool_to_int_with_if,
|
||||||
|
|
||||||
|
unknown_lints,
|
||||||
|
)]
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use parse::{CssParser, SassParser, StylesheetParser};
|
||||||
|
use serializer::Serializer;
|
||||||
|
#[cfg(feature = "wasm-exports")]
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
use codemap::CodeMap;
|
||||||
|
|
||||||
|
pub use crate::error::{
|
||||||
|
PublicSassErrorKind as ErrorKind, SassError as Error, SassResult as Result,
|
||||||
|
};
|
||||||
|
pub use crate::fs::{Fs, NullFs, StdFs};
|
||||||
|
pub use crate::options::{InputSyntax, Options, OutputStyle};
|
||||||
|
pub(crate) use crate::{context_flags::ContextFlags, token::Token};
|
||||||
|
use crate::{evaluate::Visitor, lexer::Lexer, parse::ScssParser};
|
||||||
|
|
||||||
|
mod ast;
|
||||||
|
mod builtin;
|
||||||
|
mod color;
|
||||||
|
mod common;
|
||||||
|
mod context_flags;
|
||||||
|
mod error;
|
||||||
|
mod evaluate;
|
||||||
|
mod fs;
|
||||||
|
mod interner;
|
||||||
|
mod lexer;
|
||||||
|
mod options;
|
||||||
|
mod parse;
|
||||||
|
mod selector;
|
||||||
|
mod serializer;
|
||||||
|
mod token;
|
||||||
|
mod unit;
|
||||||
|
mod utils;
|
||||||
|
mod value;
|
||||||
|
|
||||||
|
fn raw_to_parse_error(map: &CodeMap, err: Error, unicode: bool) -> Box<Error> {
|
||||||
|
let (message, span) = err.raw();
|
||||||
|
Box::new(Error::from_loc(message, map.look_up_span(span), unicode))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_string_with_file_name<P: AsRef<Path>>(
|
||||||
|
input: String,
|
||||||
|
file_name: P,
|
||||||
|
options: &Options,
|
||||||
|
) -> Result<String> {
|
||||||
|
let mut map = CodeMap::new();
|
||||||
|
let path = file_name.as_ref();
|
||||||
|
let file = map.add_file(path.to_string_lossy().into_owned(), input);
|
||||||
|
let empty_span = file.span.subspan(0, 0);
|
||||||
|
let lexer = Lexer::new_from_file(&file);
|
||||||
|
|
||||||
|
let input_syntax = options
|
||||||
|
.input_syntax
|
||||||
|
.unwrap_or_else(|| InputSyntax::for_path(path));
|
||||||
|
|
||||||
|
let stylesheet = match input_syntax {
|
||||||
|
InputSyntax::Scss => {
|
||||||
|
ScssParser::new(lexer, &mut map, options, empty_span, file_name.as_ref()).__parse()
|
||||||
|
}
|
||||||
|
InputSyntax::Sass => {
|
||||||
|
SassParser::new(lexer, &mut map, options, empty_span, file_name.as_ref()).__parse()
|
||||||
|
}
|
||||||
|
InputSyntax::Css => {
|
||||||
|
CssParser::new(lexer, &mut map, options, empty_span, file_name.as_ref()).__parse()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let stylesheet = match stylesheet {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => return Err(raw_to_parse_error(&map, *e, options.unicode_error_messages)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut visitor = Visitor::new(path, options, &mut map, empty_span);
|
||||||
|
match visitor.visit_stylesheet(stylesheet) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => return Err(raw_to_parse_error(&map, *e, options.unicode_error_messages)),
|
||||||
|
}
|
||||||
|
let stmts = visitor.finish();
|
||||||
|
|
||||||
|
let mut serializer = Serializer::new(options, &map, false, empty_span);
|
||||||
|
|
||||||
|
let mut prev_was_group_end = false;
|
||||||
|
let mut prev_requires_semicolon = false;
|
||||||
|
for stmt in stmts {
|
||||||
|
if stmt.is_invisible() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_group_end = stmt.is_group_end();
|
||||||
|
let requires_semicolon = Serializer::requires_semicolon(&stmt);
|
||||||
|
|
||||||
|
serializer
|
||||||
|
.visit_group(stmt, prev_was_group_end, prev_requires_semicolon)
|
||||||
|
.map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages))?;
|
||||||
|
|
||||||
|
prev_was_group_end = is_group_end;
|
||||||
|
prev_requires_semicolon = requires_semicolon;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(serializer.finish(prev_requires_semicolon))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compile CSS from a path
|
||||||
|
///
|
||||||
|
/// n.b. grass does not currently support files or paths that are not valid UTF-8
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// fn main() -> Result<(), Box<grass::Error>> {
|
||||||
|
/// let sass = grass::from_path("input.scss", &grass::Options::default())?;
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn from_path<P: AsRef<Path>>(p: P, options: &Options) -> Result<String> {
|
||||||
|
from_string_with_file_name(String::from_utf8(options.fs.read(p.as_ref())?)?, p, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compile CSS from a string
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// fn main() -> Result<(), Box<grass::Error>> {
|
||||||
|
/// let sass = grass::from_string("a { b { color: &; } }".to_string(), &grass::Options::default())?;
|
||||||
|
/// assert_eq!(sass, "a b {\n color: a b;\n}\n");
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn from_string(input: String, options: &Options) -> Result<String> {
|
||||||
|
from_string_with_file_name(input, "stdin", options)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "wasm-exports")]
|
||||||
|
#[wasm_bindgen(js_name = from_string)]
|
||||||
|
pub fn from_string_js(input: String) -> std::result::Result<String, String> {
|
||||||
|
from_string(input, &Options::default()).map_err(|e| e.to_string())
|
||||||
|
}
|
15
include_sass/Cargo.toml
Normal file
15
include_sass/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "include_sass"
|
||||||
|
version = "0.12.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
syn = { version = "1.0.103", default-features = false }
|
||||||
|
grass_internal = { path = "../grass_internal" }
|
||||||
|
quote = "1.0.23"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
nightly = []
|
119
include_sass/src/lib.rs
Normal file
119
include_sass/src/lib.rs
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
#![cfg_attr(feature = "nightly", feature(track_path))]
|
||||||
|
|
||||||
|
use std::{cell::RefCell, collections::HashSet, path::PathBuf};
|
||||||
|
|
||||||
|
use grass_internal::StdFs;
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use quote::format_ident;
|
||||||
|
use syn::{parse_macro_input, LitStr};
|
||||||
|
|
||||||
|
use quote::__private::TokenStream as TokenStream2;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct FileTracker<'a> {
|
||||||
|
files: RefCell<HashSet<PathBuf>>,
|
||||||
|
fs: &'a dyn grass_internal::Fs,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> grass_internal::Fs for FileTracker<'a> {
|
||||||
|
fn is_dir(&self, path: &std::path::Path) -> bool {
|
||||||
|
#[cfg(feature = "nightly")]
|
||||||
|
if let Ok(p) = std::fs::canonicalize(path) {
|
||||||
|
self.files.borrow_mut().insert(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.fs.is_dir(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_file(&self, path: &std::path::Path) -> bool {
|
||||||
|
#[cfg(feature = "nightly")]
|
||||||
|
if let Ok(p) = std::fs::canonicalize(path) {
|
||||||
|
self.files.borrow_mut().insert(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.fs.is_file(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(&self, path: &std::path::Path) -> std::io::Result<Vec<u8>> {
|
||||||
|
if let Ok(p) = std::fs::canonicalize(path) {
|
||||||
|
self.files.borrow_mut().insert(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.fs.read(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "nightly"))]
|
||||||
|
fn track_files(files: &HashSet<PathBuf>) -> TokenStream2 {
|
||||||
|
let mut s: TokenStream2 = quote::quote!();
|
||||||
|
|
||||||
|
for (idx, file) in files.iter().enumerate() {
|
||||||
|
let ident = format_ident!("__VAR{}", idx);
|
||||||
|
let file_name = file.to_string_lossy();
|
||||||
|
s.extend::<TokenStream2>(quote::quote!(
|
||||||
|
const #ident: &str = include_str!(#file_name);
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "nightly")]
|
||||||
|
fn track_files(files: &HashSet<PathBuf>) {
|
||||||
|
for file in files {
|
||||||
|
proc_macro::tracked_path::path(file.to_string_lossy());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "nightly"))]
|
||||||
|
fn finish(css: String, files: &HashSet<PathBuf>) -> TokenStream {
|
||||||
|
let files = track_files(files);
|
||||||
|
|
||||||
|
quote::quote!(
|
||||||
|
{
|
||||||
|
#files
|
||||||
|
#css
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "nightly")]
|
||||||
|
fn finish(css: String, files: &HashSet<PathBuf>) -> TokenStream {
|
||||||
|
track_files(files);
|
||||||
|
quote::quote!(#css).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn include_sass(item: TokenStream) -> TokenStream {
|
||||||
|
let input = parse_macro_input!(item as LitStr);
|
||||||
|
|
||||||
|
let options = grass_internal::Options::default();
|
||||||
|
|
||||||
|
let fs = FileTracker {
|
||||||
|
files: RefCell::new(HashSet::new()),
|
||||||
|
fs: &StdFs,
|
||||||
|
};
|
||||||
|
|
||||||
|
let value = input.value();
|
||||||
|
|
||||||
|
let css = match grass_internal::from_path(
|
||||||
|
value,
|
||||||
|
&options
|
||||||
|
.fs(&fs)
|
||||||
|
.style(grass_internal::OutputStyle::Compressed),
|
||||||
|
) {
|
||||||
|
Ok(css) => css,
|
||||||
|
Err(e) => {
|
||||||
|
let err = syn::Error::new(
|
||||||
|
input.span(),
|
||||||
|
format!("Failed to compile Sass\n{}", e.to_string()),
|
||||||
|
);
|
||||||
|
return syn::Error::into_compile_error(err).into();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let files = &*fs.files.borrow();
|
||||||
|
|
||||||
|
finish(css, files)
|
||||||
|
}
|
166
src/lib.rs
166
src/lib.rs
@ -31,6 +31,7 @@ grass input.scss
|
|||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#![cfg_attr(doc, feature(doc_cfg))]
|
||||||
#![warn(clippy::all, clippy::cargo)]
|
#![warn(clippy::all, clippy::cargo)]
|
||||||
#![deny(missing_debug_implementations)]
|
#![deny(missing_debug_implementations)]
|
||||||
#![allow(
|
#![allow(
|
||||||
@ -79,142 +80,37 @@ grass input.scss
|
|||||||
unknown_lints,
|
unknown_lints,
|
||||||
)]
|
)]
|
||||||
|
|
||||||
use std::path::Path;
|
pub use grass_internal::*;
|
||||||
|
|
||||||
use parse::{CssParser, SassParser, StylesheetParser};
|
/// Include CSS in your binary at compile time from a Sass source file
|
||||||
use serializer::Serializer;
|
///
|
||||||
#[cfg(feature = "wasm-exports")]
|
/// ```no_run
|
||||||
use wasm_bindgen::prelude::*;
|
/// static CSS: &str = grass::include!("../static/_main.scss");
|
||||||
|
/// ```
|
||||||
use codemap::CodeMap;
|
///
|
||||||
|
/// This requires the `"macro"` feature, which is not enabled by default.
|
||||||
pub use crate::error::{
|
///
|
||||||
PublicSassErrorKind as ErrorKind, SassError as Error, SassResult as Result,
|
/// By default `grass` will track files using [`include_str!`]. This allows incremental
|
||||||
};
|
/// compilation to be updated when any Sass files are modified.
|
||||||
pub use crate::fs::{Fs, NullFs, StdFs};
|
///
|
||||||
pub use crate::options::{InputSyntax, Options, OutputStyle};
|
/// If compiling with a nightly version of rust, `grass` can make use of
|
||||||
pub(crate) use crate::{context_flags::ContextFlags, token::Token};
|
/// [proc_macro::tracked_path](https://github.com/rust-lang/rust/issues/99515)
|
||||||
use crate::{evaluate::Visitor, lexer::Lexer, parse::ScssParser};
|
/// in order to force incremental recompilation, which is more robust and potentially
|
||||||
|
/// faster. This is enabled by the `"nightly"` feature.
|
||||||
mod ast;
|
///
|
||||||
mod builtin;
|
/// ###### Limitations
|
||||||
mod color;
|
///
|
||||||
mod common;
|
/// Compilation options are not configurable with this macro. The default values
|
||||||
mod context_flags;
|
/// for all options are used, except for output style, which is compressed.
|
||||||
mod error;
|
#[macro_export]
|
||||||
mod evaluate;
|
#[cfg(any(feature = "macro", doc))]
|
||||||
mod fs;
|
#[cfg_attr(doc, doc(cfg(feature = "macro")))]
|
||||||
mod interner;
|
macro_rules! include {
|
||||||
mod lexer;
|
($path:literal) => {
|
||||||
mod options;
|
$crate::__internal_include_sass::include_sass!($path);
|
||||||
mod parse;
|
|
||||||
mod selector;
|
|
||||||
mod serializer;
|
|
||||||
mod token;
|
|
||||||
mod unit;
|
|
||||||
mod utils;
|
|
||||||
mod value;
|
|
||||||
|
|
||||||
fn raw_to_parse_error(map: &CodeMap, err: Error, unicode: bool) -> Box<Error> {
|
|
||||||
let (message, span) = err.raw();
|
|
||||||
Box::new(Error::from_loc(message, map.look_up_span(span), unicode))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_string_with_file_name<P: AsRef<Path>>(
|
|
||||||
input: String,
|
|
||||||
file_name: P,
|
|
||||||
options: &Options,
|
|
||||||
) -> Result<String> {
|
|
||||||
let mut map = CodeMap::new();
|
|
||||||
let path = file_name.as_ref();
|
|
||||||
let file = map.add_file(path.to_string_lossy().into_owned(), input);
|
|
||||||
let empty_span = file.span.subspan(0, 0);
|
|
||||||
let lexer = Lexer::new_from_file(&file);
|
|
||||||
|
|
||||||
let input_syntax = options
|
|
||||||
.input_syntax
|
|
||||||
.unwrap_or_else(|| InputSyntax::for_path(path));
|
|
||||||
|
|
||||||
let stylesheet = match input_syntax {
|
|
||||||
InputSyntax::Scss => {
|
|
||||||
ScssParser::new(lexer, &mut map, options, empty_span, file_name.as_ref()).__parse()
|
|
||||||
}
|
|
||||||
InputSyntax::Sass => {
|
|
||||||
SassParser::new(lexer, &mut map, options, empty_span, file_name.as_ref()).__parse()
|
|
||||||
}
|
|
||||||
InputSyntax::Css => {
|
|
||||||
CssParser::new(lexer, &mut map, options, empty_span, file_name.as_ref()).__parse()
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let stylesheet = match stylesheet {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(e) => return Err(raw_to_parse_error(&map, *e, options.unicode_error_messages)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut visitor = Visitor::new(path, options, &mut map, empty_span);
|
|
||||||
match visitor.visit_stylesheet(stylesheet) {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => return Err(raw_to_parse_error(&map, *e, options.unicode_error_messages)),
|
|
||||||
}
|
|
||||||
let stmts = visitor.finish();
|
|
||||||
|
|
||||||
let mut serializer = Serializer::new(options, &map, false, empty_span);
|
|
||||||
|
|
||||||
let mut prev_was_group_end = false;
|
|
||||||
let mut prev_requires_semicolon = false;
|
|
||||||
for stmt in stmts {
|
|
||||||
if stmt.is_invisible() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let is_group_end = stmt.is_group_end();
|
|
||||||
let requires_semicolon = Serializer::requires_semicolon(&stmt);
|
|
||||||
|
|
||||||
serializer
|
|
||||||
.visit_group(stmt, prev_was_group_end, prev_requires_semicolon)
|
|
||||||
.map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages))?;
|
|
||||||
|
|
||||||
prev_was_group_end = is_group_end;
|
|
||||||
prev_requires_semicolon = requires_semicolon;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(serializer.finish(prev_requires_semicolon))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compile CSS from a path
|
#[doc(hidden)]
|
||||||
///
|
#[cfg(feature = "macro")]
|
||||||
/// n.b. grass does not currently support files or paths that are not valid UTF-8
|
pub use include_sass as __internal_include_sass;
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// fn main() -> Result<(), Box<grass::Error>> {
|
|
||||||
/// let sass = grass::from_path("input.scss", &grass::Options::default())?;
|
|
||||||
/// Ok(())
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn from_path<P: AsRef<Path>>(p: P, options: &Options) -> Result<String> {
|
|
||||||
from_string_with_file_name(String::from_utf8(options.fs.read(p.as_ref())?)?, p, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compile CSS from a string
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// fn main() -> Result<(), Box<grass::Error>> {
|
|
||||||
/// let sass = grass::from_string("a { b { color: &; } }".to_string(), &grass::Options::default())?;
|
|
||||||
/// assert_eq!(sass, "a b {\n color: a b;\n}\n");
|
|
||||||
/// Ok(())
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn from_string(input: String, options: &Options) -> Result<String> {
|
|
||||||
from_string_with_file_name(input, "stdin", options)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "wasm-exports")]
|
|
||||||
#[wasm_bindgen(js_name = from_string)]
|
|
||||||
pub fn from_string_js(input: String) -> std::result::Result<String, String> {
|
|
||||||
from_string(input, &Options::default()).map_err(|e| e.to_string())
|
|
||||||
}
|
|
||||||
|
7
tests/include_sass.rs
Normal file
7
tests/include_sass.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#[cfg(feature = "macro")]
|
||||||
|
#[test]
|
||||||
|
fn basic() {
|
||||||
|
let css: &str = grass::include!("./input.scss");
|
||||||
|
|
||||||
|
assert!(css == "a {\n color: red;\n}\n");
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user