move builtin fns to outer scope to reduce nesting
This commit is contained in:
parent
170759239a
commit
0aed492123
File diff suppressed because it is too large
Load Diff
@ -10,151 +10,151 @@ use crate::unit::Unit;
|
||||
use crate::value::Number;
|
||||
use crate::value::Value;
|
||||
|
||||
fn alpha(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "color") {
|
||||
Value::Color(c) => Ok(Value::Dimension(c.alpha(), Unit::None)),
|
||||
v => Err((
|
||||
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn opacity(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "color") {
|
||||
Value::Color(c) => Ok(Value::Dimension(c.alpha(), Unit::None)),
|
||||
Value::Dimension(num, unit) => Ok(Value::Ident(
|
||||
format!("opacity({}{})", num, unit),
|
||||
QuoteKind::None,
|
||||
)),
|
||||
v => Err((
|
||||
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn opacify(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let amount = match arg!(args, scope, super_selector, 1, "amount") {
|
||||
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1),
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$amount: {} is not a number.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
Ok(Value::Color(Box::new(color.fade_in(amount))))
|
||||
}
|
||||
|
||||
fn fade_in(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let amount = match arg!(args, scope, super_selector, 1, "amount") {
|
||||
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1),
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$amount: {} is not a number.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
Ok(Value::Color(Box::new(color.fade_in(amount))))
|
||||
}
|
||||
|
||||
fn transparentize(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let amount = match arg!(args, scope, super_selector, 1, "amount") {
|
||||
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1),
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$amount: {} is not a number.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
Ok(Value::Color(Box::new(color.fade_out(amount))))
|
||||
}
|
||||
|
||||
fn fade_out(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let amount = match arg!(args, scope, super_selector, 1, "amount") {
|
||||
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1),
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$amount: {} is not a number.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
Ok(Value::Color(Box::new(color.fade_out(amount))))
|
||||
}
|
||||
|
||||
pub(crate) fn register(f: &mut GlobalFunctionMap) {
|
||||
fn alpha(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "color") {
|
||||
Value::Color(c) => Ok(Value::Dimension(c.alpha(), Unit::None)),
|
||||
v => Err((
|
||||
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn opacity(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "color") {
|
||||
Value::Color(c) => Ok(Value::Dimension(c.alpha(), Unit::None)),
|
||||
Value::Dimension(num, unit) => Ok(Value::Ident(
|
||||
format!("opacity({}{})", num, unit),
|
||||
QuoteKind::None,
|
||||
)),
|
||||
v => Err((
|
||||
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn opacify(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let amount = match arg!(args, scope, super_selector, 1, "amount") {
|
||||
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1),
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$amount: {} is not a number.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
Ok(Value::Color(Box::new(color.fade_in(amount))))
|
||||
}
|
||||
|
||||
fn fade_in(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let amount = match arg!(args, scope, super_selector, 1, "amount") {
|
||||
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1),
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$amount: {} is not a number.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
Ok(Value::Color(Box::new(color.fade_in(amount))))
|
||||
}
|
||||
|
||||
fn transparentize(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let amount = match arg!(args, scope, super_selector, 1, "amount") {
|
||||
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1),
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$amount: {} is not a number.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
Ok(Value::Color(Box::new(color.fade_out(amount))))
|
||||
}
|
||||
|
||||
fn fade_out(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let amount = match arg!(args, scope, super_selector, 1, "amount") {
|
||||
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1),
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$amount: {} is not a number.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
Ok(Value::Color(Box::new(color.fade_out(amount))))
|
||||
}
|
||||
|
||||
f.insert("alpha", Builtin::new(alpha));
|
||||
f.insert("opacity", Builtin::new(opacity));
|
||||
f.insert("opacify", Builtin::new(opacify));
|
||||
|
@ -54,316 +54,302 @@ macro_rules! opt_hsl {
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn register(f: &mut GlobalFunctionMap) {
|
||||
fn change_color(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
if args.get_positional(1, scope, super_selector).is_some() {
|
||||
return Err(
|
||||
("Only one positional argument is allowed. All other arguments must be passed by name.", args.span()
|
||||
).into());
|
||||
}
|
||||
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
opt_rgba!(args, alpha, "alpha", 0, 1, scope, super_selector);
|
||||
opt_rgba!(args, red, "red", 0, 255, scope, super_selector);
|
||||
opt_rgba!(args, green, "green", 0, 255, scope, super_selector);
|
||||
opt_rgba!(args, blue, "blue", 0, 255, scope, super_selector);
|
||||
|
||||
if red.is_some() || green.is_some() || blue.is_some() {
|
||||
return Ok(Value::Color(Box::new(Color::from_rgba(
|
||||
red.unwrap_or(color.red()),
|
||||
green.unwrap_or(color.green()),
|
||||
blue.unwrap_or(color.blue()),
|
||||
alpha.unwrap_or(color.alpha()),
|
||||
))));
|
||||
}
|
||||
|
||||
let hue = match named_arg!(args, scope, super_selector, "hue" = Value::Null) {
|
||||
Value::Dimension(n, _) => Some(n),
|
||||
Value::Null => None,
|
||||
v => {
|
||||
return Err((
|
||||
format!("$hue: {} is not a number.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
opt_hsl!(
|
||||
args,
|
||||
saturation,
|
||||
"saturation",
|
||||
0,
|
||||
100,
|
||||
scope,
|
||||
super_selector
|
||||
);
|
||||
opt_hsl!(args, luminance, "lightness", 0, 100, scope, super_selector);
|
||||
|
||||
if hue.is_some() || saturation.is_some() || luminance.is_some() {
|
||||
// Color::as_hsla() returns more exact values than Color::hue(), etc.
|
||||
let (this_hue, this_saturation, this_luminance, this_alpha) = color.as_hsla();
|
||||
return Ok(Value::Color(Box::new(Color::from_hsla(
|
||||
hue.unwrap_or(this_hue),
|
||||
saturation.unwrap_or(this_saturation),
|
||||
luminance.unwrap_or(this_luminance),
|
||||
alpha.unwrap_or(this_alpha),
|
||||
))));
|
||||
}
|
||||
|
||||
Ok(Value::Color(if let Some(a) = alpha {
|
||||
Box::new(color.with_alpha(a))
|
||||
} else {
|
||||
color
|
||||
}))
|
||||
fn change_color(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
if args.get_positional(1, scope, super_selector).is_some() {
|
||||
return Err((
|
||||
"Only one positional argument is allowed. All other arguments must be passed by name.",
|
||||
args.span(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
fn adjust_color(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
opt_rgba!(args, alpha, "alpha", -1, 1, scope, super_selector);
|
||||
opt_rgba!(args, red, "red", -255, 255, scope, super_selector);
|
||||
opt_rgba!(args, green, "green", -255, 255, scope, super_selector);
|
||||
opt_rgba!(args, blue, "blue", -255, 255, scope, super_selector);
|
||||
|
||||
if red.is_some() || green.is_some() || blue.is_some() {
|
||||
return Ok(Value::Color(Box::new(Color::from_rgba(
|
||||
color.red() + red.unwrap_or(Number::zero()),
|
||||
color.green() + green.unwrap_or(Number::zero()),
|
||||
color.blue() + blue.unwrap_or(Number::zero()),
|
||||
color.alpha() + alpha.unwrap_or(Number::zero()),
|
||||
))));
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
let hue = match named_arg!(args, scope, super_selector, "hue" = Value::Null) {
|
||||
Value::Dimension(n, _) => Some(n),
|
||||
Value::Null => None,
|
||||
v => {
|
||||
return Err((
|
||||
format!("$hue: {} is not a number.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
opt_rgba!(args, alpha, "alpha", 0, 1, scope, super_selector);
|
||||
opt_rgba!(args, red, "red", 0, 255, scope, super_selector);
|
||||
opt_rgba!(args, green, "green", 0, 255, scope, super_selector);
|
||||
opt_rgba!(args, blue, "blue", 0, 255, scope, super_selector);
|
||||
|
||||
opt_hsl!(
|
||||
args,
|
||||
saturation,
|
||||
"saturation",
|
||||
-100,
|
||||
100,
|
||||
scope,
|
||||
super_selector
|
||||
);
|
||||
opt_hsl!(
|
||||
args,
|
||||
luminance,
|
||||
"lightness",
|
||||
-100,
|
||||
100,
|
||||
scope,
|
||||
super_selector
|
||||
);
|
||||
|
||||
if hue.is_some() || saturation.is_some() || luminance.is_some() {
|
||||
// Color::as_hsla() returns more exact values than Color::hue(), etc.
|
||||
let (this_hue, this_saturation, this_luminance, this_alpha) = color.as_hsla();
|
||||
return Ok(Value::Color(Box::new(Color::from_hsla(
|
||||
this_hue + hue.unwrap_or(Number::zero()),
|
||||
this_saturation + saturation.unwrap_or(Number::zero()),
|
||||
this_luminance + luminance.unwrap_or(Number::zero()),
|
||||
this_alpha + alpha.unwrap_or(Number::zero()),
|
||||
))));
|
||||
}
|
||||
|
||||
Ok(Value::Color(if let Some(a) = alpha {
|
||||
let temp_alpha = color.alpha();
|
||||
Box::new(color.with_alpha(temp_alpha + a))
|
||||
} else {
|
||||
color
|
||||
}))
|
||||
if red.is_some() || green.is_some() || blue.is_some() {
|
||||
return Ok(Value::Color(Box::new(Color::from_rgba(
|
||||
red.unwrap_or(color.red()),
|
||||
green.unwrap_or(color.green()),
|
||||
blue.unwrap_or(color.blue()),
|
||||
alpha.unwrap_or(color.alpha()),
|
||||
))));
|
||||
}
|
||||
|
||||
fn scale_color(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
fn scale(val: Number, by: Number, max: Number) -> Number {
|
||||
if by.is_zero() {
|
||||
return val;
|
||||
}
|
||||
val.clone() + (if by.is_positive() { max - val } else { val }) * by
|
||||
let hue = match named_arg!(args, scope, super_selector, "hue" = Value::Null) {
|
||||
Value::Dimension(n, _) => Some(n),
|
||||
Value::Null => None,
|
||||
v => {
|
||||
return Err((
|
||||
format!("$hue: {} is not a number.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
let span = args.span();
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
format!("$color: {} is not a color.", v.to_css_string(span)?),
|
||||
span,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
opt_hsl!(
|
||||
args,
|
||||
saturation,
|
||||
"saturation",
|
||||
0,
|
||||
100,
|
||||
scope,
|
||||
super_selector
|
||||
);
|
||||
opt_hsl!(args, luminance, "lightness", 0, 100, scope, super_selector);
|
||||
|
||||
macro_rules! opt_scale_arg {
|
||||
($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal, $scope:ident, $super_selector:ident) => {
|
||||
let $name = match named_arg!($args, $scope, $super_selector, $arg = Value::Null) {
|
||||
Value::Dimension(n, Unit::Percent) => {
|
||||
Some(bound!($args, $arg, n, Unit::Percent, $low, $high) / Number::from(100))
|
||||
}
|
||||
v @ Value::Dimension(..) => {
|
||||
return Err((
|
||||
format!(
|
||||
"${}: Expected {} to have unit \"%\".",
|
||||
$arg,
|
||||
v.to_css_string($args.span())?
|
||||
),
|
||||
$args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
Value::Null => None,
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"${}: {} is not a number.",
|
||||
$arg,
|
||||
v.to_css_string($args.span())?
|
||||
),
|
||||
$args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
if hue.is_some() || saturation.is_some() || luminance.is_some() {
|
||||
// Color::as_hsla() returns more exact values than Color::hue(), etc.
|
||||
let (this_hue, this_saturation, this_luminance, this_alpha) = color.as_hsla();
|
||||
return Ok(Value::Color(Box::new(Color::from_hsla(
|
||||
hue.unwrap_or(this_hue),
|
||||
saturation.unwrap_or(this_saturation),
|
||||
luminance.unwrap_or(this_luminance),
|
||||
alpha.unwrap_or(this_alpha),
|
||||
))));
|
||||
}
|
||||
|
||||
Ok(Value::Color(if let Some(a) = alpha {
|
||||
Box::new(color.with_alpha(a))
|
||||
} else {
|
||||
color
|
||||
}))
|
||||
}
|
||||
|
||||
fn adjust_color(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
opt_rgba!(args, alpha, "alpha", -1, 1, scope, super_selector);
|
||||
opt_rgba!(args, red, "red", -255, 255, scope, super_selector);
|
||||
opt_rgba!(args, green, "green", -255, 255, scope, super_selector);
|
||||
opt_rgba!(args, blue, "blue", -255, 255, scope, super_selector);
|
||||
|
||||
if red.is_some() || green.is_some() || blue.is_some() {
|
||||
return Ok(Value::Color(Box::new(Color::from_rgba(
|
||||
color.red() + red.unwrap_or(Number::zero()),
|
||||
color.green() + green.unwrap_or(Number::zero()),
|
||||
color.blue() + blue.unwrap_or(Number::zero()),
|
||||
color.alpha() + alpha.unwrap_or(Number::zero()),
|
||||
))));
|
||||
}
|
||||
|
||||
let hue = match named_arg!(args, scope, super_selector, "hue" = Value::Null) {
|
||||
Value::Dimension(n, _) => Some(n),
|
||||
Value::Null => None,
|
||||
v => {
|
||||
return Err((
|
||||
format!("$hue: {} is not a number.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
opt_hsl!(
|
||||
args,
|
||||
saturation,
|
||||
"saturation",
|
||||
-100,
|
||||
100,
|
||||
scope,
|
||||
super_selector
|
||||
);
|
||||
opt_hsl!(
|
||||
args,
|
||||
luminance,
|
||||
"lightness",
|
||||
-100,
|
||||
100,
|
||||
scope,
|
||||
super_selector
|
||||
);
|
||||
|
||||
if hue.is_some() || saturation.is_some() || luminance.is_some() {
|
||||
// Color::as_hsla() returns more exact values than Color::hue(), etc.
|
||||
let (this_hue, this_saturation, this_luminance, this_alpha) = color.as_hsla();
|
||||
return Ok(Value::Color(Box::new(Color::from_hsla(
|
||||
this_hue + hue.unwrap_or(Number::zero()),
|
||||
this_saturation + saturation.unwrap_or(Number::zero()),
|
||||
this_luminance + luminance.unwrap_or(Number::zero()),
|
||||
this_alpha + alpha.unwrap_or(Number::zero()),
|
||||
))));
|
||||
}
|
||||
|
||||
Ok(Value::Color(if let Some(a) = alpha {
|
||||
let temp_alpha = color.alpha();
|
||||
Box::new(color.with_alpha(temp_alpha + a))
|
||||
} else {
|
||||
color
|
||||
}))
|
||||
}
|
||||
|
||||
fn scale_color(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn scale(val: Number, by: Number, max: Number) -> Number {
|
||||
if by.is_zero() {
|
||||
return val;
|
||||
}
|
||||
val.clone() + (if by.is_positive() { max - val } else { val }) * by
|
||||
}
|
||||
|
||||
let span = args.span();
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
format!("$color: {} is not a color.", v.to_css_string(span)?),
|
||||
span,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
macro_rules! opt_scale_arg {
|
||||
($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal, $scope:ident, $super_selector:ident) => {
|
||||
let $name = match named_arg!($args, $scope, $super_selector, $arg = Value::Null) {
|
||||
Value::Dimension(n, Unit::Percent) => {
|
||||
Some(bound!($args, $arg, n, Unit::Percent, $low, $high) / Number::from(100))
|
||||
}
|
||||
v @ Value::Dimension(..) => {
|
||||
return Err((
|
||||
format!(
|
||||
"${}: Expected {} to have unit \"%\".",
|
||||
$arg,
|
||||
v.to_css_string($args.span())?
|
||||
),
|
||||
$args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
Value::Null => None,
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"${}: {} is not a number.",
|
||||
$arg,
|
||||
v.to_css_string($args.span())?
|
||||
),
|
||||
$args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
opt_scale_arg!(args, alpha, "alpha", -100, 100, scope, super_selector);
|
||||
opt_scale_arg!(args, red, "red", -100, 100, scope, super_selector);
|
||||
opt_scale_arg!(args, green, "green", -100, 100, scope, super_selector);
|
||||
opt_scale_arg!(args, blue, "blue", -100, 100, scope, super_selector);
|
||||
|
||||
if red.is_some() || green.is_some() || blue.is_some() {
|
||||
return Ok(Value::Color(Box::new(Color::from_rgba(
|
||||
scale(
|
||||
color.red(),
|
||||
red.unwrap_or(Number::zero()),
|
||||
Number::from(255),
|
||||
),
|
||||
scale(
|
||||
color.green(),
|
||||
green.unwrap_or(Number::zero()),
|
||||
Number::from(255),
|
||||
),
|
||||
scale(
|
||||
color.blue(),
|
||||
blue.unwrap_or(Number::zero()),
|
||||
Number::from(255),
|
||||
),
|
||||
scale(
|
||||
color.alpha(),
|
||||
alpha.unwrap_or(Number::zero()),
|
||||
Number::one(),
|
||||
),
|
||||
))));
|
||||
}
|
||||
|
||||
opt_scale_arg!(
|
||||
args,
|
||||
saturation,
|
||||
"saturation",
|
||||
-100,
|
||||
100,
|
||||
scope,
|
||||
super_selector
|
||||
);
|
||||
opt_scale_arg!(
|
||||
args,
|
||||
luminance,
|
||||
"lightness",
|
||||
-100,
|
||||
100,
|
||||
scope,
|
||||
super_selector
|
||||
);
|
||||
|
||||
if saturation.is_some() || luminance.is_some() {
|
||||
// Color::as_hsla() returns more exact values than Color::hue(), etc.
|
||||
let (this_hue, this_saturation, this_luminance, this_alpha) = color.as_hsla();
|
||||
return Ok(Value::Color(Box::new(Color::from_hsla(
|
||||
scale(this_hue, Number::zero(), Number::from(360)),
|
||||
scale(
|
||||
this_saturation,
|
||||
saturation.unwrap_or(Number::zero()),
|
||||
Number::one(),
|
||||
),
|
||||
scale(
|
||||
this_luminance,
|
||||
luminance.unwrap_or(Number::zero()),
|
||||
Number::one(),
|
||||
),
|
||||
scale(this_alpha, alpha.unwrap_or(Number::zero()), Number::one()),
|
||||
))));
|
||||
}
|
||||
|
||||
Ok(Value::Color(if let Some(a) = alpha {
|
||||
let temp_alpha = color.alpha();
|
||||
Box::new(color.with_alpha(scale(temp_alpha, a, Number::one())))
|
||||
} else {
|
||||
color
|
||||
}))
|
||||
}
|
||||
|
||||
fn ie_hex_str(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
Ok(Value::Ident(color.to_ie_hex_str(), QuoteKind::None))
|
||||
}
|
||||
|
||||
opt_scale_arg!(args, alpha, "alpha", -100, 100, scope, super_selector);
|
||||
opt_scale_arg!(args, red, "red", -100, 100, scope, super_selector);
|
||||
opt_scale_arg!(args, green, "green", -100, 100, scope, super_selector);
|
||||
opt_scale_arg!(args, blue, "blue", -100, 100, scope, super_selector);
|
||||
|
||||
if red.is_some() || green.is_some() || blue.is_some() {
|
||||
return Ok(Value::Color(Box::new(Color::from_rgba(
|
||||
scale(
|
||||
color.red(),
|
||||
red.unwrap_or(Number::zero()),
|
||||
Number::from(255),
|
||||
),
|
||||
scale(
|
||||
color.green(),
|
||||
green.unwrap_or(Number::zero()),
|
||||
Number::from(255),
|
||||
),
|
||||
scale(
|
||||
color.blue(),
|
||||
blue.unwrap_or(Number::zero()),
|
||||
Number::from(255),
|
||||
),
|
||||
scale(
|
||||
color.alpha(),
|
||||
alpha.unwrap_or(Number::zero()),
|
||||
Number::one(),
|
||||
),
|
||||
))));
|
||||
}
|
||||
|
||||
opt_scale_arg!(
|
||||
args,
|
||||
saturation,
|
||||
"saturation",
|
||||
-100,
|
||||
100,
|
||||
scope,
|
||||
super_selector
|
||||
);
|
||||
opt_scale_arg!(
|
||||
args,
|
||||
luminance,
|
||||
"lightness",
|
||||
-100,
|
||||
100,
|
||||
scope,
|
||||
super_selector
|
||||
);
|
||||
|
||||
if saturation.is_some() || luminance.is_some() {
|
||||
// Color::as_hsla() returns more exact values than Color::hue(), etc.
|
||||
let (this_hue, this_saturation, this_luminance, this_alpha) = color.as_hsla();
|
||||
return Ok(Value::Color(Box::new(Color::from_hsla(
|
||||
scale(this_hue, Number::zero(), Number::from(360)),
|
||||
scale(
|
||||
this_saturation,
|
||||
saturation.unwrap_or(Number::zero()),
|
||||
Number::one(),
|
||||
),
|
||||
scale(
|
||||
this_luminance,
|
||||
luminance.unwrap_or(Number::zero()),
|
||||
Number::one(),
|
||||
),
|
||||
scale(this_alpha, alpha.unwrap_or(Number::zero()), Number::one()),
|
||||
))));
|
||||
}
|
||||
|
||||
Ok(Value::Color(if let Some(a) = alpha {
|
||||
let temp_alpha = color.alpha();
|
||||
Box::new(color.with_alpha(scale(temp_alpha, a, Number::one())))
|
||||
} else {
|
||||
color
|
||||
}))
|
||||
}
|
||||
|
||||
fn ie_hex_str(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
Ok(Value::Ident(color.to_ie_hex_str(), QuoteKind::None))
|
||||
}
|
||||
|
||||
pub(crate) fn register(f: &mut GlobalFunctionMap) {
|
||||
f.insert("change-color", Builtin::new(change_color));
|
||||
f.insert("adjust-color", Builtin::new(adjust_color));
|
||||
f.insert("scale-color", Builtin::new(scale_color));
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -11,316 +11,312 @@ use crate::selector::Selector;
|
||||
use crate::unit::Unit;
|
||||
use crate::value::{Number, Value};
|
||||
|
||||
pub(crate) fn register(f: &mut GlobalFunctionMap) {
|
||||
fn length(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
let len = match arg!(args, scope, super_selector, 0, "list") {
|
||||
Value::List(v, ..) => Number::from(v.len()),
|
||||
Value::Map(m) => Number::from(m.len()),
|
||||
_ => Number::one(),
|
||||
};
|
||||
Ok(Value::Dimension(len, Unit::None))
|
||||
fn length(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
let len = match arg!(args, scope, super_selector, 0, "list") {
|
||||
Value::List(v, ..) => Number::from(v.len()),
|
||||
Value::Map(m) => Number::from(m.len()),
|
||||
_ => Number::one(),
|
||||
};
|
||||
Ok(Value::Dimension(len, Unit::None))
|
||||
}
|
||||
|
||||
fn nth(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let list = match arg!(args, scope, super_selector, 0, "list") {
|
||||
Value::List(v, ..) => v,
|
||||
Value::Map(m) => m.entries(),
|
||||
v => vec![v],
|
||||
};
|
||||
let n = match arg!(args, scope, super_selector, 1, "n") {
|
||||
Value::Dimension(num, _) => num,
|
||||
v => {
|
||||
return Err((
|
||||
format!("$n: {} is not a number.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
if n.is_zero() {
|
||||
return Err(("$n: List index may not be 0.", args.span()).into());
|
||||
}
|
||||
|
||||
fn nth(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let list = match arg!(args, scope, super_selector, 0, "list") {
|
||||
Value::List(v, ..) => v,
|
||||
Value::Map(m) => m.entries(),
|
||||
v => vec![v],
|
||||
};
|
||||
let n = match arg!(args, scope, super_selector, 1, "n") {
|
||||
Value::Dimension(num, _) => num,
|
||||
v => {
|
||||
if n.abs() > Number::from(list.len()) {
|
||||
return Err((
|
||||
format!(
|
||||
"$n: Invalid index {} for a list with {} elements.",
|
||||
n,
|
||||
list.len()
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
if n.is_decimal() {
|
||||
return Err((format!("$n: {} is not an int.", n), args.span()).into());
|
||||
}
|
||||
|
||||
if n.is_positive() {
|
||||
Ok(list[n.to_integer().to_usize().unwrap() - 1].clone())
|
||||
} else {
|
||||
Ok(list[list.len() - n.abs().to_integer().to_usize().unwrap()].clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn list_separator(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
Ok(Value::Ident(
|
||||
match arg!(args, scope, super_selector, 0, "list") {
|
||||
Value::List(_, sep, ..) => sep.name(),
|
||||
_ => ListSeparator::Space.name(),
|
||||
}
|
||||
.to_owned(),
|
||||
QuoteKind::None,
|
||||
))
|
||||
}
|
||||
|
||||
fn set_nth(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(3)?;
|
||||
let (mut list, sep, brackets) = match arg!(args, scope, super_selector, 0, "list") {
|
||||
Value::List(v, sep, b) => (v, sep, b),
|
||||
Value::Map(m) => (m.entries(), ListSeparator::Comma, Brackets::None),
|
||||
v => (vec![v], ListSeparator::Space, Brackets::None),
|
||||
};
|
||||
let n = match arg!(args, scope, super_selector, 1, "n") {
|
||||
Value::Dimension(num, _) => num,
|
||||
v => {
|
||||
return Err((
|
||||
format!("$n: {} is not a number.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
if n.is_zero() {
|
||||
return Err(("$n: List index may not be 0.", args.span()).into());
|
||||
}
|
||||
|
||||
let len = list.len();
|
||||
|
||||
if n.abs() > Number::from(len) {
|
||||
return Err((
|
||||
format!("$n: Invalid index {} for a list with {} elements.", n, len),
|
||||
args.span(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
if n.is_decimal() {
|
||||
return Err((format!("$n: {} is not an int.", n), args.span()).into());
|
||||
}
|
||||
|
||||
let val = arg!(args, scope, super_selector, 2, "value");
|
||||
|
||||
if n.is_positive() {
|
||||
list[n.to_integer().to_usize().unwrap() - 1] = val;
|
||||
} else {
|
||||
list[len - n.abs().to_integer().to_usize().unwrap()] = val;
|
||||
}
|
||||
|
||||
Ok(Value::List(list, sep, brackets))
|
||||
}
|
||||
|
||||
fn append(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(3)?;
|
||||
let (mut list, sep, brackets) = match arg!(args, scope, super_selector, 0, "list") {
|
||||
Value::List(v, sep, b) => (v, sep, b),
|
||||
v => (vec![v], ListSeparator::Space, Brackets::None),
|
||||
};
|
||||
let val = arg!(args, scope, super_selector, 1, "val");
|
||||
let sep = match arg!(
|
||||
args,
|
||||
scope,
|
||||
super_selector,
|
||||
2,
|
||||
"separator" = Value::Ident("auto".to_owned(), QuoteKind::None)
|
||||
) {
|
||||
Value::Ident(s, ..) => match s.as_str() {
|
||||
"auto" => sep,
|
||||
"comma" => ListSeparator::Comma,
|
||||
"space" => ListSeparator::Space,
|
||||
_ => {
|
||||
return Err((
|
||||
format!("$n: {} is not a number.", v.to_css_string(args.span())?),
|
||||
"$separator: Must be \"space\", \"comma\", or \"auto\".",
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
if n.is_zero() {
|
||||
return Err(("$n: List index may not be 0.", args.span()).into());
|
||||
}
|
||||
|
||||
if n.abs() > Number::from(list.len()) {
|
||||
},
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$n: Invalid index {} for a list with {} elements.",
|
||||
n,
|
||||
list.len()
|
||||
"$separator: {} is not a string.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into());
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
if n.is_decimal() {
|
||||
return Err((format!("$n: {} is not an int.", n), args.span()).into());
|
||||
}
|
||||
list.push(val);
|
||||
|
||||
if n.is_positive() {
|
||||
Ok(list[n.to_integer().to_usize().unwrap() - 1].clone())
|
||||
} else {
|
||||
Ok(list[list.len() - n.abs().to_integer().to_usize().unwrap()].clone())
|
||||
}
|
||||
}
|
||||
Ok(Value::List(list, sep, brackets))
|
||||
}
|
||||
|
||||
fn list_separator(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
Ok(Value::Ident(
|
||||
match arg!(args, scope, super_selector, 0, "list") {
|
||||
Value::List(_, sep, ..) => sep.name(),
|
||||
_ => ListSeparator::Space.name(),
|
||||
fn join(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(4)?;
|
||||
let (mut list1, sep1, brackets) = match arg!(args, scope, super_selector, 0, "list1") {
|
||||
Value::List(v, sep, brackets) => (v, sep, brackets),
|
||||
Value::Map(m) => (m.entries(), ListSeparator::Comma, Brackets::None),
|
||||
v => (vec![v], ListSeparator::Space, Brackets::None),
|
||||
};
|
||||
let (list2, sep2) = match arg!(args, scope, super_selector, 1, "list2") {
|
||||
Value::List(v, sep, ..) => (v, sep),
|
||||
Value::Map(m) => (m.entries(), ListSeparator::Comma),
|
||||
v => (vec![v], ListSeparator::Space),
|
||||
};
|
||||
let sep = match arg!(
|
||||
args,
|
||||
scope,
|
||||
super_selector,
|
||||
2,
|
||||
"separator" = Value::Ident("auto".to_owned(), QuoteKind::None)
|
||||
) {
|
||||
Value::Ident(s, ..) => match s.as_str() {
|
||||
"auto" => {
|
||||
if list1.is_empty() || (list1.len() == 1 && sep1 == ListSeparator::Space) {
|
||||
sep2
|
||||
} else {
|
||||
sep1
|
||||
}
|
||||
}
|
||||
.to_owned(),
|
||||
QuoteKind::None,
|
||||
))
|
||||
}
|
||||
|
||||
fn set_nth(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(3)?;
|
||||
let (mut list, sep, brackets) = match arg!(args, scope, super_selector, 0, "list") {
|
||||
Value::List(v, sep, b) => (v, sep, b),
|
||||
Value::Map(m) => (m.entries(), ListSeparator::Comma, Brackets::None),
|
||||
v => (vec![v], ListSeparator::Space, Brackets::None),
|
||||
};
|
||||
let n = match arg!(args, scope, super_selector, 1, "n") {
|
||||
Value::Dimension(num, _) => num,
|
||||
v => {
|
||||
"comma" => ListSeparator::Comma,
|
||||
"space" => ListSeparator::Space,
|
||||
_ => {
|
||||
return Err((
|
||||
format!("$n: {} is not a number.", v.to_css_string(args.span())?),
|
||||
"$separator: Must be \"space\", \"comma\", or \"auto\".",
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
if n.is_zero() {
|
||||
return Err(("$n: List index may not be 0.", args.span()).into());
|
||||
}
|
||||
|
||||
let len = list.len();
|
||||
|
||||
if n.abs() > Number::from(len) {
|
||||
},
|
||||
v => {
|
||||
return Err((
|
||||
format!("$n: Invalid index {} for a list with {} elements.", n, len),
|
||||
format!(
|
||||
"$separator: {} is not a string.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into());
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
if n.is_decimal() {
|
||||
return Err((format!("$n: {} is not an int.", n), args.span()).into());
|
||||
let brackets = match arg!(
|
||||
args,
|
||||
scope,
|
||||
super_selector,
|
||||
3,
|
||||
"bracketed" = Value::Ident("auto".to_owned(), QuoteKind::None)
|
||||
) {
|
||||
Value::Ident(s, ..) => match s.as_str() {
|
||||
"auto" => brackets,
|
||||
_ => Brackets::Bracketed,
|
||||
},
|
||||
v => {
|
||||
if v.is_true(args.span())? {
|
||||
Brackets::Bracketed
|
||||
} else {
|
||||
Brackets::None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let val = arg!(args, scope, super_selector, 2, "value");
|
||||
list1.extend(list2);
|
||||
|
||||
if n.is_positive() {
|
||||
list[n.to_integer().to_usize().unwrap() - 1] = val;
|
||||
} else {
|
||||
list[len - n.abs().to_integer().to_usize().unwrap()] = val;
|
||||
}
|
||||
Ok(Value::List(list1, sep, brackets))
|
||||
}
|
||||
|
||||
Ok(Value::List(list, sep, brackets))
|
||||
}
|
||||
|
||||
fn append(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(3)?;
|
||||
let (mut list, sep, brackets) = match arg!(args, scope, super_selector, 0, "list") {
|
||||
Value::List(v, sep, b) => (v, sep, b),
|
||||
v => (vec![v], ListSeparator::Space, Brackets::None),
|
||||
};
|
||||
let val = arg!(args, scope, super_selector, 1, "val");
|
||||
let sep = match arg!(
|
||||
args,
|
||||
scope,
|
||||
super_selector,
|
||||
2,
|
||||
"separator" = Value::Ident("auto".to_owned(), QuoteKind::None)
|
||||
) {
|
||||
Value::Ident(s, ..) => match s.as_str() {
|
||||
"auto" => sep,
|
||||
"comma" => ListSeparator::Comma,
|
||||
"space" => ListSeparator::Space,
|
||||
_ => {
|
||||
return Err((
|
||||
"$separator: Must be \"space\", \"comma\", or \"auto\".",
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
fn is_bracketed(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
Ok(Value::bool(
|
||||
match arg!(args, scope, super_selector, 0, "list") {
|
||||
Value::List(.., brackets) => match brackets {
|
||||
Brackets::Bracketed => true,
|
||||
Brackets::None => false,
|
||||
},
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$separator: {} is not a string.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
_ => false,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
list.push(val);
|
||||
fn index(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let list = match arg!(args, scope, super_selector, 0, "list") {
|
||||
Value::List(v, ..) => v,
|
||||
Value::Map(m) => m.entries(),
|
||||
v => vec![v],
|
||||
};
|
||||
let value = arg!(args, scope, super_selector, 1, "value");
|
||||
// TODO: find a way around this unwrap.
|
||||
// It should be impossible to hit as the arg is
|
||||
// evaluated prior to checking equality, but
|
||||
// it is still dirty.
|
||||
// Potential input to fuzz: index(1px 1in 1cm, 96px + 1rem)
|
||||
let index = match list
|
||||
.into_iter()
|
||||
.position(|v| v.equals(value.clone(), args.span()).unwrap())
|
||||
{
|
||||
Some(v) => Number::from(v + 1),
|
||||
None => return Ok(Value::Null),
|
||||
};
|
||||
Ok(Value::Dimension(index, Unit::None))
|
||||
}
|
||||
|
||||
Ok(Value::List(list, sep, brackets))
|
||||
}
|
||||
|
||||
fn join(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(4)?;
|
||||
let (mut list1, sep1, brackets) = match arg!(args, scope, super_selector, 0, "list1") {
|
||||
Value::List(v, sep, brackets) => (v, sep, brackets),
|
||||
Value::Map(m) => (m.entries(), ListSeparator::Comma, Brackets::None),
|
||||
v => (vec![v], ListSeparator::Space, Brackets::None),
|
||||
};
|
||||
let (list2, sep2) = match arg!(args, scope, super_selector, 1, "list2") {
|
||||
Value::List(v, sep, ..) => (v, sep),
|
||||
Value::Map(m) => (m.entries(), ListSeparator::Comma),
|
||||
v => (vec![v], ListSeparator::Space),
|
||||
};
|
||||
let sep = match arg!(
|
||||
args,
|
||||
scope,
|
||||
super_selector,
|
||||
2,
|
||||
"separator" = Value::Ident("auto".to_owned(), QuoteKind::None)
|
||||
) {
|
||||
Value::Ident(s, ..) => match s.as_str() {
|
||||
"auto" => {
|
||||
if list1.is_empty() || (list1.len() == 1 && sep1 == ListSeparator::Space) {
|
||||
sep2
|
||||
} else {
|
||||
sep1
|
||||
}
|
||||
}
|
||||
"comma" => ListSeparator::Comma,
|
||||
"space" => ListSeparator::Space,
|
||||
_ => {
|
||||
return Err((
|
||||
"$separator: Must be \"space\", \"comma\", or \"auto\".",
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
},
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$separator: {} is not a string.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
let brackets = match arg!(
|
||||
args,
|
||||
scope,
|
||||
super_selector,
|
||||
3,
|
||||
"bracketed" = Value::Ident("auto".to_owned(), QuoteKind::None)
|
||||
) {
|
||||
Value::Ident(s, ..) => match s.as_str() {
|
||||
"auto" => brackets,
|
||||
_ => Brackets::Bracketed,
|
||||
},
|
||||
v => {
|
||||
if v.is_true(args.span())? {
|
||||
Brackets::Bracketed
|
||||
} else {
|
||||
Brackets::None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
list1.extend(list2);
|
||||
|
||||
Ok(Value::List(list1, sep, brackets))
|
||||
}
|
||||
|
||||
fn is_bracketed(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
Ok(Value::bool(
|
||||
match arg!(args, scope, super_selector, 0, "list") {
|
||||
Value::List(.., brackets) => match brackets {
|
||||
Brackets::Bracketed => true,
|
||||
Brackets::None => false,
|
||||
},
|
||||
_ => false,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn index(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let list = match arg!(args, scope, super_selector, 0, "list") {
|
||||
Value::List(v, ..) => v,
|
||||
Value::Map(m) => m.entries(),
|
||||
v => vec![v],
|
||||
};
|
||||
let value = arg!(args, scope, super_selector, 1, "value");
|
||||
// TODO: find a way around this unwrap.
|
||||
// It should be impossible to hit as the arg is
|
||||
// evaluated prior to checking equality, but
|
||||
// it is still dirty.
|
||||
// Potential input to fuzz: index(1px 1in 1cm, 96px + 1rem)
|
||||
let index = match list
|
||||
.into_iter()
|
||||
.position(|v| v.equals(value.clone(), args.span()).unwrap())
|
||||
{
|
||||
Some(v) => Number::from(v + 1),
|
||||
None => return Ok(Value::Null),
|
||||
};
|
||||
Ok(Value::Dimension(index, Unit::None))
|
||||
}
|
||||
|
||||
fn zip(args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
let span = args.span();
|
||||
let lists = args
|
||||
.get_variadic(scope, super_selector)?
|
||||
.into_iter()
|
||||
.map(|x| {
|
||||
Ok(match x.node.eval(span)?.node {
|
||||
Value::List(v, ..) => v,
|
||||
Value::Map(m) => m.entries(),
|
||||
v => vec![v],
|
||||
})
|
||||
fn zip(args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
let span = args.span();
|
||||
let lists = args
|
||||
.get_variadic(scope, super_selector)?
|
||||
.into_iter()
|
||||
.map(|x| {
|
||||
Ok(match x.node.eval(span)?.node {
|
||||
Value::List(v, ..) => v,
|
||||
Value::Map(m) => m.entries(),
|
||||
v => vec![v],
|
||||
})
|
||||
.collect::<SassResult<Vec<Vec<Value>>>>()?;
|
||||
})
|
||||
.collect::<SassResult<Vec<Vec<Value>>>>()?;
|
||||
|
||||
let len = lists.iter().map(Vec::len).min().unwrap_or(0);
|
||||
let len = lists.iter().map(Vec::len).min().unwrap_or(0);
|
||||
|
||||
if len == 0 {
|
||||
return Ok(Value::List(
|
||||
Vec::new(),
|
||||
ListSeparator::Comma,
|
||||
Brackets::None,
|
||||
));
|
||||
}
|
||||
|
||||
let result = (0..len)
|
||||
.map(|i| {
|
||||
let items = lists.iter().map(|v| v[i].clone()).collect();
|
||||
Value::List(items, ListSeparator::Space, Brackets::None)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Value::List(result, ListSeparator::Comma, Brackets::None))
|
||||
if len == 0 {
|
||||
return Ok(Value::List(
|
||||
Vec::new(),
|
||||
ListSeparator::Comma,
|
||||
Brackets::None,
|
||||
));
|
||||
}
|
||||
|
||||
let result = (0..len)
|
||||
.map(|i| {
|
||||
let items = lists.iter().map(|v| v[i].clone()).collect();
|
||||
Value::List(items, ListSeparator::Space, Brackets::None)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Value::List(result, ListSeparator::Comma, Brackets::None))
|
||||
}
|
||||
|
||||
pub(crate) fn register(f: &mut GlobalFunctionMap) {
|
||||
f.insert("length", Builtin::new(length));
|
||||
f.insert("nth", Builtin::new(nth));
|
||||
f.insert("list-separator", Builtin::new(list_separator));
|
||||
|
@ -8,144 +8,128 @@ use crate::scope::Scope;
|
||||
use crate::selector::Selector;
|
||||
use crate::value::{SassMap, Value};
|
||||
|
||||
pub(crate) fn register(f: &mut GlobalFunctionMap) {
|
||||
fn map_get(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let key = arg!(args, scope, super_selector, 1, "key");
|
||||
let map = match arg!(args, scope, super_selector, 0, "map") {
|
||||
Value::Map(m) => m,
|
||||
Value::List(v, ..) if v.is_empty() => SassMap::new(),
|
||||
v => {
|
||||
return Err((
|
||||
format!("$map: {} is not a map.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
Ok(map.get(&key, args.span())?.unwrap_or(Value::Null))
|
||||
}
|
||||
|
||||
fn map_has_key(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let key = arg!(args, scope, super_selector, 1, "key");
|
||||
let map = match arg!(args, scope, super_selector, 0, "map") {
|
||||
Value::Map(m) => m,
|
||||
Value::List(v, ..) if v.is_empty() => SassMap::new(),
|
||||
v => {
|
||||
return Err((
|
||||
format!("$map: {} is not a map.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
Ok(Value::bool(map.get(&key, args.span())?.is_some()))
|
||||
}
|
||||
|
||||
fn map_keys(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
let map = match arg!(args, scope, super_selector, 0, "map") {
|
||||
Value::Map(m) => m,
|
||||
Value::List(v, ..) if v.is_empty() => SassMap::new(),
|
||||
v => {
|
||||
return Err((
|
||||
format!("$map: {} is not a map.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
Ok(Value::List(
|
||||
map.keys(),
|
||||
ListSeparator::Comma,
|
||||
Brackets::None,
|
||||
))
|
||||
}
|
||||
|
||||
fn map_values(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
let map = match arg!(args, scope, super_selector, 0, "map") {
|
||||
Value::Map(m) => m,
|
||||
Value::List(v, ..) if v.is_empty() => SassMap::new(),
|
||||
v => {
|
||||
return Err((
|
||||
format!("$map: {} is not a map.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
Ok(Value::List(
|
||||
map.values(),
|
||||
ListSeparator::Comma,
|
||||
Brackets::None,
|
||||
))
|
||||
}
|
||||
|
||||
fn map_merge(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let mut map1 = match arg!(args, scope, super_selector, 0, "map1") {
|
||||
Value::Map(m) => m,
|
||||
Value::List(v, ..) if v.is_empty() => SassMap::new(),
|
||||
v => {
|
||||
return Err((
|
||||
format!("$map1: {} is not a map.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let map2 = match arg!(args, scope, super_selector, 1, "map2") {
|
||||
Value::Map(m) => m,
|
||||
Value::List(v, ..) if v.is_empty() => SassMap::new(),
|
||||
v => {
|
||||
return Err((
|
||||
format!("$map2: {} is not a map.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
map1.merge(map2);
|
||||
Ok(Value::Map(map1))
|
||||
}
|
||||
|
||||
fn map_remove(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
let mut map = match arg!(args, scope, super_selector, 0, "map") {
|
||||
Value::Map(m) => m,
|
||||
Value::List(v, ..) if v.is_empty() => SassMap::new(),
|
||||
v => {
|
||||
return Err((
|
||||
format!("$map: {} is not a map.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let keys = args.get_variadic(scope, super_selector)?;
|
||||
for key in keys {
|
||||
map.remove(&key);
|
||||
fn map_get(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let key = arg!(args, scope, super_selector, 1, "key");
|
||||
let map = match arg!(args, scope, super_selector, 0, "map") {
|
||||
Value::Map(m) => m,
|
||||
Value::List(v, ..) if v.is_empty() => SassMap::new(),
|
||||
v => {
|
||||
return Err((
|
||||
format!("$map: {} is not a map.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
Ok(Value::Map(map))
|
||||
}
|
||||
};
|
||||
Ok(map.get(&key, args.span())?.unwrap_or(Value::Null))
|
||||
}
|
||||
|
||||
fn map_has_key(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let key = arg!(args, scope, super_selector, 1, "key");
|
||||
let map = match arg!(args, scope, super_selector, 0, "map") {
|
||||
Value::Map(m) => m,
|
||||
Value::List(v, ..) if v.is_empty() => SassMap::new(),
|
||||
v => {
|
||||
return Err((
|
||||
format!("$map: {} is not a map.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
Ok(Value::bool(map.get(&key, args.span())?.is_some()))
|
||||
}
|
||||
|
||||
fn map_keys(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
let map = match arg!(args, scope, super_selector, 0, "map") {
|
||||
Value::Map(m) => m,
|
||||
Value::List(v, ..) if v.is_empty() => SassMap::new(),
|
||||
v => {
|
||||
return Err((
|
||||
format!("$map: {} is not a map.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
Ok(Value::List(
|
||||
map.keys(),
|
||||
ListSeparator::Comma,
|
||||
Brackets::None,
|
||||
))
|
||||
}
|
||||
|
||||
fn map_values(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
let map = match arg!(args, scope, super_selector, 0, "map") {
|
||||
Value::Map(m) => m,
|
||||
Value::List(v, ..) if v.is_empty() => SassMap::new(),
|
||||
v => {
|
||||
return Err((
|
||||
format!("$map: {} is not a map.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
Ok(Value::List(
|
||||
map.values(),
|
||||
ListSeparator::Comma,
|
||||
Brackets::None,
|
||||
))
|
||||
}
|
||||
|
||||
fn map_merge(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let mut map1 = match arg!(args, scope, super_selector, 0, "map1") {
|
||||
Value::Map(m) => m,
|
||||
Value::List(v, ..) if v.is_empty() => SassMap::new(),
|
||||
v => {
|
||||
return Err((
|
||||
format!("$map1: {} is not a map.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let map2 = match arg!(args, scope, super_selector, 1, "map2") {
|
||||
Value::Map(m) => m,
|
||||
Value::List(v, ..) if v.is_empty() => SassMap::new(),
|
||||
v => {
|
||||
return Err((
|
||||
format!("$map2: {} is not a map.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
map1.merge(map2);
|
||||
Ok(Value::Map(map1))
|
||||
}
|
||||
|
||||
fn map_remove(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
let mut map = match arg!(args, scope, super_selector, 0, "map") {
|
||||
Value::Map(m) => m,
|
||||
Value::List(v, ..) if v.is_empty() => SassMap::new(),
|
||||
v => {
|
||||
return Err((
|
||||
format!("$map: {} is not a map.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let keys = args.get_variadic(scope, super_selector)?;
|
||||
for key in keys {
|
||||
map.remove(&key);
|
||||
}
|
||||
Ok(Value::Map(map))
|
||||
}
|
||||
|
||||
pub(crate) fn register(f: &mut GlobalFunctionMap) {
|
||||
f.insert("map-get", Builtin::new(map_get));
|
||||
f.insert("map-has-key", Builtin::new(map_has_key));
|
||||
f.insert("map-keys", Builtin::new(map_keys));
|
||||
|
@ -13,191 +13,183 @@ use crate::selector::Selector;
|
||||
use crate::unit::Unit;
|
||||
use crate::value::{Number, Value};
|
||||
|
||||
pub(crate) fn register(f: &mut GlobalFunctionMap) {
|
||||
fn percentage(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
let num = match arg!(args, scope, super_selector, 0, "number") {
|
||||
Value::Dimension(n, Unit::None) => n * Number::from(100),
|
||||
v @ Value::Dimension(..) => {
|
||||
return Err((
|
||||
format!(
|
||||
"$number: Expected {} to have no units.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$number: {} is not a number.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
Ok(Value::Dimension(num, Unit::Percent))
|
||||
}
|
||||
|
||||
fn round(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "number") {
|
||||
Value::Dimension(n, u) => Ok(Value::Dimension(n.round(), u)),
|
||||
v => Err((
|
||||
format!(
|
||||
"$number: {} is not a number.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn ceil(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "number") {
|
||||
Value::Dimension(n, u) => Ok(Value::Dimension(n.ceil(), u)),
|
||||
v => Err((
|
||||
format!(
|
||||
"$number: {} is not a number.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn floor(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "number") {
|
||||
Value::Dimension(n, u) => Ok(Value::Dimension(n.floor(), u)),
|
||||
v => Err((
|
||||
format!(
|
||||
"$number: {} is not a number.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn abs(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "number") {
|
||||
Value::Dimension(n, u) => Ok(Value::Dimension(n.abs(), u)),
|
||||
v => Err((
|
||||
format!(
|
||||
"$number: {} is not a number.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn comparable(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let unit1 = match arg!(args, scope, super_selector, 0, "number1") {
|
||||
Value::Dimension(_, u) => u,
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$number1: {} is not a number.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let unit2 = match arg!(args, scope, super_selector, 1, "number2") {
|
||||
Value::Dimension(_, u) => u,
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$number2: {} is not a number.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Value::bool(unit1.comparable(&unit2)))
|
||||
}
|
||||
|
||||
// TODO: write tests for this
|
||||
#[cfg(feature = "random")]
|
||||
fn random(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
let limit = match arg!(args, scope, super_selector, 0, "limit" = Value::Null) {
|
||||
Value::Dimension(n, _) => n,
|
||||
Value::Null => {
|
||||
let mut rng = rand::thread_rng();
|
||||
return Ok(Value::Dimension(
|
||||
Number::from(rng.gen_range(0.0, 1.0)),
|
||||
Unit::None,
|
||||
));
|
||||
}
|
||||
v => {
|
||||
return Err((
|
||||
format!("$limit: {} is not a number.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
if limit.is_one() {
|
||||
return Ok(Value::Dimension(Number::one(), Unit::None));
|
||||
}
|
||||
|
||||
if limit.is_decimal() {
|
||||
return Err((format!("$limit: {} is not an int.", limit), args.span()).into());
|
||||
}
|
||||
|
||||
if limit.is_zero() || limit.is_negative() {
|
||||
fn percentage(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
let num = match arg!(args, scope, super_selector, 0, "number") {
|
||||
Value::Dimension(n, Unit::None) => n * Number::from(100),
|
||||
v @ Value::Dimension(..) => {
|
||||
return Err((
|
||||
format!("$limit: Must be greater than 0, was {}.", limit),
|
||||
format!(
|
||||
"$number: Expected {} to have no units.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into());
|
||||
.into())
|
||||
}
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$number: {} is not a number.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
Ok(Value::Dimension(num, Unit::Percent))
|
||||
}
|
||||
|
||||
let limit = match limit.to_integer().to_u32() {
|
||||
Some(n) => n,
|
||||
None => {
|
||||
return Err((
|
||||
format!("max must be in range 0 < max \u{2264} 2^32, was {}", limit),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
fn round(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "number") {
|
||||
Value::Dimension(n, u) => Ok(Value::Dimension(n.round(), u)),
|
||||
v => Err((
|
||||
format!(
|
||||
"$number: {} is not a number.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
Ok(Value::Dimension(
|
||||
Number::from(rng.gen_range(0, limit) + 1),
|
||||
Unit::None,
|
||||
))
|
||||
fn ceil(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "number") {
|
||||
Value::Dimension(n, u) => Ok(Value::Dimension(n.ceil(), u)),
|
||||
v => Err((
|
||||
format!(
|
||||
"$number: {} is not a number.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn floor(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "number") {
|
||||
Value::Dimension(n, u) => Ok(Value::Dimension(n.floor(), u)),
|
||||
v => Err((
|
||||
format!(
|
||||
"$number: {} is not a number.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn abs(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "number") {
|
||||
Value::Dimension(n, u) => Ok(Value::Dimension(n.abs(), u)),
|
||||
v => Err((
|
||||
format!(
|
||||
"$number: {} is not a number.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn comparable(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let unit1 = match arg!(args, scope, super_selector, 0, "number1") {
|
||||
Value::Dimension(_, u) => u,
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$number1: {} is not a number.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let unit2 = match arg!(args, scope, super_selector, 1, "number2") {
|
||||
Value::Dimension(_, u) => u,
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$number2: {} is not a number.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Value::bool(unit1.comparable(&unit2)))
|
||||
}
|
||||
|
||||
// TODO: write tests for this
|
||||
#[cfg(feature = "random")]
|
||||
fn random(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
let limit = match arg!(args, scope, super_selector, 0, "limit" = Value::Null) {
|
||||
Value::Dimension(n, _) => n,
|
||||
Value::Null => {
|
||||
let mut rng = rand::thread_rng();
|
||||
return Ok(Value::Dimension(
|
||||
Number::from(rng.gen_range(0.0, 1.0)),
|
||||
Unit::None,
|
||||
));
|
||||
}
|
||||
v => {
|
||||
return Err((
|
||||
format!("$limit: {} is not a number.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
if limit.is_one() {
|
||||
return Ok(Value::Dimension(Number::one(), Unit::None));
|
||||
}
|
||||
|
||||
if limit.is_decimal() {
|
||||
return Err((format!("$limit: {} is not an int.", limit), args.span()).into());
|
||||
}
|
||||
|
||||
if limit.is_zero() || limit.is_negative() {
|
||||
return Err((
|
||||
format!("$limit: Must be greater than 0, was {}.", limit),
|
||||
args.span(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
let limit = match limit.to_integer().to_u32() {
|
||||
Some(n) => n,
|
||||
None => {
|
||||
return Err((
|
||||
format!("max must be in range 0 < max \u{2264} 2^32, was {}", limit),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
Ok(Value::Dimension(
|
||||
Number::from(rng.gen_range(0, limit) + 1),
|
||||
Unit::None,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn register(f: &mut GlobalFunctionMap) {
|
||||
f.insert("percentage", Builtin::new(percentage));
|
||||
f.insert("round", Builtin::new(round));
|
||||
f.insert("ceil", Builtin::new(ceil));
|
||||
|
@ -12,233 +12,225 @@ use crate::selector::Selector;
|
||||
use crate::unit::Unit;
|
||||
use crate::value::{SassFunction, Value};
|
||||
|
||||
pub(crate) fn register(f: &mut GlobalFunctionMap) {
|
||||
fn if_(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(3)?;
|
||||
if arg!(args, scope, super_selector, 0, "condition").is_true(args.span())? {
|
||||
Ok(arg!(args, scope, super_selector, 1, "if-true"))
|
||||
} else {
|
||||
Ok(arg!(args, scope, super_selector, 2, "if-false"))
|
||||
}
|
||||
fn if_(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(3)?;
|
||||
if arg!(args, scope, super_selector, 0, "condition").is_true(args.span())? {
|
||||
Ok(arg!(args, scope, super_selector, 1, "if-true"))
|
||||
} else {
|
||||
Ok(arg!(args, scope, super_selector, 2, "if-false"))
|
||||
}
|
||||
}
|
||||
|
||||
fn feature_exists(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "feature") {
|
||||
Value::Ident(s, _) => Ok(match s.as_str() {
|
||||
// A local variable will shadow a global variable unless
|
||||
// `!global` is used.
|
||||
"global-variable-shadowing" => Value::True,
|
||||
// the @extend rule will affect selectors nested in pseudo-classes
|
||||
// like :not()
|
||||
"extend-selector-pseudoclass" => Value::False,
|
||||
// Full support for unit arithmetic using units defined in the
|
||||
// [Values and Units Level 3][] spec.
|
||||
"units-level-3" => Value::True,
|
||||
// The Sass `@error` directive is supported.
|
||||
"at-error" => Value::True,
|
||||
// The "Custom Properties Level 1" spec is supported. This means
|
||||
// that custom properties are parsed statically, with only
|
||||
// interpolation treated as SassScript.
|
||||
"custom-property" => Value::False,
|
||||
_ => Value::False,
|
||||
}),
|
||||
v => Err((
|
||||
fn feature_exists(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "feature") {
|
||||
Value::Ident(s, _) => Ok(match s.as_str() {
|
||||
// A local variable will shadow a global variable unless
|
||||
// `!global` is used.
|
||||
"global-variable-shadowing" => Value::True,
|
||||
// the @extend rule will affect selectors nested in pseudo-classes
|
||||
// like :not()
|
||||
"extend-selector-pseudoclass" => Value::False,
|
||||
// Full support for unit arithmetic using units defined in the
|
||||
// [Values and Units Level 3][] spec.
|
||||
"units-level-3" => Value::True,
|
||||
// The Sass `@error` directive is supported.
|
||||
"at-error" => Value::True,
|
||||
// The "Custom Properties Level 1" spec is supported. This means
|
||||
// that custom properties are parsed statically, with only
|
||||
// interpolation treated as SassScript.
|
||||
"custom-property" => Value::False,
|
||||
_ => Value::False,
|
||||
}),
|
||||
v => Err((
|
||||
format!(
|
||||
"$feature: {} is not a string.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn unit(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
let unit = match arg!(args, scope, super_selector, 0, "number") {
|
||||
Value::Dimension(_, u) => u.to_string(),
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$feature: {} is not a string.",
|
||||
"$number: {} is not a number.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
.into())
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(Value::Ident(unit, QuoteKind::Quoted))
|
||||
}
|
||||
|
||||
fn unit(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
let unit = match arg!(args, scope, super_selector, 0, "number") {
|
||||
Value::Dimension(_, u) => u.to_string(),
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$number: {} is not a number.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
Ok(Value::Ident(unit, QuoteKind::Quoted))
|
||||
}
|
||||
fn type_of(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
let value = arg!(args, scope, super_selector, 0, "value");
|
||||
Ok(Value::Ident(
|
||||
value.kind(args.span())?.to_owned(),
|
||||
QuoteKind::None,
|
||||
))
|
||||
}
|
||||
|
||||
fn type_of(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
let value = arg!(args, scope, super_selector, 0, "value");
|
||||
Ok(Value::Ident(
|
||||
value.kind(args.span())?.to_owned(),
|
||||
QuoteKind::None,
|
||||
))
|
||||
fn unitless(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "number") {
|
||||
Value::Dimension(_, Unit::None) => Ok(Value::True),
|
||||
Value::Dimension(_, _) => Ok(Value::False),
|
||||
_ => Ok(Value::True),
|
||||
}
|
||||
fn unitless(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "number") {
|
||||
Value::Dimension(_, Unit::None) => Ok(Value::True),
|
||||
Value::Dimension(_, _) => Ok(Value::False),
|
||||
_ => Ok(Value::True),
|
||||
}
|
||||
}
|
||||
|
||||
fn inspect(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
Ok(Value::Ident(
|
||||
arg!(args, scope, super_selector, 0, "value").inspect(args.span())?,
|
||||
QuoteKind::None,
|
||||
))
|
||||
}
|
||||
|
||||
fn variable_exists(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "name") {
|
||||
Value::Ident(s, _) => Ok(Value::bool(scope.var_exists(&s))),
|
||||
v => Err((
|
||||
format!("$name: {} is not a string.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn inspect(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
Ok(Value::Ident(
|
||||
arg!(args, scope, super_selector, 0, "value").inspect(args.span())?,
|
||||
QuoteKind::None,
|
||||
))
|
||||
fn global_variable_exists(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "name") {
|
||||
Value::Ident(s, _) => Ok(Value::bool(global_var_exists(&s))),
|
||||
v => Err((
|
||||
format!("$name: {} is not a string.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn variable_exists(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "name") {
|
||||
Value::Ident(s, _) => Ok(Value::bool(scope.var_exists(&s))),
|
||||
v => Err((
|
||||
format!("$name: {} is not a string.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
fn mixin_exists(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
match arg!(args, scope, super_selector, 0, "name") {
|
||||
Value::Ident(s, _) => Ok(Value::bool(scope.mixin_exists(&s))),
|
||||
v => Err((
|
||||
format!("$name: {} is not a string.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn global_variable_exists(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "name") {
|
||||
Value::Ident(s, _) => Ok(Value::bool(global_var_exists(&s))),
|
||||
v => Err((
|
||||
format!("$name: {} is not a string.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
fn function_exists(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
match arg!(args, scope, super_selector, 0, "name") {
|
||||
Value::Ident(s, _) => Ok(Value::bool(
|
||||
scope.fn_exists(&s) || GLOBAL_FUNCTIONS.contains_key(s.as_str()),
|
||||
)),
|
||||
v => Err((
|
||||
format!("$name: {} is not a string.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn mixin_exists(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
match arg!(args, scope, super_selector, 0, "name") {
|
||||
Value::Ident(s, _) => Ok(Value::bool(scope.mixin_exists(&s))),
|
||||
v => Err((
|
||||
format!("$name: {} is not a string.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn function_exists(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
match arg!(args, scope, super_selector, 0, "name") {
|
||||
Value::Ident(s, _) => Ok(Value::bool(
|
||||
scope.fn_exists(&s) || GLOBAL_FUNCTIONS.contains_key(s.as_str()),
|
||||
)),
|
||||
v => Err((
|
||||
format!("$name: {} is not a string.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_function(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(3)?;
|
||||
let name = match arg!(args, scope, super_selector, 0, "name") {
|
||||
Value::Ident(s, _) => s,
|
||||
v => {
|
||||
return Err((
|
||||
format!("$name: {} is not a string.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let css =
|
||||
arg!(args, scope, super_selector, 1, "css" = Value::False).is_true(args.span())?;
|
||||
let module = match arg!(args, scope, super_selector, 2, "module" = Value::Null) {
|
||||
Value::Ident(s, ..) => Some(s),
|
||||
Value::Null => None,
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$module: {} is not a string.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
if module.is_some() && css {
|
||||
fn get_function(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(3)?;
|
||||
let name = match arg!(args, scope, super_selector, 0, "name") {
|
||||
Value::Ident(s, _) => s,
|
||||
v => {
|
||||
return Err((
|
||||
"$css and $module may not both be passed at once.",
|
||||
format!("$name: {} is not a string.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into());
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let css = arg!(args, scope, super_selector, 1, "css" = Value::False).is_true(args.span())?;
|
||||
let module = match arg!(args, scope, super_selector, 2, "module" = Value::Null) {
|
||||
Value::Ident(s, ..) => Some(s),
|
||||
Value::Null => None,
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$module: {} is not a string.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
let func = match scope.get_fn(Spanned {
|
||||
node: name.clone(),
|
||||
span: args.span(),
|
||||
}) {
|
||||
Ok(f) => SassFunction::UserDefined(Box::new(f), name),
|
||||
Err(..) => match GLOBAL_FUNCTIONS.get(name.as_str()) {
|
||||
Some(f) => SassFunction::Builtin(f.clone(), name),
|
||||
None => return Err((format!("Function not found: {}", name), args.span()).into()),
|
||||
},
|
||||
};
|
||||
|
||||
Ok(Value::Function(func))
|
||||
if module.is_some() && css {
|
||||
return Err((
|
||||
"$css and $module may not both be passed at once.",
|
||||
args.span(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
fn call(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
let func = match arg!(args, scope, super_selector, 0, "function") {
|
||||
Value::Function(f) => f,
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$function: {} is not a function reference.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
func.call(args.decrement(), scope, super_selector)
|
||||
}
|
||||
let func = match scope.get_fn(Spanned {
|
||||
node: name.clone(),
|
||||
span: args.span(),
|
||||
}) {
|
||||
Ok(f) => SassFunction::UserDefined(Box::new(f), name),
|
||||
Err(..) => match GLOBAL_FUNCTIONS.get(name.as_str()) {
|
||||
Some(f) => SassFunction::Builtin(f.clone(), name),
|
||||
None => return Err((format!("Function not found: {}", name), args.span()).into()),
|
||||
},
|
||||
};
|
||||
|
||||
Ok(Value::Function(func))
|
||||
}
|
||||
|
||||
fn call(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
let func = match arg!(args, scope, super_selector, 0, "function") {
|
||||
Value::Function(f) => f,
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$function: {} is not a function reference.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
func.call(args.decrement(), scope, super_selector)
|
||||
}
|
||||
|
||||
pub(crate) fn register(f: &mut GlobalFunctionMap) {
|
||||
f.insert("if", Builtin::new(if_));
|
||||
f.insert("feature-exists", Builtin::new(feature_exists));
|
||||
f.insert("unit", Builtin::new(unit));
|
||||
|
@ -15,365 +15,349 @@ use crate::selector::Selector;
|
||||
use crate::unit::Unit;
|
||||
use crate::value::{Number, Value};
|
||||
|
||||
pub(crate) fn register(f: &mut GlobalFunctionMap) {
|
||||
fn to_upper_case(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "string") {
|
||||
Value::Ident(mut i, q) => {
|
||||
i.make_ascii_uppercase();
|
||||
Ok(Value::Ident(i, q))
|
||||
}
|
||||
v => Err((
|
||||
fn to_upper_case(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "string") {
|
||||
Value::Ident(mut i, q) => {
|
||||
i.make_ascii_uppercase();
|
||||
Ok(Value::Ident(i, q))
|
||||
}
|
||||
v => Err((
|
||||
format!(
|
||||
"$string: {} is not a string.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_lower_case(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "string") {
|
||||
Value::Ident(mut i, q) => {
|
||||
i.make_ascii_lowercase();
|
||||
Ok(Value::Ident(i, q))
|
||||
}
|
||||
v => Err((
|
||||
format!(
|
||||
"$string: {} is not a string.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn str_length(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "string") {
|
||||
Value::Ident(i, _) => Ok(Value::Dimension(
|
||||
Number::from(i.chars().count()),
|
||||
Unit::None,
|
||||
)),
|
||||
v => Err((
|
||||
format!(
|
||||
"$string: {} is not a string.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn quote(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "string") {
|
||||
Value::Ident(i, _) => Ok(Value::Ident(i, QuoteKind::Quoted)),
|
||||
v => Err((
|
||||
format!(
|
||||
"$string: {} is not a string.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn unquote(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "string") {
|
||||
i @ Value::Ident(..) => Ok(i.unquote()),
|
||||
v => Err((
|
||||
format!(
|
||||
"$string: {} is not a string.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn str_slice(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(3)?;
|
||||
let (string, quotes) = match arg!(args, scope, super_selector, 0, "string") {
|
||||
Value::Ident(s, q) => (s, q),
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$string: {} is not a string.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let str_len = string.chars().count();
|
||||
let start = match arg!(args, scope, super_selector, 1, "start-at") {
|
||||
Value::Dimension(n, Unit::None) if n.is_decimal() => {
|
||||
return Err((format!("{} is not an int.", n), args.span()).into())
|
||||
}
|
||||
Value::Dimension(n, Unit::None) if n.is_positive() => {
|
||||
n.to_integer().to_usize().unwrap_or(str_len + 1)
|
||||
}
|
||||
Value::Dimension(n, Unit::None) if n.is_zero() => 1_usize,
|
||||
Value::Dimension(n, Unit::None) if n < -Number::from(str_len) => 1_usize,
|
||||
Value::Dimension(n, Unit::None) => (BigInt::from(str_len + 1) + n.to_integer())
|
||||
.to_usize()
|
||||
.unwrap(),
|
||||
v @ Value::Dimension(..) => {
|
||||
return Err((
|
||||
format!(
|
||||
"$start: Expected {} to have no units.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$start-at: {} is not a number.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let mut end = match arg!(args, scope, super_selector, 2, "end-at" = Value::Null) {
|
||||
Value::Dimension(n, Unit::None) if n.is_decimal() => {
|
||||
return Err((format!("{} is not an int.", n), args.span()).into())
|
||||
}
|
||||
Value::Dimension(n, Unit::None) if n.is_positive() => {
|
||||
n.to_integer().to_usize().unwrap_or(str_len + 1)
|
||||
}
|
||||
Value::Dimension(n, Unit::None) if n.is_zero() => 0_usize,
|
||||
Value::Dimension(n, Unit::None) if n < -Number::from(str_len) => 0_usize,
|
||||
Value::Dimension(n, Unit::None) => (BigInt::from(str_len + 1) + n.to_integer())
|
||||
.to_usize()
|
||||
.unwrap_or(str_len + 1),
|
||||
v @ Value::Dimension(..) => {
|
||||
return Err((
|
||||
format!(
|
||||
"$end: Expected {} to have no units.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
Value::Null => str_len,
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$end-at: {} is not a number.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
if end > str_len {
|
||||
end = str_len;
|
||||
}
|
||||
|
||||
fn to_lower_case(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "string") {
|
||||
Value::Ident(mut i, q) => {
|
||||
i.make_ascii_lowercase();
|
||||
Ok(Value::Ident(i, q))
|
||||
}
|
||||
v => Err((
|
||||
if start > end || start > str_len {
|
||||
Ok(Value::Ident(String::new(), quotes))
|
||||
} else {
|
||||
Ok(Value::Ident(
|
||||
string
|
||||
.chars()
|
||||
.skip(start - 1)
|
||||
.take(end - start + 1)
|
||||
.collect(),
|
||||
quotes,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn str_index(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let s1 = match arg!(args, scope, super_selector, 0, "string") {
|
||||
Value::Ident(i, _) => i,
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$string: {} is not a string.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
.into())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fn str_length(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "string") {
|
||||
Value::Ident(i, _) => Ok(Value::Dimension(
|
||||
Number::from(i.chars().count()),
|
||||
Unit::None,
|
||||
)),
|
||||
v => Err((
|
||||
let substr = match arg!(args, scope, super_selector, 1, "substring") {
|
||||
Value::Ident(i, _) => i,
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$substring: {} is not a string.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
Ok(match s1.find(&substr) {
|
||||
Some(v) => Value::Dimension(Number::from(v + 1), Unit::None),
|
||||
None => Value::Null,
|
||||
})
|
||||
}
|
||||
|
||||
fn str_insert(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(3)?;
|
||||
let (s1, quotes) = match arg!(args, scope, super_selector, 0, "string") {
|
||||
Value::Ident(i, q) => (i, q),
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$string: {} is not a string.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
.into())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fn quote(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "string") {
|
||||
Value::Ident(i, _) => Ok(Value::Ident(i, QuoteKind::Quoted)),
|
||||
v => Err((
|
||||
let substr = match arg!(args, scope, super_selector, 1, "insert") {
|
||||
Value::Ident(i, _) => i,
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$string: {} is not a string.",
|
||||
"$insert: {} is not a string.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
.into())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fn unquote(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "string") {
|
||||
i @ Value::Ident(..) => Ok(i.unquote()),
|
||||
v => Err((
|
||||
let index = match arg!(args, scope, super_selector, 2, "index") {
|
||||
Value::Dimension(n, Unit::None) if n.is_decimal() => {
|
||||
return Err((format!("$index: {} is not an int.", n), args.span()).into())
|
||||
}
|
||||
Value::Dimension(n, Unit::None) => n,
|
||||
v @ Value::Dimension(..) => {
|
||||
return Err((
|
||||
format!(
|
||||
"$string: {} is not a string.",
|
||||
"$index: Expected {} to have no units.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
.into())
|
||||
}
|
||||
v => {
|
||||
return Err((
|
||||
format!("$index: {} is not a number.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
if s1.is_empty() {
|
||||
return Ok(Value::Ident(substr, quotes));
|
||||
}
|
||||
|
||||
fn str_slice(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(3)?;
|
||||
let (string, quotes) = match arg!(args, scope, super_selector, 0, "string") {
|
||||
Value::Ident(s, q) => (s, q),
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$string: {} is not a string.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let str_len = string.chars().count();
|
||||
let start = match arg!(args, scope, super_selector, 1, "start-at") {
|
||||
Value::Dimension(n, Unit::None) if n.is_decimal() => {
|
||||
return Err((format!("{} is not an int.", n), args.span()).into())
|
||||
}
|
||||
Value::Dimension(n, Unit::None) if n.is_positive() => {
|
||||
n.to_integer().to_usize().unwrap_or(str_len + 1)
|
||||
}
|
||||
Value::Dimension(n, Unit::None) if n.is_zero() => 1_usize,
|
||||
Value::Dimension(n, Unit::None) if n < -Number::from(str_len) => 1_usize,
|
||||
Value::Dimension(n, Unit::None) => (BigInt::from(str_len + 1) + n.to_integer())
|
||||
let len = s1.chars().count();
|
||||
|
||||
// Insert substring at char position, rather than byte position
|
||||
let insert = |idx, s1: String, s2| {
|
||||
s1.chars()
|
||||
.enumerate()
|
||||
.map(|(i, c)| {
|
||||
if i + 1 == idx {
|
||||
c.to_string() + s2
|
||||
} else if idx == 0 && i == 0 {
|
||||
s2.to_string() + &c.to_string()
|
||||
} else {
|
||||
c.to_string()
|
||||
}
|
||||
})
|
||||
.collect::<String>()
|
||||
};
|
||||
|
||||
let string = if index.is_positive() {
|
||||
insert(
|
||||
index
|
||||
.to_integer()
|
||||
.to_usize()
|
||||
.unwrap(),
|
||||
v @ Value::Dimension(..) => {
|
||||
return Err((
|
||||
format!(
|
||||
"$start: Expected {} to have no units.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$start-at: {} is not a number.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let mut end = match arg!(args, scope, super_selector, 2, "end-at" = Value::Null) {
|
||||
Value::Dimension(n, Unit::None) if n.is_decimal() => {
|
||||
return Err((format!("{} is not an int.", n), args.span()).into())
|
||||
}
|
||||
Value::Dimension(n, Unit::None) if n.is_positive() => {
|
||||
n.to_integer().to_usize().unwrap_or(str_len + 1)
|
||||
}
|
||||
Value::Dimension(n, Unit::None) if n.is_zero() => 0_usize,
|
||||
Value::Dimension(n, Unit::None) if n < -Number::from(str_len) => 0_usize,
|
||||
Value::Dimension(n, Unit::None) => (BigInt::from(str_len + 1) + n.to_integer())
|
||||
.to_usize()
|
||||
.unwrap_or(str_len + 1),
|
||||
v @ Value::Dimension(..) => {
|
||||
return Err((
|
||||
format!(
|
||||
"$end: Expected {} to have no units.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
Value::Null => str_len,
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$end-at: {} is not a number.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
if end > str_len {
|
||||
end = str_len;
|
||||
}
|
||||
|
||||
if start > end || start > str_len {
|
||||
Ok(Value::Ident(String::new(), quotes))
|
||||
} else {
|
||||
Ok(Value::Ident(
|
||||
string
|
||||
.chars()
|
||||
.skip(start - 1)
|
||||
.take(end - start + 1)
|
||||
.collect(),
|
||||
quotes,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn str_index(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let s1 = match arg!(args, scope, super_selector, 0, "string") {
|
||||
Value::Ident(i, _) => i,
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$string: {} is not a string.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
let substr = match arg!(args, scope, super_selector, 1, "substring") {
|
||||
Value::Ident(i, _) => i,
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$substring: {} is not a string.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
Ok(match s1.find(&substr) {
|
||||
Some(v) => Value::Dimension(Number::from(v + 1), Unit::None),
|
||||
None => Value::Null,
|
||||
})
|
||||
}
|
||||
|
||||
fn str_insert(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(3)?;
|
||||
let (s1, quotes) = match arg!(args, scope, super_selector, 0, "string") {
|
||||
Value::Ident(i, q) => (i, q),
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$string: {} is not a string.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
let substr = match arg!(args, scope, super_selector, 1, "insert") {
|
||||
Value::Ident(i, _) => i,
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"$insert: {} is not a string.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
let index = match arg!(args, scope, super_selector, 2, "index") {
|
||||
Value::Dimension(n, Unit::None) if n.is_decimal() => {
|
||||
return Err((format!("$index: {} is not an int.", n), args.span()).into())
|
||||
}
|
||||
Value::Dimension(n, Unit::None) => n,
|
||||
v @ Value::Dimension(..) => {
|
||||
return Err((
|
||||
format!(
|
||||
"$index: Expected {} to have no units.",
|
||||
v.to_css_string(args.span())?
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
v => {
|
||||
return Err((
|
||||
format!("$index: {} is not a number.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
if s1.is_empty() {
|
||||
return Ok(Value::Ident(substr, quotes));
|
||||
}
|
||||
|
||||
let len = s1.chars().count();
|
||||
|
||||
// Insert substring at char position, rather than byte position
|
||||
let insert = |idx, s1: String, s2| {
|
||||
s1.chars()
|
||||
.enumerate()
|
||||
.map(|(i, c)| {
|
||||
if i + 1 == idx {
|
||||
c.to_string() + s2
|
||||
} else if idx == 0 && i == 0 {
|
||||
s2.to_string() + &c.to_string()
|
||||
} else {
|
||||
c.to_string()
|
||||
}
|
||||
})
|
||||
.collect::<String>()
|
||||
};
|
||||
|
||||
let string = if index.is_positive() {
|
||||
insert(
|
||||
index
|
||||
.to_integer()
|
||||
.to_usize()
|
||||
.unwrap_or(len + 1)
|
||||
.min(len + 1)
|
||||
- 1,
|
||||
s1,
|
||||
&substr,
|
||||
)
|
||||
} else if index.is_zero() {
|
||||
.unwrap_or(len + 1)
|
||||
.min(len + 1)
|
||||
- 1,
|
||||
s1,
|
||||
&substr,
|
||||
)
|
||||
} else if index.is_zero() {
|
||||
insert(0, s1, &substr)
|
||||
} else {
|
||||
let idx = index.abs().to_integer().to_usize().unwrap_or(len + 1);
|
||||
if idx > len {
|
||||
insert(0, s1, &substr)
|
||||
} else {
|
||||
let idx = index.abs().to_integer().to_usize().unwrap_or(len + 1);
|
||||
if idx > len {
|
||||
insert(0, s1, &substr)
|
||||
} else {
|
||||
insert(len - idx + 1, s1, &substr)
|
||||
}
|
||||
};
|
||||
insert(len - idx + 1, s1, &substr)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Value::Ident(string, quotes))
|
||||
}
|
||||
Ok(Value::Ident(string, quotes))
|
||||
}
|
||||
|
||||
#[cfg(feature = "random")]
|
||||
fn unique_id(args: CallArgs, _: &Scope, _: &Selector) -> SassResult<Value> {
|
||||
args.max_args(0)?;
|
||||
let mut rng = thread_rng();
|
||||
let string = std::iter::repeat(())
|
||||
.map(|()| rng.sample(Alphanumeric))
|
||||
.take(7)
|
||||
.collect();
|
||||
Ok(Value::Ident(string, QuoteKind::None))
|
||||
}
|
||||
#[cfg(feature = "random")]
|
||||
fn unique_id(args: CallArgs, _: &Scope, _: &Selector) -> SassResult<Value> {
|
||||
args.max_args(0)?;
|
||||
let mut rng = thread_rng();
|
||||
let string = std::iter::repeat(())
|
||||
.map(|()| rng.sample(Alphanumeric))
|
||||
.take(7)
|
||||
.collect();
|
||||
Ok(Value::Ident(string, QuoteKind::None))
|
||||
}
|
||||
|
||||
pub(crate) fn register(f: &mut GlobalFunctionMap) {
|
||||
f.insert("to-upper-case", Builtin::new(to_upper_case));
|
||||
f.insert("to-lower-case", Builtin::new(to_lower_case));
|
||||
f.insert("str-length", Builtin::new(str_length));
|
||||
|
Loading…
x
Reference in New Issue
Block a user