use super::{
CallArgs, CallError, Callable, Closure, FormalArgs, Name, ResolvedArgs,
Value,
};
use crate::css::{self, CssString, ValueToMapError};
use crate::input::{Context, Loader, Parsed, SourceKind, SourcePos};
use crate::ordermap::OrderMap;
use crate::ScopeRef;
#[derive(Clone)]
pub enum MixinDecl {
Sass(Closure),
NoBody,
LoadCss,
}
impl From<Closure> for MixinDecl {
fn from(decl: Closure) -> Self {
Self::Sass(decl)
}
}
impl MixinDecl {
pub(crate) fn get(
self,
scope: ScopeRef,
call_args: &CallArgs,
call_pos: &SourcePos,
file_context: &mut Context<impl Loader>,
) -> Result<Mixin, CallError> {
match self {
Self::Sass(decl) => {
let sel = scope.get_selectors().clone();
Ok(Mixin {
scope: decl.eval_args(
ScopeRef::sub_selectors(decl.scope.clone(), sel),
call_args.evaluate(scope)?.args,
)?,
body: Parsed::Scss(decl.body.body),
})
}
Self::NoBody => Ok(Mixin::empty(scope)),
Self::LoadCss => {
let fargs = FormalArgs::new(vec![
(name!(url), None),
(name!(with), Some(Value::Null)),
]);
let pos = SourcePos::mock_mixin(
&name!(load_css),
&fargs,
"sass:meta",
);
let argscope = fargs
.eval_call(
scope.clone(),
call_args.evaluate(scope.clone())?,
)
.map_err(|e| e.declared_at(&pos))?;
let url: CssString = argscope.get(name!(url))?;
let with = get_opt_map(&argscope, name!(with))?;
if url.value().starts_with("sass:") {
return if with.unwrap_or_default().is_empty() {
Ok(Mixin::empty(scope))
} else {
Err(CallError::msg(format!(
"Built-in module {} can't be configured.",
url.value()
)))
};
}
let source = file_context
.find_file(url.value(), SourceKind::load_css(call_pos))?
.ok_or_else(|| {
CallError::msg("Can't find stylesheet to import.")
})?;
let scope = ScopeRef::sub(scope);
if let Some(with) = with {
for (key, value) in with {
scope.define(key.into(), value)?;
}
}
file_context.unlock_loading(&source);
Ok(Mixin {
scope,
body: source.parse()?,
})
}
}
}
pub(crate) fn is_no_body(&self) -> bool {
matches!(self, Self::NoBody)
}
}
#[derive(Clone)]
pub struct Mixin {
pub scope: ScopeRef,
pub body: Parsed,
}
impl Mixin {
fn empty(scope: ScopeRef) -> Self {
Self {
scope,
body: Parsed::Css(vec![]),
}
}
pub(crate) fn define_content(
&self,
scope: &ScopeRef,
body: &Option<Callable>,
) {
self.scope.define_content(match body {
Some(body) => body.closure(scope).into(),
None => MixinDecl::NoBody,
});
}
}
fn get_opt_map(
s: &ResolvedArgs,
name: Name,
) -> Result<Option<OrderMap<CssString, css::Value>>, CallError> {
match s.get(name.clone())? {
css::Value::Null => Ok(None),
v => v.try_into().map(Some).map_err(|e| match e {
ValueToMapError::Root(err) => CallError::BadArgument(name, err),
ValueToMapError::Key(err) => {
CallError::msg(format!("${name} key: {err}"))
}
}),
}
}