support builtin map.set
This commit is contained in:
parent
81d5dbbb7e
commit
142bc9da10
@ -1,5 +1,3 @@
|
|||||||
use std::mem;
|
|
||||||
|
|
||||||
use super::{Builtin, GlobalFunctionMap};
|
use super::{Builtin, GlobalFunctionMap};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -93,7 +91,7 @@ pub(crate) fn map_merge(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResu
|
|||||||
return Err(("Expected $args to contain a key.", args.span()).into());
|
return Err(("Expected $args to contain a key.", args.span()).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let last_position = args.len().saturating_sub(1);
|
let map2_position = args.len().saturating_sub(1);
|
||||||
|
|
||||||
let mut map1 = match args.get_err(0, "map1")? {
|
let mut map1 = match args.get_err(0, "map1")? {
|
||||||
Value::Map(m) => m,
|
Value::Map(m) => m,
|
||||||
@ -108,7 +106,7 @@ pub(crate) fn map_merge(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResu
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut map2 = match args.get_err(last_position, "map2")? {
|
let map2 = match args.get_err(map2_position, "map2")? {
|
||||||
Value::Map(m) => m,
|
Value::Map(m) => m,
|
||||||
Value::List(v, ..) if v.is_empty() => SassMap::new(),
|
Value::List(v, ..) if v.is_empty() => SassMap::new(),
|
||||||
Value::ArgList(v) if v.is_empty() => SassMap::new(),
|
Value::ArgList(v) if v.is_empty() => SassMap::new(),
|
||||||
@ -121,23 +119,43 @@ pub(crate) fn map_merge(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResu
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut keys = args.get_variadic()?;
|
let keys = args.get_variadic()?;
|
||||||
|
|
||||||
if keys.is_empty() {
|
if keys.is_empty() {
|
||||||
map1.merge(map2);
|
map1.merge(map2);
|
||||||
} else {
|
} else {
|
||||||
while let Some(key) = keys.pop() {
|
let mut current_map = map1.clone();
|
||||||
let mut new_map = SassMap::new();
|
let mut map_queue = Vec::new();
|
||||||
new_map.insert(key.node, Value::Map(mem::take(&mut map2)));
|
|
||||||
map2 = new_map;
|
for key in keys {
|
||||||
|
match current_map.get(&key) {
|
||||||
|
Some(Value::Map(m1)) => {
|
||||||
|
current_map = m1.clone();
|
||||||
|
map_queue.push((key, m1));
|
||||||
|
}
|
||||||
|
Some(..) | None => {
|
||||||
|
current_map = SassMap::new();
|
||||||
|
map_queue.push((key, SassMap::new()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (key, value) in map2 {
|
match map_queue.last_mut() {
|
||||||
// if they are two maps sharing a key, merge the keys
|
Some((_, m)) => {
|
||||||
if let (Some(Value::Map(map1)), Value::Map(map2)) = (map1.get_mut(&key), &value) {
|
m.merge(map2);
|
||||||
map1.merge(map2.clone());
|
}
|
||||||
} else {
|
None => unreachable!(),
|
||||||
map1.insert(key, value);
|
};
|
||||||
|
|
||||||
|
while let Some((key, queued_map)) = map_queue.pop() {
|
||||||
|
match map_queue.last_mut() {
|
||||||
|
Some((_, map)) => {
|
||||||
|
map.insert(key.node, Value::Map(queued_map));
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
map1.insert(key.node, Value::Map(queued_map));
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -165,6 +183,68 @@ pub(crate) fn map_remove(mut args: CallArgs, parser: &mut Parser<'_>) -> SassRes
|
|||||||
Ok(Value::Map(map))
|
Ok(Value::Map(map))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn map_set(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||||
|
let key_position = args.len().saturating_sub(2);
|
||||||
|
let value_position = args.len().saturating_sub(1);
|
||||||
|
|
||||||
|
let mut map = match args.get_err(0, "map")? {
|
||||||
|
Value::Map(m) => m,
|
||||||
|
Value::List(v, ..) if v.is_empty() => SassMap::new(),
|
||||||
|
Value::ArgList(v) if v.is_empty() => SassMap::new(),
|
||||||
|
v => {
|
||||||
|
return Err((
|
||||||
|
format!("$map: {} is not a map.", v.inspect(args.span())?),
|
||||||
|
args.span(),
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let key = args.get_err(key_position, "key")?;
|
||||||
|
let value = args.get_err(value_position, "value")?;
|
||||||
|
|
||||||
|
let keys = args.get_variadic()?;
|
||||||
|
|
||||||
|
if keys.is_empty() {
|
||||||
|
map.insert(key, value);
|
||||||
|
} else {
|
||||||
|
let mut current_map = map.clone();
|
||||||
|
let mut map_queue = Vec::new();
|
||||||
|
|
||||||
|
for key in keys {
|
||||||
|
match current_map.get(&key) {
|
||||||
|
Some(Value::Map(m1)) => {
|
||||||
|
current_map = m1.clone();
|
||||||
|
map_queue.push((key, m1));
|
||||||
|
}
|
||||||
|
Some(..) | None => {
|
||||||
|
current_map = SassMap::new();
|
||||||
|
map_queue.push((key, SassMap::new()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match map_queue.last_mut() {
|
||||||
|
Some((_, m)) => m.insert(key, value),
|
||||||
|
None => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
while let Some((key, queued_map)) = map_queue.pop() {
|
||||||
|
match map_queue.last_mut() {
|
||||||
|
Some((_, next_map)) => {
|
||||||
|
next_map.insert(key.node, Value::Map(queued_map));
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
map.insert(key.node, Value::Map(queued_map));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Value::Map(map))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn declare(f: &mut GlobalFunctionMap) {
|
pub(crate) fn declare(f: &mut GlobalFunctionMap) {
|
||||||
f.insert("map-get", Builtin::new(map_get));
|
f.insert("map-get", Builtin::new(map_get));
|
||||||
f.insert("map-has-key", Builtin::new(map_has_key));
|
f.insert("map-has-key", Builtin::new(map_has_key));
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::builtin::{
|
use crate::builtin::{
|
||||||
map::{map_get, map_has_key, map_keys, map_merge, map_remove, map_values},
|
map::{map_get, map_has_key, map_keys, map_merge, map_remove, map_set, map_values},
|
||||||
modules::Module,
|
modules::Module,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -10,4 +10,5 @@ pub(crate) fn declare(f: &mut Module) {
|
|||||||
f.insert_builtin("merge", map_merge);
|
f.insert_builtin("merge", map_merge);
|
||||||
f.insert_builtin("remove", map_remove);
|
f.insert_builtin("remove", map_remove);
|
||||||
f.insert_builtin("values", map_values);
|
f.insert_builtin("values", map_values);
|
||||||
|
f.insert_builtin("set", map_set);
|
||||||
}
|
}
|
||||||
|
@ -547,14 +547,13 @@ impl Color {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if hue < Number::small_ratio(1, 6) {
|
if hue < Number::small_ratio(1, 6) {
|
||||||
return m1.clone() + (m2 - m1) * hue * Number::from(6);
|
m1.clone() + (m2 - m1) * hue * Number::from(6)
|
||||||
} else if hue < Number::small_ratio(1, 2) {
|
} else if hue < Number::small_ratio(1, 2) {
|
||||||
return m2;
|
m2
|
||||||
} else if hue < Number::small_ratio(2, 3) {
|
} else if hue < Number::small_ratio(2, 3) {
|
||||||
return m1.clone()
|
m1.clone() + (m2 - m1) * (Number::small_ratio(2, 3) - hue) * Number::from(6)
|
||||||
+ (m2 - m1) * (Number::small_ratio(2, 3) - hue) * Number::from(6);
|
|
||||||
} else {
|
} else {
|
||||||
return m1;
|
m1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,16 +51,6 @@ impl SassMap {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_mut(&mut self, key: &Value) -> Option<&mut Value> {
|
|
||||||
for (k, v) in &mut self.0 {
|
|
||||||
if k == key {
|
|
||||||
return Some(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove(&mut self, key: &Value) {
|
pub fn remove(&mut self, key: &Value) {
|
||||||
self.0.retain(|(ref k, ..)| k.not_equals(key));
|
self.0.retain(|(ref k, ..)| k.not_equals(key));
|
||||||
}
|
}
|
||||||
|
60
tests/map.rs
60
tests/map.rs
@ -120,6 +120,66 @@ test!(
|
|||||||
"a {b: inspect(map-merge((c: (d: (e: (f: (g: h))))), c, d, e, f, (g: 1)))}",
|
"a {b: inspect(map-merge((c: (d: (e: (f: (g: h))))), c, d, e, f, (g: 1)))}",
|
||||||
"a {\n b: (c: (d: (e: (f: (g: 1)))));\n}\n"
|
"a {\n b: (c: (d: (e: (f: (g: 1)))));\n}\n"
|
||||||
);
|
);
|
||||||
|
test!(
|
||||||
|
map_set_nested_empty,
|
||||||
|
"@use 'sass:map'; a {b: inspect(map.set((c: ()), c, d, e, f))}",
|
||||||
|
"a {\n b: (c: (d: (e: f)));\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
map_set_update_existing,
|
||||||
|
"@use 'sass:map'; a {b: inspect(map.set((c: (d: e)), c, d, f))}",
|
||||||
|
"a {\n b: (c: (d: f));\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
map_set_new_key,
|
||||||
|
"@use 'sass:map'; a {b: inspect(map.set((c: (d: e)), c, f, g))}",
|
||||||
|
"a {\n b: (c: (d: e, f: g));\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
map_set_value_is_not_map,
|
||||||
|
"@use 'sass:map'; a {b: inspect(map.set((c: 1), c, d, f))}",
|
||||||
|
"a {\n b: (c: (d: f));\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
map_merge_merge_into_map_with_many_keys,
|
||||||
|
r#"
|
||||||
|
@use "sass:map";
|
||||||
|
|
||||||
|
$fonts: (
|
||||||
|
"Helvetica": (
|
||||||
|
"weights": (
|
||||||
|
"regular": 400,
|
||||||
|
"medium": 500,
|
||||||
|
"bold": 700
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inspect(map.merge($fonts, "Helvetica", "weights", "regular", (a: 300)));
|
||||||
|
}"#,
|
||||||
|
"a {\n color: (\"Helvetica\": (\"weights\": (\"regular\": (a: 300), \"medium\": 500, \"bold\": 700)));\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
map_merge_nested,
|
||||||
|
r#"
|
||||||
|
@use "sass:map";
|
||||||
|
|
||||||
|
$fonts: (
|
||||||
|
"Helvetica": (
|
||||||
|
"weights": (
|
||||||
|
"regular": 400,
|
||||||
|
"medium": 500,
|
||||||
|
"bold": 700
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inspect(map.set($fonts, "Helvetica", "weights", "regular", 300));
|
||||||
|
}"#,
|
||||||
|
"a {\n color: (\"Helvetica\": (\"weights\": (\"regular\": 300, \"medium\": 500, \"bold\": 700)));\n}\n"
|
||||||
|
);
|
||||||
error!(
|
error!(
|
||||||
map_merge_map1_non_map,
|
map_merge_map1_non_map,
|
||||||
"a {\n color: map-merge(foo, (a: b));\n}\n", "Error: $map1: foo is not a map."
|
"a {\n color: map-merge(foo, (a: b));\n}\n", "Error: $map1: foo is not a map."
|
||||||
|
Loading…
x
Reference in New Issue
Block a user