@ansi-tools/parser 0.0.0
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/README.md +187 -0
- package/dist/escaped.d.ts +7 -0
- package/dist/escaped.js +203 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +3 -0
- package/dist/parse-BirjVUvQ.d.ts +64 -0
- package/dist/parse-ClmKWMZx.js +485 -0
- package/package.json +40 -0
- package/src/constants.ts +40 -0
- package/src/escaped.ts +4 -0
- package/src/index.ts +4 -0
- package/src/parse.escaped.test.ts +86 -0
- package/src/parse.test.ts +86 -0
- package/src/parse.ts +111 -0
- package/src/parsers/csi.test.ts +55 -0
- package/src/parsers/csi.ts +54 -0
- package/src/parsers/dcs.test.ts +47 -0
- package/src/parsers/dcs.ts +36 -0
- package/src/parsers/dec.test.ts +24 -0
- package/src/parsers/dec.ts +30 -0
- package/src/parsers/esc.test.ts +19 -0
- package/src/parsers/esc.ts +6 -0
- package/src/parsers/osc.test.ts +36 -0
- package/src/parsers/osc.ts +29 -0
- package/src/tokenize.escaped.test.ts +410 -0
- package/src/tokenize.escaped.ts +191 -0
- package/src/tokenize.test.ts +118 -0
- package/src/tokenize.ts +140 -0
- package/src/types.ts +24 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
//#region src/constants.ts
|
|
2
|
+
const BELL = String.fromCharCode(7);
|
|
3
|
+
const ESC = String.fromCharCode(27);
|
|
4
|
+
const BACKSLASH = String.fromCharCode(92);
|
|
5
|
+
const DCS = String.fromCharCode(144);
|
|
6
|
+
const SOS = String.fromCharCode(152);
|
|
7
|
+
const CSI = String.fromCharCode(155);
|
|
8
|
+
const ST = String.fromCharCode(156);
|
|
9
|
+
const OSC = String.fromCharCode(157);
|
|
10
|
+
const PM = String.fromCharCode(158);
|
|
11
|
+
const APC = String.fromCharCode(159);
|
|
12
|
+
const CSI_OPEN = "[";
|
|
13
|
+
const OSC_OPEN = "]";
|
|
14
|
+
const DEC_OPEN = "?";
|
|
15
|
+
const PRIVATE_OPENERS = new Set([
|
|
16
|
+
"<",
|
|
17
|
+
"=",
|
|
18
|
+
">"
|
|
19
|
+
]);
|
|
20
|
+
const DCS_OPEN = "P";
|
|
21
|
+
const APC_OPEN = "_";
|
|
22
|
+
const SOS_OPEN = "^";
|
|
23
|
+
const PM_OPEN = "X";
|
|
24
|
+
const STRING_OPENERS = new Set([
|
|
25
|
+
DCS_OPEN,
|
|
26
|
+
APC_OPEN,
|
|
27
|
+
SOS_OPEN,
|
|
28
|
+
PM_OPEN
|
|
29
|
+
]);
|
|
30
|
+
const TOKEN_TYPES = {
|
|
31
|
+
TEXT: "TEXT",
|
|
32
|
+
INTRODUCER: "INTRODUCER",
|
|
33
|
+
DATA: "DATA",
|
|
34
|
+
FINAL: "FINAL"
|
|
35
|
+
};
|
|
36
|
+
const CODE_TYPES = {
|
|
37
|
+
CSI: "CSI",
|
|
38
|
+
DCS: "DCS",
|
|
39
|
+
DEC: "DEC",
|
|
40
|
+
ESC: "ESC",
|
|
41
|
+
OSC: "OSC",
|
|
42
|
+
PRIVATE: "PRIVATE",
|
|
43
|
+
SGR: "SGR",
|
|
44
|
+
STRING: "STRING",
|
|
45
|
+
TEXT: "TEXT"
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/parsers/csi.ts
|
|
50
|
+
function parseCSI(pos, raw, data, final) {
|
|
51
|
+
const params = [];
|
|
52
|
+
let intermediates = "";
|
|
53
|
+
if (data) {
|
|
54
|
+
let i = 0;
|
|
55
|
+
let paramSection = "";
|
|
56
|
+
while (i < data.length && data.charCodeAt(i) >= 48 && data.charCodeAt(i) <= 63) {
|
|
57
|
+
paramSection += data[i];
|
|
58
|
+
i++;
|
|
59
|
+
}
|
|
60
|
+
intermediates = data.slice(i);
|
|
61
|
+
if (paramSection) {
|
|
62
|
+
let current = "";
|
|
63
|
+
for (let j = 0; j < paramSection.length; j++) if (paramSection[j] === ";") {
|
|
64
|
+
params.push(current || "-1");
|
|
65
|
+
current = "";
|
|
66
|
+
} else current += paramSection[j];
|
|
67
|
+
params.push(current || "-1");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const command = intermediates + final;
|
|
71
|
+
return {
|
|
72
|
+
type: CODE_TYPES.CSI,
|
|
73
|
+
pos,
|
|
74
|
+
raw,
|
|
75
|
+
command,
|
|
76
|
+
params
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function parsePrivateCSI(pos, raw, data, final) {
|
|
80
|
+
const privateIndicator = data[0];
|
|
81
|
+
const withoutIndicator = data.slice(1);
|
|
82
|
+
const match = withoutIndicator.match(/^([\d;]*)(.*)/);
|
|
83
|
+
const paramsRaw = match?.[1] ?? "";
|
|
84
|
+
const intermediates = match?.[2] ?? "";
|
|
85
|
+
const command = `${privateIndicator}${intermediates}${final}`;
|
|
86
|
+
const params = [];
|
|
87
|
+
if (paramsRaw) {
|
|
88
|
+
let current = "";
|
|
89
|
+
for (let i = 0; i < paramsRaw.length; i++) if (paramsRaw[i] === ";") {
|
|
90
|
+
params.push(current || "-1");
|
|
91
|
+
current = "";
|
|
92
|
+
} else current += paramsRaw[i];
|
|
93
|
+
params.push(current || "-1");
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
type: CODE_TYPES.PRIVATE,
|
|
97
|
+
pos,
|
|
98
|
+
raw,
|
|
99
|
+
command,
|
|
100
|
+
params
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region src/parsers/dcs.ts
|
|
106
|
+
const DCS_PATTERNS = new Map([
|
|
107
|
+
["$q", 2],
|
|
108
|
+
["+q", 2],
|
|
109
|
+
["+p", 2],
|
|
110
|
+
["|", 1],
|
|
111
|
+
["{", 1]
|
|
112
|
+
]);
|
|
113
|
+
function parseDCS(pos, raw, data) {
|
|
114
|
+
if (!data) return {
|
|
115
|
+
type: CODE_TYPES.DCS,
|
|
116
|
+
pos,
|
|
117
|
+
raw,
|
|
118
|
+
command: "",
|
|
119
|
+
params: []
|
|
120
|
+
};
|
|
121
|
+
for (const [pattern, length] of DCS_PATTERNS) if (data.startsWith(pattern)) {
|
|
122
|
+
const remainder = data.slice(length);
|
|
123
|
+
const params = [];
|
|
124
|
+
if (remainder) {
|
|
125
|
+
let current = "";
|
|
126
|
+
for (let i = 0; i < remainder.length; i++) if (remainder[i] === ";") {
|
|
127
|
+
params.push(current || "-1");
|
|
128
|
+
current = "";
|
|
129
|
+
} else current += remainder[i];
|
|
130
|
+
params.push(current || "-1");
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
type: CODE_TYPES.DCS,
|
|
134
|
+
pos,
|
|
135
|
+
raw,
|
|
136
|
+
command: pattern,
|
|
137
|
+
params
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
type: CODE_TYPES.DCS,
|
|
142
|
+
pos,
|
|
143
|
+
raw,
|
|
144
|
+
command: "",
|
|
145
|
+
params: [data]
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/parsers/dec.ts
|
|
151
|
+
function parseDEC(pos, raw, data, final) {
|
|
152
|
+
const withoutPrefix = data.slice(1);
|
|
153
|
+
let i = 0;
|
|
154
|
+
let paramsRaw = "";
|
|
155
|
+
while (i < withoutPrefix.length && (withoutPrefix.charCodeAt(i) >= 48 && withoutPrefix.charCodeAt(i) <= 57 || withoutPrefix[i] === ";")) {
|
|
156
|
+
paramsRaw += withoutPrefix[i];
|
|
157
|
+
i++;
|
|
158
|
+
}
|
|
159
|
+
const command = withoutPrefix.slice(i) + final;
|
|
160
|
+
const params = [];
|
|
161
|
+
if (paramsRaw) {
|
|
162
|
+
let current = "";
|
|
163
|
+
for (let j = 0; j < paramsRaw.length; j++) if (paramsRaw[j] === ";") {
|
|
164
|
+
params.push(current || "-1");
|
|
165
|
+
current = "";
|
|
166
|
+
} else current += paramsRaw[j];
|
|
167
|
+
params.push(current || "-1");
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
type: CODE_TYPES.DEC,
|
|
171
|
+
pos,
|
|
172
|
+
raw,
|
|
173
|
+
command,
|
|
174
|
+
params
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
//#endregion
|
|
179
|
+
//#region src/parsers/esc.ts
|
|
180
|
+
function parseESC(pos, raw, command, data) {
|
|
181
|
+
return {
|
|
182
|
+
type: CODE_TYPES.ESC,
|
|
183
|
+
pos,
|
|
184
|
+
raw,
|
|
185
|
+
command,
|
|
186
|
+
params: data ? [data] : []
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
//#endregion
|
|
191
|
+
//#region src/parsers/osc.ts
|
|
192
|
+
function parseOSC(pos, raw, data) {
|
|
193
|
+
const semicolonIndex = data.indexOf(";");
|
|
194
|
+
if (semicolonIndex === -1) return {
|
|
195
|
+
type: CODE_TYPES.OSC,
|
|
196
|
+
pos,
|
|
197
|
+
raw,
|
|
198
|
+
command: data,
|
|
199
|
+
params: []
|
|
200
|
+
};
|
|
201
|
+
const command = data.slice(0, semicolonIndex);
|
|
202
|
+
const remainder = data.slice(semicolonIndex + 1);
|
|
203
|
+
if (command === "1337") return {
|
|
204
|
+
type: CODE_TYPES.OSC,
|
|
205
|
+
pos,
|
|
206
|
+
raw,
|
|
207
|
+
command,
|
|
208
|
+
params: [remainder]
|
|
209
|
+
};
|
|
210
|
+
const params = [];
|
|
211
|
+
if (remainder) {
|
|
212
|
+
let current = "";
|
|
213
|
+
for (let i = 0; i < remainder.length; i++) if (remainder[i] === ";") {
|
|
214
|
+
params.push(current);
|
|
215
|
+
current = "";
|
|
216
|
+
} else current += remainder[i];
|
|
217
|
+
params.push(current);
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
type: CODE_TYPES.OSC,
|
|
221
|
+
pos,
|
|
222
|
+
raw,
|
|
223
|
+
command,
|
|
224
|
+
params
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
//#endregion
|
|
229
|
+
//#region src/tokenize.ts
|
|
230
|
+
const INTRODUCERS = new Set([
|
|
231
|
+
ESC,
|
|
232
|
+
CSI,
|
|
233
|
+
OSC,
|
|
234
|
+
DCS,
|
|
235
|
+
APC,
|
|
236
|
+
PM,
|
|
237
|
+
SOS
|
|
238
|
+
]);
|
|
239
|
+
function emit$1(token) {
|
|
240
|
+
return token;
|
|
241
|
+
}
|
|
242
|
+
function* tokenizer(input) {
|
|
243
|
+
let i = 0;
|
|
244
|
+
let state = "GROUND";
|
|
245
|
+
let currentCode;
|
|
246
|
+
function setState(next, code) {
|
|
247
|
+
state = next;
|
|
248
|
+
currentCode = code;
|
|
249
|
+
}
|
|
250
|
+
while (i < input.length) if (state === "GROUND") {
|
|
251
|
+
const textStart = i;
|
|
252
|
+
while (i < input.length) {
|
|
253
|
+
const char = input[i];
|
|
254
|
+
if (INTRODUCERS.has(char)) break;
|
|
255
|
+
i++;
|
|
256
|
+
}
|
|
257
|
+
if (i > textStart) yield emit$1({
|
|
258
|
+
type: TOKEN_TYPES.TEXT,
|
|
259
|
+
pos: textStart,
|
|
260
|
+
raw: input.substring(textStart, i)
|
|
261
|
+
});
|
|
262
|
+
if (i < input.length) {
|
|
263
|
+
const char = input[i];
|
|
264
|
+
if (char === CSI || char === OSC || char === DCS || char === APC || char === PM || char === SOS) {
|
|
265
|
+
yield emit$1({
|
|
266
|
+
type: TOKEN_TYPES.INTRODUCER,
|
|
267
|
+
pos: i,
|
|
268
|
+
raw: char,
|
|
269
|
+
code: char
|
|
270
|
+
});
|
|
271
|
+
i++;
|
|
272
|
+
setState("SEQUENCE", char);
|
|
273
|
+
} else if (char === ESC) {
|
|
274
|
+
const next = input[i + 1];
|
|
275
|
+
if (next === CSI_OPEN) {
|
|
276
|
+
yield emit$1({
|
|
277
|
+
type: TOKEN_TYPES.INTRODUCER,
|
|
278
|
+
pos: i,
|
|
279
|
+
raw: char + next,
|
|
280
|
+
code: CSI
|
|
281
|
+
});
|
|
282
|
+
i += 2;
|
|
283
|
+
setState("SEQUENCE", CSI);
|
|
284
|
+
} else if (next === OSC_OPEN) {
|
|
285
|
+
yield emit$1({
|
|
286
|
+
type: TOKEN_TYPES.INTRODUCER,
|
|
287
|
+
pos: i,
|
|
288
|
+
raw: char + next,
|
|
289
|
+
code: OSC
|
|
290
|
+
});
|
|
291
|
+
i += 2;
|
|
292
|
+
setState("SEQUENCE", OSC);
|
|
293
|
+
} else if (STRING_OPENERS.has(next)) {
|
|
294
|
+
yield emit$1({
|
|
295
|
+
type: TOKEN_TYPES.INTRODUCER,
|
|
296
|
+
pos: i,
|
|
297
|
+
raw: char + next,
|
|
298
|
+
code: next
|
|
299
|
+
});
|
|
300
|
+
i += 2;
|
|
301
|
+
setState("SEQUENCE", next);
|
|
302
|
+
} else if (next && next.charCodeAt(0) >= 32 && next.charCodeAt(0) <= 47) {
|
|
303
|
+
yield emit$1({
|
|
304
|
+
type: TOKEN_TYPES.INTRODUCER,
|
|
305
|
+
pos: i,
|
|
306
|
+
raw: char,
|
|
307
|
+
code: ESC
|
|
308
|
+
});
|
|
309
|
+
i += 1;
|
|
310
|
+
yield emit$1({
|
|
311
|
+
type: TOKEN_TYPES.FINAL,
|
|
312
|
+
pos: i,
|
|
313
|
+
raw: next
|
|
314
|
+
});
|
|
315
|
+
i++;
|
|
316
|
+
} else if (next) {
|
|
317
|
+
yield emit$1({
|
|
318
|
+
type: TOKEN_TYPES.INTRODUCER,
|
|
319
|
+
pos: i,
|
|
320
|
+
raw: char,
|
|
321
|
+
code: ESC
|
|
322
|
+
});
|
|
323
|
+
yield emit$1({
|
|
324
|
+
type: TOKEN_TYPES.FINAL,
|
|
325
|
+
pos: i + 1,
|
|
326
|
+
raw: next
|
|
327
|
+
});
|
|
328
|
+
i += 2;
|
|
329
|
+
} else {
|
|
330
|
+
yield emit$1({
|
|
331
|
+
type: TOKEN_TYPES.INTRODUCER,
|
|
332
|
+
pos: i,
|
|
333
|
+
raw: char,
|
|
334
|
+
code: ESC
|
|
335
|
+
});
|
|
336
|
+
i++;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
} else {
|
|
341
|
+
const pos = i;
|
|
342
|
+
const code = currentCode;
|
|
343
|
+
let data = "";
|
|
344
|
+
if (code === CSI) while (i < input.length) {
|
|
345
|
+
const char = input[i];
|
|
346
|
+
const charCode = char.charCodeAt(0);
|
|
347
|
+
if (charCode >= 64 && charCode < 126) {
|
|
348
|
+
if (data) yield emit$1({
|
|
349
|
+
type: TOKEN_TYPES.DATA,
|
|
350
|
+
pos,
|
|
351
|
+
raw: data
|
|
352
|
+
});
|
|
353
|
+
yield emit$1({
|
|
354
|
+
type: TOKEN_TYPES.FINAL,
|
|
355
|
+
pos: i,
|
|
356
|
+
raw: char
|
|
357
|
+
});
|
|
358
|
+
i++;
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
data += char;
|
|
362
|
+
i++;
|
|
363
|
+
}
|
|
364
|
+
else if (code) while (i < input.length) {
|
|
365
|
+
const char = input[i];
|
|
366
|
+
let terminator;
|
|
367
|
+
if (char === ST) terminator = ST;
|
|
368
|
+
else if (char === BELL && code === OSC) terminator = BELL;
|
|
369
|
+
else if (char === ESC && input[i + 1] === BACKSLASH) terminator = ESC + BACKSLASH;
|
|
370
|
+
if (terminator) {
|
|
371
|
+
if (data) yield emit$1({
|
|
372
|
+
type: TOKEN_TYPES.DATA,
|
|
373
|
+
pos,
|
|
374
|
+
raw: data
|
|
375
|
+
});
|
|
376
|
+
yield emit$1({
|
|
377
|
+
type: TOKEN_TYPES.FINAL,
|
|
378
|
+
pos: i,
|
|
379
|
+
raw: terminator
|
|
380
|
+
});
|
|
381
|
+
i += terminator.length;
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
data += char;
|
|
385
|
+
i++;
|
|
386
|
+
}
|
|
387
|
+
setState("GROUND");
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function tokenize(input) {
|
|
391
|
+
return Array.from(tokenizer(input));
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
//#endregion
|
|
395
|
+
//#region src/parse.ts
|
|
396
|
+
function emit(token) {
|
|
397
|
+
return token;
|
|
398
|
+
}
|
|
399
|
+
function* parser(tokens) {
|
|
400
|
+
let current = tokens.next();
|
|
401
|
+
while (!current.done) {
|
|
402
|
+
const token = current.value;
|
|
403
|
+
if (token.type === TOKEN_TYPES.TEXT) {
|
|
404
|
+
yield emit({
|
|
405
|
+
type: CODE_TYPES.TEXT,
|
|
406
|
+
pos: token.pos,
|
|
407
|
+
raw: token.raw
|
|
408
|
+
});
|
|
409
|
+
current = tokens.next();
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
if (token.type === TOKEN_TYPES.INTRODUCER) {
|
|
413
|
+
const pos = token.pos;
|
|
414
|
+
let raw = token.raw;
|
|
415
|
+
let data = "";
|
|
416
|
+
let finalToken;
|
|
417
|
+
current = tokens.next();
|
|
418
|
+
while (!current.done && !finalToken) {
|
|
419
|
+
const nextToken = current.value;
|
|
420
|
+
if (nextToken.type === TOKEN_TYPES.DATA) {
|
|
421
|
+
data += nextToken.raw;
|
|
422
|
+
raw += nextToken.raw;
|
|
423
|
+
} else if (nextToken.type === TOKEN_TYPES.FINAL) {
|
|
424
|
+
finalToken = nextToken;
|
|
425
|
+
raw += nextToken.raw;
|
|
426
|
+
}
|
|
427
|
+
current = tokens.next();
|
|
428
|
+
}
|
|
429
|
+
if (finalToken) switch (token.code) {
|
|
430
|
+
case CSI:
|
|
431
|
+
if (data.startsWith(DEC_OPEN)) yield emit(parseDEC(pos, raw, data, finalToken.raw));
|
|
432
|
+
else if (PRIVATE_OPENERS.has(data[0])) yield emit(parsePrivateCSI(pos, raw, data, finalToken.raw));
|
|
433
|
+
else yield emit(parseCSI(pos, raw, data, finalToken.raw));
|
|
434
|
+
break;
|
|
435
|
+
case OSC:
|
|
436
|
+
yield emit(parseOSC(pos, raw, data));
|
|
437
|
+
break;
|
|
438
|
+
case DCS:
|
|
439
|
+
case DCS_OPEN:
|
|
440
|
+
yield emit(parseDCS(pos, raw, data));
|
|
441
|
+
break;
|
|
442
|
+
case APC:
|
|
443
|
+
case APC_OPEN:
|
|
444
|
+
yield emit({
|
|
445
|
+
type: CODE_TYPES.STRING,
|
|
446
|
+
pos,
|
|
447
|
+
raw,
|
|
448
|
+
command: "APC",
|
|
449
|
+
params: data ? [data] : []
|
|
450
|
+
});
|
|
451
|
+
break;
|
|
452
|
+
case PM:
|
|
453
|
+
case PM_OPEN:
|
|
454
|
+
yield emit({
|
|
455
|
+
type: CODE_TYPES.STRING,
|
|
456
|
+
pos,
|
|
457
|
+
raw,
|
|
458
|
+
command: "PM",
|
|
459
|
+
params: data ? [data] : []
|
|
460
|
+
});
|
|
461
|
+
break;
|
|
462
|
+
case SOS:
|
|
463
|
+
case SOS_OPEN:
|
|
464
|
+
yield emit({
|
|
465
|
+
type: CODE_TYPES.STRING,
|
|
466
|
+
pos,
|
|
467
|
+
raw,
|
|
468
|
+
command: "SOS",
|
|
469
|
+
params: data ? [data] : []
|
|
470
|
+
});
|
|
471
|
+
break;
|
|
472
|
+
case ESC:
|
|
473
|
+
yield emit(parseESC(pos, raw, finalToken.raw, data));
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
else if (token.code === ESC) yield emit(parseESC(pos, raw, "", ""));
|
|
477
|
+
} else current = tokens.next();
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
function parse(input) {
|
|
481
|
+
return Array.from(parser(tokenizer(input)));
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
//#endregion
|
|
485
|
+
export { APC, APC_OPEN, BACKSLASH, BELL, CODE_TYPES, CSI, CSI_OPEN, DCS, DCS_OPEN, DEC_OPEN, ESC, OSC, OSC_OPEN, PM, PM_OPEN, PRIVATE_OPENERS, SOS, SOS_OPEN, ST, STRING_OPENERS, TOKEN_TYPES, parse, parser, tokenize, tokenizer };
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ansi-tools/parser",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Tokenize and parse strings containing ANSI escape sequences and control codes",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./escaped": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/escaped.js",
|
|
16
|
+
"default": "./dist/escaped.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"ansi",
|
|
21
|
+
"escape codes",
|
|
22
|
+
"web application"
|
|
23
|
+
],
|
|
24
|
+
"author": "Lars Kappert <lars@webpro.nl>",
|
|
25
|
+
"license": "ISC",
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^24.0.13",
|
|
31
|
+
"tsdown": "^0.12.9",
|
|
32
|
+
"typescript": "^5.8.3"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"prebuild": "pnpm type-check && pnpm test",
|
|
36
|
+
"build": "tsdown --dts src/index.ts src/escaped.ts",
|
|
37
|
+
"test": "node --test",
|
|
38
|
+
"type-check": "tsc"
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const BELL = String.fromCharCode(7);
|
|
2
|
+
export const ESC = String.fromCharCode(27);
|
|
3
|
+
export const BACKSLASH = String.fromCharCode(92);
|
|
4
|
+
export const DCS = String.fromCharCode(144);
|
|
5
|
+
export const SOS = String.fromCharCode(152);
|
|
6
|
+
export const CSI = String.fromCharCode(155);
|
|
7
|
+
export const ST = String.fromCharCode(156);
|
|
8
|
+
export const OSC = String.fromCharCode(157);
|
|
9
|
+
export const PM = String.fromCharCode(158);
|
|
10
|
+
export const APC = String.fromCharCode(159);
|
|
11
|
+
|
|
12
|
+
export const CSI_OPEN = "[";
|
|
13
|
+
export const OSC_OPEN = "]";
|
|
14
|
+
export const DEC_OPEN = "?";
|
|
15
|
+
export const PRIVATE_OPENERS = new Set(["<", "=", ">"]);
|
|
16
|
+
|
|
17
|
+
export const DCS_OPEN = "P";
|
|
18
|
+
export const APC_OPEN = "_";
|
|
19
|
+
export const SOS_OPEN = "^";
|
|
20
|
+
export const PM_OPEN = "X";
|
|
21
|
+
export const STRING_OPENERS = new Set([DCS_OPEN, APC_OPEN, SOS_OPEN, PM_OPEN]);
|
|
22
|
+
|
|
23
|
+
export const TOKEN_TYPES = {
|
|
24
|
+
TEXT: "TEXT",
|
|
25
|
+
INTRODUCER: "INTRODUCER",
|
|
26
|
+
DATA: "DATA",
|
|
27
|
+
FINAL: "FINAL",
|
|
28
|
+
} as const;
|
|
29
|
+
|
|
30
|
+
export const CODE_TYPES = {
|
|
31
|
+
CSI: "CSI",
|
|
32
|
+
DCS: "DCS",
|
|
33
|
+
DEC: "DEC",
|
|
34
|
+
ESC: "ESC",
|
|
35
|
+
OSC: "OSC",
|
|
36
|
+
PRIVATE: "PRIVATE",
|
|
37
|
+
SGR: "SGR",
|
|
38
|
+
STRING: "STRING",
|
|
39
|
+
TEXT: "TEXT",
|
|
40
|
+
} as const;
|
package/src/escaped.ts
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { tokenizer } from "./tokenize.escaped.ts";
|
|
4
|
+
import { parser } from "./parse.ts";
|
|
5
|
+
import { CODE_TYPES } from "./constants.ts";
|
|
6
|
+
|
|
7
|
+
test("parse simple text", () => {
|
|
8
|
+
const input = "hello world";
|
|
9
|
+
const tokens = tokenizer(input);
|
|
10
|
+
const codes = [...parser(tokens)];
|
|
11
|
+
assert.deepEqual(codes, [{ type: CODE_TYPES.TEXT, pos: 0, raw: "hello world" }]);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("parse mixed text and csi", () => {
|
|
15
|
+
const input = String.raw`hello \e[31mworld\e[0m`;
|
|
16
|
+
const tokens = tokenizer(input);
|
|
17
|
+
const codes = [...parser(tokens)];
|
|
18
|
+
assert.deepEqual(codes, [
|
|
19
|
+
{ type: CODE_TYPES.TEXT, pos: 0, raw: "hello " },
|
|
20
|
+
{ type: CODE_TYPES.CSI, pos: 6, raw: "\\e[31m", params: ["31"], command: "m" },
|
|
21
|
+
{ type: CODE_TYPES.TEXT, pos: 12, raw: "world" },
|
|
22
|
+
{ type: CODE_TYPES.CSI, pos: 17, raw: "\\e[0m", params: ["0"], command: "m" },
|
|
23
|
+
]);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("subsequent escape sequences", () => {
|
|
27
|
+
const input = String.raw`\e[31m\e[32m\e[33m`;
|
|
28
|
+
const tokens = tokenizer(input);
|
|
29
|
+
const codes = [...parser(tokens)];
|
|
30
|
+
assert.deepEqual(codes, [
|
|
31
|
+
{ type: CODE_TYPES.CSI, pos: 0, raw: "\\e[31m", params: ["31"], command: "m" },
|
|
32
|
+
{ type: CODE_TYPES.CSI, pos: 6, raw: "\\e[32m", params: ["32"], command: "m" },
|
|
33
|
+
{ type: CODE_TYPES.CSI, pos: 12, raw: "\\e[33m", params: ["33"], command: "m" },
|
|
34
|
+
]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("parse multiple different sequence types", () => {
|
|
38
|
+
const input = String.raw`\e[1;32m\e]0;title\a\e=\ePdata\e\\`;
|
|
39
|
+
const tokens = tokenizer(input);
|
|
40
|
+
const codes = [...parser(tokens)];
|
|
41
|
+
assert.deepEqual(codes, [
|
|
42
|
+
{ type: CODE_TYPES.CSI, pos: 0, raw: "\\e[1;32m", params: ["1", "32"], command: "m" },
|
|
43
|
+
{ type: CODE_TYPES.OSC, pos: 8, raw: "\\e]0;title\\a", params: ["title"], command: "0" },
|
|
44
|
+
{ type: CODE_TYPES.ESC, pos: 20, raw: "\\e=", command: "=", params: [] },
|
|
45
|
+
{ type: CODE_TYPES.DCS, pos: 23, raw: "\\ePdata\\e\\\\", params: ["data"], command: "" },
|
|
46
|
+
]);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("parse mixed standard and private sequences", () => {
|
|
50
|
+
const input = String.raw`\e[31m\e[<5h\e[?25l\e[>c`;
|
|
51
|
+
const tokens = tokenizer(input);
|
|
52
|
+
const codes = [...parser(tokens)];
|
|
53
|
+
assert.deepEqual(codes, [
|
|
54
|
+
{ type: CODE_TYPES.CSI, pos: 0, raw: "\\e[31m", params: ["31"], command: "m" },
|
|
55
|
+
{ type: CODE_TYPES.PRIVATE, pos: 6, raw: "\\e[<5h", params: ["5"], command: "<h" },
|
|
56
|
+
{ type: CODE_TYPES.DEC, pos: 12, raw: "\\e[?25l", params: ["25"], command: "l" },
|
|
57
|
+
{ type: CODE_TYPES.PRIVATE, pos: 19, raw: "\\e[>c", params: [], command: ">c" },
|
|
58
|
+
]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("parse complex missing parameter scenarios", () => {
|
|
62
|
+
const input = String.raw`\e[;5;m\e[?;h\eP$q;;\e\\`;
|
|
63
|
+
const tokens = tokenizer(input);
|
|
64
|
+
const codes = [...parser(tokens)];
|
|
65
|
+
assert.deepEqual(codes, [
|
|
66
|
+
{ type: CODE_TYPES.CSI, pos: 0, raw: "\\e[;5;m", params: ["-1", "5", "-1"], command: "m" },
|
|
67
|
+
{ type: CODE_TYPES.DEC, pos: 7, raw: "\\e[?;h", params: ["-1", "-1"], command: "h" },
|
|
68
|
+
{ type: CODE_TYPES.DCS, pos: 13, raw: "\\eP$q;;\\e\\\\", params: ["-1", "-1", "-1"], command: "$q" },
|
|
69
|
+
]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("parse iTerm2 image sequence", () => {
|
|
73
|
+
const input = String.raw`\e]1337;File=inline=1;width=1;height=1:R0lG=\a`;
|
|
74
|
+
const tokens = tokenizer(input);
|
|
75
|
+
const codes = [...parser(tokens)];
|
|
76
|
+
assert.deepEqual(codes, [
|
|
77
|
+
{ type: CODE_TYPES.OSC, pos: 0, raw: input, command: "1337", params: ["File=inline=1;width=1;height=1:R0lG="] },
|
|
78
|
+
]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("parse DECUDK sequence", () => {
|
|
82
|
+
const input = String.raw`\eP0|23/68656c6c6f\e\\`;
|
|
83
|
+
const tokens = tokenizer(input);
|
|
84
|
+
const codes = [...parser(tokens)];
|
|
85
|
+
assert.deepEqual(codes, [{ type: CODE_TYPES.DCS, pos: 0, raw: input, command: "", params: ["0|23/68656c6c6f"] }]);
|
|
86
|
+
});
|