From aa21e7bc81909630bec6e3e527e89f3673b75a3c Mon Sep 17 00:00:00 2001 From: Yusuke Sakurai Date: Tue, 24 Sep 2019 00:08:57 +0900 Subject: [PATCH] fix TOML's key encoding (denoland/deno_std#612) Original: https://github.com/denoland/deno_std/commit/54a5b95fefd10b41f65cf6fd3eba626577eadda7 --- encoding/toml.ts | 65 +++++++++++++++++++++++++------------------ encoding/toml_test.ts | 22 ++++++++++++++- 2 files changed, 59 insertions(+), 28 deletions(-) diff --git a/encoding/toml.ts b/encoding/toml.ts index 228c1180b4..2abc039309 100644 --- a/encoding/toml.ts +++ b/encoding/toml.ts @@ -387,6 +387,18 @@ class Parser { } } +// 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: number = 0; srcObject: object; @@ -400,7 +412,7 @@ class Dumper { this.output = this._format(); return this.output; } - _parse(obj: Record, path: string = ""): string[] { + _parse(obj: Record, keys: string[] = []): string[] { const out = []; const props = Object.keys(obj); const propObj = props.filter((e: string): boolean => { @@ -422,17 +434,17 @@ class Dumper { const prop = k[i]; const value = obj[prop]; if (value instanceof Date) { - out.push(this._dateDeclaration(prop, value)); + out.push(this._dateDeclaration([prop], value)); } else if (typeof value === "string" || value instanceof RegExp) { - out.push(this._strDeclaration(prop, value.toString())); + out.push(this._strDeclaration([prop], value.toString())); } else if (typeof value === "number") { - out.push(this._numberDeclaration(prop, value)); + out.push(this._numberDeclaration([prop], value)); } else if ( value instanceof Array && this._isSimplySerializable(value[0]) ) { // only if primitives types in the array - out.push(this._arrayDeclaration(prop, value)); + out.push(this._arrayDeclaration([prop], value)); } else if ( value instanceof Array && !this._isSimplySerializable(value[0]) @@ -440,15 +452,15 @@ class Dumper { // array of objects for (let i = 0; i < value.length; i++) { out.push(""); - out.push(this._headerGroup(path + prop)); - out.push(...this._parse(value[i], `${path}${prop}.`)); + 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(path + prop)); + out.push(this._header([...keys, prop])); if (value) { const toParse = value as Record; - out.push(...this._parse(toParse, `${path}${prop}.`)); + out.push(...this._parse(toParse, [...keys, prop])); } // out.push(...this._parse(value, `${path}${prop}.`)); } @@ -465,35 +477,36 @@ class Dumper { value instanceof Array ); } - _header(title: string): string { - return `[${title}]`; + _header(keys: string[]): string { + return `[${joinKeys(keys)}]`; } - _headerGroup(title: string): string { - return `[[${title}]]`; + _headerGroup(keys: string[]): string { + return `[[${joinKeys(keys)}]]`; } - _declaration(title: string): string { + _declaration(keys: string[]): string { + const title = joinKeys(keys); if (title.length > this.maxPad) { this.maxPad = title.length; } return `${title} = `; } - _arrayDeclaration(title: string, value: unknown[]): string { - return `${this._declaration(title)}${JSON.stringify(value)}`; + _arrayDeclaration(keys: string[], value: unknown[]): string { + return `${this._declaration(keys)}${JSON.stringify(value)}`; } - _strDeclaration(title: string, value: string): string { - return `${this._declaration(title)}"${value}"`; + _strDeclaration(keys: string[], value: string): string { + return `${this._declaration(keys)}"${value}"`; } - _numberDeclaration(title: string, value: number): string { + _numberDeclaration(keys: string[], value: number): string { switch (value) { case Infinity: - return `${this._declaration(title)}inf`; + return `${this._declaration(keys)}inf`; case -Infinity: - return `${this._declaration(title)}-inf`; + return `${this._declaration(keys)}-inf`; default: - return `${this._declaration(title)}${value}`; + return `${this._declaration(keys)}${value}`; } } - _dateDeclaration(title: string, value: Date): string { + _dateDeclaration(keys: string[], value: Date): string { function dtPad(v: string, lPad: number = 2): string { return pad(v, lPad, { char: "0" }); } @@ -505,7 +518,7 @@ class Dumper { const ms = dtPad(value.getUTCMilliseconds().toString(), 3); // formated date const fData = `${value.getUTCFullYear()}-${m}-${d}T${h}:${min}:${s}.${ms}`; - return `${this._declaration(title)}${fData}`; + return `${this._declaration(keys)}${fData}`; } _format(): string[] { const rDeclaration = /(.*)\s=/; @@ -542,9 +555,7 @@ class Dumper { } export function stringify(srcObj: object): string { - let out: string[] = []; - out = new Dumper(srcObj).dump(); - return out.join("\n"); + return new Dumper(srcObj).dump().join("\n"); } export function parse(tomlString: string): object { diff --git a/encoding/toml_test.ts b/encoding/toml_test.ts index 28a6204533..22ecfa68a9 100644 --- a/encoding/toml_test.ts +++ b/encoding/toml_test.ts @@ -1,5 +1,5 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -import { test } from "../testing/mod.ts"; +import { runIfMain, test } from "../testing/mod.ts"; import { assertEquals } from "../testing/asserts.ts"; import { existsSync } from "../fs/exists.ts"; import { readFileStrSync } from "../fs/read_file_str.ts"; @@ -301,6 +301,17 @@ test({ const src = { foo: { bar: "deno" }, this: { is: { nested: "denonono" } }, + "https://deno.land/std": { + $: "doller" + }, + "##": { + deno: { + "https://deno.land": { + proto: "https", + ":80": "port" + } + } + }, arrayObjects: [{ stuff: "in" }, {}, { the: "array" }], deno: "is", not: "[node]", @@ -376,6 +387,13 @@ bar = "deno" [this.is] nested = "denonono" +["https://deno.land/std"] +"$" = "doller" + +["##".deno."https://deno.land"] +proto = "https" +":80" = "port" + [[arrayObjects]] stuff = "in" @@ -388,3 +406,5 @@ the = "array" assertEquals(actual, expected); } }); + +runIfMain(import.meta);