snowkit 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +144 -2
- package/bin/snowkit.js +19 -0
- package/package.json +45 -4
- package/src/cli.js +177 -0
- package/src/engine/colorkit.js +338 -0
- package/src/engine/cron.js +148 -0
- package/src/engine/devtools-kit.js +1964 -0
- package/src/engine/docpdf-layout.js +1188 -0
- package/src/engine/docpdf-render.js +106 -0
- package/src/engine/html.js +59 -0
- package/src/engine/jsonkit.js +1241 -0
- package/src/engine/md-render.js +596 -0
- package/src/engine/mdpdf.js +113 -0
- package/src/engine/qrkit.js +1205 -0
- package/src/engine/readability.js +100 -0
- package/src/engine/sqlformat.js +425 -0
- package/src/engine/texttools-kit.js +623 -0
- package/src/files.js +132 -0
- package/src/pdf.js +297 -0
- package/src/qr.js +100 -0
- package/src/server.js +50 -0
- package/src/text.js +189 -0
- package/src/tools.js +223 -0
|
@@ -0,0 +1,1964 @@
|
|
|
1
|
+
export const DELIMS = { comma: ',', semicolon: ';', tab: '\t', pipe: '|' };
|
|
2
|
+
export const DELIM_NAMES = { ',': 'commas', ';': 'semicolons', '\t': 'tabs', '|': 'pipes' };
|
|
3
|
+
export const SEPARATORS = { hyphen: '-', space: ' ', dot: '.', underscore: '_', none: '' };
|
|
4
|
+
export const HASHES = ['MD5', 'SHA-1', 'SHA-256', 'SHA-384', 'SHA-512'];
|
|
5
|
+
export const SETS = {
|
|
6
|
+
lower: 'abcdefghijklmnopqrstuvwxyz',
|
|
7
|
+
upper: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
|
8
|
+
digits: '0123456789',
|
|
9
|
+
symbols: '!@#$%^&*()-_=+[]{};:,.?/~'
|
|
10
|
+
};
|
|
11
|
+
export const SIMILAR = new Set('Il1|O0o');
|
|
12
|
+
export const WORDS = [...new Set((
|
|
13
|
+
'able acid acorn actor adapt add admit adult after again agent agree ahead aim air alarm album alert alike alive alley allow alpha amber ample angle ankle answer apple apron arch arena argue arm army aroma arrow art ash aside ask asset atlas atom attic audio aunt autumn avoid awake award away axis ' +
|
|
14
|
+
'baby back bacon badge bag bake baker ball bamboo banana band bank barn barrel base basil basin basket bath beach bead beam bean bear beard beat bed bee beef begin bell belt bench berry best bike bird birth black blade blank blaze blend bless blink block bloom blue blush board boat body boil bold bolt bone bonus book boost boot border boss both bottle bounce bowl box brain brake branch brass brave bread break breeze brick bride brief bright bring brisk broad brook broom brown brush bubble bucket buddy budget build bulb bulk bunch bundle burst bus bush busy butter button buyer buzz ' +
|
|
15
|
+
'cabin cable cactus cage cake calm camel camera camp canal candle candy canoe canvas cape car card care cargo carpet carry cart case cash castle cat catch cause cave cedar cell cello cent cereal chain chair chalk champ chance change chant charm chart chase cheap check cheek cheer cheese chef cherry chess chest chew chick chief child chili chill chin chip choice chord chorus cider cinema circle city civic claim clap class clay clean clear clerk click cliff climb clinic clip clock close cloth cloud club clue coach coast coat cocoa code coffee coin cold color comb comet comic common cook cool copper copy coral cord core corn corner cost cotton couch count couple course court cousin cover cow cozy crab craft crane crate crawl crayon cream credit creek crew crisp crop cross crowd crown crumb crust cube cup curb curl curve cycle ' +
|
|
16
|
+
'daily dairy daisy dance dash data date dawn day deal debate decade deck decor deep deer delay delta denim depth desert design desk detail dial diary dice diet dig dinner dip dish disk diver dock doctor dog doll dolphin dome donkey door dose double dough dove down draft dragon drama draw dream dress drift drill drink drive drop drum dry duck dune dust duty ' +
|
|
17
|
+
'eager eagle early earn earth easel east easy echo edge edit effort egg eight elbow elder elect elm ember empty end energy engine enjoy enter entry equal era essay even event exact exit expert extra eye ' +
|
|
18
|
+
'fabric face fact fade fair fairy faith fall family fancy farm fast favor feast feather feed fence ferry fiber field fig film final find fine finger fire firm first fish fit five fix flag flame flash flat flavor fleet flight flip float flock floor flour flow flower fluid flute fly foam focus fog fold folk food foot force forest fork form fort forum fossil fox frame fresh friend frog front frost fruit fuel fun funny fur ' +
|
|
19
|
+
'gadget gain galaxy game garage garden garlic gate gather gauge gear gecko gem gentle giant gift ginger give glad glass globe glove glow glue goal goat gold golf good goose grab grace grain grand grape graph grass gravel gravy great green grid grill grin grip ground group grove grow guard guess guest guide guitar gulf gum ' +
|
|
20
|
+
'habit hair half hall hammer hand handle happy harbor hard harp harvest hat hatch hawk hay head health heap heart heat hedge height hello helmet help hen herb hero hidden high hike hill hint hobby hockey hold hole holiday hollow home honey hood hook hope horn horse host hotel hour house hover hub hug human humor hurry husky hut ' +
|
|
21
|
+
'ice icon idea igloo image inch index indoor ink inner input insect inside invite iron island item ivory ivy ' +
|
|
22
|
+
'jacket jam jar jazz jeans jelly jewel job jog join joke jolly journey joy judge juice jump jungle junior jury just ' +
|
|
23
|
+
'kale kayak keen keep kettle key kick kind king kiosk kit kite kitten kiwi knee knock knot koala ' +
|
|
24
|
+
'label lace ladder lake lamb lamp land lane laptop large laser latch late laugh lava lawn layer lead leaf lean learn least leave ledge left legal lemon lens level lever lid life lift light lilac lily limb lime limit line linen link lion lip liquid list little live lizard load loaf lobby local lock lodge loft logic long loop lotus loud lounge love loyal lucky lunar lunch ' +
|
|
25
|
+
'machine magic magnet mail main major maker mango manor map maple marble march margin market mask mat match math matter maze meadow meal medal media melon member memo menu merit mesh metal meter middle mild mile milk mill mind mineral mint minute mirror mist mix mobile model modern moment money monkey month moon moose more morning moss motor mount mouse mouth move movie much mud muffin mug mule museum music mustard ' +
|
|
26
|
+
'nail name napkin narrow nation native nature near neat neck needle nest net never new news next nice night noble noise noodle normal north nose note notice novel number nurse nut ' +
|
|
27
|
+
'oak oasis object ocean odd offer office often oil okay old olive omelet onion open opera option orange orbit orchard order organ origin other otter outer oval oven owl owner oxygen oyster ' +
|
|
28
|
+
'pace pack paddle page pail paint pair palace palm pan panda panel paper parade parcel park parrot part party pass past pasta paste patch path patio pause paw peace peach peak peanut pear pearl pebble pedal pencil penny people pepper perch person pet phone photo piano pick picnic pie piece pier pig pigeon pile pillow pilot pine pink pipe pitch pizza place plain plan planet plant plate play plaza plenty plot plum plus pocket poem poet point polar pole polish pond pony pool poppy porch port post pot potato pouch powder power press price pride prime print prize proof proud pulse pump pumpkin pupil puppy purple purse push puzzle ' +
|
|
29
|
+
'quail quart queen query quest quick quiet quilt quite quiz quote ' +
|
|
30
|
+
'rabbit race rack radar radio raft rail rain rainbow raise rake ramp ranch range rapid rare raven raw reach read ready real recipe record red reef relax relay remote rent repair reply rescue rest return rhythm rib ribbon rice rich ride ridge right ring rinse ripple rise river road roast robin robot rock rocket roll roof room root rope rose rotate rough round route royal rubber ruby rug rule ruler run rural rush ' +
|
|
31
|
+
'saddle safe sail salad salmon salt same sample sand sauce save scale scarf scene school science scoop score scout screen scroll sea seal season seat second secret seed select self sell send sense series serve set seven shade shadow shake shape share shark sharp shed sheep shelf shell shield shift shine ship shirt shoe shop shore short shovel show shower shrimp shrub side sign signal silk silver simple sing sink siren sister sit six size skate sketch ski skill skin skirt sky slate sled sleep sleeve slice slide slim slope slow small smart smile smooth snack snail snake snow soap soccer sock sofa soft soil solar solid song soon sort sound soup south space spare spark speak speed spell spend spice spider spin spirit split spoon sport spot spray spring sprout square squid stable stack staff stage stair stamp stand star start state station stay steady steam steel stem step stew stick still stone stool stop store storm story stove straw stream street strong studio study style sugar suit summer sun sunny super supply surf swan sweet swift swim swing switch syrup ' +
|
|
32
|
+
'table tablet taco tail take tale talent talk tall tank tape target task taste taxi tea teach team tell tempo ten tennis tent term test text thank theme thick thin thing three thumb ticket tide tidy tiger tile timber time tiny tip title toast today toe token tomato tone tool tooth top topic torch total touch tour towel tower town toy track trade trail train tray treat tree trend trial tribe trick trip trophy truck true trunk trust truth tulip tuna tune tunnel turkey turn turtle twin twist type ' +
|
|
33
|
+
'umbrella uncle under union unit unite upper urban usage use useful usual ' +
|
|
34
|
+
'valley value van vapor vase vault vector velvet vendor venue verb verse very vest video view villa vine violin visit visual vital vivid voice volume vote voyage ' +
|
|
35
|
+
'wafer wagon waist wait wake walk wall walnut wander warm wash watch water wave wax way wealth weave web wedge week weight well west whale wheat wheel whisk white whole wide width wild willow win wind window wing winter wire wise wish wolf wonder wood wool word work world worm wrap wren wrist write ' +
|
|
36
|
+
'yacht yard yarn year yellow yes yoga yogurt young youth zebra zero zest zigzag zinc zipper zone zoo'
|
|
37
|
+
).split(' '))];
|
|
38
|
+
|
|
39
|
+
export const subtle = typeof crypto === 'object' && crypto.subtle ? crypto.subtle : null;
|
|
40
|
+
|
|
41
|
+
export class Raw {
|
|
42
|
+
constructor(text) {
|
|
43
|
+
this.text = text;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class ParseError extends Error {
|
|
48
|
+
constructor(message, pos) {
|
|
49
|
+
super(message);
|
|
50
|
+
this.pos = pos;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const plural = (n, one, many) => `${n.toLocaleString()} ${n === 1 ? one : many || `${one}s`}`;
|
|
55
|
+
|
|
56
|
+
export const short = (s, max = 40) => (s.length > max ? `${s.slice(0, max - 3)}...` : s);
|
|
57
|
+
|
|
58
|
+
export function sizeLabel(n) {
|
|
59
|
+
if (n < 1024) return plural(n, 'byte');
|
|
60
|
+
if (n < 1048576) return `${(n / 1024).toFixed(1)} KB`;
|
|
61
|
+
if (n < 1073741824) return `${(n / 1048576).toFixed(1)} MB`;
|
|
62
|
+
return `${(n / 1073741824).toFixed(2)} GB`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function lineCount(text) {
|
|
66
|
+
let n = 1;
|
|
67
|
+
for (let k = text.indexOf('\n'); k !== -1; k = text.indexOf('\n', k + 1)) n++;
|
|
68
|
+
return n;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function lineCol(src, pos) {
|
|
72
|
+
let line = 1;
|
|
73
|
+
let start = 0;
|
|
74
|
+
for (let k = src.indexOf('\n'); k !== -1 && k < pos; k = src.indexOf('\n', k + 1)) {
|
|
75
|
+
line++;
|
|
76
|
+
start = k + 1;
|
|
77
|
+
}
|
|
78
|
+
return { line, col: pos - start + 1, start };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function caretAt(src, line, col) {
|
|
82
|
+
const all = src.split('\n');
|
|
83
|
+
let text = (all[line - 1] || '').replace(/\t/g, ' ').replace(/\r$/, '');
|
|
84
|
+
let caret = Math.max(0, col - 1);
|
|
85
|
+
if (text.length > 72) {
|
|
86
|
+
const from = Math.max(0, Math.min(caret - 36, text.length - 72));
|
|
87
|
+
text = `${from > 0 ? '...' : ''}${text.slice(from, from + 72)}`;
|
|
88
|
+
caret = caret - from + (from > 0 ? 3 : 0);
|
|
89
|
+
}
|
|
90
|
+
return `${text}\n${' '.repeat(caret)}^`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function parseJson(input) {
|
|
94
|
+
const src = input;
|
|
95
|
+
const n = src.length;
|
|
96
|
+
let i = 0;
|
|
97
|
+
let depth = 0;
|
|
98
|
+
let maxDepth = 0;
|
|
99
|
+
let keys = 0;
|
|
100
|
+
const dupes = [];
|
|
101
|
+
const big = [];
|
|
102
|
+
const ESC = { '"': '"', '\\': '\\', '/': '/', b: '\b', f: '\f', n: '\n', r: '\r', t: '\t' };
|
|
103
|
+
const isDigit = c => c >= '0' && c <= '9';
|
|
104
|
+
|
|
105
|
+
function fail(message, at) {
|
|
106
|
+
throw new ParseError(message, at === undefined ? i : at);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function describe(at) {
|
|
110
|
+
if (at >= n) return 'the end of the text';
|
|
111
|
+
const ch = src[at];
|
|
112
|
+
if (ch === '\n' || ch === '\r') return 'a line break';
|
|
113
|
+
return `"${ch}"`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function ws() {
|
|
117
|
+
while (i < n) {
|
|
118
|
+
const c = src.charCodeAt(i);
|
|
119
|
+
if (c === 32 || c === 9 || c === 10 || c === 13) i++;
|
|
120
|
+
else break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function enter() {
|
|
125
|
+
depth++;
|
|
126
|
+
if (depth > maxDepth) maxDepth = depth;
|
|
127
|
+
if (depth > 2000) fail('The JSON is nested more than 2,000 levels deep, which is too deep to check here.');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function word() {
|
|
131
|
+
const m = /^[A-Za-z_$][\w$]*/.exec(src.slice(i, i + 40));
|
|
132
|
+
return m ? m[0] : src[i];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function value() {
|
|
136
|
+
ws();
|
|
137
|
+
if (i >= n) fail('The JSON ends too early. A value is missing.');
|
|
138
|
+
const c = src[i];
|
|
139
|
+
if (c === '{') return object();
|
|
140
|
+
if (c === '[') return array();
|
|
141
|
+
if (c === '"') return string();
|
|
142
|
+
if (c === '-' || isDigit(c)) return number();
|
|
143
|
+
if (src.startsWith('true', i)) { i += 4; return true; }
|
|
144
|
+
if (src.startsWith('false', i)) { i += 5; return false; }
|
|
145
|
+
if (src.startsWith('null', i)) { i += 4; return null; }
|
|
146
|
+
if (c === "'") fail('Text must be in double quotes, not single quotes.');
|
|
147
|
+
if (c === '/') fail('Comments are not allowed in JSON.');
|
|
148
|
+
if (c === '}' || c === ']' || c === ',') fail(`Unexpected ${describe(i)}. A value is missing here.`);
|
|
149
|
+
if (c === '.' || c === '+') fail('Numbers must start with a digit or a minus sign, such as 0.5 or -1.');
|
|
150
|
+
if (/[A-Za-z_$]/.test(c)) {
|
|
151
|
+
const w = word();
|
|
152
|
+
if (w === 'True' || w === 'False') fail(`Write ${w.toLowerCase()} in lower case. JSON is case sensitive.`);
|
|
153
|
+
if (w === 'None' || w === 'undefined' || w === 'NULL' || w === 'Null') fail(`"${w}" is not JSON. Use null for an empty value.`);
|
|
154
|
+
if (w === 'NaN' || w === 'Infinity') fail(`${w} is not allowed in JSON. Use null or a number instead.`);
|
|
155
|
+
fail(`Unexpected word "${short(w, 20)}". Text values need double quotes.`);
|
|
156
|
+
}
|
|
157
|
+
fail(`Unexpected ${describe(i)}.`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function object() {
|
|
161
|
+
const start = i;
|
|
162
|
+
enter();
|
|
163
|
+
i++;
|
|
164
|
+
const out = new Map();
|
|
165
|
+
ws();
|
|
166
|
+
if (src[i] === '}') {
|
|
167
|
+
i++;
|
|
168
|
+
depth--;
|
|
169
|
+
return out;
|
|
170
|
+
}
|
|
171
|
+
let comma = -1;
|
|
172
|
+
for (;;) {
|
|
173
|
+
ws();
|
|
174
|
+
if (src[i] !== '"') {
|
|
175
|
+
if (src[i] === '}' && comma >= 0) fail('Trailing commas are not allowed in JSON. Remove the comma before }.', comma);
|
|
176
|
+
if (src[i] === "'") fail('Keys must be in double quotes, not single quotes.');
|
|
177
|
+
if (i >= n) fail('The JSON ends inside an object. A closing } is missing.', start);
|
|
178
|
+
if (/[A-Za-z_$]/.test(src[i])) fail(`The key ${short(word(), 20)} needs double quotes around it.`);
|
|
179
|
+
fail(`Expected a key in double quotes, found ${describe(i)}.`);
|
|
180
|
+
}
|
|
181
|
+
const keyAt = i;
|
|
182
|
+
const key = string();
|
|
183
|
+
keys++;
|
|
184
|
+
if (out.has(key)) dupes.push([key, keyAt]);
|
|
185
|
+
ws();
|
|
186
|
+
if (src[i] !== ':') {
|
|
187
|
+
if (src[i] === '=') fail('Use a colon, not =, between a key and its value.');
|
|
188
|
+
fail(`Expected a colon after the key "${short(key, 30)}", found ${describe(i)}.`);
|
|
189
|
+
}
|
|
190
|
+
i++;
|
|
191
|
+
out.set(key, value());
|
|
192
|
+
ws();
|
|
193
|
+
if (src[i] === ',') {
|
|
194
|
+
comma = i;
|
|
195
|
+
i++;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (src[i] === '}') {
|
|
199
|
+
i++;
|
|
200
|
+
depth--;
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
203
|
+
if (i >= n) fail('The JSON ends inside an object. A closing } is missing.', start);
|
|
204
|
+
if (src[i] === '"') fail('A comma is missing between two entries.');
|
|
205
|
+
if (src[i] === ']') fail('This ] closes an object that was opened with {. Use } instead.');
|
|
206
|
+
fail(`Expected a comma or } after the value, found ${describe(i)}.`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function array() {
|
|
211
|
+
const start = i;
|
|
212
|
+
enter();
|
|
213
|
+
i++;
|
|
214
|
+
const out = [];
|
|
215
|
+
ws();
|
|
216
|
+
if (src[i] === ']') {
|
|
217
|
+
i++;
|
|
218
|
+
depth--;
|
|
219
|
+
return out;
|
|
220
|
+
}
|
|
221
|
+
let comma = -1;
|
|
222
|
+
for (;;) {
|
|
223
|
+
ws();
|
|
224
|
+
if (src[i] === ']' && comma >= 0) fail('Trailing commas are not allowed in JSON. Remove the comma before ].', comma);
|
|
225
|
+
out.push(value());
|
|
226
|
+
ws();
|
|
227
|
+
if (src[i] === ',') {
|
|
228
|
+
comma = i;
|
|
229
|
+
i++;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (src[i] === ']') {
|
|
233
|
+
i++;
|
|
234
|
+
depth--;
|
|
235
|
+
return out;
|
|
236
|
+
}
|
|
237
|
+
if (i >= n) fail('The JSON ends inside a list. A closing ] is missing.', start);
|
|
238
|
+
if (src[i] === '}') fail('This } closes a list that was opened with [. Use ] instead.');
|
|
239
|
+
fail(`Expected a comma or ] after the value, found ${describe(i)}. A comma may be missing.`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function string() {
|
|
244
|
+
const start = i;
|
|
245
|
+
i++;
|
|
246
|
+
let out = '';
|
|
247
|
+
let from = i;
|
|
248
|
+
for (;;) {
|
|
249
|
+
if (i >= n) fail('A text value is not closed. A closing double quote is missing.', start);
|
|
250
|
+
const c = src.charCodeAt(i);
|
|
251
|
+
if (c === 34) {
|
|
252
|
+
out += src.slice(from, i);
|
|
253
|
+
i++;
|
|
254
|
+
return out;
|
|
255
|
+
}
|
|
256
|
+
if (c === 92) {
|
|
257
|
+
out += src.slice(from, i);
|
|
258
|
+
const e = src[i + 1];
|
|
259
|
+
if (e === 'u') {
|
|
260
|
+
const hex = src.slice(i + 2, i + 6);
|
|
261
|
+
if (!/^[0-9a-fA-F]{4}$/.test(hex)) fail('A \\u escape needs exactly four hex digits, such as \\u00e9.');
|
|
262
|
+
out += String.fromCharCode(parseInt(hex, 16));
|
|
263
|
+
i += 6;
|
|
264
|
+
} else if (e !== undefined && Object.prototype.hasOwnProperty.call(ESC, e)) {
|
|
265
|
+
out += ESC[e];
|
|
266
|
+
i += 2;
|
|
267
|
+
} else {
|
|
268
|
+
fail(e === undefined ? 'A text value ends with a backslash.' : `\\${e} is not a valid escape. Write a backslash as \\\\.`);
|
|
269
|
+
}
|
|
270
|
+
from = i;
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (c < 32) fail(c === 10 || c === 13 ? 'A text value runs onto a new line. Write line breaks inside text as \\n.' : 'A text value contains a control character, such as a tab. Write tabs as \\t.');
|
|
274
|
+
i++;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function number() {
|
|
279
|
+
const start = i;
|
|
280
|
+
if (src[i] === '-') i++;
|
|
281
|
+
if (src[i] === '0') {
|
|
282
|
+
i++;
|
|
283
|
+
if (isDigit(src[i] || '')) fail('Numbers cannot have leading zeros, such as 007. Put them in quotes to keep them as text.', start);
|
|
284
|
+
} else if (isDigit(src[i] || '')) {
|
|
285
|
+
while (isDigit(src[i] || '')) i++;
|
|
286
|
+
} else {
|
|
287
|
+
fail('A minus sign must be followed by a digit.');
|
|
288
|
+
}
|
|
289
|
+
if (src[i] === '.') {
|
|
290
|
+
i++;
|
|
291
|
+
if (!isDigit(src[i] || '')) fail('A decimal point must be followed by a digit, such as 1.0.');
|
|
292
|
+
while (isDigit(src[i] || '')) i++;
|
|
293
|
+
}
|
|
294
|
+
if (src[i] === 'e' || src[i] === 'E') {
|
|
295
|
+
i++;
|
|
296
|
+
if (src[i] === '+' || src[i] === '-') i++;
|
|
297
|
+
if (!isDigit(src[i] || '')) fail('An exponent needs at least one digit, such as 1e5.');
|
|
298
|
+
while (isDigit(src[i] || '')) i++;
|
|
299
|
+
}
|
|
300
|
+
const text = src.slice(start, i);
|
|
301
|
+
const v = Number(text);
|
|
302
|
+
const digits = text.replace(/^-/, '').replace(/[eE].*$/, '').replace('.', '').replace(/^0+/, '').length;
|
|
303
|
+
if (!Number.isFinite(v) || (/^-?\d+$/.test(text) && !Number.isSafeInteger(v)) || digits > 17) big.push(text);
|
|
304
|
+
return new Raw(text);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const result = value();
|
|
308
|
+
ws();
|
|
309
|
+
if (i < n) {
|
|
310
|
+
if (src[i] === ',') fail('Unexpected comma after the end of the JSON. Only one top-level value is allowed; wrap several values in [ ].');
|
|
311
|
+
fail(`Unexpected ${describe(i)} after the end of the JSON. Only one top-level value is allowed.`);
|
|
312
|
+
}
|
|
313
|
+
return { value: result, depth: maxDepth, keys, dupes, big };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export function stringify(value, indent, sort) {
|
|
317
|
+
const out = [];
|
|
318
|
+
const nl = indent ? '\n' : '';
|
|
319
|
+
const colon = indent ? ': ' : ':';
|
|
320
|
+
function write(v, pad) {
|
|
321
|
+
if (v === null || v === undefined) out.push('null');
|
|
322
|
+
else if (v instanceof Raw) out.push(v.text);
|
|
323
|
+
else if (typeof v === 'number') out.push(Number.isFinite(v) ? String(v) : 'null');
|
|
324
|
+
else if (typeof v === 'boolean') out.push(v ? 'true' : 'false');
|
|
325
|
+
else if (typeof v === 'string') out.push(JSON.stringify(v));
|
|
326
|
+
else if (Array.isArray(v)) {
|
|
327
|
+
if (!v.length) {
|
|
328
|
+
out.push('[]');
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
const inner = pad + indent;
|
|
332
|
+
out.push('[', nl);
|
|
333
|
+
for (let k = 0; k < v.length; k++) {
|
|
334
|
+
out.push(inner);
|
|
335
|
+
write(v[k], inner);
|
|
336
|
+
out.push(k < v.length - 1 ? ',' : '', nl);
|
|
337
|
+
}
|
|
338
|
+
out.push(pad, ']');
|
|
339
|
+
} else {
|
|
340
|
+
let keys = [...v.keys()];
|
|
341
|
+
if (!keys.length) {
|
|
342
|
+
out.push('{}');
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
if (sort) keys = keys.sort();
|
|
346
|
+
const inner = pad + indent;
|
|
347
|
+
out.push('{', nl);
|
|
348
|
+
keys.forEach((key, k) => {
|
|
349
|
+
out.push(inner, JSON.stringify(key), colon);
|
|
350
|
+
write(v.get(key), inner);
|
|
351
|
+
out.push(k < keys.length - 1 ? ',' : '', nl);
|
|
352
|
+
});
|
|
353
|
+
out.push(pad, '}');
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
write(value, '');
|
|
357
|
+
return out.join('');
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export function kindOf(v) {
|
|
361
|
+
if (v === null) return 'null';
|
|
362
|
+
if (v instanceof Raw || typeof v === 'number') return 'a number';
|
|
363
|
+
if (typeof v === 'boolean') return v ? 'true' : 'false';
|
|
364
|
+
if (typeof v === 'string') return 'a text value';
|
|
365
|
+
if (Array.isArray(v)) return `a list of ${plural(v.length, 'item')}`;
|
|
366
|
+
return `an object with ${plural(v.size, 'key')}`;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export const stripBom = s => (s.charCodeAt(0) === 0xfeff ? s.slice(1) : s);
|
|
370
|
+
|
|
371
|
+
export function sortDeep(v) {
|
|
372
|
+
if (Array.isArray(v)) return v.map(sortDeep);
|
|
373
|
+
if (v instanceof Map) return new Map([...v.keys()].sort().map(k => [k, sortDeep(v.get(k))]));
|
|
374
|
+
return v;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export function cellText(v) {
|
|
378
|
+
if (v === null || v === undefined) return '';
|
|
379
|
+
if (v instanceof Raw) return v.text;
|
|
380
|
+
return String(v);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export function flatten(v, prefix, out, arrays) {
|
|
384
|
+
if (v instanceof Map) {
|
|
385
|
+
if (!v.size) {
|
|
386
|
+
if (!prefix) out.set('value', '{}');
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
for (const [k, x] of v) flatten(x, prefix ? `${prefix}.${k}` : k, out, arrays);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if (Array.isArray(v)) {
|
|
393
|
+
if (arrays === 'json' || (!v.length && !prefix)) {
|
|
394
|
+
out.set(prefix || 'value', stringify(v, ''));
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
v.forEach((x, k) => flatten(x, prefix ? `${prefix}.${k}` : String(k), out, arrays));
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
out.set(prefix || 'value', cellText(v));
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export function tableFromJson(value, arrays) {
|
|
404
|
+
let list = value;
|
|
405
|
+
let note = '';
|
|
406
|
+
if (value instanceof Map) {
|
|
407
|
+
const lists = [...value].filter(([, x]) => Array.isArray(x));
|
|
408
|
+
if (value.size === 1 && lists.length === 1) {
|
|
409
|
+
list = lists[0][1];
|
|
410
|
+
note = `Used the list under "${short(lists[0][0], 30)}".`;
|
|
411
|
+
} else {
|
|
412
|
+
list = [value];
|
|
413
|
+
}
|
|
414
|
+
} else if (!Array.isArray(value)) {
|
|
415
|
+
list = [value];
|
|
416
|
+
}
|
|
417
|
+
const columns = new Map();
|
|
418
|
+
const flat = list.map(row => {
|
|
419
|
+
const out = new Map();
|
|
420
|
+
flatten(row, '', out, arrays);
|
|
421
|
+
for (const k of out.keys()) if (!columns.has(k)) columns.set(k, columns.size);
|
|
422
|
+
return out;
|
|
423
|
+
});
|
|
424
|
+
const header = [...columns.keys()];
|
|
425
|
+
const data = flat.map(row => header.map(h => (row.has(h) ? row.get(h) : '')));
|
|
426
|
+
return { header, data, note };
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
export const PLAIN_NUMBER = /^[+-]?\d+(\.\d+)?([eE][+-]?\d+)?$/;
|
|
430
|
+
|
|
431
|
+
export function csvCell(s, delim, guard) {
|
|
432
|
+
let v = s;
|
|
433
|
+
if (guard && (/^[=@\t\r]/.test(v) || (/^[+-]/.test(v) && !PLAIN_NUMBER.test(v)))) v = `'${v}`;
|
|
434
|
+
if (v.includes(delim) || /["\r\n]/.test(v) || /^\s|\s$/.test(v)) v = `"${v.replace(/"/g, '""')}"`;
|
|
435
|
+
return v;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export function detectDelimiter(text) {
|
|
439
|
+
const cands = [',', ';', '\t', '|'];
|
|
440
|
+
const counts = { ',': [], ';': [], '\t': [], '|': [] };
|
|
441
|
+
let line = { ',': 0, ';': 0, '\t': 0, '|': 0 };
|
|
442
|
+
let has = false;
|
|
443
|
+
let quoted = false;
|
|
444
|
+
let lines = 0;
|
|
445
|
+
for (let k = 0; k < text.length && lines < 40; k++) {
|
|
446
|
+
const c = text[k];
|
|
447
|
+
if (c === '"') {
|
|
448
|
+
quoted = !quoted;
|
|
449
|
+
has = true;
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
if (quoted) continue;
|
|
453
|
+
if (c === '\n' || c === '\r') {
|
|
454
|
+
if (has) {
|
|
455
|
+
for (const d of cands) counts[d].push(line[d]);
|
|
456
|
+
lines++;
|
|
457
|
+
}
|
|
458
|
+
line = { ',': 0, ';': 0, '\t': 0, '|': 0 };
|
|
459
|
+
has = false;
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
has = true;
|
|
463
|
+
if (Object.prototype.hasOwnProperty.call(line, c)) line[c]++;
|
|
464
|
+
}
|
|
465
|
+
if (has && lines < 40) for (const d of cands) counts[d].push(line[d]);
|
|
466
|
+
let best = ',';
|
|
467
|
+
let bestScore = -1;
|
|
468
|
+
for (const d of cands) {
|
|
469
|
+
const list = counts[d];
|
|
470
|
+
if (!list.length || !list[0]) continue;
|
|
471
|
+
const same = list.filter(x => x === list[0]).length / list.length;
|
|
472
|
+
const score = same * 1000 + Math.min(list[0], 999);
|
|
473
|
+
if (score > bestScore) {
|
|
474
|
+
bestScore = score;
|
|
475
|
+
best = d;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return best;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export function parseCsv(text, delim) {
|
|
482
|
+
const out = [];
|
|
483
|
+
let row = [];
|
|
484
|
+
let field = '';
|
|
485
|
+
let quoted = false;
|
|
486
|
+
let wasQuoted = false;
|
|
487
|
+
let quoteAt = 0;
|
|
488
|
+
const n = text.length;
|
|
489
|
+
let i = 0;
|
|
490
|
+
while (i < n) {
|
|
491
|
+
const c = text[i];
|
|
492
|
+
if (quoted) {
|
|
493
|
+
if (c === '"') {
|
|
494
|
+
if (text[i + 1] === '"') {
|
|
495
|
+
field += '"';
|
|
496
|
+
i += 2;
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
quoted = false;
|
|
500
|
+
i++;
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
field += c;
|
|
504
|
+
i++;
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
if (c === '"' && field === '' && !wasQuoted) {
|
|
508
|
+
quoted = true;
|
|
509
|
+
wasQuoted = true;
|
|
510
|
+
quoteAt = i;
|
|
511
|
+
i++;
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
if (c === delim) {
|
|
515
|
+
row.push(field);
|
|
516
|
+
field = '';
|
|
517
|
+
wasQuoted = false;
|
|
518
|
+
i++;
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
if (c === '\n' || c === '\r') {
|
|
522
|
+
row.push(field);
|
|
523
|
+
out.push(row);
|
|
524
|
+
row = [];
|
|
525
|
+
field = '';
|
|
526
|
+
wasQuoted = false;
|
|
527
|
+
if (c === '\r' && text[i + 1] === '\n') i++;
|
|
528
|
+
i++;
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
field += c;
|
|
532
|
+
i++;
|
|
533
|
+
}
|
|
534
|
+
if (quoted) throw new ParseError('A quoted cell is never closed. A closing double quote is missing.', quoteAt);
|
|
535
|
+
if (field !== '' || row.length || wasQuoted) {
|
|
536
|
+
row.push(field);
|
|
537
|
+
out.push(row);
|
|
538
|
+
}
|
|
539
|
+
return out.filter(r => !(r.length === 1 && r[0] === ''));
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
export const JSON_NUMBER = /^-?(0|[1-9]\d*)(\.\d+)?([eE][+-]?\d+)?$/;
|
|
543
|
+
|
|
544
|
+
export function typed(s) {
|
|
545
|
+
if (s === '' || s === 'null') return null;
|
|
546
|
+
if (s === 'true') return true;
|
|
547
|
+
if (s === 'false') return false;
|
|
548
|
+
if (JSON_NUMBER.test(s)) {
|
|
549
|
+
const v = Number(s);
|
|
550
|
+
if (!Number.isFinite(v)) return s;
|
|
551
|
+
if (/^-?\d+$/.test(s) && !Number.isSafeInteger(v)) return s;
|
|
552
|
+
return new Raw(s);
|
|
553
|
+
}
|
|
554
|
+
return s;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
export function arrayify(v) {
|
|
558
|
+
if (!(v instanceof Map)) return v;
|
|
559
|
+
const out = new Map();
|
|
560
|
+
for (const [k, x] of v) out.set(k, arrayify(x));
|
|
561
|
+
const keys = [...out.keys()];
|
|
562
|
+
if (keys.length && keys.every((k, idx) => k === String(idx))) return [...out.values()];
|
|
563
|
+
return out;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
export function nest(entries) {
|
|
567
|
+
const root = new Map();
|
|
568
|
+
for (const [key, value] of entries) {
|
|
569
|
+
const parts = key.split('.');
|
|
570
|
+
if (parts.length < 2 || parts.some(p => p === '')) {
|
|
571
|
+
if (!root.has(key)) root.set(key, value);
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
let node = root;
|
|
575
|
+
let ok = true;
|
|
576
|
+
for (let k = 0; k < parts.length - 1; k++) {
|
|
577
|
+
let next = node.get(parts[k]);
|
|
578
|
+
if (next === undefined) {
|
|
579
|
+
next = new Map();
|
|
580
|
+
node.set(parts[k], next);
|
|
581
|
+
} else if (!(next instanceof Map)) {
|
|
582
|
+
ok = false;
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
585
|
+
node = next;
|
|
586
|
+
}
|
|
587
|
+
const last = parts[parts.length - 1];
|
|
588
|
+
if (ok && !node.has(last)) node.set(last, value);
|
|
589
|
+
else root.set(key, value);
|
|
590
|
+
}
|
|
591
|
+
return arrayify(root);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
export function uniqueHeaders(row, width) {
|
|
595
|
+
const seen = new Map();
|
|
596
|
+
const out = [];
|
|
597
|
+
for (let k = 0; k < width; k++) {
|
|
598
|
+
let name = (row[k] === undefined ? '' : row[k]).trim() || `column_${k + 1}`;
|
|
599
|
+
if (seen.has(name)) {
|
|
600
|
+
let m = seen.get(name) + 1;
|
|
601
|
+
while (seen.has(`${name}_${m}`)) m++;
|
|
602
|
+
seen.set(name, m);
|
|
603
|
+
name = `${name}_${m}`;
|
|
604
|
+
}
|
|
605
|
+
seen.set(name, 1);
|
|
606
|
+
out.push(name);
|
|
607
|
+
}
|
|
608
|
+
return out;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
export const YAML_RISKY_START = /^[-?:,[\]{}#&*!|>'"%@`]/;
|
|
612
|
+
|
|
613
|
+
export function yamlNeedsQuotes(s) {
|
|
614
|
+
if (s === '') return true;
|
|
615
|
+
if (YAML_RISKY_START.test(s) || /^\s|\s$/.test(s)) return true;
|
|
616
|
+
if (/: |:$| #|\t|[\u0000-\u001f\u007f-\u009f\u2028\u2029\ufeff]/.test(s)) return true;
|
|
617
|
+
if (/^(?:~|null|true|false|yes|no|on|off|y|n)$/i.test(s)) return true;
|
|
618
|
+
if (/^[-+]?(?:\d[\d_]*(?:\.[\d_]*)?|\.\d[\d_]*)(?:[eE][-+]?\d+)?$/.test(s)) return true;
|
|
619
|
+
if (/^[-+]?(?:0x[\da-fA-F_]+|0o[0-7_]+|0b[01_]+)$/.test(s)) return true;
|
|
620
|
+
if (/^[-+]?\.(?:inf|nan)$/i.test(s)) return true;
|
|
621
|
+
if (/^\d{4}-\d\d?-\d\d?(?:[Tt\s]|$)/.test(s)) return true;
|
|
622
|
+
if (/^[-+]?\d+(?::[0-5]?\d)+(?:\.\d*)?$/.test(s)) return true;
|
|
623
|
+
if (s === '<<' || s === '=') return true;
|
|
624
|
+
return false;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
export function yamlQuote(s) {
|
|
628
|
+
return JSON.stringify(s).replace(/[\u007f-\u009f\u2028\u2029\ufeff]/g, c => `\\u${c.charCodeAt(0).toString(16).padStart(4, '0')}`);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
export function yamlLiteral(s) {
|
|
632
|
+
if (!s.includes('\n') || s.length > 100000) return null;
|
|
633
|
+
if (/[\u0000-\u0008\u000b-\u001f\u007f-\u009f\u2028\u2029\ufeff]/.test(s)) return null;
|
|
634
|
+
const lines = s.split('\n');
|
|
635
|
+
const firstText = lines.findIndex(l => l.trim() !== '');
|
|
636
|
+
if (firstText === -1) return null;
|
|
637
|
+
if (/^[ \t]/.test(lines[firstText])) return null;
|
|
638
|
+
if (lines.slice(0, firstText).some(l => l !== '')) return null;
|
|
639
|
+
let chomp = '';
|
|
640
|
+
let body = s;
|
|
641
|
+
if (!s.endsWith('\n')) chomp = '-';
|
|
642
|
+
else if (s.endsWith('\n\n')) {
|
|
643
|
+
chomp = '+';
|
|
644
|
+
body = s.slice(0, -1);
|
|
645
|
+
} else body = s.slice(0, -1);
|
|
646
|
+
return { header: `|${chomp}`, lines: body.split('\n') };
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
export function yamlScalar(v) {
|
|
650
|
+
if (v === null || v === undefined) return 'null';
|
|
651
|
+
if (v instanceof Raw) return v.text;
|
|
652
|
+
if (typeof v === 'number') return Number.isFinite(v) ? String(v) : '.nan';
|
|
653
|
+
if (typeof v === 'boolean') return v ? 'true' : 'false';
|
|
654
|
+
return yamlNeedsQuotes(v) ? yamlQuote(v) : v;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
export const yamlKey = k => (yamlNeedsQuotes(k) || k.includes('\n') ? yamlQuote(k) : k);
|
|
658
|
+
|
|
659
|
+
export function toYaml(value) {
|
|
660
|
+
const out = [];
|
|
661
|
+
const isFull = v => (Array.isArray(v) && v.length > 0) || (v instanceof Map && v.size > 0);
|
|
662
|
+
const inline = v => (Array.isArray(v) ? '[]' : v instanceof Map ? '{}' : yamlScalar(v));
|
|
663
|
+
function scalarLines(prefix, v, pad) {
|
|
664
|
+
const lit = typeof v === 'string' ? yamlLiteral(v) : null;
|
|
665
|
+
if (!lit) {
|
|
666
|
+
out.push(`${prefix}${inline(v)}`);
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
out.push(`${prefix}${lit.header}`);
|
|
670
|
+
for (const line of lit.lines) out.push(line === '' ? '' : `${pad}${line}`);
|
|
671
|
+
}
|
|
672
|
+
function block(v, pad) {
|
|
673
|
+
if (Array.isArray(v)) {
|
|
674
|
+
for (const item of v) {
|
|
675
|
+
if (isFull(item)) {
|
|
676
|
+
const start = out.length;
|
|
677
|
+
block(item, `${pad} `);
|
|
678
|
+
out[start] = `${pad}- ${out[start].slice(pad.length + 2)}`;
|
|
679
|
+
} else scalarLines(`${pad}- `, item, `${pad} `);
|
|
680
|
+
}
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
for (const [k, x] of v) {
|
|
684
|
+
const key = yamlKey(k);
|
|
685
|
+
if (isFull(x)) {
|
|
686
|
+
out.push(`${pad}${key}:`);
|
|
687
|
+
block(x, `${pad} `);
|
|
688
|
+
} else scalarLines(`${pad}${key}: `, x, `${pad} `);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
if (isFull(value)) block(value, '');
|
|
692
|
+
else scalarLines('', value, ' ');
|
|
693
|
+
return `${out.join('\n')}\n`;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
export class YamlError extends Error {
|
|
697
|
+
constructor(message, line, col) {
|
|
698
|
+
super(message);
|
|
699
|
+
this.line = line;
|
|
700
|
+
this.col = col;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
export function parseYaml(input) {
|
|
705
|
+
let src = stripBom(input).replace(/\r\n?/g, '\n');
|
|
706
|
+
const raw = src.split('\n');
|
|
707
|
+
if (src.endsWith('\n')) raw.pop();
|
|
708
|
+
src = raw.join('\n');
|
|
709
|
+
const starts = [];
|
|
710
|
+
let acc = 0;
|
|
711
|
+
for (const line of raw) {
|
|
712
|
+
starts.push(acc);
|
|
713
|
+
acc += line.length + 1;
|
|
714
|
+
}
|
|
715
|
+
const warnings = new Set();
|
|
716
|
+
let anchors = new Map();
|
|
717
|
+
let L = [];
|
|
718
|
+
let p = 0;
|
|
719
|
+
let limit = 0;
|
|
720
|
+
|
|
721
|
+
const fail = (message, num, col) => {
|
|
722
|
+
throw new YamlError(message, num + 1, (col || 0) + 1);
|
|
723
|
+
};
|
|
724
|
+
const content = line => line.raw.slice(line.col);
|
|
725
|
+
const blankText = t => t.trim() === '' || /^\s*#/.test(t);
|
|
726
|
+
const isBlank = line => blankText(content(line));
|
|
727
|
+
const isDash = c => c === '-' || c.startsWith('- ') || c.startsWith('-\t');
|
|
728
|
+
|
|
729
|
+
function where(offset) {
|
|
730
|
+
let lo = 0;
|
|
731
|
+
let hi = starts.length - 1;
|
|
732
|
+
while (lo < hi) {
|
|
733
|
+
const mid = (lo + hi + 1) >> 1;
|
|
734
|
+
if (starts[mid] <= offset) lo = mid;
|
|
735
|
+
else hi = mid - 1;
|
|
736
|
+
}
|
|
737
|
+
return { num: lo, col: offset - starts[lo] };
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function failAt(message, offset) {
|
|
741
|
+
const w = where(offset);
|
|
742
|
+
fail(message, w.num, w.col);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function skip() {
|
|
746
|
+
while (p < L.length && isBlank(L[p])) p++;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function resolve(s) {
|
|
750
|
+
if (s === '' || s === '~' || s === 'null' || s === 'Null' || s === 'NULL') return null;
|
|
751
|
+
if (s === 'true' || s === 'True' || s === 'TRUE') return true;
|
|
752
|
+
if (s === 'false' || s === 'False' || s === 'FALSE') return false;
|
|
753
|
+
if (/^[-+]?[0-9]+$/.test(s)) {
|
|
754
|
+
const neg = s[0] === '-';
|
|
755
|
+
const digits = s.replace(/^[-+]/, '').replace(/^0+(?=\d)/, '');
|
|
756
|
+
return new Raw(`${neg ? '-' : ''}${digits}`);
|
|
757
|
+
}
|
|
758
|
+
if (/^0o[0-7]+$/.test(s)) return new Raw(BigInt(`0o${s.slice(2)}`).toString());
|
|
759
|
+
if (/^0x[0-9a-fA-F]+$/.test(s)) return new Raw(BigInt(s).toString());
|
|
760
|
+
if (/^[-+]?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)(?:[eE][-+]?[0-9]+)?$/.test(s)) {
|
|
761
|
+
const v = Number(s);
|
|
762
|
+
if (!Number.isFinite(v)) {
|
|
763
|
+
warnings.add(`The number ${short(s, 24)} is too large for JSON and became null.`);
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
766
|
+
const t = s.replace(/^\+/, '');
|
|
767
|
+
return new Raw(JSON_NUMBER.test(t) ? t : String(v));
|
|
768
|
+
}
|
|
769
|
+
if (/^[-+]?\.(?:inf|Inf|INF)$/.test(s) || /^\.(?:nan|NaN|NAN)$/.test(s)) {
|
|
770
|
+
warnings.add(`JSON has no way to write ${s}, so it became null.`);
|
|
771
|
+
return null;
|
|
772
|
+
}
|
|
773
|
+
if (/^(?:yes|no|on|off|y|n)$/i.test(s)) warnings.add(`"${s}" is read as text, as YAML 1.2 does. Older YAML 1.1 tools read yes, no, on and off as true and false, so quote such values to be clear.`);
|
|
774
|
+
return s;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
function applyTag(v, tag, plainText) {
|
|
778
|
+
if (!tag) return v;
|
|
779
|
+
if (tag === '!!str') return plainText !== undefined ? plainText : v === null ? '' : typeof v === 'string' ? v : v instanceof Raw ? v.text : String(v);
|
|
780
|
+
if (tag === '!!int' || tag === '!!float') {
|
|
781
|
+
const t = plainText !== undefined ? plainText : v;
|
|
782
|
+
if (typeof t === 'string') {
|
|
783
|
+
const r = resolve(t.trim());
|
|
784
|
+
if (r instanceof Raw) return r;
|
|
785
|
+
}
|
|
786
|
+
return v;
|
|
787
|
+
}
|
|
788
|
+
if (tag === '!!bool') {
|
|
789
|
+
const t = String(plainText !== undefined ? plainText : v).toLowerCase();
|
|
790
|
+
if (t === 'true') return true;
|
|
791
|
+
if (t === 'false') return false;
|
|
792
|
+
return v;
|
|
793
|
+
}
|
|
794
|
+
if (tag === '!!null') return null;
|
|
795
|
+
if (!/^!!(map|seq|binary|timestamp|omap|set|pairs)$/.test(tag)) warnings.add(`Tags such as ${short(tag, 20)} have no meaning in JSON, so they were dropped and the values kept.`);
|
|
796
|
+
return v;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function readProps(line) {
|
|
800
|
+
let c = content(line);
|
|
801
|
+
let col = line.col;
|
|
802
|
+
let anchor = '';
|
|
803
|
+
let tag = '';
|
|
804
|
+
for (let k = 0; k < 2; k++) {
|
|
805
|
+
let m = /^&([^\s,[\]{}]+)[ \t]*/.exec(c);
|
|
806
|
+
if (m && !anchor) {
|
|
807
|
+
anchor = m[1];
|
|
808
|
+
} else {
|
|
809
|
+
m = /^(!(?:<[^>]*>|[^\s,[\]{}]*))[ \t]*/.exec(c);
|
|
810
|
+
if (m && !tag) tag = m[1];
|
|
811
|
+
else break;
|
|
812
|
+
}
|
|
813
|
+
col += m[0].length;
|
|
814
|
+
c = c.slice(m[0].length);
|
|
815
|
+
}
|
|
816
|
+
return { anchor, tag, col, has: Boolean(anchor || tag), rest: c };
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
function remember(props, v) {
|
|
820
|
+
if (props && props.anchor) anchors.set(props.anchor, v);
|
|
821
|
+
return v;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
function keyAt(line) {
|
|
825
|
+
const c = content(line);
|
|
826
|
+
if (!c || isDash(c)) return null;
|
|
827
|
+
let key;
|
|
828
|
+
let end;
|
|
829
|
+
let quoted = false;
|
|
830
|
+
if (c[0] === '"' || c[0] === "'") {
|
|
831
|
+
const q = c[0];
|
|
832
|
+
let k = 1;
|
|
833
|
+
let text = '';
|
|
834
|
+
for (;;) {
|
|
835
|
+
if (k >= c.length) return null;
|
|
836
|
+
if (q === "'" && c[k] === "'") {
|
|
837
|
+
if (c[k + 1] === "'") {
|
|
838
|
+
text += "'";
|
|
839
|
+
k += 2;
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
break;
|
|
843
|
+
}
|
|
844
|
+
if (q === '"' && c[k] === '\\') {
|
|
845
|
+
k += 2;
|
|
846
|
+
continue;
|
|
847
|
+
}
|
|
848
|
+
if (q === '"' && c[k] === '"') break;
|
|
849
|
+
text += c[k];
|
|
850
|
+
k++;
|
|
851
|
+
}
|
|
852
|
+
end = k + 1;
|
|
853
|
+
const after = c.slice(end).match(/^[ \t]*/)[0].length;
|
|
854
|
+
if (c[end + after] !== ':' || !(end + after + 1 === c.length || /[ \t]/.test(c[end + after + 1]))) return null;
|
|
855
|
+
key = q === '"' ? decodeDouble(c.slice(0, end), line) : text;
|
|
856
|
+
end += after;
|
|
857
|
+
quoted = true;
|
|
858
|
+
} else {
|
|
859
|
+
if (/^[[{&*!|>%@`#?]/.test(c)) return null;
|
|
860
|
+
let found = -1;
|
|
861
|
+
for (let k = 0; k < c.length; k++) {
|
|
862
|
+
if (c[k] === ':' && (k + 1 === c.length || c[k + 1] === ' ' || c[k + 1] === '\t')) {
|
|
863
|
+
found = k;
|
|
864
|
+
break;
|
|
865
|
+
}
|
|
866
|
+
if (c[k] === '#' && k > 0 && /[ \t]/.test(c[k - 1])) return null;
|
|
867
|
+
}
|
|
868
|
+
if (found <= 0) return null;
|
|
869
|
+
key = c.slice(0, found).replace(/[ \t]+$/, '');
|
|
870
|
+
end = found;
|
|
871
|
+
}
|
|
872
|
+
const rest = c.slice(end + 1);
|
|
873
|
+
const spaces = rest.match(/^[ \t]*/)[0].length;
|
|
874
|
+
return { key, quoted, valueCol: line.col + end + 1 + spaces };
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
function decodeDouble(text, line) {
|
|
878
|
+
const r = readDouble(starts[line.num] + line.col, starts[line.num] + line.col + text.length);
|
|
879
|
+
return r.value;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
function readDouble(offset, hardEnd) {
|
|
883
|
+
const stop = hardEnd === undefined ? limit : hardEnd;
|
|
884
|
+
let i = offset + 1;
|
|
885
|
+
let out = '';
|
|
886
|
+
let keep = 0;
|
|
887
|
+
const ESC = { '0': '\0', a: '\x07', b: '\b', t: '\t', '\t': '\t', n: '\n', v: '\v', f: '\f', r: '\r', e: '\x1b', ' ': ' ', '"': '"', '/': '/', '\\': '\\', N: '\u0085', _: '\u00a0', L: '\u2028', P: '\u2029' };
|
|
888
|
+
for (;;) {
|
|
889
|
+
if (i >= stop) failAt('A double-quoted value is never closed. A closing " is missing.', offset);
|
|
890
|
+
const c = src[i];
|
|
891
|
+
if (c === '"') return { value: out, end: i + 1 };
|
|
892
|
+
if (c === '\\') {
|
|
893
|
+
const e = src[i + 1];
|
|
894
|
+
if (e === '\n') {
|
|
895
|
+
i += 2;
|
|
896
|
+
while (src[i] === ' ' || src[i] === '\t') i++;
|
|
897
|
+
keep = out.length;
|
|
898
|
+
continue;
|
|
899
|
+
}
|
|
900
|
+
const len = e === 'x' ? 2 : e === 'u' ? 4 : e === 'U' ? 8 : 0;
|
|
901
|
+
if (len) {
|
|
902
|
+
const hex = src.slice(i + 2, i + 2 + len);
|
|
903
|
+
if (!new RegExp(`^[0-9a-fA-F]{${len}}$`).test(hex)) failAt(`\\${e} needs ${len} hex digits.`, i);
|
|
904
|
+
const code = parseInt(hex, 16);
|
|
905
|
+
if (code > 0x10ffff) failAt('That escape is beyond the last Unicode character.', i);
|
|
906
|
+
out += String.fromCodePoint(code);
|
|
907
|
+
i += 2 + len;
|
|
908
|
+
} else if (e !== undefined && Object.prototype.hasOwnProperty.call(ESC, e)) {
|
|
909
|
+
out += ESC[e];
|
|
910
|
+
i += 2;
|
|
911
|
+
} else {
|
|
912
|
+
failAt(`\\${e === undefined ? '' : e} is not a valid escape in a double-quoted value.`, i);
|
|
913
|
+
}
|
|
914
|
+
keep = out.length;
|
|
915
|
+
continue;
|
|
916
|
+
}
|
|
917
|
+
if (c === '\n') {
|
|
918
|
+
let t = out.length;
|
|
919
|
+
while (t > keep && (out[t - 1] === ' ' || out[t - 1] === '\t')) t--;
|
|
920
|
+
out = out.slice(0, t);
|
|
921
|
+
let breaks = 0;
|
|
922
|
+
i++;
|
|
923
|
+
for (;;) {
|
|
924
|
+
while (src[i] === ' ' || src[i] === '\t') i++;
|
|
925
|
+
if (src[i] === '\n' && i < stop) {
|
|
926
|
+
breaks++;
|
|
927
|
+
i++;
|
|
928
|
+
continue;
|
|
929
|
+
}
|
|
930
|
+
break;
|
|
931
|
+
}
|
|
932
|
+
out += breaks ? '\n'.repeat(breaks) : ' ';
|
|
933
|
+
keep = out.length;
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
936
|
+
out += c;
|
|
937
|
+
i++;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
function readSingle(offset) {
|
|
942
|
+
let i = offset + 1;
|
|
943
|
+
let out = '';
|
|
944
|
+
for (;;) {
|
|
945
|
+
if (i >= limit) failAt("A single-quoted value is never closed. A closing ' is missing.", offset);
|
|
946
|
+
const c = src[i];
|
|
947
|
+
if (c === "'") {
|
|
948
|
+
if (src[i + 1] === "'") {
|
|
949
|
+
out += "'";
|
|
950
|
+
i += 2;
|
|
951
|
+
continue;
|
|
952
|
+
}
|
|
953
|
+
return { value: out, end: i + 1 };
|
|
954
|
+
}
|
|
955
|
+
if (c === '\n') {
|
|
956
|
+
out = out.replace(/[ \t]+$/, '');
|
|
957
|
+
let breaks = 0;
|
|
958
|
+
i++;
|
|
959
|
+
for (;;) {
|
|
960
|
+
while (src[i] === ' ' || src[i] === '\t') i++;
|
|
961
|
+
if (src[i] === '\n' && i < limit) {
|
|
962
|
+
breaks++;
|
|
963
|
+
i++;
|
|
964
|
+
continue;
|
|
965
|
+
}
|
|
966
|
+
break;
|
|
967
|
+
}
|
|
968
|
+
out += breaks ? '\n'.repeat(breaks) : ' ';
|
|
969
|
+
continue;
|
|
970
|
+
}
|
|
971
|
+
out += c;
|
|
972
|
+
i++;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
function readFlow(offset) {
|
|
977
|
+
let i = offset;
|
|
978
|
+
const flowStart = offset;
|
|
979
|
+
function ws() {
|
|
980
|
+
for (;;) {
|
|
981
|
+
const c = src[i];
|
|
982
|
+
if (i < limit && (c === ' ' || c === '\t' || c === '\n')) i++;
|
|
983
|
+
else if (i < limit && c === '#' && (i === 0 || /\s/.test(src[i - 1]))) {
|
|
984
|
+
while (i < limit && src[i] !== '\n') i++;
|
|
985
|
+
} else break;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
function plain(asKey) {
|
|
989
|
+
const start = i;
|
|
990
|
+
while (i < limit) {
|
|
991
|
+
const c = src[i];
|
|
992
|
+
if (c === ',' || c === ']' || c === '}' || c === '[' || c === '{') break;
|
|
993
|
+
if (c === ':' && (i + 1 >= limit || /[\s,[\]{}]/.test(src[i + 1]))) break;
|
|
994
|
+
if (c === '#' && /\s/.test(src[i - 1])) break;
|
|
995
|
+
i++;
|
|
996
|
+
}
|
|
997
|
+
const text = src.slice(start, i).replace(/[ \t]*\n[ \t]*/g, ' ').trim();
|
|
998
|
+
if (i === start) failAt(`Unexpected "${src[i] === undefined ? 'end of text' : src[i]}" inside a [ ] or { } collection.`, i);
|
|
999
|
+
return asKey ? text : resolve(text);
|
|
1000
|
+
}
|
|
1001
|
+
function node(asKey) {
|
|
1002
|
+
ws();
|
|
1003
|
+
let anchor = '';
|
|
1004
|
+
let tag = '';
|
|
1005
|
+
for (let k = 0; k < 2; k++) {
|
|
1006
|
+
const rest = src.slice(i, i + 200);
|
|
1007
|
+
let m = /^&([^\s,[\]{}]+)/.exec(rest);
|
|
1008
|
+
if (m) {
|
|
1009
|
+
anchor = m[1];
|
|
1010
|
+
i += m[0].length;
|
|
1011
|
+
ws();
|
|
1012
|
+
continue;
|
|
1013
|
+
}
|
|
1014
|
+
m = /^!(?:<[^>]*>|[^\s,[\]{}]*)/.exec(rest);
|
|
1015
|
+
if (m) {
|
|
1016
|
+
tag = m[0];
|
|
1017
|
+
i += m[0].length;
|
|
1018
|
+
ws();
|
|
1019
|
+
continue;
|
|
1020
|
+
}
|
|
1021
|
+
break;
|
|
1022
|
+
}
|
|
1023
|
+
const c = src[i];
|
|
1024
|
+
let v;
|
|
1025
|
+
let text;
|
|
1026
|
+
if (c === '[') v = seq();
|
|
1027
|
+
else if (c === '{') v = map();
|
|
1028
|
+
else if (c === '"') {
|
|
1029
|
+
const r = readDouble(i);
|
|
1030
|
+
v = r.value;
|
|
1031
|
+
i = r.end;
|
|
1032
|
+
} else if (c === "'") {
|
|
1033
|
+
const r = readSingle(i);
|
|
1034
|
+
v = r.value;
|
|
1035
|
+
i = r.end;
|
|
1036
|
+
} else if (c === '*') {
|
|
1037
|
+
const m = /^\*([^\s,[\]{}]+)/.exec(src.slice(i, i + 200));
|
|
1038
|
+
if (!m || !anchors.has(m[1])) failAt(`The alias *${m ? m[1] : ''} has no matching anchor above it.`, i);
|
|
1039
|
+
i += m[0].length;
|
|
1040
|
+
v = anchors.get(m[1]);
|
|
1041
|
+
} else {
|
|
1042
|
+
text = plain(true);
|
|
1043
|
+
v = asKey ? text : tag ? applyTag(resolve(text), tag, text) : resolve(text);
|
|
1044
|
+
}
|
|
1045
|
+
if (tag && text === undefined) v = applyTag(v, tag);
|
|
1046
|
+
if (asKey && typeof v !== 'string') v = keyText(v);
|
|
1047
|
+
if (anchor) anchors.set(anchor, v);
|
|
1048
|
+
return v;
|
|
1049
|
+
}
|
|
1050
|
+
function seq() {
|
|
1051
|
+
i++;
|
|
1052
|
+
const out = [];
|
|
1053
|
+
for (;;) {
|
|
1054
|
+
ws();
|
|
1055
|
+
if (i >= limit) failAt('A [ list is never closed. A closing ] is missing.', flowStart);
|
|
1056
|
+
if (src[i] === ']') {
|
|
1057
|
+
i++;
|
|
1058
|
+
return out;
|
|
1059
|
+
}
|
|
1060
|
+
if (src[i] === ',') failAt('There is an empty entry in this list.', i);
|
|
1061
|
+
let v = node(false);
|
|
1062
|
+
ws();
|
|
1063
|
+
if (src[i] === ':') {
|
|
1064
|
+
i++;
|
|
1065
|
+
ws();
|
|
1066
|
+
const k = typeof v === 'string' ? v : keyText(v);
|
|
1067
|
+
const val = src[i] === ',' || src[i] === ']' ? null : node(false);
|
|
1068
|
+
v = new Map([[k, val]]);
|
|
1069
|
+
}
|
|
1070
|
+
out.push(v);
|
|
1071
|
+
ws();
|
|
1072
|
+
if (src[i] === ',') {
|
|
1073
|
+
i++;
|
|
1074
|
+
continue;
|
|
1075
|
+
}
|
|
1076
|
+
if (src[i] === ']') {
|
|
1077
|
+
i++;
|
|
1078
|
+
return out;
|
|
1079
|
+
}
|
|
1080
|
+
failAt(i >= limit ? 'A [ list is never closed. A closing ] is missing.' : 'Expected a comma or ] in this list.', i >= limit ? flowStart : i);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
function map() {
|
|
1084
|
+
i++;
|
|
1085
|
+
const out = new Map();
|
|
1086
|
+
for (;;) {
|
|
1087
|
+
ws();
|
|
1088
|
+
if (i >= limit) failAt('A { mapping is never closed. A closing } is missing.', flowStart);
|
|
1089
|
+
if (src[i] === '}') {
|
|
1090
|
+
i++;
|
|
1091
|
+
return out;
|
|
1092
|
+
}
|
|
1093
|
+
if (src[i] === ',') failAt('There is an empty entry in this mapping.', i);
|
|
1094
|
+
const key = node(true);
|
|
1095
|
+
ws();
|
|
1096
|
+
let v = null;
|
|
1097
|
+
if (src[i] === ':') {
|
|
1098
|
+
i++;
|
|
1099
|
+
ws();
|
|
1100
|
+
if (src[i] !== ',' && src[i] !== '}') v = node(false);
|
|
1101
|
+
}
|
|
1102
|
+
if (out.has(key)) warnings.add(`The key "${short(key, 30)}" appears more than once in a mapping. The last value is used.`);
|
|
1103
|
+
out.set(key, v);
|
|
1104
|
+
ws();
|
|
1105
|
+
if (src[i] === ',') {
|
|
1106
|
+
i++;
|
|
1107
|
+
continue;
|
|
1108
|
+
}
|
|
1109
|
+
if (src[i] === '}') {
|
|
1110
|
+
i++;
|
|
1111
|
+
return out;
|
|
1112
|
+
}
|
|
1113
|
+
failAt(i >= limit ? 'A { mapping is never closed. A closing } is missing.' : 'Expected a comma or } in this mapping.', i >= limit ? flowStart : i);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
const value = src[i] === '[' ? seq() : map();
|
|
1117
|
+
return { value, end: i };
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
function keyText(v) {
|
|
1121
|
+
if (v === null) return 'null';
|
|
1122
|
+
if (v instanceof Raw) return v.text;
|
|
1123
|
+
if (typeof v === 'string') return v;
|
|
1124
|
+
if (typeof v === 'boolean') return String(v);
|
|
1125
|
+
return stringify(v, '');
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
function finishLine(end, what) {
|
|
1129
|
+
const w = where(end);
|
|
1130
|
+
const rest = raw[w.num].slice(w.col);
|
|
1131
|
+
if (!blankText(rest)) fail(`Unexpected text after ${what}.`, w.num, w.col + rest.match(/^[ \t]*/)[0].length);
|
|
1132
|
+
p = w.num - L[0].num + 1;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
function blockScalar(line, parentInd) {
|
|
1136
|
+
const c = content(line);
|
|
1137
|
+
const m = /^([|>])([1-9])?([+-])?([1-9])?(?:[ \t]+#.*|[ \t]*)$/.exec(c);
|
|
1138
|
+
if (!m) fail('This block header is not valid. Use | or > with an optional + or - and an optional indent digit.', line.num, line.col);
|
|
1139
|
+
const folded = m[1] === '>';
|
|
1140
|
+
const digit = m[2] || m[4];
|
|
1141
|
+
const chomp = m[3] || '';
|
|
1142
|
+
p++;
|
|
1143
|
+
let indent = digit ? Math.max(parentInd, 0) + Number(digit) : -1;
|
|
1144
|
+
if (indent < 0) {
|
|
1145
|
+
for (let q = p; q < L.length; q++) {
|
|
1146
|
+
if (L[q].raw.trim() === '') continue;
|
|
1147
|
+
indent = L[q].raw.match(/^ */)[0].length;
|
|
1148
|
+
break;
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
const body = [];
|
|
1152
|
+
if (indent > parentInd) {
|
|
1153
|
+
while (p < L.length) {
|
|
1154
|
+
const r = L[p].raw;
|
|
1155
|
+
if (r.trim() === '') {
|
|
1156
|
+
body.push('');
|
|
1157
|
+
p++;
|
|
1158
|
+
continue;
|
|
1159
|
+
}
|
|
1160
|
+
if (r.match(/^ */)[0].length < indent) break;
|
|
1161
|
+
body.push(r.slice(indent));
|
|
1162
|
+
p++;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
let end = body.length;
|
|
1166
|
+
while (end > 0 && body[end - 1] === '') end--;
|
|
1167
|
+
const lines = body.slice(0, end);
|
|
1168
|
+
const trailing = body.length - end;
|
|
1169
|
+
let text;
|
|
1170
|
+
if (!folded) text = lines.join('\n');
|
|
1171
|
+
else {
|
|
1172
|
+
text = '';
|
|
1173
|
+
let prevNormal = false;
|
|
1174
|
+
let pending = 0;
|
|
1175
|
+
let started = false;
|
|
1176
|
+
for (const l of lines) {
|
|
1177
|
+
if (l === '') {
|
|
1178
|
+
pending++;
|
|
1179
|
+
continue;
|
|
1180
|
+
}
|
|
1181
|
+
const normal = !/^[ \t]/.test(l);
|
|
1182
|
+
if (!started) {
|
|
1183
|
+
text += '\n'.repeat(pending) + l;
|
|
1184
|
+
started = true;
|
|
1185
|
+
} else if (normal && prevNormal) text += (pending ? '\n'.repeat(pending) : ' ') + l;
|
|
1186
|
+
else text += '\n'.repeat(pending + 1) + l;
|
|
1187
|
+
pending = 0;
|
|
1188
|
+
prevNormal = normal;
|
|
1189
|
+
}
|
|
1190
|
+
if (!started) text = '\n'.repeat(Math.max(0, pending - 1));
|
|
1191
|
+
}
|
|
1192
|
+
if (chomp === '-') return text;
|
|
1193
|
+
if (chomp === '+') return lines.length ? `${text}\n${'\n'.repeat(trailing)}` : '\n'.repeat(trailing);
|
|
1194
|
+
return lines.length ? `${text}\n` : '';
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
function inline(line, parentInd, props) {
|
|
1198
|
+
const c = content(line);
|
|
1199
|
+
const ch = c[0];
|
|
1200
|
+
const tag = props ? props.tag : '';
|
|
1201
|
+
if (ch === '|' || ch === '>') return applyTag(blockScalar(line, parentInd), tag);
|
|
1202
|
+
if (ch === '*') {
|
|
1203
|
+
const m = /^\*([^\s,[\]{}]+)/.exec(c);
|
|
1204
|
+
if (!m || !anchors.has(m[1])) fail(`The alias *${m ? m[1] : ''} has no matching anchor above it.`, line.num, line.col);
|
|
1205
|
+
const rest = c.slice(m[0].length);
|
|
1206
|
+
if (!blankText(rest)) fail('Unexpected text after the alias.', line.num, line.col + m[0].length);
|
|
1207
|
+
p++;
|
|
1208
|
+
return anchors.get(m[1]);
|
|
1209
|
+
}
|
|
1210
|
+
if (ch === '"' || ch === "'" || ch === '[' || ch === '{') {
|
|
1211
|
+
const offset = starts[line.num] + line.col;
|
|
1212
|
+
const r = ch === '"' ? readDouble(offset) : ch === "'" ? readSingle(offset) : readFlow(offset);
|
|
1213
|
+
finishLine(r.end, ch === '[' ? 'the closing ]' : ch === '{' ? 'the closing }' : 'the closing quote');
|
|
1214
|
+
return applyTag(r.value, tag);
|
|
1215
|
+
}
|
|
1216
|
+
if (ch === '%' || ch === '@' || ch === '`') fail(`A value cannot start with ${ch} unless it is in quotes.`, line.num, line.col);
|
|
1217
|
+
let text = c;
|
|
1218
|
+
const hashAt = text.search(/[ \t]#/);
|
|
1219
|
+
if (hashAt >= 0) text = text.slice(0, hashAt);
|
|
1220
|
+
text = text.replace(/[ \t]+$/, '');
|
|
1221
|
+
const colon = text.search(/:(?:[ \t]|$)/);
|
|
1222
|
+
if (colon >= 0) fail('A value cannot contain a colon followed by a space unless it is in quotes. If this is meant to be a nested key, put it on its own line, indented.', line.num, line.col + colon);
|
|
1223
|
+
p++;
|
|
1224
|
+
let q = p;
|
|
1225
|
+
let pending = 0;
|
|
1226
|
+
while (q < L.length) {
|
|
1227
|
+
const nx = L[q];
|
|
1228
|
+
const r = nx.raw;
|
|
1229
|
+
if (r.trim() === '') {
|
|
1230
|
+
pending++;
|
|
1231
|
+
q++;
|
|
1232
|
+
continue;
|
|
1233
|
+
}
|
|
1234
|
+
if (nx.ind <= parentInd || /^\s*#/.test(r)) break;
|
|
1235
|
+
if (keyAt(nx)) fail('This line is indented more than expected.', nx.num, nx.col);
|
|
1236
|
+
let more = content(nx);
|
|
1237
|
+
const h = more.search(/[ \t]#/);
|
|
1238
|
+
if (h >= 0) more = more.slice(0, h);
|
|
1239
|
+
more = more.replace(/[ \t]+$/, '');
|
|
1240
|
+
if (/:(?:[ \t]|$)/.test(more)) fail('A value cannot contain a colon followed by a space unless it is in quotes.', nx.num, nx.col);
|
|
1241
|
+
text += pending ? '\n'.repeat(pending) : ' ';
|
|
1242
|
+
text += more;
|
|
1243
|
+
pending = 0;
|
|
1244
|
+
q++;
|
|
1245
|
+
p = q;
|
|
1246
|
+
}
|
|
1247
|
+
return tag ? applyTag(resolve(text), tag, text) : resolve(text);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
function nested(ind) {
|
|
1251
|
+
skip();
|
|
1252
|
+
if (p >= L.length) return null;
|
|
1253
|
+
const line = L[p];
|
|
1254
|
+
if (line.ind > ind) return orNull(block(ind + 1));
|
|
1255
|
+
if (line.ind === ind && isDash(content(line))) return seq(ind);
|
|
1256
|
+
return null;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
const orNull = v => (v === undefined ? null : v);
|
|
1260
|
+
|
|
1261
|
+
function block(min) {
|
|
1262
|
+
skip();
|
|
1263
|
+
if (p >= L.length) return undefined;
|
|
1264
|
+
const line = L[p];
|
|
1265
|
+
if (line.ind < min) return undefined;
|
|
1266
|
+
const c = content(line);
|
|
1267
|
+
if (isDash(c)) return seq(line.ind);
|
|
1268
|
+
if (c === '?' || c.startsWith('? ')) fail('Complex keys that start with ? are not supported here.', line.num, line.col);
|
|
1269
|
+
const props = readProps(line);
|
|
1270
|
+
if (props.has && blankText(props.rest)) {
|
|
1271
|
+
p++;
|
|
1272
|
+
const v = orNull(block(min));
|
|
1273
|
+
return remember(props, props.tag ? applyTag(v, props.tag) : v);
|
|
1274
|
+
}
|
|
1275
|
+
if (keyAt(line)) return map(line.ind);
|
|
1276
|
+
if (props.has) {
|
|
1277
|
+
line.col = props.col;
|
|
1278
|
+
if (keyAt(line)) return remember(props, map(line.ind));
|
|
1279
|
+
}
|
|
1280
|
+
return remember(props, inline(line, min - 1, props));
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
function seq(ind) {
|
|
1284
|
+
const out = [];
|
|
1285
|
+
for (;;) {
|
|
1286
|
+
skip();
|
|
1287
|
+
if (p >= L.length) break;
|
|
1288
|
+
const line = L[p];
|
|
1289
|
+
const c = content(line);
|
|
1290
|
+
if (line.ind !== ind || !isDash(c)) {
|
|
1291
|
+
if (line.ind > ind) fail('This line is indented more than expected.', line.num, line.col);
|
|
1292
|
+
if (line.ind === ind && keyAt(line)) fail('A key cannot follow list items at the same indentation. Check the indentation.', line.num, line.col);
|
|
1293
|
+
break;
|
|
1294
|
+
}
|
|
1295
|
+
const rest = c.slice(1);
|
|
1296
|
+
const spaces = rest.match(/^[ \t]*/)[0].length;
|
|
1297
|
+
if (blankText(rest)) {
|
|
1298
|
+
p++;
|
|
1299
|
+
out.push(orNull(block(ind + 1)));
|
|
1300
|
+
} else {
|
|
1301
|
+
line.col += 1 + spaces;
|
|
1302
|
+
line.ind = line.col;
|
|
1303
|
+
out.push(orNull(block(ind + 1)));
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
return out;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
function map(ind) {
|
|
1310
|
+
const out = new Map();
|
|
1311
|
+
const merges = [];
|
|
1312
|
+
for (;;) {
|
|
1313
|
+
skip();
|
|
1314
|
+
if (p >= L.length) break;
|
|
1315
|
+
const line = L[p];
|
|
1316
|
+
if (line.ind !== ind) {
|
|
1317
|
+
if (line.ind > ind) fail('This line is indented more than expected.', line.num, line.col);
|
|
1318
|
+
break;
|
|
1319
|
+
}
|
|
1320
|
+
const k = keyAt(line);
|
|
1321
|
+
if (!k) {
|
|
1322
|
+
const c = content(line);
|
|
1323
|
+
if (isDash(c)) fail('A list item cannot appear here. Check the indentation.', line.num, line.col);
|
|
1324
|
+
fail('Expected a key followed by a colon and a space.', line.num, line.col);
|
|
1325
|
+
}
|
|
1326
|
+
line.col = k.valueCol;
|
|
1327
|
+
let value;
|
|
1328
|
+
const rest = content(line);
|
|
1329
|
+
if (blankText(rest)) {
|
|
1330
|
+
p++;
|
|
1331
|
+
value = nested(ind);
|
|
1332
|
+
} else {
|
|
1333
|
+
const props = readProps(line);
|
|
1334
|
+
if (props.has && blankText(props.rest)) {
|
|
1335
|
+
p++;
|
|
1336
|
+
value = remember(props, props.tag ? applyTag(nested(ind), props.tag) : nested(ind));
|
|
1337
|
+
} else {
|
|
1338
|
+
if (props.has) line.col = props.col;
|
|
1339
|
+
value = remember(props, inline(line, ind, props));
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
if (k.key === '<<' && !k.quoted) {
|
|
1343
|
+
merges.push([value, line]);
|
|
1344
|
+
continue;
|
|
1345
|
+
}
|
|
1346
|
+
if (out.has(k.key)) warnings.add(`The key "${short(k.key, 30)}" appears more than once in a mapping. The last value is used.`);
|
|
1347
|
+
out.set(k.key, value);
|
|
1348
|
+
}
|
|
1349
|
+
for (const [value, line] of merges) {
|
|
1350
|
+
const sources = Array.isArray(value) ? value : [value];
|
|
1351
|
+
for (const source of sources) {
|
|
1352
|
+
if (!(source instanceof Map)) fail('A << merge key needs a mapping or a list of mappings, usually an alias such as *defaults.', line.num, line.col);
|
|
1353
|
+
for (const [key, v] of source) if (!out.has(key)) out.set(key, v);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
return out;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
const docs = [];
|
|
1360
|
+
let current = [];
|
|
1361
|
+
let explicit = false;
|
|
1362
|
+
const flush = () => {
|
|
1363
|
+
if (explicit || current.some(line => !isBlank(line))) docs.push(current);
|
|
1364
|
+
current = [];
|
|
1365
|
+
};
|
|
1366
|
+
for (let k = 0; k < raw.length; k++) {
|
|
1367
|
+
const r = raw[k];
|
|
1368
|
+
if (/^---(?=\s|$)/.test(r)) {
|
|
1369
|
+
flush();
|
|
1370
|
+
explicit = true;
|
|
1371
|
+
const rest = r.slice(3);
|
|
1372
|
+
const lead = rest.match(/^[ \t]*/)[0].length;
|
|
1373
|
+
if (!blankText(rest)) current.push({ num: k, raw: r, col: 3 + lead, ind: 3 + lead });
|
|
1374
|
+
continue;
|
|
1375
|
+
}
|
|
1376
|
+
if (/^\.\.\.(?=\s|$)/.test(r)) {
|
|
1377
|
+
flush();
|
|
1378
|
+
explicit = false;
|
|
1379
|
+
continue;
|
|
1380
|
+
}
|
|
1381
|
+
if (/^%/.test(r) && !current.some(line => !isBlank(line))) continue;
|
|
1382
|
+
const lead = r.match(/^ */)[0].length;
|
|
1383
|
+
if (r[lead] === '\t' && !blankText(r)) fail('Tabs cannot be used for indentation in YAML. Use spaces.', k, lead);
|
|
1384
|
+
current.push({ num: k, raw: r, col: lead, ind: lead });
|
|
1385
|
+
}
|
|
1386
|
+
flush();
|
|
1387
|
+
|
|
1388
|
+
const values = [];
|
|
1389
|
+
for (const lines of docs) {
|
|
1390
|
+
anchors = new Map();
|
|
1391
|
+
L = lines;
|
|
1392
|
+
p = 0;
|
|
1393
|
+
if (!L.length) {
|
|
1394
|
+
values.push(null);
|
|
1395
|
+
continue;
|
|
1396
|
+
}
|
|
1397
|
+
const last = L[L.length - 1].num;
|
|
1398
|
+
limit = starts[last] + raw[last].length;
|
|
1399
|
+
const v = block(0);
|
|
1400
|
+
skip();
|
|
1401
|
+
if (p < L.length) {
|
|
1402
|
+
const line = L[p];
|
|
1403
|
+
fail(line.ind > 0 ? 'This line is indented more than expected.' : 'Unexpected content. Check the indentation of this line.', line.num, line.col);
|
|
1404
|
+
}
|
|
1405
|
+
values.push(orNull(v));
|
|
1406
|
+
}
|
|
1407
|
+
return { value: values.length > 1 ? values : values.length ? values[0] : null, docs: values.length, warnings: [...warnings] };
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
export function bytesToBase64(bytes) {
|
|
1411
|
+
let s = '';
|
|
1412
|
+
for (let k = 0; k < bytes.length; k += 0x8000) s += String.fromCharCode.apply(null, bytes.subarray(k, k + 0x8000));
|
|
1413
|
+
return btoa(s);
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
export function base64ToBytes(input) {
|
|
1417
|
+
let s = input.trim().replace(/^data:[^,]*;base64,/i, '').replace(/\s+/g, '');
|
|
1418
|
+
if (!s) return new Uint8Array(0);
|
|
1419
|
+
const bad = s.search(/[^A-Za-z0-9+/=_-]/);
|
|
1420
|
+
if (bad >= 0) throw new Error(`"${s[bad]}" is not a Base64 character. Base64 uses only A to Z, a to z, 0 to 9, + and / (or - and _), with = at the end.`);
|
|
1421
|
+
if (/[-_]/.test(s) && /[+/]/.test(s)) throw new Error('The text mixes standard Base64 (+ and /) with URL-safe Base64 (- and _).');
|
|
1422
|
+
s = s.replace(/-/g, '+').replace(/_/g, '/');
|
|
1423
|
+
const body = s.replace(/=+$/, '');
|
|
1424
|
+
if (body.includes('=')) throw new Error('The = padding sign can only appear at the end.');
|
|
1425
|
+
if (body.length % 4 === 1) throw new Error('The length is not valid for Base64: one character is missing or extra.');
|
|
1426
|
+
const bin = atob(body + '='.repeat((4 - (body.length % 4)) % 4));
|
|
1427
|
+
const out = new Uint8Array(bin.length);
|
|
1428
|
+
for (let k = 0; k < bin.length; k++) out[k] = bin.charCodeAt(k);
|
|
1429
|
+
return out;
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
export function sniff(bytes) {
|
|
1433
|
+
const b = k => bytes[k];
|
|
1434
|
+
const ascii = (from, text) => [...text].every((c, k) => bytes[from + k] === c.charCodeAt(0));
|
|
1435
|
+
if (b(0) === 0x89 && ascii(1, 'PNG')) return ['PNG image', 'png', 'image/png'];
|
|
1436
|
+
if (b(0) === 0xff && b(1) === 0xd8 && b(2) === 0xff) return ['JPEG image', 'jpg', 'image/jpeg'];
|
|
1437
|
+
if (ascii(0, 'GIF8')) return ['GIF image', 'gif', 'image/gif'];
|
|
1438
|
+
if (ascii(0, 'RIFF') && ascii(8, 'WEBP')) return ['WebP image', 'webp', 'image/webp'];
|
|
1439
|
+
if (ascii(4, 'ftypavif')) return ['AVIF image', 'avif', 'image/avif'];
|
|
1440
|
+
if (b(0) === 0 && b(1) === 0 && b(2) === 1 && b(3) === 0) return ['icon file', 'ico', 'image/x-icon'];
|
|
1441
|
+
if (ascii(0, '%PDF')) return ['PDF document', 'pdf', 'application/pdf'];
|
|
1442
|
+
if (ascii(0, 'PK') && b(2) === 3 && b(3) === 4) return ['ZIP archive, or a file built on ZIP such as DOCX', 'zip', 'application/zip'];
|
|
1443
|
+
if (b(0) === 0x1f && b(1) === 0x8b) return ['gzip archive', 'gz', 'application/gzip'];
|
|
1444
|
+
if (ascii(0, 'wOF2')) return ['WOFF2 font', 'woff2', 'font/woff2'];
|
|
1445
|
+
if (ascii(0, 'wOFF')) return ['WOFF font', 'woff', 'font/woff'];
|
|
1446
|
+
if (ascii(0, 'ID3') || (b(0) === 0xff && (b(1) & 0xe0) === 0xe0)) return ['MP3 audio', 'mp3', 'audio/mpeg'];
|
|
1447
|
+
if (ascii(4, 'ftyp')) return ['MP4 or QuickTime video', 'mp4', 'video/mp4'];
|
|
1448
|
+
return null;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
export function hexDump(bytes) {
|
|
1452
|
+
const lines = [];
|
|
1453
|
+
const max = Math.min(bytes.length, 4096);
|
|
1454
|
+
for (let k = 0; k < max; k += 16) {
|
|
1455
|
+
const chunk = bytes.subarray(k, Math.min(k + 16, max));
|
|
1456
|
+
const hex = [...chunk].map(x => x.toString(16).padStart(2, '0')).join(' ');
|
|
1457
|
+
const text = [...chunk].map(x => (x >= 32 && x < 127 ? String.fromCharCode(x) : '.')).join('');
|
|
1458
|
+
lines.push(`${k.toString(16).padStart(8, '0')} ${hex.padEnd(47, ' ')} ${text}`);
|
|
1459
|
+
}
|
|
1460
|
+
if (bytes.length > max) lines.push(`... ${sizeLabel(bytes.length - max)} more`);
|
|
1461
|
+
return lines.join('\n');
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
const BASIC_NAMED = { amp: '&', lt: '<', gt: '>', quot: '"', apos: "'", nbsp: '\u00A0' };
|
|
1465
|
+
|
|
1466
|
+
export function decodeEntities(s, namedEntity = name => (Object.prototype.hasOwnProperty.call(BASIC_NAMED, name) ? BASIC_NAMED[name] : null)) {
|
|
1467
|
+
let count = 0;
|
|
1468
|
+
const out = s.replace(/&(?:#(\d{1,8})|#[xX]([0-9a-fA-F]{1,7})|([A-Za-z][A-Za-z0-9]{1,31}));?/g, (m, dec, hex, name) => {
|
|
1469
|
+
if (name) {
|
|
1470
|
+
if (!m.endsWith(';')) return m;
|
|
1471
|
+
const v = namedEntity(name);
|
|
1472
|
+
if (v === null) return m;
|
|
1473
|
+
count++;
|
|
1474
|
+
return v;
|
|
1475
|
+
}
|
|
1476
|
+
const code = dec ? parseInt(dec, 10) : parseInt(hex, 16);
|
|
1477
|
+
count++;
|
|
1478
|
+
if (!code || code > 0x10ffff || (code >= 0xd800 && code <= 0xdfff)) return '\ufffd';
|
|
1479
|
+
return String.fromCodePoint(code);
|
|
1480
|
+
});
|
|
1481
|
+
return { out, count };
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
export function encodeEntities(s, all) {
|
|
1485
|
+
const basic = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
|
|
1486
|
+
let out = '';
|
|
1487
|
+
for (const ch of s) {
|
|
1488
|
+
if (basic[ch]) out += basic[ch];
|
|
1489
|
+
else if (all && ch.codePointAt(0) > 126) out += `&#${ch.codePointAt(0)};`;
|
|
1490
|
+
else out += ch;
|
|
1491
|
+
}
|
|
1492
|
+
return out;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
export function badPercent(s) {
|
|
1496
|
+
const lone = s.search(/%(?![0-9a-fA-F]{2})/);
|
|
1497
|
+
if (lone >= 0) return `A % must be followed by two hex digits, as in %20. The one at position ${lone + 1} is not.`;
|
|
1498
|
+
const re = /(?:%[0-9a-fA-F]{2})+/g;
|
|
1499
|
+
let m;
|
|
1500
|
+
while ((m = re.exec(s))) {
|
|
1501
|
+
try {
|
|
1502
|
+
decodeURIComponent(m[0]);
|
|
1503
|
+
} catch {
|
|
1504
|
+
return `The % codes at position ${m.index + 1} (${short(m[0], 24)}) are not valid UTF-8 text.`;
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
return 'The text has % codes that cannot be decoded.';
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
export function urlParts(text) {
|
|
1511
|
+
let u;
|
|
1512
|
+
try {
|
|
1513
|
+
if (!/^[a-z][a-z0-9+.-]*:\/\/\S+$/i.test(text.trim())) return null;
|
|
1514
|
+
u = new URL(text.trim());
|
|
1515
|
+
} catch {
|
|
1516
|
+
return null;
|
|
1517
|
+
}
|
|
1518
|
+
const list = [
|
|
1519
|
+
{ label: 'Scheme', value: u.protocol.replace(/:$/, '') },
|
|
1520
|
+
{ label: 'Host', value: u.port ? `${u.hostname}:${u.port}` : u.hostname }
|
|
1521
|
+
];
|
|
1522
|
+
if (u.username) list.push({ label: 'User', value: decodeSafe(u.username) });
|
|
1523
|
+
list.push({ label: 'Path', value: decodeSafe(u.pathname) || '/' });
|
|
1524
|
+
for (const [k, v] of u.searchParams) list.push({ label: `Query: ${short(k, 40)}`, value: v || '(empty)' });
|
|
1525
|
+
if (u.hash) list.push({ label: 'Fragment', value: decodeSafe(u.hash.slice(1)) });
|
|
1526
|
+
return list;
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
export function decodeSafe(s) {
|
|
1530
|
+
try {
|
|
1531
|
+
return decodeURIComponent(s);
|
|
1532
|
+
} catch {
|
|
1533
|
+
return s;
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
export const localZone = (() => {
|
|
1538
|
+
try {
|
|
1539
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
|
|
1540
|
+
} catch {
|
|
1541
|
+
return 'UTC';
|
|
1542
|
+
}
|
|
1543
|
+
})();
|
|
1544
|
+
export const zoneOf = z => (z === 'local' ? localZone : z);
|
|
1545
|
+
export const formatters = new Map();
|
|
1546
|
+
|
|
1547
|
+
export function partsIn(ms, zone) {
|
|
1548
|
+
const z = zoneOf(zone);
|
|
1549
|
+
let f = formatters.get(z);
|
|
1550
|
+
if (!f) {
|
|
1551
|
+
f = new Intl.DateTimeFormat('en-US', { timeZone: z, hourCycle: 'h23', era: 'short', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
1552
|
+
formatters.set(z, f);
|
|
1553
|
+
}
|
|
1554
|
+
const p = {};
|
|
1555
|
+
for (const part of f.formatToParts(ms)) p[part.type] = part.value;
|
|
1556
|
+
let y = Number(p.year);
|
|
1557
|
+
if (p.era && /^B/i.test(p.era)) y = 1 - y;
|
|
1558
|
+
return { y, mo: Number(p.month), d: Number(p.day), h: Number(p.hour) % 24, mi: Number(p.minute), s: Number(p.second) };
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
export function utcMs(y, mo, d, h, mi, s, ms) {
|
|
1562
|
+
const t = new Date(Date.UTC(2000, 0, 1, h, mi, s, ms));
|
|
1563
|
+
t.setUTCFullYear(y, mo - 1, d);
|
|
1564
|
+
return t.getTime();
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
export function offsetIn(ms, zone) {
|
|
1568
|
+
if (zone === 'UTC') return 0;
|
|
1569
|
+
const base = Math.floor(ms / 1000) * 1000;
|
|
1570
|
+
const p = partsIn(base, zone);
|
|
1571
|
+
return utcMs(p.y, p.mo, p.d, p.h, p.mi, p.s, 0) - base;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
export function wallToMs(f, zone) {
|
|
1575
|
+
const guess = utcMs(f.y, f.mo, f.d, f.h, f.mi, f.s, f.ms);
|
|
1576
|
+
if (zone === 'UTC') return { ms: guess };
|
|
1577
|
+
const first = offsetIn(guess, zone);
|
|
1578
|
+
const t1 = guess - first;
|
|
1579
|
+
const second = offsetIn(t1, zone);
|
|
1580
|
+
if (second === first) return { ms: t1 };
|
|
1581
|
+
const t2 = guess - second;
|
|
1582
|
+
if (offsetIn(t2, zone) === second) return { ms: Math.min(t1, t2) };
|
|
1583
|
+
return { ms: Math.max(t1, t2), skipped: true };
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
export const pad2 = n => String(n).padStart(2, '0');
|
|
1587
|
+
|
|
1588
|
+
export function yearText(y) {
|
|
1589
|
+
if (y >= 0 && y <= 9999) return String(y).padStart(4, '0');
|
|
1590
|
+
return `${y < 0 ? '-' : '+'}${String(Math.abs(y)).padStart(6, '0')}`;
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
export function offsetText(min) {
|
|
1594
|
+
const sign = min < 0 ? '-' : '+';
|
|
1595
|
+
const a = Math.abs(min);
|
|
1596
|
+
return `${sign}${pad2(Math.floor(a / 60))}:${pad2(a % 60)}`;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
export function isoIn(ms, zone) {
|
|
1600
|
+
const p = partsIn(ms, zone);
|
|
1601
|
+
const frac = ((ms % 1000) + 1000) % 1000;
|
|
1602
|
+
const off = Math.round(offsetIn(ms, zone) / 60000);
|
|
1603
|
+
const tail = zone === 'UTC' ? 'Z' : offsetText(off);
|
|
1604
|
+
return `${yearText(p.y)}-${pad2(p.mo)}-${pad2(p.d)}T${pad2(p.h)}:${pad2(p.mi)}:${pad2(p.s)}${frac ? `.${String(frac).padStart(3, '0')}` : ''}${tail}`;
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
export function wallText(ms, zone) {
|
|
1608
|
+
const p = partsIn(ms, zone);
|
|
1609
|
+
const frac = ((ms % 1000) + 1000) % 1000;
|
|
1610
|
+
return `${yearText(p.y)}-${pad2(p.mo)}-${pad2(p.d)} ${pad2(p.h)}:${pad2(p.mi)}:${pad2(p.s)}${frac ? `.${String(frac).padStart(3, '0')}` : ''}`;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
export const rtf = typeof Intl.RelativeTimeFormat === 'function' ? new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' }) : null;
|
|
1614
|
+
|
|
1615
|
+
export function relative(ms) {
|
|
1616
|
+
const diff = ms - Date.now();
|
|
1617
|
+
const abs = Math.abs(diff);
|
|
1618
|
+
const units = [['year', 31557600000], ['month', 2629800000], ['week', 604800000], ['day', 86400000], ['hour', 3600000], ['minute', 60000], ['second', 1000]];
|
|
1619
|
+
for (const [unit, size] of units) {
|
|
1620
|
+
if (abs >= size || unit === 'second') {
|
|
1621
|
+
const v = Math.round(diff / size);
|
|
1622
|
+
if (rtf) return rtf.format(v, unit);
|
|
1623
|
+
return v < 0 ? `${plural(-v, unit)} ago` : `in ${plural(v, unit)}`;
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
return '';
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
export function isoWeek(y, mo, d) {
|
|
1630
|
+
const t = new Date(utcMs(y, mo, d, 0, 0, 0, 0));
|
|
1631
|
+
const day = t.getUTCDay() || 7;
|
|
1632
|
+
t.setUTCDate(t.getUTCDate() + 4 - day);
|
|
1633
|
+
const yearStart = utcMs(t.getUTCFullYear(), 1, 1, 0, 0, 0, 0);
|
|
1634
|
+
return { week: Math.ceil(((t.getTime() - yearStart) / 86400000 + 1) / 7), year: t.getUTCFullYear() };
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
export const MAX_MS = 8.64e15;
|
|
1638
|
+
|
|
1639
|
+
export function readStamp(text, unitSetting = 'auto') {
|
|
1640
|
+
const t = text.trim().replace(/[_,\s]/g, '');
|
|
1641
|
+
if (!t) return { ms: null };
|
|
1642
|
+
if (!/^[-+]?\d+(\.\d+)?$/.test(t)) return { error: 'A Unix timestamp is a number of seconds or milliseconds, such as 1767225600.' };
|
|
1643
|
+
const value = Number(t);
|
|
1644
|
+
let unit = unitSetting;
|
|
1645
|
+
if (unit === 'auto') {
|
|
1646
|
+
const a = Math.abs(value);
|
|
1647
|
+
unit = a >= 1e17 ? 'ns' : a >= 1e14 ? 'us' : a >= 1e11 ? 'ms' : 's';
|
|
1648
|
+
}
|
|
1649
|
+
const factor = { s: 1000, ms: 1, us: 0.001, ns: 0.000001 }[unit];
|
|
1650
|
+
const ms = Math.round(value * factor * 1000) / 1000;
|
|
1651
|
+
if (Math.abs(ms) > MAX_MS) return { error: 'That moment is outside the range of dates that can be shown, about 270,000 years either side of 1970.' };
|
|
1652
|
+
return { ms: Math.floor(ms), unit };
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
export function readDate(text, zone = 'local') {
|
|
1656
|
+
const t = text.trim();
|
|
1657
|
+
if (!t) return { ms: null };
|
|
1658
|
+
const m = /^([+-]?\d{4,6})-(\d{1,2})-(\d{1,2})(?:[Tt ]+(\d{1,2}):(\d{2})(?::(\d{2})(?:[.,](\d{1,9}))?)?)?\s*(Z|z|UTC|GMT|[+-]\d{2}(?::?\d{2})?)?$/.exec(t);
|
|
1659
|
+
if (m) {
|
|
1660
|
+
const f = { y: Number(m[1]), mo: Number(m[2]), d: Number(m[3]), h: Number(m[4] || 0), mi: Number(m[5] || 0), s: Number(m[6] || 0), ms: m[7] ? Math.floor(Number(`0.${m[7]}`) * 1000) : 0 };
|
|
1661
|
+
if (f.mo < 1 || f.mo > 12 || f.d < 1 || f.d > 31 || f.h > 23 || f.mi > 59 || f.s > 60) return { error: 'That date or time is out of range. Check the month, day, hours and minutes.' };
|
|
1662
|
+
const check = new Date(utcMs(f.y, f.mo, f.d, 0, 0, 0, 0));
|
|
1663
|
+
if (check.getUTCDate() !== f.d) return { error: `That month does not have ${f.d} days.` };
|
|
1664
|
+
if (f.s === 60) f.s = 59;
|
|
1665
|
+
const zoneText = m[8];
|
|
1666
|
+
if (zoneText) {
|
|
1667
|
+
let off = 0;
|
|
1668
|
+
if (!/^(Z|z|UTC|GMT)$/.test(zoneText)) {
|
|
1669
|
+
const z = /^([+-])(\d{2}):?(\d{2})?$/.exec(zoneText);
|
|
1670
|
+
off = (z[1] === '-' ? -1 : 1) * (Number(z[2]) * 60 + Number(z[3] || 0));
|
|
1671
|
+
}
|
|
1672
|
+
return { ms: utcMs(f.y, f.mo, f.d, f.h, f.mi, f.s, f.ms) - off * 60000, fixed: true };
|
|
1673
|
+
}
|
|
1674
|
+
return wallToMs(f, zone);
|
|
1675
|
+
}
|
|
1676
|
+
const parsed = Date.parse(t);
|
|
1677
|
+
if (!Number.isNaN(parsed)) return { ms: parsed, fixed: /(?:GMT|UTC|Z|[+-]\d{2}:?\d{2})\s*$/i.test(t), loose: true };
|
|
1678
|
+
return { error: 'That date was not recognized. Use a format such as 2026-10-20 14:30 or 2026-10-20T14:30:00Z.' };
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
export function zoneLabel(zone) {
|
|
1682
|
+
return zone === 'local' ? `your time zone, ${localZone}` : zone;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
export const hexOf = bytes => [...bytes].map(b => b.toString(16).padStart(2, '0')).join('');
|
|
1686
|
+
|
|
1687
|
+
export let lastV7 = -1;
|
|
1688
|
+
export let v7seq = 0;
|
|
1689
|
+
|
|
1690
|
+
export function uuidBytes(version) {
|
|
1691
|
+
const b = crypto.getRandomValues(new Uint8Array(16));
|
|
1692
|
+
if (version === 'v7') {
|
|
1693
|
+
const now = Date.now();
|
|
1694
|
+
if (now > lastV7) {
|
|
1695
|
+
lastV7 = now;
|
|
1696
|
+
v7seq = ((b[6] & 0x07) << 8) | b[7];
|
|
1697
|
+
} else {
|
|
1698
|
+
v7seq++;
|
|
1699
|
+
if (v7seq > 0xfff) {
|
|
1700
|
+
lastV7++;
|
|
1701
|
+
v7seq = ((b[6] & 0x07) << 8) | b[7];
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
const t = lastV7;
|
|
1705
|
+
b[0] = Math.floor(t / 1099511627776) & 0xff;
|
|
1706
|
+
b[1] = Math.floor(t / 4294967296) & 0xff;
|
|
1707
|
+
b[2] = (t >>> 24) & 0xff;
|
|
1708
|
+
b[3] = (t >>> 16) & 0xff;
|
|
1709
|
+
b[4] = (t >>> 8) & 0xff;
|
|
1710
|
+
b[5] = t & 0xff;
|
|
1711
|
+
b[6] = 0x70 | (v7seq >> 8);
|
|
1712
|
+
b[7] = v7seq & 0xff;
|
|
1713
|
+
} else {
|
|
1714
|
+
b[6] = (b[6] & 0x0f) | 0x40;
|
|
1715
|
+
}
|
|
1716
|
+
b[8] = (b[8] & 0x3f) | 0x80;
|
|
1717
|
+
return b;
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
export function formatUuid(bytes, upper, bare) {
|
|
1721
|
+
const h = hexOf(bytes);
|
|
1722
|
+
let s = bare ? h : `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
|
|
1723
|
+
if (upper) s = s.toUpperCase();
|
|
1724
|
+
return s;
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
export const MD5_K = Array.from({ length: 64 }, (_, k) => Math.floor(Math.abs(Math.sin(k + 1)) * 4294967296) >>> 0);
|
|
1728
|
+
export const MD5_S = [7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21];
|
|
1729
|
+
|
|
1730
|
+
export class Md5 {
|
|
1731
|
+
constructor() {
|
|
1732
|
+
this.h = new Int32Array([0x67452301, 0xefcdab89 | 0, 0x98badcfe | 0, 0x10325476]);
|
|
1733
|
+
this.buf = new Uint8Array(64);
|
|
1734
|
+
this.view = new DataView(this.buf.buffer);
|
|
1735
|
+
this.fill = 0;
|
|
1736
|
+
this.length = 0;
|
|
1737
|
+
this.x = new Int32Array(16);
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
block(view, offset) {
|
|
1741
|
+
const x = this.x;
|
|
1742
|
+
for (let k = 0; k < 16; k++) x[k] = view.getInt32(offset + k * 4, true);
|
|
1743
|
+
let a = this.h[0];
|
|
1744
|
+
let b = this.h[1];
|
|
1745
|
+
let c = this.h[2];
|
|
1746
|
+
let d = this.h[3];
|
|
1747
|
+
for (let k = 0; k < 64; k++) {
|
|
1748
|
+
let f;
|
|
1749
|
+
let g;
|
|
1750
|
+
if (k < 16) {
|
|
1751
|
+
f = (b & c) | (~b & d);
|
|
1752
|
+
g = k;
|
|
1753
|
+
} else if (k < 32) {
|
|
1754
|
+
f = (d & b) | (~d & c);
|
|
1755
|
+
g = (5 * k + 1) & 15;
|
|
1756
|
+
} else if (k < 48) {
|
|
1757
|
+
f = b ^ c ^ d;
|
|
1758
|
+
g = (3 * k + 5) & 15;
|
|
1759
|
+
} else {
|
|
1760
|
+
f = c ^ (b | ~d);
|
|
1761
|
+
g = (7 * k) & 15;
|
|
1762
|
+
}
|
|
1763
|
+
const t = d;
|
|
1764
|
+
d = c;
|
|
1765
|
+
c = b;
|
|
1766
|
+
const sum = (a + f + MD5_K[k] + x[g]) | 0;
|
|
1767
|
+
b = (b + ((sum << MD5_S[k]) | (sum >>> (32 - MD5_S[k])))) | 0;
|
|
1768
|
+
a = t;
|
|
1769
|
+
}
|
|
1770
|
+
this.h[0] = (this.h[0] + a) | 0;
|
|
1771
|
+
this.h[1] = (this.h[1] + b) | 0;
|
|
1772
|
+
this.h[2] = (this.h[2] + c) | 0;
|
|
1773
|
+
this.h[3] = (this.h[3] + d) | 0;
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
update(bytes) {
|
|
1777
|
+
let k = 0;
|
|
1778
|
+
this.length += bytes.length;
|
|
1779
|
+
if (this.fill) {
|
|
1780
|
+
const take = Math.min(64 - this.fill, bytes.length);
|
|
1781
|
+
this.buf.set(bytes.subarray(0, take), this.fill);
|
|
1782
|
+
this.fill += take;
|
|
1783
|
+
k = take;
|
|
1784
|
+
if (this.fill < 64) return;
|
|
1785
|
+
this.block(this.view, 0);
|
|
1786
|
+
this.fill = 0;
|
|
1787
|
+
}
|
|
1788
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
1789
|
+
for (; k + 64 <= bytes.length; k += 64) this.block(view, k);
|
|
1790
|
+
if (k < bytes.length) {
|
|
1791
|
+
this.buf.set(bytes.subarray(k), 0);
|
|
1792
|
+
this.fill = bytes.length - k;
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
digest() {
|
|
1797
|
+
const bits = this.length * 8;
|
|
1798
|
+
const tail = new Uint8Array(((this.fill < 56 ? 56 : 120) - this.fill) + 8);
|
|
1799
|
+
tail[0] = 0x80;
|
|
1800
|
+
const view = new DataView(tail.buffer);
|
|
1801
|
+
view.setUint32(tail.length - 8, bits >>> 0, true);
|
|
1802
|
+
view.setUint32(tail.length - 4, Math.floor(bits / 4294967296), true);
|
|
1803
|
+
const length = this.length;
|
|
1804
|
+
this.update(tail);
|
|
1805
|
+
this.length = length;
|
|
1806
|
+
const out = new Uint8Array(16);
|
|
1807
|
+
const outView = new DataView(out.buffer);
|
|
1808
|
+
for (let k = 0; k < 4; k++) outView.setInt32(k * 4, this.h[k], true);
|
|
1809
|
+
return out;
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
export const pause = () => new Promise(resolve => setTimeout(resolve, 0));
|
|
1814
|
+
|
|
1815
|
+
export async function digestAll(bytes, progress) {
|
|
1816
|
+
const out = new Map();
|
|
1817
|
+
const md5 = new Md5();
|
|
1818
|
+
const CHUNK = 4194304;
|
|
1819
|
+
for (let k = 0; k < bytes.length; k += CHUNK) {
|
|
1820
|
+
md5.update(bytes.subarray(k, k + CHUNK));
|
|
1821
|
+
if (bytes.length > CHUNK) {
|
|
1822
|
+
if (progress) progress(Math.min(1, (k + CHUNK) / bytes.length));
|
|
1823
|
+
await pause();
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
out.set('MD5', md5.digest());
|
|
1827
|
+
if (subtle) {
|
|
1828
|
+
for (const algo of ['SHA-1', 'SHA-256', 'SHA-384', 'SHA-512']) out.set(algo, new Uint8Array(await subtle.digest(algo, bytes)));
|
|
1829
|
+
}
|
|
1830
|
+
return out;
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
export function hashText(bytes, format) {
|
|
1834
|
+
if (format === 'base64') return bytesToBase64(bytes);
|
|
1835
|
+
const h = hexOf(bytes);
|
|
1836
|
+
return format === 'upper' ? h.toUpperCase() : h;
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
export function checksumMatches(bytes, compare) {
|
|
1840
|
+
return Boolean(compare) && (hexOf(bytes) === compare.toLowerCase() || bytesToBase64(bytes).replace(/=+$/, '') === compare.replace(/=+$/, ''));
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
export function passwordPools(o) {
|
|
1844
|
+
const pools = [];
|
|
1845
|
+
for (const name of ['lower', 'upper', 'digits', 'symbols']) {
|
|
1846
|
+
if (!o[name]) continue;
|
|
1847
|
+
const pool = o.similar ? [...SETS[name]].filter(c => !SIMILAR.has(c)).join('') : SETS[name];
|
|
1848
|
+
if (pool) pools.push(pool);
|
|
1849
|
+
}
|
|
1850
|
+
return pools;
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
export function passwordBits(o, pools) {
|
|
1854
|
+
if (o.kind === 'password') return o.length * Math.log2(new Set(pools.join('')).size);
|
|
1855
|
+
return o.words * Math.log2(WORDS.length) + (o.number ? Math.log2(100) + Math.log2(o.words) : 0);
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
export function strengthLevel(bits) {
|
|
1859
|
+
return bits < 45 ? ['Weak', 'is-weak'] : bits < 64 ? ['Fair', 'is-fair'] : bits < 80 ? ['Strong', 'is-strong'] : ['Very strong', 'is-strong'];
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
export function uuidInfo(text) {
|
|
1863
|
+
const m = /^(?:urn:uuid:)?\{?([0-9a-f]{8})-?([0-9a-f]{4})-?([0-9a-f]{4})-?([0-9a-f]{4})-?([0-9a-f]{12})\}?$/i.exec(text);
|
|
1864
|
+
if (!m) return null;
|
|
1865
|
+
const hex = m.slice(1).join('').toLowerCase();
|
|
1866
|
+
const canonical = `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
1867
|
+
if (/^0+$/.test(hex) || /^f+$/.test(hex)) return { canonical, special: hex[0] === '0' ? 'nil' : 'max' };
|
|
1868
|
+
const version = parseInt(hex[12], 16);
|
|
1869
|
+
const variantBits = parseInt(hex[16], 16);
|
|
1870
|
+
const variant = variantBits < 8 ? 'NCS, a pre-standard format' : variantBits < 12 ? 'RFC 9562, the standard variant' : variantBits < 14 ? 'Microsoft GUID, legacy' : 'reserved';
|
|
1871
|
+
const names = { 1: 'time and node', 2: 'DCE security', 3: 'name, MD5', 4: 'random', 5: 'name, SHA-1', 6: 'reordered time', 7: 'Unix time and random', 8: 'custom' };
|
|
1872
|
+
const standard = variantBits >= 8 && variantBits < 12;
|
|
1873
|
+
let ms = null;
|
|
1874
|
+
if (standard) {
|
|
1875
|
+
if (version === 7) ms = parseInt(hex.slice(0, 12), 16);
|
|
1876
|
+
if (version === 1 || version === 6) {
|
|
1877
|
+
const t = version === 1 ? BigInt(`0x${hex.slice(13, 16)}${hex.slice(8, 12)}${hex.slice(0, 8)}`) : BigInt(`0x${hex.slice(0, 12)}${hex.slice(13, 16)}`);
|
|
1878
|
+
ms = Number(t / 10000n) - 12219292800000;
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
return { canonical, special: '', version, versionName: names[version] || '', variant, standard, ms };
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
export function decodeJwtPart(part, name) {
|
|
1885
|
+
let bytes;
|
|
1886
|
+
try {
|
|
1887
|
+
bytes = b64urlBytes(part);
|
|
1888
|
+
} catch {
|
|
1889
|
+
throw new Error(`The ${name} is not valid Base64url.`);
|
|
1890
|
+
}
|
|
1891
|
+
let text;
|
|
1892
|
+
try {
|
|
1893
|
+
text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
1894
|
+
} catch {
|
|
1895
|
+
throw new Error(`The ${name} is not UTF-8 text.`);
|
|
1896
|
+
}
|
|
1897
|
+
try {
|
|
1898
|
+
return { text, value: parseJson(text).value };
|
|
1899
|
+
} catch {
|
|
1900
|
+
return { text, value: null };
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
export function randomInt(n) {
|
|
1905
|
+
const limit = Math.floor(4294967296 / n) * n;
|
|
1906
|
+
const buf = new Uint32Array(1);
|
|
1907
|
+
for (;;) {
|
|
1908
|
+
crypto.getRandomValues(buf);
|
|
1909
|
+
if (buf[0] < limit) return buf[0] % n;
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
export function makePassword(o, pools) {
|
|
1914
|
+
const all = [...new Set(pools.join(''))];
|
|
1915
|
+
const chars = pools.map(pool => pool[randomInt(pool.length)]);
|
|
1916
|
+
while (chars.length < o.length) chars.push(all[randomInt(all.length)]);
|
|
1917
|
+
for (let k = chars.length - 1; k > 0; k--) {
|
|
1918
|
+
const j = randomInt(k + 1);
|
|
1919
|
+
[chars[k], chars[j]] = [chars[j], chars[k]];
|
|
1920
|
+
}
|
|
1921
|
+
return chars.join('');
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
export function makePhrase(o) {
|
|
1925
|
+
const words = [];
|
|
1926
|
+
for (let k = 0; k < o.words; k++) {
|
|
1927
|
+
let w = WORDS[randomInt(WORDS.length)];
|
|
1928
|
+
if (o.caps) w = w[0].toUpperCase() + w.slice(1);
|
|
1929
|
+
words.push(w);
|
|
1930
|
+
}
|
|
1931
|
+
if (o.number) words[randomInt(words.length)] += String(randomInt(100));
|
|
1932
|
+
return words.join(SEPARATORS[o.sep]);
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
export function crackTime(bits) {
|
|
1936
|
+
const seconds = 2 ** (bits - 1) / 1e10;
|
|
1937
|
+
if (seconds < 1) return 'less than a second';
|
|
1938
|
+
const steps = [[60, 'second'], [3600, 'minute'], [86400, 'hour'], [31557600, 'day'], [Infinity, 'year']];
|
|
1939
|
+
let size = 1;
|
|
1940
|
+
for (const [next, unit] of steps) {
|
|
1941
|
+
if (seconds < next) {
|
|
1942
|
+
const v = seconds / size;
|
|
1943
|
+
if (unit === 'year' && v >= 1e12) return 'more than a trillion years';
|
|
1944
|
+
if (unit === 'year' && v >= 1e6) return `about ${Math.round(v / 1e6).toLocaleString()} million years`;
|
|
1945
|
+
return `about ${plural(Math.max(1, Math.round(v)), unit)}`;
|
|
1946
|
+
}
|
|
1947
|
+
size = next;
|
|
1948
|
+
}
|
|
1949
|
+
return '';
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
export function b64urlBytes(part) {
|
|
1953
|
+
if (!/^[A-Za-z0-9_=-]*$/.test(part)) throw new Error('not base64url');
|
|
1954
|
+
return base64ToBytes(part);
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
export const timeClaims = { exp: 'Expires', nbf: 'Not valid before', iat: 'Issued at', auth_time: 'Signed in at', updated_at: 'Updated at' };
|
|
1958
|
+
export const claimNames = { iss: 'Issuer', sub: 'Subject', aud: 'Audience', jti: 'Token ID', scope: 'Scope', azp: 'Authorized party', nonce: 'Nonce' };
|
|
1959
|
+
|
|
1960
|
+
export function regexError(error) {
|
|
1961
|
+
const message = String((error && error.message) || error);
|
|
1962
|
+
const reason = message.includes(': ') ? message.slice(message.lastIndexOf(': ') + 2) : message;
|
|
1963
|
+
return `Not a valid regular expression: ${reason.replace(/\.$/, '')}.`;
|
|
1964
|
+
}
|