sass_embedded/legacy/
api.rs

1use std::{
2  env, fs,
3  path::{Path, PathBuf},
4  time::{Duration, SystemTime},
5};
6
7#[cfg(feature = "serde")]
8use serde::{Deserialize, Serialize};
9use urlencoding::decode;
10
11use crate::{
12  legacy::url_to_file_path_cross_platform, CompileResult, Options,
13  StringOptions, Syntax, Url,
14};
15pub use crate::{BoxLogger, Logger, OutputStyle};
16
17use super::{
18  BoxLegacyImporter, LegacyImporter, END_OF_LOAD_PROTOCOL,
19  LEGACY_IMPORTER_PROTOCOL,
20};
21
22/// The platform-specific file delimiter, ';' for windows.
23#[cfg(target_family = "windows")]
24pub const PATH_DELIMITER: &str = ";";
25/// The platform-specific file delimiter, ':' for unix.
26#[cfg(target_family = "unix")]
27pub const PATH_DELIMITER: &str = ":";
28
29/// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/LegacyFileOptions#indentType)
30#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
31#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
32pub enum IndentType {
33  /// Space IndentType.
34  Space,
35  /// Tab IndentType.
36  Tab,
37}
38
39impl Default for IndentType {
40  fn default() -> Self {
41    Self::Space
42  }
43}
44
45/// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/LegacyFileOptions#linefeed)
46#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
48pub enum LineFeed {
49  /// 'cr' uses U+000D CARRIAGE RETURN.
50  CR,
51  /// 'crlf' uses U+000D CARRIAGE RETURN followed by U+000A LINE FEED.
52  CRLF,
53  /// 'lf' uses U+000A LINE FEED.
54  LF,
55  /// 'lfcr' uses U+000A LINE FEED followed by U+000D CARRIAGE RETURN.
56  LFCR,
57}
58
59impl Default for LineFeed {
60  fn default() -> Self {
61    Self::LF
62  }
63}
64
65/// A partially-constructed [LegacyResult] object.
66#[derive(Debug, Clone)]
67pub struct LegacyPluginThisOptionsResult {
68  /// Partial information about the compilation in progress.
69  pub stats: LegacyPluginThisOptionsResultStats,
70}
71
72/// Partial information about the compilation in progress.
73#[derive(Debug, Clone)]
74pub struct LegacyPluginThisOptionsResultStats {
75  /// The number of milliseconds between 1 January 1970 at 00:00:00 UTC and
76  /// the time at which Sass compilation began.
77  pub start: SystemTime,
78  /// [LegacyOptions.file] if it was passed, otherwise the string `"data"`.
79  pub entry: String,
80}
81
82/// A partial representation of the options passed to [Sass::render].
83#[derive(Debug, Clone)]
84pub struct LegacyPluginThisOptions {
85  /// The value passed to [LegacyOptions.file].
86  pub file: Option<PathBuf>,
87  /// The value passed to [LegacyOptions.data].
88  pub data: Option<String>,
89  /// The value passed to [LegacyOptions.include_paths] separated by
90  /// `";"` on Windows or `":"` on other operating systems. This always
91  /// includes the current working directory as the first entry.
92  pub include_paths: String,
93  /// Always the number 10.
94  pub precision: u8,
95  /// Always the number 1.
96  pub style: u8,
97  /// The value passed to [LegacyOptions.indent_type], [IndentType::Space] otherwise.
98  pub indent_type: IndentType,
99  /// The value passed to [LegacyOptions.indent_width], or `2` otherwise.
100  pub indent_width: usize,
101  /// The value passed to [LegacyOptions.linefeed], or `"\n"` otherwise.
102  pub linefeed: LineFeed,
103  /// A partially-constructed [LegacyResult] object.
104  pub result: LegacyPluginThisOptionsResult,
105}
106
107/// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/LegacyPluginThis)
108#[derive(Debug, Clone)]
109pub struct LegacyPluginThis {
110  /// A partial representation of the options passed to [Sass::render].
111  pub options: LegacyPluginThisOptions,
112}
113
114impl LegacyPluginThis {
115  /// Creates a new [LegacyPluginThis].
116  pub fn new(options: &LegacyOptions) -> Self {
117    let mut include_paths =
118      vec![env::current_dir().unwrap().to_string_lossy().to_string()];
119    include_paths.extend(
120      options
121        .include_paths
122        .iter()
123        .map(|p| p.to_str().unwrap().to_string()),
124    );
125    Self {
126      options: LegacyPluginThisOptions {
127        file: options.file.clone(),
128        data: options.data.clone(),
129        include_paths: include_paths.join(PATH_DELIMITER),
130        precision: 10,
131        style: 1,
132        indent_type: IndentType::Space,
133        indent_width: 2,
134        linefeed: LineFeed::LF,
135        result: LegacyPluginThisOptionsResult {
136          stats: LegacyPluginThisOptionsResultStats {
137            start: SystemTime::now(),
138            entry: options
139              .file
140              .as_ref()
141              .map(|file| file.to_str().unwrap().to_string())
142              .unwrap_or_else(|| "data".to_owned()),
143          },
144        },
145      },
146    }
147  }
148}
149
150/// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/LegacyImporterThis)
151pub struct LegacyImporterThis {
152  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/LegacyImporterThis#options)
153  pub options: LegacyPluginThisOptions,
154  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/LegacyImporterThis#fromImporter)
155  pub from_import: bool,
156}
157
158/// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/modules#LegacyImporterResult)
159pub enum LegacyImporterResult {
160  /// An object with the key file whose value is a path on disk. This causes
161  /// Sass to load that file as though it had been imported directly.
162  File(PathBuf),
163  /// An object with the key contents whose value is the contents of a stylesheet
164  /// (in SCSS syntax). This causes Sass to load that stylesheet’s contents.
165  Contents {
166    /// The contents of the stylesheet.
167    contents: String,
168    /// The path to the stylesheet.
169    file: Option<PathBuf>,
170  },
171}
172
173impl LegacyImporterResult {
174  /// Creates a new [LegacyImporterResult] from a [PathBuf].
175  pub fn file(path: impl Into<PathBuf>) -> Self {
176    Self::File(path.into())
177  }
178
179  /// Creates a new [LegacyImporterResult] from contents [String].
180  pub fn contents(contents: impl Into<String>) -> Self {
181    Self::Contents {
182      contents: contents.into(),
183      file: None,
184    }
185  }
186
187  /// Creates a new [LegacyImporterResult] from contents [String] and a [PathBuf].
188  pub fn both(contents: impl Into<String>, file: impl Into<PathBuf>) -> Self {
189    Self::Contents {
190      contents: contents.into(),
191      file: Some(file.into()),
192    }
193  }
194}
195
196/// A builder for [LegacyOptionsBuilder].
197#[derive(Debug, Default)]
198pub struct LegacyOptionsBuilder {
199  options: LegacyOptions,
200}
201
202impl LegacyOptionsBuilder {
203  /// Creates a new [LegacyOptionsBuilder].
204  pub fn new() -> Self {
205    Self::default()
206  }
207
208  /// Builds a [LegacyOptions].
209  pub fn build(self) -> LegacyOptions {
210    if self.options.data.is_none() && self.options.file.is_none() {
211      panic!("Either options.data or options.file must be set.");
212    }
213    self.options
214  }
215
216  /// Sets the [LegacyOptions]'s [include_paths] field.
217  pub fn include_paths(mut self, arg: &[impl AsRef<Path>]) -> Self {
218    self.options.include_paths =
219      arg.iter().map(|p| p.as_ref().to_owned()).collect();
220    self
221  }
222
223  /// Adds a path to the [LegacyOptions]'s [include_paths] field.
224  pub fn include_path(mut self, arg: impl AsRef<Path>) -> Self {
225    self.options.include_paths.push(arg.as_ref().to_owned());
226    self
227  }
228
229  /// Sets the [LegacyOptions]'s [indent_type] field.
230  pub fn indent_type(mut self, arg: impl Into<IndentType>) -> Self {
231    self.options.indent_type = arg.into();
232    self
233  }
234
235  /// Sets the [LegacyOptions]'s [indent_width] field.
236  pub fn indent_width(mut self, arg: impl Into<usize>) -> Self {
237    self.options.indent_width = arg.into();
238    self
239  }
240
241  /// Sets the [LegacyOptions]'s [linefeed] field.
242  pub fn linefeed(mut self, arg: impl Into<LineFeed>) -> Self {
243    self.options.linefeed = arg.into();
244    self
245  }
246
247  /// Sets the [LegacyOptions]'s [output_style] field.
248  pub fn output_style(mut self, arg: impl Into<OutputStyle>) -> Self {
249    self.options.output_style = arg.into();
250    self
251  }
252
253  /// Sets the [LegacyOptions]'s [source_map] field.
254  pub fn source_map(mut self, arg: impl Into<bool>) -> Self {
255    self.options.source_map = arg.into();
256    self
257  }
258
259  /// Sets the [LegacyOptions]'s [source_map_contents] field.
260  pub fn source_map_contents(mut self, arg: impl Into<bool>) -> Self {
261    self.options.source_map_contents = arg.into();
262    self
263  }
264
265  /// Sets the [LegacyOptions]'s [sass_importers] field with [SassLegacyImporter]s.
266  pub fn sass_importers(
267    mut self,
268    arg: impl IntoIterator<Item = impl Into<BoxLegacyImporter>>,
269  ) -> Self {
270    self.options.importers = Some(arg.into_iter().map(|i| i.into()).collect());
271    self
272  }
273
274  /// Adds a [SassLegacyImporter] to the [LegacyOptions]'s [sass_importers] field.
275  pub fn sass_importer(mut self, arg: impl Into<BoxLegacyImporter>) -> Self {
276    self.options.importers =
277      Some(if let Some(mut importers) = self.options.importers {
278        importers.push(arg.into());
279        importers
280      } else {
281        vec![arg.into()]
282      });
283    self
284  }
285
286  /// Sets the [LegacyOptions]'s [sass_importers] field with [LegacyImporter]s.
287  pub fn importers(
288    self,
289    arg: impl IntoIterator<Item = impl Into<Box<dyn LegacyImporter>>>,
290  ) -> Self {
291    self.sass_importers(arg)
292  }
293
294  /// Adds a [LegacyImporter] to the [LegacyOptions]'s [sass_importers] field.
295  pub fn importer<I: 'static + LegacyImporter>(self, arg: I) -> Self {
296    self.sass_importer(arg)
297  }
298
299  /// Sets the [LegacyOptions]'s [charset] field.
300  pub fn charset(mut self, arg: impl Into<bool>) -> Self {
301    self.options.charset = arg.into();
302    self
303  }
304
305  /// Sets the [LegacyOptions]'s [quiet_deps] field.
306  pub fn quiet_deps(mut self, arg: impl Into<bool>) -> Self {
307    self.options.quiet_deps = arg.into();
308    self
309  }
310
311  /// Sets the [LegacyOptions]'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 [LegacyOptions]'s [logger] field.
318  pub fn logger<L: 'static + Logger>(mut self, arg: L) -> Self {
319    self.options.logger = Some(Box::new(arg));
320    self
321  }
322
323  /// Sets the [LegacyOptions]'s [file] field.
324  pub fn file(mut self, arg: impl AsRef<Path>) -> Self {
325    self.options.file = Some(arg.as_ref().to_owned());
326    self
327  }
328
329  /// Sets the [LegacyOptions]'s [data] field.
330  pub fn data(mut self, arg: impl Into<String>) -> Self {
331    self.options.data = Some(arg.into());
332    self
333  }
334
335  /// Sets the [LegacyOptions]'s [indented_syntax] field.
336  pub fn indented_syntax(mut self, arg: impl Into<bool>) -> Self {
337    self.options.indented_syntax = Some(arg.into());
338    self
339  }
340}
341
342/// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/LegacyImporterThis)
343#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
344#[derive(Debug)]
345pub struct LegacyOptions {
346  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/LegacySharedOptions#includePaths)
347  pub include_paths: Vec<PathBuf>,
348  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/LegacySharedOptions#indentType)
349  pub indent_type: IndentType,
350  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/LegacySharedOptions#indentWidth)
351  pub indent_width: usize,
352  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/LegacySharedOptions#linefeed)
353  pub linefeed: LineFeed,
354  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/LegacySharedOptions#outputStyle)
355  pub output_style: OutputStyle,
356  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/LegacySharedOptions#sourceMap)
357  pub source_map: bool,
358  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/LegacySharedOptions#sourceMapContents)
359  pub source_map_contents: bool,
360  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/LegacySharedOptions#importer)
361  #[cfg_attr(feature = "serde", serde(skip))]
362  pub importers: Option<Vec<BoxLegacyImporter>>,
363  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/LegacySharedOptions#charset)
364  pub charset: bool,
365  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/LegacySharedOptions#quietDeps)
366  pub quiet_deps: bool,
367  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/LegacySharedOptions#verbose)
368  pub verbose: bool,
369  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/LegacySharedOptions#logger)
370  #[cfg_attr(feature = "serde", serde(skip))]
371  pub logger: Option<BoxLogger>,
372  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/LegacyFileOptions#file)
373  pub file: Option<PathBuf>,
374  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/LegacyStringOptions#data)
375  pub data: Option<String>,
376  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/LegacyStringOptions#indentedSyntax)
377  pub indented_syntax: Option<bool>,
378}
379
380impl Default for LegacyOptions {
381  fn default() -> Self {
382    Self {
383      indent_width: 2,
384      charset: true,
385      include_paths: Vec::new(),
386      indent_type: IndentType::Space,
387      linefeed: LineFeed::LF,
388      output_style: OutputStyle::Expanded,
389      source_map: false,
390      source_map_contents: false,
391      importers: None,
392      quiet_deps: false,
393      verbose: false,
394      logger: None,
395      file: None,
396      data: None,
397      indented_syntax: None,
398    }
399  }
400}
401
402impl From<LegacyOptions> for Options {
403  fn from(options: LegacyOptions) -> Self {
404    Self {
405      load_paths: options.include_paths,
406      logger: options.logger,
407      quiet_deps: options.quiet_deps,
408      source_map: options.source_map,
409      source_map_include_sources: options.source_map_contents,
410      style: options.output_style,
411      verbose: options.verbose,
412      charset: options.charset,
413      // importers: set manually after into modern options
414      ..Default::default()
415    }
416  }
417}
418
419impl From<LegacyOptions> for StringOptions {
420  fn from(options: LegacyOptions) -> Self {
421    let url = options
422      .file
423      .clone()
424      .map(|file| Url::from_file_path(file).unwrap())
425      .unwrap_or_else(|| Url::parse(LEGACY_IMPORTER_PROTOCOL).unwrap());
426    let syntax = options
427      .indented_syntax
428      .map(|s| if s { Syntax::Indented } else { Syntax::Scss })
429      .unwrap_or_default();
430    let options = Options::from(options);
431    Self {
432      common: options,
433      input_importer: None,
434      syntax,
435      url: Some(url),
436    }
437  }
438}
439
440impl LegacyOptions {
441  pub(crate) fn adjust_options(mut self) -> Self {
442    if let Some(file) = &self.file {
443      if self.data.is_none()
444        && (self.indented_syntax.is_some() || self.importers.is_some())
445      {
446        self.data = Some(fs::read_to_string(file).unwrap());
447        self.indented_syntax = Some(self.indented_syntax.unwrap_or_default());
448      }
449    }
450    self
451  }
452}
453
454/// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/LegacyResult)
455#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
456#[derive(Debug, Clone)]
457pub struct LegacyResult {
458  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/LegacyResult#css)
459  pub css: Vec<u8>,
460  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/LegacyResult#map)
461  pub map: Option<Vec<u8>>,
462  /// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/LegacyResult#stats)
463  pub stats: LegacyResultStats,
464}
465
466/// More information: [Sass documentation](https://sass-lang.com/documentation/js-api/interfaces/LegacyResult#stats)
467#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
468#[derive(Debug, Clone)]
469pub struct LegacyResultStats {
470  /// The absolute path of [LegacyOptions.file], or "data" if [LegacyOptions.file]
471  /// wasn't set.
472  pub entry: String,
473  /// The number of milliseconds between 1 January 1970 at 00:00:00 UTC and the time
474  /// at which Sass compilation began.
475  pub start: SystemTime,
476  /// The number of milliseconds between 1 January 1970 at 00:00:00 UTC and the time
477  /// at which Sass compilation ended.
478  pub end: SystemTime,
479  /// The number of milliseconds it took to compile the Sass file. This is always equal
480  /// to start minus end.
481  pub duration: Duration,
482  /// An array of the absolute paths of all Sass files loaded during compilation. If
483  /// a stylesheet was loaded from a LegacyImporter that returned the stylesheet’s
484  /// contents, the raw string of the @use or @import that loaded that stylesheet
485  /// included in this array.
486  pub included_files: Vec<String>,
487}
488
489impl LegacyResult {
490  /// Creates a new [LegacyResult].
491  pub fn new(entry: String, start: SystemTime, result: CompileResult) -> Self {
492    let end = SystemTime::now();
493    Self {
494      css: result.css.into_bytes(),
495      map: result.source_map.map(|map| map.into_bytes()),
496      stats: LegacyResultStats {
497        entry,
498        start,
499        end,
500        duration: end.duration_since(start).unwrap(),
501        included_files: result
502          .loaded_urls
503          .into_iter()
504          .filter(|url| format!("{}:", url.scheme()) != END_OF_LOAD_PROTOCOL)
505          .map(|url| {
506            if url.scheme() == "file" {
507              url_to_file_path_cross_platform(&url)
508                .to_string_lossy()
509                .to_string()
510            } else if format!("{}:", url.scheme()) == LEGACY_IMPORTER_PROTOCOL {
511              decode(url.path()).unwrap().to_string()
512            } else {
513              url.to_string()
514            }
515          })
516          .collect(),
517      },
518    }
519  }
520}