Handle basic imports

This commit is contained in:
ConnorSkees 2020-01-18 15:47:51 -05:00
parent 9be0826d01
commit 0485256115
3 changed files with 102 additions and 15 deletions

View File

@ -1,9 +1,24 @@
use crate::StyleSheetParser;
use crate::common::Scope;
use crate::{Stmt, StyleSheet};
use std::path::Path;
pub struct Importer {}
impl Importer {
pub fn import(path: &str) {}
fn find_files() {}
pub fn import(name: String) -> (Vec<Stmt>, Scope) {
let mut rules: Vec<Stmt> = Vec::new();
let mut scope = Scope::new();
for name in &[
&name,
&format!("{}.scss", name),
&format!("_{}.scss", name),
&format!("{}/index.scss", name),
&format!("{}/_index.scss", name),
&format!("{}.css", name),
] {
let p = Path::new(&name);
if p.exists() && p.is_file() {
let (rules2, scope2) = StyleSheet::export_from_path(*name).unwrap();
rules.extend(rules2);
scope.merge(scope2);
}
}
(rules, scope)
}

View File

@ -37,6 +37,7 @@ use crate::css::Css;
use crate::error::SassError;
use crate::format::PrettyPrinter;
use crate::function::{eat_call_args, eat_func_args, CallArgs, FuncArgs};
use crate::imports::import;
use crate::lexer::Lexer;
use crate::mixin::Mixin;
use crate::selector::{Attribute, Selector};
@ -88,6 +89,7 @@ impl IsWhitespace for &Token {
pub enum TokenKind {
Ident(String),
Symbol(Symbol),
String(String),
AtRule(AtRule),
Keyword(Keyword),
Number(String),
@ -105,6 +107,7 @@ impl Display for TokenKind {
match self {
TokenKind::Ident(s) | TokenKind::Number(s) => write!(f, "{}", s),
TokenKind::Symbol(s) => write!(f, "{}", s),
TokenKind::String(s) => write!(f, "\"{}\"", s),
TokenKind::AtRule(s) => write!(f, "{}", s),
TokenKind::Op(s) => write!(f, "{}", s),
TokenKind::Unit(s) => write!(f, "{}", s),
@ -285,6 +288,38 @@ impl<'a> StyleSheetParser<'a> {
self.lexer.next();
rules.push(Stmt::MultilineComment(comment));
}
TokenKind::AtRule(AtRule::Import) => {
self.lexer.next();
devour_whitespace(&mut self.lexer);
let mut file_name = String::new();
match self.lexer.next().unwrap().kind {
TokenKind::Symbol(Symbol::DoubleQuote) => {
while let Some(tok) = self.lexer.next() {
if tok.kind == TokenKind::Symbol(Symbol::DoubleQuote) {
break;
}
file_name.push_str(&tok.kind.to_string());
}
}
TokenKind::Symbol(Symbol::SingleQuote) => {
while let Some(tok) = self.lexer.next() {
if tok.kind == TokenKind::Symbol(Symbol::SingleQuote) {
break;
}
file_name.push_str(&tok.kind.to_string());
}
}
_ => todo!("expected ' or \" after @import")
}
let Token { kind, pos } = self.lexer.next().unwrap();
if kind != TokenKind::Symbol(Symbol::SemiColon) {
self.error(pos, "expected `;` after @import declaration");
}
let (new_rules, new_scope) = import(file_name);
rules.extend(new_rules);
self.global_scope.merge(new_scope);
}
TokenKind::AtRule(AtRule::Mixin) => {
let (name, mixin) =
parse_mixin(&mut self.lexer, self.global_scope.clone()).unwrap();
@ -694,7 +729,7 @@ macro_rules! test {
}
#[cfg(test)]
mod css_variables {
mod test_variables {
use super::StyleSheet;
test!(
basic_variable,
@ -754,7 +789,7 @@ mod css_variables {
}
#[cfg(test)]
mod css_selectors {
mod test_selectors {
use super::StyleSheet;
test!(
selector_nesting_el_mul_el,
@ -918,7 +953,7 @@ mod css_selectors {
}
#[cfg(test)]
mod css_units {
mod test_units {
use super::StyleSheet;
test!(unit_none, "a {\n height: 1;\n}\n");
test!(unit_not_attached, "a {\n height: 1 px;\n}\n");
@ -929,7 +964,7 @@ mod css_units {
}
#[cfg(test)]
mod css_comments {
mod test_comments {
use super::StyleSheet;
test!(
removes_inner_comments,
@ -969,7 +1004,7 @@ mod css_comments {
}
#[cfg(test)]
mod css_styles {
mod test_styles {
use super::StyleSheet;
test!(basic_style, "a {\n color: red;\n}\n");
test!(two_styles, "a {\n color: red;\n color: blue;\n}\n");
@ -1066,7 +1101,7 @@ mod css_styles {
}
#[cfg(test)]
mod css_misc {
mod test_misc {
use super::*;
test!(
combines_hyphens,
@ -1092,7 +1127,7 @@ mod css_misc {
}
#[cfg(test)]
mod css_mixins {
mod test_mixins {
use super::*;
test!(
basic_mixin,
@ -1165,3 +1200,38 @@ mod css_mixins {
"d {\n color: red;\n}\n"
);
}
#[cfg(test)]
mod test_imports {
use super::*;
use std::io::Write;
use tempfile::Builder;
macro_rules! test_import {
($func:ident, $input:literal => $output:literal | $( $name:literal($content:literal) ),*) => {
#[test]
fn $func() {
$(
write!(Builder::new().prefix($name).tempfile().unwrap(), $content).unwrap();
)*
let mut buf = Vec::new();
StyleSheet::new($input)
.expect(concat!("failed to parse on ", $input))
.print_as_css(&mut buf)
.expect(concat!("failed to pretty print on ", $input));
assert_eq!(
String::from($output),
String::from_utf8(buf).expect("produced invalid utf8")
);
}
}
}
// redundant test to ensure that the import tests are working
test_import!(basic, "a {\n color: red;\n}\n" => "a {\n color: red;\n}\n" | );
test_import!(imports_variable, "@import \"foo\";\na {\n color: $a;\n}" => "a {\n color: red;\n}\n" | "foo"("$a: red;"));
test_import!(single_quotes_import, "@import 'foo';\na {\n color: $a;\n}" => "a {\n color: red;\n}\n" | "foo"("$a: red;"));
test_import!(finds_name_scss, "@import \"foo\";\na {\n color: $a;\n}" => "a {\n color: red;\n}\n" | "foo.scss"("$a: red;"));
test_import!(finds_underscore_name_scss, "@import \"foo\";\na {\n color: $a;\n}" => "a {\n color: red;\n}\n" | "_foo.scss"("$a: red;"));
}

View File

@ -242,7 +242,9 @@ impl<'a> SelectorParser<'a> {
}
if let Some(Token { kind, .. }) = tokens.next() {
match &kind {
TokenKind::Ident(ident) => self.selectors.push(SelectorKind::Element(ident.clone())),
TokenKind::Ident(ident) => {
self.selectors.push(SelectorKind::Element(ident.clone()))
}
TokenKind::Unit(u) => self.selectors.push(SelectorKind::Element(u.to_string())),
TokenKind::Symbol(Symbol::Period) => self.selectors.push(SelectorKind::Class),
TokenKind::Symbol(Symbol::Hash) => self.selectors.push(SelectorKind::Id),