module Dev::UI::Prompt
Public Class Methods
Ask a user a question with either free form answer or a set of answers 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
Attributes¶ ↑
-
question
- (required) The question to ask the user
Options¶ ↑
-
:options
- Options to ask the user. Will useInteractiveOptions
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 -
:default
conflicts with +:allow_empty:, you cannot set these together
Example Usage:¶ ↑
Free form question
Dev::UI::Prompt.ask('What color is the sky?')
Free form question with a file answer
Dev::UI::Prompt.ask('Where is your Gemfile located?', is_file: true)
Free form question with a default answer
Dev::UI::Prompt.ask('What color is the sky?', default: 'blue')
Free form question when the answer can be empty
Dev::UI::Prompt.ask('What is your opinion on this question?', allow_empty: true)
Question with answers
Dev::UI::Prompt.ask('What kind of project is this?', options: %w(rails go ruby python))
# File lib/dev/ui/prompt.rb, line 55 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') end if default puts_question("#{question} (empty = #{default})") elsif options puts_question("#{question} {{yellow:(choose with ↑ ↓ ⏎)}}") else puts_question(question) end # Present the user with options if options resp = InteractiveOptions.call(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(' ' * Dev::UI::Terminal.width) print(ANSI.cursor_restore) puts_question("#{question} (You chose: {{italic:#{resp}}})") return resp end # Ask a free form question loop do line = readline(is_file: is_file) if line.empty? && default write_default_over_empty_input(default) return default end if !line.empty? || allow_empty return line end end end
Private Class Methods
# File lib/dev/ui/prompt.rb, line 123 def puts_question(str) Dev::UI.with_frame_color(:blue) do STDOUT.puts(Dev::UI.fmt('{{?}} ' + str)) end end
# File lib/dev/ui/prompt.rb, line 129 def readline(is_file: false) if is_file Readline.completion_proc = Readline::FILENAME_COMPLETION_PROC Readline.completion_append_character = "" else Readline.completion_proc = proc { |*| nil } Readline.completion_append_character = " " end # because Readline is a C library, Dev::UI's hooks into $stdout don't # work. We could work around this by having Dev::UI use a pipe and a # thread to manage output, but the current strategy feels like a # better tradeoff. prefix = Dev::UI.with_frame_color(:blue) { Dev::UI::Frame.prefix } prompt = prefix + Dev::UI.fmt('{{blue:> }}{{yellow:') begin line = Readline.readline(prompt, true) line.to_s.chomp rescue Interrupt Dev::UI.raw { STDERR.puts('^C' + Dev::UI::Color::RESET.code) } raise end end
# File lib/dev/ui/prompt.rb, line 111 def write_default_over_empty_input(default) Dev::UI.raw do STDERR.puts( Dev::UI::ANSI.cursor_up(1) + "\r" + Dev::UI::ANSI.cursor_forward(4) + # TODO: width default + Dev::UI::Color::RESET.code ) end end