-
-
Notifications
You must be signed in to change notification settings - Fork 302
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor Prompts #43
Merged
Refactor Prompts #43
Changes from 1 commit
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
89ae0b9
consolidate all prompts into single helper
lukeed aa6a192
replace `toArray` with concatenation
lukeed 8c65cc3
save `question.type` to variable; update existence check
lukeed 6457a15
fix `args` param rename on `autocomplete`
lukeed b42cfc0
assign type value via shared destruct
lukeed 6e43a5f
move `o.message` throw into main loop
lukeed 3f5fd9d
replace silent `choices` throws; ensure array instead
lukeed 46f7787
rewrite prompts file exports; immediate vs bottom
lukeed File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,186 +1,164 @@ | ||
'use strict'; | ||
|
||
const el = require('./elements'); | ||
const noop = () => {}; | ||
const noop = v => v; | ||
|
||
function toPrompt(type, args, opts={}) { | ||
if (typeof args.message !== 'string') { | ||
throw new Error('message is required'); | ||
} | ||
|
||
return new Promise((res, rej) => { | ||
const p = new el[type](args); | ||
const onAbort = opts.onAbort || noop; | ||
const onSubmit = opts.onSubmit || noop; | ||
p.on('state', args.onState || noop); | ||
p.on('submit', x => res(onSubmit(x))); | ||
p.on('abort', x => rej(onAbort(x))); | ||
}); | ||
} | ||
|
||
/** | ||
* Text prompt | ||
* @param {string} message Prompt message to display | ||
* @param {string} [initial] Default string value | ||
* @param {string} [style="default"] Render style ('default', 'password', 'invisible') | ||
* @param {function} [onState] On state change callback | ||
* @param {string} args.message Prompt message to display | ||
* @param {string} [args.initial] Default string value | ||
* @param {string} [args.style="default"] Render style ('default', 'password', 'invisible') | ||
* @param {function} [args.onState] On state change callback | ||
* @returns {Promise} Promise with user input | ||
*/ | ||
function text({ message, initial, style, onState = noop }) { | ||
if (typeof message !== 'string') throw new Error('message is required'); | ||
return new Promise((resolve, reject) => { | ||
const p = new el.TextPrompt({ message, initial, style }); | ||
p.on('submit', resolve); | ||
p.on('abort', reject); | ||
p.on('state', onState) | ||
}); | ||
function text(args) { | ||
return toPrompt('TextPrompt', args); | ||
} | ||
|
||
/** | ||
* Password prompt with masked input | ||
* @param {string} message Prompt message to display | ||
* @param {string} [initial] Default string value | ||
* @param {function} [onState] On state change callback | ||
* @param {string} args.message Prompt message to display | ||
* @param {string} [args.initial] Default string value | ||
* @param {function} [args.onState] On state change callback | ||
* @returns {Promise} Promise with user input | ||
* | ||
*/ | ||
const password = ({ message, initial, onState }) => text({ message, initial, onState, style: 'password' }); | ||
function password(args) { | ||
args.style = 'password'; | ||
return text(args); | ||
} | ||
|
||
/** | ||
* Prompt where input is invisible, like sudo | ||
* @param {string} message Prompt message to display | ||
* @param {string} [initial] Default string value | ||
* @param {function} [onState] On state change callback | ||
* @param {string} opts.message Prompt message to display | ||
* @param {string} [opts.initial] Default string value | ||
* @param {function} [opts.onState] On state change callback | ||
* @returns {Promise} Promise with user input | ||
*/ | ||
const invisible = ({ message, initial, onState }) => text({ message, initial, onState, style: 'invisible' }); | ||
function invisible(opts) { | ||
opts.style = 'invisible'; | ||
return text(opts); | ||
} | ||
|
||
/** | ||
* Number prompt | ||
* @param {string} message Prompt message to display | ||
* @param {number} initial Default number value | ||
* @param {number} [max] Max value | ||
* @param {number} [min] Min value | ||
* @param {string} [style="default"] Render style ('default', 'password', 'invisible') | ||
* @param {function} [onState] On state change callback | ||
* @param {string} args.message Prompt message to display | ||
* @param {number} args.initial Default number value | ||
* @param {number} [args.max] Max value | ||
* @param {number} [args.min] Min value | ||
* @param {string} [args.style="default"] Render style ('default', 'password', 'invisible') | ||
* @param {function} [args.onState] On state change callback | ||
* @returns {Promise} Promise with user input | ||
*/ | ||
function number({ message, initial, max, min, style, onState = noop }) { | ||
if (typeof message !== 'string') throw new Error('message is required'); | ||
return new Promise((resolve, reject) => { | ||
const p = new el.NumberPrompt({ message, initial, max, min, style }); | ||
p.on('submit', resolve); | ||
p.on('abort', reject); | ||
p.on('state', onState) | ||
}); | ||
function number(args) { | ||
return toPrompt('NumberPrompt', args); | ||
} | ||
|
||
/** | ||
* Classic yes/no prompt | ||
* @param {string} message Prompt message to display | ||
* @param {boolean} [initial=false] Default value | ||
* @param {function} [onState] On state change callback | ||
* @param {string} args.message Prompt message to display | ||
* @param {boolean} [args.initial=false] Default value | ||
* @param {function} [args.onState] On state change callback | ||
* @returns {Promise} Promise with user input | ||
*/ | ||
function confirm({ message, initial, onState = noop }) { | ||
if (typeof message !== 'string') throw new Error('message is required'); | ||
return new Promise((resolve, reject) => { | ||
const p = new el.ConfirmPrompt({ message, initial }); | ||
p.on('submit', resolve); | ||
p.on('abort', reject); | ||
p.on('state', onState); | ||
}); | ||
function confirm(args) { | ||
return toPrompt('ConfirmPrompt', args); | ||
} | ||
|
||
/** | ||
* List prompt, split intput string by `seperator` | ||
* @param {string} message Prompt message to display | ||
* @param {string} [initial] Default string value | ||
* @param {string} [style="default"] Render style ('default', 'password', 'invisible') | ||
* @param {string} [separator] String separator | ||
* @param {function} [onState] On state change callback | ||
* @param {string} args.message Prompt message to display | ||
* @param {string} [args.initial] Default string value | ||
* @param {string} [args.style="default"] Render style ('default', 'password', 'invisible') | ||
* @param {string} [args.separator] String separator | ||
* @param {function} [args.onState] On state change callback | ||
* @returns {Promise} Promise with user input, in form of an `Array` | ||
*/ | ||
function list({ message, initial, style, separator = ',', onState = noop }) { | ||
if (typeof message !== 'string') throw new Error('message is required'); | ||
return new Promise((resolve, reject) => { | ||
const p = new el.TextPrompt({ message, initial, style }); | ||
p.on('submit', str => resolve(str.split(separator).map(s => s.trim()))); | ||
p.on('abort', reject); | ||
p.on('state', onState); | ||
function list(args) { | ||
const sep = args.separator || ','; | ||
return toPrompt('TextPrompt', args, { | ||
onSubmit: str => str.split(sep).map(s => s.trim()) | ||
}); | ||
} | ||
|
||
/** | ||
* Toggle/switch prompt | ||
* @param {string} message Prompt message to display | ||
* @param {boolean} [initial=false] Default value | ||
* @param {string} [active="on"] Text for `active` state | ||
* @param {string} [inactive="off"] Text for `inactive` state | ||
* @param {function} [onState] On state change callback | ||
* @param {string} args.message Prompt message to display | ||
* @param {boolean} [args.initial=false] Default value | ||
* @param {string} [args.active="on"] Text for `active` state | ||
* @param {string} [args.inactive="off"] Text for `inactive` state | ||
* @param {function} [args.onState] On state change callback | ||
* @returns {Promise} Promise with user input | ||
*/ | ||
function toggle({ message, initial, active, inactive, onState = noop }) { | ||
if (typeof message !== 'string') throw new Error('message is required'); | ||
return new Promise((resolve, reject) => { | ||
const p = new el.TogglePrompt({ message, initial, active, inactive }); | ||
p.on('submit', resolve); | ||
p.on('abort', reject); | ||
p.on('state', onState); | ||
}); | ||
function toggle(args) { | ||
return toPrompt('TogglePrompt', args); | ||
} | ||
|
||
/** | ||
* Interactive select prompt | ||
* @param {string} message Prompt message to display | ||
* @param {Array} choices Array of choices objects `[{ title, value }, ...]` | ||
* @param {number} [initial] Index of default value | ||
* @param {function} [onState] On state change callback | ||
* @param {string} arr.message Prompt message to display | ||
* @param {Array} arr.choices Array of choices objects `[{ title, value }, ...]` | ||
* @param {number} [arr.initial] Index of default value | ||
* @param {function} [arr.onState] On state change callback | ||
* @returns {Promise} Promise with user input | ||
*/ | ||
function select({ message, choices, initial, onState = noop }) { | ||
if (typeof message !== 'string') throw new Error('message is required'); | ||
return new Promise((resolve, reject) => { | ||
const p = new el.SelectPrompt({ message, choices, initial }); | ||
p.on('submit', resolve); | ||
p.on('abort', reject); | ||
p.on('state', onState); | ||
}); | ||
function select(args) { | ||
return toPrompt('SelectPrompt', args); | ||
} | ||
|
||
/** | ||
* Interactive multi-select prompt | ||
* @param {string} message Prompt message to display | ||
* @param {Array} choices Array of choices objects `[{ title, value, [selected] }, ...]` | ||
* @param {number} [max] Max select | ||
* @param {string} [hint] Hint to display user | ||
* @param {function} [onState] On state change callback | ||
* @param {string} args.message Prompt message to display | ||
* @param {Array} args.choices Array of choices objects `[{ title, value, [selected] }, ...]` | ||
* @param {number} [args.max] Max select | ||
* @param {string} [args.hint] Hint to display user | ||
* @param {function} [args.onState] On state change callback | ||
* @returns {Promise} Promise with user input | ||
*/ | ||
function multiselect({ message, choices, max, hint, onState = noop }) { | ||
if (typeof message !== 'string') throw new Error('message is required'); | ||
if (!Array.isArray(choices)) throw new Error('choices array is required'); | ||
|
||
return new Promise((resolve, reject) => { | ||
const p = new el.MultiselectPrompt({ message, choices, max, hint }); | ||
const selected = items => items.filter(item => item.selected).map(item => item.value); | ||
p.on('submit', items => resolve(selected(items))); | ||
p.on('abort', items => reject(selected(items))); | ||
p.on('state', onState); | ||
function multiselect(args) { | ||
if (!Array.isArray(args.choices)) throw new Error('choices array is required'); | ||
const toSelected = items => items.filter(item => item.selected).map(item => item.value); | ||
return toPrompt('MultiselectPrompt', args, { | ||
onAbort: toSelected, | ||
onSubmit: toSelected | ||
}); | ||
} | ||
|
||
/** | ||
* Interactive multi-select prompt | ||
* @param {string} message Prompt message to display | ||
* @param {Array} choices Array of auto-complete choices objects `[{ title, value }, ...]` | ||
* @param {Function} [suggest] Function to filter results based on user input. Defaults to stort by `title` | ||
* @param {number} [limit=10] Max number of results to show | ||
* @param {string} [style="default"] Render style ('default', 'password', 'invisible') | ||
* @param {function} [onState] On state change callback | ||
* @param {string} args.message Prompt message to display | ||
* @param {Array} args.choices Array of auto-complete choices objects `[{ title, value }, ...]` | ||
* @param {Function} [args.suggest] Function to filter results based on user input. Defaults to sort by `title` | ||
* @param {number} [args.limit=10] Max number of results to show | ||
* @param {string} [args.style="default"] Render style ('default', 'password', 'invisible') | ||
* @param {function} [args.onState] On state change callback | ||
* @returns {Promise} Promise with user input | ||
*/ | ||
function autocomplete({ message, choices, suggest, limit, style, onState = noop }) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it is still destructed object, but not There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Old commit. Fixed in 6457a15 |
||
if (typeof message !== 'string') throw new Error('message is required'); | ||
if (!Array.isArray(choices)) throw new Error('choices array is required'); | ||
const suggestByTitle = (input, choices) => | ||
Promise.resolve( | ||
choices.filter( | ||
item => item.title.slice(0, input.length).toLowerCase() === input.toLowerCase() | ||
) | ||
); | ||
suggest = suggest ? suggest : suggestByTitle; | ||
return new Promise((resolve, reject) => { | ||
const p = new el.AutocompletePrompt({ message, choices, suggest, limit, style }); | ||
p.on('submit', resolve); | ||
p.on('abort', reject); | ||
p.on('state', onState); | ||
}); | ||
if (!Array.isArray(args.choices)) throw new Error('choices array is required'); | ||
args.suggest = args.suggest || byTitle; | ||
return toPrompt('AutocompletePrompt', args); | ||
} | ||
|
||
function byTitle(input, choices) { | ||
return Promise.resolve( | ||
choices.filter(item => item.title.slice(0, input.length).toLowerCase() === input.toLowerCase()) | ||
); | ||
} | ||
|
||
module.exports = { | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now all prompts also have individual
onAbort
andonSubmit
. That's coolThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Individual prompt functions, yeah. But not the user-defined prompt objects.