UNPKG

@decaf-ts/utils

Version:

module management utils for decaf-ts

608 lines 25.2 kB
import prompts from "prompts"; import { parseArgs } from "util"; import { Logging } from "@decaf-ts/logging"; /** * @description Represents a user input prompt with various configuration options. * @summary This class provides a flexible interface for creating and managing user input prompts. * It implements the PromptObject interface from the 'prompts' library and offers methods to set * various properties of the prompt. The class also includes static methods for common input scenarios * and argument parsing. * * @template R - The type of the prompt name, extending string. * * @param name - The name of the prompt, used as the key in the returned answers object. * * @class * @example * ```typescript * import { UserInput } from '@decaf-ts/utils'; * * // Create a simple text input * const nameInput = new UserInput('name') * .setMessage('What is your name?') * .setInitial('User'); * * // Create a number input with validation * const ageInput = new UserInput('age') * .setType('number') * .setMessage('How old are you?') * .setMin(0) * .setMax(120); * * // Ask for input and process the results * async function getUserInfo() { * const answers = await UserInput.ask([nameInput, ageInput]); * console.log(`Hello ${answers.name}, you are ${answers.age} years old.`); * } * * getUserInfo(); * ``` * * @mermaid * sequenceDiagram * participant Client * participant UserInput * participant PromptLibrary * * Client->>UserInput: new UserInput(name) * Client->>UserInput: setMessage(message) * Client->>UserInput: setType(type) * Client->>UserInput: setInitial(initial) * Client->>UserInput: Other configuration methods * * Client->>UserInput: ask() * UserInput->>PromptLibrary: prompts(question) * PromptLibrary->>Client: Display prompt * Client->>PromptLibrary: User provides input * PromptLibrary->>UserInput: Return answers * UserInput->>Client: Return processed answers */ export class UserInput { static { this.logger = Logging.for(UserInput); } constructor(name) { /** * @description The type of the prompt. * @summary Determines the input method (e.g., text, number, confirm). */ this.type = "text"; this.name = name; } /** * @description Sets the type of the prompt. * @summary Configures the input method for the prompt. * * @param type - The type of the prompt. * @returns This UserInput instance for method chaining. */ setType(type) { UserInput.logger.verbose(`Setting type to: ${type}`); this.type = type; return this; } /** * @description Sets the message of the prompt. * @summary Configures the question or instruction presented to the user. * * @param value - The message to be displayed. * @returns This UserInput instance for method chaining. */ setMessage(value) { UserInput.logger.verbose(`Setting message to: ${value}`); this.message = value; return this; } /** * @description Sets the initial value of the prompt. * @summary Configures the default value presented to the user. * * @param value - The initial value. * @returns This UserInput instance for method chaining. */ setInitial(value) { UserInput.logger.verbose(`Setting initial value to: ${value}`); this.initial = value; return this; } /** * @description Sets the style of the prompt. * @summary Configures the visual style of the prompt. * * @param value - The style to be applied. * @returns This UserInput instance for method chaining. */ setStyle(value) { UserInput.logger.verbose(`Setting style to: ${value}`); this.style = value; return this; } /** * @description Sets the format function of the prompt. * @summary Configures a function to format the user's input before it's returned. * * @param value - The format function. * @returns This UserInput instance for method chaining. */ setFormat(value) { UserInput.logger.verbose(`Setting format function`); this.format = value; return this; } /** * @description Sets the validation function of the prompt. * @summary Configures a function to validate the user's input. * * @param value - The validation function. * @returns This UserInput instance for method chaining. */ setValidate(value) { UserInput.logger.verbose(`Setting validate function`); this.validate = value; return this; } /** * @description Sets the onState callback of the prompt. * @summary Configures a function to be called when the state of the prompt changes. * * @param value - The onState callback function. * @returns This UserInput instance for method chaining. */ setOnState(value) { UserInput.logger.verbose(`Setting onState callback`); this.onState = value; return this; } /** * @description Sets the minimum value for number inputs. * @summary Configures the lowest number the user can input. * * @param value - The minimum value. * @returns This UserInput instance for method chaining. */ setMin(value) { UserInput.logger.verbose(`Setting min value to: ${value}`); this.min = value; return this; } /** * @description Sets the maximum value for number inputs. * @summary Configures the highest number the user can input. * * @param value - The maximum value. * @returns This UserInput instance for method chaining. */ setMax(value) { UserInput.logger.verbose(`Setting max value to: ${value}`); this.max = value; return this; } /** * @description Sets whether to allow float values for number inputs. * @summary Configures whether decimal numbers are allowed. * * @param value - Whether to allow float values. * @returns This UserInput instance for method chaining. */ setFloat(value) { UserInput.logger.verbose(`Setting float to: ${value}`); this.float = value; return this; } /** * @description Sets the number of decimal places to round to for float inputs. * @summary Configures the precision of float inputs. * * @param value - The number of decimal places. * @returns This UserInput instance for method chaining. */ setRound(value) { UserInput.logger.verbose(`Setting round to: ${value}`); this.round = value; return this; } /** * @description Sets the instructions for the user. * @summary Configures additional guidance provided to the user. * * @param value - The instructions. * @returns This UserInput instance for method chaining. */ setInstructions(value) { UserInput.logger.verbose(`Setting instructions to: ${value}`); this.instructions = value; return this; } /** * @description Sets the increment value for number inputs. * @summary Configures the step size when increasing or decreasing the number. * * @param value - The increment value. * @returns This UserInput instance for method chaining. */ setIncrement(value) { UserInput.logger.verbose(`Setting increment to: ${value}`); this.increment = value; return this; } /** * @description Sets the separator for list inputs. * @summary Configures the character used to separate list items. * * @param value - The separator character. * @returns This UserInput instance for method chaining. */ setSeparator(value) { UserInput.logger.verbose(`Setting separator to: ${value}`); this.separator = value; return this; } /** * @description Sets the active option style for select inputs. * @summary Configures the style applied to the currently selected option. * * @param value - The active option style. * @returns This UserInput instance for method chaining. */ setActive(value) { UserInput.logger.verbose(`Setting active style to: ${value}`); this.active = value; return this; } /** * @description Sets the inactive option style for select inputs. * @summary Configures the style applied to non-selected options. * * @param value - The inactive option style. * @returns This UserInput instance for method chaining. */ setInactive(value) { UserInput.logger.verbose(`Setting inactive style to: ${value}`); this.inactive = value; return this; } /** * @description Sets the choices for select, multiselect, or autocomplete inputs. * @summary Configures the available options that the user can select from in choice-based prompts. * * @param value - The array of choices or a function to determine the choices. * @returns This UserInput instance for method chaining. */ setChoices(value) { UserInput.logger.verbose(`Setting choices: ${JSON.stringify(value)}`); this.choices = value; return this; } /** * @description Sets the hint text for the prompt. * @summary Configures additional information displayed to the user. * * @param value - The hint text. * @returns This UserInput instance for method chaining. */ setHint(value) { UserInput.logger.verbose(`Setting hint to: ${value}`); this.hint = value; return this; } /** * @description Sets the warning text for the prompt. * @summary Configures a warning message displayed to the user. * * @param value - The warning text. * @returns This UserInput instance for method chaining. */ setWarn(value) { UserInput.logger.verbose(`Setting warn to: ${value}`); this.warn = value; return this; } /** * @description Sets the suggestion function for autocomplete inputs. * @summary Configures a function that provides suggestions based on the user's input and available choices. * * @param value - A function that takes the current input and available choices and returns a Promise resolving to suggestions. * @returns This UserInput instance for method chaining. */ setSuggest(value) { UserInput.logger.verbose(`Setting suggest function`); this.suggest = value; return this; } /** * @description Sets the limit for list inputs. * @summary Configures the maximum number of items that can be selected in list-type prompts. * @template R - The type of the prompt name, extending string. * @param value - The maximum number of items that can be selected, or a function to determine this value. * @return This UserInput instance for method chaining. */ setLimit(value) { UserInput.logger.verbose(`Setting limit to: ${value}`); this.limit = value; return this; } /** * @description Sets the mask for password inputs. * @summary Configures the character used to hide the user's input in password-type prompts. * @template R - The type of the prompt name, extending string. * @param value - The character used to mask the input, or a function to determine this value. * @return This UserInput instance for method chaining. */ setMask(value) { UserInput.logger.verbose(`Setting mask to: ${value}`); this.mask = value; return this; } /** * @description Sets the stdout stream for the prompt. * @summary Configures the output stream used by the prompt for displaying messages and results. * @param value - The Writable stream to be used as stdout. * @return This UserInput instance for method chaining. */ setStdout(value) { UserInput.logger.verbose(`Setting stdout stream`); this.stdout = value; return this; } /** * @description Sets the stdin stream for the prompt. * @summary Configures the input stream used by the prompt for receiving user input. * @param value - The Readable stream to be used as stdin. * @return This UserInput instance for method chaining. */ setStdin(value) { this.stdin = value; return this; } /** * @description Asks the user for input based on the current UserInput configuration. * @summary Prompts the user and returns their response as a single value. * @template R - The type of the prompt name, extending string. * @return A Promise that resolves to the user's answer. */ async ask() { return (await UserInput.ask(this))[this.name]; } /** * @description Asks the user one or more questions based on the provided UserInput configurations. * @summary Prompts the user with one or more questions and returns their answers as an object. * @template R - The type of the prompt name, extending string. * @param question - A single UserInput instance or an array of UserInput instances. * @return A Promise that resolves to an object containing the user's answers. * @mermaid * sequenceDiagram * participant U as User * participant A as ask method * participant P as prompts library * A->>P: Call prompts with question(s) * P->>U: Display prompt(s) * U->>P: Provide input * P->>A: Return answers * A->>A: Process answers * A-->>Caller: Return processed answers */ static async ask(question) { const log = UserInput.logger.for(this.ask); if (!Array.isArray(question)) { question = [question]; } let answers; try { log.verbose(`Asking questions: ${question.map((q) => q.name).join(", ")}`); answers = await prompts(question); log.verbose(`Received answers: ${JSON.stringify(answers, null, 2)}`); } catch (error) { throw new Error(`Error while getting input: ${error}`); } return answers; } /** * @description Asks the user for a number input. * @summary Prompts the user to enter a number, with optional minimum, maximum, and initial values. * @param name - The name of the prompt, used as the key in the returned answers object. * @param question - The message displayed to the user. * @param min - The minimum allowed value (optional). * @param max - The maximum allowed value (optional). * @param initial - The initial value presented to the user (optional). * @return A Promise that resolves to the number entered by the user. */ static async askNumber(name, question, min, max, initial) { const log = UserInput.logger.for(this.askNumber); log.verbose(`Asking number input: undefined, question: ${question}, min: ${min}, max: ${max}, initial: ${initial}`); const userInput = new UserInput(name) .setMessage(question) .setType("number"); if (typeof min === "number") userInput.setMin(min); if (typeof max === "number") userInput.setMax(max); if (typeof initial === "number") userInput.setInitial(initial); return (await this.ask(userInput))[name]; } /** * @description Asks the user for a text input. * @summary Prompts the user to enter text, with optional masking and initial value. * @param name - The name of the prompt, used as the key in the returned answers object. * @param question - The message displayed to the user. * @param mask - The character used to mask the input (optional, for password-like inputs). * @param initial - The initial value presented to the user (optional). * @return A Promise that resolves to the text entered by the user. */ static async askText(name, question, mask = undefined, initial) { const log = UserInput.logger.for(this.askText); log.verbose(`Asking text input: undefined, question: ${question}, mask: ${mask}, initial: ${initial}`); const userInput = new UserInput(name).setMessage(question); if (mask) userInput.setMask(mask); if (typeof initial === "string") userInput.setInitial(initial); return (await this.ask(userInput))[name]; } /** * @description Asks the user for a confirmation (yes/no). * @summary Prompts the user with a yes/no question and returns a boolean result. * @param name - The name of the prompt, used as the key in the returned answers object. * @param question - The message displayed to the user. * @param initial - The initial value presented to the user (optional). * @return A Promise that resolves to a boolean representing the user's answer. */ static async askConfirmation(name, question, initial) { const log = UserInput.logger.for(this.askConfirmation); log.verbose(`Asking confirmation input: undefined, question: ${question}, initial: ${initial}`); const userInput = new UserInput(name) .setMessage(question) .setType("confirm"); if (typeof initial !== "undefined") userInput.setInitial(initial); return (await this.ask(userInput))[name]; } /** * @description Repeatedly asks for input until a valid response is given or the limit is reached. * @summary This method insists on getting a valid input from the user, allowing for a specified number of attempts. * * @template R - The type of the expected result. * @param input - The UserInput instance to use for prompting. * @param {function(string):boolean} test - Validator function receiving the user input and returning whether it is valid. * @param defaultConfirmation - The default value for the confirmation prompt (true for yes, false for no). * @param limit - The maximum number of attempts allowed (default is 1). * @return A Promise that resolves to the valid input or undefined if the limit is reached. * * @mermaid * sequenceDiagram * participant U as User * participant I as insist method * participant A as ask method * participant T as test function * participant C as askConfirmation method * loop Until valid input or limit reached * I->>A: Call ask with input * A->>U: Prompt user * U->>A: Provide input * A->>I: Return result * I->>T: Test result * alt Test passes * I->>C: Ask for confirmation * C->>U: Confirm input * U->>C: Provide confirmation * C->>I: Return confirmation * alt Confirmed * I-->>Caller: Return valid result * else Not confirmed * I->>I: Continue loop * end * else Test fails * I->>I: Continue loop * end * end * I-->>Caller: Return undefined if limit reached */ static async insist(input, test, defaultConfirmation, limit = 1) { const log = UserInput.logger.for(this.insist); log.verbose(`Insisting on input: ${input.name}, test: ${test.toString()}, defaultConfirmation: ${defaultConfirmation}, limit: ${limit}`); let result = undefined; let count = 0; let confirmation; try { do { result = (await UserInput.ask(input))[input.name]; if (!test(result)) { result = undefined; continue; } confirmation = await UserInput.askConfirmation(`${input.name}-confirm`, `Is the ${input.type} correct?`, defaultConfirmation); if (!confirmation) result = undefined; } while (typeof result === "undefined" && limit > 1 && count++ < limit); } catch (e) { log.error(`Error while insisting: ${e}`); throw e; } if (typeof result === "undefined") log.info("no selection..."); return result; } /** * @description Repeatedly asks for text input until a valid response is given or the limit is reached. * @summary This method insists on getting a valid text input from the user, allowing for a specified number of attempts. * * @param name - The name of the prompt, used as the key in the returned answers object. * @param question - The message displayed to the user. * @param {function(number):boolean} test - Validator function receiving the user input and returning whether it is valid. * @param mask - The character used to mask the input (optional, for password-like inputs). * @param initial - The initial value presented to the user (optional). * @param defaultConfirmation - The default value for the confirmation prompt (true for yes, false for no). * @param limit - The maximum number of attempts allowed (default is -1, meaning unlimited). * @return A Promise that resolves to the valid input or undefined if the limit is reached. */ static async insistForText(name, question, test, mask = undefined, initial, defaultConfirmation = false, limit = -1) { const log = UserInput.logger.for(this.insistForText); log.verbose(`Insisting for text input: undefined, question: ${question}, test: ${test.toString()}, mask: ${mask}, initial: ${initial}, defaultConfirmation: ${defaultConfirmation}, limit: ${limit}`); const userInput = new UserInput(name).setMessage(question); if (mask) userInput.setMask(mask); if (typeof initial === "string") userInput.setInitial(initial); return (await this.insist(userInput, test, defaultConfirmation, limit)); } /** * @description Repeatedly asks for number input until a valid response is given or the limit is reached. * @summary This method insists on getting a valid number input from the user, allowing for a specified number of attempts. * * @param name - The name of the prompt, used as the key in the returned answers object. * @param question - The message displayed to the user. * @param test - A function to validate the user's input. * @param min - The minimum allowed value (optional). * @param max - The maximum allowed value (optional). * @param initial - The initial value presented to the user (optional). * @param defaultConfirmation - The default value for the confirmation prompt (true for yes, false for no). * @param limit - The maximum number of attempts allowed (default is -1, meaning unlimited). * @return A Promise that resolves to the valid input or undefined if the limit is reached. */ static async insistForNumber(name, question, test, min, max, initial, defaultConfirmation = false, limit = -1) { const log = UserInput.logger.for(this.insistForNumber); log.verbose(`Insisting for number input: undefined, question: ${question}, test: ${test.toString()}, min: ${min}, max: ${max}, initial: ${initial}, defaultConfirmation: ${defaultConfirmation}, limit: ${limit}`); const userInput = new UserInput(name) .setMessage(question) .setType("number"); if (typeof min === "number") userInput.setMin(min); if (typeof max === "number") userInput.setMax(max); if (typeof initial === "number") userInput.setInitial(initial); return (await this.insist(userInput, test, defaultConfirmation, limit)); } /** * @description Parses command-line arguments based on the provided options. * @summary Uses Node.js's util.parseArgs to parse command-line arguments and return the result. * @param options - Configuration options for parsing arguments. * @return An object containing the parsed arguments. * @mermaid * sequenceDiagram * participant C as Caller * participant P as parseArgs method * participant U as util.parseArgs * C->>P: Call with options * P->>P: Prepare args object * P->>U: Call parseArgs with prepared args * U->>P: Return parsed result * P-->>C: Return ParseArgsResult */ static parseArgs(options) { const log = UserInput.logger.for(this.parseArgs); const args = { args: process.argv.slice(2), options: options, }; log.debug(`Parsing arguments: ${JSON.stringify(args, null, 2)}`); try { return parseArgs(args); } catch (error) { log.debug(`Error while parsing arguments:\n${JSON.stringify(args, null, 2)}\n | options\n${JSON.stringify(options, null, 2)}\n | ${error}`); throw new Error(`Error while parsing arguments: ${error}`); } } } //# sourceMappingURL=input.js.map