Hwaro::Assets::Sass::Builtins
Constants
ADJUST_KWARGS = ["red", "green", "blue", "hue", "saturation", "lightness", "alpha"]
COLOR_FNS = {"adjust" => Fn.new do |args, kwargs|
compound_color("color.adjust", ComponentMode::Adjust, args, kwargs)
end, "scale" => Fn.new do |args, kwargs|
compound_color("color.scale", ComponentMode::Scale, args, kwargs)
end, "change" => Fn.new do |args, kwargs|
compound_color("color.change", ComponentMode::Change, args, kwargs)
end, "mix" => Fn.new do |args, kwargs|
bound = bind!("mix", args, kwargs, ["color1", "color2", "weight"], required: 2)
weight = (w = bound[2]) ? amount!("mix", w) : 50.0
mix_colors(color!("mix", bound[0].as(Value)), color!("mix", bound[1].as(Value)), weight)
end, "invert" => Fn.new do |args, kwargs|
bound = shadowed_bind!("invert", args, kwargs, ["color", "weight"], required: 1)
color = shadowed_color!(bound[0].as(Value))
weight = (w = bound[1]) ? amount!("invert", w) : 100.0
inverse = ColorV.new(255.0 - color.red, 255.0 - color.green, 255.0 - color.blue, color.alpha)
mix_colors(inverse, color, weight)
end, "grayscale" => Fn.new do |args, kwargs|
bound = shadowed_bind!("grayscale", args, kwargs, ["color"], required: 1)
adjust_hsl(shadowed_color!(bound[0].as(Value)), saturation: -100.0)
end, "complement" => Fn.new do |args, kwargs|
bound = bind!("complement", args, kwargs, ["color"], required: 1)
adjust_hsl(color!("complement", bound[0].as(Value)), hue: 180.0)
end, "adjust-hue" => Fn.new do |args, kwargs|
bound = bind!("adjust-hue", args, kwargs, ["color", "degrees"], required: 2)
color = color!("adjust-hue", bound[0].as(Value))
adjust_hsl(color, hue: finite!("adjust-hue", number!("adjust-hue", bound[1].as(Value))))
end, "darken" => Fn.new do |args, kwargs|
bound = bind!("darken", args, kwargs, ["color", "amount"], required: 2)
color = color!("darken", bound[0].as(Value))
adjust_hsl(color, lightness: -(amount!("darken", bound[1].as(Value))))
end, "lighten" => Fn.new do |args, kwargs|
bound = bind!("lighten", args, kwargs, ["color", "amount"], required: 2)
color = color!("lighten", bound[0].as(Value))
adjust_hsl(color, lightness: amount!("lighten", bound[1].as(Value)))
end, "saturate" => Fn.new do |args, kwargs|
bound = shadowed_bind!("saturate", args, kwargs, ["color", "amount"], required: 2)
color = shadowed_color!(bound[0].as(Value))
adjust_hsl(color, saturation: amount!("saturate", bound[1].as(Value)))
end, "desaturate" => Fn.new do |args, kwargs|
bound = bind!("desaturate", args, kwargs, ["color", "amount"], required: 2)
color = color!("desaturate", bound[0].as(Value))
adjust_hsl(color, saturation: -(amount!("desaturate", bound[1].as(Value))))
end, "opacify" => Fn.new do |args, kwargs|
bound = bind!("opacify", args, kwargs, ["color", "amount"], required: 2)
color = color!("opacify", bound[0].as(Value))
color.with_alpha(color.alpha + (alpha!("opacify", bound[1].as(Value))))
end, "transparentize" => Fn.new do |args, kwargs|
bound = bind!("transparentize", args, kwargs, ["color", "amount"], required: 2)
color = color!("transparentize", bound[0].as(Value))
color.with_alpha(color.alpha - (alpha!("transparentize", bound[1].as(Value))))
end, "red" => Fn.new do |args, kwargs|
bound = bind!("red", args, kwargs, ["color"], required: 1)
Number.new((color!("red", bound[0].as(Value))).red8.to_f)
end, "green" => Fn.new do |args, kwargs|
bound = bind!("green", args, kwargs, ["color"], required: 1)
Number.new((color!("green", bound[0].as(Value))).green8.to_f)
end, "blue" => Fn.new do |args, kwargs|
bound = bind!("blue", args, kwargs, ["color"], required: 1)
Number.new((color!("blue", bound[0].as(Value))).blue8.to_f)
end, "hue" => Fn.new do |args, kwargs|
bound = bind!("hue", args, kwargs, ["color"], required: 1)
Number.new((color!("hue", bound[0].as(Value))).hue, "deg")
end, "saturation" => Fn.new do |args, kwargs|
bound = bind!("saturation", args, kwargs, ["color"], required: 1)
Number.new((color!("saturation", bound[0].as(Value))).saturation, "%")
end, "lightness" => Fn.new do |args, kwargs|
bound = bind!("lightness", args, kwargs, ["color"], required: 1)
Number.new((color!("lightness", bound[0].as(Value))).lightness, "%")
end, "alpha" => Fn.new do |args, kwargs|
bound = bind!("alpha", args, kwargs, ["color"], required: 1)
Number.new((color!("alpha", bound[0].as(Value))).alpha)
end, "opacity" => Fn.new do |args, kwargs|
bound = shadowed_bind!("opacity", args, kwargs, ["color"], required: 1)
Number.new((shadowed_color!(bound[0].as(Value))).alpha)
end}
GLOBAL_FNS = {"if" => IF_FN, "quote" => STRING_FNS["quote"], "unquote" => STRING_FNS["unquote"], "str-length" => STRING_FNS["length"], "str-index" => STRING_FNS["index"], "str-slice" => STRING_FNS["slice"], "to-upper-case" => STRING_FNS["to-upper-case"], "to-lower-case" => STRING_FNS["to-lower-case"], "length" => LIST_FNS["length"], "nth" => LIST_FNS["nth"], "index" => LIST_FNS["index"], "append" => LIST_FNS["append"], "join" => LIST_FNS["join"], "list-separator" => LIST_FNS["separator"], "map-get" => MAP_FNS["get"], "map-has-key" => MAP_FNS["has-key"], "map-keys" => MAP_FNS["keys"], "map-values" => MAP_FNS["values"], "map-merge" => MAP_FNS["merge"], "map-remove" => MAP_FNS["remove"], "percentage" => MATH_FNS["percentage"], "round" => MATH_FNS["round"], "ceil" => MATH_FNS["ceil"], "floor" => MATH_FNS["floor"], "abs" => MATH_FNS["abs"], "min" => MATH_FNS["min"], "max" => MATH_FNS["max"], "unit" => MATH_FNS["unit"], "unitless" => MATH_FNS["is-unitless"], "comparable" => MATH_FNS["compatible"], "type-of" => META_FNS["type-of"], "inspect" => META_FNS["inspect"], "darken" => COLOR_FNS["darken"], "lighten" => COLOR_FNS["lighten"], "saturate" => COLOR_FNS["saturate"], "desaturate" => COLOR_FNS["desaturate"], "grayscale" => COLOR_FNS["grayscale"], "greyscale" => COLOR_FNS["grayscale"], "complement" => COLOR_FNS["complement"], "adjust-hue" => COLOR_FNS["adjust-hue"], "invert" => COLOR_FNS["invert"], "mix" => COLOR_FNS["mix"], "adjust-color" => COLOR_FNS["adjust"], "scale-color" => COLOR_FNS["scale"], "change-color" => COLOR_FNS["change"], "opacify" => COLOR_FNS["opacify"], "fade-in" => COLOR_FNS["opacify"], "transparentize" => COLOR_FNS["transparentize"], "fade-out" => COLOR_FNS["transparentize"], "red" => COLOR_FNS["red"], "green" => COLOR_FNS["green"], "blue" => COLOR_FNS["blue"], "hue" => COLOR_FNS["hue"], "saturation" => COLOR_FNS["saturation"], "lightness" => COLOR_FNS["lightness"], "alpha" => COLOR_FNS["alpha"], "opacity" => COLOR_FNS["opacity"], "rgba" => RGBA_FN, "rgb" => RGBA_FN}
IF_FN = Fn.new do |args, kwargs|
no_kwargs!("if", kwargs)
arity!("if", args, 3)
args[0].truthy? ? args[1] : args[2]
end
LIST_FNS = {"length" => Fn.new do |args, kwargs|
no_kwargs!("list.length", kwargs)
arity!("length", args, 1)
Number.new((list_of(args[0])).size.to_f, "")
end, "nth" => Fn.new do |args, kwargs|
no_kwargs!("list.nth", kwargs)
arity!("nth", args, 2)
items = list_of(args[0])
n = (number!("nth", args[1])).int_value("nth() index")
if n == 0
raise(SoftEvalError.new("nth() index may not be 0"))
end
idx = n > 0 ? n - 1 : items.size + n
if 0 <= idx && idx < items.size
else
raise(SoftEvalError.new("nth() index #{n} is out of bounds for a #{items.size}-element list"))
end
items[idx]
end, "index" => Fn.new do |args, kwargs|
no_kwargs!("list.index", kwargs)
arity!("index", args, 2)
items = list_of(args[0])
found = items.index(&.eq?(args[1]))
found ? Number.new((found + 1).to_f, "") : NullV.new
end, "append" => Fn.new do |args, kwargs|
no_kwargs!("list.append", kwargs)
arity!("append", args, 2, 3)
base = args[0]
sep = base.is_a?(ListV) ? base.sep : ListV::Sep::Space
bracketed = base.is_a?(ListV) && base.bracketed
ListV.new((list_of(base)) + [args[1]], sep_from(args[2]?, sep), bracketed)
end, "join" => Fn.new do |args, kwargs|
no_kwargs!("list.join", kwargs)
arity!("join", args, 2, 3)
base = args[0]
other = args[1]
sep = if base.is_a?(ListV) && base.items.size > 1
base.sep
elsif other.is_a?(ListV) && other.items.size > 1
other.sep
else
ListV::Sep::Space
end
bracketed = base.is_a?(ListV) && base.bracketed
ListV.new((list_of(base)) + (list_of(args[1])), sep_from(args[2]?, sep), bracketed)
end, "separator" => Fn.new do |args, kwargs|
no_kwargs!("list.separator", kwargs)
arity!("list-separator", args, 1)
sep = case value = args[0]
when ListV
value.sep
else
ListV::Sep::Space
end
name = case sep
in ListV::Sep::Comma
"comma"
in ListV::Sep::Space
"space"
in ListV::Sep::Slash
"slash"
end
Str.new(name, quoted: false)
end}
MAP_FNS = {"get" => Fn.new do |args, kwargs|
no_kwargs!("map.get", kwargs)
arity!("map-get", args, 2, Int32::MAX)
value = args[0].as(Value)
args[1..].each do |key|
if value.is_a?(NullV)
break
end
value = (map!("map-get", value))[key]? || NullV.new
end
value
end, "has-key" => Fn.new do |args, kwargs|
no_kwargs!("map.has-key", kwargs)
arity!("map-has-key", args, 2, Int32::MAX)
value = args[0].as(Value)
found_all = true
args[1..].each do |key|
map = (value.as?(MapV)) ||
(value.is_a?(Raw) ? (Expr.coerce(value.text)).as?(MapV) : nil)
if map && (found = map[key]?)
else
found_all = false
break
end
value = found
end
BoolV.new(found_all)
end, "keys" => Fn.new do |args, kwargs|
no_kwargs!("map.keys", kwargs)
arity!("map-keys", args, 1)
ListV.new((map!("map-keys", args[0])).entries.map(&.key), ListV::Sep::Comma)
end, "values" => Fn.new do |args, kwargs|
no_kwargs!("map.values", kwargs)
arity!("map-values", args, 1)
ListV.new((map!("map-values", args[0])).entries.map(&.value), ListV::Sep::Comma)
end, "merge" => Fn.new do |args, kwargs|
no_kwargs!("map.merge", kwargs)
arity!("map-merge", args, 2)
base = map!("map-merge", args[0])
overlay = map!("map-merge", args[1])
entries = base.entries.dup
overlay.entries.each do |entry|
if idx = entries.index() do |__arg5| __arg5.key.eq?(entry.key) end
entries[idx] = entry
else
entries << entry
end
end
MapV.new(entries)
end, "remove" => Fn.new do |args, kwargs|
no_kwargs!("map.remove", kwargs)
arity!("map-remove", args, 1, Int32::MAX)
base = map!("map-remove", args[0])
keys = args[1..]
MapV.new(base.entries.reject do |e| keys.any?(&.eq?(e.key)) end)
end}
MATH_FNS = {"div" => Fn.new do |args, kwargs|
no_kwargs!("math.div", kwargs)
arity!("math.div", args, 2)
a = number!("math.div", args[0])
b = number!("math.div", args[1])
if b.value == 0
raise(SoftEvalError.new("math.div(): division by zero"))
end
unit = if a.unit == b.unit
""
elsif b.unit.empty?
a.unit
elsif a.unit.empty?
raise(SoftEvalError.new("math.div(): can't divide unitless by #{b.unit}"))
else
raise(SoftEvalError.new("math.div(): incompatible units #{a.unit} and #{b.unit}"))
end
Number.new(a.value / b.value, unit)
end, "percentage" => Fn.new do |args, kwargs|
no_kwargs!("math.percentage", kwargs)
arity!("math.percentage", args, 1)
n = number!("math.percentage", args[0])
if n.unit.empty?
else
raise(SoftEvalError.new("math.percentage() expects a unitless number, got #{n.to_css}"))
end
Number.new(n.value * 100, "%")
end, "round" => Fn.new do |args, kwargs|
no_kwargs!("math.round", kwargs)
arity!("round", args, 1)
n = number!("round", args[0])
Number.new(n.value.round(mode: :ties_away).to_f, n.unit)
end, "ceil" => Fn.new do |args, kwargs|
no_kwargs!("math.ceil", kwargs)
arity!("ceil", args, 1)
n = number!("ceil", args[0])
Number.new(n.value.ceil.to_f, n.unit)
end, "floor" => Fn.new do |args, kwargs|
no_kwargs!("math.floor", kwargs)
arity!("floor", args, 1)
n = number!("floor", args[0])
Number.new(n.value.floor.to_f, n.unit)
end, "abs" => Fn.new do |args, kwargs|
no_kwargs!("math.abs", kwargs)
arity!("abs", args, 1)
n = number!("abs", args[0])
Number.new(n.value.abs, n.unit)
end, "min" => Fn.new do |args, kwargs|
no_kwargs!("math.min", kwargs)
arity!("min", args, 1, Int32::MAX)
numbers = args.map do |a| number!("min", a) end
same_units!("min", numbers)
winner = numbers.min_by(&.value)
Number.new(winner.value, winner.unit)
end, "max" => Fn.new do |args, kwargs|
no_kwargs!("math.max", kwargs)
arity!("max", args, 1, Int32::MAX)
numbers = args.map do |a| number!("max", a) end
same_units!("max", numbers)
winner = numbers.max_by(&.value)
Number.new(winner.value, winner.unit)
end, "clamp" => Fn.new do |args, kwargs|
no_kwargs!("math.clamp", kwargs)
arity!("math.clamp", args, 3)
numbers = args.map do |a| number!("math.clamp", a) end
same_units!("math.clamp", numbers)
low, mid, high = numbers[0], numbers[1], numbers[2]
winner = mid.value < low.value ? low : (mid.value > high.value ? high : mid)
Number.new(winner.value, winner.unit)
end, "pow" => Fn.new do |args, kwargs|
no_kwargs!("math.pow", kwargs)
arity!("math.pow", args, 2)
base = number!("math.pow", args[0])
exp = number!("math.pow", args[1])
if base.unit.empty? && exp.unit.empty?
else
raise(SoftEvalError.new("math.pow() expects unitless numbers"))
end
Number.new(base.value ** exp.value, "")
end, "sqrt" => Fn.new do |args, kwargs|
no_kwargs!("math.sqrt", kwargs)
arity!("math.sqrt", args, 1)
n = number!("math.sqrt", args[0])
if n.unit.empty?
else
raise(SoftEvalError.new("math.sqrt() expects a unitless number"))
end
if n.value < 0
raise(SoftEvalError.new("math.sqrt() of a negative number"))
end
Number.new(Math.sqrt(n.value), "")
end, "unit" => Fn.new do |args, kwargs|
no_kwargs!("math.unit", kwargs)
arity!("unit", args, 1)
Str.new((number!("unit", args[0])).unit, quoted: true)
end, "is-unitless" => Fn.new do |args, kwargs|
no_kwargs!("math.is-unitless", kwargs)
arity!("unitless", args, 1)
BoolV.new((number!("unitless", args[0])).unit.empty?)
end, "compatible" => Fn.new do |args, kwargs|
no_kwargs!("math.compatible", kwargs)
arity!("comparable", args, 2)
a = number!("comparable", args[0])
b = number!("comparable", args[1])
BoolV.new(a.compatible_unit?(b))
end}
MATH_VARS = {"pi" => "3.1415926536", "e" => "2.7182818285"}
META_FNS = {"type-of" => Fn.new do |args, kwargs|
no_kwargs!("meta.type-of", kwargs)
arity!("type-of", args, 1)
name = case value = args[0]
when Number
"number"
when ColorV
"color"
when BoolV
"bool"
when NullV
"null"
when ListV
"list"
when MapV
"map"
when Str
!value.quoted && (ColorV.parse?(value.text)) ? "color" : "string"
else
value.is_a?(Raw) ? type_of_raw(value.text) : "string"
end
Str.new(name, quoted: false)
end, "inspect" => Fn.new do |args, kwargs|
no_kwargs!("meta.inspect", kwargs)
arity!("inspect", args, 1)
Str.new(inspect_value(args[0]), quoted: false)
end}
MODULE_TABLES = {"math" => {MATH_FNS, MATH_VARS}, "string" => {STRING_FNS, {} of String => String}, "list" => {LIST_FNS, {} of String => String}, "map" => {MAP_FNS, {} of String => String}, "meta" => {META_FNS, {} of String => String}, "color" => {COLOR_FNS, {} of String => String}}
sass:<name> module tables: {functions, variables}.
RGBA_FN = Fn.new do |args, kwargs|
bound = shadowed_bind!("rgba", args, kwargs, ["color", "alpha"], required: 2)
color = shadowed_color!(bound[0].as(Value))
color.with_alpha(alpha!("rgba", bound[1].as(Value)))
end
rgba($color, $alpha) / rgb($color, $alpha) — the Sass-only
two-argument spelling. Every other shape (rgb(0, 0, 0),
rgb(0 0 0 / 50%), relative color syntax) is real CSS, so those
decline with ShapeMismatch and reconstruct verbatim instead of
unwinding the whole declaration.
SCALE_KWARGS = ["red", "green", "blue", "saturation", "lightness", "alpha"]
STRING_FNS = {"quote" => Fn.new do |args, kwargs|
no_kwargs!("string.quote", kwargs)
arity!("quote", args, 1)
s = string!("quote", args[0])
Str.new(s.text, quoted: true)
end, "unquote" => Fn.new do |args, kwargs|
no_kwargs!("string.unquote", kwargs)
arity!("unquote", args, 1)
s = string!("unquote", args[0])
Str.new(s.text, quoted: false)
end, "length" => Fn.new do |args, kwargs|
no_kwargs!("string.length", kwargs)
arity!("str-length", args, 1)
Number.new((string!("str-length", args[0])).text.size.to_f, "")
end, "index" => Fn.new do |args, kwargs|
no_kwargs!("string.index", kwargs)
arity!("str-index", args, 2)
haystack = (string!("str-index", args[0])).text
needle = (string!("str-index", args[1])).text
idx = haystack.index(needle)
idx ? Number.new((idx + 1).to_f, "") : NullV.new
end, "slice" => Fn.new do |args, kwargs|
no_kwargs!("string.slice", kwargs)
arity!("str-slice", args, 2, 3)
text = (string!("str-slice", args[0])).text
quoted = (string!("str-slice", args[0])).quoted
start_at = (number!("str-slice", args[1])).int_value("str-slice() start")
end_at = args[2]? ? (number!("str-slice", args[2])).int_value("str-slice() end") : -1
size = text.size
from = start_at < 0 ? Math.max(size + start_at, 0) : Math.max(start_at - 1, 0)
to = end_at < 0 ? size + end_at : Math.min(end_at - 1, size - 1)
sliced = from > to ? "" : text[from..to]
Str.new(sliced, quoted: quoted)
end, "to-upper-case" => Fn.new do |args, kwargs|
no_kwargs!("string.to-upper-case", kwargs)
arity!("to-upper-case", args, 1)
s = string!("to-upper-case", args[0])
Str.new(ascii_upcase(s.text), quoted: s.quoted, quote_char: s.quote_char)
end, "to-lower-case" => Fn.new do |args, kwargs|
no_kwargs!("string.to-lower-case", kwargs)
arity!("to-lower-case", args, 1)
s = string!("to-lower-case", args[0])
Str.new(ascii_downcase(s.text), quoted: s.quoted, quote_char: s.quote_char)
end}
Class methods
inspect_value(value : Value) : String
Source