diff --git a/src/lib.rs b/src/lib.rs index 02404a3..f66a247 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,7 @@ Spec progress as of 2020-07-04: ## Use as library ``` fn main() -> Result<(), Box> { - let sass = grass::from_string("a { b { color: &; } }".to_string())?; + 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(()) } @@ -122,6 +122,77 @@ mod unit; mod utils; mod value; +#[non_exhaustive] +#[derive(Debug)] +pub enum OutputStyle { + Expanded, + Compressed, +} + +#[derive(Debug)] +pub struct Options<'a> { + pub style: OutputStyle, + pub load_paths: Vec<&'a Path>, + pub allows_charset: bool, + pub unicode_error_messages: bool, + pub quiet: bool, +} + +/// +/// `load_paths` - list of paths/files to check for imports for more information see the docs: +/// - +/// - +impl<'a> Default for Options<'a> { + fn default() -> Self { + Self { + style: OutputStyle::Expanded, + load_paths: Vec::new(), + allows_charset: true, + unicode_error_messages: true, + quiet: false, + } + } +} + +impl<'a> Options<'a> { + /// `grass` currently offers 2 different output styles + /// + /// - `OutputStyle::Expanded` writes each selector and declaration on its own line. + /// - `OutputStyle::Compressed` removes as many extra characters as possible, and writes the entire stylesheet on a single line. + /// + /// By default, output is expanded. + pub fn style(mut self, style: OutputStyle) -> Self { + self.style = style; + self + } + + pub fn quiet(mut self, quiet: bool) -> Self { + self.quiet = quiet; + self + } + + pub fn load_path(mut self, path: &'a Path) -> Self { + self.load_paths.push(path); + self + } + + /// adds on to the `load_path` vec, does not set the vec to paths + pub fn load_paths(mut self, paths: &'a [&'a Path]) -> Self { + self.load_paths.extend_from_slice(paths); + self + } + + pub fn allows_charset(mut self, allows_charset: bool) -> Self { + self.allows_charset = allows_charset; + self + } + + pub fn unicode_error_messages(mut self, unicode_error_messages: bool) -> Self { + self.unicode_error_messages = unicode_error_messages; + self + } +} + fn raw_to_parse_error(map: &CodeMap, err: Error) -> Box { let (message, span) = err.raw(); Box::new(Error::from_loc(message, map.look_up_span(span))) @@ -131,7 +202,7 @@ fn raw_to_parse_error(map: &CodeMap, err: Error) -> Box { /// /// ``` /// fn main() -> Result<(), Box> { -/// let sass = grass::from_path("input.scss")?; +/// let sass = grass::from_path("input.scss", &grass::Options::default())?; /// Ok(()) /// } /// ``` @@ -139,24 +210,7 @@ fn raw_to_parse_error(map: &CodeMap, err: Error) -> Box { #[cfg_attr(feature = "profiling", inline(never))] #[cfg_attr(not(feature = "profiling"), inline)] #[cfg(not(feature = "wasm"))] -pub fn from_path(p: &str) -> Result { - from_paths(p, &Vec::new()) -} - -/// Compile CSS from a file and load in additional files through importing them -/// note: @use is currently unsupported -/// -/// ``` -/// fn main() -> Result<(), Box> { -/// let sass = grass::from_paths("input.scss", &[std::path::Path::new("benches")])?; -/// Ok(()) -/// } -/// ``` -/// (grass does not currently allow files or paths that are not valid UTF-8) -#[cfg_attr(feature = "profiling", inline(never))] -#[cfg_attr(not(feature = "profiling"), inline)] -#[cfg(not(feature = "wasm"))] -pub fn from_paths(p: &str, loadpaths: &[&Path]) -> Result { +pub fn from_path(p: &str, options: &'_ Options<'_>) -> Result { let mut map = CodeMap::new(); let file = map.add_file(p.into(), String::from_utf8(fs::read(p)?)?); let empty_span = file.span.subspan(0, 0); @@ -178,7 +232,7 @@ pub fn from_paths(p: &str, loadpaths: &[&Path]) -> Result { at_root_has_selector: false, extender: &mut Extender::new(empty_span), content_scopes: &mut Scopes::new(), - load_paths: loadpaths, + options, } .parse() .map_err(|e| raw_to_parse_error(&map, *e))?; @@ -188,11 +242,12 @@ pub fn from_paths(p: &str, loadpaths: &[&Path]) -> Result { .pretty_print(&map) .map_err(|e| raw_to_parse_error(&map, *e)) } + /// Compile CSS from a string /// /// ``` /// fn main() -> Result<(), Box> { -/// let sass = grass::from_string("a { b { color: &; } }".to_string())?; +/// 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(()) /// } @@ -200,7 +255,7 @@ pub fn from_paths(p: &str, loadpaths: &[&Path]) -> Result { #[cfg_attr(feature = "profiling", inline(never))] #[cfg_attr(not(feature = "profiling"), inline)] #[cfg(not(feature = "wasm"))] -pub fn from_string(p: String) -> Result { +pub fn from_string(p: String, options: &'_ Options<'_>) -> Result { let mut map = CodeMap::new(); let file = map.add_file("stdin".into(), p); let empty_span = file.span.subspan(0, 0); @@ -221,7 +276,7 @@ pub fn from_string(p: String) -> Result { at_root_has_selector: false, extender: &mut Extender::new(empty_span), content_scopes: &mut Scopes::new(), - load_paths: &Vec::new(), + options, } .parse() .map_err(|e| raw_to_parse_error(&map, *e))?; @@ -234,7 +289,7 @@ pub fn from_string(p: String) -> Result { #[cfg(feature = "wasm")] #[wasm_bindgen] -pub fn from_string(p: String) -> std::result::Result { +pub fn from_string(p: String, options: &'_ Options<'_>) -> std::result::Result { let mut map = CodeMap::new(); let file = map.add_file("stdin".into(), p); let empty_span = file.span.subspan(0, 0); diff --git a/src/main.rs b/src/main.rs index 6ebece7..cbcc493 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ use std::{ use clap::{arg_enum, App, AppSettings, Arg}; #[cfg(not(feature = "wasm"))] -use grass::from_paths; +use grass::{from_path, Options}; arg_enum! { #[derive(PartialEq, Debug)] @@ -189,11 +189,13 @@ fn main() -> std::io::Result<()> { vals = Vec::new(); } + let options = &Options::default().load_paths(&vals); + if let Some(name) = matches.value_of("INPUT") { if let Some(path) = matches.value_of("OUTPUT") { let mut buf = BufWriter::new(File::open(path).unwrap_or(File::create(path)?)); buf.write_all( - from_paths(name, &vals) + from_path(name, &options) .unwrap_or_else(|e| { eprintln!("{}", e); std::process::exit(1) @@ -203,7 +205,7 @@ fn main() -> std::io::Result<()> { } else { let mut stdout = BufWriter::new(stdout()); stdout.write_all( - from_paths(name, &vals) + from_path(name, &options) .unwrap_or_else(|e| { eprintln!("{}", e); std::process::exit(1) diff --git a/src/parse/control_flow.rs b/src/parse/control_flow.rs index 48a9593..5ce5333 100644 --- a/src/parse/control_flow.rs +++ b/src/parse/control_flow.rs @@ -52,7 +52,7 @@ impl<'a> Parser<'a> { at_root_has_selector: self.at_root_has_selector, extender: self.extender, content_scopes: self.content_scopes, - load_paths: self.load_paths, + options: self.options, } .parse_stmt()?; } else { @@ -111,7 +111,7 @@ impl<'a> Parser<'a> { at_root_has_selector: self.at_root_has_selector, extender: self.extender, content_scopes: self.content_scopes, - load_paths: self.load_paths, + options: self.options, } .parse_stmt()?; } else { @@ -139,7 +139,7 @@ impl<'a> Parser<'a> { at_root_has_selector: self.at_root_has_selector, extender: self.extender, content_scopes: self.content_scopes, - load_paths: self.load_paths, + options: self.options, } .parse_stmt(); } @@ -319,7 +319,7 @@ impl<'a> Parser<'a> { at_root_has_selector: self.at_root_has_selector, extender: self.extender, content_scopes: self.content_scopes, - load_paths: self.load_paths, + options: self.options, } .parse()?; if !these_stmts.is_empty() { @@ -341,7 +341,7 @@ impl<'a> Parser<'a> { at_root_has_selector: self.at_root_has_selector, extender: self.extender, content_scopes: self.content_scopes, - load_paths: self.load_paths, + options: self.options, } .parse()?, ); @@ -391,7 +391,7 @@ impl<'a> Parser<'a> { at_root_has_selector: self.at_root_has_selector, extender: self.extender, content_scopes: self.content_scopes, - load_paths: self.load_paths, + options: self.options, } .parse()?; if !these_stmts.is_empty() { @@ -413,7 +413,7 @@ impl<'a> Parser<'a> { at_root_has_selector: self.at_root_has_selector, extender: self.extender, content_scopes: self.content_scopes, - load_paths: self.load_paths, + options: self.options, } .parse()?, ); @@ -516,7 +516,7 @@ impl<'a> Parser<'a> { at_root_has_selector: self.at_root_has_selector, extender: self.extender, content_scopes: self.content_scopes, - load_paths: self.load_paths, + options: self.options, } .parse()?; if !these_stmts.is_empty() { @@ -538,7 +538,7 @@ impl<'a> Parser<'a> { at_root_has_selector: self.at_root_has_selector, extender: self.extender, content_scopes: self.content_scopes, - load_paths: self.load_paths, + options: self.options, } .parse()?, ); diff --git a/src/parse/function.rs b/src/parse/function.rs index 7b47892..fb8b516 100644 --- a/src/parse/function.rs +++ b/src/parse/function.rs @@ -107,7 +107,7 @@ impl<'a> Parser<'a> { at_root_has_selector: self.at_root_has_selector, extender: self.extender, content_scopes: self.content_scopes, - load_paths: self.load_paths, + options: self.options, } .parse()?; diff --git a/src/parse/import.rs b/src/parse/import.rs index dcc0ba3..abef143 100644 --- a/src/parse/import.rs +++ b/src/parse/import.rs @@ -7,7 +7,7 @@ use crate::{common::QuoteKind, error::SassResult, lexer::Lexer, value::Value, To use super::{Parser, Stmt}; -/// Searches the current directory of the file then searches in load paths directories +/// Searches the current directory of the file then searches in `load_paths` directories /// if the import has not yet been found. /// /// @@ -107,7 +107,7 @@ impl<'a> Parser<'a> { let name = path_buf.file_name().unwrap_or_else(|| OsStr::new("..")); - if let Some(name) = find_import(&path_buf, name, self.load_paths) { + if let Some(name) = find_import(&path_buf, name, &self.options.load_paths) { let file = self.map.add_file( name.to_string_lossy().into(), String::from_utf8(fs::read(&name)?)?, @@ -130,7 +130,7 @@ impl<'a> Parser<'a> { at_root_has_selector: self.at_root_has_selector, extender: self.extender, content_scopes: self.content_scopes, - load_paths: self.load_paths, + options: self.options, } .parse(); } diff --git a/src/parse/keyframes.rs b/src/parse/keyframes.rs index f647de2..b24ec30 100644 --- a/src/parse/keyframes.rs +++ b/src/parse/keyframes.rs @@ -165,7 +165,7 @@ impl<'a> Parser<'a> { at_root_has_selector: self.at_root_has_selector, extender: self.extender, content_scopes: self.content_scopes, - load_paths: self.load_paths, + options: self.options, }) .parse_keyframes_selector()?; @@ -196,7 +196,7 @@ impl<'a> Parser<'a> { at_root_has_selector: self.at_root_has_selector, extender: self.extender, content_scopes: self.content_scopes, - load_paths: self.load_paths, + options: self.options, } .parse_stmt()?; diff --git a/src/parse/mixin.rs b/src/parse/mixin.rs index 95709c9..e9868b9 100644 --- a/src/parse/mixin.rs +++ b/src/parse/mixin.rs @@ -154,7 +154,7 @@ impl<'a> Parser<'a> { at_root_has_selector: self.at_root_has_selector, extender: self.extender, content_scopes: self.content_scopes, - load_paths: self.load_paths, + options: self.options, } .parse()?; @@ -206,7 +206,7 @@ impl<'a> Parser<'a> { at_root_has_selector: self.at_root_has_selector, extender: self.extender, content_scopes: self.scopes, - load_paths: self.load_paths, + options: self.options, } .parse()? } else { diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 8166ebb..50d22f1 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -17,11 +17,10 @@ use crate::{ style::Style, utils::{read_until_closing_curly_brace, read_until_semicolon_or_closing_curly_brace}, value::Value, - {Cow, Token}, + Options, {Cow, Token}, }; use common::{Comment, ContextFlags, NeverEmptyVec, SelectorOrStyle}; - pub(crate) use value::{HigherIntermediateValue, ValueVisitor}; mod args; @@ -83,7 +82,7 @@ pub(crate) struct Parser<'a> { pub at_root_has_selector: bool, pub extender: &'a mut Extender, - pub load_paths: &'a [&'a Path], + pub options: &'a Options<'a>, } impl<'a> Parser<'a> { @@ -361,7 +360,7 @@ impl<'a> Parser<'a> { at_root_has_selector: self.at_root_has_selector, extender: self.extender, content_scopes: self.content_scopes, - load_paths: self.load_paths, + options: self.options, }, allows_parent, true, @@ -600,7 +599,7 @@ impl<'a> Parser<'a> { at_root_has_selector: self.at_root_has_selector, extender: self.extender, content_scopes: self.content_scopes, - load_paths: self.load_paths, + options: self.options, } .parse_stmt()?; @@ -668,7 +667,7 @@ impl<'a> Parser<'a> { at_root_has_selector, extender: self.extender, content_scopes: self.content_scopes, - load_paths: self.load_paths, + options: self.options, } .parse()? .into_iter() @@ -709,7 +708,7 @@ impl<'a> Parser<'a> { at_root_has_selector: self.at_root_has_selector, extender: self.extender, content_scopes: self.content_scopes, - load_paths: self.load_paths, + options: self.options, } .parse_selector(false, true, String::new())?; @@ -787,7 +786,7 @@ impl<'a> Parser<'a> { at_root_has_selector: self.at_root_has_selector, extender: self.extender, content_scopes: self.content_scopes, - load_paths: self.load_paths, + options: self.options, } .parse_stmt()?; diff --git a/src/parse/value/parse.rs b/src/parse/value/parse.rs index 58fb2c0..7956efd 100644 --- a/src/parse/value/parse.rs +++ b/src/parse/value/parse.rs @@ -201,7 +201,7 @@ impl<'a> Parser<'a> { at_root_has_selector: self.at_root_has_selector, extender: self.extender, content_scopes: self.content_scopes, - load_paths: self.load_paths, + options: self.options, } .parse_value(in_paren) } diff --git a/src/value/mod.rs b/src/value/mod.rs index 29aba66..4a98ab2 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -476,7 +476,7 @@ impl Value { at_root_has_selector: parser.at_root_has_selector, extender: parser.extender, content_scopes: parser.content_scopes, - load_paths: parser.load_paths, + options: parser.options, } .parse_selector(allows_parent, true, String::new()) } diff --git a/tests/imports.rs b/tests/imports.rs index 3d351b0..8c16a1f 100644 --- a/tests/imports.rs +++ b/tests/imports.rs @@ -44,7 +44,7 @@ fn imports_variable() { tempfile!("imports_variable", "$a: red;"); assert_eq!( "a {\n color: red;\n}\n", - &grass::from_string(input.to_string()).expect(input) + &grass::from_string(input.to_string(), &grass::Options::default()).expect(input) ); } @@ -59,7 +59,7 @@ fn import_no_semicolon() { fn import_no_quotes() { let input = "@import import_no_quotes"; tempfile!("import_no_quotes", "$a: red;"); - match grass::from_string(input.to_string()) { + match grass::from_string(input.to_string(), &grass::Options::default()) { Ok(..) => panic!("did not fail"), Err(e) => assert_eq!( "Error: Expected string.", @@ -78,7 +78,7 @@ fn single_quotes_import() { tempfile!("single_quotes_import", "$a: red;"); assert_eq!( "a {\n color: red;\n}\n", - &grass::from_string(input.to_string()).expect(input) + &grass::from_string(input.to_string(), &grass::Options::default()).expect(input) ); } @@ -88,7 +88,7 @@ fn finds_name_scss() { tempfile!("finds_name_scss.scss", "$a: red;"); assert_eq!( "a {\n color: red;\n}\n", - &grass::from_string(input.to_string()).expect(input) + &grass::from_string(input.to_string(), &grass::Options::default()).expect(input) ); } @@ -98,7 +98,7 @@ fn finds_underscore_name_scss() { tempfile!("_finds_underscore_name_scss.scss", "$a: red;"); assert_eq!( "a {\n color: red;\n}\n", - &grass::from_string(input.to_string()).expect(input) + &grass::from_string(input.to_string(), &grass::Options::default()).expect(input) ); } @@ -110,7 +110,7 @@ fn chained_imports() { tempfile!("chained_imports__c.scss", "$a: red;"); assert_eq!( "a {\n color: red;\n}\n", - &grass::from_string(input.to_string()).expect(input) + &grass::from_string(input.to_string(), &grass::Options::default()).expect(input) ); } @@ -129,7 +129,7 @@ fn chained_imports_in_directory() { tempfile!("chained_imports_in_directory__c.scss", "$a: red;"); assert_eq!( "a {\n color: red;\n}\n", - &grass::from_string(input.to_string()).expect(input) + &grass::from_string(input.to_string(), &grass::Options::default()).expect(input) ); } diff --git a/tests/macros.rs b/tests/macros.rs index 30d0572..c6c1c7c 100644 --- a/tests/macros.rs +++ b/tests/macros.rs @@ -7,7 +7,7 @@ macro_rules! test { #[test] #[allow(non_snake_case)] fn $func() { - let sass = grass::from_string($input.to_string()) + let sass = grass::from_string($input.to_string(), &grass::Options::default()) .expect(concat!("failed to parse on ", $input)); assert_eq!( String::from($input), @@ -20,7 +20,7 @@ macro_rules! test { #[test] #[allow(non_snake_case)] fn $func() { - let sass = grass::from_string($input.to_string()) + let sass = grass::from_string($input.to_string(), &grass::Options::default()) .expect(concat!("failed to parse on ", $input)); assert_eq!( String::from($output), @@ -39,7 +39,7 @@ macro_rules! error { #[test] #[allow(non_snake_case)] fn $func() { - match grass::from_string($input.to_string()) { + match grass::from_string($input.to_string(), &grass::Options::default()) { Ok(..) => panic!("did not fail"), Err(e) => assert_eq!($err, e.to_string() .chars()