cobalt/cobalt_model/
sass.rs1use 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}