class Infopark::UserIO
TODO
-
beep (a) on
acknowledge
,ask
orconfirm
(and maybe onlisten
, too)
Constants
- VERSION
Attributes
global[RW]
Public Class Methods
new(output_prefix: nil)
click to toggle source
# File lib/infopark/user_io.rb, line 63 def initialize(output_prefix: nil) case output_prefix when String @output_prefix = "[#{output_prefix}] " when Proc, Method @output_prefix_proc = ->() { "[#{output_prefix.call}] " } when :timestamp @output_prefix_proc = ->() { "[#{Time.now.strftime("%T.%L")}] " } end @line_pending = {} end
Public Instance Methods
<<(msg)
click to toggle source
# File lib/infopark/user_io.rb, line 172 def <<(msg) tell(msg.chomp, newline: msg.end_with?("\n")) end
acknowledge(*text)
click to toggle source
# File lib/infopark/user_io.rb, line 110 def acknowledge(*text) tell("-" * 80) tell(*text, color: :cyan, bright: true) tell("-" * 80) tell("Please press ENTER to continue.") read_line end
ask(*text, default: nil, expected: "yes")
click to toggle source
# File lib/infopark/user_io.rb, line 118 def ask(*text, default: nil, expected: "yes") # TODO implementation error if default not boolean or nil # TODO implementation error if expected not "yes" or "no" tell("-" * 80) tell(*text, color: :cyan, bright: true) tell("-" * 80) default_answer = default ? "yes" : "no" unless default.nil? tell("(yes/no) #{default_answer && "[#{default_answer}] "}> ", newline: false) until %w(yes no).include?(answer = read_line.strip.downcase) if answer.empty? answer = default_answer break end tell("I couldn't understand “#{answer}”.", newline: false, color: :red, bright: true) tell(" > ", newline: false) end answer == expected end
background_other_threads()
click to toggle source
# File lib/infopark/user_io.rb, line 155 def background_other_threads unless @foreground_thread @background_data = [] @foreground_thread = Thread.current end end
confirm(*text)
click to toggle source
# File lib/infopark/user_io.rb, line 143 def confirm(*text) ask(*text) or raise Aborted end
edit_file(kind_of_data, filename = nil, template: nil)
click to toggle source
# File lib/infopark/user_io.rb, line 180 def edit_file(kind_of_data, filename = nil, template: nil) wait_for_foreground if background? editor = ENV['EDITOR'] or raise MissingEnv, "No EDITOR specified." filename ||= Tempfile.new("").path if template && (!File.exists?(filename) || File.empty?(filename)) File.write(filename, template) end tell("Start editing #{kind_of_data} using #{editor}…") sleep(1.7) system(editor, filename) File.read(filename) end
foreground()
click to toggle source
# File lib/infopark/user_io.rb, line 162 def foreground if @foreground_thread @background_data.each(&STDOUT.method(:write)) @foreground_thread = nil # take over line_pending from background @line_pending[false] = @line_pending[true] @line_pending[true] = false end end
listen(prompt = nil, **options)
click to toggle source
# File lib/infopark/user_io.rb, line 137 def listen(prompt = nil, **options) prompt << " " if prompt tell("#{prompt}> ", **options, newline: false) read_line.strip end
new_progress(label)
click to toggle source
# File lib/infopark/user_io.rb, line 147 def new_progress(label) Progress.new(label, self) end
select(description, items, item_describer: :to_s, default: nil)
click to toggle source
# File lib/infopark/user_io.rb, line 197 def select(description, items, item_describer: :to_s, default: nil) return if items.empty? describer = case item_describer when Method, Proc item_describer else ->(item) { item.send(item_describer) } end choice = nil if items.size == 1 choice = items.first tell("Selected #{describer.call(choice)}.", color: :yellow) return choice end items = items.sort_by(&describer) tell("-" * 80) tell("Please select #{description}:", color: :cyan, bright: true) items.each_with_index do |item, i| tell("#{i + 1}: #{describer.call(item)}", color: :cyan, bright: true) end tell("-" * 80) default_index = items.index(default) default_selection = "[#{default_index + 1}] " if default_index until choice tell("Your choice #{default_selection}> ", newline: false) answer = read_line.strip if answer.empty? choice = default else int_answer = answer.to_i if int_answer.to_s != answer tell("Please enter a valid integer.") elsif int_answer < 1 || int_answer > items.size tell("Please enter a number from 1 through #{items.size}.") else choice = items[int_answer - 1] end end end choice end
start_progress(label)
click to toggle source
# File lib/infopark/user_io.rb, line 151 def start_progress(label) new_progress(label).tap(&:start) end
tell(*texts, newline: true, **line_options)
click to toggle source
# File lib/infopark/user_io.rb, line 75 def tell(*texts, newline: true, **line_options) lines = texts.flatten.map {|text| text.to_s.split("\n", -1) }.flatten lines[0...-1].each {|line| tell_line(line, **line_options) } tell_line(lines.last, newline: newline, **line_options) end
tell_error(e, **options)
click to toggle source
# File lib/infopark/user_io.rb, line 105 def tell_error(e, **options) tell(e, **options, color: :red, bright: true) tell(e.backtrace, **options, color: :red) if Exception === e end
tell_pty_stream(stream, **color_options)
click to toggle source
# File lib/infopark/user_io.rb, line 82 def tell_pty_stream(stream, **color_options) color_prefix, color_postfix = compute_color(**color_options) write_raw(output_prefix) unless line_pending? write_raw(color_prefix) nl_pending = false uncolored_prefix = "#{color_postfix}#{output_prefix}#{color_prefix}" until stream.eof? chunk = stream.read_nonblock(100) next if chunk.empty? write_raw("\n#{uncolored_prefix}") if nl_pending chunk.chop! if nl_pending = chunk.end_with?("\n") chunk.gsub!(/([\r\n])/, "\\1#{uncolored_prefix}") write_raw(chunk) end write_raw("\n") if nl_pending write_raw(color_postfix) line_pending!(false) end
tty?()
click to toggle source
# File lib/infopark/user_io.rb, line 176 def tty? STDOUT.tty? end
warn(*text)
click to toggle source
# File lib/infopark/user_io.rb, line 101 def warn(*text) tell(*text, color: :yellow, bright: true) end
Private Instance Methods
background?()
click to toggle source
# File lib/infopark/user_io.rb, line 246 def background? !!@foreground_thread && @foreground_thread != Thread.current end
compute_color(**options)
click to toggle source
# File lib/infopark/user_io.rb, line 290 def compute_color(**options) if tty? if prefix = text_color(**options) # TODO matching annihilators for options postfix = text_color(color: :none, bright: false) end end [prefix, postfix] end
control_sequence(*parameters, function)
click to toggle source
# File lib/infopark/user_io.rb, line 300 def control_sequence(*parameters, function) "\033[#{parameters.join(";")}#{function}" end
line_pending!(value)
click to toggle source
# File lib/infopark/user_io.rb, line 286 def line_pending!(value) @line_pending[background?] = value end
line_pending?()
click to toggle source
# File lib/infopark/user_io.rb, line 282 def line_pending? @line_pending[background?] end
output_prefix()
click to toggle source
# File lib/infopark/user_io.rb, line 254 def output_prefix @output_prefix || @output_prefix_proc && @output_prefix_proc.call end
read_line()
click to toggle source
# File lib/infopark/user_io.rb, line 258 def read_line wait_for_foreground if background? @line_pending[false] = false STDIN.gets.chomp end
sgr_sequence(*parameters)
click to toggle source
SGR: Select Graphic Rendition … far too long for a function name ;)
# File lib/infopark/user_io.rb, line 305 def sgr_sequence(*parameters) control_sequence(*parameters, :m) end
tell_line(line, newline: true, prefix: true, **color_options)
click to toggle source
# File lib/infopark/user_io.rb, line 264 def tell_line(line, newline: true, prefix: true, **color_options) line_prefix, line_postfix = compute_color(**color_options) prefix = false if line_pending? out_line = "#{output_prefix if prefix}#{line_prefix}#{line}#{line_postfix}#{"\n" if newline}" write_raw(out_line) line_pending!(!newline) end
text_color(color: nil, bright: nil, faint: nil, italic: nil, underline: nil)
click to toggle source
# File lib/infopark/user_io.rb, line 309 def text_color(color: nil, bright: nil, faint: nil, italic: nil, underline: nil) return if color.nil? && bright.nil? sequence = [] unless bright.nil? && faint.nil? sequence << (bright ? 1 : (faint ? 2 : 22)) end unless italic.nil? sequence << (italic ? 3 : 23) end unless underline.nil? sequence << (underline ? 4 : 24) end case color when :red sequence << 31 when :green sequence << 32 when :yellow sequence << 33 when :blue sequence << 34 when :purple sequence << 35 when :cyan sequence << 36 when :white sequence << 37 when :none sequence << 39 end sgr_sequence(*sequence) end
wait_for_foreground()
click to toggle source
# File lib/infopark/user_io.rb, line 250 def wait_for_foreground sleep 0.1 while background? end
write_raw(bytes)
click to toggle source
# File lib/infopark/user_io.rb, line 274 def write_raw(bytes) if background? @background_data << bytes else STDOUT.write(bytes) end end