1 use std::fs::create_dir_all;
2 use std::path::{Path, PathBuf};
3 
4 use glob::glob;
5 use sass_rs::{compile_file, Options, OutputStyle};
6 
7 use errors::{bail, Result};
8 use utils::fs::{create_file, ensure_directory_exists};
9 
compile_sass(base_path: &Path, output_path: &Path) -> Result<()>10 pub fn compile_sass(base_path: &Path, output_path: &Path) -> Result<()> {
11     ensure_directory_exists(output_path)?;
12 
13     let sass_path = {
14         let mut sass_path = PathBuf::from(base_path);
15         sass_path.push("sass");
16         sass_path
17     };
18 
19     let mut options = Options { output_style: OutputStyle::Compressed, ..Default::default() };
20     let mut compiled_paths = compile_sass_glob(&sass_path, output_path, "scss", &options)?;
21 
22     options.indented_syntax = true;
23     compiled_paths.extend(compile_sass_glob(&sass_path, output_path, "sass", &options)?);
24 
25     compiled_paths.sort();
26     for window in compiled_paths.windows(2) {
27         if window[0].1 == window[1].1 {
28             bail!(
29                 "SASS path conflict: \"{}\" and \"{}\" both compile to \"{}\"",
30                 window[0].0.display(),
31                 window[1].0.display(),
32                 window[0].1.display(),
33             );
34         }
35     }
36 
37     Ok(())
38 }
39 
compile_sass_glob( sass_path: &Path, output_path: &Path, extension: &str, options: &Options, ) -> Result<Vec<(PathBuf, PathBuf)>>40 fn compile_sass_glob(
41     sass_path: &Path,
42     output_path: &Path,
43     extension: &str,
44     options: &Options,
45 ) -> Result<Vec<(PathBuf, PathBuf)>> {
46     let files = get_non_partial_scss(sass_path, extension);
47 
48     let mut compiled_paths = Vec::new();
49     for file in files {
50         let css = compile_file(&file, options.clone())?;
51 
52         let path_inside_sass = file.strip_prefix(&sass_path).unwrap();
53         let parent_inside_sass = path_inside_sass.parent();
54         let css_output_path = output_path.join(path_inside_sass).with_extension("css");
55 
56         if parent_inside_sass.is_some() {
57             create_dir_all(&css_output_path.parent().unwrap())?;
58         }
59 
60         create_file(&css_output_path, &css)?;
61         compiled_paths.push((path_inside_sass.to_owned(), css_output_path));
62     }
63 
64     Ok(compiled_paths)
65 }
66 
get_non_partial_scss(sass_path: &Path, extension: &str) -> Vec<PathBuf>67 fn get_non_partial_scss(sass_path: &Path, extension: &str) -> Vec<PathBuf> {
68     let glob_string = format!("{}/**/*.{}", sass_path.display(), extension);
69     glob(&glob_string)
70         .expect("Invalid glob for sass")
71         .filter_map(|e| e.ok())
72         .filter(|entry| {
73             !entry
74                 .as_path()
75                 .iter()
76                 .last()
77                 .map(|c| c.to_string_lossy().starts_with('_'))
78                 .unwrap_or(true)
79         })
80         .collect::<Vec<_>>()
81 }
82 
83 #[test]
test_get_non_partial_scss()84 fn test_get_non_partial_scss() {
85     use std::env;
86 
87     let mut path = env::current_dir().unwrap().parent().unwrap().parent().unwrap().to_path_buf();
88     path.push("test_site");
89     path.push("sass");
90 
91     let result = get_non_partial_scss(&path, "scss");
92 
93     assert!(!result.is_empty());
94     assert!(result.iter().filter_map(|path| path.file_name()).any(|file| file == "scss.scss"))
95 }
96 #[test]
test_get_non_partial_scss_underscores()97 fn test_get_non_partial_scss_underscores() {
98     use std::env;
99 
100     let mut path = env::current_dir().unwrap().parent().unwrap().parent().unwrap().to_path_buf();
101     path.push("test_site");
102     path.push("_dir_with_underscores");
103     path.push("..");
104     path.push("sass");
105 
106     let result = get_non_partial_scss(&path, "scss");
107 
108     assert!(!result.is_empty());
109     assert!(result.iter().filter_map(|path| path.file_name()).any(|file| file == "scss.scss"))
110 }
111