use std::{
fmt::Debug,
path::{Path, PathBuf},
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::{
protocol::{
self,
outbound_message::{
compile_response::{self, CompileSuccess},
CompileResponse,
},
},
Exception, Result, Url,
};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug)]
pub struct Options {
pub alert_ascii: bool,
pub alert_color: Option<bool>,
#[cfg_attr(feature = "serde", serde(skip))]
pub importers: Vec<SassImporter>,
pub load_paths: Vec<PathBuf>,
#[cfg_attr(feature = "serde", serde(skip))]
pub logger: Option<BoxLogger>,
pub quiet_deps: bool,
pub source_map: bool,
pub source_map_include_sources: bool,
pub style: OutputStyle,
pub verbose: bool,
pub charset: bool,
}
impl Default for Options {
fn default() -> Self {
Self {
alert_ascii: false,
alert_color: None,
load_paths: Vec::new(),
importers: Vec::new(),
logger: None,
quiet_deps: false,
source_map: false,
source_map_include_sources: false,
style: OutputStyle::default(),
verbose: false,
charset: true,
}
}
}
#[derive(Debug, Default)]
pub struct OptionsBuilder {
options: Options,
}
impl OptionsBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn build(self) -> Options {
self.options
}
pub fn alert_ascii(mut self, arg: impl Into<bool>) -> Self {
self.options.alert_ascii = arg.into();
self
}
pub fn alert_color(mut self, arg: impl Into<bool>) -> Self {
self.options.alert_color = Some(arg.into());
self
}
pub fn load_paths<P: AsRef<Path>>(mut self, arg: impl AsRef<[P]>) -> Self {
self.options.load_paths =
arg.as_ref().iter().map(|p| p.as_ref().to_owned()).collect();
self
}
pub fn load_path(mut self, arg: impl AsRef<Path>) -> Self {
self.options.load_paths.push(arg.as_ref().to_owned());
self
}
pub fn quiet_deps(mut self, arg: impl Into<bool>) -> Self {
self.options.quiet_deps = arg.into();
self
}
pub fn source_map(mut self, arg: impl Into<bool>) -> Self {
self.options.source_map = arg.into();
self
}
pub fn source_map_include_sources(mut self, arg: impl Into<bool>) -> Self {
self.options.source_map_include_sources = arg.into();
self
}
pub fn style(mut self, arg: impl Into<OutputStyle>) -> Self {
self.options.style = arg.into();
self
}
pub fn verbose(mut self, arg: impl Into<bool>) -> Self {
self.options.verbose = arg.into();
self
}
pub fn charset(mut self, arg: impl Into<bool>) -> Self {
self.options.charset = arg.into();
self
}
pub fn logger<L: 'static + Logger>(mut self, arg: L) -> Self {
self.options.logger = Some(Box::new(arg));
self
}
pub fn sass_importer(mut self, arg: impl Into<SassImporter>) -> Self {
self.options.importers.push(arg.into());
self
}
pub fn sass_importers(
mut self,
arg: impl IntoIterator<Item = impl Into<SassImporter>>,
) -> Self {
self.options.importers = arg.into_iter().map(|i| i.into()).collect();
self
}
pub fn importer<I: 'static + Importer>(mut self, arg: I) -> Self {
self
.options
.importers
.push(SassImporter::Importer(Box::new(arg) as Box<dyn Importer>));
self
}
pub fn file_importer<I: 'static + FileImporter>(mut self, arg: I) -> Self {
self.options.importers.push(SassImporter::FileImporter(
Box::new(arg) as Box<dyn FileImporter>
));
self
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Default)]
pub struct StringOptions {
pub common: Options,
#[cfg_attr(feature = "serde", serde(skip))]
pub input_importer: Option<SassImporter>,
pub syntax: Syntax,
pub url: Option<Url>,
}
#[derive(Debug, Default)]
pub struct StringOptionsBuilder {
options: Options,
input_importer: Option<SassImporter>,
syntax: Syntax,
url: Option<Url>,
}
impl StringOptionsBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn build(self) -> StringOptions {
StringOptions {
common: self.options,
input_importer: self.input_importer,
syntax: self.syntax,
url: self.url,
}
}
pub fn input_sass_importer(mut self, arg: impl Into<SassImporter>) -> Self {
self.input_importer = Some(arg.into());
self
}
pub fn input_importer<I: 'static + Importer>(mut self, arg: I) -> Self {
self.input_importer = Some(SassImporter::Importer(Box::new(arg)));
self
}
pub fn input_file_importer<I: 'static + FileImporter>(
mut self,
arg: I,
) -> Self {
self.input_importer = Some(SassImporter::FileImporter(Box::new(arg)));
self
}
pub fn syntax(mut self, arg: impl Into<Syntax>) -> Self {
self.syntax = arg.into();
self
}
pub fn url(mut self, arg: impl Into<Url>) -> Self {
self.url = Some(arg.into());
self
}
pub fn alert_ascii(mut self, arg: impl Into<bool>) -> Self {
self.options.alert_ascii = arg.into();
self
}
pub fn alert_color(mut self, arg: impl Into<bool>) -> Self {
self.options.alert_color = Some(arg.into());
self
}
pub fn load_paths<P: AsRef<Path>>(mut self, arg: impl AsRef<[P]>) -> Self {
self.options.load_paths =
arg.as_ref().iter().map(|p| p.as_ref().to_owned()).collect();
self
}
pub fn load_path(mut self, arg: impl AsRef<Path>) -> Self {
self.options.load_paths.push(arg.as_ref().to_owned());
self
}
pub fn quiet_deps(mut self, arg: impl Into<bool>) -> Self {
self.options.quiet_deps = arg.into();
self
}
pub fn source_map(mut self, arg: impl Into<bool>) -> Self {
self.options.source_map = arg.into();
self
}
pub fn source_map_include_sources(mut self, arg: impl Into<bool>) -> Self {
self.options.source_map_include_sources = arg.into();
self
}
pub fn style(mut self, arg: impl Into<OutputStyle>) -> Self {
self.options.style = arg.into();
self
}
pub fn verbose(mut self, arg: impl Into<bool>) -> Self {
self.options.verbose = arg.into();
self
}
pub fn charset(mut self, arg: impl Into<bool>) -> Self {
self.options.charset = arg.into();
self
}
pub fn logger<L: 'static + Logger>(mut self, arg: L) -> Self {
self.options.logger = Some(Box::new(arg));
self
}
pub fn sass_importer(mut self, arg: impl Into<SassImporter>) -> Self {
self.options.importers.push(arg.into());
self
}
pub fn sass_importers(
mut self,
arg: impl IntoIterator<Item = impl Into<SassImporter>>,
) -> Self {
self.options.importers = arg.into_iter().map(|i| i.into()).collect();
self
}
pub fn importer<I: 'static + Importer>(mut self, arg: I) -> Self {
self
.options
.importers
.push(SassImporter::Importer(Box::new(arg)));
self
}
pub fn file_importer<I: 'static + FileImporter>(mut self, arg: I) -> Self {
self
.options
.importers
.push(SassImporter::FileImporter(Box::new(arg)));
self
}
}
pub type BoxLogger = Box<dyn Logger>;
pub trait Logger: Debug + Send + Sync {
fn warn(&self, _message: &str, options: &LoggerWarnOptions) {
eprintln!("{}", options.formatted);
}
fn debug(&self, _message: &str, options: &LoggerDebugOptions) {
eprintln!("{}", options.formatted);
}
}
pub struct LoggerWarnOptions {
pub deprecation: bool,
pub span: Option<SourceSpan>,
pub stack: Option<String>,
pub(crate) formatted: String,
}
pub struct LoggerDebugOptions {
pub span: Option<SourceSpan>,
pub(crate) formatted: String,
}
#[derive(Debug)]
pub enum SassImporter {
Importer(BoxImporter),
FileImporter(BoxFileImporter),
}
pub type BoxImporter = Box<dyn Importer>;
pub type BoxFileImporter = Box<dyn FileImporter>;
pub trait Importer: Debug + Send + Sync {
fn canonicalize(
&self,
url: &str,
options: &ImporterOptions,
) -> Result<Option<Url>>;
fn load(&self, canonical_url: &Url) -> Result<Option<ImporterResult>>;
}
pub struct ImporterOptions {
pub from_import: bool,
}
pub trait FileImporter: Debug + Send + Sync {
fn find_file_url(
&self,
url: &str,
options: &ImporterOptions,
) -> Result<Option<Url>>;
}
pub struct ImporterResult {
pub contents: String,
pub source_map_url: Option<Url>,
pub syntax: Syntax,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct CompileResult {
pub css: String,
pub loaded_urls: Vec<Url>,
pub source_map: Option<String>,
}
impl TryFrom<CompileResponse> for CompileResult {
type Error = Box<Exception>;
fn try_from(response: CompileResponse) -> Result<Self> {
let res = response.result.unwrap();
match res {
compile_response::Result::Success(success) => Ok(success.into()),
compile_response::Result::Failure(failure) => {
Err(Exception::from(failure).into())
}
}
}
}
impl From<CompileSuccess> for CompileResult {
fn from(s: CompileSuccess) -> Self {
Self {
css: s.css,
loaded_urls: s
.loaded_urls
.iter()
.map(|url| Url::parse(url).unwrap())
.collect(),
source_map: if s.source_map.is_empty() {
None
} else {
Some(s.source_map)
},
}
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy)]
pub enum OutputStyle {
Expanded,
Compressed,
}
impl Default for OutputStyle {
fn default() -> Self {
Self::Expanded
}
}
impl From<OutputStyle> for protocol::OutputStyle {
fn from(o: OutputStyle) -> Self {
match o {
OutputStyle::Expanded => Self::Expanded,
OutputStyle::Compressed => Self::Compressed,
}
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy)]
pub enum Syntax {
Scss,
Indented,
Css,
}
impl Default for Syntax {
fn default() -> Self {
Self::Scss
}
}
impl From<Syntax> for protocol::Syntax {
fn from(s: Syntax) -> Self {
match s {
Syntax::Scss => Self::Scss,
Syntax::Indented => Self::Indented,
Syntax::Css => Self::Css,
}
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct SourceSpan {
pub context: Option<String>,
pub end: SourceLocation,
pub start: SourceLocation,
pub url: Option<Url>,
pub text: String,
}
impl From<protocol::SourceSpan> for SourceSpan {
fn from(span: protocol::SourceSpan) -> Self {
let start = span.start.unwrap();
Self {
context: if span.context.is_empty() {
None
} else {
Some(span.context)
},
end: span.end.unwrap_or_else(|| start.clone()).into(),
start: start.into(),
url: if span.url.is_empty() {
None
} else {
Some(Url::parse(&span.url).unwrap())
},
text: span.text,
}
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct SourceLocation {
pub offset: usize,
pub line: usize,
pub column: usize,
}
impl From<protocol::source_span::SourceLocation> for SourceLocation {
fn from(location: protocol::source_span::SourceLocation) -> Self {
Self {
offset: location.offset as usize,
line: location.line as usize,
column: location.column as usize,
}
}
}