sass_embedded/
api.rs

1use std::{
2  fmt::Debug,
3  path::{Path, PathBuf},
4};
5
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8
9use crate::{
10  protocol::{
11    self,
12    outbound_message::{
13      compile_response::{self, CompileSuccess},
14      CompileResponse,
15    },
16  },
17  Exception, Result, Url,
18};
19
20/// Options that can be passed to [Sass::compile].
21///
22/// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/Options)
23#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
24#[derive(Debug)]
25pub struct Options {
26  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/Options#alertAscii)
27  pub alert_ascii: bool,
28  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/Options#alertColor)
29  pub alert_color: Option<bool>,
30  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/Options#importers)
31  #[cfg_attr(feature = "serde", serde(skip))]
32  pub importers: Vec<SassImporter>,
33  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/Options#loadPaths)
34  pub load_paths: Vec<PathBuf>,
35  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/Options#logger)
36  #[cfg_attr(feature = "serde", serde(skip))]
37  pub logger: Option<BoxLogger>,
38  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/Options#quietDeps)
39  pub quiet_deps: bool,
40  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/Options#sourceMap)
41  pub source_map: bool,
42  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/Options#sourceMapIncludeSources)
43  pub source_map_include_sources: bool,
44  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/Options#style)
45  pub style: OutputStyle,
46  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/Options#verbose)
47  pub verbose: bool,
48  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/Options#charset)
49  pub charset: bool,
50}
51
52impl Default for Options {
53  fn default() -> Self {
54    Self {
55      alert_ascii: false,
56      alert_color: None,
57      load_paths: Vec::new(),
58      importers: Vec::new(),
59      logger: None,
60      quiet_deps: false,
61      source_map: false,
62      source_map_include_sources: false,
63      style: OutputStyle::default(),
64      verbose: false,
65      charset: true,
66    }
67  }
68}
69
70/// A builder for [Options].
71#[derive(Debug, Default)]
72pub struct OptionsBuilder {
73  options: Options,
74}
75
76impl OptionsBuilder {
77  /// Creates a new [OptionsBuilder].
78  pub fn new() -> Self {
79    Self::default()
80  }
81
82  /// Build the [Options].
83  pub fn build(self) -> Options {
84    self.options
85  }
86
87  /// Sets the [Options]'s [alert_ascii] field.
88  pub fn alert_ascii(mut self, arg: impl Into<bool>) -> Self {
89    self.options.alert_ascii = arg.into();
90    self
91  }
92
93  /// Sets the [Options]'s [alert_color] field.
94  pub fn alert_color(mut self, arg: impl Into<bool>) -> Self {
95    self.options.alert_color = Some(arg.into());
96    self
97  }
98
99  /// Sets the [Options]'s [load_paths] field.
100  pub fn load_paths<P: AsRef<Path>>(mut self, arg: impl AsRef<[P]>) -> Self {
101    self.options.load_paths =
102      arg.as_ref().iter().map(|p| p.as_ref().to_owned()).collect();
103    self
104  }
105
106  /// Adds a load_path to the [Options]'s [load_paths] field.
107  pub fn load_path(mut self, arg: impl AsRef<Path>) -> Self {
108    self.options.load_paths.push(arg.as_ref().to_owned());
109    self
110  }
111
112  /// Sets the [Options]'s [quiet_deps] field.
113  pub fn quiet_deps(mut self, arg: impl Into<bool>) -> Self {
114    self.options.quiet_deps = arg.into();
115    self
116  }
117
118  /// Sets the [Options]'s [source_map] field.
119  pub fn source_map(mut self, arg: impl Into<bool>) -> Self {
120    self.options.source_map = arg.into();
121    self
122  }
123
124  /// Sets the [Options]'s [source_map_include_sources] field.
125  pub fn source_map_include_sources(mut self, arg: impl Into<bool>) -> Self {
126    self.options.source_map_include_sources = arg.into();
127    self
128  }
129
130  /// Sets the [Options]'s [style] field.
131  pub fn style(mut self, arg: impl Into<OutputStyle>) -> Self {
132    self.options.style = arg.into();
133    self
134  }
135
136  /// Sets the [Options]'s [verbose] field.
137  pub fn verbose(mut self, arg: impl Into<bool>) -> Self {
138    self.options.verbose = arg.into();
139    self
140  }
141
142  /// Sets the [Options]'s [charset] field.
143  pub fn charset(mut self, arg: impl Into<bool>) -> Self {
144    self.options.charset = arg.into();
145    self
146  }
147
148  /// Sets the [Options]'s [logger] field.
149  pub fn logger<L: 'static + Logger>(mut self, arg: L) -> Self {
150    self.options.logger = Some(Box::new(arg));
151    self
152  }
153
154  /// Adds a [SassImporter] to the [Options]'s [importers] field.
155  pub fn sass_importer(mut self, arg: impl Into<SassImporter>) -> Self {
156    self.options.importers.push(arg.into());
157    self
158  }
159
160  /// Sets the [Options]'s [importers] field.
161  pub fn sass_importers(
162    mut self,
163    arg: impl IntoIterator<Item = impl Into<SassImporter>>,
164  ) -> Self {
165    self.options.importers = arg.into_iter().map(|i| i.into()).collect();
166    self
167  }
168
169  /// Adds a [Importer] to the [Options]'s [importers] field.
170  pub fn importer<I: 'static + Importer>(mut self, arg: I) -> Self {
171    self
172      .options
173      .importers
174      .push(SassImporter::Importer(Box::new(arg) as Box<dyn Importer>));
175    self
176  }
177
178  /// Adds a [FileImporter] to the [Options]'s [importers] field.
179  pub fn file_importer<I: 'static + FileImporter>(mut self, arg: I) -> Self {
180    self.options.importers.push(SassImporter::FileImporter(
181      Box::new(arg) as Box<dyn FileImporter>
182    ));
183    self
184  }
185}
186
187/// Options that can be passed to [Sass::compile_string].
188///
189/// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/modules#StringOptions)
190#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
191#[derive(Debug, Default)]
192pub struct StringOptions {
193  /// Field for [Options]
194  pub common: Options,
195  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/StringOptionsWithImporter#importer)
196  #[cfg_attr(feature = "serde", serde(skip))]
197  pub input_importer: Option<SassImporter>,
198  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/StringOptionsWithoutImporter#syntax)
199  pub syntax: Syntax,
200  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/StringOptionsWithoutImporter#url)
201  pub url: Option<Url>,
202}
203
204/// A builder for [StringOptions].
205#[derive(Debug, Default)]
206pub struct StringOptionsBuilder {
207  options: Options,
208  input_importer: Option<SassImporter>,
209  syntax: Syntax,
210  url: Option<Url>,
211}
212
213impl StringOptionsBuilder {
214  /// Creates a new [StringOptionsBuilder].
215  pub fn new() -> Self {
216    Self::default()
217  }
218
219  /// Build the [StringOptions].
220  pub fn build(self) -> StringOptions {
221    StringOptions {
222      common: self.options,
223      input_importer: self.input_importer,
224      syntax: self.syntax,
225      url: self.url,
226    }
227  }
228
229  /// Sets the [StringOptions]'s [input_importer] field with a [SassImporter].
230  pub fn input_sass_importer(mut self, arg: impl Into<SassImporter>) -> Self {
231    self.input_importer = Some(arg.into());
232    self
233  }
234
235  /// Sets the [StringOptions]'s [input_importer] field with a [Importer].
236  pub fn input_importer<I: 'static + Importer>(mut self, arg: I) -> Self {
237    self.input_importer = Some(SassImporter::Importer(Box::new(arg)));
238    self
239  }
240
241  /// Sets the [StringOptions]'s [input_importer] field with a [FileImporter].
242  pub fn input_file_importer<I: 'static + FileImporter>(
243    mut self,
244    arg: I,
245  ) -> Self {
246    self.input_importer = Some(SassImporter::FileImporter(Box::new(arg)));
247    self
248  }
249
250  /// Sets the [StringOptions]'s [syntax] field.
251  pub fn syntax(mut self, arg: impl Into<Syntax>) -> Self {
252    self.syntax = arg.into();
253    self
254  }
255
256  /// Sets the [StringOptions]'s [url] field.
257  pub fn url(mut self, arg: impl Into<Url>) -> Self {
258    self.url = Some(arg.into());
259    self
260  }
261
262  /// Sets the [StringOptions]'s [alert_ascii] field.
263  pub fn alert_ascii(mut self, arg: impl Into<bool>) -> Self {
264    self.options.alert_ascii = arg.into();
265    self
266  }
267
268  /// Sets the [StringOptions]'s [alert_color] field.
269  pub fn alert_color(mut self, arg: impl Into<bool>) -> Self {
270    self.options.alert_color = Some(arg.into());
271    self
272  }
273
274  /// Sets the [StringOptions]'s [load_paths] field.
275  pub fn load_paths<P: AsRef<Path>>(mut self, arg: impl AsRef<[P]>) -> Self {
276    self.options.load_paths =
277      arg.as_ref().iter().map(|p| p.as_ref().to_owned()).collect();
278    self
279  }
280
281  /// Adds a [Path] to the [StringOptions]'s [load_paths] field.
282  pub fn load_path(mut self, arg: impl AsRef<Path>) -> Self {
283    self.options.load_paths.push(arg.as_ref().to_owned());
284    self
285  }
286
287  /// Sets the [StringOptions]'s [quiet_deps] field.
288  pub fn quiet_deps(mut self, arg: impl Into<bool>) -> Self {
289    self.options.quiet_deps = arg.into();
290    self
291  }
292
293  /// Sets the [StringOptions]'s [source_map] field.
294  pub fn source_map(mut self, arg: impl Into<bool>) -> Self {
295    self.options.source_map = arg.into();
296    self
297  }
298
299  /// Sets the [StringOptions]'s [source_map_include_sources] field.
300  pub fn source_map_include_sources(mut self, arg: impl Into<bool>) -> Self {
301    self.options.source_map_include_sources = arg.into();
302    self
303  }
304
305  /// Sets the [StringOptions]'s [style] field.
306  pub fn style(mut self, arg: impl Into<OutputStyle>) -> Self {
307    self.options.style = arg.into();
308    self
309  }
310
311  /// Sets the [StringOptions]'s [verbose] field.
312  pub fn verbose(mut self, arg: impl Into<bool>) -> Self {
313    self.options.verbose = arg.into();
314    self
315  }
316
317  /// Sets the [StringOptions]'s [charset] field.
318  pub fn charset(mut self, arg: impl Into<bool>) -> Self {
319    self.options.charset = arg.into();
320    self
321  }
322
323  /// Sets the [StringOptions]'s [logger] field.
324  pub fn logger<L: 'static + Logger>(mut self, arg: L) -> Self {
325    self.options.logger = Some(Box::new(arg));
326    self
327  }
328
329  /// Adds a [SassImporter] to the [StringOptions]'s [importers] field.
330  pub fn sass_importer(mut self, arg: impl Into<SassImporter>) -> Self {
331    self.options.importers.push(arg.into());
332    self
333  }
334
335  /// Sets the [StringOptions]'s [importers] field with [SassImporter]s.
336  pub fn sass_importers(
337    mut self,
338    arg: impl IntoIterator<Item = impl Into<SassImporter>>,
339  ) -> Self {
340    self.options.importers = arg.into_iter().map(|i| i.into()).collect();
341    self
342  }
343
344  /// Adds a [Importer] to the [StringOptions]'s [importers] field.
345  pub fn importer<I: 'static + Importer>(mut self, arg: I) -> Self {
346    self
347      .options
348      .importers
349      .push(SassImporter::Importer(Box::new(arg)));
350    self
351  }
352
353  /// Adds a [FileImporter] to the [StringOptions]'s [importers] field.
354  pub fn file_importer<I: 'static + FileImporter>(mut self, arg: I) -> Self {
355    self
356      .options
357      .importers
358      .push(SassImporter::FileImporter(Box::new(arg)));
359    self
360  }
361}
362
363/// A type alias for [Box<dyn Logger>].
364pub type BoxLogger = Box<dyn Logger>;
365
366/// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/Logger)
367pub trait Logger: Debug + Send + Sync {
368  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/Logger#warn)
369  fn warn(&self, _message: &str, options: &LoggerWarnOptions) {
370    eprintln!("{}", options.formatted);
371  }
372
373  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/Logger#debug)
374  fn debug(&self, _message: &str, options: &LoggerDebugOptions) {
375    eprintln!("{}", options.formatted);
376  }
377}
378
379/// Options for [Logger::warn].
380///
381/// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/Logger#warn)
382pub struct LoggerWarnOptions {
383  /// Whether this is a deprecation warning.
384  pub deprecation: bool,
385  /// The location in the Sass source code that generated this warning.
386  pub span: Option<SourceSpan>,
387  /// The Sass stack trace at the point the warning was issued.
388  pub stack: Option<String>,
389  pub(crate) formatted: String,
390}
391
392/// Options for [Logger::debug].
393///
394/// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/Logger#debug)
395pub struct LoggerDebugOptions {
396  /// The location in the Sass source code that generated this debug message.
397  pub span: Option<SourceSpan>,
398  pub(crate) formatted: String,
399}
400
401/// Enum wrapper for [BoxImporter] and [BoxFileImporter].
402#[derive(Debug)]
403pub enum SassImporter {
404  /// A [BoxImporter].
405  Importer(BoxImporter),
406  /// A [BoxFileImporter].
407  FileImporter(BoxFileImporter),
408}
409
410/// A type alias for [Box<dyn Importer>].
411pub type BoxImporter = Box<dyn Importer>;
412
413/// A type alias for [Box<dyn FileImporter>].
414pub type BoxFileImporter = Box<dyn FileImporter>;
415
416/// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/Importer)
417pub trait Importer: Debug + Send + Sync {
418  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/Importer#canonicalize)
419  fn canonicalize(
420    &self,
421    url: &str,
422    options: &ImporterOptions,
423  ) -> Result<Option<Url>>;
424
425  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/Importer#load)
426  fn load(&self, canonical_url: &Url) -> Result<Option<ImporterResult>>;
427}
428
429/// Options for [Importer::canonicalize] or [Importer::load].
430///
431/// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/Importer#canonicalize)
432pub struct ImporterOptions {
433  /// Whether this is being invoked because of a Sass @import rule, as opposed to a @use
434  /// or @forward rule.
435  pub from_import: bool,
436}
437
438/// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/FileImporter)
439pub trait FileImporter: Debug + Send + Sync {
440  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/FileImporter#findFileUrl)
441  fn find_file_url(
442    &self,
443    url: &str,
444    options: &ImporterOptions,
445  ) -> Result<Option<Url>>;
446}
447
448/// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/ImporterResult)
449pub struct ImporterResult {
450  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/ImporterResult#contents)
451  pub contents: String,
452  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/ImporterResult#sourceMapUrl)
453  pub source_map_url: Option<Url>,
454  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/ImporterResult#syntax)
455  pub syntax: Syntax,
456}
457
458/// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/CompileResult)
459#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
460#[derive(Debug, Clone)]
461pub struct CompileResult {
462  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/CompileResult#css)
463  pub css: String,
464  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/CompileResult#loadedUrls)
465  pub loaded_urls: Vec<Url>,
466  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/CompileResult#sourceMap)
467  pub source_map: Option<String>,
468}
469
470impl TryFrom<CompileResponse> for CompileResult {
471  type Error = Box<Exception>;
472
473  fn try_from(response: CompileResponse) -> Result<Self> {
474    let res = response.result.unwrap();
475    match res {
476      compile_response::Result::Success(success) => Ok(success.into()),
477      compile_response::Result::Failure(failure) => {
478        Err(Exception::from(failure).into())
479      }
480    }
481  }
482}
483
484impl From<CompileSuccess> for CompileResult {
485  fn from(s: CompileSuccess) -> Self {
486    Self {
487      css: s.css,
488      loaded_urls: s
489        .loaded_urls
490        .iter()
491        .map(|url| Url::parse(url).unwrap())
492        .collect(),
493      source_map: if s.source_map.is_empty() {
494        None
495      } else {
496        Some(s.source_map)
497      },
498    }
499  }
500}
501
502/// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/modules#OutputStyle)
503#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
504#[derive(Debug, Clone, Copy)]
505pub enum OutputStyle {
506  /// Writes each selector and declaration on its own line.
507  Expanded,
508  /// Removes as many extra characters as possible, and writes the entire stylesheet on a single line.
509  Compressed,
510}
511
512impl Default for OutputStyle {
513  fn default() -> Self {
514    Self::Expanded
515  }
516}
517
518impl From<OutputStyle> for protocol::OutputStyle {
519  fn from(o: OutputStyle) -> Self {
520    match o {
521      OutputStyle::Expanded => Self::Expanded,
522      OutputStyle::Compressed => Self::Compressed,
523    }
524  }
525}
526
527/// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/modules#Syntax)
528#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
529#[derive(Debug, Clone, Copy)]
530pub enum Syntax {
531  /// the [scss syntax](https://sass-lang.com/documentation/syntax#scss)
532  Scss,
533  /// the [indented syntax](https://sass-lang.com/documentation/syntax#the-indented-syntax)
534  Indented,
535  /// the plain css syntax, which is parsed like SCSS but forbids the use of any special Sass features.
536  Css,
537}
538
539impl Default for Syntax {
540  fn default() -> Self {
541    Self::Scss
542  }
543}
544
545impl From<Syntax> for protocol::Syntax {
546  fn from(s: Syntax) -> Self {
547    match s {
548      Syntax::Scss => Self::Scss,
549      Syntax::Indented => Self::Indented,
550      Syntax::Css => Self::Css,
551    }
552  }
553}
554
555/// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/SourceSpan)
556#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
557#[derive(Debug, Clone)]
558pub struct SourceSpan {
559  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/SourceSpan#context)
560  pub context: Option<String>,
561  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/SourceSpan#end)
562  pub end: SourceLocation,
563  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/SourceSpan#start)
564  pub start: SourceLocation,
565  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/SourceSpan#url)
566  pub url: Option<Url>,
567  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/SourceSpan#text)
568  pub text: String,
569}
570
571impl From<protocol::SourceSpan> for SourceSpan {
572  fn from(span: protocol::SourceSpan) -> Self {
573    let start = span.start.unwrap();
574    Self {
575      context: if span.context.is_empty() {
576        None
577      } else {
578        Some(span.context)
579      },
580      end: span.end.unwrap_or_else(|| start.clone()).into(),
581      start: start.into(),
582      url: if span.url.is_empty() {
583        None
584      } else {
585        Some(Url::parse(&span.url).unwrap())
586      },
587      text: span.text,
588    }
589  }
590}
591
592#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
593#[derive(Debug, Clone)]
594pub struct SourceLocation {
595  pub offset: usize,
596  pub line: usize,
597  pub column: usize,
598}
599
600impl From<protocol::source_span::SourceLocation> for SourceLocation {
601  fn from(location: protocol::source_span::SourceLocation) -> Self {
602    Self {
603      offset: location.offset as usize,
604      line: location.line as usize,
605      column: location.column as usize,
606    }
607  }
608}