Skip to content

Commit

Permalink
toml: add Stringify feature (#319)
Browse files Browse the repository at this point in the history
  • Loading branch information
zekth authored and ry committed Apr 5, 2019
1 parent d16072a commit 1e589b9
Show file tree
Hide file tree
Showing 4 changed files with 265 additions and 4 deletions.
6 changes: 3 additions & 3 deletions strings/pad.ts
Expand Up @@ -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 */
Expand Down Expand Up @@ -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);
Expand Down
16 changes: 16 additions & 0 deletions toml/README.md
Expand Up @@ -91,6 +91,8 @@ will output:

## Usage

### Parse

```ts
import { parseFile, parse } from "./parser.ts";

Expand All @@ -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);
```
151 changes: 151 additions & 0 deletions 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;
Expand Down Expand Up @@ -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");
Expand Down
96 changes: 95 additions & 1 deletion 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");

Expand Down Expand Up @@ -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);
}
});

0 comments on commit 1e589b9

Please sign in to comment.