mod arena;
mod ast;
mod builtins;
mod deprecation;
mod diag;
mod emit;
mod error;
mod eval;
mod fxhash;
mod host_fn;
mod importer;
mod musl_math;
mod parser;
mod ryu;
mod sass_parser;
mod scanner;
mod selector;
mod sourcemap;
mod value;
pub use arena::{set_arena_bytes, ScopedAlloc};
pub use error::Error;
pub use host_fn::{host_value_op, HostFunction};
pub use importer::{CanonicalUrl, CanonicalizeContext, FsImporter, Importer, ImporterError, ImporterResult};
pub use sourcemap::SourceMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum OutputStyle {
#[default]
Expanded,
Compressed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Syntax {
#[default]
Scss,
Sass,
Css,
}
pub struct Options<'a> {
pub style: OutputStyle,
pub syntax: Syntax,
pub importer: Option<&'a dyn Importer>,
pub url: Option<&'a str>,
pub unicode: bool,
pub source_map_include_sources: bool,
pub(crate) functions: Vec<host_fn::HostFn>,
pub(crate) warn: Option<WarnHandler>,
pub charset: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WarnKind {
Warn,
Debug,
}
pub struct WarnEvent<'a> {
pub kind: WarnKind,
pub deprecation: bool,
pub deprecation_id: &'a str,
pub message: &'a str,
pub formatted: &'a str,
pub url: &'a str,
pub line: usize,
}
pub type WarnHandler = std::rc::Rc<dyn Fn(&WarnEvent<'_>)>;
impl Default for Options<'_> {
fn default() -> Self {
Options {
style: OutputStyle::default(),
syntax: Syntax::default(),
importer: None,
url: None,
unicode: true,
source_map_include_sources: false,
functions: Vec::new(),
warn: None,
charset: true,
}
}
}
impl<'a> Options<'a> {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_style(mut self, style: OutputStyle) -> Self {
self.style = style;
self
}
#[must_use]
pub fn with_syntax(mut self, syntax: Syntax) -> Self {
self.syntax = syntax;
self
}
#[must_use]
pub fn with_importer(mut self, importer: &'a dyn Importer) -> Self {
self.importer = Some(importer);
self
}
#[must_use]
pub fn with_url(mut self, url: &'a str) -> Self {
self.url = Some(url);
self
}
#[must_use]
pub fn with_unicode(mut self, unicode: bool) -> Self {
self.unicode = unicode;
self
}
#[must_use]
pub fn with_source_map_include_sources(mut self, include: bool) -> Self {
self.source_map_include_sources = include;
self
}
#[must_use]
pub fn with_function(mut self, signature: &str, callback: host_fn::HostFunction) -> Self {
let (name, params) = host_fn::parse_signature(signature);
self.functions.push(host_fn::HostFn {
name,
params,
callback,
});
self
}
#[must_use]
pub fn with_warn_handler(mut self, handler: WarnHandler) -> Self {
self.warn = Some(handler);
self
}
#[must_use]
pub fn with_charset(mut self, charset: bool) -> Self {
self.charset = charset;
self
}
}
pub fn compile(source: &str, options: &Options<'_>) -> Result<String, Error> {
let guard = arena::Scope::enter();
let result = compile_inner(source, options);
let outermost = arena::leave_no_reset();
let owned = result.clone();
drop(result);
if outermost {
arena::reset();
}
std::mem::forget(guard);
owned
}
#[derive(Clone, Debug)]
pub struct CompileResult {
pub css: String,
pub source_map: SourceMap,
}
pub fn compile_with_source_map(source: &str, options: &Options<'_>) -> Result<CompileResult, Error> {
let guard = arena::Scope::enter();
let result = compile_inner_sm(source, options);
let outermost = arena::leave_no_reset();
let owned = result.clone();
drop(result);
if outermost {
arena::reset();
}
std::mem::forget(guard);
owned
}
fn basename(url: &str) -> &str {
url.rsplit('/').next().unwrap_or(url)
}
fn compile_inner_sm(source: &str, options: &Options<'_>) -> Result<CompileResult, Error> {
let glyphs = if options.unicode {
diag::GlyphSet::Unicode
} else {
diag::GlyphSet::Ascii
};
let sheet = match options.syntax {
Syntax::Scss => parser::parse(source),
Syntax::Css => parser::parse_plain_css(source),
Syntax::Sass => sass_parser::parse(source),
};
let sheet = match sheet {
Ok(s) => s,
Err(mut e) => {
if let Some(url) = options.url {
if e.rendered.is_none() && e.has_position() {
let span = diag::Span {
line: e.line,
col: e.col,
length: e.length,
};
e.rendered = Some(diag::render_error(&e.message, source, url, span, glyphs));
}
}
return Err(e);
}
};
eval::validate_declarations(&sheet)?;
let entry_name = options.url.unwrap_or("stdin");
let mut ev = eval::Evaluator::new(eval::EvalOptions {
style: options.style,
importer: options.importer,
functions: &options.functions,
source,
url: entry_name,
glyphs,
warn: options.warn.as_ref(),
});
let mut out = Vec::new();
ev.eval_sheet(&sheet, &mut out)?;
let (css, body_off, collector) = emit::emit_with_map(&out, options.style, options.charset);
let mappings = collector.finalize(&css, body_off).encode();
let (sources, sources_content) = ev.source_table(entry_name, options.source_map_include_sources);
let source_map = SourceMap {
file: Some(basename(entry_name).to_string()),
sources,
sources_content,
mappings,
};
Ok(CompileResult { css, source_map })
}
fn compile_inner(source: &str, options: &Options<'_>) -> Result<String, Error> {
let glyphs_for = || {
if options.unicode {
diag::GlyphSet::Unicode
} else {
diag::GlyphSet::Ascii
}
};
let sheet = match options.syntax {
Syntax::Scss => parser::parse(source),
Syntax::Css => parser::parse_plain_css(source),
Syntax::Sass => sass_parser::parse(source),
};
let sheet = match sheet {
Ok(s) => s,
Err(mut e) => {
if let Some(url) = options.url {
if e.rendered.is_none() && e.has_position() {
let span = diag::Span {
line: e.line,
col: e.col,
length: e.length,
};
e.rendered = Some(diag::render_error(&e.message, source, url, span, glyphs_for()));
}
}
return Err(e);
}
};
eval::validate_declarations(&sheet)?;
let (diag_source, diag_url) = match options.url {
Some(url) => (source, url),
None => ("", ""),
};
let glyphs = if options.unicode {
diag::GlyphSet::Unicode
} else {
diag::GlyphSet::Ascii
};
let mut ev = eval::Evaluator::new(eval::EvalOptions {
style: options.style,
importer: options.importer,
functions: &options.functions,
source: diag_source,
url: diag_url,
glyphs,
warn: options.warn.as_ref(),
});
let mut out = Vec::new();
ev.eval_sheet(&sheet, &mut out)?;
Ok(emit::emit(&out, options.style, options.charset))
}