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#[cfg(target_family = "windows")]
24pub const PATH_DELIMITER: &str = ";";
25#[cfg(target_family = "unix")]
27pub const PATH_DELIMITER: &str = ":";
28
29#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
31#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
32pub enum IndentType {
33 Space,
35 Tab,
37}
38
39impl Default for IndentType {
40 fn default() -> Self {
41 Self::Space
42 }
43}
44
45#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
48pub enum LineFeed {
49 CR,
51 CRLF,
53 LF,
55 LFCR,
57}
58
59impl Default for LineFeed {
60 fn default() -> Self {
61 Self::LF
62 }
63}
64
65#[derive(Debug, Clone)]
67pub struct LegacyPluginThisOptionsResult {
68 pub stats: LegacyPluginThisOptionsResultStats,
70}
71
72#[derive(Debug, Clone)]
74pub struct LegacyPluginThisOptionsResultStats {
75 pub start: SystemTime,
78 pub entry: String,
80}
81
82#[derive(Debug, Clone)]
84pub struct LegacyPluginThisOptions {
85 pub file: Option<PathBuf>,
87 pub data: Option<String>,
89 pub include_paths: String,
93 pub precision: u8,
95 pub style: u8,
97 pub indent_type: IndentType,
99 pub indent_width: usize,
101 pub linefeed: LineFeed,
103 pub result: LegacyPluginThisOptionsResult,
105}
106
107#[derive(Debug, Clone)]
109pub struct LegacyPluginThis {
110 pub options: LegacyPluginThisOptions,
112}
113
114impl LegacyPluginThis {
115 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
150pub struct LegacyImporterThis {
152 pub options: LegacyPluginThisOptions,
154 pub from_import: bool,
156}
157
158pub enum LegacyImporterResult {
160 File(PathBuf),
163 Contents {
166 contents: String,
168 file: Option<PathBuf>,
170 },
171}
172
173impl LegacyImporterResult {
174 pub fn file(path: impl Into<PathBuf>) -> Self {
176 Self::File(path.into())
177 }
178
179 pub fn contents(contents: impl Into<String>) -> Self {
181 Self::Contents {
182 contents: contents.into(),
183 file: None,
184 }
185 }
186
187 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#[derive(Debug, Default)]
198pub struct LegacyOptionsBuilder {
199 options: LegacyOptions,
200}
201
202impl LegacyOptionsBuilder {
203 pub fn new() -> Self {
205 Self::default()
206 }
207
208 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 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 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 pub fn indent_type(mut self, arg: impl Into<IndentType>) -> Self {
231 self.options.indent_type = arg.into();
232 self
233 }
234
235 pub fn indent_width(mut self, arg: impl Into<usize>) -> Self {
237 self.options.indent_width = arg.into();
238 self
239 }
240
241 pub fn linefeed(mut self, arg: impl Into<LineFeed>) -> Self {
243 self.options.linefeed = arg.into();
244 self
245 }
246
247 pub fn output_style(mut self, arg: impl Into<OutputStyle>) -> Self {
249 self.options.output_style = arg.into();
250 self
251 }
252
253 pub fn source_map(mut self, arg: impl Into<bool>) -> Self {
255 self.options.source_map = arg.into();
256 self
257 }
258
259 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 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 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 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 pub fn importer<I: 'static + LegacyImporter>(self, arg: I) -> Self {
296 self.sass_importer(arg)
297 }
298
299 pub fn charset(mut self, arg: impl Into<bool>) -> Self {
301 self.options.charset = arg.into();
302 self
303 }
304
305 pub fn quiet_deps(mut self, arg: impl Into<bool>) -> Self {
307 self.options.quiet_deps = arg.into();
308 self
309 }
310
311 pub fn verbose(mut self, arg: impl Into<bool>) -> Self {
313 self.options.verbose = arg.into();
314 self
315 }
316
317 pub fn logger<L: 'static + Logger>(mut self, arg: L) -> Self {
319 self.options.logger = Some(Box::new(arg));
320 self
321 }
322
323 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 pub fn data(mut self, arg: impl Into<String>) -> Self {
331 self.options.data = Some(arg.into());
332 self
333 }
334
335 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#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
344#[derive(Debug)]
345pub struct LegacyOptions {
346 pub include_paths: Vec<PathBuf>,
348 pub indent_type: IndentType,
350 pub indent_width: usize,
352 pub linefeed: LineFeed,
354 pub output_style: OutputStyle,
356 pub source_map: bool,
358 pub source_map_contents: bool,
360 #[cfg_attr(feature = "serde", serde(skip))]
362 pub importers: Option<Vec<BoxLegacyImporter>>,
363 pub charset: bool,
365 pub quiet_deps: bool,
367 pub verbose: bool,
369 #[cfg_attr(feature = "serde", serde(skip))]
371 pub logger: Option<BoxLogger>,
372 pub file: Option<PathBuf>,
374 pub data: Option<String>,
376 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 ..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#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
456#[derive(Debug, Clone)]
457pub struct LegacyResult {
458 pub css: Vec<u8>,
460 pub map: Option<Vec<u8>>,
462 pub stats: LegacyResultStats,
464}
465
466#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
468#[derive(Debug, Clone)]
469pub struct LegacyResultStats {
470 pub entry: String,
473 pub start: SystemTime,
476 pub end: SystemTime,
479 pub duration: Duration,
482 pub included_files: Vec<String>,
487}
488
489impl LegacyResult {
490 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}