forked from rDrama/rDrama
877 lines
20 KiB
JavaScript
877 lines
20 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
const Char = {
|
||
|
ANCHOR: '&',
|
||
|
COMMENT: '#',
|
||
|
TAG: '!',
|
||
|
DIRECTIVES_END: '-',
|
||
|
DOCUMENT_END: '.'
|
||
|
};
|
||
|
const Type = {
|
||
|
ALIAS: 'ALIAS',
|
||
|
BLANK_LINE: 'BLANK_LINE',
|
||
|
BLOCK_FOLDED: 'BLOCK_FOLDED',
|
||
|
BLOCK_LITERAL: 'BLOCK_LITERAL',
|
||
|
COMMENT: 'COMMENT',
|
||
|
DIRECTIVE: 'DIRECTIVE',
|
||
|
DOCUMENT: 'DOCUMENT',
|
||
|
FLOW_MAP: 'FLOW_MAP',
|
||
|
FLOW_SEQ: 'FLOW_SEQ',
|
||
|
MAP: 'MAP',
|
||
|
MAP_KEY: 'MAP_KEY',
|
||
|
MAP_VALUE: 'MAP_VALUE',
|
||
|
PLAIN: 'PLAIN',
|
||
|
QUOTE_DOUBLE: 'QUOTE_DOUBLE',
|
||
|
QUOTE_SINGLE: 'QUOTE_SINGLE',
|
||
|
SEQ: 'SEQ',
|
||
|
SEQ_ITEM: 'SEQ_ITEM'
|
||
|
};
|
||
|
const defaultTagPrefix = 'tag:yaml.org,2002:';
|
||
|
const defaultTags = {
|
||
|
MAP: 'tag:yaml.org,2002:map',
|
||
|
SEQ: 'tag:yaml.org,2002:seq',
|
||
|
STR: 'tag:yaml.org,2002:str'
|
||
|
};
|
||
|
|
||
|
function findLineStarts(src) {
|
||
|
const ls = [0];
|
||
|
let offset = src.indexOf('\n');
|
||
|
|
||
|
while (offset !== -1) {
|
||
|
offset += 1;
|
||
|
ls.push(offset);
|
||
|
offset = src.indexOf('\n', offset);
|
||
|
}
|
||
|
|
||
|
return ls;
|
||
|
}
|
||
|
|
||
|
function getSrcInfo(cst) {
|
||
|
let lineStarts, src;
|
||
|
|
||
|
if (typeof cst === 'string') {
|
||
|
lineStarts = findLineStarts(cst);
|
||
|
src = cst;
|
||
|
} else {
|
||
|
if (Array.isArray(cst)) cst = cst[0];
|
||
|
|
||
|
if (cst && cst.context) {
|
||
|
if (!cst.lineStarts) cst.lineStarts = findLineStarts(cst.context.src);
|
||
|
lineStarts = cst.lineStarts;
|
||
|
src = cst.context.src;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
lineStarts,
|
||
|
src
|
||
|
};
|
||
|
}
|
||
|
/**
|
||
|
* @typedef {Object} LinePos - One-indexed position in the source
|
||
|
* @property {number} line
|
||
|
* @property {number} col
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Determine the line/col position matching a character offset.
|
||
|
*
|
||
|
* Accepts a source string or a CST document as the second parameter. With
|
||
|
* the latter, starting indices for lines are cached in the document as
|
||
|
* `lineStarts: number[]`.
|
||
|
*
|
||
|
* Returns a one-indexed `{ line, col }` location if found, or
|
||
|
* `undefined` otherwise.
|
||
|
*
|
||
|
* @param {number} offset
|
||
|
* @param {string|Document|Document[]} cst
|
||
|
* @returns {?LinePos}
|
||
|
*/
|
||
|
|
||
|
|
||
|
function getLinePos(offset, cst) {
|
||
|
if (typeof offset !== 'number' || offset < 0) return null;
|
||
|
const {
|
||
|
lineStarts,
|
||
|
src
|
||
|
} = getSrcInfo(cst);
|
||
|
if (!lineStarts || !src || offset > src.length) return null;
|
||
|
|
||
|
for (let i = 0; i < lineStarts.length; ++i) {
|
||
|
const start = lineStarts[i];
|
||
|
|
||
|
if (offset < start) {
|
||
|
return {
|
||
|
line: i,
|
||
|
col: offset - lineStarts[i - 1] + 1
|
||
|
};
|
||
|
}
|
||
|
|
||
|
if (offset === start) return {
|
||
|
line: i + 1,
|
||
|
col: 1
|
||
|
};
|
||
|
}
|
||
|
|
||
|
const line = lineStarts.length;
|
||
|
return {
|
||
|
line,
|
||
|
col: offset - lineStarts[line - 1] + 1
|
||
|
};
|
||
|
}
|
||
|
/**
|
||
|
* Get a specified line from the source.
|
||
|
*
|
||
|
* Accepts a source string or a CST document as the second parameter. With
|
||
|
* the latter, starting indices for lines are cached in the document as
|
||
|
* `lineStarts: number[]`.
|
||
|
*
|
||
|
* Returns the line as a string if found, or `null` otherwise.
|
||
|
*
|
||
|
* @param {number} line One-indexed line number
|
||
|
* @param {string|Document|Document[]} cst
|
||
|
* @returns {?string}
|
||
|
*/
|
||
|
|
||
|
function getLine(line, cst) {
|
||
|
const {
|
||
|
lineStarts,
|
||
|
src
|
||
|
} = getSrcInfo(cst);
|
||
|
if (!lineStarts || !(line >= 1) || line > lineStarts.length) return null;
|
||
|
const start = lineStarts[line - 1];
|
||
|
let end = lineStarts[line]; // undefined for last line; that's ok for slice()
|
||
|
|
||
|
while (end && end > start && src[end - 1] === '\n') --end;
|
||
|
|
||
|
return src.slice(start, end);
|
||
|
}
|
||
|
/**
|
||
|
* Pretty-print the starting line from the source indicated by the range `pos`
|
||
|
*
|
||
|
* Trims output to `maxWidth` chars while keeping the starting column visible,
|
||
|
* using `…` at either end to indicate dropped characters.
|
||
|
*
|
||
|
* Returns a two-line string (or `null`) with `\n` as separator; the second line
|
||
|
* will hold appropriately indented `^` marks indicating the column range.
|
||
|
*
|
||
|
* @param {Object} pos
|
||
|
* @param {LinePos} pos.start
|
||
|
* @param {LinePos} [pos.end]
|
||
|
* @param {string|Document|Document[]*} cst
|
||
|
* @param {number} [maxWidth=80]
|
||
|
* @returns {?string}
|
||
|
*/
|
||
|
|
||
|
function getPrettyContext({
|
||
|
start,
|
||
|
end
|
||
|
}, cst, maxWidth = 80) {
|
||
|
let src = getLine(start.line, cst);
|
||
|
if (!src) return null;
|
||
|
let {
|
||
|
col
|
||
|
} = start;
|
||
|
|
||
|
if (src.length > maxWidth) {
|
||
|
if (col <= maxWidth - 10) {
|
||
|
src = src.substr(0, maxWidth - 1) + '…';
|
||
|
} else {
|
||
|
const halfWidth = Math.round(maxWidth / 2);
|
||
|
if (src.length > col + halfWidth) src = src.substr(0, col + halfWidth - 1) + '…';
|
||
|
col -= src.length - maxWidth;
|
||
|
src = '…' + src.substr(1 - maxWidth);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
let errLen = 1;
|
||
|
let errEnd = '';
|
||
|
|
||
|
if (end) {
|
||
|
if (end.line === start.line && col + (end.col - start.col) <= maxWidth + 1) {
|
||
|
errLen = end.col - start.col;
|
||
|
} else {
|
||
|
errLen = Math.min(src.length + 1, maxWidth) - col;
|
||
|
errEnd = '…';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const offset = col > 1 ? ' '.repeat(col - 1) : '';
|
||
|
const err = '^'.repeat(errLen);
|
||
|
return `${src}\n${offset}${err}${errEnd}`;
|
||
|
}
|
||
|
|
||
|
class Range {
|
||
|
static copy(orig) {
|
||
|
return new Range(orig.start, orig.end);
|
||
|
}
|
||
|
|
||
|
constructor(start, end) {
|
||
|
this.start = start;
|
||
|
this.end = end || start;
|
||
|
}
|
||
|
|
||
|
isEmpty() {
|
||
|
return typeof this.start !== 'number' || !this.end || this.end <= this.start;
|
||
|
}
|
||
|
/**
|
||
|
* Set `origStart` and `origEnd` to point to the original source range for
|
||
|
* this node, which may differ due to dropped CR characters.
|
||
|
*
|
||
|
* @param {number[]} cr - Positions of dropped CR characters
|
||
|
* @param {number} offset - Starting index of `cr` from the last call
|
||
|
* @returns {number} - The next offset, matching the one found for `origStart`
|
||
|
*/
|
||
|
|
||
|
|
||
|
setOrigRange(cr, offset) {
|
||
|
const {
|
||
|
start,
|
||
|
end
|
||
|
} = this;
|
||
|
|
||
|
if (cr.length === 0 || end <= cr[0]) {
|
||
|
this.origStart = start;
|
||
|
this.origEnd = end;
|
||
|
return offset;
|
||
|
}
|
||
|
|
||
|
let i = offset;
|
||
|
|
||
|
while (i < cr.length) {
|
||
|
if (cr[i] > start) break;else ++i;
|
||
|
}
|
||
|
|
||
|
this.origStart = start + i;
|
||
|
const nextOffset = i;
|
||
|
|
||
|
while (i < cr.length) {
|
||
|
// if end was at \n, it should now be at \r
|
||
|
if (cr[i] >= end) break;else ++i;
|
||
|
}
|
||
|
|
||
|
this.origEnd = end + i;
|
||
|
return nextOffset;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
/** Root class of all nodes */
|
||
|
|
||
|
class Node {
|
||
|
static addStringTerminator(src, offset, str) {
|
||
|
if (str[str.length - 1] === '\n') return str;
|
||
|
const next = Node.endOfWhiteSpace(src, offset);
|
||
|
return next >= src.length || src[next] === '\n' ? str + '\n' : str;
|
||
|
} // ^(---|...)
|
||
|
|
||
|
|
||
|
static atDocumentBoundary(src, offset, sep) {
|
||
|
const ch0 = src[offset];
|
||
|
if (!ch0) return true;
|
||
|
const prev = src[offset - 1];
|
||
|
if (prev && prev !== '\n') return false;
|
||
|
|
||
|
if (sep) {
|
||
|
if (ch0 !== sep) return false;
|
||
|
} else {
|
||
|
if (ch0 !== Char.DIRECTIVES_END && ch0 !== Char.DOCUMENT_END) return false;
|
||
|
}
|
||
|
|
||
|
const ch1 = src[offset + 1];
|
||
|
const ch2 = src[offset + 2];
|
||
|
if (ch1 !== ch0 || ch2 !== ch0) return false;
|
||
|
const ch3 = src[offset + 3];
|
||
|
return !ch3 || ch3 === '\n' || ch3 === '\t' || ch3 === ' ';
|
||
|
}
|
||
|
|
||
|
static endOfIdentifier(src, offset) {
|
||
|
let ch = src[offset];
|
||
|
const isVerbatim = ch === '<';
|
||
|
const notOk = isVerbatim ? ['\n', '\t', ' ', '>'] : ['\n', '\t', ' ', '[', ']', '{', '}', ','];
|
||
|
|
||
|
while (ch && notOk.indexOf(ch) === -1) ch = src[offset += 1];
|
||
|
|
||
|
if (isVerbatim && ch === '>') offset += 1;
|
||
|
return offset;
|
||
|
}
|
||
|
|
||
|
static endOfIndent(src, offset) {
|
||
|
let ch = src[offset];
|
||
|
|
||
|
while (ch === ' ') ch = src[offset += 1];
|
||
|
|
||
|
return offset;
|
||
|
}
|
||
|
|
||
|
static endOfLine(src, offset) {
|
||
|
let ch = src[offset];
|
||
|
|
||
|
while (ch && ch !== '\n') ch = src[offset += 1];
|
||
|
|
||
|
return offset;
|
||
|
}
|
||
|
|
||
|
static endOfWhiteSpace(src, offset) {
|
||
|
let ch = src[offset];
|
||
|
|
||
|
while (ch === '\t' || ch === ' ') ch = src[offset += 1];
|
||
|
|
||
|
return offset;
|
||
|
}
|
||
|
|
||
|
static startOfLine(src, offset) {
|
||
|
let ch = src[offset - 1];
|
||
|
if (ch === '\n') return offset;
|
||
|
|
||
|
while (ch && ch !== '\n') ch = src[offset -= 1];
|
||
|
|
||
|
return offset + 1;
|
||
|
}
|
||
|
/**
|
||
|
* End of indentation, or null if the line's indent level is not more
|
||
|
* than `indent`
|
||
|
*
|
||
|
* @param {string} src
|
||
|
* @param {number} indent
|
||
|
* @param {number} lineStart
|
||
|
* @returns {?number}
|
||
|
*/
|
||
|
|
||
|
|
||
|
static endOfBlockIndent(src, indent, lineStart) {
|
||
|
const inEnd = Node.endOfIndent(src, lineStart);
|
||
|
|
||
|
if (inEnd > lineStart + indent) {
|
||
|
return inEnd;
|
||
|
} else {
|
||
|
const wsEnd = Node.endOfWhiteSpace(src, inEnd);
|
||
|
const ch = src[wsEnd];
|
||
|
if (!ch || ch === '\n') return wsEnd;
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
static atBlank(src, offset, endAsBlank) {
|
||
|
const ch = src[offset];
|
||
|
return ch === '\n' || ch === '\t' || ch === ' ' || endAsBlank && !ch;
|
||
|
}
|
||
|
|
||
|
static nextNodeIsIndented(ch, indentDiff, indicatorAsIndent) {
|
||
|
if (!ch || indentDiff < 0) return false;
|
||
|
if (indentDiff > 0) return true;
|
||
|
return indicatorAsIndent && ch === '-';
|
||
|
} // should be at line or string end, or at next non-whitespace char
|
||
|
|
||
|
|
||
|
static normalizeOffset(src, offset) {
|
||
|
const ch = src[offset];
|
||
|
return !ch ? offset : ch !== '\n' && src[offset - 1] === '\n' ? offset - 1 : Node.endOfWhiteSpace(src, offset);
|
||
|
} // fold single newline into space, multiple newlines to N - 1 newlines
|
||
|
// presumes src[offset] === '\n'
|
||
|
|
||
|
|
||
|
static foldNewline(src, offset, indent) {
|
||
|
let inCount = 0;
|
||
|
let error = false;
|
||
|
let fold = '';
|
||
|
let ch = src[offset + 1];
|
||
|
|
||
|
while (ch === ' ' || ch === '\t' || ch === '\n') {
|
||
|
switch (ch) {
|
||
|
case '\n':
|
||
|
inCount = 0;
|
||
|
offset += 1;
|
||
|
fold += '\n';
|
||
|
break;
|
||
|
|
||
|
case '\t':
|
||
|
if (inCount <= indent) error = true;
|
||
|
offset = Node.endOfWhiteSpace(src, offset + 2) - 1;
|
||
|
break;
|
||
|
|
||
|
case ' ':
|
||
|
inCount += 1;
|
||
|
offset += 1;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
ch = src[offset + 1];
|
||
|
}
|
||
|
|
||
|
if (!fold) fold = ' ';
|
||
|
if (ch && inCount <= indent) error = true;
|
||
|
return {
|
||
|
fold,
|
||
|
offset,
|
||
|
error
|
||
|
};
|
||
|
}
|
||
|
|
||
|
constructor(type, props, context) {
|
||
|
Object.defineProperty(this, 'context', {
|
||
|
value: context || null,
|
||
|
writable: true
|
||
|
});
|
||
|
this.error = null;
|
||
|
this.range = null;
|
||
|
this.valueRange = null;
|
||
|
this.props = props || [];
|
||
|
this.type = type;
|
||
|
this.value = null;
|
||
|
}
|
||
|
|
||
|
getPropValue(idx, key, skipKey) {
|
||
|
if (!this.context) return null;
|
||
|
const {
|
||
|
src
|
||
|
} = this.context;
|
||
|
const prop = this.props[idx];
|
||
|
return prop && src[prop.start] === key ? src.slice(prop.start + (skipKey ? 1 : 0), prop.end) : null;
|
||
|
}
|
||
|
|
||
|
get anchor() {
|
||
|
for (let i = 0; i < this.props.length; ++i) {
|
||
|
const anchor = this.getPropValue(i, Char.ANCHOR, true);
|
||
|
if (anchor != null) return anchor;
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
get comment() {
|
||
|
const comments = [];
|
||
|
|
||
|
for (let i = 0; i < this.props.length; ++i) {
|
||
|
const comment = this.getPropValue(i, Char.COMMENT, true);
|
||
|
if (comment != null) comments.push(comment);
|
||
|
}
|
||
|
|
||
|
return comments.length > 0 ? comments.join('\n') : null;
|
||
|
}
|
||
|
|
||
|
commentHasRequiredWhitespace(start) {
|
||
|
const {
|
||
|
src
|
||
|
} = this.context;
|
||
|
if (this.header && start === this.header.end) return false;
|
||
|
if (!this.valueRange) return false;
|
||
|
const {
|
||
|
end
|
||
|
} = this.valueRange;
|
||
|
return start !== end || Node.atBlank(src, end - 1);
|
||
|
}
|
||
|
|
||
|
get hasComment() {
|
||
|
if (this.context) {
|
||
|
const {
|
||
|
src
|
||
|
} = this.context;
|
||
|
|
||
|
for (let i = 0; i < this.props.length; ++i) {
|
||
|
if (src[this.props[i].start] === Char.COMMENT) return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
get hasProps() {
|
||
|
if (this.context) {
|
||
|
const {
|
||
|
src
|
||
|
} = this.context;
|
||
|
|
||
|
for (let i = 0; i < this.props.length; ++i) {
|
||
|
if (src[this.props[i].start] !== Char.COMMENT) return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
get includesTrailingLines() {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
get jsonLike() {
|
||
|
const jsonLikeTypes = [Type.FLOW_MAP, Type.FLOW_SEQ, Type.QUOTE_DOUBLE, Type.QUOTE_SINGLE];
|
||
|
return jsonLikeTypes.indexOf(this.type) !== -1;
|
||
|
}
|
||
|
|
||
|
get rangeAsLinePos() {
|
||
|
if (!this.range || !this.context) return undefined;
|
||
|
const start = getLinePos(this.range.start, this.context.root);
|
||
|
if (!start) return undefined;
|
||
|
const end = getLinePos(this.range.end, this.context.root);
|
||
|
return {
|
||
|
start,
|
||
|
end
|
||
|
};
|
||
|
}
|
||
|
|
||
|
get rawValue() {
|
||
|
if (!this.valueRange || !this.context) return null;
|
||
|
const {
|
||
|
start,
|
||
|
end
|
||
|
} = this.valueRange;
|
||
|
return this.context.src.slice(start, end);
|
||
|
}
|
||
|
|
||
|
get tag() {
|
||
|
for (let i = 0; i < this.props.length; ++i) {
|
||
|
const tag = this.getPropValue(i, Char.TAG, false);
|
||
|
|
||
|
if (tag != null) {
|
||
|
if (tag[1] === '<') {
|
||
|
return {
|
||
|
verbatim: tag.slice(2, -1)
|
||
|
};
|
||
|
} else {
|
||
|
// eslint-disable-next-line no-unused-vars
|
||
|
const [_, handle, suffix] = tag.match(/^(.*!)([^!]*)$/);
|
||
|
return {
|
||
|
handle,
|
||
|
suffix
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
get valueRangeContainsNewline() {
|
||
|
if (!this.valueRange || !this.context) return false;
|
||
|
const {
|
||
|
start,
|
||
|
end
|
||
|
} = this.valueRange;
|
||
|
const {
|
||
|
src
|
||
|
} = this.context;
|
||
|
|
||
|
for (let i = start; i < end; ++i) {
|
||
|
if (src[i] === '\n') return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
parseComment(start) {
|
||
|
const {
|
||
|
src
|
||
|
} = this.context;
|
||
|
|
||
|
if (src[start] === Char.COMMENT) {
|
||
|
const end = Node.endOfLine(src, start + 1);
|
||
|
const commentRange = new Range(start, end);
|
||
|
this.props.push(commentRange);
|
||
|
return end;
|
||
|
}
|
||
|
|
||
|
return start;
|
||
|
}
|
||
|
/**
|
||
|
* Populates the `origStart` and `origEnd` values of all ranges for this
|
||
|
* node. Extended by child classes to handle descendant nodes.
|
||
|
*
|
||
|
* @param {number[]} cr - Positions of dropped CR characters
|
||
|
* @param {number} offset - Starting index of `cr` from the last call
|
||
|
* @returns {number} - The next offset, matching the one found for `origStart`
|
||
|
*/
|
||
|
|
||
|
|
||
|
setOrigRanges(cr, offset) {
|
||
|
if (this.range) offset = this.range.setOrigRange(cr, offset);
|
||
|
if (this.valueRange) this.valueRange.setOrigRange(cr, offset);
|
||
|
this.props.forEach(prop => prop.setOrigRange(cr, offset));
|
||
|
return offset;
|
||
|
}
|
||
|
|
||
|
toString() {
|
||
|
const {
|
||
|
context: {
|
||
|
src
|
||
|
},
|
||
|
range,
|
||
|
value
|
||
|
} = this;
|
||
|
if (value != null) return value;
|
||
|
const str = src.slice(range.start, range.end);
|
||
|
return Node.addStringTerminator(src, range.end, str);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
class YAMLError extends Error {
|
||
|
constructor(name, source, message) {
|
||
|
if (!message || !(source instanceof Node)) throw new Error(`Invalid arguments for new ${name}`);
|
||
|
super();
|
||
|
this.name = name;
|
||
|
this.message = message;
|
||
|
this.source = source;
|
||
|
}
|
||
|
|
||
|
makePretty() {
|
||
|
if (!this.source) return;
|
||
|
this.nodeType = this.source.type;
|
||
|
const cst = this.source.context && this.source.context.root;
|
||
|
|
||
|
if (typeof this.offset === 'number') {
|
||
|
this.range = new Range(this.offset, this.offset + 1);
|
||
|
const start = cst && getLinePos(this.offset, cst);
|
||
|
|
||
|
if (start) {
|
||
|
const end = {
|
||
|
line: start.line,
|
||
|
col: start.col + 1
|
||
|
};
|
||
|
this.linePos = {
|
||
|
start,
|
||
|
end
|
||
|
};
|
||
|
}
|
||
|
|
||
|
delete this.offset;
|
||
|
} else {
|
||
|
this.range = this.source.range;
|
||
|
this.linePos = this.source.rangeAsLinePos;
|
||
|
}
|
||
|
|
||
|
if (this.linePos) {
|
||
|
const {
|
||
|
line,
|
||
|
col
|
||
|
} = this.linePos.start;
|
||
|
this.message += ` at line ${line}, column ${col}`;
|
||
|
const ctx = cst && getPrettyContext(this.linePos, cst);
|
||
|
if (ctx) this.message += `:\n\n${ctx}\n`;
|
||
|
}
|
||
|
|
||
|
delete this.source;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
class YAMLReferenceError extends YAMLError {
|
||
|
constructor(source, message) {
|
||
|
super('YAMLReferenceError', source, message);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
class YAMLSemanticError extends YAMLError {
|
||
|
constructor(source, message) {
|
||
|
super('YAMLSemanticError', source, message);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
class YAMLSyntaxError extends YAMLError {
|
||
|
constructor(source, message) {
|
||
|
super('YAMLSyntaxError', source, message);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
class YAMLWarning extends YAMLError {
|
||
|
constructor(source, message) {
|
||
|
super('YAMLWarning', source, message);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
function _defineProperty(obj, key, value) {
|
||
|
if (key in obj) {
|
||
|
Object.defineProperty(obj, key, {
|
||
|
value: value,
|
||
|
enumerable: true,
|
||
|
configurable: true,
|
||
|
writable: true
|
||
|
});
|
||
|
} else {
|
||
|
obj[key] = value;
|
||
|
}
|
||
|
|
||
|
return obj;
|
||
|
}
|
||
|
|
||
|
class PlainValue extends Node {
|
||
|
static endOfLine(src, start, inFlow) {
|
||
|
let ch = src[start];
|
||
|
let offset = start;
|
||
|
|
||
|
while (ch && ch !== '\n') {
|
||
|
if (inFlow && (ch === '[' || ch === ']' || ch === '{' || ch === '}' || ch === ',')) break;
|
||
|
const next = src[offset + 1];
|
||
|
if (ch === ':' && (!next || next === '\n' || next === '\t' || next === ' ' || inFlow && next === ',')) break;
|
||
|
if ((ch === ' ' || ch === '\t') && next === '#') break;
|
||
|
offset += 1;
|
||
|
ch = next;
|
||
|
}
|
||
|
|
||
|
return offset;
|
||
|
}
|
||
|
|
||
|
get strValue() {
|
||
|
if (!this.valueRange || !this.context) return null;
|
||
|
let {
|
||
|
start,
|
||
|
end
|
||
|
} = this.valueRange;
|
||
|
const {
|
||
|
src
|
||
|
} = this.context;
|
||
|
let ch = src[end - 1];
|
||
|
|
||
|
while (start < end && (ch === '\n' || ch === '\t' || ch === ' ')) ch = src[--end - 1];
|
||
|
|
||
|
let str = '';
|
||
|
|
||
|
for (let i = start; i < end; ++i) {
|
||
|
const ch = src[i];
|
||
|
|
||
|
if (ch === '\n') {
|
||
|
const {
|
||
|
fold,
|
||
|
offset
|
||
|
} = Node.foldNewline(src, i, -1);
|
||
|
str += fold;
|
||
|
i = offset;
|
||
|
} else if (ch === ' ' || ch === '\t') {
|
||
|
// trim trailing whitespace
|
||
|
const wsStart = i;
|
||
|
let next = src[i + 1];
|
||
|
|
||
|
while (i < end && (next === ' ' || next === '\t')) {
|
||
|
i += 1;
|
||
|
next = src[i + 1];
|
||
|
}
|
||
|
|
||
|
if (next !== '\n') str += i > wsStart ? src.slice(wsStart, i + 1) : ch;
|
||
|
} else {
|
||
|
str += ch;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const ch0 = src[start];
|
||
|
|
||
|
switch (ch0) {
|
||
|
case '\t':
|
||
|
{
|
||
|
const msg = 'Plain value cannot start with a tab character';
|
||
|
const errors = [new YAMLSemanticError(this, msg)];
|
||
|
return {
|
||
|
errors,
|
||
|
str
|
||
|
};
|
||
|
}
|
||
|
|
||
|
case '@':
|
||
|
case '`':
|
||
|
{
|
||
|
const msg = `Plain value cannot start with reserved character ${ch0}`;
|
||
|
const errors = [new YAMLSemanticError(this, msg)];
|
||
|
return {
|
||
|
errors,
|
||
|
str
|
||
|
};
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
return str;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
parseBlockValue(start) {
|
||
|
const {
|
||
|
indent,
|
||
|
inFlow,
|
||
|
src
|
||
|
} = this.context;
|
||
|
let offset = start;
|
||
|
let valueEnd = start;
|
||
|
|
||
|
for (let ch = src[offset]; ch === '\n'; ch = src[offset]) {
|
||
|
if (Node.atDocumentBoundary(src, offset + 1)) break;
|
||
|
const end = Node.endOfBlockIndent(src, indent, offset + 1);
|
||
|
if (end === null || src[end] === '#') break;
|
||
|
|
||
|
if (src[end] === '\n') {
|
||
|
offset = end;
|
||
|
} else {
|
||
|
valueEnd = PlainValue.endOfLine(src, end, inFlow);
|
||
|
offset = valueEnd;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (this.valueRange.isEmpty()) this.valueRange.start = start;
|
||
|
this.valueRange.end = valueEnd;
|
||
|
return valueEnd;
|
||
|
}
|
||
|
/**
|
||
|
* Parses a plain value from the source
|
||
|
*
|
||
|
* Accepted forms are:
|
||
|
* ```
|
||
|
* #comment
|
||
|
*
|
||
|
* first line
|
||
|
*
|
||
|
* first line #comment
|
||
|
*
|
||
|
* first line
|
||
|
* block
|
||
|
* lines
|
||
|
*
|
||
|
* #comment
|
||
|
* block
|
||
|
* lines
|
||
|
* ```
|
||
|
* where block lines are empty or have an indent level greater than `indent`.
|
||
|
*
|
||
|
* @param {ParseContext} context
|
||
|
* @param {number} start - Index of first character
|
||
|
* @returns {number} - Index of the character after this scalar, may be `\n`
|
||
|
*/
|
||
|
|
||
|
|
||
|
parse(context, start) {
|
||
|
this.context = context;
|
||
|
const {
|
||
|
inFlow,
|
||
|
src
|
||
|
} = context;
|
||
|
let offset = start;
|
||
|
const ch = src[offset];
|
||
|
|
||
|
if (ch && ch !== '#' && ch !== '\n') {
|
||
|
offset = PlainValue.endOfLine(src, start, inFlow);
|
||
|
}
|
||
|
|
||
|
this.valueRange = new Range(start, offset);
|
||
|
offset = Node.endOfWhiteSpace(src, offset);
|
||
|
offset = this.parseComment(offset);
|
||
|
|
||
|
if (!this.hasComment || this.valueRange.isEmpty()) {
|
||
|
offset = this.parseBlockValue(offset);
|
||
|
}
|
||
|
|
||
|
return offset;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
exports.Char = Char;
|
||
|
exports.Node = Node;
|
||
|
exports.PlainValue = PlainValue;
|
||
|
exports.Range = Range;
|
||
|
exports.Type = Type;
|
||
|
exports.YAMLError = YAMLError;
|
||
|
exports.YAMLReferenceError = YAMLReferenceError;
|
||
|
exports.YAMLSemanticError = YAMLSemanticError;
|
||
|
exports.YAMLSyntaxError = YAMLSyntaxError;
|
||
|
exports.YAMLWarning = YAMLWarning;
|
||
|
exports._defineProperty = _defineProperty;
|
||
|
exports.defaultTagPrefix = defaultTagPrefix;
|
||
|
exports.defaultTags = defaultTags;
|