use crate::css;
use crate::error::Error;
use crate::sass::{Name, Value};
use crate::ScopeRef;
use std::fmt;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd)]
pub struct FormalArgs(Vec<(Name, Option<Value>)>, Option<Name>);
impl FormalArgs {
pub fn new(args: Vec<(Name, Option<Value>)>) -> FormalArgs {
FormalArgs(args, None)
}
pub fn new_va(args: Vec<(Name, Option<Value>)>) -> FormalArgs {
let mut args = args;
let va = args.pop().map(|(name, _)| name);
FormalArgs(args, va)
}
pub fn none() -> FormalArgs {
FormalArgs(vec![], None)
}
pub fn is_varargs(&self) -> bool {
self.1.is_some()
}
pub fn eval(
&self,
scope: ScopeRef,
args: css::CallArgs,
) -> Result<ScopeRef, ArgsError> {
let mut args = args;
let argscope = ScopeRef::sub(scope);
if !self.is_varargs() {
let n = self.0.len();
let m = args.len();
if m > n {
let n_p = args.positional.len();
if n_p != m && n_p > n {
return Err(ArgsError::TooManyPos(n, n_p));
} else {
return Err(ArgsError::TooMany(n, n_p));
}
}
}
let positional = args.take_positional(self.0.len());
for ((name, _default), value) in self.0.iter().zip(&positional) {
argscope.define(name.clone(), value);
}
if self.0.len() > positional.len() {
for (name, default) in &self.0[positional.len()..] {
if let Some(v) = args.named.remove(name) {
argscope.define(name.clone(), &v);
} else if let Some(default) = default {
argscope.define(
name.clone(),
&default
.do_evaluate(argscope.clone(), true)
.map_err(ArgsError::Eval)?,
);
} else {
return Err(ArgsError::Missing(name.clone()));
}
}
}
if let Some(va_name) = &self.1 {
argscope.define(
va_name.clone(),
&args.only_named(va_name).unwrap_or_else(|| args.into()),
);
} else {
args.check_no_named()?;
}
Ok(argscope)
}
}
impl fmt::Display for FormalArgs {
fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
out.write_str("(")?;
if let Some((first, rest)) = self.0.split_first() {
write!(out, "${}", first.0)?;
if let Some(default) = &first.1 {
out.write_str(": ")?;
default.inspect(out)?;
}
for (name, default) in rest {
write!(out, ", ${}", name)?;
if let Some(default) = default {
out.write_str(": ")?;
default.inspect(out)?;
}
}
}
if let Some(va) = &self.1 {
write!(out, ", ${}...", va)?;
}
out.write_str(")")
}
}
#[derive(Debug)]
pub enum ArgsError {
TooMany(usize, usize),
TooManyPos(usize, usize),
Missing(Name),
Unexpected(Name),
Eval(Error),
}
impl fmt::Display for ArgsError {
fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
match self {
ArgsError::TooManyPos(n, m) => write!(
out,
"Error: Only {} positional argument{} allowed, but {} {} passed.",
n,
if *n != 1 { "s" } else { "" },
m,
if *m != 1 { "were" } else { "was" },
),
ArgsError::TooMany(n, m) => write!(
out,
"Error: Only {} argument{} allowed, but {} {} passed.",
n,
if *n != 1 { "s" } else { "" },
m,
if *m != 1 { "were" } else { "was" },
),
ArgsError::Missing(name) => {
write!(out, "Error: Missing argument ${}.", name)
}
ArgsError::Unexpected(name) => {
write!(out, "Error: No argument named ${}.", name)
}
ArgsError::Eval(e) => e.fmt(out),
}
}
}
impl From<ArgsError> for Error {
fn from(e: ArgsError) -> Error {
Error::S(e.to_string())
}
}