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