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.
214 lines
9.0 KiB
214 lines
9.0 KiB
import { resolveBlockScalar } from '../compose/resolve-block-scalar.js'; |
|
import { resolveFlowScalar } from '../compose/resolve-flow-scalar.js'; |
|
import { YAMLParseError } from '../errors.js'; |
|
import { stringifyString } from '../stringify/stringifyString.js'; |
|
|
|
function resolveAsScalar(token, strict = true, onError) { |
|
if (token) { |
|
const _onError = (pos, code, message) => { |
|
const offset = typeof pos === 'number' ? pos : Array.isArray(pos) ? pos[0] : pos.offset; |
|
if (onError) |
|
onError(offset, code, message); |
|
else |
|
throw new YAMLParseError([offset, offset + 1], code, message); |
|
}; |
|
switch (token.type) { |
|
case 'scalar': |
|
case 'single-quoted-scalar': |
|
case 'double-quoted-scalar': |
|
return resolveFlowScalar(token, strict, _onError); |
|
case 'block-scalar': |
|
return resolveBlockScalar(token, strict, _onError); |
|
} |
|
} |
|
return null; |
|
} |
|
/** |
|
* Create a new scalar token with `value` |
|
* |
|
* Values that represent an actual string but may be parsed as a different type should use a `type` other than `'PLAIN'`, |
|
* as this function does not support any schema operations and won't check for such conflicts. |
|
* |
|
* @param value The string representation of the value, which will have its content properly indented. |
|
* @param context.end Comments and whitespace after the end of the value, or after the block scalar header. If undefined, a newline will be added. |
|
* @param context.implicitKey Being within an implicit key may affect the resolved type of the token's value. |
|
* @param context.indent The indent level of the token. |
|
* @param context.inFlow Is this scalar within a flow collection? This may affect the resolved type of the token's value. |
|
* @param context.offset The offset position of the token. |
|
* @param context.type The preferred type of the scalar token. If undefined, the previous type of the `token` will be used, defaulting to `'PLAIN'`. |
|
*/ |
|
function createScalarToken(value, context) { |
|
const { implicitKey = false, indent, inFlow = false, offset = -1, type = 'PLAIN' } = context; |
|
const source = stringifyString({ type, value }, { |
|
implicitKey, |
|
indent: indent > 0 ? ' '.repeat(indent) : '', |
|
inFlow, |
|
options: { blockQuote: true, lineWidth: -1 } |
|
}); |
|
const end = context.end ?? [ |
|
{ type: 'newline', offset: -1, indent, source: '\n' } |
|
]; |
|
switch (source[0]) { |
|
case '|': |
|
case '>': { |
|
const he = source.indexOf('\n'); |
|
const head = source.substring(0, he); |
|
const body = source.substring(he + 1) + '\n'; |
|
const props = [ |
|
{ type: 'block-scalar-header', offset, indent, source: head } |
|
]; |
|
if (!addEndtoBlockProps(props, end)) |
|
props.push({ type: 'newline', offset: -1, indent, source: '\n' }); |
|
return { type: 'block-scalar', offset, indent, props, source: body }; |
|
} |
|
case '"': |
|
return { type: 'double-quoted-scalar', offset, indent, source, end }; |
|
case "'": |
|
return { type: 'single-quoted-scalar', offset, indent, source, end }; |
|
default: |
|
return { type: 'scalar', offset, indent, source, end }; |
|
} |
|
} |
|
/** |
|
* Set the value of `token` to the given string `value`, overwriting any previous contents and type that it may have. |
|
* |
|
* Best efforts are made to retain any comments previously associated with the `token`, |
|
* though all contents within a collection's `items` will be overwritten. |
|
* |
|
* Values that represent an actual string but may be parsed as a different type should use a `type` other than `'PLAIN'`, |
|
* as this function does not support any schema operations and won't check for such conflicts. |
|
* |
|
* @param token Any token. If it does not include an `indent` value, the value will be stringified as if it were an implicit key. |
|
* @param value The string representation of the value, which will have its content properly indented. |
|
* @param context.afterKey In most cases, values after a key should have an additional level of indentation. |
|
* @param context.implicitKey Being within an implicit key may affect the resolved type of the token's value. |
|
* @param context.inFlow Being within a flow collection may affect the resolved type of the token's value. |
|
* @param context.type The preferred type of the scalar token. If undefined, the previous type of the `token` will be used, defaulting to `'PLAIN'`. |
|
*/ |
|
function setScalarValue(token, value, context = {}) { |
|
let { afterKey = false, implicitKey = false, inFlow = false, type } = context; |
|
let indent = 'indent' in token ? token.indent : null; |
|
if (afterKey && typeof indent === 'number') |
|
indent += 2; |
|
if (!type) |
|
switch (token.type) { |
|
case 'single-quoted-scalar': |
|
type = 'QUOTE_SINGLE'; |
|
break; |
|
case 'double-quoted-scalar': |
|
type = 'QUOTE_DOUBLE'; |
|
break; |
|
case 'block-scalar': { |
|
const header = token.props[0]; |
|
if (header.type !== 'block-scalar-header') |
|
throw new Error('Invalid block scalar header'); |
|
type = header.source[0] === '>' ? 'BLOCK_FOLDED' : 'BLOCK_LITERAL'; |
|
break; |
|
} |
|
default: |
|
type = 'PLAIN'; |
|
} |
|
const source = stringifyString({ type, value }, { |
|
implicitKey: implicitKey || indent === null, |
|
indent: indent !== null && indent > 0 ? ' '.repeat(indent) : '', |
|
inFlow, |
|
options: { blockQuote: true, lineWidth: -1 } |
|
}); |
|
switch (source[0]) { |
|
case '|': |
|
case '>': |
|
setBlockScalarValue(token, source); |
|
break; |
|
case '"': |
|
setFlowScalarValue(token, source, 'double-quoted-scalar'); |
|
break; |
|
case "'": |
|
setFlowScalarValue(token, source, 'single-quoted-scalar'); |
|
break; |
|
default: |
|
setFlowScalarValue(token, source, 'scalar'); |
|
} |
|
} |
|
function setBlockScalarValue(token, source) { |
|
const he = source.indexOf('\n'); |
|
const head = source.substring(0, he); |
|
const body = source.substring(he + 1) + '\n'; |
|
if (token.type === 'block-scalar') { |
|
const header = token.props[0]; |
|
if (header.type !== 'block-scalar-header') |
|
throw new Error('Invalid block scalar header'); |
|
header.source = head; |
|
token.source = body; |
|
} |
|
else { |
|
const { offset } = token; |
|
const indent = 'indent' in token ? token.indent : -1; |
|
const props = [ |
|
{ type: 'block-scalar-header', offset, indent, source: head } |
|
]; |
|
if (!addEndtoBlockProps(props, 'end' in token ? token.end : undefined)) |
|
props.push({ type: 'newline', offset: -1, indent, source: '\n' }); |
|
for (const key of Object.keys(token)) |
|
if (key !== 'type' && key !== 'offset') |
|
delete token[key]; |
|
Object.assign(token, { type: 'block-scalar', indent, props, source: body }); |
|
} |
|
} |
|
/** @returns `true` if last token is a newline */ |
|
function addEndtoBlockProps(props, end) { |
|
if (end) |
|
for (const st of end) |
|
switch (st.type) { |
|
case 'space': |
|
case 'comment': |
|
props.push(st); |
|
break; |
|
case 'newline': |
|
props.push(st); |
|
return true; |
|
} |
|
return false; |
|
} |
|
function setFlowScalarValue(token, source, type) { |
|
switch (token.type) { |
|
case 'scalar': |
|
case 'double-quoted-scalar': |
|
case 'single-quoted-scalar': |
|
token.type = type; |
|
token.source = source; |
|
break; |
|
case 'block-scalar': { |
|
const end = token.props.slice(1); |
|
let oa = source.length; |
|
if (token.props[0].type === 'block-scalar-header') |
|
oa -= token.props[0].source.length; |
|
for (const tok of end) |
|
tok.offset += oa; |
|
delete token.props; |
|
Object.assign(token, { type, source, end }); |
|
break; |
|
} |
|
case 'block-map': |
|
case 'block-seq': { |
|
const offset = token.offset + source.length; |
|
const nl = { type: 'newline', offset, indent: token.indent, source: '\n' }; |
|
delete token.items; |
|
Object.assign(token, { type, source, end: [nl] }); |
|
break; |
|
} |
|
default: { |
|
const indent = 'indent' in token ? token.indent : -1; |
|
const end = 'end' in token && Array.isArray(token.end) |
|
? token.end.filter(st => st.type === 'space' || |
|
st.type === 'comment' || |
|
st.type === 'newline') |
|
: []; |
|
for (const key of Object.keys(token)) |
|
if (key !== 'type' && key !== 'offset') |
|
delete token[key]; |
|
Object.assign(token, { type, indent, source, end }); |
|
} |
|
} |
|
} |
|
|
|
export { createScalarToken, resolveAsScalar, setScalarValue };
|
|
|