cobalt/cobalt_model/
sass.rs

1use std::ffi;
2use std::path;
3
4use serde::{Deserialize, Serialize};
5
6use super::files;
7use crate::cobalt_model::Minify;
8use crate::error::Result;
9pub(crate) use cobalt_config::SassOutputStyle;
10
11#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
12#[serde(deny_unknown_fields, default)]
13pub struct SassBuilder {
14    pub import_dir: path::PathBuf,
15    pub style: SassOutputStyle,
16}
17
18impl SassBuilder {
19    pub fn from_config(config: cobalt_config::Sass, source: &path::Path) -> Self {
20        Self {
21            style: config.style,
22            import_dir: source.join(config.import_dir),
23        }
24    }
25
26    pub fn build(self) -> SassCompiler {
27        let Self { import_dir, style } = self;
28        SassCompiler { import_dir, style }
29    }
30}
31
32#[derive(Debug, PartialEq, Eq)]
33pub struct SassCompiler {
34    import_dir: path::PathBuf,
35    style: SassOutputStyle,
36}
37
38impl SassCompiler {
39    pub fn compile_file<S: AsRef<path::Path>, D: AsRef<path::Path>, F: AsRef<path::Path>>(
40        &self,
41        source: S,
42        dest: D,
43        file_path: F,
44        minify: &Minify,
45    ) -> Result<()> {
46        self.compile_sass_internal(source.as_ref(), dest.as_ref(), file_path.as_ref(), minify)
47    }
48
49    #[cfg(feature = "sass")]
50    fn compile_sass_internal(
51        &self,
52        source: &path::Path,
53        dest: &path::Path,
54        file_path: &path::Path,
55        minify: &Minify,
56    ) -> Result<()> {
57        let sass_opts = grass::Options::default()
58            .style(match self.style {
59                SassOutputStyle::Nested | SassOutputStyle::Expanded => grass::OutputStyle::Expanded,
60                SassOutputStyle::Compact | SassOutputStyle::Compressed => {
61                    grass::OutputStyle::Compressed
62                }
63            })
64            .load_path(&self.import_dir);
65        let content = if let Some(file_path) = file_path.to_str() {
66            grass::from_path(file_path, &sass_opts)?
67        } else {
68            let raw = std::fs::read_to_string(file_path)?;
69            grass::from_string(raw, &sass_opts)?
70        };
71
72        let rel_src = file_path
73            .strip_prefix(source)
74            .expect("file was found under the root");
75        let mut dest_file = dest.join(rel_src);
76        dest_file.set_extension("css");
77
78        #[cfg(feature = "html-minifier")]
79        let content = if minify.css {
80            use html_minifier::css::minify;
81            minify(&content)
82                .map_err(|e| {
83                    anyhow::format_err!(
84                        "Could not minify saas file {} error {}",
85                        source.to_string_lossy(),
86                        e
87                    )
88                })?
89                .to_string()
90        } else {
91            content
92        };
93
94        files::write_document_file(content, dest_file)
95    }
96
97    #[cfg(not(feature = "sass"))]
98    fn compile_sass_internal(
99        &self,
100        source: &path::Path,
101        dest: &path::Path,
102        file_path: &path::Path,
103        minify: &Minify,
104    ) -> Result<()> {
105        let rel_src = file_path
106            .strip_prefix(source)
107            .expect("file was found under the root");
108        let dest_file = dest.join(rel_src);
109        files::copy_file(file_path, &dest_file)
110    }
111}
112
113impl Default for SassCompiler {
114    fn default() -> Self {
115        SassBuilder::default().build()
116    }
117}
118
119pub(crate) fn is_sass_file(file_path: &path::Path) -> bool {
120    file_path.extension() == Some(ffi::OsStr::new("scss"))
121        || file_path.extension() == Some(ffi::OsStr::new("sass"))
122}