| import { Alias } from '../nodes/Alias.js'; | |
| import { isEmptyPath, collectionFromPath } from '../nodes/Collection.js'; | |
| import { NODE_TYPE, DOC, isNode, isCollection, isScalar } from '../nodes/identity.js'; | |
| import { Pair } from '../nodes/Pair.js'; | |
| import { toJS } from '../nodes/toJS.js'; | |
| import { Schema } from '../schema/Schema.js'; | |
| import { stringifyDocument } from '../stringify/stringifyDocument.js'; | |
| import { anchorNames, findNewAnchor, createNodeAnchors } from './anchors.js'; | |
| import { applyReviver } from './applyReviver.js'; | |
| import { createNode } from './createNode.js'; | |
| import { Directives } from './directives.js'; | |
| class Document { | |
| constructor(value, replacer, options) { | |
| /** A comment before this Document */ | |
| this.commentBefore = null; | |
| /** A comment immediately after this Document */ | |
| this.comment = null; | |
| /** Errors encountered during parsing. */ | |
| this.errors = []; | |
| /** Warnings encountered during parsing. */ | |
| this.warnings = []; | |
| Object.defineProperty(this, NODE_TYPE, { value: DOC }); | |
| let _replacer = null; | |
| if (typeof replacer === 'function' || Array.isArray(replacer)) { | |
| _replacer = replacer; | |
| } | |
| else if (options === undefined && replacer) { | |
| options = replacer; | |
| replacer = undefined; | |
| } | |
| const opt = Object.assign({ | |
| intAsBigInt: false, | |
| keepSourceTokens: false, | |
| logLevel: 'warn', | |
| prettyErrors: true, | |
| strict: true, | |
| stringKeys: false, | |
| uniqueKeys: true, | |
| version: '1.2' | |
| }, options); | |
| this.options = opt; | |
| let { version } = opt; | |
| if (options?._directives) { | |
| this.directives = options._directives.atDocument(); | |
| if (this.directives.yaml.explicit) | |
| version = this.directives.yaml.version; | |
| } | |
| else | |
| this.directives = new Directives({ version }); | |
| this.setSchema(version, options); | |
| // @ts-expect-error We can't really know that this matches Contents. | |
| this.contents = | |
| value === undefined ? null : this.createNode(value, _replacer, options); | |
| } | |
| /** | |
| * Create a deep copy of this Document and its contents. | |
| * | |
| * Custom Node values that inherit from `Object` still refer to their original instances. | |
| */ | |
| clone() { | |
| const copy = Object.create(Document.prototype, { | |
| [NODE_TYPE]: { value: DOC } | |
| }); | |
| copy.commentBefore = this.commentBefore; | |
| copy.comment = this.comment; | |
| copy.errors = this.errors.slice(); | |
| copy.warnings = this.warnings.slice(); | |
| copy.options = Object.assign({}, this.options); | |
| if (this.directives) | |
| copy.directives = this.directives.clone(); | |
| copy.schema = this.schema.clone(); | |
| // @ts-expect-error We can't really know that this matches Contents. | |
| copy.contents = isNode(this.contents) | |
| ? this.contents.clone(copy.schema) | |
| : this.contents; | |
| if (this.range) | |
| copy.range = this.range.slice(); | |
| return copy; | |
| } | |
| /** Adds a value to the document. */ | |
| add(value) { | |
| if (assertCollection(this.contents)) | |
| this.contents.add(value); | |
| } | |
| /** Adds a value to the document. */ | |
| addIn(path, value) { | |
| if (assertCollection(this.contents)) | |
| this.contents.addIn(path, value); | |
| } | |
| /** | |
| * Create a new `Alias` node, ensuring that the target `node` has the required anchor. | |
| * | |
| * If `node` already has an anchor, `name` is ignored. | |
| * Otherwise, the `node.anchor` value will be set to `name`, | |
| * or if an anchor with that name is already present in the document, | |
| * `name` will be used as a prefix for a new unique anchor. | |
| * If `name` is undefined, the generated anchor will use 'a' as a prefix. | |
| */ | |
| createAlias(node, name) { | |
| if (!node.anchor) { | |
| const prev = anchorNames(this); | |
| node.anchor = | |
| // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing | |
| !name || prev.has(name) ? findNewAnchor(name || 'a', prev) : name; | |
| } | |
| return new Alias(node.anchor); | |
| } | |
| createNode(value, replacer, options) { | |
| let _replacer = undefined; | |
| if (typeof replacer === 'function') { | |
| value = replacer.call({ '': value }, '', value); | |
| _replacer = replacer; | |
| } | |
| else if (Array.isArray(replacer)) { | |
| const keyToStr = (v) => typeof v === 'number' || v instanceof String || v instanceof Number; | |
| const asStr = replacer.filter(keyToStr).map(String); | |
| if (asStr.length > 0) | |
| replacer = replacer.concat(asStr); | |
| _replacer = replacer; | |
| } | |
| else if (options === undefined && replacer) { | |
| options = replacer; | |
| replacer = undefined; | |
| } | |
| const { aliasDuplicateObjects, anchorPrefix, flow, keepUndefined, onTagObj, tag } = options ?? {}; | |
| const { onAnchor, setAnchors, sourceObjects } = createNodeAnchors(this, | |
| // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing | |
| anchorPrefix || 'a'); | |
| const ctx = { | |
| aliasDuplicateObjects: aliasDuplicateObjects ?? true, | |
| keepUndefined: keepUndefined ?? false, | |
| onAnchor, | |
| onTagObj, | |
| replacer: _replacer, | |
| schema: this.schema, | |
| sourceObjects | |
| }; | |
| const node = createNode(value, tag, ctx); | |
| if (flow && isCollection(node)) | |
| node.flow = true; | |
| setAnchors(); | |
| return node; | |
| } | |
| /** | |
| * Convert a key and a value into a `Pair` using the current schema, | |
| * recursively wrapping all values as `Scalar` or `Collection` nodes. | |
| */ | |
| createPair(key, value, options = {}) { | |
| const k = this.createNode(key, null, options); | |
| const v = this.createNode(value, null, options); | |
| return new Pair(k, v); | |
| } | |
| /** | |
| * Removes a value from the document. | |
| * @returns `true` if the item was found and removed. | |
| */ | |
| delete(key) { | |
| return assertCollection(this.contents) ? this.contents.delete(key) : false; | |
| } | |
| /** | |
| * Removes a value from the document. | |
| * @returns `true` if the item was found and removed. | |
| */ | |
| deleteIn(path) { | |
| if (isEmptyPath(path)) { | |
| if (this.contents == null) | |
| return false; | |
| // @ts-expect-error Presumed impossible if Strict extends false | |
| this.contents = null; | |
| return true; | |
| } | |
| return assertCollection(this.contents) | |
| ? this.contents.deleteIn(path) | |
| : false; | |
| } | |
| /** | |
| * 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). | |
| */ | |
| get(key, keepScalar) { | |
| return isCollection(this.contents) | |
| ? this.contents.get(key, keepScalar) | |
| : undefined; | |
| } | |
| /** | |
| * Returns item at `path`, 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) { | |
| if (isEmptyPath(path)) | |
| return !keepScalar && isScalar(this.contents) | |
| ? this.contents.value | |
| : this.contents; | |
| return isCollection(this.contents) | |
| ? this.contents.getIn(path, keepScalar) | |
| : undefined; | |
| } | |
| /** | |
| * Checks if the document includes a value with the key `key`. | |
| */ | |
| has(key) { | |
| return isCollection(this.contents) ? this.contents.has(key) : false; | |
| } | |
| /** | |
| * Checks if the document includes a value at `path`. | |
| */ | |
| hasIn(path) { | |
| if (isEmptyPath(path)) | |
| return this.contents !== undefined; | |
| return isCollection(this.contents) ? this.contents.hasIn(path) : false; | |
| } | |
| /** | |
| * Sets a value in this document. For `!!set`, `value` needs to be a | |
| * boolean to add/remove the item from the set. | |
| */ | |
| set(key, value) { | |
| if (this.contents == null) { | |
| // @ts-expect-error We can't really know that this matches Contents. | |
| this.contents = collectionFromPath(this.schema, [key], value); | |
| } | |
| else if (assertCollection(this.contents)) { | |
| this.contents.set(key, value); | |
| } | |
| } | |
| /** | |
| * Sets a value in this document. For `!!set`, `value` needs to be a | |
| * boolean to add/remove the item from the set. | |
| */ | |
| setIn(path, value) { | |
| if (isEmptyPath(path)) { | |
| // @ts-expect-error We can't really know that this matches Contents. | |
| this.contents = value; | |
| } | |
| else if (this.contents == null) { | |
| // @ts-expect-error We can't really know that this matches Contents. | |
| this.contents = collectionFromPath(this.schema, Array.from(path), value); | |
| } | |
| else if (assertCollection(this.contents)) { | |
| this.contents.setIn(path, value); | |
| } | |
| } | |
| /** | |
| * Change the YAML version and schema used by the document. | |
| * A `null` version disables support for directives, explicit tags, anchors, and aliases. | |
| * It also requires the `schema` option to be given as a `Schema` instance value. | |
| * | |
| * Overrides all previously set schema options. | |
| */ | |
| setSchema(version, options = {}) { | |
| if (typeof version === 'number') | |
| version = String(version); | |
| let opt; | |
| switch (version) { | |
| case '1.1': | |
| if (this.directives) | |
| this.directives.yaml.version = '1.1'; | |
| else | |
| this.directives = new Directives({ version: '1.1' }); | |
| opt = { resolveKnownTags: false, schema: 'yaml-1.1' }; | |
| break; | |
| case '1.2': | |
| case 'next': | |
| if (this.directives) | |
| this.directives.yaml.version = version; | |
| else | |
| this.directives = new Directives({ version }); | |
| opt = { resolveKnownTags: true, schema: 'core' }; | |
| break; | |
| case null: | |
| if (this.directives) | |
| delete this.directives; | |
| opt = null; | |
| break; | |
| default: { | |
| const sv = JSON.stringify(version); | |
| throw new Error(`Expected '1.1', '1.2' or null as first argument, but found: ${sv}`); | |
| } | |
| } | |
| // Not using `instanceof Schema` to allow for duck typing | |
| if (options.schema instanceof Object) | |
| this.schema = options.schema; | |
| else if (opt) | |
| this.schema = new Schema(Object.assign(opt, options)); | |
| else | |
| throw new Error(`With a null YAML version, the { schema: Schema } option is required`); | |
| } | |
| // json & jsonArg are only used from toJSON() | |
| toJS({ json, jsonArg, mapAsMap, maxAliasCount, onAnchor, reviver } = {}) { | |
| const ctx = { | |
| anchors: new Map(), | |
| doc: this, | |
| keep: !json, | |
| mapAsMap: mapAsMap === true, | |
| mapKeyWarned: false, | |
| maxAliasCount: typeof maxAliasCount === 'number' ? maxAliasCount : 100 | |
| }; | |
| const res = toJS(this.contents, jsonArg ?? '', ctx); | |
| if (typeof onAnchor === 'function') | |
| for (const { count, res } of ctx.anchors.values()) | |
| onAnchor(res, count); | |
| return typeof reviver === 'function' | |
| ? applyReviver(reviver, { '': res }, '', res) | |
| : res; | |
| } | |
| /** | |
| * A JSON representation of the document `contents`. | |
| * | |
| * @param jsonArg Used by `JSON.stringify` to indicate the array index or | |
| * property name. | |
| */ | |
| toJSON(jsonArg, onAnchor) { | |
| return this.toJS({ json: true, jsonArg, mapAsMap: false, onAnchor }); | |
| } | |
| /** A YAML representation of the document. */ | |
| toString(options = {}) { | |
| if (this.errors.length > 0) | |
| throw new Error('Document with errors cannot be stringified'); | |
| if ('indent' in options && | |
| (!Number.isInteger(options.indent) || Number(options.indent) <= 0)) { | |
| const s = JSON.stringify(options.indent); | |
| throw new Error(`"indent" option must be a positive integer, not ${s}`); | |
| } | |
| return stringifyDocument(this, options); | |
| } | |
| } | |
| function assertCollection(contents) { | |
| if (isCollection(contents)) | |
| return true; | |
| throw new Error('Expected a YAML collection as document contents'); | |
| } | |
| export { Document }; | |
Xet Storage Details
- Size:
- 12.9 kB
- Xet hash:
- 6501c57dfa37502b19fd3ac12df3da65c8926f965af5f07957b842e5681f0603
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.