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#[derive(Debug)]
19pub struct Options {
20 pub alert_ascii: bool,
22 pub alert_color: Option<bool>,
24 pub importers: Vec<SassImporter>,
28 pub load_paths: Vec<PathBuf>,
30 pub logger: Option<SassLogger>,
32 pub quiet_deps: bool,
34 pub source_map: bool,
36 pub source_map_include_sources: bool,
38 pub style: OutputStyle,
40 pub verbose: bool,
42 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 pub input_importer: Option<SassImporter>,
168 pub syntax: Syntax,
170 pub url: Option<Url>,
172}
173
174#[derive(Debug, Default)]
175pub struct StringOptionsBuilder {
176 options: Options,
177 input_importer: Option<SassImporter>,
179 syntax: Syntax,
181 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
343pub trait Importer: Debug + Send + Sync {
345 fn canonicalize(
347 &self,
348 url: &str,
349 options: &ImporterOptions,
350 ) -> Result<Option<Url>>;
351
352 fn load(&self, canonical_url: &Url) -> Result<Option<ImporterResult>>;
354}
355
356pub struct ImporterOptions {
357 pub from_import: bool,
358}
359
360pub trait FileImporter: Debug + Send + Sync {
362 fn find_file_url(
364 &self,
365 url: &str,
366 options: &ImporterOptions,
367 ) -> Result<Option<Url>>;
368}
369
370pub struct ImporterResult {
372 pub contents: String,
374 pub source_map_url: Option<Url>,
376 pub syntax: Syntax,
378}
379
380#[derive(Debug, Clone)]
382pub struct CompileResult {
383 pub css: String,
385 pub loaded_urls: Vec<Url>,
387 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}