Skip to main content

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}