1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
use std::iter::Peekable;

use crate::builtin::builtin_imports::*;

use crate::builtin::{
    map::{map_get, map_has_key, map_keys, map_merge, map_remove, map_set, map_values},
    modules::Module,
};

fn deep_merge_impl(map1: SassMap, map2: SassMap) -> SassMap {
    if map1.is_empty() {
        return map2;
    }
    if map2.is_empty() {
        return map1;
    }

    let mut result = map1;

    for (key, value) in map2 {
        match result.get_ref(&key.node).and_then(Value::try_map) {
            Some(result_map) => match value.try_map() {
                Some(value_map) => {
                    let merged = deep_merge_impl(result_map, value_map);
                    result.insert(key, Value::Map(merged));
                }
                None => {
                    result.insert(key, value);
                }
            },
            None => {
                result.insert(key, value);
            }
        }
    }

    result
}

fn deep_merge(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
    args.max_args(2)?;

    let span = args.span();

    let map1 = args
        .get_err(0, "map1")?
        .assert_map_with_name("map1", span)?;

    let map2 = args
        .get_err(1, "map2")?
        .assert_map_with_name("map2", span)?;

    Ok(Value::Map(deep_merge_impl(map1, map2)))
}

fn deep_remove(mut args: ArgumentResult, _: &mut Visitor) -> SassResult<Value> {
    let span = args.span();

    let map = args.get_err(0, "map")?.assert_map_with_name("map", span)?;

    let key = args.get_err(1, "key")?;
    let keys = args.get_variadic()?.into_iter().map(|arg| arg.node);
    let mut keys = std::iter::once(key).chain(keys).collect::<Vec<_>>();

    let last = keys.pop();

    let map = modify_map(
        map,
        keys.into_iter(),
        |value| {
            let last = match last.as_ref() {
                Some(v) => v,
                None => return value,
            };

            match value.try_map() {
                Some(mut nested_map) if nested_map.contains(last) => {
                    nested_map.remove(last);
                    Value::Map(nested_map)
                }
                Some(..) | None => value,
            }
        },
        false,
        span,
    );

    Ok(map)
}

fn modify_map(
    map: SassMap,
    keys: impl Iterator<Item = Value>,
    modify: impl Fn(Value) -> Value,
    // default=true
    add_nesting: bool,
    span: Span,
) -> Value {
    let mut keys = keys.peekable();
    fn modify_nested_map(
        mut mutable_map: SassMap,
        mut keys: Peekable<impl Iterator<Item = Value>>,
        add_nesting: bool,
        span: Span,
        modify: impl Fn(Value) -> Value,
    ) -> SassMap {
        let key = keys.next().unwrap();

        if keys.peek().is_none() {
            let value = modify(mutable_map.get_ref(&key).cloned().unwrap_or(Value::Null));
            mutable_map.insert(key.span(span), value);
            return mutable_map;
        }

        let nested_map = mutable_map.get_ref(&key).and_then(|v| v.try_map());

        if nested_map.is_none() && !add_nesting {
            return mutable_map;
        }

        mutable_map.insert(
            key.span(span),
            Value::Map(modify_nested_map(
                nested_map.unwrap_or_default(),
                keys,
                add_nesting,
                span,
                modify,
            )),
        );

        mutable_map
    }

    if keys.peek().is_some() {
        Value::Map(modify_nested_map(map, keys, add_nesting, span, modify))
    } else {
        modify(Value::Map(map))
    }
}

pub(crate) fn declare(f: &mut Module) {
    f.insert_builtin("get", map_get);
    f.insert_builtin("has-key", map_has_key);
    f.insert_builtin("keys", map_keys);
    f.insert_builtin("merge", map_merge);
    f.insert_builtin("remove", map_remove);
    f.insert_builtin("values", map_values);
    f.insert_builtin("set", map_set);
    f.insert_builtin("deep-merge", deep_merge);
    f.insert_builtin("deep-remove", deep_remove);
}