sass_embedded_host_rust/
api.rs

1use std::{
2  fmt::Debug,
3  path::{Path, PathBuf},
4};
5
6use crate::{
7  protocol::{
8    outbound_message::{
9      compile_response::{self, CompileSuccess},
10      CompileResponse,
11    },
12    OutputStyle, SourceSpan, Syntax,
13  },
14  Exception, Result, Url,
15};
16
17/// https://sass-lang.com/documentation/js-api/interfaces/Options
18#[derive(Debug)]
19pub struct Options {
20  /// https://sass-lang.com/documentation/js-api/interfaces/Options#alertAscii
21  pub alert_ascii: bool,
22  /// https://sass-lang.com/documentation/js-api/interfaces/Options#alertColor
23  pub alert_color: Option<bool>,
24  // /// https://sass-lang.com/documentation/js-api/interfaces/Options#functions
25  // pub functions
26  /// https://sass-lang.com/documentation/js-api/interfaces/Options#importers
27  pub importers: Vec<SassImporter>,
28  /// https://sass-lang.com/documentation/js-api/interfaces/Options#loadPaths
29  pub load_paths: Vec<PathBuf>,
30  /// https://sass-lang.com/documentation/js-api/interfaces/Options#logger
31  pub logger: Option<SassLogger>,
32  /// https://sass-lang.com/documentation/js-api/interfaces/Options#quietDeps
33  pub quiet_deps: bool,
34  /// https://sass-lang.com/documentation/js-api/interfaces/Options#sourceMap
35  pub source_map: bool,
36  /// https://sass-lang.com/documentation/js-api/interfaces/Options#sourceMapIncludeSources
37  pub source_map_include_sources: bool,
38  /// https://sass-lang.com/documentation/js-api/interfaces/Options#style
39  pub style: OutputStyle,
40  /// https://sass-lang.com/documentation/js-api/interfaces/Options#verbose
41  pub verbose: bool,
42  /// https://sass-lang.com/documentation/js-api/interfaces/Options#charset
43  pub charset: bool,
44}
45
46impl Default for Options {
47  fn default() -> Self {
48    Self {
49      alert_ascii: false,
50      alert_color: None,
51      load_paths: Vec::new(),
52      importers: Vec::new(),
53      logger: None,
54      quiet_deps: false,
55      source_map: false,
56      source_map_include_sources: false,
57      style: OutputStyle::default(),
58      verbose: false,
59      charset: true,
60    }
61  }
62}
63
64#[derive(Debug, Default)]
65pub struct OptionsBuilder {
66  options: Options,
67}
68
69impl OptionsBuilder {
70  pub fn new() -> Self {
71    Self::default()
72  }
73
74  pub fn build(self) -> Options {
75    self.options
76  }
77
78  pub fn alert_ascii(mut self, arg: impl Into<bool>) -> Self {
79    self.options.alert_ascii = arg.into();
80    self
81  }
82
83  pub fn alert_color(mut self, arg: impl Into<bool>) -> Self {
84    self.options.alert_color = Some(arg.into());
85    self
86  }
87
88  pub fn load_paths(mut self, arg: &[impl AsRef<Path>]) -> Self {
89    self.options.load_paths =
90      arg.iter().map(|p| p.as_ref().to_owned()).collect();
91    self
92  }
93
94  pub fn load_path(mut self, arg: impl AsRef<Path>) -> Self {
95    self.options.load_paths.push(arg.as_ref().to_owned());
96    self
97  }
98
99  pub fn quiet_deps(mut self, arg: impl Into<bool>) -> Self {
100    self.options.quiet_deps = arg.into();
101    self
102  }
103
104  pub fn source_map(mut self, arg: impl Into<bool>) -> Self {
105    self.options.source_map = arg.into();
106    self
107  }
108
109  pub fn source_map_include_sources(mut self, arg: impl Into<bool>) -> Self {
110    self.options.source_map_include_sources = arg.into();
111    self
112  }
113
114  pub fn style(mut self, arg: impl Into<OutputStyle>) -> Self {
115    self.options.style = arg.into();
116    self
117  }
118
119  pub fn verbose(mut self, arg: impl Into<bool>) -> Self {
120    self.options.verbose = arg.into();
121    self
122  }
123
124  pub fn charset(mut self, arg: impl Into<bool>) -> Self {
125    self.options.charset = arg.into();
126    self
127  }
128
129  pub fn logger<L: 'static + Logger>(mut self, arg: L) -> Self {
130    self.options.logger = Some(Box::new(arg));
131    self
132  }
133
134  pub fn sass_importer(mut self, arg: impl Into<SassImporter>) -> Self {
135    self.options.importers.push(arg.into());
136    self
137  }
138
139  pub fn sass_importers(
140    mut self,
141    arg: impl IntoIterator<Item = impl Into<SassImporter>>,
142  ) -> Self {
143    self.options.importers = arg.into_iter().map(|i| i.into()).collect();
144    self
145  }
146
147  pub fn importer<I: 'static + Importer>(mut self, arg: I) -> Self {
148    self
149      .options
150      .importers
151      .push(SassImporter::Importer(Box::new(arg) as Box<dyn Importer>));
152    self
153  }
154
155  pub fn file_importer<I: 'static + FileImporter>(mut self, arg: I) -> Self {
156    self.options.importers.push(SassImporter::FileImporter(
157      Box::new(arg) as Box<dyn FileImporter>
158    ));
159    self
160  }
161}
162
163#[derive(Debug, Default)]
164pub struct StringOptions {
165  pub common: Options,
166  /// https://sass-lang.com/documentation/js-api/interfaces/StringOptionsWithImporter#importer
167  pub input_importer: Option<SassImporter>,
168  /// https://sass-lang.com/documentation/js-api/interfaces/StringOptionsWithoutImporter#syntax
169  pub syntax: Syntax,
170  /// https://sass-lang.com/documentation/js-api/interfaces/StringOptionsWithImporter#url
171  pub url: Option<Url>,
172}
173
174#[derive(Debug, Default)]
175pub struct StringOptionsBuilder {
176  options: Options,
177  /// https://sass-lang.com/documentation/js-api/interfaces/StringOptionsWithImporter#importer
178  input_importer: Option<SassImporter>,
179  /// https://sass-lang.com/documentation/js-api/interfaces/StringOptionsWithoutImporter#syntax
180  syntax: Syntax,
181  /// https://sass-lang.com/documentation/js-api/interfaces/StringOptionsWithImporter#url
182  url: Option<Url>,
183}
184
185impl StringOptionsBuilder {
186  pub fn new() -> Self {
187    Self::default()
188  }
189
190  pub fn build(self) -> StringOptions {
191    StringOptions {
192      common: self.options,
193      input_importer: self.input_importer,
194      syntax: self.syntax,
195      url: self.url,
196    }
197  }
198
199  pub fn input_sass_importer(mut self, arg: impl Into<SassImporter>) -> Self {
200    self.input_importer = Some(arg.into());
201    self
202  }
203
204  pub fn input_importer<I: 'static + Importer>(mut self, arg: I) -> Self {
205    self.input_importer = Some(SassImporter::Importer(Box::new(arg)));
206    self
207  }
208
209  pub fn input_file_importer<I: 'static + FileImporter>(
210    mut self,
211    arg: I,
212  ) -> Self {
213    self.input_importer = Some(SassImporter::FileImporter(Box::new(arg)));
214    self
215  }
216
217  pub fn syntax(mut self, arg: impl Into<Syntax>) -> Self {
218    self.syntax = arg.into();
219    self
220  }
221
222  pub fn url(mut self, arg: impl Into<Url>) -> Self {
223    self.url = Some(arg.into());
224    self
225  }
226
227  pub fn alert_ascii(mut self, arg: impl Into<bool>) -> Self {
228    self.options.alert_ascii = arg.into();
229    self
230  }
231
232  pub fn alert_color(mut self, arg: impl Into<bool>) -> Self {
233    self.options.alert_color = Some(arg.into());
234    self
235  }
236
237  pub fn load_paths(mut self, arg: &[impl AsRef<Path>]) -> Self {
238    self.options.load_paths =
239      arg.iter().map(|p| p.as_ref().to_owned()).collect();
240    self
241  }
242
243  pub fn load_path(mut self, arg: impl AsRef<Path>) -> Self {
244    self.options.load_paths.push(arg.as_ref().to_owned());
245    self
246  }
247
248  pub fn quiet_deps(mut self, arg: impl Into<bool>) -> Self {
249    self.options.quiet_deps = arg.into();
250    self
251  }
252
253  pub fn source_map(mut self, arg: impl Into<bool>) -> Self {
254    self.options.source_map = arg.into();
255    self
256  }
257
258  pub fn source_map_include_sources(mut self, arg: impl Into<bool>) -> Self {
259    self.options.source_map_include_sources = arg.into();
260    self
261  }
262
263  pub fn style(mut self, arg: impl Into<OutputStyle>) -> Self {
264    self.options.style = arg.into();
265    self
266  }
267
268  pub fn verbose(mut self, arg: impl Into<bool>) -> Self {
269    self.options.verbose = arg.into();
270    self
271  }
272
273  pub fn charset(mut self, arg: impl Into<bool>) -> Self {
274    self.options.charset = arg.into();
275    self
276  }
277
278  pub fn logger<L: 'static + Logger>(mut self, arg: L) -> Self {
279    self.options.logger = Some(Box::new(arg));
280    self
281  }
282
283  pub fn sass_importer(mut self, arg: impl Into<SassImporter>) -> Self {
284    self.options.importers.push(arg.into());
285    self
286  }
287
288  pub fn sass_importers(
289    mut self,
290    arg: impl IntoIterator<Item = impl Into<SassImporter>>,
291  ) -> Self {
292    self.options.importers = arg.into_iter().map(|i| i.into()).collect();
293    self
294  }
295
296  pub fn importer<I: 'static + Importer>(mut self, arg: I) -> Self {
297    self
298      .options
299      .importers
300      .push(SassImporter::Importer(Box::new(arg)));
301    self
302  }
303
304  pub fn file_importer<I: 'static + FileImporter>(mut self, arg: I) -> Self {
305    self
306      .options
307      .importers
308      .push(SassImporter::FileImporter(Box::new(arg)));
309    self
310  }
311}
312
313pub type SassLogger = Box<dyn Logger>;
314
315pub trait Logger: Debug + Send + Sync {
316  fn warn(&self, _message: &str, options: &LoggerWarnOptions) {
317    eprintln!("{}", options.formatted);
318  }
319
320  fn debug(&self, _message: &str, options: &LoggerDebugOptions) {
321    eprintln!("{}", options.formatted);
322  }
323}
324
325pub struct LoggerWarnOptions {
326  pub deprecation: bool,
327  pub span: Option<SourceSpan>,
328  pub stack: Option<String>,
329  pub(crate) formatted: String,
330}
331
332pub struct LoggerDebugOptions {
333  pub span: Option<SourceSpan>,
334  pub(crate) formatted: String,
335}
336
337#[derive(Debug)]
338pub enum SassImporter {
339  Importer(Box<dyn Importer>),
340  FileImporter(Box<dyn FileImporter>),
341}
342
343/// https://sass-lang.com/documentation/js-api/interfaces/Importer
344pub trait Importer: Debug + Send + Sync {
345  /// https://sass-lang.com/documentation/js-api/interfaces/Importer#canonicalize
346  fn canonicalize(
347    &self,
348    url: &str,
349    options: &ImporterOptions,
350  ) -> Result<Option<Url>>;
351
352  /// https://sass-lang.com/documentation/js-api/interfaces/Importer#load
353  fn load(&self, canonical_url: &Url) -> Result<Option<ImporterResult>>;
354}
355
356pub struct ImporterOptions {
357  pub from_import: bool,
358}
359
360/// https://sass-lang.com/documentation/js-api/interfaces/FileImporter
361pub trait FileImporter: Debug + Send + Sync {
362  /// https://sass-lang.com/documentation/js-api/interfaces/FileImporter#findFileUrl
363  fn find_file_url(
364    &self,
365    url: &str,
366    options: &ImporterOptions,
367  ) -> Result<Option<Url>>;
368}
369
370/// https://sass-lang.com/documentation/js-api/interfaces/ImporterResult
371pub struct ImporterResult {
372  /// https://sass-lang.com/documentation/js-api/interfaces/ImporterResult#contents
373  pub contents: String,
374  /// https://sass-lang.com/documentation/js-api/interfaces/ImporterResult#sourceMapUrl
375  pub source_map_url: Option<Url>,
376  /// https://sass-lang.com/documentation/js-api/interfaces/ImporterResult#syntax
377  pub syntax: Syntax,
378}
379
380/// https://sass-lang.com/documentation/js-api/interfaces/CompileResult
381#[derive(Debug, Clone)]
382pub struct CompileResult {
383  /// https://sass-lang.com/documentation/js-api/interfaces/CompileResult#css
384  pub css: String,
385  /// https://sass-lang.com/documentation/js-api/interfaces/CompileResult#loadedUrls
386  pub loaded_urls: Vec<Url>,
387  /// https://sass-lang.com/documentation/js-api/interfaces/CompileResult#sourceMap
388  pub source_map: Option<String>,
389}
390
391impl TryFrom<CompileResponse> for CompileResult {
392  type Error = Exception;
393
394  fn try_from(response: CompileResponse) -> Result<Self> {
395    let res = response.result.unwrap();
396    match res {
397      compile_response::Result::Success(success) => Ok(success.into()),
398      compile_response::Result::Failure(failure) => {
399        Err(Exception::from(failure))
400      }
401    }
402  }
403}
404
405impl From<CompileSuccess> for CompileResult {
406  fn from(s: CompileSuccess) -> Self {
407    Self {
408      css: s.css,
409      loaded_urls: s
410        .loaded_urls
411        .iter()
412        .map(|url| Url::parse(url).unwrap())
413        .collect(),
414      source_map: if s.source_map.is_empty() {
415        None
416      } else {
417        Some(s.source_map)
418      },
419    }
420  }
421}