use super::{Builtin, GlobalFunctionMap};
use num_traits::One;
use crate::{
args::CallArgs,
color::Color,
common::QuoteKind,
error::SassResult,
parse::Parser,
unit::Unit,
value::{Number, Value},
};
#[allow(clippy::cognitive_complexity)]
fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
if args.is_empty() {
return Err(("Missing argument $channels.", args.span()).into());
}
if args.len() == 1 {
let mut channels = match args.get_err(0, "channels")? {
Value::List(v, ..) => v,
_ => return Err(("Missing argument $channels.", args.span()).into()),
};
if channels.len() > 3 {
return Err((
format!(
"Only 3 elements allowed, but {} were passed.",
channels.len()
),
args.span(),
)
.into());
}
let blue = match channels.pop() {
Some(Value::Dimension(Some(n), Unit::None, _)) => n,
Some(Value::Dimension(Some(n), Unit::Percent, _)) => {
(n / Number::from(100)) * Number::from(255)
}
Some(Value::Dimension(None, ..)) => todo!(),
Some(v) if v.is_special_function() => {
let green = channels.pop().unwrap();
let red = channels.pop().unwrap();
return Ok(Value::String(
format!(
"{}({}, {}, {})",
name,
red.to_css_string(args.span())?,
green.to_css_string(args.span())?,
v.to_css_string(args.span())?
),
QuoteKind::None,
));
}
Some(v) => {
return Err((
format!("$blue: {} is not a number.", v.inspect(args.span())?),
args.span(),
)
.into())
}
None => return Err(("Missing element $blue.", args.span()).into()),
};
let green = match channels.pop() {
Some(Value::Dimension(Some(n), Unit::None, _)) => n,
Some(Value::Dimension(Some(n), Unit::Percent, _)) => {
(n / Number::from(100)) * Number::from(255)
}
Some(Value::Dimension(None, ..)) => todo!(),
Some(v) if v.is_special_function() => {
let string = match channels.pop() {
Some(red) => format!(
"{}({}, {}, {})",
name,
red.to_css_string(args.span())?,
v.to_css_string(args.span())?,
blue
),
None => format!("{}({} {})", name, v.to_css_string(args.span())?, blue),
};
return Ok(Value::String(string, QuoteKind::None));
}
Some(v) => {
return Err((
format!("$green: {} is not a number.", v.inspect(args.span())?),
args.span(),
)
.into())
}
None => return Err(("Missing element $green.", args.span()).into()),
};
let red = match channels.pop() {
Some(Value::Dimension(Some(n), Unit::None, _)) => n,
Some(Value::Dimension(Some(n), Unit::Percent, _)) => {
(n / Number::from(100)) * Number::from(255)
}
Some(Value::Dimension(None, ..)) => todo!(),
Some(v) if v.is_special_function() => {
return Ok(Value::String(
format!(
"{}({}, {}, {})",
name,
v.to_css_string(args.span())?,
green,
blue
),
QuoteKind::None,
));
}
Some(v) => {
return Err((
format!("$red: {} is not a number.", v.inspect(args.span())?),
args.span(),
)
.into())
}
None => return Err(("Missing element $red.", args.span()).into()),
};
let color = Color::from_rgba(red, green, blue, Number::one());
Ok(Value::Color(Box::new(color)))
} else if args.len() == 2 {
let color = match args.get_err(0, "color")? {
Value::Color(c) => c,
v if v.is_special_function() => {
let alpha = args.get_err(1, "alpha")?;
return Ok(Value::String(
format!(
"{}({}, {})",
name,
v.to_css_string(args.span())?,
alpha.to_css_string(args.span())?
),
QuoteKind::None,
));
}
v => {
return Err((
format!("$color: {} is not a color.", v.inspect(args.span())?),
args.span(),
)
.into())
}
};
let alpha = match args.get_err(1, "alpha")? {
Value::Dimension(Some(n), Unit::None, _) => n,
Value::Dimension(Some(n), Unit::Percent, _) => n / Number::from(100),
Value::Dimension(None, ..) => todo!(),
v @ Value::Dimension(..) => {
return Err((
format!(
"$alpha: Expected {} to have no units or \"%\".",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
v if v.is_special_function() => {
return Ok(Value::String(
format!(
"{}({}, {}, {}, {})",
name,
color.red(),
color.green(),
color.blue(),
v.to_css_string(args.span())?
),
QuoteKind::None,
));
}
v => {
return Err((
format!("$alpha: {} is not a number.", v.inspect(args.span())?),
args.span(),
)
.into())
}
};
Ok(Value::Color(Box::new(color.with_alpha(alpha))))
} else {
let red = match args.get_err(0, "red")? {
Value::Dimension(Some(n), Unit::None, _) => n,
Value::Dimension(Some(n), Unit::Percent, _) => {
(n / Number::from(100)) * Number::from(255)
}
Value::Dimension(None, ..) => todo!(),
v @ Value::Dimension(..) => {
return Err((
format!(
"$red: Expected {} to have no units or \"%\".",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
v if v.is_special_function() => {
let green = args.get_err(1, "green")?;
let blue = args.get_err(2, "blue")?;
let mut string = format!(
"{}({}, {}, {}",
name,
v.to_css_string(args.span())?,
green.to_css_string(args.span())?,
blue.to_css_string(args.span())?
);
if !args.is_empty() {
string.push_str(", ");
string.push_str(&args.get_err(3, "alpha")?.to_css_string(args.span())?);
}
string.push(')');
return Ok(Value::String(string, QuoteKind::None));
}
v => {
return Err((
format!("$red: {} is not a number.", v.inspect(args.span())?),
args.span(),
)
.into())
}
};
let green = match args.get_err(1, "green")? {
Value::Dimension(Some(n), Unit::None, _) => n,
Value::Dimension(Some(n), Unit::Percent, _) => {
(n / Number::from(100)) * Number::from(255)
}
Value::Dimension(None, ..) => todo!(),
v @ Value::Dimension(..) => {
return Err((
format!(
"$green: Expected {} to have no units or \"%\".",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
v if v.is_special_function() => {
let blue = args.get_err(2, "blue")?;
let mut string = format!(
"{}({}, {}, {}",
name,
red,
v.to_css_string(args.span())?,
blue.to_css_string(args.span())?
);
if !args.is_empty() {
string.push_str(", ");
string.push_str(&args.get_err(3, "alpha")?.to_css_string(args.span())?);
}
string.push(')');
return Ok(Value::String(string, QuoteKind::None));
}
v => {
return Err((
format!("$green: {} is not a number.", v.inspect(args.span())?),
args.span(),
)
.into())
}
};
let blue = match args.get_err(2, "blue")? {
Value::Dimension(Some(n), Unit::None, _) => n,
Value::Dimension(Some(n), Unit::Percent, _) => {
(n / Number::from(100)) * Number::from(255)
}
Value::Dimension(None, ..) => todo!(),
v @ Value::Dimension(..) => {
return Err((
format!(
"$blue: Expected {} to have no units or \"%\".",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
v if v.is_special_function() => {
let mut string = format!(
"{}({}, {}, {}",
name,
red,
green,
v.to_css_string(args.span())?
);
if !args.is_empty() {
string.push_str(", ");
string.push_str(&args.get_err(3, "alpha")?.to_css_string(args.span())?);
}
string.push(')');
return Ok(Value::String(string, QuoteKind::None));
}
v => {
return Err((
format!("$blue: {} is not a number.", v.inspect(args.span())?),
args.span(),
)
.into())
}
};
let alpha = match args.default_arg(
3,
"alpha",
Value::Dimension(Some(Number::one()), Unit::None, true),
)? {
Value::Dimension(Some(n), Unit::None, _) => n,
Value::Dimension(Some(n), Unit::Percent, _) => n / Number::from(100),
Value::Dimension(None, ..) => todo!(),
v @ Value::Dimension(..) => {
return Err((
format!(
"$alpha: Expected {} to have no units or \"%\".",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
v if v.is_special_function() => {
let string = format!(
"{}({}, {}, {}, {})",
name,
red,
green,
blue,
v.to_css_string(args.span())?
);
return Ok(Value::String(string, QuoteKind::None));
}
v => {
return Err((
format!("$alpha: {} is not a number.", v.inspect(args.span())?),
args.span(),
)
.into())
}
};
Ok(Value::Color(Box::new(Color::from_rgba(
red, green, blue, alpha,
))))
}
}
pub(crate) fn rgb(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
inner_rgb("rgb", args, parser)
}
pub(crate) fn rgba(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
inner_rgb("rgba", args, parser)
}
pub(crate) fn red(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?;
match args.get_err(0, "color")? {
Value::Color(c) => Ok(Value::Dimension(Some(c.red()), Unit::None, true)),
v => Err((
format!("$color: {} is not a color.", v.inspect(args.span())?),
args.span(),
)
.into()),
}
}
pub(crate) fn green(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?;
match args.get_err(0, "color")? {
Value::Color(c) => Ok(Value::Dimension(Some(c.green()), Unit::None, true)),
v => Err((
format!("$color: {} is not a color.", v.inspect(args.span())?),
args.span(),
)
.into()),
}
}
pub(crate) fn blue(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?;
match args.get_err(0, "color")? {
Value::Color(c) => Ok(Value::Dimension(Some(c.blue()), Unit::None, true)),
v => Err((
format!("$color: {} is not a color.", v.inspect(args.span())?),
args.span(),
)
.into()),
}
}
pub(crate) fn mix(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(3)?;
let color1 = match args.get_err(0, "color1")? {
Value::Color(c) => c,
v => {
return Err((
format!("$color1: {} is not a color.", v.inspect(args.span())?),
args.span(),
)
.into())
}
};
let color2 = match args.get_err(1, "color2")? {
Value::Color(c) => c,
v => {
return Err((
format!("$color2: {} is not a color.", v.inspect(args.span())?),
args.span(),
)
.into())
}
};
let weight = match args.default_arg(
2,
"weight",
Value::Dimension(Some(Number::from(50)), Unit::None, true),
)? {
Value::Dimension(Some(n), u, _) => bound!(args, "weight", n, u, 0, 100) / Number::from(100),
Value::Dimension(None, ..) => todo!(),
v => {
return Err((
format!(
"$weight: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
Ok(Value::Color(Box::new(color1.mix(&color2, weight))))
}
pub(crate) fn declare(f: &mut GlobalFunctionMap) {
f.insert("rgb", Builtin::new(rgb));
f.insert("rgba", Builtin::new(rgba));
f.insert("red", Builtin::new(red));
f.insert("green", Builtin::new(green));
f.insert("blue", Builtin::new(blue));
f.insert("mix", Builtin::new(mix));
}