Formatting pass
This commit is contained in:
parent
136b3e386c
commit
e925828328
@ -1,88 +1,92 @@
|
|||||||
import { type ParseArgsOptionsConfig } from 'util'
|
import { type ParseArgsOptionsConfig } from 'util';
|
||||||
import type { BrigadierTreeCommand, BrigadierTree } from "./types";
|
import type { BrigadierTreeCommand, BrigadierTree } from './types';
|
||||||
|
|
||||||
const commandObj:
|
const commandObj: { _opts?: ParseArgsOptionsConfig } & Record<
|
||||||
{ _opts?: ParseArgsOptionsConfig } &
|
string,
|
||||||
Record<string, BrigadierTreeCommand> = {}
|
BrigadierTreeCommand
|
||||||
|
> = {};
|
||||||
|
|
||||||
commandObj._opts = {
|
commandObj._opts = {
|
||||||
verbose: {
|
verbose: {
|
||||||
type: "boolean",
|
type: 'boolean',
|
||||||
short: 'v',
|
short: 'v',
|
||||||
},
|
},
|
||||||
silent: {
|
silent: {
|
||||||
type: "boolean",
|
type: 'boolean',
|
||||||
short: "s",
|
short: 's',
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
commandObj.run = {
|
commandObj.run = {
|
||||||
command: "run",
|
command: 'run',
|
||||||
options: {
|
options: {
|
||||||
speed: {
|
speed: {
|
||||||
type: "string",
|
type: 'string',
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
commandObj.walk = {
|
commandObj.walk = {
|
||||||
command: "walk",
|
command: 'walk',
|
||||||
options: {
|
options: {
|
||||||
fancy: {
|
fancy: {
|
||||||
type: "boolean",
|
type: 'boolean',
|
||||||
short: "f"
|
short: 'f',
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
commandObj.stand = {
|
commandObj.stand = {
|
||||||
command: "stand",
|
command: 'stand',
|
||||||
}
|
};
|
||||||
|
|
||||||
commandObj.sit = {
|
commandObj.sit = {
|
||||||
command: "sit",
|
command: 'sit',
|
||||||
}
|
};
|
||||||
|
|
||||||
commandObj.say = {
|
commandObj.say = {
|
||||||
command: "say",
|
command: 'say',
|
||||||
value: {
|
value: {
|
||||||
required: true,
|
required: true,
|
||||||
inputType: "string",
|
inputType: 'string',
|
||||||
dataType: "string[]",
|
dataType: 'string[]',
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
accent: {
|
accent: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
default: "American",
|
default: 'American',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
subcommands: [
|
subcommands: [
|
||||||
{
|
{
|
||||||
command: "talk",
|
command: 'talk',
|
||||||
value: {
|
value: {
|
||||||
required: true,
|
required: true,
|
||||||
inputType: "string",
|
inputType: 'string',
|
||||||
dataType: "string[]"
|
dataType: 'string[]',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command: "whisper",
|
command: 'whisper',
|
||||||
value: {
|
value: {
|
||||||
required: true,
|
required: true,
|
||||||
inputType: "string",
|
inputType: 'string',
|
||||||
dataType: "string[]"
|
dataType: 'string[]',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command: "yell",
|
command: 'yell',
|
||||||
value: {
|
value: {
|
||||||
required: true,
|
required: true,
|
||||||
inputType: "string",
|
inputType: 'string',
|
||||||
dataType: "string[]"
|
dataType: 'string[]',
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
};
|
||||||
|
|
||||||
const { _opts, ...commands } = commandObj
|
const { _opts, ...commands } = commandObj;
|
||||||
export const main: BrigadierTree = { _opts, commands: [...Object.values(commands)] }
|
export const main: BrigadierTree = {
|
||||||
|
_opts,
|
||||||
|
commands: [...Object.values(commands)],
|
||||||
|
};
|
||||||
|
|||||||
@ -1,82 +1,82 @@
|
|||||||
export class BrigadierInternalError extends Error {
|
export class BrigadierInternalError extends Error {
|
||||||
constructor(error: Error | undefined = undefined) {
|
constructor(error: Error | undefined = undefined) {
|
||||||
super()
|
super();
|
||||||
|
|
||||||
this.name = this.constructor.name;
|
this.name = this.constructor.name;
|
||||||
this.message = "The argument parser experience an internal error."
|
this.message = 'The argument parser experience an internal error.';
|
||||||
if (error) this.cause = error
|
if (error) this.cause = error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BrigadierConfigError extends Error {
|
export class BrigadierConfigError extends Error {
|
||||||
constructor(message: string, error: Error | undefined = undefined) {
|
constructor(message: string, error: Error | undefined = undefined) {
|
||||||
super(message)
|
super(message);
|
||||||
|
|
||||||
this.name = this.constructor.name;
|
this.name = this.constructor.name;
|
||||||
if (error) this.cause = error
|
if (error) this.cause = error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BrigadierOptionError extends Error {
|
export class BrigadierOptionError extends Error {
|
||||||
#defaultCode = "ERR_PARSE_ARGS_BRIGADIER_PARSE_OPTION";
|
#defaultCode = 'ERR_PARSE_ARGS_BRIGADIER_PARSE_OPTION';
|
||||||
#defaultMessages = {
|
#defaultMessages = {
|
||||||
helpPrimitive: `Run '--help' for more information on how to use this command.`,
|
helpPrimitive: `Run '--help' for more information on how to use this command.`,
|
||||||
get generic() {
|
get generic() {
|
||||||
return `There was an error processing on of the given options. ${this.helpPrimitive}`;
|
return `There was an error processing on of the given options. ${this.helpPrimitive}`;
|
||||||
},
|
},
|
||||||
get unknownOption() {
|
get unknownOption() {
|
||||||
return `Unknown option. ${this.helpPrimitive}`;
|
return `Unknown option. ${this.helpPrimitive}`;
|
||||||
},
|
},
|
||||||
get invalidOptionValue() {
|
get invalidOptionValue() {
|
||||||
return `An option has an invalid value. ${this.helpPrimitive}`;
|
return `An option has an invalid value. ${this.helpPrimitive}`;
|
||||||
},
|
},
|
||||||
get missingOptionValue() {
|
get missingOptionValue() {
|
||||||
return `An option value is missing. ${this.helpPrimitive}`;
|
return `An option value is missing. ${this.helpPrimitive}`;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
option: string | undefined = undefined;
|
option: string | undefined = undefined;
|
||||||
code: string = this.#defaultCode;
|
code: string = this.#defaultCode;
|
||||||
|
|
||||||
constructor(message: string, error: Error) {
|
constructor(message: string, error: Error) {
|
||||||
super(message);
|
super(message);
|
||||||
|
|
||||||
if ("code" in error && typeof error.code === "string")
|
if ('code' in error && typeof error.code === 'string')
|
||||||
this.#switchOnCodes(error.code);
|
this.#switchOnCodes(error.code);
|
||||||
|
|
||||||
this.name = this.constructor.name;
|
this.name = this.constructor.name;
|
||||||
if (error) this.cause = error;
|
if (error) this.cause = error;
|
||||||
}
|
}
|
||||||
|
|
||||||
#switchOnCodes(code: string) {
|
#switchOnCodes(code: string) {
|
||||||
if (code === "ERR_PARSE_ARGS_UNKNOWN_OPTION") {
|
if (code === 'ERR_PARSE_ARGS_UNKNOWN_OPTION') {
|
||||||
this.message = this.#unknownOption(this.message);
|
this.message = this.#unknownOption(this.message);
|
||||||
this.code = code;
|
this.code = code;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (code === "ERR_PARSE_ARGS_INVALID_OPTION_VALUE") {
|
if (code === 'ERR_PARSE_ARGS_INVALID_OPTION_VALUE') {
|
||||||
this.message = this.#invalidOptionValue(this.message);
|
this.message = this.#invalidOptionValue(this.message);
|
||||||
this.code = code;
|
this.code = code;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#unknownOption(message: string) {
|
#unknownOption(message: string) {
|
||||||
const index = 18;
|
const index = 18;
|
||||||
if (typeof message !== "string" || message[index - 3] !== "'")
|
if (typeof message !== 'string' || message[index - 3] !== "'")
|
||||||
return this.#defaultMessages.unknownOption;
|
return this.#defaultMessages.unknownOption;
|
||||||
this.option = this.#extractOptionString(message, index, ". ");
|
this.option = this.#extractOptionString(message, index, '. ');
|
||||||
return `Unknown option '--${this.option}'. ${this.#defaultMessages.helpPrimitive}`;
|
return `Unknown option '--${this.option}'. ${this.#defaultMessages.helpPrimitive}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
#invalidOptionValue(message: string) {
|
#invalidOptionValue(message: string) {
|
||||||
const index = 10;
|
const index = 10;
|
||||||
if (typeof message !== "string" || message[index - 3] !== "'")
|
if (typeof message !== 'string' || message[index - 3] !== "'")
|
||||||
return this.#defaultMessages.invalidOptionValue;
|
return this.#defaultMessages.invalidOptionValue;
|
||||||
this.option = this.#extractOptionString(message, index, "<value>' ");
|
this.option = this.#extractOptionString(message, index, "<value>' ");
|
||||||
return `Option '--${this.option}' has an invalid value. ${this.#defaultMessages.helpPrimitive}`;
|
return `Option '--${this.option}' has an invalid value. ${this.#defaultMessages.helpPrimitive}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
#extractOptionString(message: string, index: number, testString: string) {
|
#extractOptionString(message: string, index: number, testString: string) {
|
||||||
return message.slice(index, message.indexOf(testString) - 1);
|
return message.slice(index, message.indexOf(testString) - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,139 +1,193 @@
|
|||||||
import { tryCatch } from "@maxmorozoff/try-catch-tuple";
|
import { tryCatch } from '@maxmorozoff/try-catch-tuple';
|
||||||
import {
|
import {
|
||||||
parseArgs,
|
parseArgs,
|
||||||
type ParseArgsConfig,
|
type ParseArgsConfig,
|
||||||
type ParseArgsOptionsConfig,
|
type ParseArgsOptionsConfig,
|
||||||
} from "util";
|
} from 'util';
|
||||||
import { assertArrayOfStrings } from "../util/typeCheck";
|
import { assertArrayOfStrings } from '../util/typeCheck';
|
||||||
import { BrigadierConfigError, BrigadierInternalError, BrigadierOptionError } from "./errors";
|
import {
|
||||||
import type { BrigadierTree, BrigadierParserOverrides, BrigadierTreeCommand, BrigadierOutput } from "./types";
|
BrigadierConfigError,
|
||||||
import { treeUtils } from "./tree";
|
BrigadierInternalError,
|
||||||
|
BrigadierOptionError,
|
||||||
|
} from './errors';
|
||||||
|
import type {
|
||||||
|
BrigadierTree,
|
||||||
|
BrigadierParserOverrides,
|
||||||
|
BrigadierTreeCommand,
|
||||||
|
BrigadierOutput,
|
||||||
|
} from './types';
|
||||||
|
import { treeUtils } from './tree';
|
||||||
|
|
||||||
const defaultOverrides: BrigadierParserOverrides = {
|
const defaultOverrides: BrigadierParserOverrides = {
|
||||||
allowPositionals: true,
|
allowPositionals: true,
|
||||||
allowNegative: true,
|
allowNegative: true,
|
||||||
tokens: false,
|
tokens: false,
|
||||||
strict: true,
|
strict: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
function findNextTree(tree: Array<BrigadierTreeCommand>, command: string) {
|
function findNextTree(tree: Array<BrigadierTreeCommand>, command: string) {
|
||||||
const nextCommand = tree.filter((obj) => obj.command === command)
|
const nextCommand = tree.filter((obj) => obj.command === command);
|
||||||
return nextCommand[0]?.subcommands
|
return nextCommand[0]?.subcommands;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findCommands(tree: Array<BrigadierTreeCommand> | undefined, args: Array<string>, out: Array<string> = []) {
|
function findCommands(
|
||||||
const item = args[0]
|
tree: Array<BrigadierTreeCommand> | undefined,
|
||||||
if (!item || !tree) return out
|
args: Array<string>,
|
||||||
|
out: Array<string> = []
|
||||||
|
) {
|
||||||
|
const item = args[0];
|
||||||
|
if (!item || !tree) return out;
|
||||||
|
|
||||||
const [commands, commandError] = tryCatch(() => treeUtils.commandArray(tree))
|
const [commands, commandError] = tryCatch(() => treeUtils.commandArray(tree));
|
||||||
if (commandError) throw commandError
|
if (commandError) throw commandError;
|
||||||
|
|
||||||
if (item.slice(0, 0) === "-" || !commands.includes(item)) return out
|
if (item.slice(0, 0) === '-' || !commands.includes(item)) return out;
|
||||||
|
|
||||||
const [newTree, newTreeError] = tryCatch(() => findNextTree(tree, item))
|
const [newTree, newTreeError] = tryCatch(() => findNextTree(tree, item));
|
||||||
if (newTreeError) throw newTreeError
|
if (newTreeError) throw newTreeError;
|
||||||
|
|
||||||
const newArgs = args.slice(1)
|
const newArgs = args.slice(1);
|
||||||
const newOut = [...out, item]
|
const newOut = [...out, item];
|
||||||
|
|
||||||
return findCommands(newTree, newArgs, newOut)
|
return findCommands(newTree, newArgs, newOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
function findOpts(tree: Array<BrigadierTreeCommand>, commands: Array<string>, opts: ParseArgsOptionsConfig = {}) {
|
function findOpts(
|
||||||
const item = commands[0]
|
tree: Array<BrigadierTreeCommand>,
|
||||||
if (!item) throw new TypeError("The first item in the array is missing.")
|
commands: Array<string>,
|
||||||
|
opts: ParseArgsOptionsConfig = {}
|
||||||
|
) {
|
||||||
|
const item = commands[0];
|
||||||
|
if (!item) throw new TypeError('The first item in the array is missing.');
|
||||||
|
|
||||||
const commandObj = tree.filter((obj) => obj.command === item)
|
const commandObj = tree.filter((obj) => obj.command === item);
|
||||||
const thisOpts = commandObj[0]?.options ? commandObj[0].options : {}
|
const thisOpts = commandObj[0]?.options ? commandObj[0].options : {};
|
||||||
const nextOpts = Object.assign(opts, thisOpts)
|
const nextOpts = Object.assign(opts, thisOpts);
|
||||||
|
|
||||||
if (commands.length === 1) {
|
if (commands.length === 1) {
|
||||||
const config = nextOpts
|
const config = nextOpts;
|
||||||
return config
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextTree = commandObj[0]?.subcommands
|
|
||||||
if (!nextTree) throw new BrigadierConfigError(`The '${item}' command was expected to have subcommands.`)
|
|
||||||
return findOpts(nextTree, commands.slice(1), nextOpts)
|
|
||||||
|
|
||||||
|
const nextTree = commandObj[0]?.subcommands;
|
||||||
|
if (!nextTree)
|
||||||
|
throw new BrigadierConfigError(
|
||||||
|
`The '${item}' command was expected to have subcommands.`
|
||||||
|
);
|
||||||
|
return findOpts(nextTree, commands.slice(1), nextOpts);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setParsedArgsConfig(
|
function setParsedArgsConfig(
|
||||||
args: Array<string>,
|
args: Array<string>,
|
||||||
options: ParseArgsOptionsConfig,
|
options: ParseArgsOptionsConfig,
|
||||||
overrides: BrigadierParserOverrides | undefined,
|
overrides: BrigadierParserOverrides | undefined,
|
||||||
commands: Array<string>
|
commands: Array<string>
|
||||||
) {
|
) {
|
||||||
if (!assertArrayOfStrings(args))
|
if (!assertArrayOfStrings(args))
|
||||||
throw new TypeError(`Parameter "args" must be an array of strings`);
|
throw new TypeError(`Parameter "args" must be an array of strings`);
|
||||||
const obj =
|
const obj =
|
||||||
overrides === undefined
|
overrides === undefined
|
||||||
? defaultOverrides
|
? defaultOverrides
|
||||||
: Object.assign({}, defaultOverrides, overrides);
|
: Object.assign({}, defaultOverrides, overrides);
|
||||||
|
|
||||||
const configObj: ParseArgsConfig = {
|
const configObj: ParseArgsConfig = {
|
||||||
args,
|
args,
|
||||||
options,
|
options,
|
||||||
...obj,
|
...obj,
|
||||||
};
|
};
|
||||||
return { config: configObj, commands: commands };
|
return { config: configObj, commands: commands };
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateParsedArgsConfig(tree: BrigadierTree, args: Array<string>, overrides: BrigadierParserOverrides | undefined = undefined) {
|
function generateParsedArgsConfig(
|
||||||
const [commandsArray, commandsError] = tryCatch(() => findCommands(tree.commands, args.slice(2)))
|
tree: BrigadierTree,
|
||||||
if (commandsError) throw commandsError
|
args: Array<string>,
|
||||||
if (!commandsArray[0]) {
|
overrides: BrigadierParserOverrides | undefined = undefined
|
||||||
const opts = tree._opts
|
) {
|
||||||
return setParsedArgsConfig(args, opts, overrides, commandsArray)
|
const [commandsArray, commandsError] = tryCatch(() =>
|
||||||
}
|
findCommands(tree.commands, args.slice(2))
|
||||||
|
);
|
||||||
|
if (commandsError) throw commandsError;
|
||||||
|
if (!commandsArray[0]) {
|
||||||
|
const opts = tree._opts;
|
||||||
|
return setParsedArgsConfig(args, opts, overrides, commandsArray);
|
||||||
|
}
|
||||||
|
|
||||||
const [commandOpts, commandOptsError] = tryCatch(() => findOpts(tree.commands, commandsArray))
|
const [commandOpts, commandOptsError] = tryCatch(() =>
|
||||||
if (commandOptsError) throw commandOptsError
|
findOpts(tree.commands, commandsArray)
|
||||||
const opts = Object.assign({}, tree._opts, commandOpts)
|
);
|
||||||
return setParsedArgsConfig(args, opts, overrides, commandsArray)
|
if (commandOptsError) throw commandOptsError;
|
||||||
|
const opts = Object.assign({}, tree._opts, commandOpts);
|
||||||
|
return setParsedArgsConfig(args, opts, overrides, commandsArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getParsedArgs(opts: { config: ParseArgsConfig, commands: Array<string> }) {
|
function getParsedArgs(opts: {
|
||||||
const [result, error] = tryCatch(() => {
|
config: ParseArgsConfig;
|
||||||
return parseArgs(opts.config);
|
commands: Array<string>;
|
||||||
});
|
}) {
|
||||||
if (error) throw new BrigadierOptionError(error.message, error);
|
const [result, error] = tryCatch(() => {
|
||||||
return { ...result, commands: opts.commands };
|
return parseArgs(opts.config);
|
||||||
|
});
|
||||||
|
if (error) throw new BrigadierOptionError(error.message, error);
|
||||||
|
return { ...result, commands: opts.commands };
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildPositionalsArray(commands: Array<string>, positionals: Array<string>) {
|
function buildPositionalsArray(
|
||||||
const testSet = new Set(commands)
|
commands: Array<string>,
|
||||||
return positionals.filter(str => !testSet.has(str))
|
positionals: Array<string>
|
||||||
|
) {
|
||||||
|
const testSet = new Set(commands);
|
||||||
|
return positionals.filter((str) => !testSet.has(str));
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleErr(error: Error) {
|
function handleErr(error: Error) {
|
||||||
if (error instanceof BrigadierOptionError || error instanceof BrigadierConfigError) return error
|
if (
|
||||||
return new BrigadierInternalError()
|
error instanceof BrigadierOptionError ||
|
||||||
|
error instanceof BrigadierConfigError
|
||||||
|
)
|
||||||
|
return error;
|
||||||
|
return new BrigadierInternalError();
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildOutputObject(tree: BrigadierTree, args: Array<string>, overrides: BrigadierParserOverrides | undefined = undefined) {
|
function buildOutputObject(
|
||||||
try {
|
tree: BrigadierTree,
|
||||||
const [config, configError] = tryCatch(() => generateParsedArgsConfig(tree, args, overrides))
|
args: Array<string>,
|
||||||
if (configError) throw configError
|
overrides: BrigadierParserOverrides | undefined = undefined
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const [config, configError] = tryCatch(() =>
|
||||||
|
generateParsedArgsConfig(tree, args, overrides)
|
||||||
|
);
|
||||||
|
if (configError) throw configError;
|
||||||
|
|
||||||
const [parsedArgs, parsedError] = tryCatch(() => getParsedArgs(config))
|
const [parsedArgs, parsedError] = tryCatch(() => getParsedArgs(config));
|
||||||
if (parsedError) throw parsedError
|
if (parsedError) throw parsedError;
|
||||||
|
|
||||||
const programPaths = {
|
const programPaths = {
|
||||||
bun: parsedArgs.positionals[0] ? parsedArgs.positionals[0] : "",
|
bun: parsedArgs.positionals[0] ? parsedArgs.positionals[0] : '',
|
||||||
path: parsedArgs.positionals[1] ? parsedArgs.positionals[1] : "",
|
path: parsedArgs.positionals[1] ? parsedArgs.positionals[1] : '',
|
||||||
}
|
};
|
||||||
const command = config.commands[0] ? { command: config.commands[0] } : {}
|
const command = config.commands[0] ? { command: config.commands[0] } : {};
|
||||||
const subcommands = config.commands[1] ? { subcommands: config.commands.slice(1) } : {}
|
const subcommands = config.commands[1]
|
||||||
const [positionals, positionalsError] = tryCatch(() => buildPositionalsArray(config.commands, parsedArgs.positionals.slice(2)))
|
? { subcommands: config.commands.slice(1) }
|
||||||
if (positionalsError) throw positionalsError
|
: {};
|
||||||
const positionalsObj = positionals.length > 0 ? { positionals } : {}
|
const [positionals, positionalsError] = tryCatch(() =>
|
||||||
const values = parsedArgs.values ? parsedArgs.values : {}
|
buildPositionalsArray(config.commands, parsedArgs.positionals.slice(2))
|
||||||
|
);
|
||||||
|
if (positionalsError) throw positionalsError;
|
||||||
|
const positionalsObj = positionals.length > 0 ? { positionals } : {};
|
||||||
|
const values = parsedArgs.values ? parsedArgs.values : {};
|
||||||
|
|
||||||
const out: BrigadierOutput = Object.assign({}, programPaths, command, subcommands, positionalsObj, { values })
|
const out: BrigadierOutput = Object.assign(
|
||||||
return out
|
{},
|
||||||
} catch (error) {
|
programPaths,
|
||||||
throw handleErr(error as Error)
|
command,
|
||||||
}
|
subcommands,
|
||||||
|
positionalsObj,
|
||||||
|
{ values }
|
||||||
|
);
|
||||||
|
return out;
|
||||||
|
} catch (error) {
|
||||||
|
throw handleErr(error as Error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export const main = buildOutputObject;
|
export const main = buildOutputObject;
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
import { demoTree } from ".";
|
import { demoTree } from '.';
|
||||||
import type { BrigadierTreeCommand } from "./types";
|
import type { BrigadierTreeCommand } from './types';
|
||||||
|
|
||||||
|
|
||||||
function commandArray(array: Array<BrigadierTreeCommand>) {
|
function commandArray(array: Array<BrigadierTreeCommand>) {
|
||||||
const out: Array<string> = []
|
const out: Array<string> = [];
|
||||||
array.forEach((obj) => out.push(obj.command))
|
array.forEach((obj) => out.push(obj.command));
|
||||||
return out
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const treeUtils = {
|
export const treeUtils = {
|
||||||
commandArray
|
commandArray,
|
||||||
}
|
};
|
||||||
|
|||||||
@ -1,49 +1,48 @@
|
|||||||
import { type ParseArgsOptionsConfig } from 'util'
|
import { type ParseArgsOptionsConfig } from 'util';
|
||||||
|
|
||||||
export type BrigadierCommandValue =
|
export type BrigadierCommandValue =
|
||||||
"string" |
|
| 'string'
|
||||||
"number" |
|
| 'number'
|
||||||
"boolean" |
|
| 'boolean'
|
||||||
"string[]" |
|
| 'string[]'
|
||||||
"number[]" |
|
| 'number[]'
|
||||||
"boolean[]" |
|
| 'boolean[]'
|
||||||
"mixed[]"
|
| 'mixed[]';
|
||||||
|
|
||||||
export type BrigadierTreeCommand = {
|
export type BrigadierTreeCommand = {
|
||||||
command: string,
|
command: string;
|
||||||
value?: {
|
value?: {
|
||||||
required: boolean,
|
required: boolean;
|
||||||
inputType: "string" | "boolean",
|
inputType: 'string' | 'boolean';
|
||||||
dataType: BrigadierCommandValue,
|
dataType: BrigadierCommandValue;
|
||||||
default?: string | boolean
|
default?: string | boolean;
|
||||||
}
|
};
|
||||||
options?: ParseArgsOptionsConfig,
|
options?: ParseArgsOptionsConfig;
|
||||||
subcommands?: Array<BrigadierTreeCommand>
|
subcommands?: Array<BrigadierTreeCommand>;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type BrigadierTree = {
|
export type BrigadierTree = {
|
||||||
_opts: ParseArgsOptionsConfig,
|
_opts: ParseArgsOptionsConfig;
|
||||||
commands: Array<BrigadierTreeCommand>
|
commands: Array<BrigadierTreeCommand>;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type BrigadierOutput = {
|
export type BrigadierOutput = {
|
||||||
bun: string,
|
bun: string;
|
||||||
path: string,
|
path: string;
|
||||||
command?: string,
|
command?: string;
|
||||||
subcommands?: Array<string>,
|
subcommands?: Array<string>;
|
||||||
positionals?: Array<string>,
|
positionals?: Array<string>;
|
||||||
values?: Record<string, string | boolean>
|
values?: Record<string, string | boolean>;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type BrigadierParserOverrides = {
|
export type BrigadierParserOverrides = {
|
||||||
strict?: boolean;
|
strict?: boolean;
|
||||||
allowPositionals?: boolean;
|
allowPositionals?: boolean;
|
||||||
allowNegative?: boolean;
|
allowNegative?: boolean;
|
||||||
tokens?: boolean;
|
tokens?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BrigadierInput = BrigadierTree & {
|
export type BrigadierInput = BrigadierTree & {
|
||||||
args: Array<string>
|
args: Array<string>;
|
||||||
overrides?: BrigadierParserOverrides;
|
overrides?: BrigadierParserOverrides;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
# Features to Impliment
|
||||||
|
|
||||||
## Basic Inputs
|
## Basic Inputs
|
||||||
|
|
||||||
- [ ] **Text Input** – User types a free-form response.
|
- [ ] **Text Input** – User types a free-form response.
|
||||||
|
|||||||
@ -4,125 +4,166 @@ import { TerminalUtils, type CursorPosition } from '../util/terminal';
|
|||||||
import { assertArrayOfStrings } from '../util/typeCheck';
|
import { assertArrayOfStrings } from '../util/typeCheck';
|
||||||
|
|
||||||
export type PromptsmithConfig = {
|
export type PromptsmithConfig = {
|
||||||
format?: {
|
format?: {
|
||||||
selected?: string,
|
selected?: string;
|
||||||
selectedText?: string,
|
selectedText?: string;
|
||||||
defualt?: string,
|
defualt?: string;
|
||||||
defualtText?: string,
|
defualtText?: string;
|
||||||
},
|
};
|
||||||
selector?: {
|
selector?: {
|
||||||
selected?: string,
|
selected?: string;
|
||||||
default?: string,
|
default?: string;
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
// A pure function to render options without side effects
|
// A pure function to render options without side effects
|
||||||
const formatText = (text: string, format: string): string => `${format}${text}\x1B[0m`;
|
const formatText = (text: string, format: string): string =>
|
||||||
|
`${format}${text}\x1B[0m`;
|
||||||
|
|
||||||
const renderOptions = (options: string[], selectedIndex: number, pos: CursorPosition, config: PromptsmithConfig): string[] => {
|
const renderOptions = (
|
||||||
TerminalUtils.cursor.restorePosition(pos);
|
options: string[],
|
||||||
TerminalUtils.clear.below();
|
selectedIndex: number,
|
||||||
|
pos: CursorPosition,
|
||||||
|
config: PromptsmithConfig
|
||||||
|
): string[] => {
|
||||||
|
TerminalUtils.cursor.restorePosition(pos);
|
||||||
|
TerminalUtils.clear.below();
|
||||||
|
|
||||||
return [
|
return [
|
||||||
formatText('Use the ↑ and ↓ keys to select an option. Press ENTER to confirm.\n', '\x1B[1m'), // Bold
|
formatText(
|
||||||
...options.map((option, index) => {
|
'Use the ↑ and ↓ keys to select an option. Press ENTER to confirm.\n',
|
||||||
const isSelected = index === selectedIndex;
|
'\x1B[1m'
|
||||||
const selector = formatText(
|
), // Bold
|
||||||
isSelected ? config.selector.selected as string : config.selector.default as string,
|
...options.map((option, index) => {
|
||||||
isSelected ? config.format.selected as string : config.format.defualt as string
|
const isSelected = index === selectedIndex;
|
||||||
)
|
const selector = formatText(
|
||||||
const optionText = formatText(
|
isSelected
|
||||||
option,
|
? (config.selector.selected as string)
|
||||||
isSelected ? config.format.selectedText as string : config.format.defualtText as string
|
: (config.selector.default as string),
|
||||||
)
|
isSelected
|
||||||
return selector + optionText
|
? (config.format.selected as string)
|
||||||
}),
|
: (config.format.defualt as string)
|
||||||
];
|
);
|
||||||
|
const optionText = formatText(
|
||||||
|
option,
|
||||||
|
isSelected
|
||||||
|
? (config.format.selectedText as string)
|
||||||
|
: (config.format.defualtText as string)
|
||||||
|
);
|
||||||
|
return selector + optionText;
|
||||||
|
}),
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
// A pure function to update selected index based on input key
|
// A pure function to update selected index based on input key
|
||||||
const updateSelection = (selectedIndex: number, keyName: string, options: string[]): number =>
|
const updateSelection = (
|
||||||
keyName === 'up' ? Math.max(selectedIndex - 1, 0) :
|
selectedIndex: number,
|
||||||
keyName === 'down' ? Math.min(selectedIndex + 1, options.length - 1) :
|
keyName: string,
|
||||||
selectedIndex;
|
options: string[]
|
||||||
|
): number =>
|
||||||
|
keyName === 'up'
|
||||||
|
? Math.max(selectedIndex - 1, 0)
|
||||||
|
: keyName === 'down'
|
||||||
|
? Math.min(selectedIndex + 1, options.length - 1)
|
||||||
|
: selectedIndex;
|
||||||
|
|
||||||
// Handles keypress events as pure transformations
|
// Handles keypress events as pure transformations
|
||||||
const handleKeyPress = async (options: string[], selectedIndex: number, pos: CursorPosition, config: PromptsmithConfig): Promise<string> => {
|
const handleKeyPress = async (
|
||||||
return new Promise((resolve) => {
|
options: string[],
|
||||||
if (!assertArrayOfStrings(options)) throw new TypeError("'options' should be Array<string>.")
|
selectedIndex: number,
|
||||||
|
pos: CursorPosition,
|
||||||
|
config: PromptsmithConfig
|
||||||
|
): Promise<string> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (!assertArrayOfStrings(options))
|
||||||
|
throw new TypeError("'options' should be Array<string>.");
|
||||||
|
|
||||||
process.stdin.resume();
|
process.stdin.resume();
|
||||||
readline.emitKeypressEvents(process.stdin);
|
readline.emitKeypressEvents(process.stdin);
|
||||||
|
|
||||||
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
||||||
|
|
||||||
const onKeyPress = async (_: string, key: readline.Key) => {
|
const onKeyPress = async (_: string, key: readline.Key) => {
|
||||||
const newIndex = updateSelection(selectedIndex, key.name as string, options);
|
const newIndex = updateSelection(
|
||||||
|
selectedIndex,
|
||||||
|
key.name as string,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
|
||||||
if (key.name === 'return') {
|
if (key.name === 'return') {
|
||||||
cleanup();
|
cleanup();
|
||||||
resolve(options[newIndex] as string);
|
resolve(options[newIndex] as string);
|
||||||
return;
|
return;
|
||||||
} else if (key.name === 'c' && key.ctrl) {
|
} else if (key.name === 'c' && key.ctrl) {
|
||||||
cleanup();
|
cleanup();
|
||||||
process.exit();
|
process.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
renderOptions(options, newIndex, pos, config).forEach(line => console.log(line));
|
renderOptions(options, newIndex, pos, config).forEach((line) =>
|
||||||
selectedIndex = newIndex;
|
console.log(line)
|
||||||
};
|
);
|
||||||
|
selectedIndex = newIndex;
|
||||||
|
};
|
||||||
|
|
||||||
process.stdin.on('keypress', onKeyPress);
|
process.stdin.on('keypress', onKeyPress);
|
||||||
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
process.stdin.removeListener('keypress', onKeyPress);
|
process.stdin.removeListener('keypress', onKeyPress);
|
||||||
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
||||||
TerminalUtils.cursor.restorePosition(pos)
|
TerminalUtils.cursor.restorePosition(pos);
|
||||||
TerminalUtils.clear.below()
|
TerminalUtils.clear.below();
|
||||||
process.stdin.pause();
|
process.stdin.pause();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const setConfig = (config: PromptsmithConfig) => {
|
const setConfig = (config: PromptsmithConfig) => {
|
||||||
if (!config.format) config.format = {}
|
if (!config.format) config.format = {};
|
||||||
|
|
||||||
if (!config.format.selected) config.format.selected = '\x1B[32m\x1B[1m'
|
if (!config.format.selected) config.format.selected = '\x1B[32m\x1B[1m';
|
||||||
if (!config.format.selectedText) config.format.selectedText = config.format.selected
|
if (!config.format.selectedText)
|
||||||
if (!config.format.defualt) config.format.defualt = ''
|
config.format.selectedText = config.format.selected;
|
||||||
if (!config.format.defualtText) config.format.defualtText = config.format.defualt
|
if (!config.format.defualt) config.format.defualt = '';
|
||||||
|
if (!config.format.defualtText)
|
||||||
|
config.format.defualtText = config.format.defualt;
|
||||||
|
|
||||||
if (!config.selector) config.selector = {}
|
if (!config.selector) config.selector = {};
|
||||||
if (!config.selector.default) config.selector.default = ' '
|
if (!config.selector.default) config.selector.default = ' ';
|
||||||
if (!config.selector.selected) config.selector.selected = '> '
|
if (!config.selector.selected) config.selector.selected = '> ';
|
||||||
|
|
||||||
return config
|
return config;
|
||||||
}
|
};
|
||||||
|
|
||||||
// Main function using pure composition
|
// Main function using pure composition
|
||||||
const defaultConfig: PromptsmithConfig = {
|
const defaultConfig: PromptsmithConfig = {
|
||||||
format: {
|
format: {
|
||||||
selected: '\x1B[32m\x1B[1m',
|
selected: '\x1B[32m\x1B[1m',
|
||||||
},
|
},
|
||||||
selector: {
|
selector: {
|
||||||
selected: '> ',
|
selected: '> ',
|
||||||
default: ' '
|
default: ' ',
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
const interactiveSelect = async (options: string[], config: PromptsmithConfig = {}): Promise<string> => {
|
const interactiveSelect = async (
|
||||||
const newConfig = setConfig(config)
|
options: string[],
|
||||||
|
config: PromptsmithConfig = {}
|
||||||
|
): Promise<string> => {
|
||||||
|
const newConfig = setConfig(config);
|
||||||
|
|
||||||
const pos = await TerminalUtils.cursor.getCursorPosition();
|
const pos = await TerminalUtils.cursor.getCursorPosition();
|
||||||
renderOptions(options, 0, pos, newConfig).forEach(line => console.log(line));
|
renderOptions(options, 0, pos, newConfig).forEach((line) =>
|
||||||
return handleKeyPress(options, 0, pos, newConfig);
|
console.log(line)
|
||||||
|
);
|
||||||
|
return handleKeyPress(options, 0, pos, newConfig);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Execution
|
// Execution
|
||||||
(async () => {
|
(async () => {
|
||||||
const options = ['Option A', 'Option B', 'Option C'];
|
const options = ['Option A', 'Option B', 'Option C'];
|
||||||
const selectedOption = await interactiveSelect(options, { selector: { selected: '* ' } });
|
const selectedOption = await interactiveSelect(options, {
|
||||||
console.log(`You selected: ${selectedOption}`);
|
selector: { selected: '* ' },
|
||||||
|
});
|
||||||
|
console.log(`You selected: ${selectedOption}`);
|
||||||
|
|
||||||
process.stdin.pause(); // Ensures stdin closes AFTER selection
|
process.stdin.pause(); // Ensures stdin closes AFTER selection
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|||||||
@ -3,47 +3,50 @@
|
|||||||
export type CursorPosition = { row: number; col: number };
|
export type CursorPosition = { row: number; col: number };
|
||||||
|
|
||||||
export const TerminalUtils = {
|
export const TerminalUtils = {
|
||||||
clear: {
|
clear: {
|
||||||
screen: () => process.stdout.write('\x1Bc'),
|
screen: () => process.stdout.write('\x1Bc'),
|
||||||
content: () => process.stdout.write('\x1B[2J'),
|
content: () => process.stdout.write('\x1B[2J'),
|
||||||
cursorHome: () => process.stdout.write('\x1B[H'),
|
cursorHome: () => process.stdout.write('\x1B[H'),
|
||||||
line: () => process.stdout.write('\x1B[K'),
|
line: () => process.stdout.write('\x1B[K'),
|
||||||
below: () => process.stdout.write('\x1B[J'),
|
below: () => process.stdout.write('\x1B[J'),
|
||||||
above: () => process.stdout.write('\x1B[1J'),
|
above: () => process.stdout.write('\x1B[1J'),
|
||||||
clear: () => console.clear(),
|
clear: () => console.clear(),
|
||||||
},
|
},
|
||||||
|
|
||||||
cursor: { /** Move cursor to a specific row & column */
|
cursor: {
|
||||||
moveCursor: (row: number, col: number) => process.stdout.write(`\x1B[${row};${col}H`),
|
/** Move cursor to a specific row & column */
|
||||||
|
moveCursor: (row: number, col: number) =>
|
||||||
|
process.stdout.write(`\x1B[${row};${col}H`),
|
||||||
|
|
||||||
/** Queries the terminal for the current cursor position */
|
/** Queries the terminal for the current cursor position */
|
||||||
async getCursorPosition(): Promise<CursorPosition> {
|
async getCursorPosition(): Promise<CursorPosition> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const stdin = process.stdin;
|
const stdin = process.stdin;
|
||||||
stdin.setRawMode(true);
|
stdin.setRawMode(true);
|
||||||
stdin.resume();
|
stdin.resume();
|
||||||
stdin.setEncoding('utf8');
|
stdin.setEncoding('utf8');
|
||||||
|
|
||||||
stdin.once('data', (data) => {
|
stdin.once('data', (data) => {
|
||||||
const match = /\[(\d+);(\d+)R/.exec(data.toString());
|
const match = /\[(\d+);(\d+)R/.exec(data.toString());
|
||||||
resolve({
|
resolve({
|
||||||
row: parseInt(match?.[1] ?? '0', 10),
|
row: parseInt(match?.[1] ?? '0', 10),
|
||||||
col: parseInt(match?.[2] ?? '0', 10),
|
col: parseInt(match?.[2] ?? '0', 10),
|
||||||
});
|
});
|
||||||
stdin.setRawMode(false);
|
stdin.setRawMode(false);
|
||||||
stdin.pause();
|
stdin.pause();
|
||||||
});
|
});
|
||||||
|
|
||||||
process.stdout.write('\x1B[6n'); // Ask terminal for cursor position
|
process.stdout.write('\x1B[6n'); // Ask terminal for cursor position
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/** Stores detected cursor position in a constant */
|
/** Stores detected cursor position in a constant */
|
||||||
async storePosition(): Promise<CursorPosition> {
|
async storePosition(): Promise<CursorPosition> {
|
||||||
return TerminalUtils.cursor.getCursorPosition();
|
return TerminalUtils.cursor.getCursorPosition();
|
||||||
},
|
},
|
||||||
|
|
||||||
/** Restores the passed cursor position */
|
/** Restores the passed cursor position */
|
||||||
restorePosition: (pos: CursorPosition) => TerminalUtils.cursor.moveCursor(pos.row, pos.col),
|
restorePosition: (pos: CursorPosition) =>
|
||||||
}
|
TerminalUtils.cursor.moveCursor(pos.row, pos.col),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
export function assertArrayOfStrings(arr: unknown) {
|
export function assertArrayOfStrings(arr: unknown) {
|
||||||
if (!Array.isArray(arr)) return false;
|
if (!Array.isArray(arr)) return false;
|
||||||
if (!arr.every((item: unknown) => typeof item === "string")) return false;
|
if (!arr.every((item: unknown) => typeof item === 'string')) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user