@avandar/acclimate 0.2.1 → 0.3.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/dist/index.cjs +302 -105
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -2
- package/dist/index.d.ts +16 -2
- package/dist/index.js +302 -105
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { camelCase } from 'change-case';
|
|
2
2
|
import { match } from 'ts-pattern';
|
|
3
|
+
import { stdout, stdin } from 'process';
|
|
4
|
+
import { createInterface } from 'readline/promises';
|
|
3
5
|
|
|
4
6
|
// src/CLIError.ts
|
|
5
7
|
var CLIError = class _CLIError extends Error {
|
|
@@ -172,14 +174,224 @@ var defaultCLIState = {
|
|
|
172
174
|
positionalArgs: [],
|
|
173
175
|
optionArgs: {},
|
|
174
176
|
globalOptionArgs: {},
|
|
175
|
-
action:
|
|
176
|
-
}
|
|
177
|
+
action: void 0
|
|
177
178
|
};
|
|
178
179
|
|
|
179
180
|
// src/AcclimateCLI/createCLI/createCLI.ts
|
|
180
181
|
function createCLI(name) {
|
|
181
182
|
return AcclimateCLI({ ...defaultCLIState, name });
|
|
182
183
|
}
|
|
184
|
+
|
|
185
|
+
// src/generateTerminalMessage/generateTerminalMessage.ts
|
|
186
|
+
var COLOR_CODES = {
|
|
187
|
+
reset: "\x1B[0m",
|
|
188
|
+
black: "\x1B[30m",
|
|
189
|
+
red: "\x1B[31m",
|
|
190
|
+
green: "\x1B[32m",
|
|
191
|
+
yellow: "\x1B[33m",
|
|
192
|
+
blue: "\x1B[34m",
|
|
193
|
+
magenta: "\x1B[35m",
|
|
194
|
+
cyan: "\x1B[36m",
|
|
195
|
+
white: "\x1B[37m",
|
|
196
|
+
bright_black: "\x1B[90m",
|
|
197
|
+
gray: "\x1B[90m",
|
|
198
|
+
grey: "\x1B[90m",
|
|
199
|
+
bright_red: "\x1B[91m",
|
|
200
|
+
bright_green: "\x1B[92m",
|
|
201
|
+
bright_yellow: "\x1B[93m",
|
|
202
|
+
bright_blue: "\x1B[94m",
|
|
203
|
+
bright_magenta: "\x1B[95m",
|
|
204
|
+
bright_cyan: "\x1B[96m",
|
|
205
|
+
bright_white: "\x1B[97m"
|
|
206
|
+
};
|
|
207
|
+
var PARAM_TOKEN_REGEX = /\$([a-zA-Z0-9_]+)\$/g;
|
|
208
|
+
var COLOR_TOKEN_REGEX = /\|([a-zA-Z_]+)\|/g;
|
|
209
|
+
function interpolateParams(message, params) {
|
|
210
|
+
return message.replace(PARAM_TOKEN_REGEX, (match2, key) => {
|
|
211
|
+
const value = params[key];
|
|
212
|
+
if (value === void 0) {
|
|
213
|
+
return "";
|
|
214
|
+
}
|
|
215
|
+
return value;
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
function applyColors(message) {
|
|
219
|
+
return message.replace(COLOR_TOKEN_REGEX, (match2, colorName) => {
|
|
220
|
+
const code = COLOR_CODES[colorName.toLowerCase()];
|
|
221
|
+
return code ?? match2;
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
function generateTerminalMessage(message, params = {}) {
|
|
225
|
+
const hasColorToken = Boolean(message.match(COLOR_TOKEN_REGEX));
|
|
226
|
+
const endsWithReset = message.trimEnd().endsWith("|reset|");
|
|
227
|
+
const withReset = hasColorToken && !endsWithReset ? `${message}|reset|` : message;
|
|
228
|
+
const interpolated = interpolateParams(withReset, params);
|
|
229
|
+
return applyColors(interpolated);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/AcclimateCLI/generateHelpText/generateHelpText.ts
|
|
233
|
+
function formatSectionTitle(title) {
|
|
234
|
+
return `|bright_yellow|${title}|reset|`;
|
|
235
|
+
}
|
|
236
|
+
function formatNoneLine() {
|
|
237
|
+
return " |gray|None|reset|";
|
|
238
|
+
}
|
|
239
|
+
function formatDefaultValue(value) {
|
|
240
|
+
return `|gray|[default: ${JSON.stringify(value)}]|reset|`;
|
|
241
|
+
}
|
|
242
|
+
function formatParamLine(param) {
|
|
243
|
+
const description = param.description ?? "No description";
|
|
244
|
+
const requiredLabel = param.required ? "|red|required|reset|" : "|gray|optional|reset|";
|
|
245
|
+
const defaultLabel = param.defaultValue !== void 0 ? formatDefaultValue(param.defaultValue) : void 0;
|
|
246
|
+
const aliasList = param.aliases && param.aliases.length > 0 ? param.aliases.join(", ") : "";
|
|
247
|
+
const displayName = aliasList ? `${param.name}, ${aliasList}` : param.name;
|
|
248
|
+
const segments = [
|
|
249
|
+
` |bright_white|${displayName}|reset|`,
|
|
250
|
+
`(${param.type})|reset| ${requiredLabel}`,
|
|
251
|
+
`- |gray|${description}|reset|`,
|
|
252
|
+
defaultLabel
|
|
253
|
+
].filter(Boolean);
|
|
254
|
+
return segments.join(" ");
|
|
255
|
+
}
|
|
256
|
+
function formatSection(title, params) {
|
|
257
|
+
if (params.length === 0) {
|
|
258
|
+
return [formatSectionTitle(title), formatNoneLine()];
|
|
259
|
+
}
|
|
260
|
+
return [formatSectionTitle(title), ...params.map(formatParamLine)];
|
|
261
|
+
}
|
|
262
|
+
function formatCommandLine(options) {
|
|
263
|
+
const { name, description } = options;
|
|
264
|
+
const label = description ?? "No description";
|
|
265
|
+
return ` |bright_white|${name}|reset| - |gray|${label}|reset|`;
|
|
266
|
+
}
|
|
267
|
+
function formatAvailableCommandsLine(commands) {
|
|
268
|
+
const label = commands.length === 0 ? "None" : commands.map((cmd) => {
|
|
269
|
+
return cmd;
|
|
270
|
+
}).join(", ");
|
|
271
|
+
return `|bright_yellow|Available Commands:|reset| |gray|${label}|reset|`;
|
|
272
|
+
}
|
|
273
|
+
function _generateHelpTextHelper(cli, options) {
|
|
274
|
+
const name = cli.getName();
|
|
275
|
+
const description = cli.state.description;
|
|
276
|
+
const level = Number(options.level);
|
|
277
|
+
const positionalParams = cli.state.positionalArgs;
|
|
278
|
+
const optionParams = Object.values(
|
|
279
|
+
cli.state.optionArgs
|
|
280
|
+
);
|
|
281
|
+
const globalOptionParams = Object.values(
|
|
282
|
+
cli.state.globalOptionArgs
|
|
283
|
+
);
|
|
284
|
+
const sortedCommands = Object.entries(cli.state.commands).sort(([a], [b]) => {
|
|
285
|
+
return a.localeCompare(b);
|
|
286
|
+
});
|
|
287
|
+
const commandNames = sortedCommands.map(([commandName]) => {
|
|
288
|
+
return commandName;
|
|
289
|
+
});
|
|
290
|
+
const headerLines = [`|bright_cyan|${name}|reset|`];
|
|
291
|
+
if (description !== void 0) {
|
|
292
|
+
headerLines.push(` |gray|${description}|reset|`);
|
|
293
|
+
}
|
|
294
|
+
let commandSection;
|
|
295
|
+
if (level === 2) {
|
|
296
|
+
commandSection = [
|
|
297
|
+
formatSectionTitle("Commands"),
|
|
298
|
+
` ${formatAvailableCommandsLine(commandNames)}`
|
|
299
|
+
];
|
|
300
|
+
} else if (sortedCommands.length === 0) {
|
|
301
|
+
commandSection = [formatSectionTitle("Commands"), formatNoneLine()];
|
|
302
|
+
} else {
|
|
303
|
+
commandSection = [
|
|
304
|
+
formatSectionTitle("Commands"),
|
|
305
|
+
...sortedCommands.map(([commandName, commandCLI]) => {
|
|
306
|
+
return formatCommandLine({
|
|
307
|
+
name: commandName,
|
|
308
|
+
description: commandCLI.state.description
|
|
309
|
+
});
|
|
310
|
+
})
|
|
311
|
+
];
|
|
312
|
+
}
|
|
313
|
+
const sections = [
|
|
314
|
+
formatSection("Positional Arguments", positionalParams),
|
|
315
|
+
formatSection("Options", optionParams),
|
|
316
|
+
formatSection("Global Options", globalOptionParams),
|
|
317
|
+
commandSection
|
|
318
|
+
];
|
|
319
|
+
const lines = [...headerLines, ""];
|
|
320
|
+
sections.forEach((section, idx) => {
|
|
321
|
+
lines.push(...section);
|
|
322
|
+
if (idx < sections.length - 1) {
|
|
323
|
+
lines.push("");
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
if (level === 1) {
|
|
327
|
+
const subCommandHelpText = sortedCommands.map(([, commandCLI]) => {
|
|
328
|
+
return _generateHelpTextHelper(commandCLI, { level: 2 });
|
|
329
|
+
});
|
|
330
|
+
if (subCommandHelpText.length > 0) {
|
|
331
|
+
subCommandHelpText.forEach((text) => {
|
|
332
|
+
lines.push("", ...text.split("\n"));
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
const indent = " ".repeat(Math.max(0, level - 1));
|
|
337
|
+
return lines.map((line) => {
|
|
338
|
+
return line === "" ? "" : `${indent}${line}`;
|
|
339
|
+
}).join("\n");
|
|
340
|
+
}
|
|
341
|
+
function generateHelpText(cli) {
|
|
342
|
+
return generateTerminalMessage(_generateHelpTextHelper(cli, { level: 1 }));
|
|
343
|
+
}
|
|
344
|
+
var COLOR_TOKEN_REGEX2 = /\|[a-zA-Z_]+\|/g;
|
|
345
|
+
function messageHasPunctuation(message) {
|
|
346
|
+
const stripped = message.replace(COLOR_TOKEN_REGEX2, "").trimEnd();
|
|
347
|
+
return stripped.endsWith(":") || stripped.endsWith("?");
|
|
348
|
+
}
|
|
349
|
+
async function requestTerminalInput(options) {
|
|
350
|
+
const { message, params, options: promptOptions } = options;
|
|
351
|
+
const notice = promptOptions.required ? "" : " |gray|(press Enter to leave empty)|reset|";
|
|
352
|
+
const booleanNotice = promptOptions.type === "boolean" ? " |reset|(y/n)" : "";
|
|
353
|
+
const promptMessage = `${message}${booleanNotice}${notice}`;
|
|
354
|
+
const promptText = messageHasPunctuation(message) ? `${promptMessage} ` : `${promptMessage}: `;
|
|
355
|
+
const prompt = generateTerminalMessage(promptText, params);
|
|
356
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
357
|
+
try {
|
|
358
|
+
while (true) {
|
|
359
|
+
const answer = await rl.question(prompt);
|
|
360
|
+
const trimmed = answer.trim();
|
|
361
|
+
if (trimmed.length === 0) {
|
|
362
|
+
if (promptOptions.required) {
|
|
363
|
+
stdout.write(
|
|
364
|
+
generateTerminalMessage(
|
|
365
|
+
"|red|This value is required.|reset| Please enter a value.\n"
|
|
366
|
+
)
|
|
367
|
+
);
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
return void 0;
|
|
371
|
+
}
|
|
372
|
+
if (promptOptions.type === "boolean") {
|
|
373
|
+
const normalized = trimmed.toLowerCase();
|
|
374
|
+
if (normalized === "y" || normalized === "yes") {
|
|
375
|
+
return "true";
|
|
376
|
+
}
|
|
377
|
+
if (normalized === "n" || normalized === "no") {
|
|
378
|
+
return "false";
|
|
379
|
+
}
|
|
380
|
+
stdout.write(
|
|
381
|
+
generateTerminalMessage(
|
|
382
|
+
"|red|That was not a valid response.|reset| Please enter y or n.\n"
|
|
383
|
+
)
|
|
384
|
+
);
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
return answer;
|
|
388
|
+
}
|
|
389
|
+
} finally {
|
|
390
|
+
rl.close();
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// src/AcclimateCLI/runCLI/runCLI.ts
|
|
183
395
|
function _isValidFullOptionName(name) {
|
|
184
396
|
return name.startsWith("--");
|
|
185
397
|
}
|
|
@@ -248,7 +460,19 @@ function _replaceAliases({
|
|
|
248
460
|
{}
|
|
249
461
|
);
|
|
250
462
|
}
|
|
251
|
-
function
|
|
463
|
+
async function askForValue(options) {
|
|
464
|
+
const { argConfig } = options;
|
|
465
|
+
const askIfEmpty = argConfig.askIfEmpty;
|
|
466
|
+
const description = argConfig.description ? ` (${argConfig.description})` : "";
|
|
467
|
+
const baseMessage = typeof askIfEmpty === "object" ? askIfEmpty.message : `Please enter a value for ${argConfig.name}${description}`;
|
|
468
|
+
const coloredMessage = `|bright_cyan|${baseMessage}|reset|`;
|
|
469
|
+
return requestTerminalInput({
|
|
470
|
+
message: coloredMessage,
|
|
471
|
+
params: {},
|
|
472
|
+
options: { required: argConfig.required, type: argConfig.type }
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
async function _runCLIHelper(options) {
|
|
252
476
|
const { rawGlobalOptionArgs, rawOptionArgs, rawPositionalArgs, cli } = {
|
|
253
477
|
...options,
|
|
254
478
|
rawGlobalOptionArgs: _replaceAliases({
|
|
@@ -285,74 +509,79 @@ function _runCLIHelper(options) {
|
|
|
285
509
|
count: rawPositionalArgs.length
|
|
286
510
|
});
|
|
287
511
|
}
|
|
288
|
-
const parsedPositionalArgs =
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
}
|
|
297
|
-
acc[argConfig.name] = _parseAndValidateValue({
|
|
512
|
+
const parsedPositionalArgs = {};
|
|
513
|
+
for (const [idx, argConfig] of cli.state.positionalArgs.entries()) {
|
|
514
|
+
let rawVal = rawPositionalArgs[idx];
|
|
515
|
+
if (argConfig.askIfEmpty && rawVal === void 0) {
|
|
516
|
+
rawVal = await askForValue({ argConfig });
|
|
517
|
+
}
|
|
518
|
+
if (argConfig.required && rawVal === void 0) {
|
|
519
|
+
throw CLIError.missingRequiredPositionalArg({
|
|
298
520
|
cliName,
|
|
299
|
-
|
|
300
|
-
defaultValue: argConfig.defaultValue,
|
|
301
|
-
paramConfig: argConfig
|
|
521
|
+
positionalArgName: argConfig.name
|
|
302
522
|
});
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
523
|
+
}
|
|
524
|
+
parsedPositionalArgs[argConfig.name] = _parseAndValidateValue({
|
|
525
|
+
cliName,
|
|
526
|
+
inputValue: rawVal,
|
|
527
|
+
defaultValue: argConfig.defaultValue,
|
|
528
|
+
paramConfig: argConfig
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
const parsedOptionArgs = {};
|
|
532
|
+
for (const argConfig of Object.values(
|
|
308
533
|
cli.state.optionArgs
|
|
309
|
-
)
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
acc[camelCase(argConfig.name)] = _parseAndValidateValue({
|
|
534
|
+
)) {
|
|
535
|
+
let rawVal = rawOptionArgs[argConfig.name];
|
|
536
|
+
if (argConfig.askIfEmpty && rawVal === void 0) {
|
|
537
|
+
rawVal = await askForValue({ argConfig });
|
|
538
|
+
}
|
|
539
|
+
if (argConfig.required && rawVal === void 0) {
|
|
540
|
+
throw CLIError.missingRequiredOption({
|
|
319
541
|
cliName,
|
|
320
|
-
|
|
321
|
-
defaultValue: argConfig.defaultValue,
|
|
322
|
-
paramConfig: argConfig
|
|
542
|
+
optionName: argConfig.name
|
|
323
543
|
});
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
544
|
+
}
|
|
545
|
+
parsedOptionArgs[camelCase(argConfig.name)] = _parseAndValidateValue({
|
|
546
|
+
cliName,
|
|
547
|
+
inputValue: rawVal,
|
|
548
|
+
defaultValue: argConfig.defaultValue,
|
|
549
|
+
paramConfig: argConfig
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
const parsedGlobalOptionArgs = {};
|
|
553
|
+
for (const argConfig of Object.values(
|
|
329
554
|
cli.state.globalOptionArgs
|
|
330
|
-
)
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
acc[camelCase(argConfig.name)] = _parseAndValidateValue({
|
|
555
|
+
)) {
|
|
556
|
+
let rawVal = rawGlobalOptionArgs[argConfig.name];
|
|
557
|
+
if (argConfig.askIfEmpty && rawVal === void 0) {
|
|
558
|
+
rawVal = await askForValue({ argConfig });
|
|
559
|
+
}
|
|
560
|
+
if (argConfig.required && rawVal === void 0) {
|
|
561
|
+
throw CLIError.missingRequiredOption({
|
|
340
562
|
cliName,
|
|
341
|
-
|
|
342
|
-
defaultValue: argConfig.defaultValue,
|
|
343
|
-
paramConfig: argConfig
|
|
563
|
+
optionName: argConfig.name
|
|
344
564
|
});
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
565
|
+
}
|
|
566
|
+
parsedGlobalOptionArgs[camelCase(argConfig.name)] = _parseAndValidateValue({
|
|
567
|
+
cliName,
|
|
568
|
+
inputValue: rawVal,
|
|
569
|
+
defaultValue: argConfig.defaultValue,
|
|
570
|
+
paramConfig: argConfig
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
const action = cli.state.action;
|
|
574
|
+
if (action) {
|
|
575
|
+
action({
|
|
576
|
+
...parsedPositionalArgs,
|
|
577
|
+
...parsedOptionArgs,
|
|
578
|
+
...parsedGlobalOptionArgs
|
|
579
|
+
});
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
console.log(generateHelpText(cli));
|
|
354
583
|
}
|
|
355
|
-
function runCLI(options) {
|
|
584
|
+
async function runCLI(options) {
|
|
356
585
|
const { input, cli } = options;
|
|
357
586
|
const firstOptionIdx = input.findIndex((token) => {
|
|
358
587
|
return token.startsWith("-");
|
|
@@ -380,57 +609,25 @@ function runCLI(options) {
|
|
|
380
609
|
currentVals.push(argVal);
|
|
381
610
|
}
|
|
382
611
|
}
|
|
383
|
-
_runCLIHelper({
|
|
612
|
+
await _runCLIHelper({
|
|
613
|
+
rawPositionalArgs,
|
|
614
|
+
rawOptionArgs,
|
|
615
|
+
rawGlobalOptionArgs,
|
|
616
|
+
cli
|
|
617
|
+
});
|
|
384
618
|
}
|
|
385
619
|
|
|
386
620
|
// src/Acclimate.ts
|
|
387
|
-
var COLOR_CODES = {
|
|
388
|
-
reset: "\x1B[0m",
|
|
389
|
-
black: "\x1B[30m",
|
|
390
|
-
red: "\x1B[31m",
|
|
391
|
-
green: "\x1B[32m",
|
|
392
|
-
yellow: "\x1B[33m",
|
|
393
|
-
blue: "\x1B[34m",
|
|
394
|
-
magenta: "\x1B[35m",
|
|
395
|
-
cyan: "\x1B[36m",
|
|
396
|
-
white: "\x1B[37m",
|
|
397
|
-
bright_black: "\x1B[90m",
|
|
398
|
-
gray: "\x1B[90m",
|
|
399
|
-
grey: "\x1B[90m",
|
|
400
|
-
bright_red: "\x1B[91m",
|
|
401
|
-
bright_green: "\x1B[92m",
|
|
402
|
-
bright_yellow: "\x1B[93m",
|
|
403
|
-
bright_blue: "\x1B[94m",
|
|
404
|
-
bright_magenta: "\x1B[95m",
|
|
405
|
-
bright_cyan: "\x1B[96m",
|
|
406
|
-
bright_white: "\x1B[97m"
|
|
407
|
-
};
|
|
408
|
-
var PARAM_TOKEN_REGEX = /\$([a-zA-Z0-9_]+)\$/g;
|
|
409
|
-
var COLOR_TOKEN_REGEX = /\|([a-zA-Z_]+)\|/g;
|
|
410
|
-
function interpolateParams(message, params) {
|
|
411
|
-
return message.replace(PARAM_TOKEN_REGEX, (match2, key) => {
|
|
412
|
-
const value = params[key];
|
|
413
|
-
return value ?? match2;
|
|
414
|
-
});
|
|
415
|
-
}
|
|
416
|
-
function applyColors(message) {
|
|
417
|
-
return message.replace(COLOR_TOKEN_REGEX, (match2, colorName) => {
|
|
418
|
-
const code = COLOR_CODES[colorName.toLowerCase()];
|
|
419
|
-
return code ?? match2;
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
621
|
var Acclimate = {
|
|
423
622
|
createCLI,
|
|
424
623
|
run: (cli) => {
|
|
425
|
-
runCLI({ cli, input: process.argv.slice(2) });
|
|
624
|
+
void runCLI({ cli, input: process.argv.slice(2) });
|
|
426
625
|
},
|
|
427
626
|
log: (message, params = {}) => {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
const colorized = applyColors(interpolated);
|
|
433
|
-
console.log(colorized);
|
|
627
|
+
console.log(generateTerminalMessage(message, params));
|
|
628
|
+
},
|
|
629
|
+
requestInput: (options) => {
|
|
630
|
+
return requestTerminalInput(options);
|
|
434
631
|
}
|
|
435
632
|
};
|
|
436
633
|
|