fugo_sass_compiler/options.rs
1use std::{
2 collections::HashMap,
3 path::{Path, PathBuf},
4};
5
6use crate::{Fs, Logger, StdFs, StdLogger, builtin::Builtin};
7
8/// Configuration for Sass compilation
9///
10/// The simplest usage is `fugo_sass::Options::default()`; however, a builder pattern
11/// is also exposed to offer more control.
12#[derive(Debug)]
13pub struct Options<'a> {
14 pub(crate) fs: &'a dyn Fs,
15 pub(crate) logger: &'a dyn Logger,
16 pub(crate) style: OutputStyle,
17 pub(crate) load_paths: Vec<PathBuf>,
18 pub(crate) allows_charset: bool,
19 pub(crate) unicode_error_messages: bool,
20 pub(crate) quiet: bool,
21 pub(crate) input_syntax: Option<InputSyntax>,
22 pub(crate) custom_fns: HashMap<String, Builtin>,
23}
24
25impl Default for Options<'_> {
26 #[inline]
27 fn default() -> Self {
28 Self {
29 fs: &StdFs,
30 logger: &StdLogger,
31 style: OutputStyle::Expanded,
32 load_paths: Vec::new(),
33 allows_charset: true,
34 unicode_error_messages: true,
35 quiet: false,
36 input_syntax: None,
37 custom_fns: HashMap::new(),
38 }
39 }
40}
41
42impl<'a> Options<'a> {
43 /// This option allows you to control the file system that Sass will see.
44 ///
45 /// By default, it uses [`StdFs`], which is backed by [`std::fs`],
46 /// allowing direct, unfettered access to the local file system.
47 #[must_use]
48 #[inline]
49 pub fn fs(mut self, fs: &'a dyn Fs) -> Self {
50 self.fs = fs;
51 self
52 }
53
54 /// This option allows you to define how log events should be handled
55 ///
56 /// Be default, [`StdLogger`] is used, which writes all events to standard output.
57 #[must_use]
58 #[inline]
59 pub fn logger(mut self, logger: &'a dyn Logger) -> Self {
60 self.logger = logger;
61 self
62 }
63
64 /// `fugo-sass` currently offers 2 different output styles
65 ///
66 /// - [`OutputStyle::Expanded`] writes each selector and declaration on its own line.
67 /// - [`OutputStyle::Compressed`] removes as many extra characters as possible
68 /// and writes the entire stylesheet on a single line.
69 ///
70 /// By default, output is expanded.
71 #[must_use]
72 #[inline]
73 pub const fn style(mut self, style: OutputStyle) -> Self {
74 self.style = style;
75 self
76 }
77
78 /// This flag tells Sass not to emit any warnings when compiling. By default,
79 /// Sass emits warnings when deprecated features are used or when the `@warn`
80 /// rule is encountered. It also silences the `@debug` rule.
81 ///
82 /// Setting this option to `true` will stop all logs from reaching the [`crate::Logger`].
83 ///
84 /// By default, this value is `false` and warnings are emitted.
85 #[must_use]
86 #[inline]
87 pub const fn quiet(mut self, quiet: bool) -> Self {
88 self.quiet = quiet;
89 self
90 }
91
92 /// All Sass implementations allow users to provide load paths: paths on the
93 /// filesystem that Sass will look in when locating modules. For example, if
94 /// you pass `node_modules/susy/sass` as a load path, you can use
95 /// `@import "susy"` to load `node_modules/susy/sass/susy.scss`.
96 ///
97 /// Imports will always be resolved relative to the current file first, though.
98 /// Load paths will only be used if no relative file exists that matches the
99 /// module's URL. This ensures that you can't accidentally mess up your relative
100 /// imports when you add a new library.
101 ///
102 /// This method will append a single path to the list.
103 #[must_use]
104 #[inline]
105 pub fn load_path<P: AsRef<Path>>(mut self, path: P) -> Self {
106 self.load_paths.push(path.as_ref().to_owned());
107 self
108 }
109
110 /// Append multiple loads paths
111 ///
112 /// Note that this method does *not* remove existing load paths
113 ///
114 /// See [`Options::load_path`](Options::load_path) for more information about
115 /// load paths
116 #[must_use]
117 #[inline]
118 pub fn load_paths<P: AsRef<Path>>(mut self, paths: &[P]) -> Self {
119 for path in paths {
120 self.load_paths.push(path.as_ref().to_owned());
121 }
122
123 self
124 }
125
126 /// This flag tells Sass whether to emit a `@charset`
127 /// declaration or a UTF-8 byte-order mark.
128 ///
129 /// By default, Sass will insert either a `@charset`
130 /// declaration (in expanded output mode) or a byte-order
131 /// mark (in compressed output mode) if the stylesheet
132 /// contains any non-ASCII characters.
133 #[must_use]
134 #[inline]
135 pub const fn allows_charset(mut self, allows_charset: bool) -> Self {
136 self.allows_charset = allows_charset;
137 self
138 }
139
140 /// This flag tells Sass only to emit ASCII characters as
141 /// part of error messages.
142 ///
143 /// By default Sass will emit non-ASCII characters for
144 /// these messages.
145 ///
146 /// This flag does not affect the CSS output.
147 #[must_use]
148 #[inline]
149 pub const fn unicode_error_messages(mut self, unicode_error_messages: bool) -> Self {
150 self.unicode_error_messages = unicode_error_messages;
151 self
152 }
153
154 /// This option forces Sass to parse input using the given syntax.
155 ///
156 /// By default, Sass will attempt to read the file extension to determine
157 /// the syntax. If this is not possible, it will default to [`InputSyntax::Scss`].
158 ///
159 /// This flag only affects the first file loaded. Files that are loaded using
160 /// `@import`, `@use`, or `@forward` will always have their syntax inferred.
161 #[must_use]
162 #[inline]
163 pub const fn input_syntax(mut self, syntax: InputSyntax) -> Self {
164 self.input_syntax = Some(syntax);
165 self
166 }
167
168 /// Add a custom function accessible from within Sass
169 ///
170 /// See the [`Builtin`] documentation for additional information
171 #[must_use]
172 #[inline]
173 #[cfg(any(feature = "custom-builtin-fns", doc))]
174 #[cfg_attr(docsrs, doc(cfg(feature = "custom-builtin-fns")))]
175 pub fn add_custom_fn<S: Into<String>>(mut self, name: S, func: Builtin) -> Self {
176 self.custom_fns.insert(name.into(), func);
177 self
178 }
179
180 pub(crate) fn is_compressed(&self) -> bool {
181 matches!(self.style, OutputStyle::Compressed)
182 }
183}
184
185/// Useful when parsing Sass from sources other than the file system
186///
187/// See [`Options::input_syntax`] for additional information
188#[non_exhaustive]
189#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
190pub enum InputSyntax {
191 /// The CSS-superset SCSS syntax.
192 Scss,
193
194 /// The whitespace-sensitive indented syntax.
195 Sass,
196
197 /// The plain CSS syntax, which disallows special Sass features.
198 Css,
199}
200
201impl InputSyntax {
202 pub(crate) fn for_path(path: &Path) -> Self {
203 match path
204 .extension()
205 .and_then(|ext| ext.to_str())
206 .map(str::to_ascii_lowercase)
207 .as_deref()
208 {
209 Some("css") => Self::Css,
210 Some("sass") => Self::Sass,
211 _ => Self::Scss,
212 }
213 }
214}
215
216#[non_exhaustive]
217#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
218pub enum OutputStyle {
219 /// This mode writes each selector and declaration on its own line.
220 ///
221 /// This is the default output.
222 Expanded,
223
224 /// Ideal for release builds, this mode removes as many extra characters as
225 /// possible and writes the entire stylesheet on a single line.
226 Compressed,
227}