You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
147 lines
5.0 KiB
147 lines
5.0 KiB
import { createNode } from '../doc/createNode.js'; |
|
import { NodeBase, isNode, isPair, isCollection, isScalar } from './Node.js'; |
|
|
|
function collectionFromPath(schema, path, value) { |
|
let v = value; |
|
for (let i = path.length - 1; i >= 0; --i) { |
|
const k = path[i]; |
|
if (typeof k === 'number' && Number.isInteger(k) && k >= 0) { |
|
const a = []; |
|
a[k] = v; |
|
v = a; |
|
} |
|
else { |
|
v = new Map([[k, v]]); |
|
} |
|
} |
|
return createNode(v, undefined, { |
|
aliasDuplicateObjects: false, |
|
keepUndefined: false, |
|
onAnchor: () => { |
|
throw new Error('This should not happen, please report a bug.'); |
|
}, |
|
schema, |
|
sourceObjects: new Map() |
|
}); |
|
} |
|
// Type guard is intentionally a little wrong so as to be more useful, |
|
// as it does not cover untypable empty non-string iterables (e.g. []). |
|
const isEmptyPath = (path) => path == null || |
|
(typeof path === 'object' && !!path[Symbol.iterator]().next().done); |
|
class Collection extends NodeBase { |
|
constructor(type, schema) { |
|
super(type); |
|
Object.defineProperty(this, 'schema', { |
|
value: schema, |
|
configurable: true, |
|
enumerable: false, |
|
writable: true |
|
}); |
|
} |
|
/** |
|
* Create a copy of this collection. |
|
* |
|
* @param schema - If defined, overwrites the original's schema |
|
*/ |
|
clone(schema) { |
|
const copy = Object.create(Object.getPrototypeOf(this), Object.getOwnPropertyDescriptors(this)); |
|
if (schema) |
|
copy.schema = schema; |
|
copy.items = copy.items.map(it => isNode(it) || isPair(it) ? it.clone(schema) : it); |
|
if (this.range) |
|
copy.range = this.range.slice(); |
|
return copy; |
|
} |
|
/** |
|
* Adds a value to the collection. For `!!map` and `!!omap` the value must |
|
* be a Pair instance or a `{ key, value }` object, which may not have a key |
|
* that already exists in the map. |
|
*/ |
|
addIn(path, value) { |
|
if (isEmptyPath(path)) |
|
this.add(value); |
|
else { |
|
const [key, ...rest] = path; |
|
const node = this.get(key, true); |
|
if (isCollection(node)) |
|
node.addIn(rest, value); |
|
else if (node === undefined && this.schema) |
|
this.set(key, collectionFromPath(this.schema, rest, value)); |
|
else |
|
throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`); |
|
} |
|
} |
|
/** |
|
* Removes a value from the collection. |
|
* @returns `true` if the item was found and removed. |
|
*/ |
|
deleteIn(path) { |
|
const [key, ...rest] = path; |
|
if (rest.length === 0) |
|
return this.delete(key); |
|
const node = this.get(key, true); |
|
if (isCollection(node)) |
|
return node.deleteIn(rest); |
|
else |
|
throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`); |
|
} |
|
/** |
|
* Returns item at `key`, or `undefined` if not found. By default unwraps |
|
* scalar values from their surrounding node; to disable set `keepScalar` to |
|
* `true` (collections are always returned intact). |
|
*/ |
|
getIn(path, keepScalar) { |
|
const [key, ...rest] = path; |
|
const node = this.get(key, true); |
|
if (rest.length === 0) |
|
return !keepScalar && isScalar(node) ? node.value : node; |
|
else |
|
return isCollection(node) ? node.getIn(rest, keepScalar) : undefined; |
|
} |
|
hasAllNullValues(allowScalar) { |
|
return this.items.every(node => { |
|
if (!isPair(node)) |
|
return false; |
|
const n = node.value; |
|
return (n == null || |
|
(allowScalar && |
|
isScalar(n) && |
|
n.value == null && |
|
!n.commentBefore && |
|
!n.comment && |
|
!n.tag)); |
|
}); |
|
} |
|
/** |
|
* Checks if the collection includes a value with the key `key`. |
|
*/ |
|
hasIn(path) { |
|
const [key, ...rest] = path; |
|
if (rest.length === 0) |
|
return this.has(key); |
|
const node = this.get(key, true); |
|
return isCollection(node) ? node.hasIn(rest) : false; |
|
} |
|
/** |
|
* Sets a value in this collection. For `!!set`, `value` needs to be a |
|
* boolean to add/remove the item from the set. |
|
*/ |
|
setIn(path, value) { |
|
const [key, ...rest] = path; |
|
if (rest.length === 0) { |
|
this.set(key, value); |
|
} |
|
else { |
|
const node = this.get(key, true); |
|
if (isCollection(node)) |
|
node.setIn(rest, value); |
|
else if (node === undefined && this.schema) |
|
this.set(key, collectionFromPath(this.schema, rest, value)); |
|
else |
|
throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`); |
|
} |
|
} |
|
} |
|
Collection.maxFlowStringSingleLineLength = 60; |
|
|
|
export { Collection, collectionFromPath, isEmptyPath };
|
|
|