File system interception, and various other matters (#55)
This commit is contained in:
parent
dd92ebf39b
commit
3c5463ac4c
@ -1,3 +1,8 @@
|
|||||||
|
# 0.11.0
|
||||||
|
|
||||||
|
- `fs` option added to allow interception and reimplementation of all file system operations (such as imports)
|
||||||
|
- `wasm` feature renamed to/replaced with `wasm-exports`, which no longer materially alters the API: `from_path` is reinstated, and `from_string` once again returns the full error type; but the WASM export `from_string` (which returns a string error) is now a new function `from_string_js`. (It was renamed from `wasm` to `wasm-exports` because the name was misleading; Rust code that uses grass doesn’t need this feature, it’s solely to get this `from_string` WASM export.)
|
||||||
|
|
||||||
# 0.10.8
|
# 0.10.8
|
||||||
|
|
||||||
- bugfix: properly emit the number `0` in compressed mode (#53)
|
- bugfix: properly emit the number `0` in compressed mode (#53)
|
||||||
|
@ -74,8 +74,8 @@ commandline = ["clap"]
|
|||||||
nightly = []
|
nightly = []
|
||||||
# Option (enabled by default): enable the builtin functions `random([$limit])` and `unique-id()`
|
# Option (enabled by default): enable the builtin functions `random([$limit])` and `unique-id()`
|
||||||
random = ["rand"]
|
random = ["rand"]
|
||||||
# Option: compile to web assembly
|
# Option: expose JavaScript-friendly WebAssembly exports
|
||||||
wasm = ["wasm-bindgen"]
|
wasm-exports = ["wasm-bindgen"]
|
||||||
# Option: enable features that assist in profiling (e.g. inline(never))
|
# Option: enable features that assist in profiling (e.g. inline(never))
|
||||||
profiling = []
|
profiling = []
|
||||||
# Option: enable criterion for benchmarking
|
# Option: enable criterion for benchmarking
|
||||||
|
70
src/fs.rs
Normal file
70
src/fs.rs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
use std::io::{Error, ErrorKind, Result};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
/// A trait to allow replacing the file system lookup mechanisms.
|
||||||
|
///
|
||||||
|
/// As it stands, this is imperfect: it’s still using the types and some operations from
|
||||||
|
/// `std::path`, which constrain it to the target platform’s norms. This could be ameliorated by
|
||||||
|
/// the use of associated types for `Path` and `PathBuf`, and putting all remaining methods on this
|
||||||
|
/// trait (`is_absolute`, `parent`, `join`, *&c.*); but that would infect too many other APIs to be
|
||||||
|
/// desirable, so we live with it as it is—which is also acceptable, because the motivating example
|
||||||
|
/// use case is mostly using this as an optimisation over the real platform underneath.
|
||||||
|
pub trait Fs: std::fmt::Debug {
|
||||||
|
/// Returns `true` if the path exists on disk and is pointing at a directory.
|
||||||
|
fn is_dir(&self, path: &Path) -> bool;
|
||||||
|
/// Returns `true` if the path exists on disk and is pointing at a regular file.
|
||||||
|
fn is_file(&self, path: &Path) -> bool;
|
||||||
|
/// Read the entire contents of a file into a bytes vector.
|
||||||
|
fn read(&self, path: &Path) -> Result<Vec<u8>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use [`std::fs`] to read any files from disk.
|
||||||
|
///
|
||||||
|
/// This is the default file system implementation.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct StdFs;
|
||||||
|
|
||||||
|
impl Fs for StdFs {
|
||||||
|
#[inline]
|
||||||
|
fn is_file(&self, path: &Path) -> bool {
|
||||||
|
path.is_file()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_dir(&self, path: &Path) -> bool {
|
||||||
|
path.is_dir()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn read(&self, path: &Path) -> Result<Vec<u8>> {
|
||||||
|
std::fs::read(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A file system implementation that acts like it’s completely empty.
|
||||||
|
///
|
||||||
|
/// This may be useful for security as it denies all access to the file system (so `@import` is
|
||||||
|
/// prevented from leaking anything); you’ll need to use [`from_string`][crate::from_string] for
|
||||||
|
/// this to make any sense (since [`from_path`][crate::from_path] would fail to find a file).
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct NullFs;
|
||||||
|
|
||||||
|
impl Fs for NullFs {
|
||||||
|
#[inline]
|
||||||
|
fn is_file(&self, _path: &Path) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_dir(&self, _path: &Path) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn read(&self, _path: &Path) -> Result<Vec<u8>> {
|
||||||
|
Err(Error::new(
|
||||||
|
ErrorKind::NotFound,
|
||||||
|
"NullFs, there is no file system",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
60
src/lib.rs
60
src/lib.rs
@ -92,9 +92,10 @@ grass input.scss
|
|||||||
)]
|
)]
|
||||||
#![cfg_attr(feature = "nightly", feature(track_caller))]
|
#![cfg_attr(feature = "nightly", feature(track_caller))]
|
||||||
#![cfg_attr(feature = "profiling", inline(never))]
|
#![cfg_attr(feature = "profiling", inline(never))]
|
||||||
use std::{fs, path::Path};
|
|
||||||
|
|
||||||
#[cfg(feature = "wasm")]
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[cfg(feature = "wasm-exports")]
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
pub(crate) use beef::lean::Cow;
|
pub(crate) use beef::lean::Cow;
|
||||||
@ -102,6 +103,7 @@ pub(crate) use beef::lean::Cow;
|
|||||||
use codemap::CodeMap;
|
use codemap::CodeMap;
|
||||||
|
|
||||||
pub use crate::error::{SassError as Error, SassResult as Result};
|
pub use crate::error::{SassError as Error, SassResult as Result};
|
||||||
|
pub use crate::fs::{Fs, NullFs, StdFs};
|
||||||
pub(crate) use crate::token::Token;
|
pub(crate) use crate::token::Token;
|
||||||
use crate::{
|
use crate::{
|
||||||
builtin::modules::{ModuleConfig, Modules},
|
builtin::modules::{ModuleConfig, Modules},
|
||||||
@ -121,6 +123,7 @@ mod builtin;
|
|||||||
mod color;
|
mod color;
|
||||||
mod common;
|
mod common;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod fs;
|
||||||
mod interner;
|
mod interner;
|
||||||
mod lexer;
|
mod lexer;
|
||||||
mod output;
|
mod output;
|
||||||
@ -152,6 +155,7 @@ pub enum OutputStyle {
|
|||||||
/// more control.
|
/// more control.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Options<'a> {
|
pub struct Options<'a> {
|
||||||
|
fs: &'a dyn Fs,
|
||||||
style: OutputStyle,
|
style: OutputStyle,
|
||||||
load_paths: Vec<&'a Path>,
|
load_paths: Vec<&'a Path>,
|
||||||
allows_charset: bool,
|
allows_charset: bool,
|
||||||
@ -163,6 +167,7 @@ impl Default for Options<'_> {
|
|||||||
#[inline]
|
#[inline]
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
fs: &StdFs,
|
||||||
style: OutputStyle::Expanded,
|
style: OutputStyle::Expanded,
|
||||||
load_paths: Vec::new(),
|
load_paths: Vec::new(),
|
||||||
allows_charset: true,
|
allows_charset: true,
|
||||||
@ -173,6 +178,17 @@ impl Default for Options<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Options<'a> {
|
impl<'a> Options<'a> {
|
||||||
|
/// This option allows you to control the file system that Sass will see.
|
||||||
|
///
|
||||||
|
/// By default, it uses [`StdFs`], which is backed by [`std::fs`],
|
||||||
|
/// allowing direct, unfettered access to the local file system.
|
||||||
|
#[must_use]
|
||||||
|
#[inline]
|
||||||
|
pub fn fs(mut self, fs: &'a dyn Fs) -> Self {
|
||||||
|
self.fs = fs;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// `grass` currently offers 2 different output styles
|
/// `grass` currently offers 2 different output styles
|
||||||
///
|
///
|
||||||
/// - `OutputStyle::Expanded` writes each selector and declaration on its own line.
|
/// - `OutputStyle::Expanded` writes each selector and declaration on its own line.
|
||||||
@ -317,9 +333,8 @@ fn from_string_with_file_name(input: String, file_name: &str, options: &Options)
|
|||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "profiling", inline(never))]
|
#[cfg_attr(feature = "profiling", inline(never))]
|
||||||
#[cfg_attr(not(feature = "profiling"), inline)]
|
#[cfg_attr(not(feature = "profiling"), inline)]
|
||||||
#[cfg(not(feature = "wasm"))]
|
|
||||||
pub fn from_path(p: &str, options: &Options) -> Result<String> {
|
pub fn from_path(p: &str, options: &Options) -> Result<String> {
|
||||||
from_string_with_file_name(String::from_utf8(fs::read(p)?)?, p, options)
|
from_string_with_file_name(String::from_utf8(options.fs.read(Path::new(p))?)?, p, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compile CSS from a string
|
/// Compile CSS from a string
|
||||||
@ -333,41 +348,12 @@ pub fn from_path(p: &str, options: &Options) -> Result<String> {
|
|||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "profiling", inline(never))]
|
#[cfg_attr(feature = "profiling", inline(never))]
|
||||||
#[cfg_attr(not(feature = "profiling"), inline)]
|
#[cfg_attr(not(feature = "profiling"), inline)]
|
||||||
#[cfg(not(feature = "wasm"))]
|
|
||||||
pub fn from_string(input: String, options: &Options) -> Result<String> {
|
pub fn from_string(input: String, options: &Options) -> Result<String> {
|
||||||
from_string_with_file_name(input, "stdin", options)
|
from_string_with_file_name(input, "stdin", options)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "wasm")]
|
#[cfg(feature = "wasm-exports")]
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen(js_name = from_string)]
|
||||||
pub fn from_string(p: String) -> std::result::Result<String, JsValue> {
|
pub fn from_string_js(p: String) -> std::result::Result<String, JsValue> {
|
||||||
let mut map = CodeMap::new();
|
from_string(Options::default()).map_err(|e| e.to_string())
|
||||||
let file = map.add_file("stdin".into(), p);
|
|
||||||
let empty_span = file.span.subspan(0, 0);
|
|
||||||
|
|
||||||
let stmts = Parser {
|
|
||||||
toks: &mut Lexer::new_from_file(&file),
|
|
||||||
map: &mut map,
|
|
||||||
path: Path::new(""),
|
|
||||||
scopes: &mut Scopes::new(),
|
|
||||||
global_scope: &mut Scope::new(),
|
|
||||||
super_selectors: &mut NeverEmptyVec::new(Selector::new(empty_span)),
|
|
||||||
span_before: empty_span,
|
|
||||||
content: &mut Vec::new(),
|
|
||||||
flags: ContextFlags::empty(),
|
|
||||||
at_root: true,
|
|
||||||
at_root_has_selector: false,
|
|
||||||
extender: &mut Extender::new(empty_span),
|
|
||||||
content_scopes: &mut Scopes::new(),
|
|
||||||
options: &Options::default(),
|
|
||||||
modules: &mut Modules::default(),
|
|
||||||
module_config: &mut ModuleConfig::default(),
|
|
||||||
}
|
|
||||||
.parse()
|
|
||||||
.map_err(|e| raw_to_parse_error(&map, *e, true).to_string())?;
|
|
||||||
|
|
||||||
Ok(Css::from_stmts(stmts, false, true)
|
|
||||||
.map_err(|e| raw_to_parse_error(&map, *e, true).to_string())?
|
|
||||||
.pretty_print(&map, options.style)
|
|
||||||
.map_err(|e| raw_to_parse_error(&map, *e, true).to_string())?)
|
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ use std::{
|
|||||||
|
|
||||||
use clap::{arg_enum, App, AppSettings, Arg};
|
use clap::{arg_enum, App, AppSettings, Arg};
|
||||||
|
|
||||||
#[cfg(not(feature = "wasm"))]
|
|
||||||
use grass::{from_path, from_string, Options, OutputStyle};
|
use grass::{from_path, from_string, Options, OutputStyle};
|
||||||
|
|
||||||
// TODO remove this
|
// TODO remove this
|
||||||
@ -26,10 +25,6 @@ arg_enum! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "wasm")]
|
|
||||||
fn main() {}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "wasm"))]
|
|
||||||
#[cfg_attr(feature = "profiling", inline(never))]
|
#[cfg_attr(feature = "profiling", inline(never))]
|
||||||
fn main() -> std::io::Result<()> {
|
fn main() -> std::io::Result<()> {
|
||||||
let matches = App::new("grass")
|
let matches = App::new("grass")
|
||||||
|
@ -13,6 +13,48 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
impl<'a, 'b> Parser<'a, 'b> {
|
impl<'a, 'b> Parser<'a, 'b> {
|
||||||
|
fn subparser_with_in_control_flow_flag<'c>(&'c mut self) -> Parser<'c, 'b> {
|
||||||
|
Parser {
|
||||||
|
toks: self.toks,
|
||||||
|
map: self.map,
|
||||||
|
path: self.path,
|
||||||
|
scopes: self.scopes,
|
||||||
|
global_scope: self.global_scope,
|
||||||
|
super_selectors: self.super_selectors,
|
||||||
|
span_before: self.span_before,
|
||||||
|
content: self.content,
|
||||||
|
flags: self.flags | ContextFlags::IN_CONTROL_FLOW,
|
||||||
|
at_root: self.at_root,
|
||||||
|
at_root_has_selector: self.at_root_has_selector,
|
||||||
|
extender: self.extender,
|
||||||
|
content_scopes: self.content_scopes,
|
||||||
|
options: self.options,
|
||||||
|
modules: self.modules,
|
||||||
|
module_config: self.module_config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_toks<'d>(self, toks: &'a mut Lexer<'d>) -> Parser<'a, 'd> {
|
||||||
|
Parser {
|
||||||
|
toks,
|
||||||
|
map: self.map,
|
||||||
|
path: self.path,
|
||||||
|
scopes: self.scopes,
|
||||||
|
global_scope: self.global_scope,
|
||||||
|
super_selectors: self.super_selectors,
|
||||||
|
span_before: self.span_before,
|
||||||
|
content: self.content,
|
||||||
|
flags: self.flags,
|
||||||
|
at_root: self.at_root,
|
||||||
|
at_root_has_selector: self.at_root_has_selector,
|
||||||
|
extender: self.extender,
|
||||||
|
content_scopes: self.content_scopes,
|
||||||
|
options: self.options,
|
||||||
|
modules: self.modules,
|
||||||
|
module_config: self.module_config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn parse_if(&mut self) -> SassResult<Vec<Stmt>> {
|
pub(super) fn parse_if(&mut self) -> SassResult<Vec<Stmt>> {
|
||||||
self.whitespace_or_comment();
|
self.whitespace_or_comment();
|
||||||
|
|
||||||
@ -28,29 +70,9 @@ impl<'a, 'b> Parser<'a, 'b> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if init_cond.is_true() {
|
if init_cond.is_true() {
|
||||||
self.scopes.enter_new_scope();
|
|
||||||
found_true = true;
|
found_true = true;
|
||||||
|
self.scopes.enter_new_scope();
|
||||||
body = Parser {
|
body = self.subparser_with_in_control_flow_flag().parse_stmt()?;
|
||||||
toks: self.toks,
|
|
||||||
map: self.map,
|
|
||||||
path: self.path,
|
|
||||||
scopes: self.scopes,
|
|
||||||
global_scope: self.global_scope,
|
|
||||||
super_selectors: self.super_selectors,
|
|
||||||
span_before: self.span_before,
|
|
||||||
content: self.content,
|
|
||||||
flags: self.flags | ContextFlags::IN_CONTROL_FLOW,
|
|
||||||
at_root: self.at_root,
|
|
||||||
at_root_has_selector: self.at_root_has_selector,
|
|
||||||
extender: self.extender,
|
|
||||||
content_scopes: self.content_scopes,
|
|
||||||
options: self.options,
|
|
||||||
modules: self.modules,
|
|
||||||
module_config: self.module_config,
|
|
||||||
}
|
|
||||||
.parse_stmt()?;
|
|
||||||
|
|
||||||
self.scopes.exit_scope();
|
self.scopes.exit_scope();
|
||||||
} else {
|
} else {
|
||||||
self.throw_away_until_closing_curly_brace()?;
|
self.throw_away_until_closing_curly_brace()?;
|
||||||
@ -91,26 +113,7 @@ impl<'a, 'b> Parser<'a, 'b> {
|
|||||||
if cond {
|
if cond {
|
||||||
found_true = true;
|
found_true = true;
|
||||||
self.scopes.enter_new_scope();
|
self.scopes.enter_new_scope();
|
||||||
body = Parser {
|
body = self.subparser_with_in_control_flow_flag().parse_stmt()?;
|
||||||
toks: self.toks,
|
|
||||||
map: self.map,
|
|
||||||
path: self.path,
|
|
||||||
scopes: self.scopes,
|
|
||||||
global_scope: self.global_scope,
|
|
||||||
super_selectors: self.super_selectors,
|
|
||||||
span_before: self.span_before,
|
|
||||||
content: self.content,
|
|
||||||
flags: self.flags | ContextFlags::IN_CONTROL_FLOW,
|
|
||||||
at_root: self.at_root,
|
|
||||||
at_root_has_selector: self.at_root_has_selector,
|
|
||||||
extender: self.extender,
|
|
||||||
content_scopes: self.content_scopes,
|
|
||||||
options: self.options,
|
|
||||||
modules: self.modules,
|
|
||||||
module_config: self.module_config,
|
|
||||||
}
|
|
||||||
.parse_stmt()?;
|
|
||||||
|
|
||||||
self.scopes.exit_scope();
|
self.scopes.exit_scope();
|
||||||
} else {
|
} else {
|
||||||
self.throw_away_until_closing_curly_brace()?;
|
self.throw_away_until_closing_curly_brace()?;
|
||||||
@ -125,25 +128,7 @@ impl<'a, 'b> Parser<'a, 'b> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.scopes.enter_new_scope();
|
self.scopes.enter_new_scope();
|
||||||
let tmp = Parser {
|
let tmp = self.subparser_with_in_control_flow_flag().parse_stmt();
|
||||||
toks: self.toks,
|
|
||||||
map: self.map,
|
|
||||||
path: self.path,
|
|
||||||
scopes: self.scopes,
|
|
||||||
global_scope: self.global_scope,
|
|
||||||
super_selectors: self.super_selectors,
|
|
||||||
span_before: self.span_before,
|
|
||||||
content: self.content,
|
|
||||||
flags: self.flags | ContextFlags::IN_CONTROL_FLOW,
|
|
||||||
at_root: self.at_root,
|
|
||||||
at_root_has_selector: self.at_root_has_selector,
|
|
||||||
extender: self.extender,
|
|
||||||
content_scopes: self.content_scopes,
|
|
||||||
options: self.options,
|
|
||||||
modules: self.modules,
|
|
||||||
module_config: self.module_config,
|
|
||||||
}
|
|
||||||
.parse_stmt();
|
|
||||||
self.scopes.exit_scope();
|
self.scopes.exit_scope();
|
||||||
return tmp;
|
return tmp;
|
||||||
}
|
}
|
||||||
@ -272,51 +257,15 @@ impl<'a, 'b> Parser<'a, 'b> {
|
|||||||
var.node,
|
var.node,
|
||||||
Value::Dimension(Some(Number::from(i)), Unit::None, true),
|
Value::Dimension(Some(Number::from(i)), Unit::None, true),
|
||||||
);
|
);
|
||||||
if self.flags.in_function() {
|
let mut these_stmts = self.subparser_with_in_control_flow_flag()
|
||||||
let these_stmts = Parser {
|
.with_toks(&mut Lexer::new_ref(&body))
|
||||||
toks: &mut Lexer::new_ref(&body),
|
|
||||||
map: self.map,
|
|
||||||
path: self.path,
|
|
||||||
scopes: self.scopes,
|
|
||||||
global_scope: self.global_scope,
|
|
||||||
super_selectors: self.super_selectors,
|
|
||||||
span_before: self.span_before,
|
|
||||||
content: self.content,
|
|
||||||
flags: self.flags | ContextFlags::IN_CONTROL_FLOW,
|
|
||||||
at_root: self.at_root,
|
|
||||||
at_root_has_selector: self.at_root_has_selector,
|
|
||||||
extender: self.extender,
|
|
||||||
content_scopes: self.content_scopes,
|
|
||||||
options: self.options,
|
|
||||||
modules: self.modules,
|
|
||||||
module_config: self.module_config,
|
|
||||||
}
|
|
||||||
.parse_stmt()?;
|
.parse_stmt()?;
|
||||||
|
if self.flags.in_function() {
|
||||||
if !these_stmts.is_empty() {
|
if !these_stmts.is_empty() {
|
||||||
return Ok(these_stmts);
|
return Ok(these_stmts);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
stmts.append(
|
stmts.append(&mut these_stmts);
|
||||||
&mut Parser {
|
|
||||||
toks: &mut Lexer::new_ref(&body),
|
|
||||||
map: self.map,
|
|
||||||
path: self.path,
|
|
||||||
scopes: self.scopes,
|
|
||||||
global_scope: self.global_scope,
|
|
||||||
super_selectors: self.super_selectors,
|
|
||||||
span_before: self.span_before,
|
|
||||||
content: self.content,
|
|
||||||
flags: self.flags | ContextFlags::IN_CONTROL_FLOW,
|
|
||||||
at_root: self.at_root,
|
|
||||||
at_root_has_selector: self.at_root_has_selector,
|
|
||||||
extender: self.extender,
|
|
||||||
content_scopes: self.content_scopes,
|
|
||||||
options: self.options,
|
|
||||||
modules: self.modules,
|
|
||||||
module_config: self.module_config,
|
|
||||||
}
|
|
||||||
.parse_stmt()?,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -349,51 +298,15 @@ impl<'a, 'b> Parser<'a, 'b> {
|
|||||||
let mut val = self.parse_value_from_vec(&cond, true)?;
|
let mut val = self.parse_value_from_vec(&cond, true)?;
|
||||||
self.scopes.enter_new_scope();
|
self.scopes.enter_new_scope();
|
||||||
while val.node.is_true() {
|
while val.node.is_true() {
|
||||||
if self.flags.in_function() {
|
let mut these_stmts = self.subparser_with_in_control_flow_flag()
|
||||||
let these_stmts = Parser {
|
.with_toks(&mut Lexer::new_ref(&body))
|
||||||
toks: &mut Lexer::new_ref(&body),
|
|
||||||
map: self.map,
|
|
||||||
path: self.path,
|
|
||||||
scopes: self.scopes,
|
|
||||||
global_scope: self.global_scope,
|
|
||||||
super_selectors: self.super_selectors,
|
|
||||||
span_before: self.span_before,
|
|
||||||
content: self.content,
|
|
||||||
flags: self.flags | ContextFlags::IN_CONTROL_FLOW,
|
|
||||||
at_root: self.at_root,
|
|
||||||
at_root_has_selector: self.at_root_has_selector,
|
|
||||||
extender: self.extender,
|
|
||||||
content_scopes: self.content_scopes,
|
|
||||||
options: self.options,
|
|
||||||
modules: self.modules,
|
|
||||||
module_config: self.module_config,
|
|
||||||
}
|
|
||||||
.parse_stmt()?;
|
.parse_stmt()?;
|
||||||
|
if self.flags.in_function() {
|
||||||
if !these_stmts.is_empty() {
|
if !these_stmts.is_empty() {
|
||||||
return Ok(these_stmts);
|
return Ok(these_stmts);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
stmts.append(
|
stmts.append(&mut these_stmts);
|
||||||
&mut Parser {
|
|
||||||
toks: &mut Lexer::new_ref(&body),
|
|
||||||
map: self.map,
|
|
||||||
path: self.path,
|
|
||||||
scopes: self.scopes,
|
|
||||||
global_scope: self.global_scope,
|
|
||||||
super_selectors: self.super_selectors,
|
|
||||||
span_before: self.span_before,
|
|
||||||
content: self.content,
|
|
||||||
flags: self.flags | ContextFlags::IN_CONTROL_FLOW,
|
|
||||||
at_root: self.at_root,
|
|
||||||
at_root_has_selector: self.at_root_has_selector,
|
|
||||||
extender: self.extender,
|
|
||||||
content_scopes: self.content_scopes,
|
|
||||||
options: self.options,
|
|
||||||
modules: self.modules,
|
|
||||||
module_config: self.module_config,
|
|
||||||
}
|
|
||||||
.parse_stmt()?,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
val = self.parse_value_from_vec(&cond, true)?;
|
val = self.parse_value_from_vec(&cond, true)?;
|
||||||
}
|
}
|
||||||
@ -461,51 +374,15 @@ impl<'a, 'b> Parser<'a, 'b> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.flags.in_function() {
|
let mut these_stmts = self.subparser_with_in_control_flow_flag()
|
||||||
let these_stmts = Parser {
|
.with_toks(&mut Lexer::new_ref(&body))
|
||||||
toks: &mut Lexer::new_ref(&body),
|
|
||||||
map: self.map,
|
|
||||||
path: self.path,
|
|
||||||
scopes: self.scopes,
|
|
||||||
global_scope: self.global_scope,
|
|
||||||
super_selectors: self.super_selectors,
|
|
||||||
span_before: self.span_before,
|
|
||||||
content: self.content,
|
|
||||||
flags: self.flags | ContextFlags::IN_CONTROL_FLOW,
|
|
||||||
at_root: self.at_root,
|
|
||||||
at_root_has_selector: self.at_root_has_selector,
|
|
||||||
extender: self.extender,
|
|
||||||
content_scopes: self.content_scopes,
|
|
||||||
options: self.options,
|
|
||||||
modules: self.modules,
|
|
||||||
module_config: self.module_config,
|
|
||||||
}
|
|
||||||
.parse_stmt()?;
|
.parse_stmt()?;
|
||||||
|
if self.flags.in_function() {
|
||||||
if !these_stmts.is_empty() {
|
if !these_stmts.is_empty() {
|
||||||
return Ok(these_stmts);
|
return Ok(these_stmts);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
stmts.append(
|
stmts.append(&mut these_stmts);
|
||||||
&mut Parser {
|
|
||||||
toks: &mut Lexer::new_ref(&body),
|
|
||||||
map: self.map,
|
|
||||||
path: self.path,
|
|
||||||
scopes: self.scopes,
|
|
||||||
global_scope: self.global_scope,
|
|
||||||
super_selectors: self.super_selectors,
|
|
||||||
span_before: self.span_before,
|
|
||||||
content: self.content,
|
|
||||||
flags: self.flags | ContextFlags::IN_CONTROL_FLOW,
|
|
||||||
at_root: self.at_root,
|
|
||||||
at_root_has_selector: self.at_root_has_selector,
|
|
||||||
extender: self.extender,
|
|
||||||
content_scopes: self.content_scopes,
|
|
||||||
options: self.options,
|
|
||||||
modules: self.modules,
|
|
||||||
module_config: self.module_config,
|
|
||||||
}
|
|
||||||
.parse_stmt()?,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::{ffi::OsStr, fs, path::Path, path::PathBuf};
|
use std::{ffi::OsStr, path::Path, path::PathBuf};
|
||||||
|
|
||||||
use codemap::{Span, Spanned};
|
use codemap::{Span, Spanned};
|
||||||
|
|
||||||
@ -45,49 +45,41 @@ impl<'a, 'b> Parser<'a, 'b> {
|
|||||||
|
|
||||||
let name = path_buf.file_name().unwrap_or_else(|| OsStr::new(".."));
|
let name = path_buf.file_name().unwrap_or_else(|| OsStr::new(".."));
|
||||||
|
|
||||||
let paths = [
|
macro_rules! try_path {
|
||||||
path_buf.with_file_name(name).with_extension("scss"),
|
($name:expr) => {
|
||||||
path_buf
|
let name = $name;
|
||||||
.with_file_name(format!("_{}", name.to_str().unwrap()))
|
if self.options.fs.is_file(&name) {
|
||||||
.with_extension("scss"),
|
|
||||||
path_buf.clone(),
|
|
||||||
path_buf.join("index.scss"),
|
|
||||||
path_buf.join("_index.scss"),
|
|
||||||
];
|
|
||||||
|
|
||||||
for name in &paths {
|
|
||||||
if name.is_file() {
|
|
||||||
return Some(name.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for path in &self.options.load_paths {
|
|
||||||
let paths: Vec<PathBuf> = if path.is_dir() {
|
|
||||||
vec![
|
|
||||||
path.join(&path_buf)
|
|
||||||
.with_file_name(name)
|
|
||||||
.with_extension("scss"),
|
|
||||||
path.join(&path_buf)
|
|
||||||
.with_file_name(format!("_{}", name.to_str().unwrap()))
|
|
||||||
.with_extension("scss"),
|
|
||||||
path.join(&path_buf).join("index.scss"),
|
|
||||||
path.join(&path_buf).join("_index.scss"),
|
|
||||||
]
|
|
||||||
} else {
|
|
||||||
vec![
|
|
||||||
path.to_path_buf(),
|
|
||||||
path.with_file_name(name).with_extension("scss"),
|
|
||||||
path.with_file_name(format!("_{}", name.to_str().unwrap()))
|
|
||||||
.with_extension("scss"),
|
|
||||||
path.join("index.scss"),
|
|
||||||
path.join("_index.scss"),
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
for name in paths {
|
|
||||||
if name.is_file() {
|
|
||||||
return Some(name);
|
return Some(name);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try_path!(path_buf.with_file_name(name).with_extension("scss"));
|
||||||
|
try_path!(path_buf
|
||||||
|
.with_file_name(format!("_{}", name.to_str().unwrap()))
|
||||||
|
.with_extension("scss"));
|
||||||
|
try_path!(path_buf.clone());
|
||||||
|
try_path!(path_buf.join("index.scss"));
|
||||||
|
try_path!(path_buf.join("_index.scss"));
|
||||||
|
|
||||||
|
for path in &self.options.load_paths {
|
||||||
|
if self.options.fs.is_dir(path) {
|
||||||
|
try_path!(path.join(&path_buf)
|
||||||
|
.with_file_name(name)
|
||||||
|
.with_extension("scss"));
|
||||||
|
try_path!(path.join(&path_buf)
|
||||||
|
.with_file_name(format!("_{}", name.to_str().unwrap()))
|
||||||
|
.with_extension("scss"));
|
||||||
|
try_path!(path.join(&path_buf).join("index.scss"));
|
||||||
|
try_path!(path.join(&path_buf).join("_index.scss"));
|
||||||
|
} else {
|
||||||
|
try_path!(path.to_path_buf());
|
||||||
|
try_path!(path.with_file_name(name).with_extension("scss"));
|
||||||
|
try_path!(path
|
||||||
|
.with_file_name(format!("_{}", name.to_str().unwrap()))
|
||||||
|
.with_extension("scss"));
|
||||||
|
try_path!(path.join("index.scss"));
|
||||||
|
try_path!(path.join("_index.scss"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +96,7 @@ impl<'a, 'b> Parser<'a, 'b> {
|
|||||||
if let Some(name) = self.find_import(path) {
|
if let Some(name) = self.find_import(path) {
|
||||||
let file = self.map.add_file(
|
let file = self.map.add_file(
|
||||||
name.to_string_lossy().into(),
|
name.to_string_lossy().into(),
|
||||||
String::from_utf8(fs::read(&name)?)?,
|
String::from_utf8(self.options.fs.read(&name)?)?,
|
||||||
);
|
);
|
||||||
return Parser {
|
return Parser {
|
||||||
toks: &mut Lexer::new_from_file(&file),
|
toks: &mut Lexer::new_from_file(&file),
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::{convert::TryFrom, fs};
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
use codemap::Spanned;
|
use codemap::Spanned;
|
||||||
|
|
||||||
@ -120,9 +120,10 @@ impl<'a, 'b> Parser<'a, 'b> {
|
|||||||
if let Some(import) = self.find_import(name.as_ref()) {
|
if let Some(import) = self.find_import(name.as_ref()) {
|
||||||
let mut global_scope = Scope::new();
|
let mut global_scope = Scope::new();
|
||||||
|
|
||||||
let file = self
|
let file = self.map.add_file(
|
||||||
.map
|
name.to_owned(),
|
||||||
.add_file(name.to_owned(), String::from_utf8(fs::read(&import)?)?);
|
String::from_utf8(self.options.fs.read(&import)?)?,
|
||||||
|
);
|
||||||
|
|
||||||
let mut modules = Modules::default();
|
let mut modules = Modules::default();
|
||||||
|
|
||||||
|
@ -569,8 +569,7 @@ impl Pseudo {
|
|||||||
.any(|pseudo2| self.selector == pseudo2.selector),
|
.any(|pseudo2| self.selector == pseudo2.selector),
|
||||||
"nth-child" | "nth-last-child" => compound.components.iter().any(|pseudo2| {
|
"nth-child" | "nth-last-child" => compound.components.iter().any(|pseudo2| {
|
||||||
if let SimpleSelector::Pseudo(
|
if let SimpleSelector::Pseudo(
|
||||||
pseudo
|
pseudo @ Pseudo {
|
||||||
@ Pseudo {
|
|
||||||
selector: Some(..), ..
|
selector: Some(..), ..
|
||||||
},
|
},
|
||||||
) = pseudo2
|
) = pseudo2
|
||||||
|
@ -3,6 +3,25 @@ use std::io::Write;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod macros;
|
mod macros;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn null_fs_cannot_import() {
|
||||||
|
let input = "@import \"foo\";";
|
||||||
|
tempfile!("foo.scss", "");
|
||||||
|
match grass::from_string(
|
||||||
|
input.to_string(),
|
||||||
|
&grass::Options::default().fs(&grass::NullFs),
|
||||||
|
) {
|
||||||
|
Err(e)
|
||||||
|
if e.to_string()
|
||||||
|
.starts_with("Error: Can't find stylesheet to import.\n") =>
|
||||||
|
{
|
||||||
|
()
|
||||||
|
}
|
||||||
|
Ok(..) => panic!("did not fail"),
|
||||||
|
Err(e) => panic!("failed in the wrong way: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn imports_variable() {
|
fn imports_variable() {
|
||||||
let input = "@import \"imports_variable\";\na {\n color: $a;\n}";
|
let input = "@import \"imports_variable\";\na {\n color: $a;\n}";
|
||||||
|
Loading…
x
Reference in New Issue
Block a user