@lewishowles/cli-style 0.1.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/LICENSE +21 -0
- package/README.md +153 -0
- package/adapters/bash/cli-style.sh +50 -0
- package/adapters/python/cli_style.py +104 -0
- package/bin/cli-style.js +82 -0
- package/examples/diagnostic.mjs +30 -0
- package/examples/diagnostic.py +38 -0
- package/examples/diagnostic.sh +29 -0
- package/examples/gallery.mjs +18 -0
- package/package.json +42 -0
- package/src/capability/resolve-profile.js +69 -0
- package/src/capability/terminal.js +105 -0
- package/src/cli/gallery-command.js +238 -0
- package/src/cli/help.js +35 -0
- package/src/cli/render-command.js +283 -0
- package/src/create-cli-style.js +53 -0
- package/src/formatters/ansi.js +154 -0
- package/src/gallery/render-gallery.js +758 -0
- package/src/index.js +52 -0
- package/src/patterns/agent-transcript.js +180 -0
- package/src/patterns/audit-finding.js +107 -0
- package/src/patterns/command-result.js +91 -0
- package/src/patterns/compact-data-table.js +67 -0
- package/src/patterns/confirmation-result.js +88 -0
- package/src/patterns/diagnostic-report.js +144 -0
- package/src/patterns/helpers.js +106 -0
- package/src/patterns/next-step-block.js +118 -0
- package/src/patterns/task-summary.js +59 -0
- package/src/primitives/bar-chart.js +131 -0
- package/src/primitives/chip.js +33 -0
- package/src/primitives/divider.js +64 -0
- package/src/primitives/feedback.js +83 -0
- package/src/primitives/panel.js +100 -0
- package/src/primitives/progress-bar.js +121 -0
- package/src/primitives/row.js +80 -0
- package/src/primitives/status.js +39 -0
- package/src/primitives/step-progress.js +184 -0
- package/src/primitives/table.js +239 -0
- package/src/profiles/profiles.js +24 -0
- package/src/theme/colours.js +141 -0
- package/src/theme/results.js +98 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { foreground } from "../formatters/ansi.js";
|
|
2
|
+
import { tableColours } from "../theme/colours.js";
|
|
3
|
+
|
|
4
|
+
// Tables assume a standard terminal width when no capability is provided.
|
|
5
|
+
const defaultWidth = 80;
|
|
6
|
+
|
|
7
|
+
// Columns use a compact but readable gap.
|
|
8
|
+
const columnSeparator = " ";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Render tabular data with a narrow-width key/value fallback.
|
|
12
|
+
*
|
|
13
|
+
* @param {object} options
|
|
14
|
+
* Rendering options.
|
|
15
|
+
* @param {boolean} options.colour
|
|
16
|
+
* Whether ANSI colour should be applied.
|
|
17
|
+
* @param {object[]} options.columns
|
|
18
|
+
* Column keys and labels.
|
|
19
|
+
* @param {object[]} options.rows
|
|
20
|
+
* Table records.
|
|
21
|
+
* @param {boolean} options.unicode
|
|
22
|
+
* Whether Unicode rule characters should be used.
|
|
23
|
+
* @param {number} options.width
|
|
24
|
+
* Available output width.
|
|
25
|
+
* @returns {string}
|
|
26
|
+
* Rendered table.
|
|
27
|
+
*/
|
|
28
|
+
export function table(options = {}) {
|
|
29
|
+
const columns = normaliseColumns(options.columns);
|
|
30
|
+
const rows = normaliseRows(options.rows);
|
|
31
|
+
|
|
32
|
+
if (columns.length === 0 || rows.length === 0) {
|
|
33
|
+
return "";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const widths = columnWidths(columns, rows);
|
|
37
|
+
const naturalWidth = widths.reduce((total, width) => total + width, 0)
|
|
38
|
+
+ (columnSeparator.length * (columns.length - 1));
|
|
39
|
+
const availableWidth = Number.isFinite(options.width) ? options.width : defaultWidth;
|
|
40
|
+
|
|
41
|
+
if (naturalWidth > availableWidth) {
|
|
42
|
+
return renderBlocks(columns, rows, options);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return renderTable(columns, rows, widths, options);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Return columns with usable keys and labels.
|
|
50
|
+
*
|
|
51
|
+
* @param {object[]} columns
|
|
52
|
+
* Requested columns.
|
|
53
|
+
* @returns {object[]}
|
|
54
|
+
* Valid columns.
|
|
55
|
+
*/
|
|
56
|
+
function normaliseColumns(columns) {
|
|
57
|
+
if (!Array.isArray(columns)) {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return columns.filter((column) => (
|
|
62
|
+
column !== null
|
|
63
|
+
&& typeof column === "object"
|
|
64
|
+
&& typeof column.key === "string"
|
|
65
|
+
&& column.key !== ""
|
|
66
|
+
&& typeof column.label === "string"
|
|
67
|
+
&& column.label !== ""
|
|
68
|
+
));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Return object records suitable for rendering.
|
|
73
|
+
*
|
|
74
|
+
* @param {object[]} rows
|
|
75
|
+
* Requested rows.
|
|
76
|
+
* @returns {object[]}
|
|
77
|
+
* Valid rows.
|
|
78
|
+
*/
|
|
79
|
+
function normaliseRows(rows) {
|
|
80
|
+
if (!Array.isArray(rows)) {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return rows.filter((row) => row !== null && typeof row === "object");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Calculate the display width of each column.
|
|
89
|
+
*
|
|
90
|
+
* @param {object[]} columns
|
|
91
|
+
* Table columns.
|
|
92
|
+
* @param {object[]} rows
|
|
93
|
+
* Table records.
|
|
94
|
+
* @returns {number[]}
|
|
95
|
+
* Column widths.
|
|
96
|
+
*/
|
|
97
|
+
function columnWidths(columns, rows) {
|
|
98
|
+
return columns.map((column) => Math.max(
|
|
99
|
+
column.label.length,
|
|
100
|
+
...rows.map((row) => cellValue(row[column.key]).length),
|
|
101
|
+
));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Render a wide aligned table.
|
|
106
|
+
*
|
|
107
|
+
* @param {object[]} columns
|
|
108
|
+
* Table columns.
|
|
109
|
+
* @param {object[]} rows
|
|
110
|
+
* Table records.
|
|
111
|
+
* @param {number[]} widths
|
|
112
|
+
* Column widths.
|
|
113
|
+
* @param {object} options
|
|
114
|
+
* Rendering options.
|
|
115
|
+
* @returns {string}
|
|
116
|
+
* Rendered table.
|
|
117
|
+
*/
|
|
118
|
+
function renderTable(columns, rows, widths, options) {
|
|
119
|
+
const header = columns
|
|
120
|
+
.map((column, index) => renderHeader(
|
|
121
|
+
padColumn(column.label, widths[index], index, columns.length),
|
|
122
|
+
options,
|
|
123
|
+
))
|
|
124
|
+
.join(columnSeparator);
|
|
125
|
+
const ruleCharacter = options.unicode === false ? "-" : "─";
|
|
126
|
+
const rule = widths
|
|
127
|
+
.map((width) => ruleCharacter.repeat(width))
|
|
128
|
+
.join(columnSeparator);
|
|
129
|
+
const renderedRule = options.colour === true
|
|
130
|
+
? foreground(rule, tableColours.border, options)
|
|
131
|
+
: rule;
|
|
132
|
+
const body = rows.map((row) => columns
|
|
133
|
+
.map((column, index) => renderCell(
|
|
134
|
+
padColumn(cellValue(row[column.key]), widths[index], index, columns.length),
|
|
135
|
+
index,
|
|
136
|
+
options,
|
|
137
|
+
))
|
|
138
|
+
.join(columnSeparator));
|
|
139
|
+
|
|
140
|
+
return [header, renderedRule, ...body].join("\n");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Pad columns except the final visible value.
|
|
145
|
+
*
|
|
146
|
+
* @param {string} value
|
|
147
|
+
* Column text.
|
|
148
|
+
* @param {number} width
|
|
149
|
+
* Calculated column width.
|
|
150
|
+
* @param {number} index
|
|
151
|
+
* Zero-based column position.
|
|
152
|
+
* @param {number} columnCount
|
|
153
|
+
* Total visible columns.
|
|
154
|
+
* @returns {string}
|
|
155
|
+
* Padded column text.
|
|
156
|
+
*/
|
|
157
|
+
function padColumn(value, width, index, columnCount) {
|
|
158
|
+
return index === columnCount - 1 ? value : value.padEnd(width, " ");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Render records as separated key/value blocks.
|
|
163
|
+
*
|
|
164
|
+
* @param {object[]} columns
|
|
165
|
+
* Table columns.
|
|
166
|
+
* @param {object[]} rows
|
|
167
|
+
* Table records.
|
|
168
|
+
* @param {object} options
|
|
169
|
+
* Rendering options.
|
|
170
|
+
* @returns {string}
|
|
171
|
+
* Rendered key/value blocks.
|
|
172
|
+
*/
|
|
173
|
+
function renderBlocks(columns, rows, options) {
|
|
174
|
+
const labelWidth = Math.max(...columns.map((column) => column.label.length));
|
|
175
|
+
|
|
176
|
+
return rows.map((row) => columns
|
|
177
|
+
.map((column) => {
|
|
178
|
+
const label = renderHeader(column.label.padEnd(labelWidth, " "), options);
|
|
179
|
+
const value = renderCell(cellValue(row[column.key]), 0, options);
|
|
180
|
+
|
|
181
|
+
return `${label}${columnSeparator}${value}`;
|
|
182
|
+
})
|
|
183
|
+
.join("\n"))
|
|
184
|
+
.join("\n\n");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Convert a cell value to display text.
|
|
189
|
+
*
|
|
190
|
+
* @param {*} value
|
|
191
|
+
* Cell value.
|
|
192
|
+
* @returns {string}
|
|
193
|
+
* Display text.
|
|
194
|
+
*/
|
|
195
|
+
function cellValue(value) {
|
|
196
|
+
return value === null || value === undefined ? "" : String(value);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Render table header text.
|
|
201
|
+
*
|
|
202
|
+
* @param {string} value
|
|
203
|
+
* Header text.
|
|
204
|
+
* @param {object} options
|
|
205
|
+
* Rendering options.
|
|
206
|
+
* @returns {string}
|
|
207
|
+
* Rendered header.
|
|
208
|
+
*/
|
|
209
|
+
function renderHeader(value, options) {
|
|
210
|
+
if (options.colour !== true) {
|
|
211
|
+
return value;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return foreground(value, tableColours.header, options);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Render table cell text.
|
|
219
|
+
*
|
|
220
|
+
* @param {string} value
|
|
221
|
+
* Cell text.
|
|
222
|
+
* @param {number} columnIndex
|
|
223
|
+
* Zero-based column position.
|
|
224
|
+
* @param {object} options
|
|
225
|
+
* Rendering options.
|
|
226
|
+
* @returns {string}
|
|
227
|
+
* Rendered cell.
|
|
228
|
+
*/
|
|
229
|
+
function renderCell(value, columnIndex, options) {
|
|
230
|
+
if (options.colour !== true) {
|
|
231
|
+
return value;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const colour = columnIndex === 0
|
|
235
|
+
? tableColours.primaryText
|
|
236
|
+
: tableColours.secondaryText;
|
|
237
|
+
|
|
238
|
+
return foreground(value, colour, options);
|
|
239
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Supported output profiles for terminal rendering.
|
|
2
|
+
export const profiles = {
|
|
3
|
+
AGENT: "agent",
|
|
4
|
+
CI: "ci",
|
|
5
|
+
DIAGNOSTIC: "diagnostic",
|
|
6
|
+
HUMAN: "human",
|
|
7
|
+
JSON: "json",
|
|
8
|
+
PLAIN: "plain",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// Profile names accepted by public APIs and CLI flags.
|
|
12
|
+
const profileValues = new Set(Object.values(profiles));
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Check whether a profile name is supported.
|
|
16
|
+
*
|
|
17
|
+
* @param {string} profile
|
|
18
|
+
* Profile name to check.
|
|
19
|
+
* @returns {boolean}
|
|
20
|
+
* Whether the profile is supported.
|
|
21
|
+
*/
|
|
22
|
+
export function isProfile(profile) {
|
|
23
|
+
return profileValues.has(profile);
|
|
24
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// Semantic colour tokens for terminal output and generated previews.
|
|
2
|
+
export const colourTokens = {
|
|
3
|
+
accent: "#c58cff",
|
|
4
|
+
background: "#070b0d",
|
|
5
|
+
border: "#2a3a42",
|
|
6
|
+
danger: "#ff7272",
|
|
7
|
+
info: "#8bbdff",
|
|
8
|
+
muted: "#94a3aa",
|
|
9
|
+
success: "#8fdf72",
|
|
10
|
+
surface: "#0f181b",
|
|
11
|
+
surfaceRaised: "#132024",
|
|
12
|
+
text: "#e8eef0",
|
|
13
|
+
warning: "#f4bd5f",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// Terminal frame colours for gallery-style previews.
|
|
17
|
+
export const terminalColours = {
|
|
18
|
+
background: "background",
|
|
19
|
+
border: "border",
|
|
20
|
+
title: "muted",
|
|
21
|
+
trafficLights: {
|
|
22
|
+
close: "#ff7272",
|
|
23
|
+
minimise: "#f4bd5f",
|
|
24
|
+
zoom: "#8fdf72",
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Chip and pill colour pairs for speaker labels and compact states.
|
|
29
|
+
export const chipColours = {
|
|
30
|
+
agent: {
|
|
31
|
+
background: "#3a2815",
|
|
32
|
+
foreground: "warning",
|
|
33
|
+
},
|
|
34
|
+
danger: {
|
|
35
|
+
background: "#3c1d22",
|
|
36
|
+
foreground: "danger",
|
|
37
|
+
},
|
|
38
|
+
info: {
|
|
39
|
+
background: "#14273b",
|
|
40
|
+
foreground: "info",
|
|
41
|
+
},
|
|
42
|
+
neutral: {
|
|
43
|
+
background: "#172126",
|
|
44
|
+
foreground: "muted",
|
|
45
|
+
},
|
|
46
|
+
success: {
|
|
47
|
+
background: "#18351f",
|
|
48
|
+
foreground: "success",
|
|
49
|
+
},
|
|
50
|
+
user: {
|
|
51
|
+
background: "#1f3b1f",
|
|
52
|
+
foreground: "success",
|
|
53
|
+
},
|
|
54
|
+
warning: {
|
|
55
|
+
background: "#3a2815",
|
|
56
|
+
foreground: "warning",
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Panel colours for grouped reports and agent responses.
|
|
61
|
+
export const panelColours = {
|
|
62
|
+
background: "surface",
|
|
63
|
+
border: "border",
|
|
64
|
+
danger: "danger",
|
|
65
|
+
info: "info",
|
|
66
|
+
success: "success",
|
|
67
|
+
warning: "warning",
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Table colours for dense operational data.
|
|
71
|
+
export const tableColours = {
|
|
72
|
+
border: "border",
|
|
73
|
+
header: "muted",
|
|
74
|
+
primaryText: "text",
|
|
75
|
+
secondaryText: "muted",
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Chart colours for simple bars and positive/negative trend segments.
|
|
79
|
+
export const chartColours = {
|
|
80
|
+
barNegative: "#c48745",
|
|
81
|
+
barPositive: "success",
|
|
82
|
+
barWarning: "warning",
|
|
83
|
+
label: "muted",
|
|
84
|
+
value: "text",
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Prompt colours for lightweight interactive flows.
|
|
88
|
+
export const promptColours = {
|
|
89
|
+
active: "success",
|
|
90
|
+
inactive: "text",
|
|
91
|
+
marker: "success",
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Result tones map semantic outcomes onto colour token names.
|
|
95
|
+
export const toneColours = {
|
|
96
|
+
danger: "danger",
|
|
97
|
+
info: "info",
|
|
98
|
+
muted: "muted",
|
|
99
|
+
success: "success",
|
|
100
|
+
warning: "warning",
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Resolve a colour token by name.
|
|
105
|
+
*
|
|
106
|
+
* @param {string} colourName
|
|
107
|
+
* Colour token name.
|
|
108
|
+
* @returns {string|null}
|
|
109
|
+
* Hex colour value when supported.
|
|
110
|
+
*/
|
|
111
|
+
export function getColourToken(colourName) {
|
|
112
|
+
return colourTokens[colourName] ?? null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Resolve a tone to a colour token value.
|
|
117
|
+
*
|
|
118
|
+
* @param {string} tone
|
|
119
|
+
* Tone name to resolve.
|
|
120
|
+
* @returns {string|null}
|
|
121
|
+
* Hex colour value when supported.
|
|
122
|
+
*/
|
|
123
|
+
export function getToneColour(tone) {
|
|
124
|
+
return getColourToken(toneColours[tone]);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Resolve a direct hex colour or colour token reference.
|
|
129
|
+
*
|
|
130
|
+
* @param {string} colour
|
|
131
|
+
* Hex colour value or colour token name.
|
|
132
|
+
* @returns {string|null}
|
|
133
|
+
* Hex colour value when supported.
|
|
134
|
+
*/
|
|
135
|
+
export function resolveColourValue(colour) {
|
|
136
|
+
if (colour?.startsWith("#")) {
|
|
137
|
+
return colour;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return getColourToken(colour);
|
|
141
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Result states shared by primitive and pattern renderers.
|
|
2
|
+
export const resultTypes = {
|
|
3
|
+
FAILED: "failed",
|
|
4
|
+
PARTIAL: "partial",
|
|
5
|
+
SKIPPED: "skipped",
|
|
6
|
+
SUCCESS: "success",
|
|
7
|
+
UNKNOWN: "unknown",
|
|
8
|
+
WARNING: "warning",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// Display tokens for result states before colour rendering exists.
|
|
12
|
+
export const resultTokens = {
|
|
13
|
+
[resultTypes.FAILED]: {
|
|
14
|
+
exitCode: 1,
|
|
15
|
+
label: "Failed",
|
|
16
|
+
symbols: {
|
|
17
|
+
ascii: "x",
|
|
18
|
+
unicode: "×",
|
|
19
|
+
},
|
|
20
|
+
tone: "danger",
|
|
21
|
+
},
|
|
22
|
+
[resultTypes.PARTIAL]: {
|
|
23
|
+
exitCode: null,
|
|
24
|
+
label: "Partial",
|
|
25
|
+
symbols: {
|
|
26
|
+
ascii: "!",
|
|
27
|
+
unicode: "◐",
|
|
28
|
+
},
|
|
29
|
+
tone: "warning",
|
|
30
|
+
},
|
|
31
|
+
[resultTypes.SKIPPED]: {
|
|
32
|
+
exitCode: null,
|
|
33
|
+
label: "Skipped",
|
|
34
|
+
symbols: {
|
|
35
|
+
ascii: "-",
|
|
36
|
+
unicode: "–",
|
|
37
|
+
},
|
|
38
|
+
tone: "muted",
|
|
39
|
+
},
|
|
40
|
+
[resultTypes.SUCCESS]: {
|
|
41
|
+
exitCode: 0,
|
|
42
|
+
label: "Success",
|
|
43
|
+
symbols: {
|
|
44
|
+
ascii: "OK",
|
|
45
|
+
unicode: "✓",
|
|
46
|
+
},
|
|
47
|
+
tone: "success",
|
|
48
|
+
},
|
|
49
|
+
[resultTypes.UNKNOWN]: {
|
|
50
|
+
exitCode: null,
|
|
51
|
+
label: "Unknown",
|
|
52
|
+
symbols: {
|
|
53
|
+
ascii: "?",
|
|
54
|
+
unicode: "?",
|
|
55
|
+
},
|
|
56
|
+
tone: "muted",
|
|
57
|
+
},
|
|
58
|
+
[resultTypes.WARNING]: {
|
|
59
|
+
exitCode: null,
|
|
60
|
+
label: "Warning",
|
|
61
|
+
symbols: {
|
|
62
|
+
ascii: "!",
|
|
63
|
+
unicode: "⚠",
|
|
64
|
+
},
|
|
65
|
+
tone: "warning",
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Resolve the token for a result type.
|
|
71
|
+
*
|
|
72
|
+
* @param {string} resultType
|
|
73
|
+
* Result type to resolve.
|
|
74
|
+
* @returns {object}
|
|
75
|
+
* Matching result token, or the unknown token for unsupported values.
|
|
76
|
+
*/
|
|
77
|
+
export function getResultToken(resultType) {
|
|
78
|
+
return resultTokens[resultType] ?? resultTokens[resultTypes.UNKNOWN];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Resolve the display symbol for a result type.
|
|
83
|
+
*
|
|
84
|
+
* @param {string} resultType
|
|
85
|
+
* Result type to resolve.
|
|
86
|
+
* @param {object} options
|
|
87
|
+
* Symbol rendering options.
|
|
88
|
+
* @param {boolean} options.unicode
|
|
89
|
+
* Whether to return the Unicode symbol.
|
|
90
|
+
* @returns {string}
|
|
91
|
+
* Display symbol for the requested symbol mode.
|
|
92
|
+
*/
|
|
93
|
+
export function getResultSymbol(resultType, options = {}) {
|
|
94
|
+
const token = getResultToken(resultType);
|
|
95
|
+
const usesUnicode = options.unicode ?? true;
|
|
96
|
+
|
|
97
|
+
return usesUnicode ? token.symbols.unicode : token.symbols.ascii;
|
|
98
|
+
}
|