move builtin fns to outer scope to reduce nesting

This commit is contained in:
ConnorSkees 2020-04-30 19:36:34 -04:00
parent 170759239a
commit 0aed492123
9 changed files with 2759 additions and 2861 deletions

File diff suppressed because it is too large Load Diff

View File

@ -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));

View File

@ -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

View File

@ -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));

View File

@ -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));

View File

@ -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));

View File

@ -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));

View File

@ -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));