-
Notifications
You must be signed in to change notification settings - Fork 0
/
mod.ts
181 lines (162 loc) · 4.56 KB
/
mod.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/**
* Types
*/
// deno-lint-ignore no-explicit-any
type typeHandler = (
el: any,
indentLevel?: number,
numSpaces?: number,
) => string;
interface IHandlerMap {
[key: string]: typeHandler;
}
/**
* Checks if the input string is a special string.
*
* This is separated from stringHandler() because both normal values and keys
* of objects need to know whether the string is special, but may handle it in
* different ways.
*/
function isSpecialString(s: string): boolean {
return (
s === "true" ||
s === "false" ||
s === "~" ||
s === "null" ||
s === "undefined" ||
s === "}" ||
s === "]" ||
!isNaN(+s) ||
!isNaN(Date.parse(s)) ||
s.startsWith("-") ||
s.startsWith("{") ||
s.startsWith("[") ||
/^\s/.test(s) ||
/[:,&*#?|<>=!%@`]/.test(s) ||
JSON.stringify(s) != `"${s}"`
);
}
/**
* Removes the trailing white spaces and line terminator characters from each
* line of a string.
*/
function removeTrailingSpaces(input: string): string {
return input.split("\n").map((s: string) => s.trimRight()).join("\n");
}
/**
* Why?
* The JavaScript typeof operator is uninformative e.g. typeof [] === 'object'.
* Therefore, we declare our own typeOf function.
*/
// deno-lint-ignore no-explicit-any
function typeOf(x: any): string {
if (x === null) return "null";
if (x === undefined) return "undefined";
switch (Object.prototype.toString.call(x)) {
case "[object Array]":
return "array";
case "[object Boolean]":
return "boolean";
case "[object Date]":
return "date";
case "[object Function]":
return "function";
case "[object Number]":
return "number";
case "[object Object]":
return "object";
case "[object RegExp]":
return "regexp";
case "[object String]":
return "string";
default:
return "object";
}
}
const handlers: IHandlerMap = {
"undefined": undefinedHandler,
"null": nullHandler,
"number": numberHandler,
"boolean": booleanHandler,
"string": stringHandler,
"function": functionHandler,
"array": arrayHandler,
"object": objectHandler,
};
function undefinedHandler(): string {
return "null";
}
function nullHandler(): string {
return "null";
}
function numberHandler(n: number): string {
return n.toString();
}
function booleanHandler(b: boolean): string {
return b.toString();
}
function stringHandler(s: string): string {
return isSpecialString(s) ? JSON.stringify(s) : s;
}
function functionHandler(): string {
return "[object Function]";
}
// deno-lint-ignore no-explicit-any
function arrayHandler(a: any[], indentLevel = 0, numSpaces = 2): string {
if (a.length === 0) {
return "[]";
}
// deno-lint-ignore no-explicit-any
return a.reduce((output: string, el: any): string => {
const type: string = typeOf(el);
const handler: typeHandler = handlers[type];
if (handler === undefined) {
throw new Error(`Encountered unknown type: ${type}`);
}
const indent: string = " ".repeat(indentLevel * numSpaces);
const gap: string = " ".repeat(numSpaces - 1);
return `${output}\n${indent}-${gap}${
handler(el, indentLevel + 1, numSpaces).trimLeft()
}`;
}, "");
}
function objectHandler(o: object, indentLevel = 0, numSpaces = 2): string {
if (Object.keys(o).length === 0) {
return "{}";
}
return Object.keys(o).reduce(
(output: string, k: string, i: number): string => {
// @ts-ignore: TS7053
// deno-lint-ignore no-explicit-any
const val: any = o[k];
const type: string = typeOf(val);
const handler: typeHandler = handlers[type];
if (handler === undefined) {
throw new Error(`Encountered unknown type: ${type}`);
}
const indent: string = " ".repeat(indentLevel * numSpaces);
const keyString = stringHandler(k);
return `${output}\n${indent}${keyString}: ${
handler(val, indentLevel + 1, numSpaces)
}`;
},
"",
);
}
/**
* Converts a valid JSON string to a YAML string (trailing newline included).
* The function will throw an error when the input string is an invalid JSON,
* or an invalid argument is supplied.
*
* @param s The JSON string to convert to YAML
* @param numSpaces The number of spaces to use for indents. Must be > 1
*/
export function json2yaml(s: string, numSpaces = 2): string {
if (numSpaces < 2) {
throw new Error("Invalid argument: numSpaces has to be > 1");
}
// deno-lint-ignore no-explicit-any
const o: object | any[] = JSON.parse(s);
const yaml = handlers[typeOf(o)](o, 0, numSpaces);
return removeTrailingSpaces(yaml).trimLeft().concat("\n");
}