From e92582832838528d3d69dbacf8cf28bf6f28479a Mon Sep 17 00:00:00 2001 From: themodernhakr Date: Sat, 17 May 2025 01:24:03 -0500 Subject: [PATCH] Formatting pass --- src/lib/brigadier/demoCommands.ts | 152 ++++++++-------- src/lib/brigadier/errors.ts | 130 ++++++------- src/lib/brigadier/parser.ts | 252 ++++++++++++++++---------- src/lib/brigadier/tree.ts | 15 +- src/lib/brigadier/types.ts | 69 ++++--- src/lib/promptsmith/README.md | 2 + src/lib/promptsmith/select-options.ts | 221 +++++++++++++--------- src/lib/util/terminal.ts | 77 ++++---- src/lib/util/typeCheck.ts | 2 +- 9 files changed, 511 insertions(+), 409 deletions(-) diff --git a/src/lib/brigadier/demoCommands.ts b/src/lib/brigadier/demoCommands.ts index 654445f..bdbbc03 100644 --- a/src/lib/brigadier/demoCommands.ts +++ b/src/lib/brigadier/demoCommands.ts @@ -1,88 +1,92 @@ -import { type ParseArgsOptionsConfig } from 'util' -import type { BrigadierTreeCommand, BrigadierTree } from "./types"; +import { type ParseArgsOptionsConfig } from 'util'; +import type { BrigadierTreeCommand, BrigadierTree } from './types'; -const commandObj: - { _opts?: ParseArgsOptionsConfig } & - Record = {} +const commandObj: { _opts?: ParseArgsOptionsConfig } & Record< + string, + BrigadierTreeCommand +> = {}; commandObj._opts = { - verbose: { - type: "boolean", - short: 'v', - }, - silent: { - type: "boolean", - short: "s", - }, -} + verbose: { + type: 'boolean', + short: 'v', + }, + silent: { + type: 'boolean', + short: 's', + }, +}; commandObj.run = { - command: "run", - options: { - speed: { - type: "string", - } - } -} + command: 'run', + options: { + speed: { + type: 'string', + }, + }, +}; commandObj.walk = { - command: "walk", - options: { - fancy: { - type: "boolean", - short: "f" - } - } -} + command: 'walk', + options: { + fancy: { + type: 'boolean', + short: 'f', + }, + }, +}; commandObj.stand = { - command: "stand", -} + command: 'stand', +}; commandObj.sit = { - command: "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[]" - } - } - ] -} + 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)] } +const { _opts, ...commands } = commandObj; +export const main: BrigadierTree = { + _opts, + commands: [...Object.values(commands)], +}; diff --git a/src/lib/brigadier/errors.ts b/src/lib/brigadier/errors.ts index 720d72b..b5e1526 100644 --- a/src/lib/brigadier/errors.ts +++ b/src/lib/brigadier/errors.ts @@ -1,82 +1,82 @@ export class BrigadierInternalError extends Error { - constructor(error: Error | undefined = undefined) { - super() + 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 - } + 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) + constructor(message: string, error: Error | undefined = undefined) { + super(message); - this.name = this.constructor.name; - if (error) this.cause = error - } + 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; + #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); + constructor(message: string, error: Error) { + super(message); - if ("code" in error && typeof error.code === "string") - this.#switchOnCodes(error.code); + if ('code' in error && typeof error.code === 'string') + this.#switchOnCodes(error.code); - this.name = this.constructor.name; - if (error) this.cause = error; - } + 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; - } - } + #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}`; - } + #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, "' "); - return `Option '--${this.option}' has an invalid value. ${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, "' "); + 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); - } + #extractOptionString(message: string, index: number, testString: string) { + return message.slice(index, message.indexOf(testString) - 1); + } } diff --git a/src/lib/brigadier/parser.ts b/src/lib/brigadier/parser.ts index e08d54e..97a280d 100644 --- a/src/lib/brigadier/parser.ts +++ b/src/lib/brigadier/parser.ts @@ -1,139 +1,193 @@ -import { tryCatch } from "@maxmorozoff/try-catch-tuple"; +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"; + 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, + allowPositionals: true, + allowNegative: true, + tokens: false, + strict: true, }; function findNextTree(tree: Array, command: string) { - const nextCommand = tree.filter((obj) => obj.command === command) - return nextCommand[0]?.subcommands + const nextCommand = tree.filter((obj) => obj.command === command); + return nextCommand[0]?.subcommands; } -function findCommands(tree: Array | undefined, args: Array, out: Array = []) { - const item = args[0] - if (!item || !tree) return out +function findCommands( + tree: Array | undefined, + args: Array, + out: Array = [] +) { + const item = args[0]; + if (!item || !tree) return out; - const [commands, commandError] = tryCatch(() => treeUtils.commandArray(tree)) - if (commandError) throw commandError + const [commands, commandError] = tryCatch(() => treeUtils.commandArray(tree)); + 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)) - if (newTreeError) throw newTreeError + const [newTree, newTreeError] = tryCatch(() => findNextTree(tree, item)); + if (newTreeError) throw newTreeError; - const newArgs = args.slice(1) - const newOut = [...out, item] + const newArgs = args.slice(1); + const newOut = [...out, item]; - return findCommands(newTree, newArgs, newOut) + return findCommands(newTree, newArgs, newOut); } -function findOpts(tree: Array, commands: Array, opts: ParseArgsOptionsConfig = {}) { - const item = commands[0] - if (!item) throw new TypeError("The first item in the array is missing.") +function findOpts( + tree: Array, + commands: Array, + 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) + 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) + 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, - options: ParseArgsOptionsConfig, - overrides: BrigadierParserOverrides | undefined, - commands: Array + args: Array, + options: ParseArgsOptionsConfig, + overrides: BrigadierParserOverrides | undefined, + commands: Array ) { - if (!assertArrayOfStrings(args)) - throw new TypeError(`Parameter "args" must be an array of strings`); - const obj = - overrides === undefined - ? defaultOverrides - : Object.assign({}, defaultOverrides, overrides); + 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 }; + const configObj: ParseArgsConfig = { + args, + options, + ...obj, + }; + return { config: configObj, commands: commands }; } -function generateParsedArgsConfig(tree: BrigadierTree, args: Array, 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) - } +function generateParsedArgsConfig( + tree: BrigadierTree, + args: Array, + 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) + 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 }) { - const [result, error] = tryCatch(() => { - return parseArgs(opts.config); - }); - if (error) throw new BrigadierOptionError(error.message, error); - return { ...result, commands: opts.commands }; +function getParsedArgs(opts: { + config: ParseArgsConfig; + commands: Array; +}) { + 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, positionals: Array) { - const testSet = new Set(commands) - return positionals.filter(str => !testSet.has(str)) +function buildPositionalsArray( + commands: Array, + positionals: Array +) { + 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() + if ( + error instanceof BrigadierOptionError || + error instanceof BrigadierConfigError + ) + return error; + return new BrigadierInternalError(); } -function buildOutputObject(tree: BrigadierTree, args: Array, overrides: BrigadierParserOverrides | undefined = undefined) { - try { - const [config, configError] = tryCatch(() => generateParsedArgsConfig(tree, args, overrides)) - if (configError) throw configError +function buildOutputObject( + tree: BrigadierTree, + args: Array, + 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 [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 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) - } + const out: BrigadierOutput = Object.assign( + {}, + programPaths, + command, + subcommands, + positionalsObj, + { values } + ); + return out; + } catch (error) { + throw handleErr(error as Error); + } } export const main = buildOutputObject; diff --git a/src/lib/brigadier/tree.ts b/src/lib/brigadier/tree.ts index 154ff48..a7ed218 100644 --- a/src/lib/brigadier/tree.ts +++ b/src/lib/brigadier/tree.ts @@ -1,13 +1,12 @@ -import { demoTree } from "."; -import type { BrigadierTreeCommand } from "./types"; - +import { demoTree } from '.'; +import type { BrigadierTreeCommand } from './types'; function commandArray(array: Array) { - const out: Array = [] - array.forEach((obj) => out.push(obj.command)) - return out + const out: Array = []; + array.forEach((obj) => out.push(obj.command)); + return out; } export const treeUtils = { - commandArray -} + commandArray, +}; diff --git a/src/lib/brigadier/types.ts b/src/lib/brigadier/types.ts index 178fb55..ca4b56b 100644 --- a/src/lib/brigadier/types.ts +++ b/src/lib/brigadier/types.ts @@ -1,49 +1,48 @@ -import { type ParseArgsOptionsConfig } from 'util' +import { type ParseArgsOptionsConfig } from 'util'; export type BrigadierCommandValue = - "string" | - "number" | - "boolean" | - "string[]" | - "number[]" | - "boolean[]" | - "mixed[]" + | '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 -} + command: string; + value?: { + required: boolean; + inputType: 'string' | 'boolean'; + dataType: BrigadierCommandValue; + default?: string | boolean; + }; + options?: ParseArgsOptionsConfig; + subcommands?: Array; +}; export type BrigadierTree = { - _opts: ParseArgsOptionsConfig, - commands: Array -} + _opts: ParseArgsOptionsConfig; + commands: Array; +}; export type BrigadierOutput = { - bun: string, - path: string, - command?: string, - subcommands?: Array, - positionals?: Array, - values?: Record -} + bun: string; + path: string; + command?: string; + subcommands?: Array; + positionals?: Array; + values?: Record; +}; export type BrigadierParserOverrides = { - strict?: boolean; - allowPositionals?: boolean; - allowNegative?: boolean; - tokens?: boolean; + strict?: boolean; + allowPositionals?: boolean; + allowNegative?: boolean; + tokens?: boolean; }; export type BrigadierInput = BrigadierTree & { - args: Array - overrides?: BrigadierParserOverrides; + args: Array; + overrides?: BrigadierParserOverrides; }; - diff --git a/src/lib/promptsmith/README.md b/src/lib/promptsmith/README.md index 2721a33..0488dbe 100644 --- a/src/lib/promptsmith/README.md +++ b/src/lib/promptsmith/README.md @@ -1,3 +1,5 @@ +# Features to Impliment + ## Basic Inputs - [ ] **Text Input** – User types a free-form response. diff --git a/src/lib/promptsmith/select-options.ts b/src/lib/promptsmith/select-options.ts index b15dfb0..82031b4 100644 --- a/src/lib/promptsmith/select-options.ts +++ b/src/lib/promptsmith/select-options.ts @@ -4,125 +4,166 @@ import { TerminalUtils, type CursorPosition } from '../util/terminal'; import { assertArrayOfStrings } from '../util/typeCheck'; export type PromptsmithConfig = { - format?: { - selected?: string, - selectedText?: string, - defualt?: string, - defualtText?: string, - }, - selector?: { - selected?: string, - default?: string, - } -} + format?: { + selected?: string; + selectedText?: string; + defualt?: string; + defualtText?: string; + }; + selector?: { + selected?: string; + default?: string; + }; +}; // 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[] => { - TerminalUtils.cursor.restorePosition(pos); - TerminalUtils.clear.below(); +const renderOptions = ( + options: string[], + selectedIndex: number, + pos: CursorPosition, + config: PromptsmithConfig +): string[] => { + TerminalUtils.cursor.restorePosition(pos); + TerminalUtils.clear.below(); - return [ - formatText('Use the ↑ and ↓ keys to select an option. Press ENTER to confirm.\n', '\x1B[1m'), // Bold - ...options.map((option, index) => { - const isSelected = index === selectedIndex; - const selector = formatText( - isSelected ? config.selector.selected as string : config.selector.default as string, - isSelected ? 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 - }), - ]; + return [ + formatText( + 'Use the ↑ and ↓ keys to select an option. Press ENTER to confirm.\n', + '\x1B[1m' + ), // Bold + ...options.map((option, index) => { + const isSelected = index === selectedIndex; + const selector = formatText( + isSelected + ? (config.selector.selected as string) + : (config.selector.default as string), + isSelected + ? (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 -const updateSelection = (selectedIndex: number, keyName: string, options: string[]): number => - keyName === 'up' ? Math.max(selectedIndex - 1, 0) : - keyName === 'down' ? Math.min(selectedIndex + 1, options.length - 1) : - selectedIndex; +const updateSelection = ( + selectedIndex: number, + keyName: string, + 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 -const handleKeyPress = async (options: string[], selectedIndex: number, pos: CursorPosition, config: PromptsmithConfig): Promise => { - return new Promise((resolve) => { - if (!assertArrayOfStrings(options)) throw new TypeError("'options' should be Array.") +const handleKeyPress = async ( + options: string[], + selectedIndex: number, + pos: CursorPosition, + config: PromptsmithConfig +): Promise => { + return new Promise((resolve) => { + if (!assertArrayOfStrings(options)) + throw new TypeError("'options' should be Array."); - process.stdin.resume(); - readline.emitKeypressEvents(process.stdin); + process.stdin.resume(); + 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 newIndex = updateSelection(selectedIndex, key.name as string, options); + const onKeyPress = async (_: string, key: readline.Key) => { + const newIndex = updateSelection( + selectedIndex, + key.name as string, + options + ); - if (key.name === 'return') { - cleanup(); - resolve(options[newIndex] as string); - return; - } else if (key.name === 'c' && key.ctrl) { - cleanup(); - process.exit(); - } + if (key.name === 'return') { + cleanup(); + resolve(options[newIndex] as string); + return; + } else if (key.name === 'c' && key.ctrl) { + cleanup(); + process.exit(); + } - renderOptions(options, newIndex, pos, config).forEach(line => console.log(line)); - selectedIndex = newIndex; - }; + renderOptions(options, newIndex, pos, config).forEach((line) => + console.log(line) + ); + selectedIndex = newIndex; + }; - process.stdin.on('keypress', onKeyPress); + process.stdin.on('keypress', onKeyPress); - const cleanup = () => { - process.stdin.removeListener('keypress', onKeyPress); - if (process.stdin.isTTY) process.stdin.setRawMode(false); - TerminalUtils.cursor.restorePosition(pos) - TerminalUtils.clear.below() - process.stdin.pause(); - }; - }); + const cleanup = () => { + process.stdin.removeListener('keypress', onKeyPress); + if (process.stdin.isTTY) process.stdin.setRawMode(false); + TerminalUtils.cursor.restorePosition(pos); + TerminalUtils.clear.below(); + process.stdin.pause(); + }; + }); }; 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.selectedText) config.format.selectedText = config.format.selected - if (!config.format.defualt) config.format.defualt = '' - if (!config.format.defualtText) config.format.defualtText = config.format.defualt + if (!config.format.selected) config.format.selected = '\x1B[32m\x1B[1m'; + if (!config.format.selectedText) + config.format.selectedText = config.format.selected; + 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.default) config.selector.default = ' ' - if (!config.selector.selected) config.selector.selected = '> ' + if (!config.selector) config.selector = {}; + if (!config.selector.default) config.selector.default = ' '; + if (!config.selector.selected) config.selector.selected = '> '; - return config -} + return config; +}; // Main function using pure composition const defaultConfig: PromptsmithConfig = { - format: { - selected: '\x1B[32m\x1B[1m', - }, - selector: { - selected: '> ', - default: ' ' - } -} -const interactiveSelect = async (options: string[], config: PromptsmithConfig = {}): Promise => { - const newConfig = setConfig(config) + format: { + selected: '\x1B[32m\x1B[1m', + }, + selector: { + selected: '> ', + default: ' ', + }, +}; +const interactiveSelect = async ( + options: string[], + config: PromptsmithConfig = {} +): Promise => { + const newConfig = setConfig(config); - const pos = await TerminalUtils.cursor.getCursorPosition(); - renderOptions(options, 0, pos, newConfig).forEach(line => console.log(line)); - return handleKeyPress(options, 0, pos, newConfig); + const pos = await TerminalUtils.cursor.getCursorPosition(); + renderOptions(options, 0, pos, newConfig).forEach((line) => + console.log(line) + ); + return handleKeyPress(options, 0, pos, newConfig); }; // Execution (async () => { - const options = ['Option A', 'Option B', 'Option C']; - const selectedOption = await interactiveSelect(options, { selector: { selected: '* ' } }); - console.log(`You selected: ${selectedOption}`); + const options = ['Option A', 'Option B', 'Option C']; + const selectedOption = await interactiveSelect(options, { + selector: { selected: '* ' }, + }); + console.log(`You selected: ${selectedOption}`); - process.stdin.pause(); // Ensures stdin closes AFTER selection + process.stdin.pause(); // Ensures stdin closes AFTER selection })(); - diff --git a/src/lib/util/terminal.ts b/src/lib/util/terminal.ts index 40d2477..3a660b9 100644 --- a/src/lib/util/terminal.ts +++ b/src/lib/util/terminal.ts @@ -3,47 +3,50 @@ export type CursorPosition = { row: number; col: number }; export const TerminalUtils = { - clear: { - screen: () => process.stdout.write('\x1Bc'), - content: () => process.stdout.write('\x1B[2J'), - cursorHome: () => process.stdout.write('\x1B[H'), - line: () => process.stdout.write('\x1B[K'), - below: () => process.stdout.write('\x1B[J'), - above: () => process.stdout.write('\x1B[1J'), - clear: () => console.clear(), - }, + clear: { + screen: () => process.stdout.write('\x1Bc'), + content: () => process.stdout.write('\x1B[2J'), + cursorHome: () => process.stdout.write('\x1B[H'), + line: () => process.stdout.write('\x1B[K'), + below: () => process.stdout.write('\x1B[J'), + above: () => process.stdout.write('\x1B[1J'), + clear: () => console.clear(), + }, - cursor: { /** Move cursor to a specific row & column */ - moveCursor: (row: number, col: number) => process.stdout.write(`\x1B[${row};${col}H`), + cursor: { + /** 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 */ - async getCursorPosition(): Promise { - return new Promise((resolve) => { - const stdin = process.stdin; - stdin.setRawMode(true); - stdin.resume(); - stdin.setEncoding('utf8'); + /** Queries the terminal for the current cursor position */ + async getCursorPosition(): Promise { + return new Promise((resolve) => { + const stdin = process.stdin; + stdin.setRawMode(true); + stdin.resume(); + stdin.setEncoding('utf8'); - stdin.once('data', (data) => { - const match = /\[(\d+);(\d+)R/.exec(data.toString()); - resolve({ - row: parseInt(match?.[1] ?? '0', 10), - col: parseInt(match?.[2] ?? '0', 10), - }); - stdin.setRawMode(false); - stdin.pause(); - }); + stdin.once('data', (data) => { + const match = /\[(\d+);(\d+)R/.exec(data.toString()); + resolve({ + row: parseInt(match?.[1] ?? '0', 10), + col: parseInt(match?.[2] ?? '0', 10), + }); + stdin.setRawMode(false); + 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 */ - async storePosition(): Promise { - return TerminalUtils.cursor.getCursorPosition(); - }, + /** Stores detected cursor position in a constant */ + async storePosition(): Promise { + return TerminalUtils.cursor.getCursorPosition(); + }, - /** Restores the passed cursor position */ - restorePosition: (pos: CursorPosition) => TerminalUtils.cursor.moveCursor(pos.row, pos.col), - } + /** Restores the passed cursor position */ + restorePosition: (pos: CursorPosition) => + TerminalUtils.cursor.moveCursor(pos.row, pos.col), + }, }; diff --git a/src/lib/util/typeCheck.ts b/src/lib/util/typeCheck.ts index 8a56592..0818897 100644 --- a/src/lib/util/typeCheck.ts +++ b/src/lib/util/typeCheck.ts @@ -1,5 +1,5 @@ export function assertArrayOfStrings(arr: unknown) { 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; }