import { YAMLError } from "../error.ts";import type { RepresentFn, StyleVariant, Type } from "../type.ts";import * as common from "../utils.ts";import { DumperState, DumperStateOptions } from "./dumper_state.ts";
type Any = common.Any;type ArrayObject<T = Any> = common.ArrayObject<T>;
const _toString = Object.prototype.toString;const _hasOwnProperty = Object.prototype.hasOwnProperty;
const CHAR_TAB = 0x09; const CHAR_LINE_FEED = 0x0a; const CHAR_SPACE = 0x20; const CHAR_EXCLAMATION = 0x21; const CHAR_DOUBLE_QUOTE = 0x22; const CHAR_SHARP = 0x23; const CHAR_PERCENT = 0x25; const CHAR_AMPERSAND = 0x26; const CHAR_SINGLE_QUOTE = 0x27; const CHAR_ASTERISK = 0x2a; const CHAR_COMMA = 0x2c; const CHAR_MINUS = 0x2d; const CHAR_COLON = 0x3a; const CHAR_GREATER_THAN = 0x3e; const CHAR_QUESTION = 0x3f; const CHAR_COMMERCIAL_AT = 0x40; const CHAR_LEFT_SQUARE_BRACKET = 0x5b; const CHAR_RIGHT_SQUARE_BRACKET = 0x5d; const CHAR_GRAVE_ACCENT = 0x60; const CHAR_LEFT_CURLY_BRACKET = 0x7b; const CHAR_VERTICAL_LINE = 0x7c; const CHAR_RIGHT_CURLY_BRACKET = 0x7d;
const ESCAPE_SEQUENCES: { [char: number]: string } = {};
ESCAPE_SEQUENCES[0x00] = "\\0";ESCAPE_SEQUENCES[0x07] = "\\a";ESCAPE_SEQUENCES[0x08] = "\\b";ESCAPE_SEQUENCES[0x09] = "\\t";ESCAPE_SEQUENCES[0x0a] = "\\n";ESCAPE_SEQUENCES[0x0b] = "\\v";ESCAPE_SEQUENCES[0x0c] = "\\f";ESCAPE_SEQUENCES[0x0d] = "\\r";ESCAPE_SEQUENCES[0x1b] = "\\e";ESCAPE_SEQUENCES[0x22] = '\\"';ESCAPE_SEQUENCES[0x5c] = "\\\\";ESCAPE_SEQUENCES[0x85] = "\\N";ESCAPE_SEQUENCES[0xa0] = "\\_";ESCAPE_SEQUENCES[0x2028] = "\\L";ESCAPE_SEQUENCES[0x2029] = "\\P";
const DEPRECATED_BOOLEANS_SYNTAX = [ "y", "Y", "yes", "Yes", "YES", "on", "On", "ON", "n", "N", "no", "No", "NO", "off", "Off", "OFF",];
function encodeHex(character: number): string { const string = character.toString(16).toUpperCase();
let handle: string; let length: number; if (character <= 0xff) { handle = "x"; length = 2; } else if (character <= 0xffff) { handle = "u"; length = 4; } else if (character <= 0xffffffff) { handle = "U"; length = 8; } else { throw new YAMLError( "code point within a string may not be greater than 0xFFFFFFFF", ); }
return `\\${handle}${common.repeat("0", length - string.length)}${string}`;}
function indentString(string: string, spaces: number): string { const ind = common.repeat(" ", spaces), length = string.length; let position = 0, next = -1, result = "", line: string;
while (position < length) { next = string.indexOf("\n", position); if (next === -1) { line = string.slice(position); position = length; } else { line = string.slice(position, next + 1); position = next + 1; }
if (line.length && line !== "\n") result += ind;
result += line; }
return result;}
function generateNextLine(state: DumperState, level: number): string { return `\n${common.repeat(" ", state.indent * level)}`;}
function testImplicitResolving(state: DumperState, str: string): boolean { let type: Type; for ( let index = 0, length = state.implicitTypes.length; index < length; index += 1 ) { type = state.implicitTypes[index];
if (type.resolve(str)) { return true; } }
return false;}
function isWhitespace(c: number): boolean { return c === CHAR_SPACE || c === CHAR_TAB;}
function isPrintable(c: number): boolean { return ( (0x00020 <= c && c <= 0x00007e) || (0x000a1 <= c && c <= 0x00d7ff && c !== 0x2028 && c !== 0x2029) || (0x0e000 <= c && c <= 0x00fffd && c !== 0xfeff) || (0x10000 <= c && c <= 0x10ffff) );}
function isPlainSafe(c: number): boolean { return ( isPrintable(c) && c !== 0xfeff && c !== CHAR_COMMA && c !== CHAR_LEFT_SQUARE_BRACKET && c !== CHAR_RIGHT_SQUARE_BRACKET && c !== CHAR_LEFT_CURLY_BRACKET && c !== CHAR_RIGHT_CURLY_BRACKET && c !== CHAR_COLON && c !== CHAR_SHARP );}
function isPlainSafeFirst(c: number): boolean { return ( isPrintable(c) && c !== 0xfeff && !isWhitespace(c) && c !== CHAR_MINUS && c !== CHAR_QUESTION && c !== CHAR_COLON && c !== CHAR_COMMA && c !== CHAR_LEFT_SQUARE_BRACKET && c !== CHAR_RIGHT_SQUARE_BRACKET && c !== CHAR_LEFT_CURLY_BRACKET && c !== CHAR_RIGHT_CURLY_BRACKET && c !== CHAR_SHARP && c !== CHAR_AMPERSAND && c !== CHAR_ASTERISK && c !== CHAR_EXCLAMATION && c !== CHAR_VERTICAL_LINE && c !== CHAR_GREATER_THAN && c !== CHAR_SINGLE_QUOTE && c !== CHAR_DOUBLE_QUOTE && c !== CHAR_PERCENT && c !== CHAR_COMMERCIAL_AT && c !== CHAR_GRAVE_ACCENT );}
function needIndentIndicator(string: string): boolean { const leadingSpaceRe = /^\n* /; return leadingSpaceRe.test(string);}
const STYLE_PLAIN = 1, STYLE_SINGLE = 2, STYLE_LITERAL = 3, STYLE_FOLDED = 4, STYLE_DOUBLE = 5;
function chooseScalarStyle( string: string, singleLineOnly: boolean, indentPerLevel: number, lineWidth: number, testAmbiguousType: (...args: Any[]) => Any,): number { const shouldTrackWidth = lineWidth !== -1; let hasLineBreak = false, hasFoldableLine = false, previousLineBreak = -1, plain = isPlainSafeFirst(string.charCodeAt(0)) && !isWhitespace(string.charCodeAt(string.length - 1));
let char: number, i: number; if (singleLineOnly) { for (i = 0; i < string.length; i++) { char = string.charCodeAt(i); if (!isPrintable(char)) { return STYLE_DOUBLE; } plain = plain && isPlainSafe(char); } } else { for (i = 0; i < string.length; i++) { char = string.charCodeAt(i); if (char === CHAR_LINE_FEED) { hasLineBreak = true; if (shouldTrackWidth) { hasFoldableLine = hasFoldableLine || (i - previousLineBreak - 1 > lineWidth && string[previousLineBreak + 1] !== " "); previousLineBreak = i; } } else if (!isPrintable(char)) { return STYLE_DOUBLE; } plain = plain && isPlainSafe(char); } hasFoldableLine = hasFoldableLine || (shouldTrackWidth && i - previousLineBreak - 1 > lineWidth && string[previousLineBreak + 1] !== " "); } if (!hasLineBreak && !hasFoldableLine) { return plain && !testAmbiguousType(string) ? STYLE_PLAIN : STYLE_SINGLE; } if (indentPerLevel > 9 && needIndentIndicator(string)) { return STYLE_DOUBLE; } return hasFoldableLine ? STYLE_FOLDED : STYLE_LITERAL;}
function foldLine(line: string, width: number): string { if (line === "" || line[0] === " ") return line;
const breakRe = / [^ ]/g; let match; let start = 0, end, curr = 0, next = 0; let result = "";
while ((match = breakRe.exec(line))) { next = match.index; if (next - start > width) { end = curr > start ? curr : next; result += `\n${line.slice(start, end)}`; start = end + 1; } curr = next; }
result += "\n"; if (line.length - start > width && curr > start) { result += `${line.slice(start, curr)}\n${line.slice(curr + 1)}`; } else { result += line.slice(start); }
return result.slice(1); }
function dropEndingNewline(string: string): string { return string[string.length - 1] === "\n" ? string.slice(0, -1) : string;}
function foldString(string: string, width: number): string { const lineRe = /(\n+)([^\n]*)/g;
let result = ((): string => { let nextLF = string.indexOf("\n"); nextLF = nextLF !== -1 ? nextLF : string.length; lineRe.lastIndex = nextLF; return foldLine(string.slice(0, nextLF), width); })(); let prevMoreIndented = string[0] === "\n" || string[0] === " "; let moreIndented;
let match; while ((match = lineRe.exec(string))) { const prefix = match[1], line = match[2]; moreIndented = line[0] === " "; result += prefix + (!prevMoreIndented && !moreIndented && line !== "" ? "\n" : "") + foldLine(line, width); prevMoreIndented = moreIndented; }
return result;}
function escapeString(string: string): string { let result = ""; let char, nextChar; let escapeSeq;
for (let i = 0; i < string.length; i++) { char = string.charCodeAt(i); if (char >= 0xd800 && char <= 0xdbff ) { nextChar = string.charCodeAt(i + 1); if (nextChar >= 0xdc00 && nextChar <= 0xdfff ) { result += encodeHex( (char - 0xd800) * 0x400 + nextChar - 0xdc00 + 0x10000, ); i++; continue; } } escapeSeq = ESCAPE_SEQUENCES[char]; result += !escapeSeq && isPrintable(char) ? string[i] : escapeSeq || encodeHex(char); }
return result;}
function blockHeader(string: string, indentPerLevel: number): string { const indentIndicator = needIndentIndicator(string) ? String(indentPerLevel) : "";
const clip = string[string.length - 1] === "\n"; const keep = clip && (string[string.length - 2] === "\n" || string === "\n"); const chomp = keep ? "+" : clip ? "" : "-";
return `${indentIndicator}${chomp}\n`;}
function writeScalar( state: DumperState, string: string, level: number, iskey: boolean,): void { state.dump = ((): string => { if (string.length === 0) { return "''"; } if ( !state.noCompatMode && DEPRECATED_BOOLEANS_SYNTAX.indexOf(string) !== -1 ) { return `'${string}'`; }
const indent = state.indent * Math.max(1, level); const lineWidth = state.lineWidth === -1 ? -1 : Math.max(Math.min(state.lineWidth, 40), state.lineWidth - indent);
const singleLineOnly = iskey || (state.flowLevel > -1 && level >= state.flowLevel); function testAmbiguity(str: string): boolean { return testImplicitResolving(state, str); }
switch ( chooseScalarStyle( string, singleLineOnly, state.indent, lineWidth, testAmbiguity, ) ) { case STYLE_PLAIN: return string; case STYLE_SINGLE: return `'${string.replace(/'/g, "''")}'`; case STYLE_LITERAL: return `|${blockHeader(string, state.indent)}${ dropEndingNewline(indentString(string, indent)) }`; case STYLE_FOLDED: return `>${blockHeader(string, state.indent)}${ dropEndingNewline( indentString(foldString(string, lineWidth), indent), ) }`; case STYLE_DOUBLE: return `"${escapeString(string)}"`; default: throw new YAMLError("impossible error: invalid scalar style"); } })();}
function writeFlowSequence( state: DumperState, level: number, object: Any,): void { let _result = ""; const _tag = state.tag;
for (let index = 0, length = object.length; index < length; index += 1) { if (writeNode(state, level, object[index], false, false)) { if (index !== 0) _result += `,${!state.condenseFlow ? " " : ""}`; _result += state.dump; } }
state.tag = _tag; state.dump = `[${_result}]`;}
function writeBlockSequence( state: DumperState, level: number, object: Any, compact = false,): void { let _result = ""; const _tag = state.tag;
for (let index = 0, length = object.length; index < length; index += 1) { if (writeNode(state, level + 1, object[index], true, true)) { if (!compact || index !== 0) { _result += generateNextLine(state, level); }
if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) { _result += "-"; } else { _result += "- "; }
_result += state.dump; } }
state.tag = _tag; state.dump = _result || "[]"; }
function writeFlowMapping( state: DumperState, level: number, object: Any,): void { let _result = ""; const _tag = state.tag, objectKeyList = Object.keys(object);
let pairBuffer: string, objectKey: string, objectValue: Any; for ( let index = 0, length = objectKeyList.length; index < length; index += 1 ) { pairBuffer = state.condenseFlow ? '"' : "";
if (index !== 0) pairBuffer += ", ";
objectKey = objectKeyList[index]; objectValue = object[objectKey];
if (!writeNode(state, level, objectKey, false, false)) { continue; }
if (state.dump.length > 1024) pairBuffer += "? ";
pairBuffer += `${state.dump}${state.condenseFlow ? '"' : ""}:${ state.condenseFlow ? "" : " " }`;
if (!writeNode(state, level, objectValue, false, false)) { continue; }
pairBuffer += state.dump;
_result += pairBuffer; }
state.tag = _tag; state.dump = `{${_result}}`;}
function writeBlockMapping( state: DumperState, level: number, object: Any, compact = false,): void { const _tag = state.tag, objectKeyList = Object.keys(object); let _result = "";
if (state.sortKeys === true) { objectKeyList.sort(); } else if (typeof state.sortKeys === "function") { objectKeyList.sort(state.sortKeys); } else if (state.sortKeys) { throw new YAMLError("sortKeys must be a boolean or a function"); }
let pairBuffer = "", objectKey: string, objectValue: Any, explicitPair: boolean; for ( let index = 0, length = objectKeyList.length; index < length; index += 1 ) { pairBuffer = "";
if (!compact || index !== 0) { pairBuffer += generateNextLine(state, level); }
objectKey = objectKeyList[index]; objectValue = object[objectKey];
if (!writeNode(state, level + 1, objectKey, true, true, true)) { continue; }
explicitPair = (state.tag !== null && state.tag !== "?") || (state.dump && state.dump.length > 1024);
if (explicitPair) { if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) { pairBuffer += "?"; } else { pairBuffer += "? "; } }
pairBuffer += state.dump;
if (explicitPair) { pairBuffer += generateNextLine(state, level); }
if (!writeNode(state, level + 1, objectValue, true, explicitPair)) { continue; }
if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) { pairBuffer += ":"; } else { pairBuffer += ": "; }
pairBuffer += state.dump;
_result += pairBuffer; }
state.tag = _tag; state.dump = _result || "{}"; }
function detectType( state: DumperState, object: Any, explicit = false,): boolean { const typeList = explicit ? state.explicitTypes : state.implicitTypes;
let type: Type; let style: StyleVariant; let _result: string; for (let index = 0, length = typeList.length; index < length; index += 1) { type = typeList[index];
if ( (type.instanceOf || type.predicate) && (!type.instanceOf || (typeof object === "object" && object instanceof type.instanceOf)) && (!type.predicate || type.predicate(object)) ) { state.tag = explicit ? type.tag : "?";
if (type.represent) { style = state.styleMap[type.tag] || type.defaultStyle;
if (_toString.call(type.represent) === "[object Function]") { _result = (type.represent as RepresentFn)(object, style); } else if (_hasOwnProperty.call(type.represent, style)) { _result = (type.represent as ArrayObject<RepresentFn>)[style]( object, style, ); } else { throw new YAMLError( `!<${type.tag}> tag resolver accepts not "${style}" style`, ); }
state.dump = _result; }
return true; } }
return false;}
function writeNode( state: DumperState, level: number, object: Any, block: boolean, compact: boolean, iskey = false,): boolean { state.tag = null; state.dump = object;
if (!detectType(state, object, false)) { detectType(state, object, true); }
const type = _toString.call(state.dump);
if (block) { block = state.flowLevel < 0 || state.flowLevel > level; }
const objectOrArray = type === "[object Object]" || type === "[object Array]";
let duplicateIndex = -1; let duplicate = false; if (objectOrArray) { duplicateIndex = state.duplicates.indexOf(object); duplicate = duplicateIndex !== -1; }
if ( (state.tag !== null && state.tag !== "?") || duplicate || (state.indent !== 2 && level > 0) ) { compact = false; }
if (duplicate && state.usedDuplicates[duplicateIndex]) { state.dump = `*ref_${duplicateIndex}`; } else { if (objectOrArray && duplicate && !state.usedDuplicates[duplicateIndex]) { state.usedDuplicates[duplicateIndex] = true; } if (type === "[object Object]") { if (block && Object.keys(state.dump).length !== 0) { writeBlockMapping(state, level, state.dump, compact); if (duplicate) { state.dump = `&ref_${duplicateIndex}${state.dump}`; } } else { writeFlowMapping(state, level, state.dump); if (duplicate) { state.dump = `&ref_${duplicateIndex} ${state.dump}`; } } } else if (type === "[object Array]") { const arrayLevel = state.noArrayIndent && level > 0 ? level - 1 : level; if (block && state.dump.length !== 0) { writeBlockSequence(state, arrayLevel, state.dump, compact); if (duplicate) { state.dump = `&ref_${duplicateIndex}${state.dump}`; } } else { writeFlowSequence(state, arrayLevel, state.dump); if (duplicate) { state.dump = `&ref_${duplicateIndex} ${state.dump}`; } } } else if (type === "[object String]") { if (state.tag !== "?") { writeScalar(state, state.dump, level, iskey); } } else { if (state.skipInvalid) return false; throw new YAMLError(`unacceptable kind of an object to dump ${type}`); }
if (state.tag !== null && state.tag !== "?") { state.dump = `!<${state.tag}> ${state.dump}`; } }
return true;}
function inspectNode( object: Any, objects: Any[], duplicatesIndexes: number[],): void { if (object !== null && typeof object === "object") { const index = objects.indexOf(object); if (index !== -1) { if (duplicatesIndexes.indexOf(index) === -1) { duplicatesIndexes.push(index); } } else { objects.push(object);
if (Array.isArray(object)) { for (let idx = 0, length = object.length; idx < length; idx += 1) { inspectNode(object[idx], objects, duplicatesIndexes); } } else { const objectKeyList = Object.keys(object);
for ( let idx = 0, length = objectKeyList.length; idx < length; idx += 1 ) { inspectNode(object[objectKeyList[idx]], objects, duplicatesIndexes); } } } }}
function getDuplicateReferences(object: object, state: DumperState): void { const objects: Any[] = [], duplicatesIndexes: number[] = [];
inspectNode(object, objects, duplicatesIndexes);
const length = duplicatesIndexes.length; for (let index = 0; index < length; index += 1) { state.duplicates.push(objects[duplicatesIndexes[index]]); } state.usedDuplicates = new Array(length);}
export function dump(input: Any, options?: DumperStateOptions): string { options = options || {};
const state = new DumperState(options);
if (!state.noRefs) getDuplicateReferences(input, state);
if (writeNode(state, 0, input, true, true)) return `${state.dump}\n`;
return "";}