Skip to content

Instantly share code, notes, and snippets.

@psenger
Created January 3, 2025 02:17
Show Gist options
  • Save psenger/eefb95d72835e2a1fd828bdd4c4f5afc to your computer and use it in GitHub Desktop.
Save psenger/eefb95d72835e2a1fd828bdd4c4f5afc to your computer and use it in GitHub Desktop.
[Prompt with NodeJS] #JavaScript #prompt
const readline = require('readline')
/**
* Prompts the user with a question and returns their answer.
*
* @param {readline.Interface} rl - The readline interface instance
* @param {string} query - The question to ask the user
* @returns {Promise<string>} A promise that resolves with the user's answer
*
* @example
* const rl = readline.createInterface({
* input: process.stdin,
* output: process.stdout
* });
* const answer = await askQuestion(rl, 'What is your name? ');
*/
const askQuestion = (rl, query) => new Promise((resolve) => rl.question(query, resolve));
/**
* Presents a numbered list of options to the user and prompts them to select one.
* Continues prompting until a valid selection is made.
*
* @param {string} label - The text to display above the list of options
* @param {string[]} options - Array of options to present to the user
* @returns {Promise<string>} A promise that resolves with the selected option
*
* @throws {Error} Will not throw, but may never resolve if user never provides valid input
*
* @example
* // Basic usage
* const host = await promptOptions('Select a host', [
* 'https://abc.com/',
* 'https://def.com/'
* ]);
* console.log(`Selected host: ${host}`);
*
* @example
* // Example output:
* // Select a host:
* // 1. https://abc.com/
* // 2. https://def.com/
* // Enter option number: 1
* // Returns: 'https://abc.com/'
*/
const promptOptions = async (label, options) => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
while (true) { // Infinite loop, will exit via return statements
console.log(`${label}:\n${options.map((host, index) => {
return `${index + 1}. ${host}`;
}).join('\n')}`);
const answer = await askQuestion(rl, 'Enter option number: ');
const index = Number.parseInt(answer.trim(), 10) - 1;
if (index >= 0 && index <= options.length - 1) {
rl.close();
return options[index];
}
}
}
/**
* Presents a numbered list of options to the user and returns both the selected value and label.
* Each option consists of a value-label pair provided as an object.
*
* @template T The type of the value in the value-label pair
* @param {string} label - The text to display above the list of options
* @param {Array<{value: T, label: string}>} options - Array of options, each with a value and display label
* @returns {Promise<{value: T, label: string}>} A promise that resolves with the selected option object
*
* @example
* // Basic usage with mixed value types
* const route = await promptWithValueLabel('Enter route', [
* { value: 'A24', label: 'Highway A23' },
* { value: 53, label: 'Route 53' },
* { value: null, label: 'none' }
* ]);
* console.log(`Selected: ${route.label} (value: ${route.value})`);
*
* @example
* // Example output:
* // Enter route:
* // 1. Highway A23
* // 2. Route 53
* // 3. none
* // Enter option number: 1
* // Returns: { value: 'A24', label: 'Highway A23' }
*
* @throws {Error} Will not throw, but may never resolve if user never provides valid input
*/
const promptWithValueLabel = async (label, options) => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
while (true) { // Infinite loop, will exit via return statements
console.log(`${label}:\n${options.map(({value: optionValue, label: optionLabel}, index) => {
return `${index + 1}. ${optionLabel}`;
}).join('\n')}`);
const answer = await askQuestion(rl, 'Enter option number: ');
const index = Number.parseInt(answer.trim(), 10) - 1;
if (index >= 0 && index <= options.length - 1) {
rl.close();
return {
value: options[index].value,
label: options[index].label
};
}
}
};
/**
* Prompts the user for input with a custom label and validates that the input is not empty.
* Continues prompting until valid input is received.
*
* @param {string} label - The prompt text to display to the user
* @returns {Promise<string>} A promise that resolves with the validated, trimmed user input
*
* @example
* // Basic usage
* async function main() {
* const name = await promptInput('Enter your name');
* console.log(`Hello, ${name}!`);
* }
*
* @example
* // Example with validation loop
* // Enter your name: // User hits enter with no input
* // Invalid. Please try again.
* // Enter your name: John
* // Returns: "John"
*
* @example
* // Handling whitespace
* // Enter your name: John Smith
* // Returns: "John Smith" // Note: leading/trailing spaces removed
*
* @throws {Error} Will not throw, but may never resolve if user never provides valid input
*/
const promptInput = async (label) => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
while (true) { // Infinite loop, will exit via return statements
const answer = await askQuestion(rl, `${label}: `);
if (answer.trim() !== '') {
rl.close();
return answer.trim();
}
console.log('Invalid. Please try again.');
}
};
const example = async () => {
const host = await promptOptions('Select a host', [
'https://abc.com/',
'https://def.com/'
])
const route = await promptWithValueLabel('Enter route', [
{value: 'A24', label: 'Highway A23'},
{value: 53, label: 'Route 53'},
{value: null, label: 'none'},
])
const name = await promptInput('Enter your name')
return {host, route, name}
}
example()
.then((...args) => {
console.log(...args)
process.exit(0)
})
.catch((err) => {
console.log(err)
process.exit(1)
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment