2022-12-26 15:33:04 -05:00
|
|
|
/*!
|
|
|
|
This crate provides functionality for compiling [Sass](https://sass-lang.com/) to CSS.
|
2020-04-18 18:53:18 -04:00
|
|
|
|
|
|
|
## Use as library
|
|
|
|
```
|
2020-06-26 08:03:43 -04:00
|
|
|
fn main() -> Result<(), Box<grass::Error>> {
|
2022-12-26 15:33:04 -05:00
|
|
|
let css = grass::from_string(
|
|
|
|
"a { b { color: &; } }".to_owned(),
|
|
|
|
&grass::Options::default()
|
|
|
|
)?;
|
|
|
|
assert_eq!(css, "a b {\n color: a b;\n}\n");
|
2020-04-18 18:53:18 -04:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
## Use as binary
|
|
|
|
```bash
|
|
|
|
cargo install grass
|
|
|
|
grass input.scss
|
|
|
|
```
|
|
|
|
*/
|
2020-03-01 09:08:13 -05:00
|
|
|
|
2022-12-26 15:33:04 -05:00
|
|
|
#![warn(clippy::all, clippy::cargo)]
|
2020-01-20 11:00:01 -05:00
|
|
|
#![deny(missing_debug_implementations)]
|
|
|
|
#![allow(
|
|
|
|
clippy::use_self,
|
|
|
|
clippy::missing_docs_in_private_items,
|
|
|
|
clippy::unreachable,
|
2020-02-02 10:27:08 -05:00
|
|
|
clippy::module_name_repetitions,
|
2020-05-24 17:41:24 -04:00
|
|
|
// filter isn't fallible
|
2021-07-03 23:17:31 -04:00
|
|
|
clippy::manual_filter_map,
|
2020-05-31 05:32:19 -04:00
|
|
|
clippy::new_ret_no_self,
|
2020-06-07 23:11:43 -04:00
|
|
|
renamed_and_removed_lints,
|
|
|
|
clippy::unknown_clippy_lints,
|
2020-06-24 07:05:14 -04:00
|
|
|
clippy::single_match,
|
2020-08-19 06:20:04 -04:00
|
|
|
clippy::unimplemented,
|
2020-11-16 03:25:55 -05:00
|
|
|
clippy::option_if_let_else,
|
2022-05-31 09:50:48 -04:00
|
|
|
clippy::branches_sharing_code,
|
2022-09-02 17:00:07 -04:00
|
|
|
clippy::derive_partial_eq_without_eq,
|
2020-03-30 15:43:15 -04:00
|
|
|
|
2020-02-14 18:28:09 -05:00
|
|
|
// 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,
|
2020-04-21 18:22:26 -04:00
|
|
|
clippy::redundant_pub_crate,
|
2020-07-03 12:56:19 -04:00
|
|
|
// the api is changing too often to allot this
|
|
|
|
clippy::missing_errors_doc,
|
2020-11-16 03:25:55 -05:00
|
|
|
clippy::missing_const_for_fn,
|
2020-11-16 14:17:49 -05:00
|
|
|
clippy::multiple_crate_versions,
|
2020-05-31 05:32:19 -04:00
|
|
|
|
|
|
|
clippy::wrong_self_convention,
|
|
|
|
clippy::items_after_statements,
|
2020-07-08 22:38:56 -04:00
|
|
|
// this is only available on nightly
|
|
|
|
clippy::unnested_or_patterns,
|
2022-12-26 15:33:04 -05:00
|
|
|
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,
|
2022-12-28 17:50:25 -05:00
|
|
|
|
|
|
|
unknown_lints,
|
2020-01-20 11:00:01 -05:00
|
|
|
)]
|
|
|
|
|
2022-02-04 09:41:10 +11:00
|
|
|
use std::path::Path;
|
|
|
|
|
2022-12-26 15:33:04 -05:00
|
|
|
use parse::{CssParser, SassParser, StylesheetParser};
|
|
|
|
use serializer::Serializer;
|
2022-02-04 09:41:10 +11:00
|
|
|
#[cfg(feature = "wasm-exports")]
|
2020-06-16 20:40:19 -04:00
|
|
|
use wasm_bindgen::prelude::*;
|
|
|
|
|
2020-06-16 19:38:30 -04:00
|
|
|
use codemap::CodeMap;
|
2020-04-12 19:37:12 -04:00
|
|
|
|
2022-09-03 19:23:07 -04:00
|
|
|
pub use crate::error::{
|
|
|
|
PublicSassErrorKind as ErrorKind, SassError as Error, SassResult as Result,
|
|
|
|
};
|
2022-02-04 09:41:10 +11:00
|
|
|
pub use crate::fs::{Fs, NullFs, StdFs};
|
2022-12-26 15:33:04 -05:00
|
|
|
pub use crate::options::{InputSyntax, Options, OutputStyle};
|
|
|
|
pub(crate) use crate::{context_flags::ContextFlags, token::Token};
|
|
|
|
use crate::{evaluate::Visitor, lexer::Lexer, parse::ScssParser};
|
2020-01-20 11:00:01 -05:00
|
|
|
|
2022-12-26 15:33:04 -05:00
|
|
|
mod ast;
|
2020-01-25 20:58:30 -05:00
|
|
|
mod builtin;
|
2020-01-20 11:00:01 -05:00
|
|
|
mod color;
|
|
|
|
mod common;
|
2022-12-26 15:33:04 -05:00
|
|
|
mod context_flags;
|
2020-01-20 11:00:01 -05:00
|
|
|
mod error;
|
2022-12-26 15:33:04 -05:00
|
|
|
mod evaluate;
|
2022-02-04 09:41:10 +11:00
|
|
|
mod fs;
|
2020-07-08 22:38:56 -04:00
|
|
|
mod interner;
|
2020-01-20 11:00:01 -05:00
|
|
|
mod lexer;
|
2022-12-26 15:33:04 -05:00
|
|
|
mod options;
|
2020-06-16 19:38:30 -04:00
|
|
|
mod parse;
|
2020-01-20 11:00:01 -05:00
|
|
|
mod selector;
|
2022-12-26 15:33:04 -05:00
|
|
|
mod serializer;
|
2020-03-19 19:32:11 -04:00
|
|
|
mod token;
|
2020-03-19 16:24:31 -04:00
|
|
|
mod unit;
|
2020-01-20 11:00:01 -05:00
|
|
|
mod utils;
|
2020-01-25 09:58:53 -05:00
|
|
|
mod value;
|
2020-01-20 11:00:01 -05:00
|
|
|
|
2022-12-26 15:33:04 -05:00
|
|
|
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))
|
2020-07-15 12:37:19 +01:00
|
|
|
}
|
|
|
|
|
2022-12-26 19:11:36 -05:00
|
|
|
fn from_string_with_file_name<P: AsRef<Path>>(
|
|
|
|
input: String,
|
|
|
|
file_name: P,
|
|
|
|
options: &Options,
|
|
|
|
) -> Result<String> {
|
2022-12-26 15:33:04 -05:00
|
|
|
let mut map = CodeMap::new();
|
2022-12-26 19:06:20 -05:00
|
|
|
let path = file_name.as_ref();
|
|
|
|
let file = map.add_file(path.to_string_lossy().into_owned(), input);
|
2022-12-26 15:33:04 -05:00
|
|
|
let empty_span = file.span.subspan(0, 0);
|
|
|
|
let lexer = Lexer::new_from_file(&file);
|
2022-02-04 09:41:10 +11:00
|
|
|
|
2022-12-26 15:33:04 -05:00
|
|
|
let input_syntax = options
|
|
|
|
.input_syntax
|
|
|
|
.unwrap_or_else(|| InputSyntax::for_path(path));
|
2020-07-15 12:37:19 +01:00
|
|
|
|
2022-12-26 15:33:04 -05:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
};
|
2020-07-15 12:37:19 +01:00
|
|
|
|
2022-12-26 15:33:04 -05:00
|
|
|
let stylesheet = match stylesheet {
|
|
|
|
Ok(v) => v,
|
|
|
|
Err(e) => return Err(raw_to_parse_error(&map, *e, options.unicode_error_messages)),
|
|
|
|
};
|
2020-07-15 13:40:39 -04:00
|
|
|
|
2022-12-26 15:33:04 -05:00
|
|
|
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)),
|
2020-07-15 12:37:19 +01:00
|
|
|
}
|
2022-12-26 15:33:04 -05:00
|
|
|
let stmts = visitor.finish();
|
2020-07-15 12:37:19 +01:00
|
|
|
|
2022-12-26 15:33:04 -05:00
|
|
|
let mut serializer = Serializer::new(options, &map, false, empty_span);
|
2021-07-25 09:53:18 -04:00
|
|
|
|
2022-12-26 15:33:04 -05:00
|
|
|
let mut prev_was_group_end = false;
|
|
|
|
let mut prev_requires_semicolon = false;
|
|
|
|
for stmt in stmts {
|
|
|
|
if stmt.is_invisible() {
|
|
|
|
continue;
|
|
|
|
}
|
2020-07-15 12:37:19 +01:00
|
|
|
|
2022-12-26 15:33:04 -05:00
|
|
|
let is_group_end = stmt.is_group_end();
|
|
|
|
let requires_semicolon = Serializer::requires_semicolon(&stmt);
|
2020-04-12 19:37:12 -04:00
|
|
|
|
2022-12-26 15:33:04 -05:00
|
|
|
serializer
|
|
|
|
.visit_group(stmt, prev_was_group_end, prev_requires_semicolon)
|
|
|
|
.map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages))?;
|
2020-07-04 12:38:09 -04:00
|
|
|
|
2022-12-26 15:33:04 -05:00
|
|
|
prev_was_group_end = is_group_end;
|
|
|
|
prev_requires_semicolon = requires_semicolon;
|
2020-07-04 12:38:09 -04:00
|
|
|
}
|
|
|
|
|
2022-12-26 15:33:04 -05:00
|
|
|
Ok(serializer.finish(prev_requires_semicolon))
|
2020-01-20 11:00:01 -05:00
|
|
|
}
|
2020-07-15 12:37:19 +01:00
|
|
|
|
2021-07-22 21:23:09 -04:00
|
|
|
/// Compile CSS from a path
|
|
|
|
///
|
2021-07-25 13:02:58 -04:00
|
|
|
/// n.b. grass does not currently support files or paths that are not valid UTF-8
|
|
|
|
///
|
2021-07-22 21:23:09 -04:00
|
|
|
/// ```
|
|
|
|
/// fn main() -> Result<(), Box<grass::Error>> {
|
|
|
|
/// let sass = grass::from_path("input.scss", &grass::Options::default())?;
|
|
|
|
/// Ok(())
|
|
|
|
/// }
|
|
|
|
/// ```
|
2022-12-26 15:33:04 -05:00
|
|
|
|
|
|
|
#[inline]
|
2022-12-26 19:06:20 -05:00
|
|
|
pub fn from_path<P: AsRef<Path>>(p: P, options: &Options) -> Result<String> {
|
2022-12-26 19:11:36 -05:00
|
|
|
from_string_with_file_name(String::from_utf8(options.fs.read(p.as_ref())?)?, p, options)
|
2021-07-22 21:23:09 -04:00
|
|
|
}
|
|
|
|
|
2020-07-06 10:24:29 -04:00
|
|
|
/// Compile CSS from a string
|
2020-06-16 19:38:30 -04:00
|
|
|
///
|
|
|
|
/// ```
|
2020-06-26 06:40:34 -04:00
|
|
|
/// fn main() -> Result<(), Box<grass::Error>> {
|
2020-07-15 12:37:19 +01:00
|
|
|
/// let sass = grass::from_string("a { b { color: &; } }".to_string(), &grass::Options::default())?;
|
2020-06-16 19:38:30 -04:00
|
|
|
/// assert_eq!(sass, "a b {\n color: a b;\n}\n");
|
|
|
|
/// Ok(())
|
|
|
|
/// }
|
|
|
|
/// ```
|
2022-12-26 15:33:04 -05:00
|
|
|
|
|
|
|
#[inline]
|
2021-07-22 21:23:09 -04:00
|
|
|
pub fn from_string(input: String, options: &Options) -> Result<String> {
|
|
|
|
from_string_with_file_name(input, "stdin", options)
|
2020-01-20 11:00:01 -05:00
|
|
|
}
|
2020-06-16 20:40:19 -04:00
|
|
|
|
2022-02-04 09:41:10 +11:00
|
|
|
#[cfg(feature = "wasm-exports")]
|
|
|
|
#[wasm_bindgen(js_name = from_string)]
|
2022-09-03 19:42:27 -04:00
|
|
|
pub fn from_string_js(input: String) -> std::result::Result<String, String> {
|
|
|
|
from_string(input, &Options::default()).map_err(|e| e.to_string())
|
2020-06-16 20:40:19 -04:00
|
|
|
}
|