cli-kiss 0.0.1 → 0.0.2
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/dist/index.d.ts +56 -16
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +4 -1
- package/src/lib/Command.ts +62 -42
- package/src/lib/Grid.ts +56 -0
- package/src/lib/Option.ts +1 -1
- package/src/lib/{Processor.ts → Process.ts} +8 -8
- package/src/lib/Reader.ts +17 -11
- package/src/lib/Run.ts +26 -9
- package/src/lib/Typo.ts +73 -0
- package/src/lib/Usage.ts +90 -68
- package/tests/unit.Reader.commons.ts +2 -2
- package/tests/unit.command.run.ts +7 -7
- package/tests/unit.command.usage.ts +102 -21
package/src/lib/Command.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { ArgumentUsage } from "./Argument";
|
|
2
2
|
import { OptionUsage } from "./Option";
|
|
3
|
-
import {
|
|
3
|
+
import { Process } from "./Process";
|
|
4
4
|
import { ReaderTokenizer } from "./Reader";
|
|
5
5
|
|
|
6
6
|
export type Command<Context, Result> = {
|
|
7
|
-
|
|
7
|
+
getTitle(): string | undefined;
|
|
8
8
|
prepareRunner(
|
|
9
9
|
readerTokenizer: ReaderTokenizer,
|
|
10
10
|
): CommandRunner<Context, Result>;
|
|
@@ -16,48 +16,55 @@ export type CommandRunner<Context, Result> = {
|
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
export type CommandUsage = {
|
|
19
|
-
breadcrumbs: Array<
|
|
20
|
-
|
|
19
|
+
breadcrumbs: Array<CommandUsageBreadcrumb>;
|
|
20
|
+
title: string;
|
|
21
|
+
description: Array<string> | undefined;
|
|
21
22
|
options: Array<OptionUsage>;
|
|
22
23
|
arguments: Array<ArgumentUsage>;
|
|
23
|
-
subcommands: Array<{
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
subcommands: Array<{ name: string; title: string | undefined }>;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type CommandUsageBreadcrumb = {
|
|
28
|
+
kind: "command" | "argument";
|
|
29
|
+
value: string;
|
|
27
30
|
};
|
|
28
31
|
|
|
29
32
|
export function command<Context, Result>(
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
metadata: {
|
|
34
|
+
title: string;
|
|
35
|
+
description?: Array<string>;
|
|
36
|
+
},
|
|
37
|
+
process: Process<Context, Result>,
|
|
32
38
|
): Command<Context, Result> {
|
|
33
39
|
return {
|
|
34
|
-
|
|
35
|
-
return
|
|
40
|
+
getTitle() {
|
|
41
|
+
return metadata.title;
|
|
36
42
|
},
|
|
37
43
|
prepareRunner(readerTokenizer: ReaderTokenizer) {
|
|
38
44
|
function computeUsage(): CommandUsage {
|
|
39
|
-
const
|
|
45
|
+
const processUsage = process.computeUsage();
|
|
40
46
|
return {
|
|
41
|
-
breadcrumbs:
|
|
42
|
-
(argument
|
|
47
|
+
breadcrumbs: processUsage.arguments.map((argument) =>
|
|
48
|
+
breadcrumbArgument(argument.label),
|
|
43
49
|
),
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
title: metadata.title,
|
|
51
|
+
description: metadata.description,
|
|
52
|
+
options: processUsage.options,
|
|
53
|
+
arguments: processUsage.arguments,
|
|
47
54
|
subcommands: [],
|
|
48
55
|
};
|
|
49
56
|
}
|
|
50
57
|
try {
|
|
51
|
-
const
|
|
58
|
+
const processResolver = process.prepareResolver(readerTokenizer);
|
|
52
59
|
const lastPositional = readerTokenizer.consumePositional();
|
|
53
60
|
if (lastPositional !== undefined) {
|
|
54
61
|
throw Error(`Unprocessed positional: ${lastPositional}`);
|
|
55
62
|
}
|
|
56
|
-
const
|
|
63
|
+
const processRunner = processResolver();
|
|
57
64
|
return {
|
|
58
65
|
computeUsage,
|
|
59
66
|
async execute(context: Context) {
|
|
60
|
-
return await
|
|
67
|
+
return await processRunner.execute(context);
|
|
61
68
|
},
|
|
62
69
|
};
|
|
63
70
|
} catch (error) {
|
|
@@ -73,17 +80,20 @@ export function command<Context, Result>(
|
|
|
73
80
|
}
|
|
74
81
|
|
|
75
82
|
export function commandWithSubcommands<Context, Payload, Result>(
|
|
76
|
-
|
|
77
|
-
|
|
83
|
+
metadata: {
|
|
84
|
+
title: string;
|
|
85
|
+
description?: Array<string>;
|
|
86
|
+
},
|
|
87
|
+
process: Process<Context, Payload>,
|
|
78
88
|
subcommands: { [subcommand: Lowercase<string>]: Command<Payload, Result> },
|
|
79
89
|
): Command<Context, Result> {
|
|
80
90
|
return {
|
|
81
|
-
|
|
82
|
-
return
|
|
91
|
+
getTitle() {
|
|
92
|
+
return metadata.title;
|
|
83
93
|
},
|
|
84
94
|
prepareRunner(readerTokenizer: ReaderTokenizer) {
|
|
85
95
|
try {
|
|
86
|
-
const
|
|
96
|
+
const processResolver = process.prepareResolver(readerTokenizer);
|
|
87
97
|
const subcommandName = readerTokenizer.consumePositional();
|
|
88
98
|
if (subcommandName === undefined) {
|
|
89
99
|
throw new Error("Expected a subcommand");
|
|
@@ -94,44 +104,46 @@ export function commandWithSubcommands<Context, Payload, Result>(
|
|
|
94
104
|
throw new Error(`Unknown subcommand: ${subcommandName}`);
|
|
95
105
|
}
|
|
96
106
|
const subcommandRunner = subcommandInput.prepareRunner(readerTokenizer);
|
|
97
|
-
const
|
|
107
|
+
const processRunner = processResolver();
|
|
98
108
|
return {
|
|
99
109
|
computeUsage() {
|
|
100
|
-
const
|
|
110
|
+
const processUsage = process.computeUsage();
|
|
101
111
|
const subcommandUsage = subcommandRunner.computeUsage();
|
|
102
112
|
return {
|
|
103
|
-
breadcrumbs:
|
|
104
|
-
.map((argument) => argument.label)
|
|
105
|
-
.concat([subcommandName])
|
|
113
|
+
breadcrumbs: processUsage.arguments
|
|
114
|
+
.map((argument) => breadcrumbArgument(argument.label))
|
|
115
|
+
.concat([breadcrumbCommand(subcommandName)])
|
|
106
116
|
.concat(subcommandUsage.breadcrumbs),
|
|
117
|
+
title: subcommandUsage.title,
|
|
107
118
|
description: subcommandUsage.description,
|
|
108
|
-
options:
|
|
109
|
-
arguments:
|
|
119
|
+
options: processUsage.options.concat(subcommandUsage.options),
|
|
120
|
+
arguments: processUsage.arguments.concat(
|
|
110
121
|
subcommandUsage.arguments,
|
|
111
122
|
),
|
|
112
123
|
subcommands: subcommandUsage.subcommands,
|
|
113
124
|
};
|
|
114
125
|
},
|
|
115
126
|
async execute(context: Context) {
|
|
116
|
-
const payload = await
|
|
127
|
+
const payload = await processRunner.execute(context);
|
|
117
128
|
return await subcommandRunner.execute(payload);
|
|
118
129
|
},
|
|
119
130
|
};
|
|
120
131
|
} catch (error) {
|
|
121
132
|
return {
|
|
122
133
|
computeUsage() {
|
|
123
|
-
const
|
|
134
|
+
const processUsage = process.computeUsage();
|
|
124
135
|
return {
|
|
125
|
-
breadcrumbs:
|
|
126
|
-
.map((argument) => argument.label)
|
|
127
|
-
.concat(["<SUBCOMMAND>"]),
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
136
|
+
breadcrumbs: processUsage.arguments
|
|
137
|
+
.map((argument) => breadcrumbArgument(argument.label))
|
|
138
|
+
.concat([breadcrumbCommand("<SUBCOMMAND>")]),
|
|
139
|
+
title: metadata.title,
|
|
140
|
+
description: metadata.description,
|
|
141
|
+
options: processUsage.options,
|
|
142
|
+
arguments: processUsage.arguments,
|
|
131
143
|
subcommands: Object.entries(subcommands).map(
|
|
132
144
|
([name, subcommand]) => ({
|
|
133
145
|
name,
|
|
134
|
-
|
|
146
|
+
title: subcommand.getTitle(),
|
|
135
147
|
}),
|
|
136
148
|
),
|
|
137
149
|
};
|
|
@@ -144,3 +156,11 @@ export function commandWithSubcommands<Context, Payload, Result>(
|
|
|
144
156
|
},
|
|
145
157
|
};
|
|
146
158
|
}
|
|
159
|
+
|
|
160
|
+
function breadcrumbArgument(label: string): CommandUsageBreadcrumb {
|
|
161
|
+
return { kind: "argument", value: label };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function breadcrumbCommand(name: string): CommandUsageBreadcrumb {
|
|
165
|
+
return { kind: "command", value: name };
|
|
166
|
+
}
|
package/src/lib/Grid.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { TypoSupport, TypoText, typoPrintableString } from "./Typo";
|
|
2
|
+
|
|
3
|
+
export type Grid = Array<GridRow>;
|
|
4
|
+
export type GridRow = Array<GridCell>;
|
|
5
|
+
export type GridCell = Array<TypoText>;
|
|
6
|
+
|
|
7
|
+
export function gridToPrintableLines(grid: Grid, typoSupport: TypoSupport) {
|
|
8
|
+
const lines = new Array<string>();
|
|
9
|
+
const gridWidths = new Array<number>();
|
|
10
|
+
for (const gridRow of grid) {
|
|
11
|
+
for (
|
|
12
|
+
let gridColumnIndex = 0;
|
|
13
|
+
gridColumnIndex < gridRow.length;
|
|
14
|
+
gridColumnIndex++
|
|
15
|
+
) {
|
|
16
|
+
const gridCell = gridRow[gridColumnIndex]!;
|
|
17
|
+
const length = gridCellLength(gridCell);
|
|
18
|
+
if (
|
|
19
|
+
gridWidths[gridColumnIndex] === undefined ||
|
|
20
|
+
length > gridWidths[gridColumnIndex]!
|
|
21
|
+
) {
|
|
22
|
+
gridWidths[gridColumnIndex] = length;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
for (const gridRow of grid) {
|
|
27
|
+
const lineColumns = new Array<string>();
|
|
28
|
+
for (
|
|
29
|
+
let gridColumnIndex = 0;
|
|
30
|
+
gridColumnIndex < gridRow.length;
|
|
31
|
+
gridColumnIndex++
|
|
32
|
+
) {
|
|
33
|
+
const gridCell = gridRow[gridColumnIndex]!;
|
|
34
|
+
const parts = gridCell.map((text) =>
|
|
35
|
+
typoPrintableString(typoSupport, text),
|
|
36
|
+
);
|
|
37
|
+
if (gridColumnIndex < gridRow.length - 1) {
|
|
38
|
+
const length = gridCellLength(gridCell);
|
|
39
|
+
const padding = " ".repeat(gridWidths[gridColumnIndex]! - length);
|
|
40
|
+
lineColumns.push(parts.join("") + padding);
|
|
41
|
+
} else {
|
|
42
|
+
lineColumns.push(parts.join(""));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
lines.push(lineColumns.join(" "));
|
|
46
|
+
}
|
|
47
|
+
return lines;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function gridCellLength(cell: GridCell): number {
|
|
51
|
+
let length = 0;
|
|
52
|
+
for (const text of cell) {
|
|
53
|
+
length += text.value.length;
|
|
54
|
+
}
|
|
55
|
+
return length;
|
|
56
|
+
}
|
package/src/lib/Option.ts
CHANGED
|
@@ -122,7 +122,7 @@ export function optionSingleValue<Value>(definition: {
|
|
|
122
122
|
}
|
|
123
123
|
readerTokenizer.registerOption({ key, longs, shorts });
|
|
124
124
|
return () => {
|
|
125
|
-
// TODO - error handling
|
|
125
|
+
// TODO - smooth and beautiful error handling
|
|
126
126
|
const values = readerTokenizer.consumeOption(definition.long);
|
|
127
127
|
if (values.length > 1) {
|
|
128
128
|
throw new Error(
|
|
@@ -2,28 +2,28 @@ import { Argument, ArgumentUsage } from "./Argument";
|
|
|
2
2
|
import { Option, OptionUsage } from "./Option";
|
|
3
3
|
import { ReaderTokenizer } from "./Reader";
|
|
4
4
|
|
|
5
|
-
export type
|
|
6
|
-
computeUsage():
|
|
5
|
+
export type Process<Context, Result> = {
|
|
6
|
+
computeUsage(): ProcessUsage;
|
|
7
7
|
prepareResolver(
|
|
8
8
|
readerTokenizer: ReaderTokenizer,
|
|
9
|
-
):
|
|
9
|
+
): ProcessResolver<Context, Result>;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
export type
|
|
12
|
+
export type ProcessResolver<Context, Result> = () => ProcessRunner<
|
|
13
13
|
Context,
|
|
14
14
|
Result
|
|
15
15
|
>;
|
|
16
16
|
|
|
17
|
-
export type
|
|
17
|
+
export type ProcessRunner<Context, Result> = {
|
|
18
18
|
execute(context: Context): Promise<Result>;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
-
export type
|
|
21
|
+
export type ProcessUsage = {
|
|
22
22
|
options: Array<OptionUsage>;
|
|
23
23
|
arguments: Array<ArgumentUsage>;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
export function
|
|
26
|
+
export function process<
|
|
27
27
|
Context,
|
|
28
28
|
Result,
|
|
29
29
|
Options extends { [option: string]: Option<any> },
|
|
@@ -43,7 +43,7 @@ export function processor<
|
|
|
43
43
|
};
|
|
44
44
|
},
|
|
45
45
|
) => Promise<Result>,
|
|
46
|
-
):
|
|
46
|
+
): Process<Context, Result> {
|
|
47
47
|
return {
|
|
48
48
|
computeUsage() {
|
|
49
49
|
const optionsUsage = new Array<OptionUsage>();
|
package/src/lib/Reader.ts
CHANGED
|
@@ -187,11 +187,9 @@ export class ReaderTokenizer {
|
|
|
187
187
|
const flagKey = this.#flagKeyByLong.get(long);
|
|
188
188
|
if (flagKey !== undefined) {
|
|
189
189
|
if (direct !== null) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
if (direct === "false") {
|
|
194
|
-
return this.#acknowledgeFlag(flagKey, false);
|
|
190
|
+
const value = asBoolean(direct);
|
|
191
|
+
if (value !== undefined) {
|
|
192
|
+
return this.#acknowledgeFlag(flagKey, value);
|
|
195
193
|
}
|
|
196
194
|
throw new Error(
|
|
197
195
|
`Invalid parameter for long flag: ${flagKey}, value: ${direct}`,
|
|
@@ -213,12 +211,9 @@ export class ReaderTokenizer {
|
|
|
213
211
|
const flagKey = this.#flagKeyByShort.get(short);
|
|
214
212
|
if (flagKey !== undefined) {
|
|
215
213
|
if (rest.startsWith("=")) {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
220
|
-
if (rest === "=false") {
|
|
221
|
-
this.#acknowledgeFlag(flagKey, false);
|
|
214
|
+
const value = asBoolean(rest.slice(1));
|
|
215
|
+
if (value !== undefined) {
|
|
216
|
+
this.#acknowledgeFlag(flagKey, value);
|
|
222
217
|
return true;
|
|
223
218
|
}
|
|
224
219
|
throw new Error(
|
|
@@ -283,3 +278,14 @@ export class ReaderTokenizer {
|
|
|
283
278
|
}
|
|
284
279
|
}
|
|
285
280
|
}
|
|
281
|
+
|
|
282
|
+
function asBoolean(value: string): boolean | undefined {
|
|
283
|
+
const lower = value.toLowerCase();
|
|
284
|
+
if (lower === "true" || lower === "t" || lower === "y" || lower === "yes") {
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
if (lower === "false" || lower === "f" || lower === "n" || lower === "no") {
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
return undefined;
|
|
291
|
+
}
|
package/src/lib/Run.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { Command } from "./Command";
|
|
2
2
|
import { ReaderTokenizer } from "./Reader";
|
|
3
|
-
import {
|
|
3
|
+
import { typoInferSupport } from "./Typo";
|
|
4
|
+
import { usageToPrintableLines } from "./Usage";
|
|
4
5
|
|
|
5
6
|
export async function runWithArgv<Context, Result>(
|
|
6
7
|
argv: string[],
|
|
7
8
|
context: Context,
|
|
8
9
|
command: Command<Context, Result>,
|
|
9
|
-
cliInfo?: {
|
|
10
|
+
cliInfo?: {
|
|
11
|
+
name?: Lowercase<string>;
|
|
12
|
+
version?: string;
|
|
13
|
+
helpOnError?: boolean;
|
|
14
|
+
},
|
|
10
15
|
): Promise<Result> {
|
|
11
16
|
const cliName = cliInfo?.name ?? argv[1]!;
|
|
12
17
|
const readerTokenizer = new ReaderTokenizer(argv.slice(2));
|
|
13
|
-
readerTokenizer.registerFlag({
|
|
14
|
-
key: "help",
|
|
15
|
-
shorts: [],
|
|
16
|
-
longs: ["help"],
|
|
17
|
-
});
|
|
18
18
|
if (cliInfo?.version) {
|
|
19
19
|
readerTokenizer.registerFlag({
|
|
20
20
|
key: "version",
|
|
@@ -22,6 +22,11 @@ export async function runWithArgv<Context, Result>(
|
|
|
22
22
|
longs: ["version"],
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
|
+
readerTokenizer.registerFlag({
|
|
26
|
+
key: "help",
|
|
27
|
+
shorts: [],
|
|
28
|
+
longs: ["help"],
|
|
29
|
+
});
|
|
25
30
|
/*
|
|
26
31
|
// TODO - handle completions ?
|
|
27
32
|
readerTokenizer.registerFlag({
|
|
@@ -39,14 +44,26 @@ export async function runWithArgv<Context, Result>(
|
|
|
39
44
|
}
|
|
40
45
|
}
|
|
41
46
|
if (readerTokenizer.consumeFlag("help")) {
|
|
42
|
-
console.log(
|
|
47
|
+
console.log(
|
|
48
|
+
usageToPrintableLines({
|
|
49
|
+
cliName,
|
|
50
|
+
commandUsage: commandRunner.computeUsage(),
|
|
51
|
+
typoSupport: typoInferSupport(),
|
|
52
|
+
}).join("\n"),
|
|
53
|
+
);
|
|
43
54
|
process.exit(0);
|
|
44
55
|
}
|
|
45
56
|
try {
|
|
46
57
|
return await commandRunner.execute(context);
|
|
47
58
|
} catch (error) {
|
|
48
59
|
if (cliInfo?.helpOnError ?? true) {
|
|
49
|
-
console.log(
|
|
60
|
+
console.log(
|
|
61
|
+
usageToPrintableLines({
|
|
62
|
+
cliName,
|
|
63
|
+
commandUsage: commandRunner.computeUsage(),
|
|
64
|
+
typoSupport: typoInferSupport(),
|
|
65
|
+
}).join("\n"),
|
|
66
|
+
);
|
|
50
67
|
}
|
|
51
68
|
console.error(error);
|
|
52
69
|
process.exit(1);
|
package/src/lib/Typo.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export type TypoSupport = "none" | "tty" | "html" | "mock";
|
|
2
|
+
|
|
3
|
+
export type TypoText = {
|
|
4
|
+
value: string;
|
|
5
|
+
color?: keyof typeof colorCodes;
|
|
6
|
+
bold?: boolean;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function typoPrintableString(
|
|
10
|
+
typoSupport: TypoSupport,
|
|
11
|
+
typoText: TypoText,
|
|
12
|
+
): string {
|
|
13
|
+
if (typoSupport === "none") {
|
|
14
|
+
return typoText.value;
|
|
15
|
+
}
|
|
16
|
+
if (typoSupport === "tty") {
|
|
17
|
+
const colorStartCode = typoText.color ? colorCodes[typoText.color] : "";
|
|
18
|
+
const colorBoldCode = typoText.bold ? boldCode : "";
|
|
19
|
+
return `${colorStartCode}${colorBoldCode}${typoText.value}${resetCode}`;
|
|
20
|
+
}
|
|
21
|
+
if (typoSupport === "html") {
|
|
22
|
+
const colorStartTag = typoText.color
|
|
23
|
+
? `<span style="color: ${typoText.color}">`
|
|
24
|
+
: "";
|
|
25
|
+
const colorEndTag = typoText.color ? "</span>" : "";
|
|
26
|
+
const boldStartTag = typoText.bold ? "<b>" : "";
|
|
27
|
+
const boldEndTag = typoText.bold ? "</b>" : "";
|
|
28
|
+
return `${colorStartTag}${boldStartTag}${typoText.value}${boldEndTag}${colorEndTag}`;
|
|
29
|
+
}
|
|
30
|
+
if (typoSupport === "mock") {
|
|
31
|
+
if (typoText.color && typoText.bold) {
|
|
32
|
+
return `{${typoText.value}}@${typoText.color}+`;
|
|
33
|
+
}
|
|
34
|
+
if (typoText.color) {
|
|
35
|
+
return `{${typoText.value}}@${typoText.color}`;
|
|
36
|
+
}
|
|
37
|
+
if (typoText.bold) {
|
|
38
|
+
return `{${typoText.value}}+`;
|
|
39
|
+
}
|
|
40
|
+
return `{${typoText.value}}`;
|
|
41
|
+
}
|
|
42
|
+
throw new Error(`Unknown typo support: ${typoSupport}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function typoInferSupport(): TypoSupport {
|
|
46
|
+
if (process.env) {
|
|
47
|
+
if (process.env["FORCE_COLOR"] === "0") {
|
|
48
|
+
return "none";
|
|
49
|
+
}
|
|
50
|
+
if (process.env["FORCE_COLOR"]) {
|
|
51
|
+
return "tty";
|
|
52
|
+
}
|
|
53
|
+
if ("NO_COLOR" in process.env) {
|
|
54
|
+
return "none";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (!process || !process.stdout || !process.stdout.isTTY) {
|
|
58
|
+
return "none";
|
|
59
|
+
}
|
|
60
|
+
return "tty";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const resetCode = "\x1b[0m";
|
|
64
|
+
const boldCode = "\x1b[1m";
|
|
65
|
+
const colorCodes = {
|
|
66
|
+
red: "\x1b[31m",
|
|
67
|
+
green: "\x1b[32m",
|
|
68
|
+
yellow: "\x1b[33m",
|
|
69
|
+
blue: "\x1b[34m",
|
|
70
|
+
magenta: "\x1b[35m",
|
|
71
|
+
cyan: "\x1b[36m",
|
|
72
|
+
grey: "\x1b[90m",
|
|
73
|
+
};
|