summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVincent LE GOFF <g_n_s@hotmail.fr>2019-04-05 06:23:05 +0200
committerRyan Dahl <ry@tinyclouds.org>2019-04-05 00:23:05 -0400
commitae9148752ca1ec8dbbfd121dabc44c724bd8f7fd (patch)
treed60014d61579bb60c9f05f0118f85ff1c8d12154
parentf8f561135016d23c0bb4ee75b6d6e54eda9daed8 (diff)
toml: add Stringify feature (denoland/deno_std#319)
Original: https://github.com/denoland/deno_std/commit/1e589b95532fd3575b81df65a2a99f7004f8ea1b
-rw-r--r--strings/pad.ts6
-rw-r--r--toml/README.md16
-rw-r--r--toml/parser.ts151
-rw-r--r--toml/parser_test.ts96
4 files changed, 265 insertions, 4 deletions
diff --git a/strings/pad.ts b/strings/pad.ts
index 1c1868769..3c5084da2 100644
--- a/strings/pad.ts
+++ b/strings/pad.ts
@@ -3,9 +3,9 @@
/** FillOption Object */
export interface FillOption {
/** Char to fill in */
- char: string;
+ char?: string;
/** Side to fill in */
- side: "left" | "right";
+ side?: "left" | "right";
/** If strict, output string can't be greater than strLen*/
strict?: boolean;
/** char/string used to specify the string has been truncated */
@@ -54,7 +54,7 @@ export function pad(
let out = input;
const outL = out.length;
if (outL < strLen) {
- if (opts.side === "left") {
+ if (!opts.side || opts.side === "left") {
out = out.padStart(strLen, opts.char);
} else {
out = out.padEnd(strLen, opts.char);
diff --git a/toml/README.md b/toml/README.md
index b19974b2e..f4ae25857 100644
--- a/toml/README.md
+++ b/toml/README.md
@@ -91,6 +91,8 @@ will output:
## Usage
+### Parse
+
```ts
import { parseFile, parse } from "./parser.ts";
@@ -99,3 +101,17 @@ const tomlObject = parseFile("file.toml");
const tomlString = 'foo.bar = "Deno"';
const tomlObject22 = parse(tomlString);
```
+
+### Stringify
+
+```ts
+import { stringify } from "./parser.ts";
+const obj = {
+ bin: [
+ { name: "deno", path: "cli/main.rs" },
+ { name: "deno_core", path: "src/foo.rs" }
+ ],
+ nib: [{ name: "node", path: "not_found" }]
+};
+const tomlString = stringify(obj);
+```
diff --git a/toml/parser.ts b/toml/parser.ts
index 09203775a..a7dd97750 100644
--- a/toml/parser.ts
+++ b/toml/parser.ts
@@ -1,6 +1,7 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
import { existsSync } from "../fs/exists.ts";
import { deepAssign } from "../util/deep_assign.ts";
+import { pad } from "../strings/pad.ts";
class KeyValuePair {
key: string;
@@ -381,6 +382,156 @@ class Parser {
}
}
+class Dumper {
+ maxPad: number = 0;
+ srcObject: object;
+ output: string[] = [];
+ constructor(srcObjc: object) {
+ this.srcObject = srcObjc;
+ }
+ dump(): string[] {
+ this.output = this._parse(this.srcObject);
+ this.output = this._format();
+ return this.output;
+ }
+ _parse(obj: object, path: string = ""): string[] {
+ const out = [];
+ const props = Object.keys(obj);
+ const propObj = props.filter(
+ e =>
+ (obj[e] instanceof Array && !this._isSimplySerializable(obj[e][0])) ||
+ !this._isSimplySerializable(obj[e])
+ );
+ const propPrim = props.filter(
+ e =>
+ !(obj[e] instanceof Array && !this._isSimplySerializable(obj[e][0])) &&
+ 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 (
+ 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(path + prop));
+ out.push(...this._parse(value[i], `${path}${prop}.`));
+ }
+ } else if (typeof value === "object") {
+ out.push("");
+ out.push(this._header(path + prop));
+ out.push(...this._parse(value, `${path}${prop}.`));
+ }
+ }
+ out.push("");
+ return out;
+ }
+ _isSimplySerializable(value: unknown): boolean {
+ return (
+ typeof value === "string" ||
+ typeof value === "number" ||
+ value instanceof RegExp ||
+ value instanceof Date ||
+ value instanceof Array
+ );
+ }
+ _header(title: string): string {
+ return `[${title}]`;
+ }
+ _headerGroup(title: string): string {
+ return `[[${title}]]`;
+ }
+ _declaration(title: string): string {
+ if (title.length > this.maxPad) {
+ this.maxPad = title.length;
+ }
+ return `${title} = `;
+ }
+ _arrayDeclaration(title: string, value: unknown[]): string {
+ return `${this._declaration(title)}${JSON.stringify(value)}`;
+ }
+ _strDeclaration(title: string, value: string): string {
+ return `${this._declaration(title)}"${value}"`;
+ }
+ _numberDeclaration(title: string, value: number): string {
+ switch (value) {
+ case Infinity:
+ return `${this._declaration(title)}inf`;
+ case -Infinity:
+ return `${this._declaration(title)}-inf`;
+ default:
+ return `${this._declaration(title)}${value}`;
+ }
+ }
+ _dateDeclaration(title: string, value: Date): string {
+ function dtPad(v: string, lPad: number = 2): string {
+ return pad(v, lPad, { char: "0" });
+ }
+ let m = dtPad((value.getUTCMonth() + 1).toString());
+ let 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);
+ const fmtDate = `${value.getUTCFullYear()}-${m}-${d}T${h}:${min}:${s}.${ms}`;
+ return `${this._declaration(title)}${fmtDate}`;
+ }
+ _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], pad(m[1], this.maxPad, { side: "right" })));
+ } 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;
+ }
+}
+
+export function stringify(srcObj: object): string {
+ let out: string[] = [];
+ out = new Dumper(srcObj).dump();
+ return out.join("\n");
+}
+
export function parse(tomlString: string): object {
// File is potentially using EOL CRLF
tomlString = tomlString.replace(/\r\n/g, "\n").replace(/\\\n/g, "\n");
diff --git a/toml/parser_test.ts b/toml/parser_test.ts
index 77bf9dc97..6cafb4b0d 100644
--- a/toml/parser_test.ts
+++ b/toml/parser_test.ts
@@ -1,7 +1,7 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
import { test } from "../testing/mod.ts";
import { assertEquals } from "../testing/asserts.ts";
-import { parseFile } from "./parser.ts";
+import { parseFile, stringify } from "./parser.ts";
import * as path from "../fs/path/mod.ts";
const testFilesDir = path.resolve("toml", "testdata");
@@ -282,3 +282,97 @@ test({
assertEquals(actual, expected);
}
});
+
+test({
+ name: "[TOML] Stringify",
+ fn() {
+ const src = {
+ foo: { bar: "deno" },
+ this: { is: { nested: "denonono" } },
+ arrayObjects: [{ stuff: "in" }, {}, { the: "array" }],
+ deno: "is",
+ not: "[node]",
+ regex: "<ic*s*>",
+ NANI: "何?!",
+ comment: "Comment inside # the comment",
+ int1: 99,
+ int2: 42,
+ int3: 0,
+ int4: -17,
+ int5: 1000,
+ int6: 5349221,
+ int7: 12345,
+ flt1: 1.0,
+ flt2: 3.1415,
+ flt3: -0.01,
+ flt4: 5e22,
+ flt5: 1e6,
+ flt6: -2e-2,
+ flt7: 6.626e-34,
+ odt1: new Date("1979-05-01T07:32:00Z"),
+ odt2: new Date("1979-05-27T00:32:00-07:00"),
+ odt3: new Date("1979-05-27T00:32:00.999999-07:00"),
+ odt4: new Date("1979-05-27 07:32:00Z"),
+ ld1: new Date("1979-05-27"),
+ reg: /foo[bar]/,
+ sf1: Infinity,
+ sf2: Infinity,
+ sf3: -Infinity,
+ sf4: NaN,
+ sf5: NaN,
+ sf6: NaN,
+ data: [["gamma", "delta"], [1, 2]],
+ hosts: ["alpha", "omega"]
+ };
+ const expected = `deno = "is"
+not = "[node]"
+regex = "<ic*s*>"
+NANI = "何?!"
+comment = "Comment inside # the comment"
+int1 = 99
+int2 = 42
+int3 = 0
+int4 = -17
+int5 = 1000
+int6 = 5349221
+int7 = 12345
+flt1 = 1
+flt2 = 3.1415
+flt3 = -0.01
+flt4 = 5e+22
+flt5 = 1000000
+flt6 = -0.02
+flt7 = 6.626e-34
+odt1 = 1979-05-01T07:32:00.000
+odt2 = 1979-05-27T07:32:00.000
+odt3 = 1979-05-27T07:32:00.999
+odt4 = 1979-05-27T07:32:00.000
+ld1 = 1979-05-27T00:00:00.000
+reg = "/foo[bar]/"
+sf1 = inf
+sf2 = inf
+sf3 = -inf
+sf4 = NaN
+sf5 = NaN
+sf6 = NaN
+data = [["gamma","delta"],[1,2]]
+hosts = ["alpha","omega"]
+
+[foo]
+bar = "deno"
+
+[this.is]
+nested = "denonono"
+
+[[arrayObjects]]
+stuff = "in"
+
+[[arrayObjects]]
+
+[[arrayObjects]]
+the = "array"
+`;
+ const actual = stringify(src);
+ assertEquals(actual, expected);
+ }
+});