summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLilian Saget-Lethias <lilian.sagetlethias@gmail.com>2019-11-18 15:39:32 +0100
committerRy Dahl <ry@tinyclouds.org>2019-11-18 09:39:32 -0500
commit5671d38d8f0176c2828a242a79a856fcfae93c3e (patch)
tree54e059f109baae9b3ff9161847149ecdac753e50
parenta2f5bccad7f73607dc4ec20bdb60c6c4ac8dc9e6 (diff)
feat: Add std/encoding/yaml module (#3361)
-rw-r--r--std/encoding/README.md21
-rw-r--r--std/encoding/yaml.ts8
-rw-r--r--std/encoding/yaml/dumper/dumper.ts899
-rw-r--r--std/encoding/yaml/dumper/dumper_state.ts138
-rw-r--r--std/encoding/yaml/error.ts22
-rw-r--r--std/encoding/yaml/example/dump.ts22
-rw-r--r--std/encoding/yaml/example/inout.ts27
-rw-r--r--std/encoding/yaml/example/parse.ts19
-rw-r--r--std/encoding/yaml/example/sample_document.ts23
-rw-r--r--std/encoding/yaml/example/sample_document.yml197
-rw-r--r--std/encoding/yaml/loader/loader.ts1798
-rw-r--r--std/encoding/yaml/loader/loader_state.ts74
-rw-r--r--std/encoding/yaml/mark.ts77
-rw-r--r--std/encoding/yaml/parse.ts32
-rw-r--r--std/encoding/yaml/parse_test.ts25
-rw-r--r--std/encoding/yaml/schema.ts101
-rw-r--r--std/encoding/yaml/schema/core.ts13
-rw-r--r--std/encoding/yaml/schema/default.ts16
-rw-r--r--std/encoding/yaml/schema/failsafe.ts13
-rw-r--r--std/encoding/yaml/schema/json.ts15
-rw-r--r--std/encoding/yaml/schema/mod.ts9
-rw-r--r--std/encoding/yaml/state.ts11
-rw-r--r--std/encoding/yaml/stringify.ts18
-rw-r--r--std/encoding/yaml/stringify_test.ts42
-rw-r--r--std/encoding/yaml/type.ts55
-rw-r--r--std/encoding/yaml/type/binary.ts139
-rw-r--r--std/encoding/yaml/type/bool.ts39
-rw-r--r--std/encoding/yaml/type/float.ts125
-rw-r--r--std/encoding/yaml/type/int.ts191
-rw-r--r--std/encoding/yaml/type/map.ts14
-rw-r--r--std/encoding/yaml/type/merge.ts15
-rw-r--r--std/encoding/yaml/type/mod.ts18
-rw-r--r--std/encoding/yaml/type/nil.ts45
-rw-r--r--std/encoding/yaml/type/omap.ts46
-rw-r--r--std/encoding/yaml/type/pairs.ts49
-rw-r--r--std/encoding/yaml/type/seq.ts14
-rw-r--r--std/encoding/yaml/type/set.ts31
-rw-r--r--std/encoding/yaml/type/str.ts12
-rw-r--r--std/encoding/yaml/type/timestamp.ts96
-rw-r--r--std/encoding/yaml/utils.ts84
-rw-r--r--std/encoding/yaml_test.ts4
41 files changed, 4597 insertions, 0 deletions
diff --git a/std/encoding/README.md b/std/encoding/README.md
index 2b209c6b6..dbb9f4191 100644
--- a/std/encoding/README.md
+++ b/std/encoding/README.md
@@ -219,3 +219,24 @@ const obj = {
};
const tomlString = stringify(obj);
```
+
+## YAML
+
+YAML parser / dumper for Deno
+
+Heavily inspired from [js-yaml]
+
+### Example
+
+See [`./yaml/example`](./yaml/example) folder and [js-yaml] repository.
+
+### :warning: Limitations
+
+- `binary` type is currently not stable
+- `function`, `regexp`, and `undefined` type are currently not supported
+
+# Basic usage
+
+TBD
+
+[js-yaml]: https://github.com/nodeca/js-yaml
diff --git a/std/encoding/yaml.ts b/std/encoding/yaml.ts
new file mode 100644
index 000000000..15b12c04f
--- /dev/null
+++ b/std/encoding/yaml.ts
@@ -0,0 +1,8 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+export * from "./yaml/parse.ts";
+export * from "./yaml/stringify.ts";
+export * from "./yaml/schema/mod.ts";
diff --git a/std/encoding/yaml/dumper/dumper.ts b/std/encoding/yaml/dumper/dumper.ts
new file mode 100644
index 000000000..4ee8e36b4
--- /dev/null
+++ b/std/encoding/yaml/dumper/dumper.ts
@@ -0,0 +1,899 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+/* eslint-disable max-len */
+
+import { YAMLError } from "../error.ts";
+import { 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; /* Tab */
+const CHAR_LINE_FEED = 0x0a; /* LF */
+const CHAR_SPACE = 0x20; /* Space */
+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}`;
+}
+
+// Indents every line in a string. Empty lines (\n only) are not indented.
+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;
+}
+
+// [33] s-white ::= s-space | s-tab
+function isWhitespace(c: number): boolean {
+ return c === CHAR_SPACE || c === CHAR_TAB;
+}
+
+// Returns true if the character can be printed without escaping.
+// From YAML 1.2: "any allowed characters known to be non-printable
+// should also be escaped. [However,] This isn’t mandatory"
+// Derived from nb-char - \t - #x85 - #xA0 - #x2028 - #x2029.
+function isPrintable(c: number): boolean {
+ return (
+ (0x00020 <= c && c <= 0x00007e) ||
+ (0x000a1 <= c && c <= 0x00d7ff && c !== 0x2028 && c !== 0x2029) ||
+ (0x0e000 <= c && c <= 0x00fffd && c !== 0xfeff) /* BOM */ ||
+ (0x10000 <= c && c <= 0x10ffff)
+ );
+}
+
+// Simplified test for values allowed after the first character in plain style.
+function isPlainSafe(c: number): boolean {
+ // Uses a subset of nb-char - c-flow-indicator - ":" - "#"
+ // where nb-char ::= c-printable - b-char - c-byte-order-mark.
+ return (
+ isPrintable(c) &&
+ c !== 0xfeff &&
+ // - c-flow-indicator
+ 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
+ );
+}
+
+// Simplified test for values allowed as the first character in plain style.
+function isPlainSafeFirst(c: number): boolean {
+ // Uses a subset of ns-char - c-indicator
+ // where ns-char = nb-char - s-white.
+ return (
+ isPrintable(c) &&
+ c !== 0xfeff &&
+ !isWhitespace(c) && // - s-white
+ // - (c-indicator ::=
+ // “-” | “?” | “:” | “,” | “[” | “]” | “{” | “}”
+ 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
+ );
+}
+
+// Determines whether block indentation indicator is required.
+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;
+
+// Determines which scalar styles are possible and returns the preferred style.
+// lineWidth = -1 => no limit.
+// Pre-conditions: str.length > 0.
+// Post-conditions:
+// STYLE_PLAIN or STYLE_SINGLE => no \n are in the string.
+// STYLE_LITERAL => no lines are suitable for folding (or lineWidth is -1).
+// STYLE_FOLDED => a line > lineWidth and can be folded (and lineWidth != -1).
+function chooseScalarStyle(
+ string: string,
+ singleLineOnly: boolean,
+ indentPerLevel: number,
+ lineWidth: number,
+ testAmbiguousType: (...args: Any[]) => Any
+): number {
+ const shouldTrackWidth = lineWidth !== -1;
+ let hasLineBreak = false,
+ hasFoldableLine = false, // only checked if shouldTrackWidth
+ previousLineBreak = -1, // count the first line correctly
+ plain =
+ isPlainSafeFirst(string.charCodeAt(0)) &&
+ !isWhitespace(string.charCodeAt(string.length - 1));
+
+ let char: number, i: number;
+ if (singleLineOnly) {
+ // Case: no block styles.
+ // Check for disallowed characters to rule out plain and single.
+ for (i = 0; i < string.length; i++) {
+ char = string.charCodeAt(i);
+ if (!isPrintable(char)) {
+ return STYLE_DOUBLE;
+ }
+ plain = plain && isPlainSafe(char);
+ }
+ } else {
+ // Case: block styles permitted.
+ for (i = 0; i < string.length; i++) {
+ char = string.charCodeAt(i);
+ if (char === CHAR_LINE_FEED) {
+ hasLineBreak = true;
+ // Check if any line can be folded.
+ if (shouldTrackWidth) {
+ hasFoldableLine =
+ hasFoldableLine ||
+ // Foldable line = too long, and not more-indented.
+ (i - previousLineBreak - 1 > lineWidth &&
+ string[previousLineBreak + 1] !== " ");
+ previousLineBreak = i;
+ }
+ } else if (!isPrintable(char)) {
+ return STYLE_DOUBLE;
+ }
+ plain = plain && isPlainSafe(char);
+ }
+ // in case the end is missing a \n
+ hasFoldableLine =
+ hasFoldableLine ||
+ (shouldTrackWidth &&
+ i - previousLineBreak - 1 > lineWidth &&
+ string[previousLineBreak + 1] !== " ");
+ }
+ // Although every style can represent \n without escaping, prefer block styles
+ // for multiline, since they're more readable and they don't add empty lines.
+ // Also prefer folding a super-long line.
+ if (!hasLineBreak && !hasFoldableLine) {
+ // Strings interpretable as another type have to be quoted;
+ // e.g. the string 'true' vs. the boolean true.
+ return plain && !testAmbiguousType(string) ? STYLE_PLAIN : STYLE_SINGLE;
+ }
+ // Edge case: block indentation indicator can only have one digit.
+ if (indentPerLevel > 9 && needIndentIndicator(string)) {
+ return STYLE_DOUBLE;
+ }
+ // At this point we know block styles are valid.
+ // Prefer literal style unless we want to fold.
+ return hasFoldableLine ? STYLE_FOLDED : STYLE_LITERAL;
+}
+
+// Greedy line breaking.
+// Picks the longest line under the limit each time,
+// otherwise settles for the shortest line over the limit.
+// NB. More-indented lines *cannot* be folded, as that would add an extra \n.
+function foldLine(line: string, width: number): string {
+ if (line === "" || line[0] === " ") return line;
+
+ // Since a more-indented line adds a \n, breaks can't be followed by a space.
+ const breakRe = / [^ ]/g; // note: the match index will always be <= length-2.
+ let match;
+ // start is an inclusive index. end, curr, and next are exclusive.
+ let start = 0,
+ end,
+ curr = 0,
+ next = 0;
+ let result = "";
+
+ // Invariants: 0 <= start <= length-1.
+ // 0 <= curr <= next <= max(0, length-2). curr - start <= width.
+ // Inside the loop:
+ // A match implies length >= 2, so curr and next are <= length-2.
+ // tslint:disable-next-line:no-conditional-assignment
+ while ((match = breakRe.exec(line))) {
+ next = match.index;
+ // maintain invariant: curr - start <= width
+ if (next - start > width) {
+ end = curr > start ? curr : next; // derive end <= length-2
+ result += `\n${line.slice(start, end)}`;
+ // skip the space that was output as \n
+ start = end + 1; // derive start <= length-1
+ }
+ curr = next;
+ }
+
+ // By the invariants, start <= length-1, so there is something left over.
+ // It is either the whole string or a part starting from non-whitespace.
+ result += "\n";
+ // Insert a break if the remainder is too long and there is a break available.
+ 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); // drop extra \n joiner
+}
+
+// (See the note for writeScalar.)
+function dropEndingNewline(string: string): string {
+ return string[string.length - 1] === "\n" ? string.slice(0, -1) : string;
+}
+
+// Note: a long line without a suitable break point will exceed the width limit.
+// Pre-conditions: every char in str isPrintable, str.length > 0, width > 0.
+function foldString(string: string, width: number): string {
+ // In folded style, $k$ consecutive newlines output as $k+1$ newlines—
+ // unless they're before or after a more-indented line, or at the very
+ // beginning or end, in which case $k$ maps to $k$.
+ // Therefore, parse each chunk as newline(s) followed by a content line.
+ const lineRe = /(\n+)([^\n]*)/g;
+
+ // first line (possibly an empty line)
+ let result = ((): string => {
+ let nextLF = string.indexOf("\n");
+ nextLF = nextLF !== -1 ? nextLF : string.length;
+ lineRe.lastIndex = nextLF;
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
+ return foldLine(string.slice(0, nextLF), width);
+ })();
+ // If we haven't reached the first content line yet, don't add an extra \n.
+ let prevMoreIndented = string[0] === "\n" || string[0] === " ";
+ let moreIndented;
+
+ // rest of the lines
+ let match;
+ // tslint:disable-next-line:no-conditional-assignment
+ while ((match = lineRe.exec(string))) {
+ const prefix = match[1],
+ line = match[2];
+ moreIndented = line[0] === " ";
+ result +=
+ prefix +
+ (!prevMoreIndented && !moreIndented && line !== "" ? "\n" : "") +
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
+ foldLine(line, width);
+ prevMoreIndented = moreIndented;
+ }
+
+ return result;
+}
+
+// Escapes a double-quoted string.
+function escapeString(string: string): string {
+ let result = "";
+ let char, nextChar;
+ let escapeSeq;
+
+ for (let i = 0; i < string.length; i++) {
+ char = string.charCodeAt(i);
+ // Check for surrogate pairs (reference Unicode 3.0 section "3.7 Surrogates").
+ if (char >= 0xd800 && char <= 0xdbff /* high surrogate */) {
+ nextChar = string.charCodeAt(i + 1);
+ if (nextChar >= 0xdc00 && nextChar <= 0xdfff /* low surrogate */) {
+ // Combine the surrogate pair and store it escaped.
+ result += encodeHex(
+ (char - 0xd800) * 0x400 + nextChar - 0xdc00 + 0x10000
+ );
+ // Advance index one extra since we already used that char here.
+ i++;
+ continue;
+ }
+ }
+ escapeSeq = ESCAPE_SEQUENCES[char];
+ result +=
+ !escapeSeq && isPrintable(char)
+ ? string[i]
+ : escapeSeq || encodeHex(char);
+ }
+
+ return result;
+}
+
+// Pre-conditions: string is valid for a block scalar, 1 <= indentPerLevel <= 9.
+function blockHeader(string: string, indentPerLevel: number): string {
+ const indentIndicator = needIndentIndicator(string)
+ ? String(indentPerLevel)
+ : "";
+
+ // note the special case: the string '\n' counts as a "trailing" empty line.
+ 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`;
+}
+
+// Note: line breaking/folding is implemented for only the folded style.
+// NB. We drop the last trailing newline (if any) of a returned block scalar
+// since the dumper adds its own newline. This always works:
+// • No ending newline => unaffected; already using strip "-" chomping.
+// • Ending newline => removed then restored.
+// Importantly, this keeps the "+" chomp indicator from gaining an extra line.
+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); // no 0-indent scalars
+ // As indentation gets deeper, let the width decrease monotonically
+ // to the lower bound min(state.lineWidth, 40).
+ // Note that this implies
+ // state.lineWidth ≤ 40 + state.indent: width is fixed at the lower bound.
+ // state.lineWidth > 40 + state.indent: width decreases until the lower
+ // bound.
+ // This behaves better than a constant minimum width which disallows
+ // narrower options, or an indent threshold which causes the width
+ // to suddenly increase.
+ const lineWidth =
+ state.lineWidth === -1
+ ? -1
+ : Math.max(Math.min(state.lineWidth, 40), state.lineWidth - indent);
+
+ // Without knowing if keys are implicit/explicit,
+ // assume implicit for safety.
+ const singleLineOnly =
+ iskey ||
+ // No block styles in flow mode.
+ (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) {
+ // Write only valid elements.
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
+ 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) {
+ // Write only valid elements.
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
+ 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 || "[]"; // Empty sequence if no valid values.
+}
+
+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];
+
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
+ if (!writeNode(state, level, objectKey, false, false)) {
+ continue; // Skip this pair because of invalid key;
+ }
+
+ if (state.dump.length > 1024) pairBuffer += "? ";
+
+ pairBuffer += `${state.dump}${state.condenseFlow ? '"' : ""}:${
+ state.condenseFlow ? "" : " "
+ }`;
+
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
+ if (!writeNode(state, level, objectValue, false, false)) {
+ continue; // Skip this pair because of invalid value.
+ }
+
+ pairBuffer += state.dump;
+
+ // Both key and value are valid.
+ _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 = "";
+
+ // Allow sorting keys so that the output file is deterministic
+ if (state.sortKeys === true) {
+ // Default sorting
+ objectKeyList.sort();
+ } else if (typeof state.sortKeys === "function") {
+ // Custom sort function
+ objectKeyList.sort(state.sortKeys);
+ } else if (state.sortKeys) {
+ // Something is wrong
+ 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];
+
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
+ if (!writeNode(state, level + 1, objectKey, true, true, true)) {
+ continue; // Skip this pair because of invalid key.
+ }
+
+ 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);
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
+ if (!writeNode(state, level + 1, objectValue, true, explicitPair)) {
+ continue; // Skip this pair because of invalid value.
+ }
+
+ if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) {
+ pairBuffer += ":";
+ } else {
+ pairBuffer += ": ";
+ }
+
+ pairBuffer += state.dump;
+
+ // Both key and value are valid.
+ _result += pairBuffer;
+ }
+
+ state.tag = _tag;
+ state.dump = _result || "{}"; // Empty mapping if no valid pairs.
+}
+
+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;
+}
+
+// Serializes `object` and writes it to global `result`.
+// Returns true on success, or false on invalid object.
+//
+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 "";
+}
diff --git a/std/encoding/yaml/dumper/dumper_state.ts b/std/encoding/yaml/dumper/dumper_state.ts
new file mode 100644
index 000000000..30e65f9a7
--- /dev/null
+++ b/std/encoding/yaml/dumper/dumper_state.ts
@@ -0,0 +1,138 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { Schema, SchemaDefinition } from "../schema.ts";
+import { State } from "../state.ts";
+import { StyleVariant, Type } from "../type.ts";
+import { ArrayObject, Any } from "../utils.ts";
+
+const _hasOwnProperty = Object.prototype.hasOwnProperty;
+
+function compileStyleMap(
+ schema: Schema,
+ map?: ArrayObject<StyleVariant> | null
+): ArrayObject<StyleVariant> {
+ if (typeof map === "undefined" || map === null) return {};
+
+ let type: Type;
+ const result: ArrayObject<StyleVariant> = {};
+ const keys = Object.keys(map);
+ let tag: string, style: StyleVariant;
+ for (let index = 0, length = keys.length; index < length; index += 1) {
+ tag = keys[index];
+ style = String(map[tag]) as StyleVariant;
+ if (tag.slice(0, 2) === "!!") {
+ tag = `tag:yaml.org,2002:${tag.slice(2)}`;
+ }
+ type = schema.compiledTypeMap.fallback[tag];
+
+ if (
+ type &&
+ typeof type.styleAliases !== "undefined" &&
+ _hasOwnProperty.call(type.styleAliases, style)
+ ) {
+ style = type.styleAliases[style];
+ }
+
+ result[tag] = style;
+ }
+
+ return result;
+}
+
+export interface DumperStateOptions {
+ /** indentation width to use (in spaces). */
+ indent?: number;
+ /** when true, will not add an indentation level to array elements */
+ noArrayIndent?: boolean;
+ /**
+ * do not throw on invalid types (like function in the safe schema)
+ * and skip pairs and single values with such types.
+ */
+ skipInvalid?: boolean;
+ /**
+ * specifies level of nesting, when to switch from
+ * block to flow style for collections. -1 means block style everwhere
+ */
+ flowLevel?: number;
+ /** Each tag may have own set of styles. - "tag" => "style" map. */
+ styles?: ArrayObject<StyleVariant> | null;
+ /** specifies a schema to use. */
+ schema?: SchemaDefinition;
+ /**
+ * if true, sort keys when dumping YAML.
+ * If a function, use the function to sort the keys. (default: false)
+ */
+ sortKeys?: boolean | ((a: Any, b: Any) => number);
+ /** set max line width. (default: 80) */
+ lineWidth?: number;
+ /**
+ * if true, don't convert duplicate objects
+ * into references (default: false)
+ */
+ noRefs?: boolean;
+ /**
+ * if true don't try to be compatible with older yaml versions.
+ * Currently: don't quote "yes", "no" and so on,
+ * as required for YAML 1.1 (default: false)
+ */
+ noCompatMode?: boolean;
+ /**
+ * if true flow sequences will be condensed, omitting the
+ * space between `key: value` or `a, b`. Eg. `'[a,b]'` or `{a:{b:c}}`.
+ * Can be useful when using yaml for pretty URL query params
+ * as spaces are %-encoded. (default: false).
+ */
+ condenseFlow?: boolean;
+}
+
+export class DumperState extends State {
+ public indent: number;
+ public noArrayIndent: boolean;
+ public skipInvalid: boolean;
+ public flowLevel: number;
+ public sortKeys: boolean | ((a: Any, b: Any) => number);
+ public lineWidth: number;
+ public noRefs: boolean;
+ public noCompatMode: boolean;
+ public condenseFlow: boolean;
+ public implicitTypes: Type[];
+ public explicitTypes: Type[];
+ public tag: string | null = null;
+ public result = "";
+ public duplicates: Any[] = [];
+ public usedDuplicates: Any[] = []; // changed from null to []
+ public styleMap: ArrayObject<StyleVariant>;
+ public dump: Any;
+
+ constructor({
+ schema,
+ indent = 2,
+ noArrayIndent = false,
+ skipInvalid = false,
+ flowLevel = -1,
+ styles = null,
+ sortKeys = false,
+ lineWidth = 80,
+ noRefs = false,
+ noCompatMode = false,
+ condenseFlow = false
+ }: DumperStateOptions) {
+ super(schema);
+ this.indent = Math.max(1, indent);
+ this.noArrayIndent = noArrayIndent;
+ this.skipInvalid = skipInvalid;
+ this.flowLevel = flowLevel;
+ this.styleMap = compileStyleMap(this.schema as Schema, styles);
+ this.sortKeys = sortKeys;
+ this.lineWidth = lineWidth;
+ this.noRefs = noRefs;
+ this.noCompatMode = noCompatMode;
+ this.condenseFlow = condenseFlow;
+
+ this.implicitTypes = (this.schema as Schema).compiledImplicit;
+ this.explicitTypes = (this.schema as Schema).compiledExplicit;
+ }
+}
diff --git a/std/encoding/yaml/error.ts b/std/encoding/yaml/error.ts
new file mode 100644
index 000000000..8baf805b5
--- /dev/null
+++ b/std/encoding/yaml/error.ts
@@ -0,0 +1,22 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { Mark } from "./mark.ts";
+
+const { DenoError, ErrorKind } = Deno;
+
+export class YAMLError extends DenoError<typeof ErrorKind.Other> {
+ constructor(
+ message = "(unknown reason)",
+ protected mark: Mark | string = ""
+ ) {
+ super(ErrorKind.Other, `${message} ${mark}`);
+ this.name = this.constructor.name;
+ }
+
+ public toString(_compact: boolean): string {
+ return `${this.name}: ${this.message} ${this.mark}`;
+ }
+}
diff --git a/std/encoding/yaml/example/dump.ts b/std/encoding/yaml/example/dump.ts
new file mode 100644
index 000000000..c4282d657
--- /dev/null
+++ b/std/encoding/yaml/example/dump.ts
@@ -0,0 +1,22 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { stringify } from "../../yaml.ts";
+
+console.log(
+ stringify({
+ foo: {
+ bar: true,
+ test: [
+ "a",
+ "b",
+ {
+ a: false
+ },
+ {
+ a: false
+ }
+ ]
+ },
+ test: "foobar"
+ })
+);
diff --git a/std/encoding/yaml/example/inout.ts b/std/encoding/yaml/example/inout.ts
new file mode 100644
index 000000000..6a52d808b
--- /dev/null
+++ b/std/encoding/yaml/example/inout.ts
@@ -0,0 +1,27 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { parse, stringify } from "../../yaml.ts";
+
+const test = {
+ foo: {
+ bar: true,
+ test: [
+ "a",
+ "b",
+ {
+ a: false
+ },
+ {
+ a: false
+ }
+ ]
+ },
+ test: "foobar"
+};
+
+const string = stringify(test);
+if (Deno.inspect(test) === Deno.inspect(parse(string))) {
+ console.log("In-Out as expected.");
+} else {
+ console.log("Someting went wrong.");
+}
diff --git a/std/encoding/yaml/example/parse.ts b/std/encoding/yaml/example/parse.ts
new file mode 100644
index 000000000..31d4c8877
--- /dev/null
+++ b/std/encoding/yaml/example/parse.ts
@@ -0,0 +1,19 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { parse } from "../../yaml.ts";
+
+const result = parse(`
+test: toto
+foo:
+ bar: True
+ baz: 1
+ qux: ~
+`);
+console.log(result);
+
+const expected = '{ test: "toto", foo: { bar: true, baz: 1, qux: null } }';
+if (Deno.inspect(result) === expected) {
+ console.log("Output is as expected.");
+} else {
+ console.error("Error during parse. Output is not as expect.", expected);
+}
diff --git a/std/encoding/yaml/example/sample_document.ts b/std/encoding/yaml/example/sample_document.ts
new file mode 100644
index 000000000..7b426c142
--- /dev/null
+++ b/std/encoding/yaml/example/sample_document.ts
@@ -0,0 +1,23 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+
+import { parse } from "../../yaml.ts";
+
+const { readFileSync, cwd } = Deno;
+
+(async () => {
+ const yml = readFileSync(`${cwd()}/example/sample_document.yml`);
+
+ const document = new TextDecoder().decode(yml);
+ const obj = parse(document) as object;
+ console.log(obj);
+
+ let i = 0;
+ for (const o of Object.values(obj)) {
+ console.log(`======${i}`);
+ for (const [key, value] of Object.entries(o)) {
+ console.log(key, value);
+ }
+ i++;
+ }
+})();
diff --git a/std/encoding/yaml/example/sample_document.yml b/std/encoding/yaml/example/sample_document.yml
new file mode 100644
index 000000000..1f3c2eb3e
--- /dev/null
+++ b/std/encoding/yaml/example/sample_document.yml
@@ -0,0 +1,197 @@
+---
+# Collection Types #############################################################
+################################################################################
+
+# http://yaml.org/type/map.html -----------------------------------------------#
+
+map:
+ # Unordered set of key: value pairs.
+ Block style: !!map
+ Clark : Evans
+ Ingy : döt Net
+ Oren : Ben-Kiki
+ Flow style: !!map { Clark: Evans, Ingy: döt Net, Oren: Ben-Kiki }
+
+# http://yaml.org/type/omap.html ----------------------------------------------#
+
+omap:
+ # Explicitly typed ordered map (dictionary).
+ Bestiary: !!omap
+ - aardvark: African pig-like ant eater. Ugly.
+ - anteater: South-American ant eater. Two species.
+ - anaconda: South-American constrictor snake. Scaly.
+ # Etc.
+ # Flow style
+ Numbers: !!omap [ one: 1, two: 2, three : 3 ]
+
+# http://yaml.org/type/pairs.html ---------------------------------------------#
+
+pairs:
+ # Explicitly typed pairs.
+ Block tasks: !!pairs
+ - meeting: with team.
+ - meeting: with boss.
+ - break: lunch.
+ - meeting: with client.
+ Flow tasks: !!pairs [ meeting: with team, meeting: with boss ]
+
+# http://yaml.org/type/set.html -----------------------------------------------#
+
+set:
+ # Explicitly typed set.
+ baseball players: !!set
+ ? Mark McGwire
+ ? Sammy Sosa
+ ? Ken Griffey
+ # Flow style
+ baseball teams: !!set { Boston Red Sox, Detroit Tigers, New York Yankees }
+
+# http://yaml.org/type/seq.html -----------------------------------------------#
+
+seq:
+ # Ordered sequence of nodes
+ Block style: !!seq
+ - Mercury # Rotates - no light/dark sides.
+ - Venus # Deadliest. Aptly named.
+ - Earth # Mostly dirt.
+ - Mars # Seems empty.
+ - Jupiter # The king.
+ - Saturn # Pretty.
+ - Uranus # Where the sun hardly shines.
+ - Neptune # Boring. No rings.
+ - Pluto # You call this a planet?
+ Flow style: !!seq [ Mercury, Venus, Earth, Mars, # Rocks
+ Jupiter, Saturn, Uranus, Neptune, # Gas
+ Pluto ] # Overrated
+
+
+# Scalar Types #################################################################
+################################################################################
+
+# http://yaml.org/type/binary.html --------------------------------------------#
+
+binary:
+ canonical: !!binary "\
+ R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5\
+ OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+\
+ +f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC\
+ AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs="
+ generic: !!binary |
+ R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5
+ OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+
+ +f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC
+ AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs=
+ description:
+ The binary value above is a tiny arrow encoded as a gif image.
+
+# http://yaml.org/type/bool.html ----------------------------------------------#
+
+bool:
+ - true
+ - True
+ - TRUE
+ - false
+ - False
+ - FALSE
+
+# http://yaml.org/type/float.html ---------------------------------------------#
+
+float:
+ canonical: 6.8523015e+5
+ exponential: 685.230_15e+03
+ fixed: 685_230.15
+ sexagesimal: 190:20:30.15
+ negative infinity: -.inf
+ not a number: .NaN
+
+# http://yaml.org/type/int.html -----------------------------------------------#
+
+int:
+ canonical: 685230
+ decimal: +685_230
+ octal: 02472256
+ hexadecimal: 0x_0A_74_AE
+ binary: 0b1010_0111_0100_1010_1110
+ sexagesimal: 190:20:30
+
+# http://yaml.org/type/merge.html ---------------------------------------------#
+
+merge:
+ - &CENTER { x: 1, y: 2 }
+ - &LEFT { x: 0, y: 2 }
+ - &BIG { r: 10 }
+ - &SMALL { r: 1 }
+
+ # All the following maps are equal:
+
+ - # Explicit keys
+ x: 1
+ y: 2
+ r: 10
+ label: nothing
+
+ - # Merge one map
+ << : *CENTER
+ r: 10
+ label: center
+
+ - # Merge multiple maps
+ << : [ *CENTER, *BIG ]
+ label: center/big
+
+ - # Override
+ << : [ *BIG, *LEFT, *SMALL ]
+ x: 1
+ label: big/left/small
+
+# http://yaml.org/type/null.html ----------------------------------------------#
+
+null:
+ # This mapping has four keys,
+ # one has a value.
+ empty:
+ canonical: ~
+ english: null
+ ~: null key
+ # This sequence has five
+ # entries, two have values.
+ sparse:
+ - ~
+ - 2nd entry
+ -
+ - 4th entry
+ - Null
+
+# http://yaml.org/type/str.html -----------------------------------------------#
+
+string: abcd
+
+# http://yaml.org/type/timestamp.html -----------------------------------------#
+
+timestamp:
+ canonical: 2001-12-15T02:59:43.1Z
+ valid iso8601: 2001-12-14t21:59:43.10-05:00
+ space separated: 2001-12-14 21:59:43.10 -5
+ no time zone (Z): 2001-12-15 2:59:43.10
+ date (00:00:00Z): 2002-12-14
+
+
+# JavaScript Specific Types ####################################################
+################################################################################
+
+# https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/RegExp
+
+# regexp:
+# simple: !!js/regexp foobar
+# modifiers: !!js/regexp /foobar/mi
+
+# https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/undefined
+
+# undefined: !!js/undefined ~
+
+# https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function
+
+# function: !!js/function >
+# function foobar() {
+# return 'Wow! JS-YAML Rocks!';
+# }
diff --git a/std/encoding/yaml/loader/loader.ts b/std/encoding/yaml/loader/loader.ts
new file mode 100644
index 000000000..e06581b7e
--- /dev/null
+++ b/std/encoding/yaml/loader/loader.ts
@@ -0,0 +1,1798 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+/* eslint-disable no-conditional-assignment */
+/* eslint-disable max-len */
+
+import { YAMLError } from "../error.ts";
+import { Mark } from "../mark.ts";
+import { Type } from "../type.ts";
+import * as common from "../utils.ts";
+import { LoaderState, LoaderStateOptions, ResultType } from "./loader_state.ts";
+
+type Any = common.Any;
+type ArrayObject<T = Any> = common.ArrayObject<T>;
+
+const _hasOwnProperty = Object.prototype.hasOwnProperty;
+
+const CONTEXT_FLOW_IN = 1;
+const CONTEXT_FLOW_OUT = 2;
+const CONTEXT_BLOCK_IN = 3;
+const CONTEXT_BLOCK_OUT = 4;
+
+const CHOMPING_CLIP = 1;
+const CHOMPING_STRIP = 2;
+const CHOMPING_KEEP = 3;
+
+const PATTERN_NON_PRINTABLE = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x84\x86-\x9F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/;
+const PATTERN_NON_ASCII_LINE_BREAKS = /[\x85\u2028\u2029]/;
+const PATTERN_FLOW_INDICATORS = /[,\[\]\{\}]/;
+const PATTERN_TAG_HANDLE = /^(?:!|!!|![a-z\-]+!)$/i;
+/* eslint-disable-next-line max-len */
+const PATTERN_TAG_URI = /^(?:!|[^,\[\]\{\}])(?:%[0-9a-f]{2}|[0-9a-z\-#;\/\?:@&=\+\$,_\.!~\*'\(\)\[\]])*$/i;
+
+function _class(obj: unknown): string {
+ return Object.prototype.toString.call(obj);
+}
+
+function isEOL(c: number): boolean {
+ return c === 0x0a /* LF */ || c === 0x0d /* CR */;
+}
+
+function isWhiteSpace(c: number): boolean {
+ return c === 0x09 /* Tab */ || c === 0x20 /* Space */;
+}
+
+function isWsOrEol(c: number): boolean {
+ return (
+ c === 0x09 /* Tab */ ||
+ c === 0x20 /* Space */ ||
+ c === 0x0a /* LF */ ||
+ c === 0x0d /* CR */
+ );
+}
+
+function isFlowIndicator(c: number): boolean {
+ return (
+ c === 0x2c /* , */ ||
+ c === 0x5b /* [ */ ||
+ c === 0x5d /* ] */ ||
+ c === 0x7b /* { */ ||
+ c === 0x7d /* } */
+ );
+}
+
+function fromHexCode(c: number): number {
+ if (0x30 /* 0 */ <= c && c <= 0x39 /* 9 */) {
+ return c - 0x30;
+ }
+
+ const lc = c | 0x20;
+
+ if (0x61 /* a */ <= lc && lc <= 0x66 /* f */) {
+ return lc - 0x61 + 10;
+ }
+
+ return -1;
+}
+
+function escapedHexLen(c: number): number {
+ if (c === 0x78 /* x */) {
+ return 2;
+ }
+ if (c === 0x75 /* u */) {
+ return 4;
+ }
+ if (c === 0x55 /* U */) {
+ return 8;
+ }
+ return 0;
+}
+
+function fromDecimalCode(c: number): number {
+ if (0x30 /* 0 */ <= c && c <= 0x39 /* 9 */) {
+ return c - 0x30;
+ }
+
+ return -1;
+}
+
+function simpleEscapeSequence(c: number): string {
+ /* eslint:disable:prettier */
+ return c === 0x30 /* 0 */
+ ? "\x00"
+ : c === 0x61 /* a */
+ ? "\x07"
+ : c === 0x62 /* b */
+ ? "\x08"
+ : c === 0x74 /* t */
+ ? "\x09"
+ : c === 0x09 /* Tab */
+ ? "\x09"
+ : c === 0x6e /* n */
+ ? "\x0A"
+ : c === 0x76 /* v */
+ ? "\x0B"
+ : c === 0x66 /* f */
+ ? "\x0C"
+ : c === 0x72 /* r */
+ ? "\x0D"
+ : c === 0x65 /* e */
+ ? "\x1B"
+ : c === 0x20 /* Space */
+ ? " "
+ : c === 0x22 /* " */
+ ? "\x22"
+ : c === 0x2f /* / */
+ ? "/"
+ : c === 0x5c /* \ */
+ ? "\x5C"
+ : c === 0x4e /* N */
+ ? "\x85"
+ : c === 0x5f /* _ */
+ ? "\xA0"
+ : c === 0x4c /* L */
+ ? "\u2028"
+ : c === 0x50 /* P */
+ ? "\u2029"
+ : "";
+ /* eslint:enable:prettier */
+}
+
+function charFromCodepoint(c: number): string {
+ if (c <= 0xffff) {
+ return String.fromCharCode(c);
+ }
+ // Encode UTF-16 surrogate pair
+ // https://en.wikipedia.org/wiki/UTF-16#Code_points_U.2B010000_to_U.2B10FFFF
+ return String.fromCharCode(
+ ((c - 0x010000) >> 10) + 0xd800,
+ ((c - 0x010000) & 0x03ff) + 0xdc00
+ );
+}
+
+const simpleEscapeCheck = new Array(256); // integer, for fast access
+const simpleEscapeMap = new Array(256);
+for (let i = 0; i < 256; i++) {
+ simpleEscapeCheck[i] = simpleEscapeSequence(i) ? 1 : 0;
+ simpleEscapeMap[i] = simpleEscapeSequence(i);
+}
+
+function generateError(state: LoaderState, message: string): YAMLError {
+ return new YAMLError(
+ message,
+ new Mark(
+ state.filename as string,
+ state.input,
+ state.position,
+ state.line,
+ state.position - state.lineStart
+ )
+ );
+}
+
+function throwError(state: LoaderState, message: string): never {
+ throw generateError(state, message);
+}
+
+function throwWarning(state: LoaderState, message: string): void {
+ if (state.onWarning) {
+ state.onWarning.call(null, generateError(state, message));
+ }
+}
+
+interface DirectiveHandlers {
+ [directive: string]: (
+ state: LoaderState,
+ name: string,
+ ...args: unknown[]
+ ) => void;
+}
+
+const directiveHandlers: DirectiveHandlers = {
+ YAML(state, _name, ...args: string[]) {
+ if (state.version !== null) {
+ return throwError(state, "duplication of %YAML directive");
+ }
+
+ if (args.length !== 1) {
+ return throwError(state, "YAML directive accepts exactly one argument");
+ }
+
+ const match = /^([0-9]+)\.([0-9]+)$/.exec(args[0]);
+ if (match === null) {
+ return throwError(state, "ill-formed argument of the YAML directive");
+ }
+
+ const major = parseInt(match[1], 10);
+ const minor = parseInt(match[2], 10);
+ if (major !== 1) {
+ return throwError(state, "unacceptable YAML version of the document");
+ }
+
+ state.version = args[0];
+ state.checkLineBreaks = minor < 2;
+ if (minor !== 1 && minor !== 2) {
+ return throwWarning(state, "unsupported YAML version of the document");
+ }
+ },
+
+ TAG(state, _name, ...args: string[]): void {
+ if (args.length !== 2) {
+ return throwError(state, "TAG directive accepts exactly two arguments");
+ }
+
+ const handle = args[0];
+ const prefix = args[1];
+
+ if (!PATTERN_TAG_HANDLE.test(handle)) {
+ return throwError(
+ state,
+ "ill-formed tag handle (first argument) of the TAG directive"
+ );
+ }
+
+ if (_hasOwnProperty.call(state.tagMap, handle)) {
+ return throwError(
+ state,
+ `there is a previously declared suffix for "${handle}" tag handle`
+ );
+ }
+
+ if (!PATTERN_TAG_URI.test(prefix)) {
+ return throwError(
+ state,
+ "ill-formed tag prefix (second argument) of the TAG directive"
+ );
+ }
+
+ if (typeof state.tagMap === "undefined") {
+ state.tagMap = {};
+ }
+ state.tagMap[handle] = prefix;
+ }
+};
+
+function captureSegment(
+ state: LoaderState,
+ start: number,
+ end: number,
+ checkJson: boolean
+): void {
+ let result: string;
+ if (start < end) {
+ result = state.input.slice(start, end);
+
+ if (checkJson) {
+ for (
+ let position = 0, length = result.length;
+ position < length;
+ position++
+ ) {
+ const character = result.charCodeAt(position);
+ if (
+ !(character === 0x09 || (0x20 <= character && character <= 0x10ffff))
+ ) {
+ return throwError(state, "expected valid JSON character");
+ }
+ }
+ } else if (PATTERN_NON_PRINTABLE.test(result)) {
+ return throwError(state, "the stream contains non-printable characters");
+ }
+
+ state.result += result;
+ }
+}
+
+function mergeMappings(
+ state: LoaderState,
+ destination: ArrayObject,
+ source: ArrayObject,
+ overridableKeys: ArrayObject<boolean>
+): void {
+ if (!common.isObject(source)) {
+ return throwError(
+ state,
+ "cannot merge mappings; the provided source object is unacceptable"
+ );
+ }
+
+ const keys = Object.keys(source);
+ for (let i = 0, len = keys.length; i < len; i++) {
+ const key = keys[i];
+ if (!_hasOwnProperty.call(destination, key)) {
+ destination[key] = (source as ArrayObject)[key];
+ overridableKeys[key] = true;
+ }
+ }
+}
+
+function storeMappingPair(
+ state: LoaderState,
+ result: ArrayObject | null,
+ overridableKeys: ArrayObject<boolean>,
+ keyTag: string | null,
+ keyNode: Any,
+ valueNode: unknown,
+ startLine?: number,
+ startPos?: number
+): ArrayObject {
+ // The output is a plain object here, so keys can only be strings.
+ // We need to convert keyNode to a string, but doing so can hang the process
+ // (deeply nested arrays that explode exponentially using aliases).
+ if (Array.isArray(keyNode)) {
+ keyNode = Array.prototype.slice.call(keyNode);
+
+ for (let index = 0, quantity = keyNode.length; index < quantity; index++) {
+ if (Array.isArray(keyNode[index])) {
+ return throwError(state, "nested arrays are not supported inside keys");
+ }
+
+ if (
+ typeof keyNode === "object" &&
+ _class(keyNode[index]) === "[object Object]"
+ ) {
+ keyNode[index] = "[object Object]";
+ }
+ }
+ }
+
+ // Avoid code execution in load() via toString property
+ // (still use its own toString for arrays, timestamps,
+ // and whatever user schema extensions happen to have @@toStringTag)
+ if (typeof keyNode === "object" && _class(keyNode) === "[object Object]") {
+ keyNode = "[object Object]";
+ }
+
+ keyNode = String(keyNode);
+
+ if (result === null) {
+ result = {};
+ }
+
+ if (keyTag === "tag:yaml.org,2002:merge") {
+ if (Array.isArray(valueNode)) {
+ for (
+ let index = 0, quantity = valueNode.length;
+ index < quantity;
+ index++
+ ) {
+ mergeMappings(state, result, valueNode[index], overridableKeys);
+ }
+ } else {
+ mergeMappings(state, result, valueNode, overridableKeys);
+ }
+ } else {
+ if (
+ !state.json &&
+ !_hasOwnProperty.call(overridableKeys, keyNode) &&
+ _hasOwnProperty.call(result, keyNode)
+ ) {
+ state.line = startLine || state.line;
+ state.position = startPos || state.position;
+ return throwError(state, "duplicated mapping key");
+ }
+ result[keyNode] = valueNode;
+ delete overridableKeys[keyNode];
+ }
+
+ return result;
+}
+
+function readLineBreak(state: LoaderState): void {
+ const ch = state.input.charCodeAt(state.position);
+
+ if (ch === 0x0a /* LF */) {
+ state.position++;
+ } else if (ch === 0x0d /* CR */) {
+ state.position++;
+ if (state.input.charCodeAt(state.position) === 0x0a /* LF */) {
+ state.position++;
+ }
+ } else {
+ return throwError(state, "a line break is expected");
+ }
+
+ state.line += 1;
+ state.lineStart = state.position;
+}
+
+function skipSeparationSpace(
+ state: LoaderState,
+ allowComments: boolean,
+ checkIndent: number
+): number {
+ let lineBreaks = 0,
+ ch = state.input.charCodeAt(state.position);
+
+ while (ch !== 0) {
+ while (isWhiteSpace(ch)) {
+ ch = state.input.charCodeAt(++state.position);
+ }
+
+ if (allowComments && ch === 0x23 /* # */) {
+ do {
+ ch = state.input.charCodeAt(++state.position);
+ } while (ch !== 0x0a /* LF */ && ch !== 0x0d /* CR */ && ch !== 0);
+ }
+
+ if (isEOL(ch)) {
+ readLineBreak(state);
+
+ ch = state.input.charCodeAt(state.position);
+ lineBreaks++;
+ state.lineIndent = 0;
+
+ while (ch === 0x20 /* Space */) {
+ state.lineIndent++;
+ ch = state.input.charCodeAt(++state.position);
+ }
+ } else {
+ break;
+ }
+ }
+
+ if (
+ checkIndent !== -1 &&
+ lineBreaks !== 0 &&
+ state.lineIndent < checkIndent
+ ) {
+ throwWarning(state, "deficient indentation");
+ }
+
+ return lineBreaks;
+}
+
+function testDocumentSeparator(state: LoaderState): boolean {
+ let _position = state.position;
+ let ch = state.input.charCodeAt(_position);
+
+ // Condition state.position === state.lineStart is tested
+ // in parent on each call, for efficiency. No needs to test here again.
+ if (
+ (ch === 0x2d /* - */ || ch === 0x2e) /* . */ &&
+ ch === state.input.charCodeAt(_position + 1) &&
+ ch === state.input.charCodeAt(_position + 2)
+ ) {
+ _position += 3;
+
+ ch = state.input.charCodeAt(_position);
+
+ if (ch === 0 || isWsOrEol(ch)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+function writeFoldedLines(state: LoaderState, count: number): void {
+ if (count === 1) {
+ state.result += " ";
+ } else if (count > 1) {
+ state.result += common.repeat("\n", count - 1);
+ }
+}
+
+function readPlainScalar(
+ state: LoaderState,
+ nodeIndent: number,
+ withinFlowCollection: boolean
+): boolean {
+ const kind = state.kind;
+ const result = state.result;
+ let ch = state.input.charCodeAt(state.position);
+
+ if (
+ isWsOrEol(ch) ||
+ isFlowIndicator(ch) ||
+ ch === 0x23 /* # */ ||
+ ch === 0x26 /* & */ ||
+ ch === 0x2a /* * */ ||
+ ch === 0x21 /* ! */ ||
+ ch === 0x7c /* | */ ||
+ ch === 0x3e /* > */ ||
+ ch === 0x27 /* ' */ ||
+ ch === 0x22 /* " */ ||
+ ch === 0x25 /* % */ ||
+ ch === 0x40 /* @ */ ||
+ ch === 0x60 /* ` */
+ ) {
+ return false;
+ }
+
+ let following: number;
+ if (ch === 0x3f /* ? */ || ch === 0x2d /* - */) {
+ following = state.input.charCodeAt(state.position + 1);
+
+ if (
+ isWsOrEol(following) ||
+ (withinFlowCollection && isFlowIndicator(following))
+ ) {
+ return false;
+ }
+ }
+
+ state.kind = "scalar";
+ state.result = "";
+ let captureEnd: number,
+ captureStart = (captureEnd = state.position);
+ let hasPendingContent = false;
+ let line = 0;
+ while (ch !== 0) {
+ if (ch === 0x3a /* : */) {
+ following = state.input.charCodeAt(state.position + 1);
+
+ if (
+ isWsOrEol(following) ||
+ (withinFlowCollection && isFlowIndicator(following))
+ ) {
+ break;
+ }
+ } else if (ch === 0x23 /* # */) {
+ const preceding = state.input.charCodeAt(state.position - 1);
+
+ if (isWsOrEol(preceding)) {
+ break;
+ }
+ } else if (
+ (state.position === state.lineStart && testDocumentSeparator(state)) ||
+ (withinFlowCollection && isFlowIndicator(ch))
+ ) {
+ break;
+ } else if (isEOL(ch)) {
+ line = state.line;
+ const lineStart = state.lineStart;
+ const lineIndent = state.lineIndent;
+ skipSeparationSpace(state, false, -1);
+
+ if (state.lineIndent >= nodeIndent) {
+ hasPendingContent = true;
+ ch = state.input.charCodeAt(state.position);
+ continue;
+ } else {
+ state.position = captureEnd;
+ state.line = line;
+ state.lineStart = lineStart;
+ state.lineIndent = lineIndent;
+ break;
+ }
+ }
+
+ if (hasPendingContent) {
+ captureSegment(state, captureStart, captureEnd, false);
+ writeFoldedLines(state, state.line - line);
+ captureStart = captureEnd = state.position;
+ hasPendingContent = false;
+ }
+
+ if (!isWhiteSpace(ch)) {
+ captureEnd = state.position + 1;
+ }
+
+ ch = state.input.charCodeAt(++state.position);
+ }
+
+ captureSegment(state, captureStart, captureEnd, false);
+
+ if (!common.isNullOrUndefined(state.result)) {
+ return true;
+ }
+
+ state.kind = kind;
+ state.result = result;
+ return false;
+}
+
+function readSingleQuotedScalar(
+ state: LoaderState,
+ nodeIndent: number
+): boolean {
+ let ch, captureStart, captureEnd;
+
+ ch = state.input.charCodeAt(state.position);
+
+ if (ch !== 0x27 /* ' */) {
+ return false;
+ }
+
+ state.kind = "scalar";
+ state.result = "";
+ state.position++;
+ captureStart = captureEnd = state.position;
+
+ while ((ch = state.input.charCodeAt(state.position)) !== 0) {
+ if (ch === 0x27 /* ' */) {
+ captureSegment(state, captureStart, state.position, true);
+ ch = state.input.charCodeAt(++state.position);
+
+ if (ch === 0x27 /* ' */) {
+ captureStart = state.position;
+ state.position++;
+ captureEnd = state.position;
+ } else {
+ return true;
+ }
+ } else if (isEOL(ch)) {
+ captureSegment(state, captureStart, captureEnd, true);
+ writeFoldedLines(state, skipSeparationSpace(state, false, nodeIndent));
+ captureStart = captureEnd = state.position;
+ } else if (
+ state.position === state.lineStart &&
+ testDocumentSeparator(state)
+ ) {
+ return throwError(
+ state,
+ "unexpected end of the document within a single quoted scalar"
+ );
+ } else {
+ state.position++;
+ captureEnd = state.position;
+ }
+ }
+
+ return throwError(
+ state,
+ "unexpected end of the stream within a single quoted scalar"
+ );
+}
+
+function readDoubleQuotedScalar(
+ state: LoaderState,
+ nodeIndent: number
+): boolean {
+ let ch = state.input.charCodeAt(state.position);
+
+ if (ch !== 0x22 /* " */) {
+ return false;
+ }
+
+ state.kind = "scalar";
+ state.result = "";
+ state.position++;
+ let captureEnd: number,
+ captureStart = (captureEnd = state.position);
+ let tmp: number;
+ while ((ch = state.input.charCodeAt(state.position)) !== 0) {
+ if (ch === 0x22 /* " */) {
+ captureSegment(state, captureStart, state.position, true);
+ state.position++;
+ return true;
+ }
+ if (ch === 0x5c /* \ */) {
+ captureSegment(state, captureStart, state.position, true);
+ ch = state.input.charCodeAt(++state.position);
+
+ if (isEOL(ch)) {
+ skipSeparationSpace(state, false, nodeIndent);
+
+ // TODO: rework to inline fn with no type cast?
+ } else if (ch < 256 && simpleEscapeCheck[ch]) {
+ state.result += simpleEscapeMap[ch];
+ state.position++;
+ } else if ((tmp = escapedHexLen(ch)) > 0) {
+ let hexLength = tmp;
+ let hexResult = 0;
+
+ for (; hexLength > 0; hexLength--) {
+ ch = state.input.charCodeAt(++state.position);
+
+ if ((tmp = fromHexCode(ch)) >= 0) {
+ hexResult = (hexResult << 4) + tmp;
+ } else {
+ return throwError(state, "expected hexadecimal character");
+ }
+ }
+
+ state.result += charFromCodepoint(hexResult);
+
+ state.position++;
+ } else {
+ return throwError(state, "unknown escape sequence");
+ }
+
+ captureStart = captureEnd = state.position;
+ } else if (isEOL(ch)) {
+ captureSegment(state, captureStart, captureEnd, true);
+ writeFoldedLines(state, skipSeparationSpace(state, false, nodeIndent));
+ captureStart = captureEnd = state.position;
+ } else if (
+ state.position === state.lineStart &&
+ testDocumentSeparator(state)
+ ) {
+ return throwError(
+ state,
+ "unexpected end of the document within a double quoted scalar"
+ );
+ } else {
+ state.position++;
+ captureEnd = state.position;
+ }
+ }
+
+ return throwError(
+ state,
+ "unexpected end of the stream within a double quoted scalar"
+ );
+}
+
+function readFlowCollection(state: LoaderState, nodeIndent: number): boolean {
+ let ch = state.input.charCodeAt(state.position);
+ let terminator: number;
+ let isMapping = true;
+ let result: ResultType = {};
+ if (ch === 0x5b /* [ */) {
+ terminator = 0x5d; /* ] */
+ isMapping = false;
+ result = [];
+ } else if (ch === 0x7b /* { */) {
+ terminator = 0x7d; /* } */
+ } else {
+ return false;
+ }
+
+ if (
+ state.anchor !== null &&
+ typeof state.anchor != "undefined" &&
+ typeof state.anchorMap != "undefined"
+ ) {
+ state.anchorMap[state.anchor] = result;
+ }
+
+ ch = state.input.charCodeAt(++state.position);
+
+ const tag = state.tag,
+ anchor = state.anchor;
+ let readNext = true;
+ let valueNode,
+ keyNode,
+ keyTag: string | null = (keyNode = valueNode = null),
+ isExplicitPair: boolean,
+ isPair = (isExplicitPair = false);
+ let following = 0,
+ line = 0;
+ const overridableKeys: ArrayObject<boolean> = {};
+ while (ch !== 0) {
+ skipSeparationSpace(state, true, nodeIndent);
+
+ ch = state.input.charCodeAt(state.position);
+
+ if (ch === terminator) {
+ state.position++;
+ state.tag = tag;
+ state.anchor = anchor;
+ state.kind = isMapping ? "mapping" : "sequence";
+ state.result = result;
+ return true;
+ }
+ if (!readNext) {
+ return throwError(state, "missed comma between flow collection entries");
+ }
+
+ keyTag = keyNode = valueNode = null;
+ isPair = isExplicitPair = false;
+
+ if (ch === 0x3f /* ? */) {
+ following = state.input.charCodeAt(state.position + 1);
+
+ if (isWsOrEol(following)) {
+ isPair = isExplicitPair = true;
+ state.position++;
+ skipSeparationSpace(state, true, nodeIndent);
+ }
+ }
+
+ line = state.line;
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
+ composeNode(state, nodeIndent, CONTEXT_FLOW_IN, false, true);
+ keyTag = state.tag || null;
+ keyNode = state.result;
+ skipSeparationSpace(state, true, nodeIndent);
+
+ ch = state.input.charCodeAt(state.position);
+
+ if ((isExplicitPair || state.line === line) && ch === 0x3a /* : */) {
+ isPair = true;
+ ch = state.input.charCodeAt(++state.position);
+ skipSeparationSpace(state, true, nodeIndent);
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
+ composeNode(state, nodeIndent, CONTEXT_FLOW_IN, false, true);
+ valueNode = state.result;
+ }
+
+ if (isMapping) {
+ storeMappingPair(
+ state,
+ result,
+ overridableKeys,
+ keyTag,
+ keyNode,
+ valueNode
+ );
+ } else if (isPair) {
+ (result as Array<{}>).push(
+ storeMappingPair(
+ state,
+ null,
+ overridableKeys,
+ keyTag,
+ keyNode,
+ valueNode
+ )
+ );
+ } else {
+ (result as ResultType[]).push(keyNode as ResultType);
+ }
+
+ skipSeparationSpace(state, true, nodeIndent);
+
+ ch = state.input.charCodeAt(state.position);
+
+ if (ch === 0x2c /* , */) {
+ readNext = true;
+ ch = state.input.charCodeAt(++state.position);
+ } else {
+ readNext = false;
+ }
+ }
+
+ return throwError(
+ state,
+ "unexpected end of the stream within a flow collection"
+ );
+}
+
+function readBlockScalar(state: LoaderState, nodeIndent: number): boolean {
+ let chomping = CHOMPING_CLIP,
+ didReadContent = false,
+ detectedIndent = false,
+ textIndent = nodeIndent,
+ emptyLines = 0,
+ atMoreIndented = false;
+
+ let ch = state.input.charCodeAt(state.position);
+
+ let folding = false;
+ if (ch === 0x7c /* | */) {
+ folding = false;
+ } else if (ch === 0x3e /* > */) {
+ folding = true;
+ } else {
+ return false;
+ }
+
+ state.kind = "scalar";
+ state.result = "";
+
+ let tmp = 0;
+ while (ch !== 0) {
+ ch = state.input.charCodeAt(++state.position);
+
+ if (ch === 0x2b /* + */ || ch === 0x2d /* - */) {
+ if (CHOMPING_CLIP === chomping) {
+ chomping = ch === 0x2b /* + */ ? CHOMPING_KEEP : CHOMPING_STRIP;
+ } else {
+ return throwError(state, "repeat of a chomping mode identifier");
+ }
+ } else if ((tmp = fromDecimalCode(ch)) >= 0) {
+ if (tmp === 0) {
+ return throwError(
+ state,
+ "bad explicit indentation width of a block scalar; it cannot be less than one"
+ );
+ } else if (!detectedIndent) {
+ textIndent = nodeIndent + tmp - 1;
+ detectedIndent = true;
+ } else {
+ return throwError(state, "repeat of an indentation width identifier");
+ }
+ } else {
+ break;
+ }
+ }
+
+ if (isWhiteSpace(ch)) {
+ do {
+ ch = state.input.charCodeAt(++state.position);
+ } while (isWhiteSpace(ch));
+
+ if (ch === 0x23 /* # */) {
+ do {
+ ch = state.input.charCodeAt(++state.position);
+ } while (!isEOL(ch) && ch !== 0);
+ }
+ }
+
+ while (ch !== 0) {
+ readLineBreak(state);
+ state.lineIndent = 0;
+
+ ch = state.input.charCodeAt(state.position);
+
+ while (
+ (!detectedIndent || state.lineIndent < textIndent) &&
+ ch === 0x20 /* Space */
+ ) {
+ state.lineIndent++;
+ ch = state.input.charCodeAt(++state.position);
+ }
+
+ if (!detectedIndent && state.lineIndent > textIndent) {
+ textIndent = state.lineIndent;
+ }
+
+ if (isEOL(ch)) {
+ emptyLines++;
+ continue;
+ }
+
+ // End of the scalar.
+ if (state.lineIndent < textIndent) {
+ // Perform the chomping.
+ if (chomping === CHOMPING_KEEP) {
+ state.result += common.repeat(
+ "\n",
+ didReadContent ? 1 + emptyLines : emptyLines
+ );
+ } else if (chomping === CHOMPING_CLIP) {
+ if (didReadContent) {
+ // i.e. only if the scalar is not empty.
+ state.result += "\n";
+ }
+ }
+
+ // Break this `while` cycle and go to the funciton's epilogue.
+ break;
+ }
+
+ // Folded style: use fancy rules to handle line breaks.
+ if (folding) {
+ // Lines starting with white space characters (more-indented lines) are not folded.
+ if (isWhiteSpace(ch)) {
+ atMoreIndented = true;
+ // except for the first content line (cf. Example 8.1)
+ state.result += common.repeat(
+ "\n",
+ didReadContent ? 1 + emptyLines : emptyLines
+ );
+
+ // End of more-indented block.
+ } else if (atMoreIndented) {
+ atMoreIndented = false;
+ state.result += common.repeat("\n", emptyLines + 1);
+
+ // Just one line break - perceive as the same line.
+ } else if (emptyLines === 0) {
+ if (didReadContent) {
+ // i.e. only if we have already read some scalar content.
+ state.result += " ";
+ }
+
+ // Several line breaks - perceive as different lines.
+ } else {
+ state.result += common.repeat("\n", emptyLines);
+ }
+
+ // Literal style: just add exact number of line breaks between content lines.
+ } else {
+ // Keep all line breaks except the header line break.
+ state.result += common.repeat(
+ "\n",
+ didReadContent ? 1 + emptyLines : emptyLines
+ );
+ }
+
+ didReadContent = true;
+ detectedIndent = true;
+ emptyLines = 0;
+ const captureStart = state.position;
+
+ while (!isEOL(ch) && ch !== 0) {
+ ch = state.input.charCodeAt(++state.position);
+ }
+
+ captureSegment(state, captureStart, state.position, false);
+ }
+
+ return true;
+}
+
+function readBlockSequence(state: LoaderState, nodeIndent: number): boolean {
+ let line: number,
+ following: number,
+ detected = false,
+ ch: number;
+ const tag = state.tag,
+ anchor = state.anchor,
+ result: unknown[] = [];
+
+ if (
+ state.anchor !== null &&
+ typeof state.anchor !== "undefined" &&
+ typeof state.anchorMap !== "undefined"
+ ) {
+ state.anchorMap[state.anchor] = result;
+ }
+
+ ch = state.input.charCodeAt(state.position);
+
+ while (ch !== 0) {
+ if (ch !== 0x2d /* - */) {
+ break;
+ }
+
+ following = state.input.charCodeAt(state.position + 1);
+
+ if (!isWsOrEol(following)) {
+ break;
+ }
+
+ detected = true;
+ state.position++;
+
+ if (skipSeparationSpace(state, true, -1)) {
+ if (state.lineIndent <= nodeIndent) {
+ result.push(null);
+ ch = state.input.charCodeAt(state.position);
+ continue;
+ }
+ }
+
+ line = state.line;
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
+ composeNode(state, nodeIndent, CONTEXT_BLOCK_IN, false, true);
+ result.push(state.result);
+ skipSeparationSpace(state, true, -1);
+
+ ch = state.input.charCodeAt(state.position);
+
+ if ((state.line === line || state.lineIndent > nodeIndent) && ch !== 0) {
+ return throwError(state, "bad indentation of a sequence entry");
+ } else if (state.lineIndent < nodeIndent) {
+ break;
+ }
+ }
+
+ if (detected) {
+ state.tag = tag;
+ state.anchor = anchor;
+ state.kind = "sequence";
+ state.result = result;
+ return true;
+ }
+ return false;
+}
+
+function readBlockMapping(
+ state: LoaderState,
+ nodeIndent: number,
+ flowIndent: number
+): boolean {
+ const tag = state.tag,
+ anchor = state.anchor,
+ result = {},
+ overridableKeys = {};
+ let following: number,
+ allowCompact = false,
+ line: number,
+ pos: number,
+ keyTag = null,
+ keyNode = null,
+ valueNode = null,
+ atExplicitKey = false,
+ detected = false,
+ ch: number;
+
+ if (
+ state.anchor !== null &&
+ typeof state.anchor !== "undefined" &&
+ typeof state.anchorMap !== "undefined"
+ ) {
+ state.anchorMap[state.anchor] = result;
+ }
+
+ ch = state.input.charCodeAt(state.position);
+
+ while (ch !== 0) {
+ following = state.input.charCodeAt(state.position + 1);
+ line = state.line; // Save the current line.
+ pos = state.position;
+
+ //
+ // Explicit notation case. There are two separate blocks:
+ // first for the key (denoted by "?") and second for the value (denoted by ":")
+ //
+ if ((ch === 0x3f /* ? */ || ch === 0x3a) /* : */ && isWsOrEol(following)) {
+ if (ch === 0x3f /* ? */) {
+ if (atExplicitKey) {
+ storeMappingPair(
+ state,
+ result,
+ overridableKeys,
+ keyTag as string,
+ keyNode,
+ null
+ );
+ keyTag = keyNode = valueNode = null;
+ }
+
+ detected = true;
+ atExplicitKey = true;
+ allowCompact = true;
+ } else if (atExplicitKey) {
+ // i.e. 0x3A/* : */ === character after the explicit key.
+ atExplicitKey = false;
+ allowCompact = true;
+ } else {
+ return throwError(
+ state,
+ "incomplete explicit mapping pair; a key node is missed; or followed by a non-tabulated empty line"
+ );
+ }
+
+ state.position += 1;
+ ch = following;
+
+ //
+ // Implicit notation case. Flow-style node as the key first, then ":", and the value.
+ //
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
+ } else if (composeNode(state, flowIndent, CONTEXT_FLOW_OUT, false, true)) {
+ if (state.line === line) {
+ ch = state.input.charCodeAt(state.position);
+
+ while (isWhiteSpace(ch)) {
+ ch = state.input.charCodeAt(++state.position);
+ }
+
+ if (ch === 0x3a /* : */) {
+ ch = state.input.charCodeAt(++state.position);
+
+ if (!isWsOrEol(ch)) {
+ return throwError(
+ state,
+ "a whitespace character is expected after the key-value separator within a block mapping"
+ );
+ }
+
+ if (atExplicitKey) {
+ storeMappingPair(
+ state,
+ result,
+ overridableKeys,
+ keyTag as string,
+ keyNode,
+ null
+ );
+ keyTag = keyNode = valueNode = null;
+ }
+
+ detected = true;
+ atExplicitKey = false;
+ allowCompact = false;
+ keyTag = state.tag;
+ keyNode = state.result;
+ } else if (detected) {
+ return throwError(
+ state,
+ "can not read an implicit mapping pair; a colon is missed"
+ );
+ } else {
+ state.tag = tag;
+ state.anchor = anchor;
+ return true; // Keep the result of `composeNode`.
+ }
+ } else if (detected) {
+ return throwError(
+ state,
+ "can not read a block mapping entry; a multiline key may not be an implicit key"
+ );
+ } else {
+ state.tag = tag;
+ state.anchor = anchor;
+ return true; // Keep the result of `composeNode`.
+ }
+ } else {
+ break; // Reading is done. Go to the epilogue.
+ }
+
+ //
+ // Common reading code for both explicit and implicit notations.
+ //
+ if (state.line === line || state.lineIndent > nodeIndent) {
+ if (
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
+ composeNode(state, nodeIndent, CONTEXT_BLOCK_OUT, true, allowCompact)
+ ) {
+ if (atExplicitKey) {
+ keyNode = state.result;
+ } else {
+ valueNode = state.result;
+ }
+ }
+
+ if (!atExplicitKey) {
+ storeMappingPair(
+ state,
+ result,
+ overridableKeys,
+ keyTag as string,
+ keyNode,
+ valueNode,
+ line,
+ pos
+ );
+ keyTag = keyNode = valueNode = null;
+ }
+
+ skipSeparationSpace(state, true, -1);
+ ch = state.input.charCodeAt(state.position);
+ }
+
+ if (state.lineIndent > nodeIndent && ch !== 0) {
+ return throwError(state, "bad indentation of a mapping entry");
+ } else if (state.lineIndent < nodeIndent) {
+ break;
+ }
+ }
+
+ //
+ // Epilogue.
+ //
+
+ // Special case: last mapping's node contains only the key in explicit notation.
+ if (atExplicitKey) {
+ storeMappingPair(
+ state,
+ result,
+ overridableKeys,
+ keyTag as string,
+ keyNode,
+ null
+ );
+ }
+
+ // Expose the resulting mapping.
+ if (detected) {
+ state.tag = tag;
+ state.anchor = anchor;
+ state.kind = "mapping";
+ state.result = result;
+ }
+
+ return detected;
+}
+
+function readTagProperty(state: LoaderState): boolean {
+ let position: number,
+ isVerbatim = false,
+ isNamed = false,
+ tagHandle = "",
+ tagName: string,
+ ch: number;
+
+ ch = state.input.charCodeAt(state.position);
+
+ if (ch !== 0x21 /* ! */) return false;
+
+ if (state.tag !== null) {
+ return throwError(state, "duplication of a tag property");
+ }
+
+ ch = state.input.charCodeAt(++state.position);
+
+ if (ch === 0x3c /* < */) {
+ isVerbatim = true;
+ ch = state.input.charCodeAt(++state.position);
+ } else if (ch === 0x21 /* ! */) {
+ isNamed = true;
+ tagHandle = "!!";
+ ch = state.input.charCodeAt(++state.position);
+ } else {
+ tagHandle = "!";
+ }
+
+ position = state.position;
+
+ if (isVerbatim) {
+ do {
+ ch = state.input.charCodeAt(++state.position);
+ } while (ch !== 0 && ch !== 0x3e /* > */);
+
+ if (state.position < state.length) {
+ tagName = state.input.slice(position, state.position);
+ ch = state.input.charCodeAt(++state.position);
+ } else {
+ return throwError(
+ state,
+ "unexpected end of the stream within a verbatim tag"
+ );
+ }
+ } else {
+ while (ch !== 0 && !isWsOrEol(ch)) {
+ if (ch === 0x21 /* ! */) {
+ if (!isNamed) {
+ tagHandle = state.input.slice(position - 1, state.position + 1);
+
+ if (!PATTERN_TAG_HANDLE.test(tagHandle)) {
+ return throwError(
+ state,
+ "named tag handle cannot contain such characters"
+ );
+ }
+
+ isNamed = true;
+ position = state.position + 1;
+ } else {
+ return throwError(
+ state,
+ "tag suffix cannot contain exclamation marks"
+ );
+ }
+ }
+
+ ch = state.input.charCodeAt(++state.position);
+ }
+
+ tagName = state.input.slice(position, state.position);
+
+ if (PATTERN_FLOW_INDICATORS.test(tagName)) {
+ return throwError(
+ state,
+ "tag suffix cannot contain flow indicator characters"
+ );
+ }
+ }
+
+ if (tagName && !PATTERN_TAG_URI.test(tagName)) {
+ return throwError(
+ state,
+ `tag name cannot contain such characters: ${tagName}`
+ );
+ }
+
+ if (isVerbatim) {
+ state.tag = tagName;
+ } else if (
+ typeof state.tagMap !== "undefined" &&
+ _hasOwnProperty.call(state.tagMap, tagHandle)
+ ) {
+ state.tag = state.tagMap[tagHandle] + tagName;
+ } else if (tagHandle === "!") {
+ state.tag = `!${tagName}`;
+ } else if (tagHandle === "!!") {
+ state.tag = `tag:yaml.org,2002:${tagName}`;
+ } else {
+ return throwError(state, `undeclared tag handle "${tagHandle}"`);
+ }
+
+ return true;
+}
+
+function readAnchorProperty(state: LoaderState): boolean {
+ let ch = state.input.charCodeAt(state.position);
+ if (ch !== 0x26 /* & */) return false;
+
+ if (state.anchor !== null) {
+ return throwError(state, "duplication of an anchor property");
+ }
+ ch = state.input.charCodeAt(++state.position);
+
+ const position = state.position;
+ while (ch !== 0 && !isWsOrEol(ch) && !isFlowIndicator(ch)) {
+ ch = state.input.charCodeAt(++state.position);
+ }
+
+ if (state.position === position) {
+ return throwError(
+ state,
+ "name of an anchor node must contain at least one character"
+ );
+ }
+
+ state.anchor = state.input.slice(position, state.position);
+ return true;
+}
+
+function readAlias(state: LoaderState): boolean {
+ let ch = state.input.charCodeAt(state.position);
+
+ if (ch !== 0x2a /* * */) return false;
+
+ ch = state.input.charCodeAt(++state.position);
+ const _position = state.position;
+
+ while (ch !== 0 && !isWsOrEol(ch) && !isFlowIndicator(ch)) {
+ ch = state.input.charCodeAt(++state.position);
+ }
+
+ if (state.position === _position) {
+ return throwError(
+ state,
+ "name of an alias node must contain at least one character"
+ );
+ }
+
+ const alias = state.input.slice(_position, state.position);
+ if (
+ typeof state.anchorMap !== "undefined" &&
+ !state.anchorMap.hasOwnProperty(alias)
+ ) {
+ return throwError(state, `unidentified alias "${alias}"`);
+ }
+
+ if (typeof state.anchorMap !== "undefined") {
+ state.result = state.anchorMap[alias];
+ }
+ skipSeparationSpace(state, true, -1);
+ return true;
+}
+
+function composeNode(
+ state: LoaderState,
+ parentIndent: number,
+ nodeContext: number,
+ allowToSeek: boolean,
+ allowCompact: boolean
+): boolean {
+ let allowBlockScalars: boolean,
+ allowBlockCollections: boolean,
+ indentStatus = 1, // 1: this>parent, 0: this=parent, -1: this<parent
+ atNewLine = false,
+ hasContent = false,
+ type: Type,
+ flowIndent: number,
+ blockIndent: number;
+
+ if (state.listener && state.listener !== null) {
+ state.listener("open", state);
+ }
+
+ state.tag = null;
+ state.anchor = null;
+ state.kind = null;
+ state.result = null;
+
+ const allowBlockStyles = (allowBlockScalars = allowBlockCollections =
+ CONTEXT_BLOCK_OUT === nodeContext || CONTEXT_BLOCK_IN === nodeContext);
+
+ if (allowToSeek) {
+ if (skipSeparationSpace(state, true, -1)) {
+ atNewLine = true;
+
+ if (state.lineIndent > parentIndent) {
+ indentStatus = 1;
+ } else if (state.lineIndent === parentIndent) {
+ indentStatus = 0;
+ } else if (state.lineIndent < parentIndent) {
+ indentStatus = -1;
+ }
+ }
+ }
+
+ if (indentStatus === 1) {
+ while (readTagProperty(state) || readAnchorProperty(state)) {
+ if (skipSeparationSpace(state, true, -1)) {
+ atNewLine = true;
+ allowBlockCollections = allowBlockStyles;
+
+ if (state.lineIndent > parentIndent) {
+ indentStatus = 1;
+ } else if (state.lineIndent === parentIndent) {
+ indentStatus = 0;
+ } else if (state.lineIndent < parentIndent) {
+ indentStatus = -1;
+ }
+ } else {
+ allowBlockCollections = false;
+ }
+ }
+ }
+
+ if (allowBlockCollections) {
+ allowBlockCollections = atNewLine || allowCompact;
+ }
+
+ if (indentStatus === 1 || CONTEXT_BLOCK_OUT === nodeContext) {
+ const cond =
+ CONTEXT_FLOW_IN === nodeContext || CONTEXT_FLOW_OUT === nodeContext;
+ flowIndent = cond ? parentIndent : parentIndent + 1;
+
+ blockIndent = state.position - state.lineStart;
+
+ if (indentStatus === 1) {
+ if (
+ (allowBlockCollections &&
+ (readBlockSequence(state, blockIndent) ||
+ readBlockMapping(state, blockIndent, flowIndent))) ||
+ readFlowCollection(state, flowIndent)
+ ) {
+ hasContent = true;
+ } else {
+ if (
+ (allowBlockScalars && readBlockScalar(state, flowIndent)) ||
+ readSingleQuotedScalar(state, flowIndent) ||
+ readDoubleQuotedScalar(state, flowIndent)
+ ) {
+ hasContent = true;
+ } else if (readAlias(state)) {
+ hasContent = true;
+
+ if (state.tag !== null || state.anchor !== null) {
+ return throwError(
+ state,
+ "alias node should not have Any properties"
+ );
+ }
+ } else if (
+ readPlainScalar(state, flowIndent, CONTEXT_FLOW_IN === nodeContext)
+ ) {
+ hasContent = true;
+
+ if (state.tag === null) {
+ state.tag = "?";
+ }
+ }
+
+ if (state.anchor !== null && typeof state.anchorMap !== "undefined") {
+ state.anchorMap[state.anchor] = state.result;
+ }
+ }
+ } else if (indentStatus === 0) {
+ // Special case: block sequences are allowed to have same indentation level as the parent.
+ // http://www.yaml.org/spec/1.2/spec.html#id2799784
+ hasContent =
+ allowBlockCollections && readBlockSequence(state, blockIndent);
+ }
+ }
+
+ if (state.tag !== null && state.tag !== "!") {
+ if (state.tag === "?") {
+ for (
+ let typeIndex = 0, typeQuantity = state.implicitTypes.length;
+ typeIndex < typeQuantity;
+ typeIndex++
+ ) {
+ type = state.implicitTypes[typeIndex];
+
+ // Implicit resolving is not allowed for non-scalar types, and '?'
+ // non-specific tag is only assigned to plain scalars. So, it isn't
+ // needed to check for 'kind' conformity.
+
+ if (type.resolve(state.result)) {
+ // `state.result` updated in resolver if matched
+ state.result = type.construct(state.result);
+ state.tag = type.tag;
+ if (state.anchor !== null && typeof state.anchorMap !== "undefined") {
+ state.anchorMap[state.anchor] = state.result;
+ }
+ break;
+ }
+ }
+ } else if (
+ _hasOwnProperty.call(state.typeMap[state.kind || "fallback"], state.tag)
+ ) {
+ type = state.typeMap[state.kind || "fallback"][state.tag];
+
+ if (state.result !== null && type.kind !== state.kind) {
+ return throwError(
+ state,
+ `unacceptable node kind for !<${state.tag}> tag; it should be "${type.kind}", not "${state.kind}"`
+ );
+ }
+
+ if (!type.resolve(state.result)) {
+ // `state.result` updated in resolver if matched
+ return throwError(
+ state,
+ `cannot resolve a node with !<${state.tag}> explicit tag`
+ );
+ } else {
+ state.result = type.construct(state.result);
+ if (state.anchor !== null && typeof state.anchorMap !== "undefined") {
+ state.anchorMap[state.anchor] = state.result;
+ }
+ }
+ } else {
+ return throwError(state, `unknown tag !<${state.tag}>`);
+ }
+ }
+
+ if (state.listener && state.listener !== null) {
+ state.listener("close", state);
+ }
+ return state.tag !== null || state.anchor !== null || hasContent;
+}
+
+function readDocument(state: LoaderState): void {
+ const documentStart = state.position;
+ let position: number,
+ directiveName: string,
+ directiveArgs: unknown[],
+ hasDirectives = false,
+ ch: number;
+
+ state.version = null;
+ state.checkLineBreaks = state.legacy;
+ state.tagMap = {};
+ state.anchorMap = {};
+
+ while ((ch = state.input.charCodeAt(state.position)) !== 0) {
+ skipSeparationSpace(state, true, -1);
+
+ ch = state.input.charCodeAt(state.position);
+
+ if (state.lineIndent > 0 || ch !== 0x25 /* % */) {
+ break;
+ }
+
+ hasDirectives = true;
+ ch = state.input.charCodeAt(++state.position);
+ position = state.position;
+
+ while (ch !== 0 && !isWsOrEol(ch)) {
+ ch = state.input.charCodeAt(++state.position);
+ }
+
+ directiveName = state.input.slice(position, state.position);
+ directiveArgs = [];
+
+ if (directiveName.length < 1) {
+ return throwError(
+ state,
+ "directive name must not be less than one character in length"
+ );
+ }
+
+ while (ch !== 0) {
+ while (isWhiteSpace(ch)) {
+ ch = state.input.charCodeAt(++state.position);
+ }
+
+ if (ch === 0x23 /* # */) {
+ do {
+ ch = state.input.charCodeAt(++state.position);
+ } while (ch !== 0 && !isEOL(ch));
+ break;
+ }
+
+ if (isEOL(ch)) break;
+
+ position = state.position;
+
+ while (ch !== 0 && !isWsOrEol(ch)) {
+ ch = state.input.charCodeAt(++state.position);
+ }
+
+ directiveArgs.push(state.input.slice(position, state.position));
+ }
+
+ if (ch !== 0) readLineBreak(state);
+
+ if (_hasOwnProperty.call(directiveHandlers, directiveName)) {
+ directiveHandlers[directiveName](state, directiveName, ...directiveArgs);
+ } else {
+ throwWarning(state, `unknown document directive "${directiveName}"`);
+ }
+ }
+
+ skipSeparationSpace(state, true, -1);
+
+ if (
+ state.lineIndent === 0 &&
+ state.input.charCodeAt(state.position) === 0x2d /* - */ &&
+ state.input.charCodeAt(state.position + 1) === 0x2d /* - */ &&
+ state.input.charCodeAt(state.position + 2) === 0x2d /* - */
+ ) {
+ state.position += 3;
+ skipSeparationSpace(state, true, -1);
+ } else if (hasDirectives) {
+ return throwError(state, "directives end mark is expected");
+ }
+
+ composeNode(state, state.lineIndent - 1, CONTEXT_BLOCK_OUT, false, true);
+ skipSeparationSpace(state, true, -1);
+
+ if (
+ state.checkLineBreaks &&
+ PATTERN_NON_ASCII_LINE_BREAKS.test(
+ state.input.slice(documentStart, state.position)
+ )
+ ) {
+ throwWarning(state, "non-ASCII line breaks are interpreted as content");
+ }
+
+ state.documents.push(state.result);
+
+ if (state.position === state.lineStart && testDocumentSeparator(state)) {
+ if (state.input.charCodeAt(state.position) === 0x2e /* . */) {
+ state.position += 3;
+ skipSeparationSpace(state, true, -1);
+ }
+ return;
+ }
+
+ if (state.position < state.length - 1) {
+ return throwError(
+ state,
+ "end of the stream or a document separator is expected"
+ );
+ } else {
+ return;
+ }
+}
+
+function loadDocuments(input: string, options?: LoaderStateOptions): unknown[] {
+ input = String(input);
+ options = options || {};
+
+ if (input.length !== 0) {
+ // Add tailing `\n` if not exists
+ if (
+ input.charCodeAt(input.length - 1) !== 0x0a /* LF */ &&
+ input.charCodeAt(input.length - 1) !== 0x0d /* CR */
+ ) {
+ input += "\n";
+ }
+
+ // Strip BOM
+ if (input.charCodeAt(0) === 0xfeff) {
+ input = input.slice(1);
+ }
+ }
+
+ const state = new LoaderState(input, options);
+
+ // Use 0 as string terminator. That significantly simplifies bounds check.
+ state.input += "\0";
+
+ while (state.input.charCodeAt(state.position) === 0x20 /* Space */) {
+ state.lineIndent += 1;
+ state.position += 1;
+ }
+
+ while (state.position < state.length - 1) {
+ readDocument(state);
+ }
+
+ return state.documents;
+}
+
+export type CbFunction = (doc: unknown) => void;
+function isCbFunction(fn: unknown): fn is CbFunction {
+ return typeof fn === "function";
+}
+
+export function loadAll<T extends CbFunction | LoaderStateOptions>(
+ input: string,
+ iteratorOrOption?: T,
+ options?: LoaderStateOptions
+): T extends CbFunction ? void : unknown[] {
+ if (!isCbFunction(iteratorOrOption)) {
+ return loadDocuments(input, iteratorOrOption as LoaderStateOptions) as Any;
+ }
+
+ const documents = loadDocuments(input, options);
+ const iterator = iteratorOrOption;
+ for (let index = 0, length = documents.length; index < length; index++) {
+ iterator(documents[index]);
+ }
+
+ return void 0 as Any;
+}
+
+export function load(input: string, options?: LoaderStateOptions): unknown {
+ const documents = loadDocuments(input, options);
+
+ if (documents.length === 0) {
+ return;
+ }
+ if (documents.length === 1) {
+ return documents[0];
+ }
+ throw new YAMLError(
+ "expected a single document in the stream, but found more"
+ );
+}
diff --git a/std/encoding/yaml/loader/loader_state.ts b/std/encoding/yaml/loader/loader_state.ts
new file mode 100644
index 000000000..a316f5031
--- /dev/null
+++ b/std/encoding/yaml/loader/loader_state.ts
@@ -0,0 +1,74 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { YAMLError } from "../error.ts";
+import { Schema, SchemaDefinition, TypeMap } from "../schema.ts";
+import { State } from "../state.ts";
+import { Type } from "../type.ts";
+import { Any, ArrayObject } from "../utils.ts";
+
+export interface LoaderStateOptions {
+ legacy?: boolean;
+ listener?: ((...args: Any[]) => void) | null;
+ /** string to be used as a file path in error/warning messages. */
+ filename?: string;
+ /** specifies a schema to use. */
+ schema?: SchemaDefinition;
+ /** compatibility with JSON.parse behaviour. */
+ json?: boolean;
+ /** function to call on warning messages. */
+ onWarning?(this: null, e?: YAMLError): void;
+}
+
+export type ResultType = [] | {} | string;
+
+export class LoaderState extends State {
+ public documents: Any[] = [];
+ public length: number;
+ public lineIndent = 0;
+ public lineStart = 0;
+ public position = 0;
+ public line = 0;
+ public filename?: string;
+ public onWarning?: (...args: Any[]) => void;
+ public legacy: boolean;
+ public json: boolean;
+ public listener?: ((...args: Any[]) => void) | null;
+ public implicitTypes: Type[];
+ public typeMap: TypeMap;
+
+ public version?: string | null;
+ public checkLineBreaks?: boolean;
+ public tagMap?: ArrayObject;
+ public anchorMap?: ArrayObject;
+ public tag?: string | null;
+ public anchor?: string | null;
+ public kind?: string | null;
+ public result: ResultType | null = "";
+
+ constructor(
+ public input: string,
+ {
+ filename,
+ schema,
+ onWarning,
+ legacy = false,
+ json = false,
+ listener = null
+ }: LoaderStateOptions
+ ) {
+ super(schema);
+ this.filename = filename;
+ this.onWarning = onWarning;
+ this.legacy = legacy;
+ this.json = json;
+ this.listener = listener;
+
+ this.implicitTypes = (this.schema as Schema).compiledImplicit;
+ this.typeMap = (this.schema as Schema).compiledTypeMap;
+
+ this.length = input.length;
+ }
+}
diff --git a/std/encoding/yaml/mark.ts b/std/encoding/yaml/mark.ts
new file mode 100644
index 000000000..d91e3950d
--- /dev/null
+++ b/std/encoding/yaml/mark.ts
@@ -0,0 +1,77 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { repeat } from "./utils.ts";
+
+export class Mark {
+ constructor(
+ public name: string,
+ public buffer: string,
+ public position: number,
+ public line: number,
+ public column: number
+ ) {}
+
+ public getSnippet(indent = 4, maxLength = 75): string | null {
+ if (!this.buffer) return null;
+
+ let head = "";
+ let start = this.position;
+
+ while (
+ start > 0 &&
+ "\x00\r\n\x85\u2028\u2029".indexOf(this.buffer.charAt(start - 1)) === -1
+ ) {
+ start -= 1;
+ if (this.position - start > maxLength / 2 - 1) {
+ head = " ... ";
+ start += 5;
+ break;
+ }
+ }
+
+ let tail = "";
+ let end = this.position;
+
+ while (
+ end < this.buffer.length &&
+ "\x00\r\n\x85\u2028\u2029".indexOf(this.buffer.charAt(end)) === -1
+ ) {
+ end += 1;
+ if (end - this.position > maxLength / 2 - 1) {
+ tail = " ... ";
+ end -= 5;
+ break;
+ }
+ }
+
+ const snippet = this.buffer.slice(start, end);
+ return `${repeat(" ", indent)}${head}${snippet}${tail}\n${repeat(
+ " ",
+ indent + this.position - start + head.length
+ )}^`;
+ }
+
+ public toString(compact?: boolean): string {
+ let snippet,
+ where = "";
+
+ if (this.name) {
+ where += `in "${this.name}" `;
+ }
+
+ where += `at line ${this.line + 1}, column ${this.column + 1}`;
+
+ if (!compact) {
+ snippet = this.getSnippet();
+
+ if (snippet) {
+ where += `:\n${snippet}`;
+ }
+ }
+
+ return where;
+ }
+}
diff --git a/std/encoding/yaml/parse.ts b/std/encoding/yaml/parse.ts
new file mode 100644
index 000000000..aacffe7f9
--- /dev/null
+++ b/std/encoding/yaml/parse.ts
@@ -0,0 +1,32 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { CbFunction, load, loadAll } from "./loader/loader.ts";
+import { LoaderStateOptions } from "./loader/loader_state.ts";
+
+export type ParseOptions = LoaderStateOptions;
+
+/**
+ * Parses `content` as single YAML document.
+ *
+ * Returns a JavaScript object or throws `YAMLException` on error.
+ * By default, does not support regexps, functions and undefined. This method is safe for untrusted data.
+ *
+ */
+export function parse(content: string, options?: ParseOptions): unknown {
+ return load(content, options);
+}
+
+/**
+ * Same as `parse()`, but understands multi-document sources.
+ * Applies iterator to each document if specified, or returns array of documents.
+ */
+export function parseAll(
+ content: string,
+ iterator?: CbFunction,
+ options?: ParseOptions
+): unknown {
+ return loadAll(content, iterator, options);
+}
diff --git a/std/encoding/yaml/parse_test.ts b/std/encoding/yaml/parse_test.ts
new file mode 100644
index 000000000..029665e44
--- /dev/null
+++ b/std/encoding/yaml/parse_test.ts
@@ -0,0 +1,25 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { parse } from "./parse.ts";
+import { test } from "../../testing/mod.ts";
+import { assertEquals } from "../../testing/asserts.ts";
+
+test({
+ name: "parsed correctly",
+ fn(): void {
+ const FIXTURE = `
+ test: toto
+ foo:
+ bar: True
+ baz: 1
+ qux: ~
+ `;
+
+ const ASSERTS = { test: "toto", foo: { bar: true, baz: 1, qux: null } };
+
+ assertEquals(parse(FIXTURE), ASSERTS);
+ }
+});
diff --git a/std/encoding/yaml/schema.ts b/std/encoding/yaml/schema.ts
new file mode 100644
index 000000000..715e17359
--- /dev/null
+++ b/std/encoding/yaml/schema.ts
@@ -0,0 +1,101 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { YAMLError } from "./error.ts";
+import { KindType, Type } from "./type.ts";
+import { ArrayObject, Any } from "./utils.ts";
+
+function compileList(
+ schema: Schema,
+ name: "implicit" | "explicit",
+ result: Type[]
+): Type[] {
+ const exclude: number[] = [];
+
+ for (const includedSchema of schema.include) {
+ result = compileList(includedSchema, name, result);
+ }
+
+ for (const currentType of schema[name]) {
+ for (
+ let previousIndex = 0;
+ previousIndex < result.length;
+ previousIndex++
+ ) {
+ const previousType = result[previousIndex];
+ if (
+ previousType.tag === currentType.tag &&
+ previousType.kind === currentType.kind
+ ) {
+ exclude.push(previousIndex);
+ }
+ }
+
+ result.push(currentType);
+ }
+
+ return result.filter((type, index): unknown => !exclude.includes(index));
+}
+
+export type TypeMap = { [k in KindType | "fallback"]: ArrayObject<Type> };
+function compileMap(...typesList: Type[][]): TypeMap {
+ const result: TypeMap = {
+ fallback: {},
+ mapping: {},
+ scalar: {},
+ sequence: {}
+ };
+
+ for (const types of typesList) {
+ for (const type of types) {
+ if (type.kind !== null) {
+ result[type.kind][type.tag] = result["fallback"][type.tag] = type;
+ }
+ }
+ }
+ return result;
+}
+
+export class Schema implements SchemaDefinition {
+ public static SCHEMA_DEFAULT?: Schema;
+
+ public implicit: Type[];
+ public explicit: Type[];
+ public include: Schema[];
+
+ public compiledImplicit: Type[];
+ public compiledExplicit: Type[];
+ public compiledTypeMap: TypeMap;
+
+ constructor(definition: SchemaDefinition) {
+ this.explicit = definition.explicit || [];
+ this.implicit = definition.implicit || [];
+ this.include = definition.include || [];
+
+ for (const type of this.implicit) {
+ if (type.loadKind && type.loadKind !== "scalar") {
+ throw new YAMLError(
+ // eslint-disable-next-line max-len
+ "There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported."
+ );
+ }
+ }
+
+ this.compiledImplicit = compileList(this, "implicit", []);
+ this.compiledExplicit = compileList(this, "explicit", []);
+ this.compiledTypeMap = compileMap(
+ this.compiledImplicit,
+ this.compiledExplicit
+ );
+ }
+
+ public static create(): void {}
+}
+
+export interface SchemaDefinition {
+ implicit?: Any[];
+ explicit?: Type[];
+ include?: Schema[];
+}
diff --git a/std/encoding/yaml/schema/core.ts b/std/encoding/yaml/schema/core.ts
new file mode 100644
index 000000000..b37f4a335
--- /dev/null
+++ b/std/encoding/yaml/schema/core.ts
@@ -0,0 +1,13 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { Schema } from "../schema.ts";
+import { json } from "./json.ts";
+
+// Standard YAML's Core schema.
+// http://www.yaml.org/spec/1.2/spec.html#id2804923
+export const core = new Schema({
+ include: [json]
+});
diff --git a/std/encoding/yaml/schema/default.ts b/std/encoding/yaml/schema/default.ts
new file mode 100644
index 000000000..6a326cbb2
--- /dev/null
+++ b/std/encoding/yaml/schema/default.ts
@@ -0,0 +1,16 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { Schema } from "../schema.ts";
+import { binary, merge, omap, pairs, set, timestamp } from "../type/mod.ts";
+import { core } from "./core.ts";
+
+// JS-YAML's default schema for `safeLoad` function.
+// It is not described in the YAML specification.
+export const def = new Schema({
+ explicit: [binary, omap, pairs, set],
+ implicit: [timestamp, merge],
+ include: [core]
+});
diff --git a/std/encoding/yaml/schema/failsafe.ts b/std/encoding/yaml/schema/failsafe.ts
new file mode 100644
index 000000000..f8892c27d
--- /dev/null
+++ b/std/encoding/yaml/schema/failsafe.ts
@@ -0,0 +1,13 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { Schema } from "../schema.ts";
+import { map, seq, str } from "../type/mod.ts";
+
+// Standard YAML's Failsafe schema.
+// http://www.yaml.org/spec/1.2/spec.html#id2802346
+export const failsafe = new Schema({
+ explicit: [str, seq, map]
+});
diff --git a/std/encoding/yaml/schema/json.ts b/std/encoding/yaml/schema/json.ts
new file mode 100644
index 000000000..331313237
--- /dev/null
+++ b/std/encoding/yaml/schema/json.ts
@@ -0,0 +1,15 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { Schema } from "../schema.ts";
+import { bool, float, int, nil } from "../type/mod.ts";
+import { failsafe } from "./failsafe.ts";
+
+// Standard YAML's JSON schema.
+// http://www.yaml.org/spec/1.2/spec.html#id2803231
+export const json = new Schema({
+ implicit: [nil, bool, int, float],
+ include: [failsafe]
+});
diff --git a/std/encoding/yaml/schema/mod.ts b/std/encoding/yaml/schema/mod.ts
new file mode 100644
index 000000000..4eaf30e37
--- /dev/null
+++ b/std/encoding/yaml/schema/mod.ts
@@ -0,0 +1,9 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+export { core as CORE_SCHEMA } from "./core.ts";
+export { def as DEFAULT_SCHEMA } from "./default.ts";
+export { failsafe as FAILSAFE_SCHEMA } from "./failsafe.ts";
+export { json as JSON_SCHEMA } from "./json.ts";
diff --git a/std/encoding/yaml/state.ts b/std/encoding/yaml/state.ts
new file mode 100644
index 000000000..6c85c7dd3
--- /dev/null
+++ b/std/encoding/yaml/state.ts
@@ -0,0 +1,11 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { SchemaDefinition } from "./schema.ts";
+import { DEFAULT_SCHEMA } from "./schema/mod.ts";
+
+export abstract class State {
+ constructor(public schema: SchemaDefinition = DEFAULT_SCHEMA) {}
+}
diff --git a/std/encoding/yaml/stringify.ts b/std/encoding/yaml/stringify.ts
new file mode 100644
index 000000000..0e94a18e5
--- /dev/null
+++ b/std/encoding/yaml/stringify.ts
@@ -0,0 +1,18 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { dump } from "./dumper/dumper.ts";
+import { DumperStateOptions } from "./dumper/dumper_state.ts";
+
+export type DumpOptions = DumperStateOptions;
+
+/**
+ * Serializes `object` as a YAML document.
+ *
+ * You can disable exceptions by setting the skipInvalid option to true.
+ */
+export function stringify(obj: object, options?: DumpOptions): string {
+ return dump(obj, options);
+}
diff --git a/std/encoding/yaml/stringify_test.ts b/std/encoding/yaml/stringify_test.ts
new file mode 100644
index 000000000..60b41fd75
--- /dev/null
+++ b/std/encoding/yaml/stringify_test.ts
@@ -0,0 +1,42 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { test } from "../../testing/mod.ts";
+import { assertEquals } from "../../testing/asserts.ts";
+import { stringify } from "./stringify.ts";
+
+test({
+ name: "stringified correctly",
+ fn(): void {
+ const FIXTURE = {
+ foo: {
+ bar: true,
+ test: [
+ "a",
+ "b",
+ {
+ a: false
+ },
+ {
+ a: false
+ }
+ ]
+ },
+ test: "foobar"
+ };
+
+ const ASSERTS = `foo:
+ bar: true
+ test:
+ - a
+ - b
+ - a: false
+ - a: false
+test: foobar
+`;
+
+ assertEquals(stringify(FIXTURE), ASSERTS);
+ }
+});
diff --git a/std/encoding/yaml/type.ts b/std/encoding/yaml/type.ts
new file mode 100644
index 000000000..7f8918d4f
--- /dev/null
+++ b/std/encoding/yaml/type.ts
@@ -0,0 +1,55 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { ArrayObject, Any } from "./utils.ts";
+
+export type KindType = "sequence" | "scalar" | "mapping";
+export type StyleVariant = "lowercase" | "uppercase" | "camelcase" | "decimal";
+export type RepresentFn = (data: Any, style?: StyleVariant) => Any;
+
+const DEFAULT_RESOLVE = (): boolean => true;
+const DEFAULT_CONSTRUCT = (data: Any): Any => data;
+
+interface TypeOptions {
+ kind: KindType;
+ resolve?: (data: Any) => boolean;
+ construct?: (data: string) => Any;
+ instanceOf?: Any;
+ predicate?: (data: object) => boolean;
+ represent?: RepresentFn | ArrayObject<RepresentFn>;
+ defaultStyle?: StyleVariant;
+ styleAliases?: ArrayObject;
+}
+
+function checkTagFormat(tag: string): string {
+ return tag;
+}
+
+export class Type {
+ public tag: string;
+ public kind: KindType | null = null;
+ public instanceOf: Any;
+ public predicate?: (data: object) => boolean;
+ public represent?: RepresentFn | ArrayObject<RepresentFn>;
+ public defaultStyle?: StyleVariant;
+ public styleAliases?: ArrayObject;
+ public loadKind?: KindType;
+
+ constructor(tag: string, options?: TypeOptions) {
+ this.tag = checkTagFormat(tag);
+ if (options) {
+ this.kind = options.kind;
+ this.resolve = options.resolve || DEFAULT_RESOLVE;
+ this.construct = options.construct || DEFAULT_CONSTRUCT;
+ this.instanceOf = options.instanceOf;
+ this.predicate = options.predicate;
+ this.represent = options.represent;
+ this.defaultStyle = options.defaultStyle;
+ this.styleAliases = options.styleAliases;
+ }
+ }
+ public resolve: (data?: Any) => boolean = (): boolean => true;
+ public construct: (data?: Any) => Any = (data): Any => data;
+}
diff --git a/std/encoding/yaml/type/binary.ts b/std/encoding/yaml/type/binary.ts
new file mode 100644
index 000000000..7c4fc1f06
--- /dev/null
+++ b/std/encoding/yaml/type/binary.ts
@@ -0,0 +1,139 @@
+// Ported from js-yaml v3.13.1:
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { Type } from "../type.ts";
+import { Any } from "../utils.ts";
+
+const { Buffer } = Deno;
+
+// [ 64, 65, 66 ] -> [ padding, CR, LF ]
+const BASE64_MAP =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n\r";
+
+function resolveYamlBinary(data: Any): boolean {
+ if (data === null) return false;
+
+ let code: number;
+ let bitlen = 0;
+ const max = data.length;
+ const map = BASE64_MAP;
+
+ // Convert one by one.
+ for (let idx = 0; idx < max; idx++) {
+ code = map.indexOf(data.charAt(idx));
+
+ // Skip CR/LF
+ if (code > 64) continue;
+
+ // Fail on illegal characters
+ if (code < 0) return false;
+
+ bitlen += 6;
+ }
+
+ // If there are any bits left, source was corrupted
+ return bitlen % 8 === 0;
+}
+
+function constructYamlBinary(data: string): Deno.Buffer {
+ // remove CR/LF & padding to simplify scan
+ const input = data.replace(/[\r\n=]/g, "");
+ const max = input.length;
+ const map = BASE64_MAP;
+
+ // Collect by 6*4 bits (3 bytes)
+
+ const result = [];
+ let bits = 0;
+ for (let idx = 0; idx < max; idx++) {
+ if (idx % 4 === 0 && idx) {
+ result.push((bits >> 16) & 0xff);
+ result.push((bits >> 8) & 0xff);
+ result.push(bits & 0xff);
+ }
+
+ bits = (bits << 6) | map.indexOf(input.charAt(idx));
+ }
+
+ // Dump tail
+
+ const tailbits = (max % 4) * 6;
+
+ if (tailbits === 0) {
+ result.push((bits >> 16) & 0xff);
+ result.push((bits >> 8) & 0xff);
+ result.push(bits & 0xff);
+ } else if (tailbits === 18) {
+ result.push((bits >> 10) & 0xff);
+ result.push((bits >> 2) & 0xff);
+ } else if (tailbits === 12) {
+ result.push((bits >> 4) & 0xff);
+ }
+
+ return new Buffer(new Uint8Array(result));
+}
+
+function representYamlBinary(object: Uint8Array): string {
+ const max = object.length;
+ const map = BASE64_MAP;
+
+ // Convert every three bytes to 4 ASCII characters.
+
+ let result = "";
+ let bits = 0;
+ for (let idx = 0; idx < max; idx++) {
+ if (idx % 3 === 0 && idx) {
+ result += map[(bits >> 18) & 0x3f];
+ result += map[(bits >> 12) & 0x3f];
+ result += map[(bits >> 6) & 0x3f];
+ result += map[bits & 0x3f];
+ }
+
+ bits = (bits << 8) + object[idx];
+ }
+
+ // Dump tail
+
+ const tail = max % 3;
+
+ if (tail === 0) {
+ result += map[(bits >> 18) & 0x3f];
+ result += map[(bits >> 12) & 0x3f];
+ result += map[(bits >> 6) & 0x3f];
+ result += map[bits & 0x3f];
+ } else if (tail === 2) {
+ result += map[(bits >> 10) & 0x3f];
+ result += map[(bits >> 4) & 0x3f];
+ result += map[(bits << 2) & 0x3f];
+ result += map[64];
+ } else if (tail === 1) {
+ result += map[(bits >> 2) & 0x3f];
+ result += map[(bits << 4) & 0x3f];
+ result += map[64];
+ result += map[64];
+ }
+
+ return result;
+}
+
+function isBinary(obj: Any): obj is Deno.Buffer {
+ const buf = new Buffer();
+ try {
+ if (0 > buf.readFromSync(obj as Deno.Buffer)) return true;
+ return false;
+ } catch {
+ return false;
+ } finally {
+ buf.reset();
+ }
+}
+
+export const binary = new Type("tag:yaml.org,2002:binary", {
+ construct: constructYamlBinary,
+ kind: "scalar",
+ predicate: isBinary,
+ represent: representYamlBinary,
+ resolve: resolveYamlBinary
+});
diff --git a/std/encoding/yaml/type/bool.ts b/std/encoding/yaml/type/bool.ts
new file mode 100644
index 000000000..28e9b2e9e
--- /dev/null
+++ b/std/encoding/yaml/type/bool.ts
@@ -0,0 +1,39 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { Type } from "../type.ts";
+import { isBoolean } from "../utils.ts";
+
+function resolveYamlBoolean(data: string): boolean {
+ const max = data.length;
+
+ return (
+ (max === 4 && (data === "true" || data === "True" || data === "TRUE")) ||
+ (max === 5 && (data === "false" || data === "False" || data === "FALSE"))
+ );
+}
+
+function constructYamlBoolean(data: string): boolean {
+ return data === "true" || data === "True" || data === "TRUE";
+}
+
+export const bool = new Type("tag:yaml.org,2002:bool", {
+ construct: constructYamlBoolean,
+ defaultStyle: "lowercase",
+ kind: "scalar",
+ predicate: isBoolean,
+ represent: {
+ lowercase(object: boolean): string {
+ return object ? "true" : "false";
+ },
+ uppercase(object: boolean): string {
+ return object ? "TRUE" : "FALSE";
+ },
+ camelcase(object: boolean): string {
+ return object ? "True" : "False";
+ }
+ },
+ resolve: resolveYamlBoolean
+});
diff --git a/std/encoding/yaml/type/float.ts b/std/encoding/yaml/type/float.ts
new file mode 100644
index 000000000..359a7cc37
--- /dev/null
+++ b/std/encoding/yaml/type/float.ts
@@ -0,0 +1,125 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { StyleVariant, Type } from "../type.ts";
+import { isNegativeZero, Any } from "../utils.ts";
+
+const YAML_FLOAT_PATTERN = new RegExp(
+ // 2.5e4, 2.5 and integers
+ "^(?:[-+]?(?:0|[1-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?" +
+ // .2e4, .2
+ // special case, seems not from spec
+ "|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?" +
+ // 20:59
+ "|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*" +
+ // .inf
+ "|[-+]?\\.(?:inf|Inf|INF)" +
+ // .nan
+ "|\\.(?:nan|NaN|NAN))$"
+);
+
+function resolveYamlFloat(data: string): boolean {
+ if (
+ !YAML_FLOAT_PATTERN.test(data) ||
+ // Quick hack to not allow integers end with `_`
+ // Probably should update regexp & check speed
+ data[data.length - 1] === "_"
+ ) {
+ return false;
+ }
+
+ return true;
+}
+
+function constructYamlFloat(data: string): number {
+ let value = data.replace(/_/g, "").toLowerCase();
+ const sign = value[0] === "-" ? -1 : 1;
+ const digits: number[] = [];
+
+ if ("+-".indexOf(value[0]) >= 0) {
+ value = value.slice(1);
+ }
+
+ if (value === ".inf") {
+ return sign === 1 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY;
+ }
+ if (value === ".nan") {
+ return NaN;
+ }
+ if (value.indexOf(":") >= 0) {
+ value.split(":").forEach((v): void => {
+ digits.unshift(parseFloat(v));
+ });
+
+ let valueNb = 0.0;
+ let base = 1;
+
+ digits.forEach((d): void => {
+ valueNb += d * base;
+ base *= 60;
+ });
+
+ return sign * valueNb;
+ }
+ return sign * parseFloat(value);
+}
+
+const SCIENTIFIC_WITHOUT_DOT = /^[-+]?[0-9]+e/;
+
+function representYamlFloat(object: Any, style?: StyleVariant): Any {
+ if (isNaN(object)) {
+ switch (style) {
+ case "lowercase":
+ return ".nan";
+ case "uppercase":
+ return ".NAN";
+ case "camelcase":
+ return ".NaN";
+ }
+ } else if (Number.POSITIVE_INFINITY === object) {
+ switch (style) {
+ case "lowercase":
+ return ".inf";
+ case "uppercase":
+ return ".INF";
+ case "camelcase":
+ return ".Inf";
+ }
+ } else if (Number.NEGATIVE_INFINITY === object) {
+ switch (style) {
+ case "lowercase":
+ return "-.inf";
+ case "uppercase":
+ return "-.INF";
+ case "camelcase":
+ return "-.Inf";
+ }
+ } else if (isNegativeZero(object)) {
+ return "-0.0";
+ }
+
+ const res = object.toString(10);
+
+ // JS stringifier can build scientific format without dots: 5e-100,
+ // while YAML requres dot: 5.e-100. Fix it with simple hack
+
+ return SCIENTIFIC_WITHOUT_DOT.test(res) ? res.replace("e", ".e") : res;
+}
+
+function isFloat(object: Any): boolean {
+ return (
+ Object.prototype.toString.call(object) === "[object Number]" &&
+ (object % 1 !== 0 || isNegativeZero(object))
+ );
+}
+
+export const float = new Type("tag:yaml.org,2002:float", {
+ construct: constructYamlFloat,
+ defaultStyle: "lowercase",
+ kind: "scalar",
+ predicate: isFloat,
+ represent: representYamlFloat,
+ resolve: resolveYamlFloat
+});
diff --git a/std/encoding/yaml/type/int.ts b/std/encoding/yaml/type/int.ts
new file mode 100644
index 000000000..d2ffb1701
--- /dev/null
+++ b/std/encoding/yaml/type/int.ts
@@ -0,0 +1,191 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { Type } from "../type.ts";
+import { isNegativeZero, Any } from "../utils.ts";
+
+function isHexCode(c: number): boolean {
+ return (
+ (0x30 /* 0 */ <= c && c <= 0x39) /* 9 */ ||
+ (0x41 /* A */ <= c && c <= 0x46) /* F */ ||
+ (0x61 /* a */ <= c && c <= 0x66) /* f */
+ );
+}
+
+function isOctCode(c: number): boolean {
+ return 0x30 /* 0 */ <= c && c <= 0x37 /* 7 */;
+}
+
+function isDecCode(c: number): boolean {
+ return 0x30 /* 0 */ <= c && c <= 0x39 /* 9 */;
+}
+
+function resolveYamlInteger(data: string): boolean {
+ const max = data.length;
+ let index = 0;
+ let hasDigits = false;
+
+ if (!max) return false;
+
+ let ch = data[index];
+
+ // sign
+ if (ch === "-" || ch === "+") {
+ ch = data[++index];
+ }
+
+ if (ch === "0") {
+ // 0
+ if (index + 1 === max) return true;
+ ch = data[++index];
+
+ // base 2, base 8, base 16
+
+ if (ch === "b") {
+ // base 2
+ index++;
+
+ for (; index < max; index++) {
+ ch = data[index];
+ if (ch === "_") continue;
+ if (ch !== "0" && ch !== "1") return false;
+ hasDigits = true;
+ }
+ return hasDigits && ch !== "_";
+ }
+
+ if (ch === "x") {
+ // base 16
+ index++;
+
+ for (; index < max; index++) {
+ ch = data[index];
+ if (ch === "_") continue;
+ if (!isHexCode(data.charCodeAt(index))) return false;
+ hasDigits = true;
+ }
+ return hasDigits && ch !== "_";
+ }
+
+ // base 8
+ for (; index < max; index++) {
+ ch = data[index];
+ if (ch === "_") continue;
+ if (!isOctCode(data.charCodeAt(index))) return false;
+ hasDigits = true;
+ }
+ return hasDigits && ch !== "_";
+ }
+
+ // base 10 (except 0) or base 60
+
+ // value should not start with `_`;
+ if (ch === "_") return false;
+
+ for (; index < max; index++) {
+ ch = data[index];
+ if (ch === "_") continue;
+ if (ch === ":") break;
+ if (!isDecCode(data.charCodeAt(index))) {
+ return false;
+ }
+ hasDigits = true;
+ }
+
+ // Should have digits and should not end with `_`
+ if (!hasDigits || ch === "_") return false;
+
+ // if !base60 - done;
+ if (ch !== ":") return true;
+
+ // base60 almost not used, no needs to optimize
+ return /^(:[0-5]?[0-9])+$/.test(data.slice(index));
+}
+
+function constructYamlInteger(data: string): number {
+ let value = data;
+ const digits: number[] = [];
+
+ if (value.indexOf("_") !== -1) {
+ value = value.replace(/_/g, "");
+ }
+
+ let sign = 1;
+ let ch = value[0];
+ if (ch === "-" || ch === "+") {
+ if (ch === "-") sign = -1;
+ value = value.slice(1);
+ ch = value[0];
+ }
+
+ if (value === "0") return 0;
+
+ if (ch === "0") {
+ if (value[1] === "b") return sign * parseInt(value.slice(2), 2);
+ if (value[1] === "x") return sign * parseInt(value, 16);
+ return sign * parseInt(value, 8);
+ }
+
+ if (value.indexOf(":") !== -1) {
+ value.split(":").forEach((v): void => {
+ digits.unshift(parseInt(v, 10));
+ });
+
+ let valueInt = 0;
+ let base = 1;
+
+ digits.forEach((d): void => {
+ valueInt += d * base;
+ base *= 60;
+ });
+
+ return sign * valueInt;
+ }
+
+ return sign * parseInt(value, 10);
+}
+
+function isInteger(object: Any): boolean {
+ return (
+ Object.prototype.toString.call(object) === "[object Number]" &&
+ object % 1 === 0 &&
+ !isNegativeZero(object)
+ );
+}
+
+export const int = new Type("tag:yaml.org,2002:int", {
+ construct: constructYamlInteger,
+ defaultStyle: "decimal",
+ kind: "scalar",
+ predicate: isInteger,
+ represent: {
+ binary(obj: number): string {
+ return obj >= 0
+ ? `0b${obj.toString(2)}`
+ : `-0b${obj.toString(2).slice(1)}`;
+ },
+ octal(obj: number): string {
+ return obj >= 0 ? `0${obj.toString(8)}` : `-0${obj.toString(8).slice(1)}`;
+ },
+ decimal(obj: number): string {
+ return obj.toString(10);
+ },
+ hexadecimal(obj: number): string {
+ return obj >= 0
+ ? `0x${obj.toString(16).toUpperCase()}`
+ : `-0x${obj
+ .toString(16)
+ .toUpperCase()
+ .slice(1)}`;
+ }
+ },
+ resolve: resolveYamlInteger,
+ styleAliases: {
+ binary: [2, "bin"],
+ decimal: [10, "dec"],
+ hexadecimal: [16, "hex"],
+ octal: [8, "oct"]
+ }
+});
diff --git a/std/encoding/yaml/type/map.ts b/std/encoding/yaml/type/map.ts
new file mode 100644
index 000000000..6d273254d
--- /dev/null
+++ b/std/encoding/yaml/type/map.ts
@@ -0,0 +1,14 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { Type } from "../type.ts";
+import { Any } from "../utils.ts";
+
+export const map = new Type("tag:yaml.org,2002:map", {
+ construct(data): Any {
+ return data !== null ? data : {};
+ },
+ kind: "mapping"
+});
diff --git a/std/encoding/yaml/type/merge.ts b/std/encoding/yaml/type/merge.ts
new file mode 100644
index 000000000..9389a0d20
--- /dev/null
+++ b/std/encoding/yaml/type/merge.ts
@@ -0,0 +1,15 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { Type } from "../type.ts";
+
+function resolveYamlMerge(data: string): boolean {
+ return data === "<<" || data === null;
+}
+
+export const merge = new Type("tag:yaml.org,2002:merge", {
+ kind: "scalar",
+ resolve: resolveYamlMerge
+});
diff --git a/std/encoding/yaml/type/mod.ts b/std/encoding/yaml/type/mod.ts
new file mode 100644
index 000000000..1970753df
--- /dev/null
+++ b/std/encoding/yaml/type/mod.ts
@@ -0,0 +1,18 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+export { binary } from "./binary.ts";
+export { bool } from "./bool.ts";
+export { float } from "./float.ts";
+export { int } from "./int.ts";
+export { map } from "./map.ts";
+export { merge } from "./merge.ts";
+export { nil } from "./nil.ts";
+export { omap } from "./omap.ts";
+export { pairs } from "./pairs.ts";
+export { seq } from "./seq.ts";
+export { set } from "./set.ts";
+export { str } from "./str.ts";
+export { timestamp } from "./timestamp.ts";
diff --git a/std/encoding/yaml/type/nil.ts b/std/encoding/yaml/type/nil.ts
new file mode 100644
index 000000000..c7e87f96d
--- /dev/null
+++ b/std/encoding/yaml/type/nil.ts
@@ -0,0 +1,45 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { Type } from "../type.ts";
+
+function resolveYamlNull(data: string): boolean {
+ const max = data.length;
+
+ return (
+ (max === 1 && data === "~") ||
+ (max === 4 && (data === "null" || data === "Null" || data === "NULL"))
+ );
+}
+
+function constructYamlNull(): null {
+ return null;
+}
+
+function isNull(object: unknown): object is null {
+ return object === null;
+}
+
+export const nil = new Type("tag:yaml.org,2002:null", {
+ construct: constructYamlNull,
+ defaultStyle: "lowercase",
+ kind: "scalar",
+ predicate: isNull,
+ represent: {
+ canonical(): string {
+ return "~";
+ },
+ lowercase(): string {
+ return "null";
+ },
+ uppercase(): string {
+ return "NULL";
+ },
+ camelcase(): string {
+ return "Null";
+ }
+ },
+ resolve: resolveYamlNull
+});
diff --git a/std/encoding/yaml/type/omap.ts b/std/encoding/yaml/type/omap.ts
new file mode 100644
index 000000000..1b7a79a50
--- /dev/null
+++ b/std/encoding/yaml/type/omap.ts
@@ -0,0 +1,46 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { Type } from "../type.ts";
+import { Any } from "../utils.ts";
+
+const _hasOwnProperty = Object.prototype.hasOwnProperty;
+const _toString = Object.prototype.toString;
+
+function resolveYamlOmap(data: Any): boolean {
+ const objectKeys: string[] = [];
+ let pairKey = "";
+ let pairHasKey = false;
+
+ for (const pair of data) {
+ pairHasKey = false;
+
+ if (_toString.call(pair) !== "[object Object]") return false;
+
+ for (pairKey in pair) {
+ if (_hasOwnProperty.call(pair, pairKey)) {
+ if (!pairHasKey) pairHasKey = true;
+ else return false;
+ }
+ }
+
+ if (!pairHasKey) return false;
+
+ if (objectKeys.indexOf(pairKey) === -1) objectKeys.push(pairKey);
+ else return false;
+ }
+
+ return true;
+}
+
+function constructYamlOmap(data: Any): Any {
+ return data !== null ? data : [];
+}
+
+export const omap = new Type("tag:yaml.org,2002:omap", {
+ construct: constructYamlOmap,
+ kind: "sequence",
+ resolve: resolveYamlOmap
+});
diff --git a/std/encoding/yaml/type/pairs.ts b/std/encoding/yaml/type/pairs.ts
new file mode 100644
index 000000000..37f8eb630
--- /dev/null
+++ b/std/encoding/yaml/type/pairs.ts
@@ -0,0 +1,49 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { Type } from "../type.ts";
+import { Any } from "../utils.ts";
+
+const _toString = Object.prototype.toString;
+
+function resolveYamlPairs(data: Any[][]): boolean {
+ const result = new Array(data.length);
+
+ for (let index = 0; index < data.length; index++) {
+ const pair = data[index];
+
+ if (_toString.call(pair) !== "[object Object]") return false;
+
+ const keys = Object.keys(pair);
+
+ if (keys.length !== 1) return false;
+
+ result[index] = [keys[0], pair[keys[0] as Any]];
+ }
+
+ return true;
+}
+
+function constructYamlPairs(data: string): Any[] {
+ if (data === null) return [];
+
+ const result = new Array(data.length);
+
+ for (let index = 0; index < data.length; index += 1) {
+ const pair = data[index];
+
+ const keys = Object.keys(pair);
+
+ result[index] = [keys[0], pair[keys[0] as Any]];
+ }
+
+ return result;
+}
+
+export const pairs = new Type("tag:yaml.org,2002:pairs", {
+ construct: constructYamlPairs,
+ kind: "sequence",
+ resolve: resolveYamlPairs
+});
diff --git a/std/encoding/yaml/type/seq.ts b/std/encoding/yaml/type/seq.ts
new file mode 100644
index 000000000..32c700b6e
--- /dev/null
+++ b/std/encoding/yaml/type/seq.ts
@@ -0,0 +1,14 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { Type } from "../type.ts";
+import { Any } from "../utils.ts";
+
+export const seq = new Type("tag:yaml.org,2002:seq", {
+ construct(data): Any {
+ return data !== null ? data : [];
+ },
+ kind: "sequence"
+});
diff --git a/std/encoding/yaml/type/set.ts b/std/encoding/yaml/type/set.ts
new file mode 100644
index 000000000..3273223f7
--- /dev/null
+++ b/std/encoding/yaml/type/set.ts
@@ -0,0 +1,31 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { Type } from "../type.ts";
+import { Any } from "../utils.ts";
+
+const _hasOwnProperty = Object.prototype.hasOwnProperty;
+
+function resolveYamlSet(data: Any): boolean {
+ if (data === null) return true;
+
+ for (const key in data) {
+ if (_hasOwnProperty.call(data, key)) {
+ if (data[key] !== null) return false;
+ }
+ }
+
+ return true;
+}
+
+function constructYamlSet(data: string): Any {
+ return data !== null ? data : {};
+}
+
+export const set = new Type("tag:yaml.org,2002:set", {
+ construct: constructYamlSet,
+ kind: "mapping",
+ resolve: resolveYamlSet
+});
diff --git a/std/encoding/yaml/type/str.ts b/std/encoding/yaml/type/str.ts
new file mode 100644
index 000000000..8c24e9398
--- /dev/null
+++ b/std/encoding/yaml/type/str.ts
@@ -0,0 +1,12 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { Type } from "../type.ts";
+
+export const str = new Type("tag:yaml.org,2002:str", {
+ construct(data): string {
+ return data !== null ? data : "";
+ },
+ kind: "scalar"
+});
diff --git a/std/encoding/yaml/type/timestamp.ts b/std/encoding/yaml/type/timestamp.ts
new file mode 100644
index 000000000..38cc9d940
--- /dev/null
+++ b/std/encoding/yaml/type/timestamp.ts
@@ -0,0 +1,96 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import { Type } from "../type.ts";
+
+const YAML_DATE_REGEXP = new RegExp(
+ "^([0-9][0-9][0-9][0-9])" + // [1] year
+ "-([0-9][0-9])" + // [2] month
+ "-([0-9][0-9])$" // [3] day
+);
+
+const YAML_TIMESTAMP_REGEXP = new RegExp(
+ "^([0-9][0-9][0-9][0-9])" + // [1] year
+ "-([0-9][0-9]?)" + // [2] month
+ "-([0-9][0-9]?)" + // [3] day
+ "(?:[Tt]|[ \\t]+)" + // ...
+ "([0-9][0-9]?)" + // [4] hour
+ ":([0-9][0-9])" + // [5] minute
+ ":([0-9][0-9])" + // [6] second
+ "(?:\\.([0-9]*))?" + // [7] fraction
+ "(?:[ \\t]*(Z|([-+])([0-9][0-9]?)" + // [8] tz [9] tz_sign [10] tz_hour
+ "(?::([0-9][0-9]))?))?$" // [11] tz_minute
+);
+
+function resolveYamlTimestamp(data: string): boolean {
+ if (data === null) return false;
+ if (YAML_DATE_REGEXP.exec(data) !== null) return true;
+ if (YAML_TIMESTAMP_REGEXP.exec(data) !== null) return true;
+ return false;
+}
+
+function constructYamlTimestamp(data: string): Date {
+ let match = YAML_DATE_REGEXP.exec(data);
+ if (match === null) match = YAML_TIMESTAMP_REGEXP.exec(data);
+
+ if (match === null) throw new Error("Date resolve error");
+
+ // match: [1] year [2] month [3] day
+
+ const year = +match[1];
+ const month = +match[2] - 1; // JS month starts with 0
+ const day = +match[3];
+
+ if (!match[4]) {
+ // no hour
+ return new Date(Date.UTC(year, month, day));
+ }
+
+ // match: [4] hour [5] minute [6] second [7] fraction
+
+ const hour = +match[4];
+ const minute = +match[5];
+ const second = +match[6];
+
+ let fraction = 0;
+ if (match[7]) {
+ let partFraction = match[7].slice(0, 3);
+ while (partFraction.length < 3) {
+ // milli-seconds
+ partFraction += "0";
+ }
+ fraction = +partFraction;
+ }
+
+ // match: [8] tz [9] tz_sign [10] tz_hour [11] tz_minute
+
+ let delta = null;
+ if (match[9]) {
+ const tzHour = +match[10];
+ const tzMinute = +(match[11] || 0);
+ delta = (tzHour * 60 + tzMinute) * 60000; // delta in mili-seconds
+ if (match[9] === "-") delta = -delta;
+ }
+
+ const date = new Date(
+ Date.UTC(year, month, day, hour, minute, second, fraction)
+ );
+
+ if (delta) date.setTime(date.getTime() - delta);
+
+ return date;
+}
+
+function representYamlTimestamp(date: Date): string {
+ return date.toISOString();
+}
+
+export const timestamp = new Type("tag:yaml.org,2002:timestamp", {
+ construct: constructYamlTimestamp,
+ instanceOf: Date,
+ kind: "scalar",
+ represent: representYamlTimestamp,
+ resolve: resolveYamlTimestamp
+});
diff --git a/std/encoding/yaml/utils.ts b/std/encoding/yaml/utils.ts
new file mode 100644
index 000000000..2f4ea00b5
--- /dev/null
+++ b/std/encoding/yaml/utils.ts
@@ -0,0 +1,84 @@
+// Ported from js-yaml v3.13.1:
+// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
+// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
+export type Any = any;
+
+export function isNothing(subject: unknown): subject is never {
+ return typeof subject === "undefined" || subject === null;
+}
+
+export function isArray(value: unknown): value is Any[] {
+ return Array.isArray(value);
+}
+
+export function isBoolean(value: unknown): value is boolean {
+ return typeof value === "boolean" || value instanceof Boolean;
+}
+
+export function isNull(value: unknown): value is null {
+ return value === null;
+}
+
+export function isNullOrUndefined(value: unknown): value is null | undefined {
+ return value === null || value === undefined;
+}
+
+export function isNumber(value: unknown): value is number {
+ return typeof value === "number" || value instanceof Number;
+}
+
+export function isString(value: unknown): value is string {
+ return typeof value === "string" || value instanceof String;
+}
+
+export function isSymbol(value: unknown): value is symbol {
+ return typeof value === "symbol";
+}
+
+export function isUndefined(value: unknown): value is undefined {
+ return value === undefined;
+}
+
+export function isObject(value: unknown): value is object {
+ return value !== null && typeof value === "object";
+}
+
+export function isError(e: unknown): boolean {
+ return e instanceof Error;
+}
+
+export function isFunction(value: unknown): value is () => void {
+ return typeof value === "function";
+}
+
+export function isRegExp(value: unknown): value is RegExp {
+ return value instanceof RegExp;
+}
+
+export function toArray<T>(sequence: T): T | [] | [T] {
+ if (isArray(sequence)) return sequence;
+ if (isNothing(sequence)) return [];
+
+ return [sequence];
+}
+
+export function repeat(str: string, count: number): string {
+ let result = "";
+
+ for (let cycle = 0; cycle < count; cycle++) {
+ result += str;
+ }
+
+ return result;
+}
+
+export function isNegativeZero(i: number): boolean {
+ return i === 0 && Number.NEGATIVE_INFINITY === 1 / i;
+}
+
+export interface ArrayObject<T = Any> {
+ [P: string]: T;
+}
diff --git a/std/encoding/yaml_test.ts b/std/encoding/yaml_test.ts
new file mode 100644
index 000000000..40de6a94d
--- /dev/null
+++ b/std/encoding/yaml_test.ts
@@ -0,0 +1,4 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import "./yaml/parse_test.ts";
+import "./yaml/stringify_test.ts";