1 | 'use strict';
|
---|
2 |
|
---|
3 | var createNode = require('../doc/createNode.js');
|
---|
4 | var identity = require('./identity.js');
|
---|
5 | var Node = require('./Node.js');
|
---|
6 |
|
---|
7 | function collectionFromPath(schema, path, value) {
|
---|
8 | let v = value;
|
---|
9 | for (let i = path.length - 1; i >= 0; --i) {
|
---|
10 | const k = path[i];
|
---|
11 | if (typeof k === 'number' && Number.isInteger(k) && k >= 0) {
|
---|
12 | const a = [];
|
---|
13 | a[k] = v;
|
---|
14 | v = a;
|
---|
15 | }
|
---|
16 | else {
|
---|
17 | v = new Map([[k, v]]);
|
---|
18 | }
|
---|
19 | }
|
---|
20 | return createNode.createNode(v, undefined, {
|
---|
21 | aliasDuplicateObjects: false,
|
---|
22 | keepUndefined: false,
|
---|
23 | onAnchor: () => {
|
---|
24 | throw new Error('This should not happen, please report a bug.');
|
---|
25 | },
|
---|
26 | schema,
|
---|
27 | sourceObjects: new Map()
|
---|
28 | });
|
---|
29 | }
|
---|
30 | // Type guard is intentionally a little wrong so as to be more useful,
|
---|
31 | // as it does not cover untypable empty non-string iterables (e.g. []).
|
---|
32 | const isEmptyPath = (path) => path == null ||
|
---|
33 | (typeof path === 'object' && !!path[Symbol.iterator]().next().done);
|
---|
34 | class Collection extends Node.NodeBase {
|
---|
35 | constructor(type, schema) {
|
---|
36 | super(type);
|
---|
37 | Object.defineProperty(this, 'schema', {
|
---|
38 | value: schema,
|
---|
39 | configurable: true,
|
---|
40 | enumerable: false,
|
---|
41 | writable: true
|
---|
42 | });
|
---|
43 | }
|
---|
44 | /**
|
---|
45 | * Create a copy of this collection.
|
---|
46 | *
|
---|
47 | * @param schema - If defined, overwrites the original's schema
|
---|
48 | */
|
---|
49 | clone(schema) {
|
---|
50 | const copy = Object.create(Object.getPrototypeOf(this), Object.getOwnPropertyDescriptors(this));
|
---|
51 | if (schema)
|
---|
52 | copy.schema = schema;
|
---|
53 | copy.items = copy.items.map(it => identity.isNode(it) || identity.isPair(it) ? it.clone(schema) : it);
|
---|
54 | if (this.range)
|
---|
55 | copy.range = this.range.slice();
|
---|
56 | return copy;
|
---|
57 | }
|
---|
58 | /**
|
---|
59 | * Adds a value to the collection. For `!!map` and `!!omap` the value must
|
---|
60 | * be a Pair instance or a `{ key, value }` object, which may not have a key
|
---|
61 | * that already exists in the map.
|
---|
62 | */
|
---|
63 | addIn(path, value) {
|
---|
64 | if (isEmptyPath(path))
|
---|
65 | this.add(value);
|
---|
66 | else {
|
---|
67 | const [key, ...rest] = path;
|
---|
68 | const node = this.get(key, true);
|
---|
69 | if (identity.isCollection(node))
|
---|
70 | node.addIn(rest, value);
|
---|
71 | else if (node === undefined && this.schema)
|
---|
72 | this.set(key, collectionFromPath(this.schema, rest, value));
|
---|
73 | else
|
---|
74 | throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
|
---|
75 | }
|
---|
76 | }
|
---|
77 | /**
|
---|
78 | * Removes a value from the collection.
|
---|
79 | * @returns `true` if the item was found and removed.
|
---|
80 | */
|
---|
81 | deleteIn(path) {
|
---|
82 | const [key, ...rest] = path;
|
---|
83 | if (rest.length === 0)
|
---|
84 | return this.delete(key);
|
---|
85 | const node = this.get(key, true);
|
---|
86 | if (identity.isCollection(node))
|
---|
87 | return node.deleteIn(rest);
|
---|
88 | else
|
---|
89 | throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
|
---|
90 | }
|
---|
91 | /**
|
---|
92 | * Returns item at `key`, or `undefined` if not found. By default unwraps
|
---|
93 | * scalar values from their surrounding node; to disable set `keepScalar` to
|
---|
94 | * `true` (collections are always returned intact).
|
---|
95 | */
|
---|
96 | getIn(path, keepScalar) {
|
---|
97 | const [key, ...rest] = path;
|
---|
98 | const node = this.get(key, true);
|
---|
99 | if (rest.length === 0)
|
---|
100 | return !keepScalar && identity.isScalar(node) ? node.value : node;
|
---|
101 | else
|
---|
102 | return identity.isCollection(node) ? node.getIn(rest, keepScalar) : undefined;
|
---|
103 | }
|
---|
104 | hasAllNullValues(allowScalar) {
|
---|
105 | return this.items.every(node => {
|
---|
106 | if (!identity.isPair(node))
|
---|
107 | return false;
|
---|
108 | const n = node.value;
|
---|
109 | return (n == null ||
|
---|
110 | (allowScalar &&
|
---|
111 | identity.isScalar(n) &&
|
---|
112 | n.value == null &&
|
---|
113 | !n.commentBefore &&
|
---|
114 | !n.comment &&
|
---|
115 | !n.tag));
|
---|
116 | });
|
---|
117 | }
|
---|
118 | /**
|
---|
119 | * Checks if the collection includes a value with the key `key`.
|
---|
120 | */
|
---|
121 | hasIn(path) {
|
---|
122 | const [key, ...rest] = path;
|
---|
123 | if (rest.length === 0)
|
---|
124 | return this.has(key);
|
---|
125 | const node = this.get(key, true);
|
---|
126 | return identity.isCollection(node) ? node.hasIn(rest) : false;
|
---|
127 | }
|
---|
128 | /**
|
---|
129 | * Sets a value in this collection. For `!!set`, `value` needs to be a
|
---|
130 | * boolean to add/remove the item from the set.
|
---|
131 | */
|
---|
132 | setIn(path, value) {
|
---|
133 | const [key, ...rest] = path;
|
---|
134 | if (rest.length === 0) {
|
---|
135 | this.set(key, value);
|
---|
136 | }
|
---|
137 | else {
|
---|
138 | const node = this.get(key, true);
|
---|
139 | if (identity.isCollection(node))
|
---|
140 | node.setIn(rest, value);
|
---|
141 | else if (node === undefined && this.schema)
|
---|
142 | this.set(key, collectionFromPath(this.schema, rest, value));
|
---|
143 | else
|
---|
144 | throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
|
---|
145 | }
|
---|
146 | }
|
---|
147 | }
|
---|
148 | Collection.maxFlowStringSingleLineLength = 60;
|
---|
149 |
|
---|
150 | exports.Collection = Collection;
|
---|
151 | exports.collectionFromPath = collectionFromPath;
|
---|
152 | exports.isEmptyPath = isEmptyPath;
|
---|