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#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
24#[derive(Debug)]
25pub struct Options {
26 pub alert_ascii: bool,
28 pub alert_color: Option<bool>,
30 #[cfg_attr(feature = "serde", serde(skip))]
32 pub importers: Vec<SassImporter>,
33 pub load_paths: Vec<PathBuf>,
35 #[cfg_attr(feature = "serde", serde(skip))]
37 pub logger: Option<BoxLogger>,
38 pub quiet_deps: bool,
40 pub source_map: bool,
42 pub source_map_include_sources: bool,
44 pub style: OutputStyle,
46 pub verbose: bool,
48 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#[derive(Debug, Default)]
72pub struct OptionsBuilder {
73 options: Options,
74}
75
76impl OptionsBuilder {
77 pub fn new() -> Self {
79 Self::default()
80 }
81
82 pub fn build(self) -> Options {
84 self.options
85 }
86
87 pub fn alert_ascii(mut self, arg: impl Into<bool>) -> Self {
89 self.options.alert_ascii = arg.into();
90 self
91 }
92
93 pub fn alert_color(mut self, arg: impl Into<bool>) -> Self {
95 self.options.alert_color = Some(arg.into());
96 self
97 }
98
99 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 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 pub fn quiet_deps(mut self, arg: impl Into<bool>) -> Self {
114 self.options.quiet_deps = arg.into();
115 self
116 }
117
118 pub fn source_map(mut self, arg: impl Into<bool>) -> Self {
120 self.options.source_map = arg.into();
121 self
122 }
123
124 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 pub fn style(mut self, arg: impl Into<OutputStyle>) -> Self {
132 self.options.style = arg.into();
133 self
134 }
135
136 pub fn verbose(mut self, arg: impl Into<bool>) -> Self {
138 self.options.verbose = arg.into();
139 self
140 }
141
142 pub fn charset(mut self, arg: impl Into<bool>) -> Self {
144 self.options.charset = arg.into();
145 self
146 }
147
148 pub fn logger<L: 'static + Logger>(mut self, arg: L) -> Self {
150 self.options.logger = Some(Box::new(arg));
151 self
152 }
153
154 pub fn sass_importer(mut self, arg: impl Into<SassImporter>) -> Self {
156 self.options.importers.push(arg.into());
157 self
158 }
159
160 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 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 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#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
191#[derive(Debug, Default)]
192pub struct StringOptions {
193 pub common: Options,
195 #[cfg_attr(feature = "serde", serde(skip))]
197 pub input_importer: Option<SassImporter>,
198 pub syntax: Syntax,
200 pub url: Option<Url>,
202}
203
204#[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 pub fn new() -> Self {
216 Self::default()
217 }
218
219 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 pub fn input_sass_importer(mut self, arg: impl Into<SassImporter>) -> Self {
231 self.input_importer = Some(arg.into());
232 self
233 }
234
235 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 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 pub fn syntax(mut self, arg: impl Into<Syntax>) -> Self {
252 self.syntax = arg.into();
253 self
254 }
255
256 pub fn url(mut self, arg: impl Into<Url>) -> Self {
258 self.url = Some(arg.into());
259 self
260 }
261
262 pub fn alert_ascii(mut self, arg: impl Into<bool>) -> Self {
264 self.options.alert_ascii = arg.into();
265 self
266 }
267
268 pub fn alert_color(mut self, arg: impl Into<bool>) -> Self {
270 self.options.alert_color = Some(arg.into());
271 self
272 }
273
274 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 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 pub fn quiet_deps(mut self, arg: impl Into<bool>) -> Self {
289 self.options.quiet_deps = arg.into();
290 self
291 }
292
293 pub fn source_map(mut self, arg: impl Into<bool>) -> Self {
295 self.options.source_map = arg.into();
296 self
297 }
298
299 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 pub fn style(mut self, arg: impl Into<OutputStyle>) -> Self {
307 self.options.style = 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 charset(mut self, arg: impl Into<bool>) -> Self {
319 self.options.charset = arg.into();
320 self
321 }
322
323 pub fn logger<L: 'static + Logger>(mut self, arg: L) -> Self {
325 self.options.logger = Some(Box::new(arg));
326 self
327 }
328
329 pub fn sass_importer(mut self, arg: impl Into<SassImporter>) -> Self {
331 self.options.importers.push(arg.into());
332 self
333 }
334
335 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 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 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
363pub type BoxLogger = Box<dyn Logger>;
365
366pub trait Logger: Debug + Send + Sync {
368 fn warn(&self, _message: &str, options: &LoggerWarnOptions) {
370 eprintln!("{}", options.formatted);
371 }
372
373 fn debug(&self, _message: &str, options: &LoggerDebugOptions) {
375 eprintln!("{}", options.formatted);
376 }
377}
378
379pub struct LoggerWarnOptions {
383 pub deprecation: bool,
385 pub span: Option<SourceSpan>,
387 pub stack: Option<String>,
389 pub(crate) formatted: String,
390}
391
392pub struct LoggerDebugOptions {
396 pub span: Option<SourceSpan>,
398 pub(crate) formatted: String,
399}
400
401#[derive(Debug)]
403pub enum SassImporter {
404 Importer(BoxImporter),
406 FileImporter(BoxFileImporter),
408}
409
410pub type BoxImporter = Box<dyn Importer>;
412
413pub type BoxFileImporter = Box<dyn FileImporter>;
415
416pub trait Importer: Debug + Send + Sync {
418 fn canonicalize(
420 &self,
421 url: &str,
422 options: &ImporterOptions,
423 ) -> Result<Option<Url>>;
424
425 fn load(&self, canonical_url: &Url) -> Result<Option<ImporterResult>>;
427}
428
429pub struct ImporterOptions {
433 pub from_import: bool,
436}
437
438pub trait FileImporter: Debug + Send + Sync {
440 fn find_file_url(
442 &self,
443 url: &str,
444 options: &ImporterOptions,
445 ) -> Result<Option<Url>>;
446}
447
448pub struct ImporterResult {
450 pub contents: String,
452 pub source_map_url: Option<Url>,
454 pub syntax: Syntax,
456}
457
458#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
460#[derive(Debug, Clone)]
461pub struct CompileResult {
462 pub css: String,
464 pub loaded_urls: Vec<Url>,
466 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#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
504#[derive(Debug, Clone, Copy)]
505pub enum OutputStyle {
506 Expanded,
508 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#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
529#[derive(Debug, Clone, Copy)]
530pub enum Syntax {
531 Scss,
533 Indented,
535 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#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
557#[derive(Debug, Clone)]
558pub struct SourceSpan {
559 pub context: Option<String>,
561 pub end: SourceLocation,
563 pub start: SourceLocation,
565 pub url: Option<Url>,
567 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}