Initial commit
Includes first version of Bridagier command parser
This commit is contained in:
commit
825fe82168
34
.gitignore
vendored
Normal file
34
.gitignore
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
# dependencies (bun install)
|
||||
node_modules
|
||||
|
||||
# output
|
||||
out
|
||||
dist
|
||||
*.tgz
|
||||
|
||||
# code coverage
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# logs
|
||||
logs
|
||||
_.log
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# caches
|
||||
.eslintcache
|
||||
.cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# IntelliJ based IDEs
|
||||
.idea
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
15
README.md
Normal file
15
README.md
Normal file
@ -0,0 +1,15 @@
|
||||
# cli-framework
|
||||
|
||||
To install dependencies:
|
||||
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
To run:
|
||||
|
||||
```bash
|
||||
bun run index.ts
|
||||
```
|
||||
|
||||
This project was created using `bun init` in bun v1.2.9. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
|
||||
76
bun.lock
Normal file
76
bun.lock
Normal file
@ -0,0 +1,76 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "cli-framework",
|
||||
"devDependencies": {
|
||||
"@maxmorozoff/try-catch-tuple": "^0.1.2",
|
||||
"@maxmorozoff/try-catch-tuple-ts-plugin": "^0.0.1",
|
||||
"@types/bun": "latest",
|
||||
"ts-patch": "^3.3.0",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.8.3",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@maxmorozoff/try-catch-tuple": ["@maxmorozoff/try-catch-tuple@0.1.2", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-stFwucLpAkJPkLIlBGsXB/Q9A3kSTFoioqqlz7V5r6pwsysQ1/+vpVKEutmbjSztoIOdy+iTn/iSSKyJXb+ebQ=="],
|
||||
|
||||
"@maxmorozoff/try-catch-tuple-ts-plugin": ["@maxmorozoff/try-catch-tuple-ts-plugin@0.0.1", "", { "peerDependencies": { "ts-patch": "^3.3.0", "typescript": "^5.0.0" }, "optionalPeers": ["ts-patch"] }, "sha512-OeQpI8YfkuB1gFXSCyogsllfoMmEYnZvIRsSV+MWOxk/amrpA4rKjpbmqaHpVVo3YKFLqTLB8RLDFgVkoWBGhQ=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.2.13", "", { "dependencies": { "bun-types": "1.2.13" } }, "sha512-u6vXep/i9VBxoJl3GjZsl/BFIsvML8DfVDO0RYLEwtSZSp981kEO1V5NwRcO1CPJ7AmvpbnDCiMKo3JvbDEjAg=="],
|
||||
|
||||
"@types/node": ["@types/node@22.14.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"bun-types": ["bun-types@1.2.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-rRjA1T6n7wto4gxhAO/ErZEtOXyEZEmnIHQfl0Dt1QQSB4QV0iP6BZ9/YB5fZaHFQ2dwHFrmPaRQ9GGMX01k9Q=="],
|
||||
|
||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||
|
||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
"global-prefix": ["global-prefix@4.0.0", "", { "dependencies": { "ini": "^4.1.3", "kind-of": "^6.0.3", "which": "^4.0.0" } }, "sha512-w0Uf9Y9/nyHinEk5vMJKRie+wa4kR5hmDbEhGGds/kG1PwGLLHKRoNMeJOyCQjjBkANlnScqgzcFwGHgmgLkVA=="],
|
||||
|
||||
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||
|
||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||
|
||||
"ini": ["ini@4.1.3", "", {}, "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg=="],
|
||||
|
||||
"is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
|
||||
|
||||
"isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="],
|
||||
|
||||
"kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="],
|
||||
|
||||
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
||||
|
||||
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
|
||||
|
||||
"resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="],
|
||||
|
||||
"semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
|
||||
|
||||
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
||||
|
||||
"ts-patch": ["ts-patch@3.3.0", "", { "dependencies": { "chalk": "^4.1.2", "global-prefix": "^4.0.0", "minimist": "^1.2.8", "resolve": "^1.22.2", "semver": "^7.6.3", "strip-ansi": "^6.0.1" }, "bin": { "ts-patch": "bin/ts-patch.js", "tspc": "bin/tspc.js" } }, "sha512-zAOzDnd5qsfEnjd9IGy1IRuvA7ygyyxxdxesbhMdutt8AHFjD8Vw8hU2rMF89HX1BKRWFYqKHrO8Q6lw0NeUZg=="],
|
||||
|
||||
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
||||
|
||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||
|
||||
"which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="],
|
||||
}
|
||||
}
|
||||
15
package.json
Normal file
15
package.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "cli-framework",
|
||||
"module": "index.ts",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@maxmorozoff/try-catch-tuple": "^0.1.2",
|
||||
"@maxmorozoff/try-catch-tuple-ts-plugin": "^0.0.1",
|
||||
"@types/bun": "latest",
|
||||
"ts-patch": "^3.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
88
src/lib/brigadier/demoCommands.ts
Normal file
88
src/lib/brigadier/demoCommands.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import { type ParseArgsOptionsConfig } from 'util'
|
||||
import type { BrigadierTreeCommand, BrigadierTree } from "./types";
|
||||
|
||||
const commandObj:
|
||||
{ _opts?: ParseArgsOptionsConfig } &
|
||||
Record<string, BrigadierTreeCommand> = {}
|
||||
|
||||
commandObj._opts = {
|
||||
verbose: {
|
||||
type: "boolean",
|
||||
short: 'v',
|
||||
},
|
||||
silent: {
|
||||
type: "boolean",
|
||||
short: "s",
|
||||
},
|
||||
}
|
||||
|
||||
commandObj.run = {
|
||||
command: "run",
|
||||
options: {
|
||||
speed: {
|
||||
type: "string",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
commandObj.walk = {
|
||||
command: "walk",
|
||||
options: {
|
||||
fancy: {
|
||||
type: "boolean",
|
||||
short: "f"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
commandObj.stand = {
|
||||
command: "stand",
|
||||
}
|
||||
|
||||
commandObj.sit = {
|
||||
command: "sit",
|
||||
}
|
||||
|
||||
commandObj.say = {
|
||||
command: "say",
|
||||
value: {
|
||||
required: true,
|
||||
inputType: "string",
|
||||
dataType: "string[]",
|
||||
},
|
||||
options: {
|
||||
accent: {
|
||||
type: 'string',
|
||||
default: "American",
|
||||
}
|
||||
},
|
||||
subcommands: [
|
||||
{
|
||||
command: "talk",
|
||||
value: {
|
||||
required: true,
|
||||
inputType: "string",
|
||||
dataType: "string[]"
|
||||
},
|
||||
},
|
||||
{
|
||||
command: "whisper",
|
||||
value: {
|
||||
required: true,
|
||||
inputType: "string",
|
||||
dataType: "string[]"
|
||||
}
|
||||
},
|
||||
{
|
||||
command: "yell",
|
||||
value: {
|
||||
required: true,
|
||||
inputType: "string",
|
||||
dataType: "string[]"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const { _opts, ...commands } = commandObj
|
||||
export const main: BrigadierTree = { _opts, commands: [...Object.values(commands)] }
|
||||
82
src/lib/brigadier/errors.ts
Normal file
82
src/lib/brigadier/errors.ts
Normal file
@ -0,0 +1,82 @@
|
||||
export class BrigadierInternalError extends Error {
|
||||
constructor(error: Error | undefined = undefined) {
|
||||
super()
|
||||
|
||||
this.name = this.constructor.name;
|
||||
this.message = "The argument parser experience an internal error."
|
||||
if (error) this.cause = error
|
||||
}
|
||||
}
|
||||
|
||||
export class BrigadierConfigError extends Error {
|
||||
constructor(message: string, error: Error | undefined = undefined) {
|
||||
super(message)
|
||||
|
||||
this.name = this.constructor.name;
|
||||
if (error) this.cause = error
|
||||
}
|
||||
}
|
||||
|
||||
export class BrigadierOptionError extends Error {
|
||||
#defaultCode = "ERR_PARSE_ARGS_BRIGADIER_PARSE_OPTION";
|
||||
#defaultMessages = {
|
||||
helpPrimitive: `Run '--help' for more information on how to use this command.`,
|
||||
get generic() {
|
||||
return `There was an error processing on of the given options. ${this.helpPrimitive}`;
|
||||
},
|
||||
get unknownOption() {
|
||||
return `Unknown option. ${this.helpPrimitive}`;
|
||||
},
|
||||
get invalidOptionValue() {
|
||||
return `An option has an invalid value. ${this.helpPrimitive}`;
|
||||
},
|
||||
get missingOptionValue() {
|
||||
return `An option value is missing. ${this.helpPrimitive}`;
|
||||
},
|
||||
};
|
||||
option: string | undefined = undefined;
|
||||
code: string = this.#defaultCode;
|
||||
|
||||
constructor(message: string, error: Error) {
|
||||
super(message);
|
||||
|
||||
if ("code" in error && typeof error.code === "string")
|
||||
this.#switchOnCodes(error.code);
|
||||
|
||||
this.name = this.constructor.name;
|
||||
if (error) this.cause = error;
|
||||
}
|
||||
|
||||
#switchOnCodes(code: string) {
|
||||
if (code === "ERR_PARSE_ARGS_UNKNOWN_OPTION") {
|
||||
this.message = this.#unknownOption(this.message);
|
||||
this.code = code;
|
||||
return;
|
||||
}
|
||||
if (code === "ERR_PARSE_ARGS_INVALID_OPTION_VALUE") {
|
||||
this.message = this.#invalidOptionValue(this.message);
|
||||
this.code = code;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#unknownOption(message: string) {
|
||||
const index = 18;
|
||||
if (typeof message !== "string" || message[index - 3] !== "'")
|
||||
return this.#defaultMessages.unknownOption;
|
||||
this.option = this.#extractOptionString(message, index, ". ");
|
||||
return `Unknown option '--${this.option}'. ${this.#defaultMessages.helpPrimitive}`;
|
||||
}
|
||||
|
||||
#invalidOptionValue(message: string) {
|
||||
const index = 10;
|
||||
if (typeof message !== "string" || message[index - 3] !== "'")
|
||||
return this.#defaultMessages.invalidOptionValue;
|
||||
this.option = this.#extractOptionString(message, index, "<value>' ");
|
||||
return `Option '--${this.option}' has an invalid value. ${this.#defaultMessages.helpPrimitive}`;
|
||||
}
|
||||
|
||||
#extractOptionString(message: string, index: number, testString: string) {
|
||||
return message.slice(index, message.indexOf(testString) - 1);
|
||||
}
|
||||
}
|
||||
2
src/lib/brigadier/index.ts
Normal file
2
src/lib/brigadier/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { main as parser } from "./parser";
|
||||
export { treeUtils as tree } from "./tree"
|
||||
139
src/lib/brigadier/parser.ts
Normal file
139
src/lib/brigadier/parser.ts
Normal file
@ -0,0 +1,139 @@
|
||||
import { tryCatch } from "@maxmorozoff/try-catch-tuple";
|
||||
import {
|
||||
parseArgs,
|
||||
type ParseArgsConfig,
|
||||
type ParseArgsOptionsConfig,
|
||||
} from "util";
|
||||
import { assertArrayOfStrings } from "../util/typeCheck";
|
||||
import { BrigadierConfigError, BrigadierInternalError, BrigadierOptionError } from "./errors";
|
||||
import type { BrigadierTree, BrigadierParserOverrides, BrigadierTreeCommand, BrigadierOutput } from "./types";
|
||||
import { treeUtils } from "./tree";
|
||||
|
||||
const defaultOverrides: BrigadierParserOverrides = {
|
||||
allowPositionals: true,
|
||||
allowNegative: true,
|
||||
tokens: false,
|
||||
strict: true,
|
||||
};
|
||||
|
||||
function findNextTree(tree: Array<BrigadierTreeCommand>, command: string) {
|
||||
const nextCommand = tree.filter((obj) => obj.command === command)
|
||||
return nextCommand[0]?.subcommands
|
||||
}
|
||||
|
||||
function findCommands(tree: Array<BrigadierTreeCommand> | undefined, args: Array<string>, out: Array<string> = []) {
|
||||
const item = args[0]
|
||||
if (!item || !tree) return out
|
||||
|
||||
const [commands, commandError] = tryCatch(() => treeUtils.commandArray(tree))
|
||||
if (commandError) throw commandError
|
||||
|
||||
if (item.slice(0, 0) === "-" || !commands.includes(item)) return out
|
||||
|
||||
const [newTree, newTreeError] = tryCatch(() => findNextTree(tree, item))
|
||||
if (newTreeError) throw newTreeError
|
||||
|
||||
const newArgs = args.slice(1)
|
||||
const newOut = [...out, item]
|
||||
|
||||
return findCommands(newTree, newArgs, newOut)
|
||||
}
|
||||
|
||||
function findOpts(tree: Array<BrigadierTreeCommand>, 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 thisOpts = commandObj[0]?.options ? commandObj[0].options : {}
|
||||
const nextOpts = Object.assign(opts, thisOpts)
|
||||
|
||||
if (commands.length === 1) {
|
||||
const config = nextOpts
|
||||
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)
|
||||
|
||||
}
|
||||
|
||||
function setParsedArgsConfig(
|
||||
args: Array<string>,
|
||||
options: ParseArgsOptionsConfig,
|
||||
overrides: BrigadierParserOverrides | undefined,
|
||||
commands: Array<string>
|
||||
) {
|
||||
if (!assertArrayOfStrings(args))
|
||||
throw new TypeError(`Parameter "args" must be an array of strings`);
|
||||
const obj =
|
||||
overrides === undefined
|
||||
? defaultOverrides
|
||||
: Object.assign({}, defaultOverrides, overrides);
|
||||
|
||||
const configObj: ParseArgsConfig = {
|
||||
args,
|
||||
options,
|
||||
...obj,
|
||||
};
|
||||
return { config: configObj, commands: commands };
|
||||
}
|
||||
|
||||
function generateParsedArgsConfig(tree: BrigadierTree, args: Array<string>, overrides: BrigadierParserOverrides | undefined = undefined) {
|
||||
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))
|
||||
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> }) {
|
||||
const [result, error] = tryCatch(() => {
|
||||
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>) {
|
||||
const testSet = new Set(commands)
|
||||
return positionals.filter(str => !testSet.has(str))
|
||||
}
|
||||
|
||||
function handleErr(error: Error) {
|
||||
if (error instanceof BrigadierOptionError || error instanceof BrigadierConfigError) return error
|
||||
return new BrigadierInternalError()
|
||||
}
|
||||
|
||||
function buildOutputObject(tree: BrigadierTree, args: Array<string>, overrides: BrigadierParserOverrides | undefined = undefined) {
|
||||
try {
|
||||
const [config, configError] = tryCatch(() => generateParsedArgsConfig(tree, args, overrides))
|
||||
if (configError) throw configError
|
||||
|
||||
const [parsedArgs, parsedError] = tryCatch(() => getParsedArgs(config))
|
||||
if (parsedError) throw parsedError
|
||||
|
||||
const programPaths = {
|
||||
bun: parsedArgs.positionals[0] ? parsedArgs.positionals[0] : "",
|
||||
path: parsedArgs.positionals[1] ? parsedArgs.positionals[1] : "",
|
||||
}
|
||||
const command = config.commands[0] ? { command: config.commands[0] } : {}
|
||||
const subcommands = config.commands[1] ? { subcommands: config.commands.slice(1) } : {}
|
||||
const [positionals, positionalsError] = tryCatch(() => 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 })
|
||||
return out
|
||||
} catch (error) {
|
||||
throw handleErr(error as Error)
|
||||
}
|
||||
}
|
||||
export const main = buildOutputObject;
|
||||
13
src/lib/brigadier/tree.ts
Normal file
13
src/lib/brigadier/tree.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { demoTree } from ".";
|
||||
import type { BrigadierTreeCommand } from "./types";
|
||||
|
||||
|
||||
function commandArray(array: Array<BrigadierTreeCommand>) {
|
||||
const out: Array<string> = []
|
||||
array.forEach((obj) => out.push(obj.command))
|
||||
return out
|
||||
}
|
||||
|
||||
export const treeUtils = {
|
||||
commandArray
|
||||
}
|
||||
49
src/lib/brigadier/types.ts
Normal file
49
src/lib/brigadier/types.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { type ParseArgsOptionsConfig } from 'util'
|
||||
|
||||
export type BrigadierCommandValue =
|
||||
"string" |
|
||||
"number" |
|
||||
"boolean" |
|
||||
"string[]" |
|
||||
"number[]" |
|
||||
"boolean[]" |
|
||||
"mixed[]"
|
||||
|
||||
export type BrigadierTreeCommand = {
|
||||
command: string,
|
||||
value?: {
|
||||
required: boolean,
|
||||
inputType: "string" | "boolean",
|
||||
dataType: BrigadierCommandValue,
|
||||
default?: string | boolean
|
||||
}
|
||||
options?: ParseArgsOptionsConfig,
|
||||
subcommands?: Array<BrigadierTreeCommand>
|
||||
}
|
||||
|
||||
export type BrigadierTree = {
|
||||
_opts: ParseArgsOptionsConfig,
|
||||
commands: Array<BrigadierTreeCommand>
|
||||
}
|
||||
|
||||
export type BrigadierOutput = {
|
||||
bun: string,
|
||||
path: string,
|
||||
command?: string,
|
||||
subcommands?: Array<string>,
|
||||
positionals?: Array<string>,
|
||||
values?: Record<string, string | boolean>
|
||||
}
|
||||
|
||||
export type BrigadierParserOverrides = {
|
||||
strict?: boolean;
|
||||
allowPositionals?: boolean;
|
||||
allowNegative?: boolean;
|
||||
tokens?: boolean;
|
||||
};
|
||||
|
||||
export type BrigadierInput = BrigadierTree & {
|
||||
args: Array<string>
|
||||
overrides?: BrigadierParserOverrides;
|
||||
};
|
||||
|
||||
6
src/lib/util/typeCheck.ts
Normal file
6
src/lib/util/typeCheck.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export function assertArrayOfStrings(arr: unknown) {
|
||||
const arrName = Object.keys({ arr })[0];
|
||||
if (!Array.isArray(arr)) return false;
|
||||
if (!arr.every((item: unknown) => typeof item === "string")) return false;
|
||||
return true;
|
||||
}
|
||||
38
tsconfig.json
Normal file
38
tsconfig.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Environment setup & latest features
|
||||
"lib": ["ESNext"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
// Best practices
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
|
||||
// Plugins
|
||||
"plugins": [
|
||||
{
|
||||
"name": "@maxmorozoff/try-catch-tuple-ts-plugin",
|
||||
// --- Optional Configuration for LSP ---
|
||||
"errorLevel": "error",
|
||||
"allowIgnoredError": true,
|
||||
"checkWrappedCalls": true}
|
||||
]
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user