Class: Infopark::UserIO

Inherits:
Object
  • Object
show all
Defined in:
lib/infopark/user_io.rb,
lib/infopark/user_io/version.rb

Overview

TODO

  • beep (a) on #acknowledge, #ask or #confirm (and maybe on #listen, too)

Defined Under Namespace

Modules: Global Classes: Aborted, MissingEnv, Progress

Constant Summary collapse

VERSION =
"1.1.0"

Class Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(output_prefix: nil) ⇒ UserIO

Returns a new instance of UserIO.



63
64
65
66
67
68
69
70
71
72
73
# 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

Class Attribute Details

.globalObject

Returns the value of attribute global.



60
61
62
# File 'lib/infopark/user_io.rb', line 60

def global
  @global
end

Instance Method Details

#<<(msg) ⇒ Object



172
173
174
# File 'lib/infopark/user_io.rb', line 172

def <<(msg)
  tell(msg.chomp, newline: msg.end_with?("\n"))
end

#acknowledge(*text) ⇒ Object



110
111
112
113
114
115
116
# 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") ⇒ Object



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# 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_threadsObject



155
156
157
158
159
160
# 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) ⇒ Object



143
144
145
# File 'lib/infopark/user_io.rb', line 143

def confirm(*text)
  ask(*text) or raise Aborted
end

#edit_file(kind_of_data, filename = nil) ⇒ Object



180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/infopark/user_io.rb', line 180

def edit_file(kind_of_data, filename = nil)
  wait_for_foreground if background?

  editor = ENV['EDITOR'] or raise MissingEnv, "No EDITOR specified."

  filename ||= Tempfile.new("").path
  tell("Start editing #{kind_of_data} using #{editor}")
  sleep(1.7)
  system(editor, filename)

  File.read(filename)
end

#foregroundObject



162
163
164
165
166
167
168
169
170
# 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) ⇒ Object



137
138
139
140
141
# 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) ⇒ Object



147
148
149
# 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) ⇒ Object



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/infopark/user_io.rb', line 193

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) ⇒ Object



151
152
153
# File 'lib/infopark/user_io.rb', line 151

def start_progress(label)
  new_progress(label).tap(&:start)
end

#tell(*texts, newline: true, **line_options) ⇒ Object



75
76
77
78
79
80
# 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) ⇒ Object



105
106
107
108
# 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) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# 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?Boolean

Returns:

  • (Boolean)


176
177
178
# File 'lib/infopark/user_io.rb', line 176

def tty?
  STDOUT.tty?
end

#warn(*text) ⇒ Object



101
102
103
# File 'lib/infopark/user_io.rb', line 101

def warn(*text)
  tell(*text, color: :yellow, bright: true)
end