lib/cli/ui/prompt.rb in cli-ui-1.0.0 vs lib/cli/ui/prompt.rb in cli-ui-1.1.0
- old
+ new
@@ -3,16 +3,17 @@
module CLI
module UI
module Prompt
autoload :InteractiveOptions, 'cli/ui/prompt/interactive_options'
- private_constant :InteractiveOptions
+ autoload :OptionsHandler, 'cli/ui/prompt/options_handler'
+ private_constant :InteractiveOptions, :OptionsHandler
class << self
- # Ask a user a question with either free form answer or a set of answers
+ # Ask a user a question with either free form answer or a set of answers (multiple choice)
+ # Can use arrows, y/n, numbers (1/2), and vim bindings to control multiple choice selection
# Do not use this method for yes/no questions. Use +confirm+
- # Can use arrows, y/n, numbers, and vim bindings to control
#
# * Handles free form answers (options are nil)
# * Handles default answers for free form text
# * Handles file auto completion for file input
# * Handles interactively choosing answers using +InteractiveOptions+
@@ -23,19 +24,30 @@
#
# * +question+ - (required) The question to ask the user
#
# ==== Options
#
- # * +:options+ - Options to ask the user. Will use +InteractiveOptions+ to do so
+ # * +:options+ - Options that the user may select from. Will use +InteractiveOptions+ to do so.
# * +:default+ - The default answer to the question (e.g. they just press enter and don't input anything)
# * +:is_file+ - Tells the input to use file auto-completion (tab completion)
# * +:allow_empty+ - Allows the answer to be empty
#
# Note:
- # * +:options+ conflicts with +:default+ and +:is_file+, you cannot set options with either of these keywords
+ # * +:options+ or providing a +Block+ conflicts with +:default+ and +:is_file+, you cannot set options with either of these keywords
# * +:default+ conflicts with +:allow_empty:, you cannot set these together
+ # * +:options+ conflicts with providing a +Block+ , you may only set one
#
+ # ==== Block (optional)
+ #
+ # * A Proc that provides a +OptionsHandler+ and uses the public +:option+ method to add options and their
+ # respective handlers
+ #
+ # ==== Return Value
+ #
+ # * If a +Block+ was not provided, the selected option or response to the free form question will be returned
+ # * If a +Block+ was provided, the evaluted value of the +Block+ will be returned
+ #
# ==== Example Usage:
#
# Free form question
# CLI::UI::Prompt.ask('What color is the sky?')
#
@@ -46,39 +58,54 @@
# CLI::UI::Prompt.ask('What color is the sky?', default: 'blue')
#
# Free form question when the answer can be empty
# CLI::UI::Prompt.ask('What is your opinion on this question?', allow_empty: true)
#
- # Question with answers
+ # Interactive (multiple choice) question
# CLI::UI::Prompt.ask('What kind of project is this?', options: %w(rails go ruby python))
#
+ # Interactive (multiple choice) question with defined handlers
+ # CLI::UI::Prompt.ask('What kind of project is this?') do |handler|
+ # handler.option('rails') { |selection| selection }
+ # handler.option('go') { |selection| selection }
+ # handler.option('ruby') { |selection| selection }
+ # handler.option('python') { |selection| selection }
+ # end
#
- def ask(question, options: nil, default: nil, is_file: nil, allow_empty: true)
- if (default && !allow_empty) || (options && (default || is_file))
- raise(ArgumentError, 'conflicting arguments')
+ def ask(question, options: nil, default: nil, is_file: nil, allow_empty: true, &options_proc)
+ if ((options || block_given?) && (default || is_file))
+ raise(ArgumentError, 'conflicting arguments: options provided with default or is_file')
end
- if default
- puts_question("#{question} (empty = #{default})")
- elsif options
- puts_question("#{question} {{yellow:(choose with ↑ ↓ ⏎)}}")
+ if options || block_given?
+ ask_interactive(question, options, &options_proc)
else
- puts_question(question)
+ ask_free_form(question, default, is_file, allow_empty)
end
+ end
- # Present the user with options
- if options
- resp = InteractiveOptions.call(options)
+ # Asks the user a yes/no question.
+ # Can use arrows, y/n, numbers (1/2), and vim bindings to control
+ #
+ # ==== Example Usage:
+ #
+ # Confirmation question
+ # CLI::UI::Prompt.confirm('Is the sky blue?')
+ #
+ def confirm(question)
+ ask_interactive(question, %w(yes no)) == 'yes'
+ end
- # Clear the line, and reset the question to include the answer
- print(ANSI.previous_line + ANSI.end_of_line + ' ')
- print(ANSI.cursor_save)
- print(' ' * CLI::UI::Terminal.width)
- print(ANSI.cursor_restore)
- puts_question("#{question} (You chose: {{italic:#{resp}}})")
+ private
- return resp
+ def ask_free_form(question, default, is_file, allow_empty)
+ raise(ArgumentError, 'conflicting arguments: default enabled but allow_empty is false') if (default && !allow_empty)
+
+ if default
+ puts_question("#{question} (empty = #{default})")
+ else
+ puts_question(question)
end
# Ask a free form question
loop do
line = readline(is_file: is_file)
@@ -92,22 +119,37 @@
return line
end
end
end
- # Asks the user a yes/no question.
- # Can use arrows, y/n, numbers (1/2), and vim bindings to control
- #
- # ==== Example Usage:
- #
- # Confirmation question
- # CLI::UI::Prompt.confirm('Is the sky blue?')
- #
- def confirm(question)
- ask(question, options: %w(yes no)) == 'yes'
+ def ask_interactive(question, options = nil)
+ raise(ArgumentError, 'conflicting arguments: options and block given') if options && block_given?
+
+ options ||= if block_given?
+ handler = OptionsHandler.new
+ yield handler
+ handler.options
+ end
+
+ raise(ArgumentError, 'insufficient options') if options.nil? || options.size < 2
+ puts_question("#{question} {{yellow:(choose with ↑ ↓ ⏎)}}")
+ resp = interactive_prompt(options)
+
+ # Clear the line, and reset the question to include the answer
+ print(ANSI.previous_line + ANSI.end_of_line + ' ')
+ print(ANSI.cursor_save)
+ print(' ' * CLI::UI::Terminal.width)
+ print(ANSI.cursor_restore)
+ puts_question("#{question} (You chose: {{italic:#{resp}}})")
+
+ return handler.call(resp) if block_given?
+ resp
end
- private
+ # Useful for stubbing in tests
+ def interactive_prompt(options)
+ InteractiveOptions.call(options)
+ end
def write_default_over_empty_input(default)
CLI::UI.raw do
STDERR.puts(
CLI::UI::ANSI.cursor_up(1) +