module Interactive
Copyright © 2012 Alex Suraci
Constants
- ESCAPES
- EVENTS
Public Instance Methods
Ask a question and get an answer.
See Interact#read_line for the other possible values in
options
.
- question
-
The prompt, without “: ” at the end.
- options
-
An optional hash containing the following options.
- default
-
The default value, also used to attempt type conversion of the answer (e.g. numeric/boolean).
- choices
-
An array (or
Enumerable
) of strings to choose from. - indexed
-
Use alternative choice listing, and allow choosing by number. Good for when there are many choices or choices with long names.
# File lib/interact/interactive.rb, line 172 def ask(question, options = {}) choices = options[:choices] && options[:choices].to_a list_choices(choices, options) if choices while true prompt(question, options) ok, res = answered(read_line(options), options) return res if ok end end
Read a single character.
- options
-
An optional hash containing the following options.
- input
-
The input source (defaults to
$stdin
).
# File lib/interact/interactive.rb, line 109 def read_char(options = {}) input = options[:input] || $stdin with_char_io(input) do get_character(input) end end
Read a single event.
- options
-
An optional hash containing the following options.
- input
-
The input source (defaults to
$stdin
).
# File lib/interact/interactive.rb, line 123 def read_event(options = {}) input = options[:input] || $stdin with_char_io(input) do get_event(input) end end
Read a line of input.
- options
-
An optional hash containing the following options.
- input
-
The input source (defaults to
$stdin
). - echo
-
A string to echo when showing the input; used for things like hiding password input.
# File lib/interact/interactive.rb, line 141 def read_line(options = {}) input = options[:input] || $stdin state = input_state(options) with_char_io(input) do until state.done? handler(get_event(input), state) end end state.answer end
Private Instance Methods
# File lib/interact/interactive.rb, line 240 def answered(ans, options) print "\n" if ans.empty? if options.key?(:default) [true, options[:default]] end elsif choices = options[:choices] matches = [] choices.each do |x| completion = choice_completion(x, options) if completion == ans return [true, x] elsif completion.start_with? ans matches << x end end if choices and ans =~ /^\s*\d+\s*$/ and ans.to_i - 1 >= 0 and res = choices.to_a[ans.to_i - 1] [true, res] elsif matches.size == 1 [true, matches.first] elsif matches.size > 1 matches_list = matches.collect { |m| show_choice(m, options) }.join " or " puts "Please disambiguate: #{matches_list}?" [false, nil] elsif options[:allow_other] [true, ans] else puts "Unknown answer, please try again!" [false, nil] end else [true, match_type(ans, options[:default])] end end
# File lib/interact/interactive.rb, line 297 def choice_completion(choice, options = {}) complete = options[:complete] || options[:display] || proc(&:to_s) complete.call(choice) end
# File lib/interact/interactive.rb, line 481 def chr(x) case x when nil nil when String x else x.chr end end
# File lib/interact/interactive.rb, line 186 def clear_input(state) state.goto(0) state.clear(state.answer.size) state.answer = "" end
# File lib/interact/interactive.rb, line 302 def common_prefix(*strs) return strs.first.dup if strs.size == 1 longest = strs.sort_by(&:size).last longest.size.times do |i| sub = longest[0..(-1 - i)] if strs.all? { |s| s.start_with?(sub) } return sub end end "" end
# File lib/interact/interactive.rb, line 506 def get_character(input) if input == STDIN begin chr(Win32API.new("msvcrt", "_getch", [], "L").call) rescue chr(Win32API.new("crtdll", "_getch", [], "L").call) end else chr(input.getc) end end
# File lib/interact/interactive.rb, line 209 def get_event(input) escaped = false escape_seq = "" while true c = get_character(input) if not c return :eof elsif c == "\e" || c == "\xE0" escaped = true elsif escaped escape_seq << c if cmd = ESCAPES[escape_seq] return cmd elsif ESCAPES.select { |k, v| k.start_with? escape_seq }.empty? escaped, escape_seq = false, "" end elsif EVENTS.key? c return EVENTS[c] elsif c < " " # ignore else return [:key, c] end end end
# File lib/interact/interactive.rb, line 316 def handler(which, state) ans = state.answer pos = state.position case which when :up # nothing when :down # nothing when :tab matches = if choices = state.options[:choices] choices.collect { |c| choice_completion(c, state.options) }.select { |c| c.start_with? ans } else matching_paths(ans) end if matches.empty? print("\a") # bell else old = ans ans = state.answer = common_prefix(*matches) state.display(ans[pos .. -1]) print("\a") if ans == old end when :right unless pos == ans.size state.display(ans[pos .. pos]) end when :left unless pos == 0 state.back(1) end when :delete unless pos == ans.size ans.slice!(pos, 1) rest = ans[pos .. -1] state.display(rest) state.clear(1) state.back(rest.size) end when :home state.goto(0) when :end state.goto(ans.size) when :backspace if pos > 0 rest = ans[pos .. -1] ans.slice!(pos - 1, 1) state.back(1) state.display(rest) state.clear(1) state.back(rest.size) end when :interrupt raise Interrupt.new when :eof state.done! if ans.empty? when :kill_word if pos > 0 start = /[[:alnum:]]*\s*[^[:alnum:]]?$/ =~ ans[0 .. (pos - 1)] if pos < ans.size to_end = ans.size - pos rest = ans[pos .. -1] state.clear(to_end) end length = pos - start ans.slice!(start, length) state.back(length) state.clear(length) if to_end state.display(rest) state.back(to_end) end end when :enter state.done! when Array case which[0] when :key c = which[1] rest = ans[pos .. -1] ans.insert(pos, c) state.display(c + rest) state.back(rest.size) end else return false end true end
# File lib/interact/interactive.rb, line 205 def input_state(options) InputState.new(options) end
# File lib/interact/interactive.rb, line 284 def list_choices(choices, options = {}) return unless options[:indexed] choices.each_with_index do |o, i| puts "#{i + 1}: #{show_choice(o, options)}" end end
# File lib/interact/interactive.rb, line 463 def match_type(str, x) case x when Integer str.to_i when true, false str.upcase.start_with? "Y" else str end end
# File lib/interact/interactive.rb, line 435 def matching_paths(input) home = File.expand_path("~") Dir.glob(input.sub("~", home) + "*").collect do |p| p.sub(home, "~") end end
# File lib/interact/interactive.rb, line 443 def prompt(question, options = {}) print question if (choices = options[:choices]) && !options[:indexed] print " (#{choices.collect(&:to_s).join ", "})" end case options[:default] when true print " [Yn]" when false print " [yN]" when nil else print " [#{options[:default]}]" end print ": " end
# File lib/interact/interactive.rb, line 198 def redraw_input(state) pos = state.position state.goto(0) state.display(state.answer) state.goto(pos) end
# File lib/interact/interactive.rb, line 502 def restore_input_state(input, state) nil end
# File lib/interact/interactive.rb, line 192 def set_input(state, input) clear_input(state) state.display(input) state.answer = input end
# File lib/interact/interactive.rb, line 498 def set_input_state(input) nil end
# File lib/interact/interactive.rb, line 292 def show_choice(choice, options = {}) display = options[:display] || proc(&:to_s) display.call(choice) end
# File lib/interact/interactive.rb, line 474 def with_char_io(input) before = set_input_state(input) yield ensure restore_input_state(input, before) end