Skip to content

Commit d4990bb

Browse files
committed
add << merge key to "spread" mappings into a mapping.
In order to gracefully compose data structures, you need a way to insert an entire set of mappings into another at a single place. This is equivalent to the javascript `...` operator. YAML allows support for this with the language independent type "merge-key" feature. This implements the spec here https://yaml.org/type/merge.html Thus evaluating this example: ```yaml composed: <<: one: 1 two: 2 <<: three: 3 four: 4 <<: - five: 5 - six: 6 ``` is: ```yaml composed: one: 1 two: 2 three: 3 four: 4 five: 5 six: 6 ``` Although ubiquitous, this syntax is officially deprecated and will be replaced by something else in YAML 1.3 but nobody is for sure what that will be.
1 parent 63b63f9 commit d4990bb

File tree

2 files changed

+87
-1
lines changed

2 files changed

+87
-1
lines changed

evaluate.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,32 @@ export function createYSEnv(parent = global): PSEnv {
8585
} else if (value.type === "map") {
8686
let entries: [PSMapKey, PSValue][] = [];
8787
for (let [k, v] of value.value.entries()) {
88-
entries.push([k, yield* env.eval(v)]);
88+
let target = yield* env.eval(v);
89+
if (k.type === "string" && !k.quote && k.value == "<<") {
90+
if (target.type === "map") {
91+
for (let [subkey, subvalue] of target.value.entries()) {
92+
entries.push([subkey, subvalue]);
93+
}
94+
} else if (target.type === "list") {
95+
for (let item of target.value) {
96+
if (item.type === "map") {
97+
for (let [subkey, subvalue] of item.value.entries()) {
98+
entries.push([subkey, subvalue]);
99+
}
100+
} else {
101+
throw new Error(
102+
`merge key value must be either a map, or a sequence of maps`,
103+
);
104+
}
105+
}
106+
} else {
107+
throw new Error(
108+
`merge key value must be either a map, or a sequence of maps`,
109+
);
110+
}
111+
} else {
112+
entries.push([k, target]);
113+
}
89114
}
90115
return { type: "map", value: new Map(entries) };
91116
} else if (value.type === "list") {

test/merge-key.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { describe, expect, it } from "./suite.ts";
2+
3+
import * as ps from "../mod.ts";
4+
import { lookup$ } from "../psmap.ts";
5+
6+
// https://yaml.org/type/merge.html
7+
8+
describe("merge keys", () => {
9+
it("mix in all the properties of a map", async () => {
10+
let interp = ps.createPlatformScript();
11+
let program = ps.parse(`
12+
<<:
13+
one: 1
14+
two: 2
15+
`);
16+
let map = await interp.eval(program) as ps.PSMap;
17+
expect(lookup$("one", map)).toEqual(ps.number(1));
18+
expect(lookup$("two", map)).toEqual(ps.number(2));
19+
});
20+
it("mix in all the maps in a seq", async () => {
21+
let interp = ps.createPlatformScript();
22+
let program = ps.parse(`
23+
<<:
24+
-
25+
one: 1
26+
two: 2
27+
-
28+
three: 3
29+
four: 4
30+
`);
31+
let map = await interp.eval(program) as ps.PSMap;
32+
expect(lookup$("one", map)).toEqual(ps.number(1));
33+
expect(lookup$("two", map)).toEqual(ps.number(2));
34+
expect(lookup$("three", map)).toEqual(ps.number(3));
35+
expect(lookup$("four", map)).toEqual(ps.number(4));
36+
});
37+
it("throw an error if the mapping points to a non-collection", async () => {
38+
// TODO: this is a type error when we implement type system.
39+
let program = ps.parse(`<<: not a map`);
40+
let interp = ps.createPlatformScript();
41+
try {
42+
await interp.eval(program);
43+
throw new Error(
44+
"expected mapping a non-collection to fail, but it did not",
45+
);
46+
} catch (error) {
47+
expect(error.message).toMatch(/merge key/);
48+
}
49+
});
50+
it("can invoke a function", async () => {
51+
let program = ps.parse(`
52+
$let:
53+
id(x): $x
54+
$do: {<<: { $id: { hello: world } } }
55+
`);
56+
let interp = ps.createPlatformScript();
57+
let map = await interp.eval(program) as ps.PSMap;
58+
expect(lookup$("hello", map)).toEqual(ps.string("world"));
59+
});
60+
61+
});

0 commit comments

Comments
 (0)