diff options
| author | Casper Beyer <caspervonb@pm.me> | 2021-02-02 19:05:46 +0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-02-02 12:05:46 +0100 |
| commit | 6abf126c2a7a451cded8c6b5e6ddf1b69c84055d (patch) | |
| tree | fd94c013a19fcb38954844085821ec1601c20e18 /std/encoding/toml.ts | |
| parent | a2b5d44f1aa9d64f448a2a3cc2001272e2f60b98 (diff) | |
chore: remove std directory (#9361)
This removes the std folder from the tree.
Various parts of the tests are pretty tightly dependent
on std (47 direct imports and 75 indirect imports, not
counting the cli tests that use them as fixtures) so I've
added std as a submodule for now.
Diffstat (limited to 'std/encoding/toml.ts')
| -rw-r--r-- | std/encoding/toml.ts | 736 |
1 files changed, 0 insertions, 736 deletions
diff --git a/std/encoding/toml.ts b/std/encoding/toml.ts deleted file mode 100644 index 6cd8faa71..000000000 --- a/std/encoding/toml.ts +++ /dev/null @@ -1,736 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -import { deepAssign } from "../_util/deep_assign.ts"; -import { assert } from "../_util/assert.ts"; - -class TOMLError extends Error {} - -class KeyValuePair { - constructor(public key: string, public value: unknown) {} -} - -class ParserGroup { - arrValues: unknown[] = []; - objValues: Record<string, unknown> = {}; - - constructor(public type: string, public name: string) {} -} - -class ParserContext { - currentGroup?: ParserGroup; - output: Record<string, unknown> = {}; -} - -class Parser { - tomlLines: string[]; - context: ParserContext; - constructor(tomlString: string) { - this.tomlLines = this._split(tomlString); - this.context = new ParserContext(); - } - _sanitize(): void { - const out: string[] = []; - for (let i = 0; i < this.tomlLines.length; i++) { - const s = this.tomlLines[i]; - const trimmed = s.trim(); - if (trimmed !== "") { - out.push(s); - } - } - this.tomlLines = out; - this._removeComments(); - this._mergeMultilines(); - } - - _removeComments(): void { - function isFullLineComment(line: string) { - return line.match(/^#/) ? true : false; - } - - function stringStart(line: string) { - const m = line.match(/(?:=\s*\[?\s*)("""|'''|"|')/); - if (!m) { - return false; - } - - // We want to know which syntax was used to open the string - openStringSyntax = m[1]; - return true; - } - - function stringEnd(line: string) { - // match the syntax used to open the string when searching for string close - // e.g. if we open with ''' we must close with a ''' - const reg = RegExp(`(?<!(=\\s*))${openStringSyntax}(?!(.*"))`); - if (!line.match(reg)) { - return false; - } - - openStringSyntax = ""; - return true; - } - - const cleaned = []; - let isOpenString = false; - let openStringSyntax = ""; - for (let i = 0; i < this.tomlLines.length; i++) { - const line = this.tomlLines[i]; - - // stringStart and stringEnd are separate conditions to - // support both single-line and multi-line strings - if (!isOpenString && stringStart(line)) { - isOpenString = true; - } - if (isOpenString && stringEnd(line)) { - isOpenString = false; - } - - if (!isOpenString && !isFullLineComment(line)) { - const out = line.split( - /(?<=([\,\[\]\{\}]|".*"|'.*'|\w(?!.*("|')+))\s*)#/gi, - ); - cleaned.push(out[0].trim()); - } else if (isOpenString || !isFullLineComment(line)) { - cleaned.push(line); - } - - // If a single line comment doesnt end on the same line, throw error - if ( - isOpenString && (openStringSyntax === "'" || openStringSyntax === '"') - ) { - throw new TOMLError(`Single-line string is not closed:\n${line}`); - } - } - - if (isOpenString) { - throw new TOMLError(`Incomplete string until EOF`); - } - - this.tomlLines = cleaned; - } - - _mergeMultilines(): void { - function arrayStart(line: string): boolean { - const reg = /.*=\s*\[/g; - return reg.test(line) && !(line[line.length - 1] === "]"); - } - - function arrayEnd(line: string): boolean { - return line[line.length - 1] === "]"; - } - - function stringStart(line: string): boolean { - const m = line.match(/.*=\s*(?:\"\"\"|''')/); - if (!m) { - return false; - } - return !line.endsWith(`"""`) || !line.endsWith(`'''`); - } - - function stringEnd(line: string): boolean { - return line.endsWith(`'''`) || line.endsWith(`"""`); - } - - function isLiteralString(line: string): boolean { - return line.match(/'''/) ? true : false; - } - - const merged = []; - let acc = [], - isLiteral = false, - capture = false, - captureType = "", - merge = false; - - for (let i = 0; i < this.tomlLines.length; i++) { - const line = this.tomlLines[i]; - const trimmed = line.trim(); - if (!capture && arrayStart(trimmed)) { - capture = true; - captureType = "array"; - } else if (!capture && stringStart(trimmed)) { - isLiteral = isLiteralString(trimmed); - capture = true; - captureType = "string"; - } else if (capture && arrayEnd(trimmed)) { - merge = true; - } else if (capture && stringEnd(trimmed)) { - merge = true; - } - - if (capture) { - if (isLiteral) { - acc.push(line); - } else { - acc.push(trimmed); - } - } else { - if (isLiteral) { - merged.push(line); - } else { - merged.push(trimmed); - } - } - - if (merge) { - capture = false; - merge = false; - if (captureType === "string") { - merged.push( - acc - .join("\n") - .replace(/"""/g, '"') - .replace(/'''/g, `'`) - .replace(/\n/g, "\\n"), - ); - isLiteral = false; - } else { - merged.push(acc.join("")); - } - captureType = ""; - acc = []; - } - } - this.tomlLines = merged; - } - _unflat( - keys: string[], - values: Record<string, unknown> | unknown[] = {}, - cObj: Record<string, unknown> | unknown[] = {}, - ): Record<string, unknown> { - const out: Record<string, unknown> = {}; - if (keys.length === 0) { - return cObj as Record<string, unknown>; - } else { - if (Object.keys(cObj).length === 0) { - cObj = values; - } - const key: string | undefined = keys.pop(); - if (key) { - out[key] = cObj; - } - return this._unflat(keys, values, out); - } - } - _groupToOutput(): void { - assert(this.context.currentGroup != null, "currentGroup must be set"); - const arrProperty = this.context.currentGroup.name - .replace(/"/g, "") - .replace(/'/g, "") - .split("."); - let u = {}; - if (this.context.currentGroup.type === "array") { - u = this._unflat(arrProperty, this.context.currentGroup.arrValues); - } else { - u = this._unflat(arrProperty, this.context.currentGroup.objValues); - } - deepAssign(this.context.output, u); - delete this.context.currentGroup; - } - _split(str: string): string[] { - const out = []; - out.push(...str.split("\n")); - return out; - } - _isGroup(line: string): boolean { - const t = line.trim(); - return t[0] === "[" && /\[(.*)\]/.exec(t) ? true : false; - } - _isDeclaration(line: string): boolean { - return line.split("=").length > 1; - } - _createGroup(line: string): void { - const captureReg = /\[(.*)\]/; - if (this.context.currentGroup) { - this._groupToOutput(); - } - - let type; - let m = line.match(captureReg); - assert(m != null, "line mut be matched"); - let name = m[1]; - if (name.match(/\[.*\]/)) { - type = "array"; - m = name.match(captureReg); - assert(m != null, "name must be matched"); - name = m[1]; - } else { - type = "object"; - } - this.context.currentGroup = new ParserGroup(type, name); - } - _processDeclaration(line: string): KeyValuePair { - const idx = line.indexOf("="); - const key = line.substring(0, idx).trim(); - const value = this._parseData(line.slice(idx + 1)); - return new KeyValuePair(key, value); - } - _parseData(dataString: string): unknown { - dataString = dataString.trim(); - switch (dataString[0]) { - case '"': - case "'": - return this._parseString(dataString); - case "[": - case "{": - return this._parseInlineTableOrArray(dataString); - default: { - // Strip a comment. - const match = /#.*$/.exec(dataString); - if (match) { - dataString = dataString.slice(0, match.index).trim(); - } - - switch (dataString) { - case "true": - return true; - case "false": - return false; - case "inf": - case "+inf": - return Infinity; - case "-inf": - return -Infinity; - case "nan": - case "+nan": - case "-nan": - return NaN; - default: - return this._parseNumberOrDate(dataString); - } - } - } - } - _parseInlineTableOrArray(dataString: string): unknown { - const invalidArr = /,\]/g.exec(dataString); - if (invalidArr) { - dataString = dataString.replace(/,]/g, "]"); - } - - if ( - (dataString[0] === "{" && dataString[dataString.length - 1] === "}") || - (dataString[0] === "[" && dataString[dataString.length - 1] === "]") - ) { - const reg = /([a-zA-Z0-9-_\.]*) (=)/gi; - let result; - while ((result = reg.exec(dataString))) { - const ogVal = result[0]; - const newVal = ogVal - .replace(result[1], `"${result[1]}"`) - .replace(result[2], ":"); - dataString = dataString.replace(ogVal, newVal); - } - return JSON.parse(dataString); - } - throw new TOMLError("Malformed inline table or array literal"); - } - _parseString(dataString: string): string { - const quote = dataString[0]; - // Handle First and last EOL for multiline strings - if (dataString.startsWith(`"\\n`)) { - dataString = dataString.replace(`"\\n`, `"`); - } else if (dataString.startsWith(`'\\n`)) { - dataString = dataString.replace(`'\\n`, `'`); - } - if (dataString.endsWith(`\\n"`)) { - dataString = dataString.replace(`\\n"`, `"`); - } else if (dataString.endsWith(`\\n'`)) { - dataString = dataString.replace(`\\n'`, `'`); - } - let value = ""; - for (let i = 1; i < dataString.length; i++) { - switch (dataString[i]) { - case "\\": - i++; - // See https://toml.io/en/v1.0.0-rc.3#string - switch (dataString[i]) { - case "b": - value += "\b"; - break; - case "t": - value += "\t"; - break; - case "n": - value += "\n"; - break; - case "f": - value += "\f"; - break; - case "r": - value += "\r"; - break; - case "u": - case "U": { - // Unicode character - const codePointLen = dataString[i] === "u" ? 4 : 6; - const codePoint = parseInt( - "0x" + dataString.slice(i + 1, i + 1 + codePointLen), - 16, - ); - value += String.fromCodePoint(codePoint); - i += codePointLen; - break; - } - case "\\": - value += "\\"; - break; - default: - value += dataString[i]; - break; - } - break; - case quote: - if (dataString[i - 1] !== "\\") { - return value; - } - break; - default: - value += dataString[i]; - break; - } - } - throw new TOMLError("Incomplete string literal"); - } - _parseNumberOrDate(dataString: string): unknown { - if (this._isDate(dataString)) { - return new Date(dataString); - } - - if (this._isLocalTime(dataString)) { - return dataString; - } - - // If binary / octal / hex - const hex = /^(0(?:x|o|b)[0-9a-f_]*)/gi.exec(dataString); - if (hex && hex[0]) { - return hex[0].trim(); - } - - const testNumber = this._isParsableNumber(dataString); - if (testNumber !== false && !isNaN(testNumber as number)) { - return testNumber; - } - - return String(dataString); - } - _isLocalTime(str: string): boolean { - const reg = /(\d{2}):(\d{2}):(\d{2})/; - return reg.test(str); - } - _isParsableNumber(dataString: string): number | boolean { - const m = /((?:\+|-|)[0-9_\.e+\-]*)[^#]/i.exec(dataString); - if (!m) { - return false; - } else { - return parseFloat(m[0].replace(/_/g, "")); - } - } - _isDate(dateStr: string): boolean { - const reg = /\d{4}-\d{2}-\d{2}/; - return reg.test(dateStr); - } - _parseDeclarationName(declaration: string): string[] { - const out = []; - let acc = []; - let inLiteral = false; - for (let i = 0; i < declaration.length; i++) { - const c = declaration[i]; - switch (c) { - case ".": - if (!inLiteral) { - out.push(acc.join("")); - acc = []; - } else { - acc.push(c); - } - break; - case `"`: - if (inLiteral) { - inLiteral = false; - } else { - inLiteral = true; - } - break; - default: - acc.push(c); - break; - } - } - if (acc.length !== 0) { - out.push(acc.join("")); - } - return out; - } - _parseLines(): void { - for (let i = 0; i < this.tomlLines.length; i++) { - const line = this.tomlLines[i]; - - // TODO(zekth): Handle unflat of array of tables - if (this._isGroup(line)) { - // if the current group is an array we push the - // parsed objects in it. - if ( - this.context.currentGroup && - this.context.currentGroup.type === "array" - ) { - this.context.currentGroup.arrValues.push( - this.context.currentGroup.objValues, - ); - this.context.currentGroup.objValues = {}; - } - // If we need to create a group or to change group - if ( - !this.context.currentGroup || - (this.context.currentGroup && - this.context.currentGroup.name !== - line.replace(/\[/g, "").replace(/\]/g, "")) - ) { - this._createGroup(line); - continue; - } - } - if (this._isDeclaration(line)) { - const kv = this._processDeclaration(line); - const key = kv.key; - const value = kv.value; - if (!this.context.currentGroup) { - this.context.output[key] = value; - } else { - this.context.currentGroup.objValues[key] = value; - } - } - } - if (this.context.currentGroup) { - if (this.context.currentGroup.type === "array") { - this.context.currentGroup.arrValues.push( - this.context.currentGroup.objValues, - ); - } - this._groupToOutput(); - } - } - _cleanOutput(): void { - this._propertyClean(this.context.output); - } - _propertyClean(obj: Record<string, unknown>): void { - const keys = Object.keys(obj); - for (let i = 0; i < keys.length; i++) { - let k = keys[i]; - if (k) { - let v = obj[k]; - const pathDeclaration = this._parseDeclarationName(k); - delete obj[k]; - if (pathDeclaration.length > 1) { - const shift = pathDeclaration.shift(); - if (shift) { - k = shift.replace(/"/g, ""); - v = this._unflat(pathDeclaration, v as Record<string, unknown>); - } - } else { - k = k.replace(/"/g, ""); - } - obj[k] = v; - if (v instanceof Object) { - // deno-lint-ignore no-explicit-any - this._propertyClean(v as any); - } - } - } - } - parse(): Record<string, unknown> { - this._sanitize(); - this._parseLines(); - this._cleanOutput(); - return this.context.output; - } -} - -// Bare keys may only contain ASCII letters, -// ASCII digits, underscores, and dashes (A-Za-z0-9_-). -function joinKeys(keys: string[]): string { - // Dotted keys are a sequence of bare or quoted keys joined with a dot. - // This allows for grouping similar properties together: - return keys - .map((str: string): string => { - return str.match(/[^A-Za-z0-9_-]/) ? `"${str}"` : str; - }) - .join("."); -} - -class Dumper { - maxPad = 0; - srcObject: Record<string, unknown>; - output: string[] = []; - constructor(srcObjc: Record<string, unknown>) { - this.srcObject = srcObjc; - } - dump(): string[] { - // deno-lint-ignore no-explicit-any - this.output = this._parse(this.srcObject as any); - this.output = this._format(); - return this.output; - } - _parse(obj: Record<string, unknown>, keys: string[] = []): string[] { - const out = []; - const props = Object.keys(obj); - const propObj = props.filter((e: string): boolean => { - if (obj[e] instanceof Array) { - const d: unknown[] = obj[e] as unknown[]; - return !this._isSimplySerializable(d[0]); - } - return !this._isSimplySerializable(obj[e]); - }); - const propPrim = props.filter((e: string): boolean => { - if (obj[e] instanceof Array) { - const d: unknown[] = obj[e] as unknown[]; - return this._isSimplySerializable(d[0]); - } - return this._isSimplySerializable(obj[e]); - }); - const k = propPrim.concat(propObj); - for (let i = 0; i < k.length; i++) { - const prop = k[i]; - const value = obj[prop]; - if (value instanceof Date) { - out.push(this._dateDeclaration([prop], value)); - } else if (typeof value === "string" || value instanceof RegExp) { - out.push(this._strDeclaration([prop], value.toString())); - } else if (typeof value === "number") { - out.push(this._numberDeclaration([prop], value)); - } else if (typeof value === "boolean") { - out.push(this._boolDeclaration([prop], value)); - } else if ( - value instanceof Array && - this._isSimplySerializable(value[0]) - ) { - // only if primitives types in the array - out.push(this._arrayDeclaration([prop], value)); - } else if ( - value instanceof Array && - !this._isSimplySerializable(value[0]) - ) { - // array of objects - for (let i = 0; i < value.length; i++) { - out.push(""); - out.push(this._headerGroup([...keys, prop])); - out.push(...this._parse(value[i], [...keys, prop])); - } - } else if (typeof value === "object") { - out.push(""); - out.push(this._header([...keys, prop])); - if (value) { - const toParse = value as Record<string, unknown>; - out.push(...this._parse(toParse, [...keys, prop])); - } - // out.push(...this._parse(value, `${path}${prop}.`)); - } - } - out.push(""); - return out; - } - _isSimplySerializable(value: unknown): boolean { - return ( - typeof value === "string" || - typeof value === "number" || - typeof value === "boolean" || - value instanceof RegExp || - value instanceof Date || - value instanceof Array - ); - } - _header(keys: string[]): string { - return `[${joinKeys(keys)}]`; - } - _headerGroup(keys: string[]): string { - return `[[${joinKeys(keys)}]]`; - } - _declaration(keys: string[]): string { - const title = joinKeys(keys); - if (title.length > this.maxPad) { - this.maxPad = title.length; - } - return `${title} = `; - } - _arrayDeclaration(keys: string[], value: unknown[]): string { - return `${this._declaration(keys)}${JSON.stringify(value)}`; - } - _strDeclaration(keys: string[], value: string): string { - return `${this._declaration(keys)}"${value}"`; - } - _numberDeclaration(keys: string[], value: number): string { - switch (value) { - case Infinity: - return `${this._declaration(keys)}inf`; - case -Infinity: - return `${this._declaration(keys)}-inf`; - default: - return `${this._declaration(keys)}${value}`; - } - } - _boolDeclaration(keys: string[], value: boolean): string { - return `${this._declaration(keys)}${value}`; - } - _dateDeclaration(keys: string[], value: Date): string { - function dtPad(v: string, lPad = 2): string { - return v.padStart(lPad, "0"); - } - const m = dtPad((value.getUTCMonth() + 1).toString()); - const d = dtPad(value.getUTCDate().toString()); - const h = dtPad(value.getUTCHours().toString()); - const min = dtPad(value.getUTCMinutes().toString()); - const s = dtPad(value.getUTCSeconds().toString()); - const ms = dtPad(value.getUTCMilliseconds().toString(), 3); - // formatted date - const fData = `${value.getUTCFullYear()}-${m}-${d}T${h}:${min}:${s}.${ms}`; - return `${this._declaration(keys)}${fData}`; - } - _format(): string[] { - const rDeclaration = /(.*)\s=/; - const out = []; - for (let i = 0; i < this.output.length; i++) { - const l = this.output[i]; - // we keep empty entry for array of objects - if (l[0] === "[" && l[1] !== "[") { - // empty object - if (this.output[i + 1] === "") { - i += 1; - continue; - } - out.push(l); - } else { - const m = rDeclaration.exec(l); - if (m) { - out.push(l.replace(m[1], m[1].padEnd(this.maxPad))); - } else { - out.push(l); - } - } - } - // Cleaning multiple spaces - const cleanedOutput = []; - for (let i = 0; i < out.length; i++) { - const l = out[i]; - if (!(l === "" && out[i + 1] === "")) { - cleanedOutput.push(l); - } - } - return cleanedOutput; - } -} - -/** - * Stringify dumps source object into TOML string and returns it. - * @param srcObj - */ -export function stringify(srcObj: Record<string, unknown>): string { - return new Dumper(srcObj).dump().join("\n"); -} - -/** - * Parse parses TOML string into an object. - * @param tomlString - */ -export function parse(tomlString: string): Record<string, unknown> { - // File is potentially using EOL CRLF - tomlString = tomlString.replace(/\r\n/g, "\n").replace(/\\\n/g, "\n"); - return new Parser(tomlString).parse(); -} |
