cobalt/cobalt_model/
sass.rs

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