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
|
||||
|
||||
- bugfix: properly emit the number `0` in compressed mode (#53)
|
||||
|
@ -74,8 +74,8 @@ commandline = ["clap"]
|
||||
nightly = []
|
||||
# Option (enabled by default): enable the builtin functions `random([$limit])` and `unique-id()`
|
||||
random = ["rand"]
|
||||
# Option: compile to web assembly
|
||||
wasm = ["wasm-bindgen"]
|
||||
# Option: expose JavaScript-friendly WebAssembly exports
|
||||
wasm-exports = ["wasm-bindgen"]
|
||||
# Option: enable features that assist in profiling (e.g. inline(never))
|
||||
profiling = []
|
||||
# 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 = "profiling", inline(never))]
|
||||
use std::{fs, path::Path};
|
||||
|
||||
#[cfg(feature = "wasm")]
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(feature = "wasm-exports")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
pub(crate) use beef::lean::Cow;
|
||||
@ -102,6 +103,7 @@ pub(crate) use beef::lean::Cow;
|
||||
use codemap::CodeMap;
|
||||
|
||||
pub use crate::error::{SassError as Error, SassResult as Result};
|
||||
pub use crate::fs::{Fs, NullFs, StdFs};
|
||||
pub(crate) use crate::token::Token;
|
||||
use crate::{
|
||||
builtin::modules::{ModuleConfig, Modules},
|
||||
@ -121,6 +123,7 @@ mod builtin;
|
||||
mod color;
|
||||
mod common;
|
||||
mod error;
|
||||
mod fs;
|
||||
mod interner;
|
||||
mod lexer;
|
||||
mod output;
|
||||
@ -152,6 +155,7 @@ pub enum OutputStyle {
|
||||
/// more control.
|
||||
#[derive(Debug)]
|
||||
pub struct Options<'a> {
|
||||
fs: &'a dyn Fs,
|
||||
style: OutputStyle,
|
||||
load_paths: Vec<&'a Path>,
|
||||
allows_charset: bool,
|
||||
@ -163,6 +167,7 @@ impl Default for Options<'_> {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
fs: &StdFs,
|
||||
style: OutputStyle::Expanded,
|
||||
load_paths: Vec::new(),
|
||||
allows_charset: true,
|
||||
@ -173,6 +178,17 @@ impl Default for Options<'_> {
|
||||
}
|
||||
|
||||
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
|
||||
///
|
||||
/// - `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(not(feature = "profiling"), inline)]
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
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
|
||||
@ -333,41 +348,12 @@ pub fn from_path(p: &str, options: &Options) -> Result<String> {
|
||||
/// ```
|
||||
#[cfg_attr(feature = "profiling", inline(never))]
|
||||
#[cfg_attr(not(feature = "profiling"), inline)]
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
pub fn from_string(input: String, options: &Options) -> Result<String> {
|
||||
from_string_with_file_name(input, "stdin", options)
|
||||
}
|
||||
|
||||
#[cfg(feature = "wasm")]
|
||||
#[wasm_bindgen]
|
||||
pub fn from_string(p: String) -> std::result::Result<String, JsValue> {
|
||||
let mut map = CodeMap::new();
|
||||
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())?)
|
||||
#[cfg(feature = "wasm-exports")]
|
||||
#[wasm_bindgen(js_name = from_string)]
|
||||
pub fn from_string_js(p: String) -> std::result::Result<String, JsValue> {
|
||||
from_string(Options::default()).map_err(|e| e.to_string())
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ use std::{
|
||||
|
||||
use clap::{arg_enum, App, AppSettings, Arg};
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
use grass::{from_path, from_string, Options, OutputStyle};
|
||||
|
||||
// TODO remove this
|
||||
@ -26,10 +25,6 @@ arg_enum! {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "wasm")]
|
||||
fn main() {}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[cfg_attr(feature = "profiling", inline(never))]
|
||||
fn main() -> std::io::Result<()> {
|
||||
let matches = App::new("grass")
|
||||
|
@ -13,6 +13,48 @@ use crate::{
|
||||
};
|
||||
|
||||
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>> {
|
||||
self.whitespace_or_comment();
|
||||
|
||||
@ -28,29 +70,9 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||
}
|
||||
|
||||
if init_cond.is_true() {
|
||||
self.scopes.enter_new_scope();
|
||||
found_true = true;
|
||||
|
||||
body = 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,
|
||||
}
|
||||
.parse_stmt()?;
|
||||
|
||||
self.scopes.enter_new_scope();
|
||||
body = self.subparser_with_in_control_flow_flag().parse_stmt()?;
|
||||
self.scopes.exit_scope();
|
||||
} else {
|
||||
self.throw_away_until_closing_curly_brace()?;
|
||||
@ -91,26 +113,7 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||
if cond {
|
||||
found_true = true;
|
||||
self.scopes.enter_new_scope();
|
||||
body = 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,
|
||||
}
|
||||
.parse_stmt()?;
|
||||
|
||||
body = self.subparser_with_in_control_flow_flag().parse_stmt()?;
|
||||
self.scopes.exit_scope();
|
||||
} else {
|
||||
self.throw_away_until_closing_curly_brace()?;
|
||||
@ -125,25 +128,7 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||
}
|
||||
|
||||
self.scopes.enter_new_scope();
|
||||
let tmp = 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,
|
||||
}
|
||||
.parse_stmt();
|
||||
let tmp = self.subparser_with_in_control_flow_flag().parse_stmt();
|
||||
self.scopes.exit_scope();
|
||||
return tmp;
|
||||
}
|
||||
@ -272,51 +257,15 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||
var.node,
|
||||
Value::Dimension(Some(Number::from(i)), Unit::None, true),
|
||||
);
|
||||
if self.flags.in_function() {
|
||||
let these_stmts = 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,
|
||||
}
|
||||
let mut these_stmts = self.subparser_with_in_control_flow_flag()
|
||||
.with_toks(&mut Lexer::new_ref(&body))
|
||||
.parse_stmt()?;
|
||||
if self.flags.in_function() {
|
||||
if !these_stmts.is_empty() {
|
||||
return Ok(these_stmts);
|
||||
}
|
||||
} else {
|
||||
stmts.append(
|
||||
&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()?,
|
||||
);
|
||||
stmts.append(&mut these_stmts);
|
||||
}
|
||||
}
|
||||
|
||||
@ -349,51 +298,15 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||
let mut val = self.parse_value_from_vec(&cond, true)?;
|
||||
self.scopes.enter_new_scope();
|
||||
while val.node.is_true() {
|
||||
if self.flags.in_function() {
|
||||
let these_stmts = 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,
|
||||
}
|
||||
let mut these_stmts = self.subparser_with_in_control_flow_flag()
|
||||
.with_toks(&mut Lexer::new_ref(&body))
|
||||
.parse_stmt()?;
|
||||
if self.flags.in_function() {
|
||||
if !these_stmts.is_empty() {
|
||||
return Ok(these_stmts);
|
||||
}
|
||||
} else {
|
||||
stmts.append(
|
||||
&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()?,
|
||||
);
|
||||
stmts.append(&mut these_stmts);
|
||||
}
|
||||
val = self.parse_value_from_vec(&cond, true)?;
|
||||
}
|
||||
@ -461,51 +374,15 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||
}
|
||||
}
|
||||
|
||||
if self.flags.in_function() {
|
||||
let these_stmts = 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,
|
||||
}
|
||||
let mut these_stmts = self.subparser_with_in_control_flow_flag()
|
||||
.with_toks(&mut Lexer::new_ref(&body))
|
||||
.parse_stmt()?;
|
||||
if self.flags.in_function() {
|
||||
if !these_stmts.is_empty() {
|
||||
return Ok(these_stmts);
|
||||
}
|
||||
} else {
|
||||
stmts.append(
|
||||
&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()?,
|
||||
);
|
||||
stmts.append(&mut these_stmts);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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};
|
||||
|
||||
@ -45,49 +45,41 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||
|
||||
let name = path_buf.file_name().unwrap_or_else(|| OsStr::new(".."));
|
||||
|
||||
let paths = [
|
||||
path_buf.with_file_name(name).with_extension("scss"),
|
||||
path_buf
|
||||
.with_file_name(format!("_{}", name.to_str().unwrap()))
|
||||
.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() {
|
||||
macro_rules! try_path {
|
||||
($name:expr) => {
|
||||
let name = $name;
|
||||
if self.options.fs.is_file(&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) {
|
||||
let file = self.map.add_file(
|
||||
name.to_string_lossy().into(),
|
||||
String::from_utf8(fs::read(&name)?)?,
|
||||
String::from_utf8(self.options.fs.read(&name)?)?,
|
||||
);
|
||||
return Parser {
|
||||
toks: &mut Lexer::new_from_file(&file),
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::{convert::TryFrom, fs};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use codemap::Spanned;
|
||||
|
||||
@ -120,9 +120,10 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||
if let Some(import) = self.find_import(name.as_ref()) {
|
||||
let mut global_scope = Scope::new();
|
||||
|
||||
let file = self
|
||||
.map
|
||||
.add_file(name.to_owned(), String::from_utf8(fs::read(&import)?)?);
|
||||
let file = self.map.add_file(
|
||||
name.to_owned(),
|
||||
String::from_utf8(self.options.fs.read(&import)?)?,
|
||||
);
|
||||
|
||||
let mut modules = Modules::default();
|
||||
|
||||
|
@ -569,8 +569,7 @@ impl Pseudo {
|
||||
.any(|pseudo2| self.selector == pseudo2.selector),
|
||||
"nth-child" | "nth-last-child" => compound.components.iter().any(|pseudo2| {
|
||||
if let SimpleSelector::Pseudo(
|
||||
pseudo
|
||||
@ Pseudo {
|
||||
pseudo @ Pseudo {
|
||||
selector: Some(..), ..
|
||||
},
|
||||
) = pseudo2
|
||||
|
@ -3,6 +3,25 @@ use std::io::Write;
|
||||
#[macro_use]
|
||||
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]
|
||||
fn imports_variable() {
|
||||
let input = "@import \"imports_variable\";\na {\n color: $a;\n}";
|
||||
|
Loading…
x
Reference in New Issue
Block a user