@softwarefactory-project/re-ansi 0.5.0 → 0.7.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 +10 -2
- package/package.json +16 -16
- package/{bsconfig.json → rescript.json} +5 -7
- package/src/{Ansi.re → Ansi.res} +78 -55
- package/src/Ansi.res.js +919 -0
- package/src/Ansi.bs.js +0 -931
package/README.md
CHANGED
|
@@ -71,8 +71,8 @@ Get started by running:
|
|
|
71
71
|
```sh
|
|
72
72
|
git clone https://github.com/softwarefactory-project/re-ansi
|
|
73
73
|
cd re-ansi
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
npm install
|
|
75
|
+
npm run test
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
Then build and run tests with `yarn test`.
|
|
@@ -81,6 +81,14 @@ Make sure to read about [React][reason-react] and [Reason][rescript-lang] too.
|
|
|
81
81
|
|
|
82
82
|
## Changes
|
|
83
83
|
|
|
84
|
+
### 0.7.0
|
|
85
|
+
|
|
86
|
+
- Update to rescript version 11.
|
|
87
|
+
|
|
88
|
+
### 0.6.0
|
|
89
|
+
|
|
90
|
+
- Fix support for bright colors.
|
|
91
|
+
|
|
84
92
|
### 0.5.0
|
|
85
93
|
|
|
86
94
|
- Add support for `[m` and `[K`.
|
package/package.json
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softwarefactory-project/re-ansi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "ANSI code to HTML",
|
|
5
|
-
"files": [
|
|
6
|
-
|
|
5
|
+
"files": [
|
|
6
|
+
"README.md",
|
|
7
|
+
"LICENSE",
|
|
8
|
+
"rescript.json",
|
|
9
|
+
"src"
|
|
10
|
+
],
|
|
11
|
+
"main": "./src/Ansi.res.js",
|
|
7
12
|
"license": "Apache-2.0",
|
|
8
13
|
"homepage": "https://github.com/softwarefactory-project/re-ansi",
|
|
9
14
|
"repository": {
|
|
@@ -11,24 +16,19 @@
|
|
|
11
16
|
"url": "git+https://softwarefactory-project.io/r/software-factory/re-ansi.git"
|
|
12
17
|
},
|
|
13
18
|
"scripts": {
|
|
14
|
-
"build": "
|
|
15
|
-
"
|
|
16
|
-
"
|
|
19
|
+
"build": "rescript",
|
|
20
|
+
"clean": "rescript clean",
|
|
21
|
+
"dev": "rescript -w",
|
|
22
|
+
"test": "npm run build && node tests/Spec.res.js"
|
|
17
23
|
},
|
|
18
24
|
"keywords": [
|
|
19
|
-
"ReasonReact",
|
|
20
25
|
"ansi",
|
|
21
|
-
"reason",
|
|
22
|
-
"reason-react",
|
|
23
|
-
"reasonml",
|
|
24
26
|
"rescript",
|
|
25
|
-
"
|
|
27
|
+
"react"
|
|
26
28
|
],
|
|
27
29
|
"dependencies": {
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
"devDependencies": {
|
|
32
|
-
"react": "^16.14.0"
|
|
30
|
+
"@rescript/core": "^1.3.0",
|
|
31
|
+
"@rescript/react": "^0.12.1",
|
|
32
|
+
"rescript": "^11.1.0"
|
|
33
33
|
}
|
|
34
34
|
}
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softwarefactory-project/re-ansi",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
3
|
+
"jsx": {
|
|
4
|
+
"version": 4
|
|
5
5
|
},
|
|
6
6
|
"sources": ["src", { "dir": "tests", "type": "dev" }],
|
|
7
|
-
"bsc-flags": ["-
|
|
7
|
+
"bsc-flags": ["-open RescriptCore"],
|
|
8
8
|
"package-specs": [
|
|
9
9
|
{
|
|
10
10
|
"module": "commonjs",
|
|
11
11
|
"in-source": true
|
|
12
12
|
}
|
|
13
13
|
],
|
|
14
|
-
"suffix": ".
|
|
15
|
-
"bs-dependencies": ["
|
|
16
|
-
"bs-dev-dependencies": [],
|
|
17
|
-
"refmt": 3
|
|
14
|
+
"suffix": ".res.js",
|
|
15
|
+
"bs-dependencies": ["@rescript/react", "@rescript/core"]
|
|
18
16
|
}
|
package/src/{Ansi.re → Ansi.res}
RENAMED
|
@@ -15,16 +15,14 @@
|
|
|
15
15
|
// Ansi renders a log ANSI code to a React component
|
|
16
16
|
|
|
17
17
|
// Document type
|
|
18
|
-
type document = list
|
|
18
|
+
type rec document = list<atom>
|
|
19
19
|
and atom =
|
|
20
20
|
| Text(string)
|
|
21
21
|
| Link(string)
|
|
22
22
|
| LineBreak
|
|
23
23
|
| DocStyle(ReactDOM.Style.t, document);
|
|
24
24
|
|
|
25
|
-
type parser
|
|
26
|
-
|
|
27
|
-
open Belt;
|
|
25
|
+
type parser<'a> = (int, option<'a>);
|
|
28
26
|
|
|
29
27
|
module AnsiCode = {
|
|
30
28
|
type code =
|
|
@@ -35,7 +33,7 @@ module AnsiCode = {
|
|
|
35
33
|
| Style(ReactDOM.Style.t);
|
|
36
34
|
|
|
37
35
|
// Convert a 4 bits color code to its css color: https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit
|
|
38
|
-
let fourBitColors = (code: int): option
|
|
36
|
+
let fourBitColors = (code: int): option<string> =>
|
|
39
37
|
switch (code) {
|
|
40
38
|
| 00 => "black"->Some
|
|
41
39
|
| 01 => "red"->Some
|
|
@@ -59,6 +57,21 @@ module AnsiCode = {
|
|
|
59
57
|
None;
|
|
60
58
|
};
|
|
61
59
|
|
|
60
|
+
let threeBitColors = (code: int): option<string> =>
|
|
61
|
+
switch (code) {
|
|
62
|
+
| 00 => "grey"->Some
|
|
63
|
+
| 01 => "red"->Some
|
|
64
|
+
| 02 => "green"->Some
|
|
65
|
+
| 03 => "yellow"->Some
|
|
66
|
+
| 04 => "blue"->Some
|
|
67
|
+
| 05 => "magenta"->Some
|
|
68
|
+
| 06 => "cyan"->Some
|
|
69
|
+
| 07 => "white"->Some
|
|
70
|
+
| _ =>
|
|
71
|
+
Js.log2("Unknown color value:", code);
|
|
72
|
+
None;
|
|
73
|
+
};
|
|
74
|
+
|
|
62
75
|
// Css utility
|
|
63
76
|
let combine = (css1, css2) => ReactDOM.Style.combine(css1, css2);
|
|
64
77
|
let addWeight = fontWeight => ReactDOM.Style.make(~fontWeight, ());
|
|
@@ -70,11 +83,12 @@ module AnsiCode = {
|
|
|
70
83
|
module ColorCss = {
|
|
71
84
|
type t =
|
|
72
85
|
| Foreground(int)
|
|
86
|
+
| BrightForeground(int)
|
|
73
87
|
| Background(int);
|
|
74
|
-
let getColorStyle = (colorMode: int, colorValue: int): option
|
|
88
|
+
let getColorStyle = (colorMode: int, colorValue: int): option<t> =>
|
|
75
89
|
switch (colorMode) {
|
|
76
|
-
| 3
|
|
77
|
-
| 9 => colorValue->
|
|
90
|
+
| 3 => colorValue->Foreground->Some
|
|
91
|
+
| 9 => colorValue->BrightForeground->Some
|
|
78
92
|
| 4
|
|
79
93
|
| 0 => colorValue->Background->Some
|
|
80
94
|
| _ =>
|
|
@@ -82,12 +96,16 @@ module AnsiCode = {
|
|
|
82
96
|
None;
|
|
83
97
|
};
|
|
84
98
|
|
|
85
|
-
let getColorStyleCss = (color: t): option
|
|
99
|
+
let getColorStyleCss = (color: t): option<ReactDOM.Style.t> =>
|
|
86
100
|
switch (color) {
|
|
87
101
|
| Foreground(v) =>
|
|
88
102
|
v
|
|
89
103
|
->fourBitColors
|
|
90
104
|
->Option.flatMap(color => ReactDOM.Style.make(~color, ())->Some)
|
|
105
|
+
| BrightForeground(v) =>
|
|
106
|
+
v
|
|
107
|
+
->threeBitColors
|
|
108
|
+
->Option.flatMap(color => ReactDOM.Style.make(~color, ~fontWeight="bold", ())->Some)
|
|
91
109
|
| Background(v) =>
|
|
92
110
|
v
|
|
93
111
|
->fourBitColors
|
|
@@ -96,7 +114,7 @@ module AnsiCode = {
|
|
|
96
114
|
)
|
|
97
115
|
};
|
|
98
116
|
|
|
99
|
-
let get = (colorMode: int, colorValue: int): option
|
|
117
|
+
let get = (colorMode: int, colorValue: int): option<ReactDOM.Style.t> =>
|
|
100
118
|
colorMode
|
|
101
119
|
->int_of_cp
|
|
102
120
|
->getColorStyle(colorValue->int_of_cp)
|
|
@@ -109,7 +127,7 @@ module AnsiCode = {
|
|
|
109
127
|
| Regular
|
|
110
128
|
| FontStyle(ReactDOM.Style.t);
|
|
111
129
|
|
|
112
|
-
let getFontStyle = (fontMode: int): option
|
|
130
|
+
let getFontStyle = (fontMode: int): option<t> =>
|
|
113
131
|
switch (fontMode) {
|
|
114
132
|
| 0 => Regular->Some
|
|
115
133
|
| 1 => "bold"->addWeight->FontStyle->Some
|
|
@@ -123,61 +141,66 @@ module AnsiCode = {
|
|
|
123
141
|
| _ => None
|
|
124
142
|
};
|
|
125
143
|
|
|
126
|
-
let getFontStyleCss = (font: t): option
|
|
144
|
+
let getFontStyleCss = (font: t): option<ReactDOM.Style.t> =>
|
|
127
145
|
switch (font) {
|
|
128
146
|
| Regular => None
|
|
129
147
|
| FontStyle(css) => css->Some
|
|
130
148
|
};
|
|
131
149
|
|
|
132
|
-
let get = (fontMode: int): option
|
|
150
|
+
let get = (fontMode: int): option<ReactDOM.Style.t> =>
|
|
133
151
|
fontMode->int_of_cp->getFontStyle->Option.flatMap(getFontStyleCss);
|
|
134
152
|
};
|
|
135
153
|
|
|
136
154
|
// Link management
|
|
137
155
|
module HttpLink = {
|
|
138
|
-
let linkRe =
|
|
156
|
+
let linkRe = RegExp.fromString("^(http(s)?:\\/\\/[^\\s]+)");
|
|
139
157
|
|
|
140
|
-
let get = (txt: string): parser
|
|
158
|
+
let get = (txt: string): parser<code> =>
|
|
141
159
|
linkRe
|
|
142
|
-
->
|
|
143
|
-
->Option.flatMap(res =>
|
|
144
|
-
(
|
|
145
|
-
switch (res->Js.Re.captures) {
|
|
146
|
-
| [|url, _, _|] => url->Js.Nullable.toOption
|
|
147
|
-
| _ => None
|
|
148
|
-
}
|
|
149
|
-
)
|
|
160
|
+
->RegExp.exec(txt)
|
|
161
|
+
->Option.flatMap(res => res->Array.get(0)
|
|
150
162
|
->Option.flatMap(url =>
|
|
151
163
|
(url->Js.String.length, url->HRef->Some)->Some
|
|
152
164
|
)
|
|
153
165
|
)
|
|
154
|
-
->Option.
|
|
166
|
+
->Option.getOr((1, None));
|
|
155
167
|
};
|
|
156
168
|
|
|
157
169
|
// Parse an ANSI code, returning the length of the sequence
|
|
158
|
-
let parse = (txt: string, pos: int): parser
|
|
170
|
+
let parse = (txt: string, pos: int): parser<code> =>
|
|
159
171
|
switch (Js.String.codePointAt(pos, txt)) {
|
|
160
172
|
| Some(0x68) => HttpLink.get(txt->Js.String.slice(~from=pos, ~to_=512))
|
|
161
173
|
| Some(0x0a) => (1, CarriageReturn->Some)
|
|
162
174
|
| Some(0x0d)
|
|
163
175
|
| Some(0x1b) =>
|
|
164
176
|
// escape sequence begin
|
|
165
|
-
let
|
|
177
|
+
let codePoints = [];
|
|
178
|
+
|
|
179
|
+
let rec readCodePoints = (idx: int) =>
|
|
166
180
|
switch (idx > 10, Js.String.codePointAt(pos + idx, txt)) {
|
|
167
|
-
| (false, Some(109)) =>
|
|
168
|
-
| (false, Some(n)) =>
|
|
169
|
-
|
|
181
|
+
| (false, Some(109)) => ()
|
|
182
|
+
| (false, Some(n)) => {
|
|
183
|
+
codePoints->Array.push(n);
|
|
184
|
+
readCodePoints(idx + 1)
|
|
185
|
+
}
|
|
186
|
+
| _ => ()
|
|
170
187
|
};
|
|
188
|
+
readCodePoints(1);
|
|
189
|
+
|
|
190
|
+
// use get for pattern match value, it's fine to use 'getUnsafe' because we'll get undefined value.
|
|
191
|
+
let get = (idx) => codePoints->Array.getUnsafe(idx)
|
|
171
192
|
|
|
172
|
-
|
|
173
|
-
let length = codePoints->
|
|
193
|
+
// Add 2 to take the 0x1b and 109 into account
|
|
194
|
+
let length = codePoints->Array.length + 2;
|
|
174
195
|
switch (codePoints) {
|
|
175
196
|
// \n\x0d[1A\x0d[J
|
|
176
|
-
|
|
|
197
|
+
| _ when
|
|
198
|
+
get(0) == 10 && get(1) == 27 && get(2) == 91 && get(3) == 49 &&
|
|
199
|
+
get(4) == 65 && get(5) == 27 && get(6) == 91 && get(7) == 74 => (9, EraseLine->Some)
|
|
177
200
|
// [_K
|
|
178
|
-
|
|
|
201
|
+
| _ when get(0) == 91 && get(2) == 75 => (4, EraseLine->Some)
|
|
179
202
|
// [K
|
|
180
|
-
|
|
|
203
|
+
| _ when get(0) == 91 && get(1) == 75 => (3, EraseLine->Some)
|
|
181
204
|
// [00m
|
|
182
205
|
| [91, 48, 48]
|
|
183
206
|
// [0m
|
|
@@ -196,7 +219,7 @@ module AnsiCode = {
|
|
|
196
219
|
length,
|
|
197
220
|
ColorCss.get(
|
|
198
221
|
colorMode,
|
|
199
|
-
colorValue + (xs->
|
|
222
|
+
colorValue + (xs->Array.length == 4 ? 10 : 0),
|
|
200
223
|
)
|
|
201
224
|
->Option.flatMap(colorCss => colorCss->Style->Some),
|
|
202
225
|
)
|
|
@@ -226,7 +249,7 @@ module AnsiCode = {
|
|
|
226
249
|
length,
|
|
227
250
|
ColorCss.get(cm1, cv1)
|
|
228
251
|
->Option.flatMap(colorCss1 =>
|
|
229
|
-
ColorCss.get(cm2, cv2 + (xs->
|
|
252
|
+
ColorCss.get(cm2, cv2 + (xs->Array.length == 9 ? 10 : 0))
|
|
230
253
|
->Option.flatMap(colorCss2 => {
|
|
231
254
|
let css = combine(colorCss1, colorCss2);
|
|
232
255
|
switch (style->FontCss.get) {
|
|
@@ -250,26 +273,26 @@ module Document = {
|
|
|
250
273
|
txt->Js.String.slice(~from, ~to_)->Text;
|
|
251
274
|
|
|
252
275
|
// Parse a document
|
|
253
|
-
let parse = (txt: string, length: int, pos: int): parser
|
|
276
|
+
let parse = (txt: string, length: int, pos: int): parser<document> => {
|
|
254
277
|
let rec go = (pos: int, prev: int) =>
|
|
255
278
|
switch (pos == length, txt->AnsiCode.parse(pos)) {
|
|
256
279
|
// we reached the end of the txt
|
|
257
|
-
| (true, _) => (pos,
|
|
280
|
+
| (true, _) => (pos, list{text(txt, prev, pos)}->Some)
|
|
258
281
|
// current codepoint is an ANSI code or a HRef
|
|
259
282
|
| (_, (length, Some(code))) =>
|
|
260
283
|
let prevElem = txt->text(prev, pos);
|
|
261
284
|
let pos = pos + length;
|
|
262
285
|
switch (code) {
|
|
263
|
-
| Clear => (pos,
|
|
286
|
+
| Clear => (pos, list{prevElem}->Some)
|
|
264
287
|
| EraseLine => pos->go(pos)
|
|
265
|
-
| CarriageReturn => (pos,
|
|
266
|
-
| HRef(link) => (pos,
|
|
288
|
+
| CarriageReturn => (pos, list{prevElem, LineBreak}->Some)
|
|
289
|
+
| HRef(link) => (pos, list{prevElem, link->Link}->Some)
|
|
267
290
|
| Style(style) =>
|
|
268
291
|
// recursively parse the stylized block
|
|
269
292
|
switch (pos->go(pos)) {
|
|
270
293
|
| (pos, Some(styled)) => (
|
|
271
294
|
pos,
|
|
272
|
-
|
|
295
|
+
list{prevElem, DocStyle(style, styled)}->Some,
|
|
273
296
|
)
|
|
274
297
|
| _ => (pos, None)
|
|
275
298
|
}
|
|
@@ -283,7 +306,7 @@ module Document = {
|
|
|
283
306
|
|
|
284
307
|
// Convert a string to a document
|
|
285
308
|
let parse = (txt: string): document => {
|
|
286
|
-
let rec go = (txt: string, acc: list
|
|
309
|
+
let rec go = (txt: string, acc: list<document>) => {
|
|
287
310
|
let length = txt->Js.String.length;
|
|
288
311
|
switch (txt->Document.parse(length, 0)) {
|
|
289
312
|
| (pos, Some(doc)) when pos == length => acc->List.add(doc)
|
|
@@ -292,39 +315,39 @@ let parse = (txt: string): document => {
|
|
|
292
315
|
| _ => acc
|
|
293
316
|
};
|
|
294
317
|
};
|
|
295
|
-
txt->go(
|
|
318
|
+
txt->go(list{})->Belt.List.reverse->Belt.List.flatten;
|
|
296
319
|
};
|
|
297
320
|
|
|
298
321
|
// Convert a document to a React.element
|
|
299
322
|
let render = (doc: document): React.element => {
|
|
300
323
|
let rec go =
|
|
301
|
-
(xs: document, idx: int, acc: list
|
|
324
|
+
(xs: document, idx: int, acc: list<React.element>): React.element =>
|
|
302
325
|
switch (xs) {
|
|
303
|
-
|
|
|
304
|
-
|
|
|
326
|
+
| list{} => acc->List.reverse->List.toArray->React.array
|
|
327
|
+
| list{LineBreak, ...xs} =>
|
|
305
328
|
xs->go(idx + 1, acc->List.add(<br key={idx->string_of_int} />))
|
|
306
|
-
|
|
|
307
|
-
xs->go(idx + 1, acc->List.add(
|
|
308
|
-
|
|
|
329
|
+
| list{Text(txt), ...xs} =>
|
|
330
|
+
xs->go(idx + 1, acc->List.add(React.string(txt)))
|
|
331
|
+
| list{Link(href), ...xs} =>
|
|
309
332
|
xs->go(
|
|
310
333
|
idx + 1,
|
|
311
334
|
acc->List.add(
|
|
312
|
-
<a key={idx->string_of_int} href> href->React.string </a>,
|
|
335
|
+
<a key={idx->string_of_int} href> {href->React.string} </a>,
|
|
313
336
|
),
|
|
314
337
|
)
|
|
315
|
-
|
|
|
338
|
+
| list{DocStyle(style, elems), ...xs} =>
|
|
316
339
|
xs->go(
|
|
317
340
|
idx + 1,
|
|
318
341
|
acc->List.add(
|
|
319
|
-
<span key={idx->string_of_int} style> {elems->go(0,
|
|
342
|
+
<span key={idx->string_of_int} style> {elems->go(0, list{})} </span>,
|
|
320
343
|
),
|
|
321
344
|
)
|
|
322
345
|
};
|
|
323
|
-
doc->go(0,
|
|
346
|
+
doc->go(0, list{});
|
|
324
347
|
};
|
|
325
348
|
|
|
326
349
|
// The react component
|
|
327
|
-
|
|
350
|
+
@react.component
|
|
328
351
|
let make = (~log: string) => {
|
|
329
352
|
<div> {log->parse->render} </div>;
|
|
330
353
|
};
|