mirror of
https://github.com/denoland/deno.git
synced 2024-11-23 15:16:54 -05:00
toml: add Stringify feature (denoland/deno_std#319)
Original: 1e589b9553
This commit is contained in:
parent
f8f5611350
commit
ae9148752c
4 changed files with 265 additions and 4 deletions
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
```
|
||||
|
|
151
toml/parser.ts
151
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");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue