@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,118 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
import { tokenize } from "./tokenize.ts";
|
|
4
|
+
|
|
5
|
+
test("unescaped (ESC)", () => {
|
|
6
|
+
const input = "\x1b[2q";
|
|
7
|
+
const tokens = tokenize(input);
|
|
8
|
+
assert.deepEqual(tokens, [
|
|
9
|
+
{ type: "INTRODUCER", pos: 0, raw: "\x1b[", code: "\x9b" },
|
|
10
|
+
{ type: "DATA", pos: 2, raw: "2" },
|
|
11
|
+
{ type: "FINAL", pos: 3, raw: "q" },
|
|
12
|
+
]);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("unescaped (ESC/2)", () => {
|
|
16
|
+
const input = "\u001b[2q";
|
|
17
|
+
const tokens = tokenize(input);
|
|
18
|
+
assert.deepEqual(tokens, [
|
|
19
|
+
{ type: "INTRODUCER", pos: 0, raw: "\u001b[", code: "\x9b" },
|
|
20
|
+
{ type: "DATA", pos: 2, raw: "2" },
|
|
21
|
+
{ type: "FINAL", pos: 3, raw: "q" },
|
|
22
|
+
]);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("unescaped (CSI)", () => {
|
|
26
|
+
const input = "\u009b32mGreen text\u009b0m.";
|
|
27
|
+
const tokens = tokenize(input);
|
|
28
|
+
assert.deepEqual(tokens, [
|
|
29
|
+
{ type: "INTRODUCER", pos: 0, raw: "\u009b", code: "\x9b" },
|
|
30
|
+
{ type: "DATA", pos: 1, raw: "32" },
|
|
31
|
+
{ type: "FINAL", pos: 3, raw: "m" },
|
|
32
|
+
{ type: "TEXT", pos: 4, raw: "Green text" },
|
|
33
|
+
{ type: "INTRODUCER", pos: 14, raw: "\u009b", code: "\x9b" },
|
|
34
|
+
{ type: "DATA", pos: 15, raw: "0" },
|
|
35
|
+
{ type: "FINAL", pos: 16, raw: "m" },
|
|
36
|
+
{ type: "TEXT", pos: 17, raw: "." },
|
|
37
|
+
]);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("unescaped (BEL)", () => {
|
|
41
|
+
const input = "\x1b]0;title\x07";
|
|
42
|
+
const tokens = tokenize(input);
|
|
43
|
+
assert.deepEqual(tokens, [
|
|
44
|
+
{ type: "INTRODUCER", pos: 0, raw: "\x1b]", code: "\x9d" },
|
|
45
|
+
{ type: "DATA", pos: 2, raw: "0;title" },
|
|
46
|
+
{ type: "FINAL", pos: 9, raw: "\x07" },
|
|
47
|
+
]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("unescaped (ST - String Terminator)", () => {
|
|
51
|
+
const input = "\x1b]0;title\x9c";
|
|
52
|
+
const tokens = tokenize(input);
|
|
53
|
+
assert.deepEqual(tokens, [
|
|
54
|
+
{ type: "INTRODUCER", pos: 0, raw: "\x1b]", code: "\x9d" },
|
|
55
|
+
{ type: "DATA", pos: 2, raw: "0;title" },
|
|
56
|
+
{ type: "FINAL", pos: 9, raw: "\x9c" },
|
|
57
|
+
]);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("unescaped (OSC - Operating System Command)", () => {
|
|
61
|
+
const input = "\x9d0;title\x07";
|
|
62
|
+
const tokens = tokenize(input);
|
|
63
|
+
assert.deepEqual(tokens, [
|
|
64
|
+
{ type: "INTRODUCER", pos: 0, raw: "\x9d", code: "\x9d" },
|
|
65
|
+
{ type: "DATA", pos: 1, raw: "0;title" },
|
|
66
|
+
{ type: "FINAL", pos: 8, raw: "\x07" },
|
|
67
|
+
]);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("unescaped (DCS - Device Control String)", () => {
|
|
71
|
+
const input = "\x900;1|data\x9c";
|
|
72
|
+
const tokens = tokenize(input);
|
|
73
|
+
assert.deepEqual(tokens, [
|
|
74
|
+
{ type: "INTRODUCER", pos: 0, raw: "\x90", code: "\x90" },
|
|
75
|
+
{ type: "DATA", pos: 1, raw: "0;1|data" },
|
|
76
|
+
{ type: "FINAL", pos: 9, raw: "\x9c" },
|
|
77
|
+
]);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("unescaped (APC - Application Program Command)", () => {
|
|
81
|
+
const input = "\x9fapp data\x9c";
|
|
82
|
+
const tokens = tokenize(input);
|
|
83
|
+
assert.deepEqual(tokens, [
|
|
84
|
+
{ type: "INTRODUCER", pos: 0, raw: "\x9f", code: "\x9f" },
|
|
85
|
+
{ type: "DATA", pos: 1, raw: "app data" },
|
|
86
|
+
{ type: "FINAL", pos: 9, raw: "\x9c" },
|
|
87
|
+
]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("unescaped (PM - Privacy Message)", () => {
|
|
91
|
+
const input = "\x9eprivacy data\x9c";
|
|
92
|
+
const tokens = tokenize(input);
|
|
93
|
+
assert.deepEqual(tokens, [
|
|
94
|
+
{ type: "INTRODUCER", pos: 0, raw: "\x9e", code: "\x9e" },
|
|
95
|
+
{ type: "DATA", pos: 1, raw: "privacy data" },
|
|
96
|
+
{ type: "FINAL", pos: 13, raw: "\x9c" },
|
|
97
|
+
]);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("unescaped (SOS - Start of String)", () => {
|
|
101
|
+
const input = "\x98string data\x9c";
|
|
102
|
+
const tokens = tokenize(input);
|
|
103
|
+
assert.deepEqual(tokens, [
|
|
104
|
+
{ type: "INTRODUCER", pos: 0, raw: "\x98", code: "\x98" },
|
|
105
|
+
{ type: "DATA", pos: 1, raw: "string data" },
|
|
106
|
+
{ type: "FINAL", pos: 12, raw: "\x9c" },
|
|
107
|
+
]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("unescaped (ESC with backslash terminator)", () => {
|
|
111
|
+
const input = "\x1b_payload\x1b\\";
|
|
112
|
+
const tokens = tokenize(input);
|
|
113
|
+
assert.deepEqual(tokens, [
|
|
114
|
+
{ type: "INTRODUCER", pos: 0, raw: "\x1b_", code: "_" },
|
|
115
|
+
{ type: "DATA", pos: 2, raw: "payload" },
|
|
116
|
+
{ type: "FINAL", pos: 9, raw: "\x1b\\" },
|
|
117
|
+
]);
|
|
118
|
+
});
|
package/src/tokenize.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import {
|
|
2
|
+
APC,
|
|
3
|
+
BACKSLASH,
|
|
4
|
+
BELL,
|
|
5
|
+
CSI,
|
|
6
|
+
CSI_OPEN,
|
|
7
|
+
DCS,
|
|
8
|
+
ESC,
|
|
9
|
+
OSC,
|
|
10
|
+
OSC_OPEN,
|
|
11
|
+
PM,
|
|
12
|
+
SOS,
|
|
13
|
+
ST,
|
|
14
|
+
STRING_OPENERS,
|
|
15
|
+
TOKEN_TYPES,
|
|
16
|
+
} from "./constants.ts";
|
|
17
|
+
import type { TOKEN } from "./types.ts";
|
|
18
|
+
|
|
19
|
+
type State = "GROUND" | "SEQUENCE";
|
|
20
|
+
|
|
21
|
+
const debug = false;
|
|
22
|
+
|
|
23
|
+
const INTRODUCERS = new Set([ESC, CSI, OSC, DCS, APC, PM, SOS]);
|
|
24
|
+
|
|
25
|
+
function emit(token: TOKEN) {
|
|
26
|
+
if (debug) console.log("token", token);
|
|
27
|
+
return token;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function* tokenizer(input: string): Generator<TOKEN> {
|
|
31
|
+
let i = 0;
|
|
32
|
+
let state: State = "GROUND";
|
|
33
|
+
let currentCode: string | undefined;
|
|
34
|
+
|
|
35
|
+
function setState(next: State, code?: string) {
|
|
36
|
+
if (debug) console.log(`state ${state} → ${next}`);
|
|
37
|
+
state = next;
|
|
38
|
+
currentCode = code;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
while (i < input.length) {
|
|
42
|
+
if (state === "GROUND") {
|
|
43
|
+
const textStart = i;
|
|
44
|
+
while (i < input.length) {
|
|
45
|
+
const char = input[i];
|
|
46
|
+
if (INTRODUCERS.has(char)) {
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
i++;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (i > textStart) {
|
|
53
|
+
yield emit({ type: TOKEN_TYPES.TEXT, pos: textStart, raw: input.substring(textStart, i) });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (i < input.length) {
|
|
57
|
+
const char = input[i];
|
|
58
|
+
if (char === CSI || char === OSC || char === DCS || char === APC || char === PM || char === SOS) {
|
|
59
|
+
yield emit({ type: TOKEN_TYPES.INTRODUCER, pos: i, raw: char, code: char });
|
|
60
|
+
i++;
|
|
61
|
+
setState("SEQUENCE", char);
|
|
62
|
+
} else if (char === ESC) {
|
|
63
|
+
const next = input[i + 1];
|
|
64
|
+
if (next === CSI_OPEN) {
|
|
65
|
+
yield emit({ type: TOKEN_TYPES.INTRODUCER, pos: i, raw: char + next, code: CSI });
|
|
66
|
+
i += 2;
|
|
67
|
+
setState("SEQUENCE", CSI);
|
|
68
|
+
} else if (next === OSC_OPEN) {
|
|
69
|
+
yield emit({ type: TOKEN_TYPES.INTRODUCER, pos: i, raw: char + next, code: OSC });
|
|
70
|
+
i += 2;
|
|
71
|
+
setState("SEQUENCE", OSC);
|
|
72
|
+
} else if (STRING_OPENERS.has(next)) {
|
|
73
|
+
yield emit({ type: TOKEN_TYPES.INTRODUCER, pos: i, raw: char + next, code: next });
|
|
74
|
+
i += 2;
|
|
75
|
+
setState("SEQUENCE", next);
|
|
76
|
+
} else if (next && next.charCodeAt(0) >= 0x20 && next.charCodeAt(0) <= 0x2f) {
|
|
77
|
+
yield emit({ type: TOKEN_TYPES.INTRODUCER, pos: i, raw: char, code: ESC });
|
|
78
|
+
i += 1;
|
|
79
|
+
yield emit({ type: TOKEN_TYPES.FINAL, pos: i, raw: next });
|
|
80
|
+
i++;
|
|
81
|
+
} else if (next) {
|
|
82
|
+
yield emit({ type: TOKEN_TYPES.INTRODUCER, pos: i, raw: char, code: ESC });
|
|
83
|
+
yield emit({ type: TOKEN_TYPES.FINAL, pos: i + 1, raw: next });
|
|
84
|
+
i += 2;
|
|
85
|
+
} else {
|
|
86
|
+
yield emit({ type: TOKEN_TYPES.INTRODUCER, pos: i, raw: char, code: ESC });
|
|
87
|
+
i++;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
const pos = i;
|
|
93
|
+
const code = currentCode;
|
|
94
|
+
let data = "";
|
|
95
|
+
|
|
96
|
+
if (code === CSI) {
|
|
97
|
+
while (i < input.length) {
|
|
98
|
+
const char = input[i];
|
|
99
|
+
const charCode = char.charCodeAt(0);
|
|
100
|
+
if (charCode >= 0x40 && charCode < 0x7e) {
|
|
101
|
+
if (data) yield emit({ type: TOKEN_TYPES.DATA, pos, raw: data });
|
|
102
|
+
yield emit({ type: TOKEN_TYPES.FINAL, pos: i, raw: char });
|
|
103
|
+
i++;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
data += char;
|
|
107
|
+
i++;
|
|
108
|
+
}
|
|
109
|
+
} else if (code) {
|
|
110
|
+
while (i < input.length) {
|
|
111
|
+
const char = input[i];
|
|
112
|
+
let terminator: string | undefined;
|
|
113
|
+
|
|
114
|
+
if (char === ST) {
|
|
115
|
+
terminator = ST;
|
|
116
|
+
} else if (char === BELL && code === OSC) {
|
|
117
|
+
terminator = BELL;
|
|
118
|
+
} else if (char === ESC && input[i + 1] === BACKSLASH) {
|
|
119
|
+
terminator = ESC + BACKSLASH;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (terminator) {
|
|
123
|
+
if (data) yield emit({ type: TOKEN_TYPES.DATA, pos, raw: data });
|
|
124
|
+
yield emit({ type: TOKEN_TYPES.FINAL, pos: i, raw: terminator });
|
|
125
|
+
i += terminator.length;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
data += char;
|
|
130
|
+
i++;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
setState("GROUND");
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function tokenize(input: string): TOKEN[] {
|
|
139
|
+
return Array.from(tokenizer(input));
|
|
140
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { TOKEN_TYPES } from "./constants.ts";
|
|
2
|
+
|
|
3
|
+
export type TOKEN = {
|
|
4
|
+
type: keyof typeof TOKEN_TYPES;
|
|
5
|
+
pos: number;
|
|
6
|
+
raw: string;
|
|
7
|
+
code?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type CONTROL_CODE = {
|
|
11
|
+
type: "CSI" | "DCS" | "DEC" | "ESC" | "OSC" | "SGR" | "STRING" | "PRIVATE";
|
|
12
|
+
command: string;
|
|
13
|
+
raw: string;
|
|
14
|
+
params: string[];
|
|
15
|
+
pos: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type CONTROL_CODE_TEXT = {
|
|
19
|
+
type: "TEXT";
|
|
20
|
+
raw: string;
|
|
21
|
+
pos: number;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type CODE = CONTROL_CODE | CONTROL_CODE_TEXT;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"allowImportingTsExtensions": true,
|
|
4
|
+
"esModuleInterop": true,
|
|
5
|
+
"forceConsistentCasingInFileNames": true,
|
|
6
|
+
"module": "nodenext",
|
|
7
|
+
"moduleResolution": "nodenext",
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"outDir": "./dist",
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"strict": true,
|
|
12
|
+
"target": "esnext"
|
|
13
|
+
},
|
|
14
|
+
"include": ["src"],
|
|
15
|
+
"exclude": ["node_modules", "dist"]
|
|
16
|
+
}
|