From 0485256115eec5d514b5751086f9e7d94cbed7f8 Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Sat, 18 Jan 2020 15:47:51 -0500 Subject: [PATCH] Handle basic imports --- src/imports.rs | 29 ++++++++++++----- src/main.rs | 84 ++++++++++++++++++++++++++++++++++++++++++++----- src/selector.rs | 4 ++- 3 files changed, 102 insertions(+), 15 deletions(-) diff --git a/src/imports.rs b/src/imports.rs index f112f43..a0575bb 100644 --- a/src/imports.rs +++ b/src/imports.rs @@ -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, Scope) { + let mut rules: Vec = 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) } diff --git a/src/main.rs b/src/main.rs index e1319aa..9e3c400 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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;")); +} diff --git a/src/selector.rs b/src/selector.rs index 601193c..aef4678 100644 --- a/src/selector.rs +++ b/src/selector.rs @@ -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),