-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
syml.ts
163 lines (122 loc) Β· 4.64 KB
/
syml.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
import {safeLoad, FAILSAFE_SCHEMA} from 'js-yaml';
import {parse} from './grammars/syml';
const simpleStringPattern = /^(?![-?:,\][{}#&*!|>'"%@` \t\r\n]).([ \t]*(?![,\][{}:# \t\r\n]).)*$/;
// The following keys will always be stored at the top of the object, in the
// specified order. It's not fair but life isn't fair either.
const specialObjectKeys = [`__metadata`, `version`, `resolution`, `dependencies`, `peerDependencies`, `dependenciesMeta`, `peerDependenciesMeta`, `binaries`];
export class PreserveOrdering {
constructor(public readonly data: any) {
}
}
function stringifyString(value: string): string {
if (value.match(simpleStringPattern)) {
return value;
} else {
return JSON.stringify(value);
}
}
function isRemovableField(value: any): boolean {
if (typeof value === `undefined`)
return true;
if (typeof value === `object` && value !== null && !Array.isArray(value))
return Object.keys(value).every(key => isRemovableField(value[key]));
return false;
}
function stringifyValue(value: any, indentLevel: number, newLineIfObject: boolean): string {
if (value === null)
return `null\n`;
if (typeof value === `number` || typeof value === `boolean`)
return `${value.toString()}\n`;
if (typeof value === `string`)
return `${stringifyString(value)}\n`;
if (Array.isArray(value)) {
if (value.length === 0)
return `[]\n`;
const indent = ` `.repeat(indentLevel);
const serialized = value.map(sub => {
return `${indent}- ${stringifyValue(sub, indentLevel + 1, false)}`;
}).join(``);
return `\n${serialized}`;
}
if (typeof value === `object` && value) {
const [data, sort] = value instanceof PreserveOrdering
? [value.data, false]
: [value, true];
const indent = ` `.repeat(indentLevel);
const keys = Object.keys(data);
if (sort) {
keys.sort((a, b) => {
const aIndex = specialObjectKeys.indexOf(a);
const bIndex = specialObjectKeys.indexOf(b);
if (aIndex === -1 && bIndex === -1)
return a < b ? -1 : a > b ? +1 : 0;
if (aIndex !== -1 && bIndex === -1)
return -1;
if (aIndex === -1 && bIndex !== -1)
return +1;
return aIndex - bIndex;
});
}
const fields = keys.filter(key => {
return !isRemovableField(data[key]);
}).map((key, index) => {
const value = data[key];
const stringifiedKey = stringifyString(key);
const stringifiedValue = stringifyValue(value, indentLevel + 1, true);
const recordIndentation = index > 0 || newLineIfObject
? indent
: ``;
// Yaml 1.2 spec says that keys over 1024 characters need to be prefixed with ? and the : goes in a new line
const keyPart = stringifiedKey.length > 1024
? `? ${stringifiedKey}\n${recordIndentation}:`
: `${stringifiedKey}:`;
const valuePart = stringifiedValue.startsWith(`\n`)
? stringifiedValue
: ` ${stringifiedValue}`;
return `${recordIndentation}${keyPart}${valuePart}`;
}).join(indentLevel === 0 ? `\n` : ``) || `\n`;
if (!newLineIfObject) {
return `${fields}`;
} else {
return `\n${fields}`;
}
}
throw new Error(`Unsupported value type (${value})`);
}
export function stringifySyml(value: any) {
try {
const stringified = stringifyValue(value, 0, false);
return stringified !== `\n` ? stringified : ``;
} catch (error) {
if (error.location)
error.message = error.message.replace(/(\.)?$/, ` (line ${error.location.start.line}, column ${error.location.start.column})$1`);
throw error;
}
}
stringifySyml.PreserveOrdering = PreserveOrdering;
function parseViaPeg(source: string) {
if (!source.endsWith(`\n`))
source += `\n`;
return parse(source);
}
const LEGACY_REGEXP = /^(#.*(\r?\n))*?#\s+yarn\s+lockfile\s+v1\r?\n/i;
function parseViaJsYaml(source: string) {
if (LEGACY_REGEXP.test(source))
return parseViaPeg(source);
const value = safeLoad(source, {
schema: FAILSAFE_SCHEMA,
json: true,
});
// Empty files are parsed as `undefined` instead of an empty object
// Empty files with 2 newlines or more are `null` instead
if (value === undefined || value === null)
return {} as {[key: string]: string};
if (typeof value !== `object`)
throw new Error(`Expected an indexed object, got a ${typeof value} instead. Does your file follow Yaml's rules?`);
if (Array.isArray(value))
throw new Error(`Expected an indexed object, got an array instead. Does your file follow Yaml's rules?`);
return value as {[key: string]: string};
}
export function parseSyml(source: string) {
return parseViaJsYaml(source);
}