| ; | |
| var identity = require('../nodes/identity.js'); | |
| var visit = require('../visit.js'); | |
| const escapeChars = { | |
| '!': '%21', | |
| ',': '%2C', | |
| '[': '%5B', | |
| ']': '%5D', | |
| '{': '%7B', | |
| '}': '%7D' | |
| }; | |
| const escapeTagName = (tn) => tn.replace(/[!,[\]{}]/g, ch => escapeChars[ch]); | |
| class Directives { | |
| constructor(yaml, tags) { | |
| /** | |
| * The directives-end/doc-start marker `---`. If `null`, a marker may still be | |
| * included in the document's stringified representation. | |
| */ | |
| this.docStart = null; | |
| /** The doc-end marker `...`. */ | |
| this.docEnd = false; | |
| this.yaml = Object.assign({}, Directives.defaultYaml, yaml); | |
| this.tags = Object.assign({}, Directives.defaultTags, tags); | |
| } | |
| clone() { | |
| const copy = new Directives(this.yaml, this.tags); | |
| copy.docStart = this.docStart; | |
| return copy; | |
| } | |
| /** | |
| * During parsing, get a Directives instance for the current document and | |
| * update the stream state according to the current version's spec. | |
| */ | |
| atDocument() { | |
| const res = new Directives(this.yaml, this.tags); | |
| switch (this.yaml.version) { | |
| case '1.1': | |
| this.atNextDocument = true; | |
| break; | |
| case '1.2': | |
| this.atNextDocument = false; | |
| this.yaml = { | |
| explicit: Directives.defaultYaml.explicit, | |
| version: '1.2' | |
| }; | |
| this.tags = Object.assign({}, Directives.defaultTags); | |
| break; | |
| } | |
| return res; | |
| } | |
| /** | |
| * @param onError - May be called even if the action was successful | |
| * @returns `true` on success | |
| */ | |
| add(line, onError) { | |
| if (this.atNextDocument) { | |
| this.yaml = { explicit: Directives.defaultYaml.explicit, version: '1.1' }; | |
| this.tags = Object.assign({}, Directives.defaultTags); | |
| this.atNextDocument = false; | |
| } | |
| const parts = line.trim().split(/[ \t]+/); | |
| const name = parts.shift(); | |
| switch (name) { | |
| case '%TAG': { | |
| if (parts.length !== 2) { | |
| onError(0, '%TAG directive should contain exactly two parts'); | |
| if (parts.length < 2) | |
| return false; | |
| } | |
| const [handle, prefix] = parts; | |
| this.tags[handle] = prefix; | |
| return true; | |
| } | |
| case '%YAML': { | |
| this.yaml.explicit = true; | |
| if (parts.length !== 1) { | |
| onError(0, '%YAML directive should contain exactly one part'); | |
| return false; | |
| } | |
| const [version] = parts; | |
| if (version === '1.1' || version === '1.2') { | |
| this.yaml.version = version; | |
| return true; | |
| } | |
| else { | |
| const isValid = /^\d+\.\d+$/.test(version); | |
| onError(6, `Unsupported YAML version ${version}`, isValid); | |
| return false; | |
| } | |
| } | |
| default: | |
| onError(0, `Unknown directive ${name}`, true); | |
| return false; | |
| } | |
| } | |
| /** | |
| * Resolves a tag, matching handles to those defined in %TAG directives. | |
| * | |
| * @returns Resolved tag, which may also be the non-specific tag `'!'` or a | |
| * `'!local'` tag, or `null` if unresolvable. | |
| */ | |
| tagName(source, onError) { | |
| if (source === '!') | |
| return '!'; // non-specific tag | |
| if (source[0] !== '!') { | |
| onError(`Not a valid tag: ${source}`); | |
| return null; | |
| } | |
| if (source[1] === '<') { | |
| const verbatim = source.slice(2, -1); | |
| if (verbatim === '!' || verbatim === '!!') { | |
| onError(`Verbatim tags aren't resolved, so ${source} is invalid.`); | |
| return null; | |
| } | |
| if (source[source.length - 1] !== '>') | |
| onError('Verbatim tags must end with a >'); | |
| return verbatim; | |
| } | |
| const [, handle, suffix] = source.match(/^(.*!)([^!]*)$/s); | |
| if (!suffix) | |
| onError(`The ${source} tag has no suffix`); | |
| const prefix = this.tags[handle]; | |
| if (prefix) { | |
| try { | |
| return prefix + decodeURIComponent(suffix); | |
| } | |
| catch (error) { | |
| onError(String(error)); | |
| return null; | |
| } | |
| } | |
| if (handle === '!') | |
| return source; // local tag | |
| onError(`Could not resolve tag: ${source}`); | |
| return null; | |
| } | |
| /** | |
| * Given a fully resolved tag, returns its printable string form, | |
| * taking into account current tag prefixes and defaults. | |
| */ | |
| tagString(tag) { | |
| for (const [handle, prefix] of Object.entries(this.tags)) { | |
| if (tag.startsWith(prefix)) | |
| return handle + escapeTagName(tag.substring(prefix.length)); | |
| } | |
| return tag[0] === '!' ? tag : `!<${tag}>`; | |
| } | |
| toString(doc) { | |
| const lines = this.yaml.explicit | |
| ? [`%YAML ${this.yaml.version || '1.2'}`] | |
| : []; | |
| const tagEntries = Object.entries(this.tags); | |
| let tagNames; | |
| if (doc && tagEntries.length > 0 && identity.isNode(doc.contents)) { | |
| const tags = {}; | |
| visit.visit(doc.contents, (_key, node) => { | |
| if (identity.isNode(node) && node.tag) | |
| tags[node.tag] = true; | |
| }); | |
| tagNames = Object.keys(tags); | |
| } | |
| else | |
| tagNames = []; | |
| for (const [handle, prefix] of tagEntries) { | |
| if (handle === '!!' && prefix === 'tag:yaml.org,2002:') | |
| continue; | |
| if (!doc || tagNames.some(tn => tn.startsWith(prefix))) | |
| lines.push(`%TAG ${handle} ${prefix}`); | |
| } | |
| return lines.join('\n'); | |
| } | |
| } | |
| Directives.defaultYaml = { explicit: false, version: '1.2' }; | |
| Directives.defaultTags = { '!!': 'tag:yaml.org,2002:' }; | |
| exports.Directives = Directives; | |
Xet Storage Details
- Size:
- 6.24 kB
- Xet hash:
- b33171a1d91a4ba65359bc8c3690e4907ce45dd08e21d4dcbd6bb137a566de57
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.