infopark-user_io 1.3.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/infopark/user_io/global.rb +1 -1
- data/lib/infopark/user_io/version.rb +1 -1
- data/lib/infopark/user_io.rb +112 -111
- data/spec/user_io_spec.rb +175 -28
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b20bdfdfb2bd29180da2445be83e82295a71b4a30276450351c08ef114ed9da5
|
4
|
+
data.tar.gz: cfe242fc1e3a48b1c0e2b7d0ab40d8663178b6425f9e8b4215c13a67bef85b29
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4d75731717e150e66512d1aecab2193938aa159cb276eaa9cae1abbae05753d88559e01b57216f210dfca8f3163a5cb5bee699593e1d5f740e51e7e95c6c3037
|
7
|
+
data.tar.gz: cb512e8b47045e191bcda99ce905b0c5e8a8dffefbd9f489ee0b543841bd75cef8e473e31caa9eddb5caebd97a67c7b5b85a9e91b1ef032894fa443e1ecd9bc2
|
data/lib/infopark/user_io.rb
CHANGED
@@ -76,88 +76,39 @@ module Infopark
|
|
76
76
|
@line_pending = {}
|
77
77
|
end
|
78
78
|
|
79
|
-
def
|
80
|
-
|
81
|
-
|
82
|
-
lines[0...-1].each {|line| tell_line(line, **line_options) }
|
83
|
-
tell_line(lines.last, newline:, **line_options)
|
84
|
-
end
|
85
|
-
|
86
|
-
def tell_pty_stream(stream, **color_options)
|
87
|
-
color_prefix, color_postfix = compute_color(**color_options)
|
88
|
-
write_raw(output_prefix) unless line_pending?
|
89
|
-
write_raw(color_prefix)
|
90
|
-
nl_pending = false
|
91
|
-
uncolored_prefix = "#{color_postfix}#{output_prefix}#{color_prefix}"
|
92
|
-
until stream.eof?
|
93
|
-
chunk = stream.read_nonblock(100)
|
94
|
-
next if chunk.empty?
|
95
|
-
|
96
|
-
write_raw("\n#{uncolored_prefix}") if nl_pending
|
97
|
-
chunk.chop! if (nl_pending = chunk.end_with?("\n"))
|
98
|
-
chunk.gsub!(/([\r\n])/, "\\1#{uncolored_prefix}")
|
99
|
-
write_raw(chunk)
|
100
|
-
end
|
101
|
-
write_raw("\n") if nl_pending
|
102
|
-
write_raw(color_postfix)
|
103
|
-
line_pending!(false)
|
104
|
-
end
|
105
|
-
|
106
|
-
def warn(*text)
|
107
|
-
tell(*text, color: :yellow, bright: true)
|
108
|
-
end
|
109
|
-
|
110
|
-
def tell_error(e, **options)
|
111
|
-
tell(e, **options, color: :red, bright: true)
|
112
|
-
tell(e.backtrace, **options, color: :red) if Exception === e
|
79
|
+
def <<(msg)
|
80
|
+
tell(msg.chomp, newline: msg.end_with?("\n"))
|
113
81
|
end
|
114
82
|
|
115
|
-
def acknowledge(*
|
116
|
-
tell("-" * 80)
|
117
|
-
tell(*
|
118
|
-
tell("-" * 80)
|
119
|
-
tell("Please press ENTER to continue.")
|
83
|
+
def acknowledge(*texts, **options)
|
84
|
+
tell("-" * 80, **options)
|
85
|
+
tell(*texts, **options, color: :cyan, bright: true)
|
86
|
+
tell("-" * 80, **options)
|
87
|
+
tell("Please press ENTER to continue.", **options)
|
120
88
|
read_line
|
89
|
+
nil
|
121
90
|
end
|
122
91
|
|
123
|
-
def ask(*
|
92
|
+
def ask(*texts, default: nil, expected: "yes", **tell_options)
|
124
93
|
# TODO
|
125
94
|
# - implementation error if default not boolean or nil
|
126
95
|
# - implementation error if expected not "yes" or "no"
|
127
|
-
tell("-" * 80)
|
128
|
-
tell(*
|
129
|
-
tell("-" * 80)
|
96
|
+
tell("-" * 80, **tell_options)
|
97
|
+
tell(*texts, **tell_options, color: :cyan, bright: true)
|
98
|
+
tell("-" * 80, **tell_options)
|
130
99
|
default_answer = default ? "yes" : "no" unless default.nil?
|
131
|
-
tell("(yes/no) #{default_answer && "[#{default_answer}] "}> ", newline: false)
|
132
|
-
until %w(yes no).include?(answer = read_line.strip.downcase)
|
100
|
+
tell("(yes/no) #{default_answer && "[#{default_answer}] "}> ", **tell_options, newline: false)
|
101
|
+
until %w(yes no).include?((answer = read_line.strip.downcase))
|
133
102
|
if answer.empty?
|
134
103
|
answer = default_answer
|
135
104
|
break
|
136
105
|
end
|
137
|
-
tell("I couldn't understand “#{answer}”.", newline: false, color: :red, bright: true)
|
138
|
-
tell(" > ", newline: false)
|
106
|
+
tell("I couldn't understand “#{answer}”.", **tell_options, newline: false, color: :red, bright: true)
|
107
|
+
tell(" > ", **tell_options, newline: false)
|
139
108
|
end
|
140
109
|
answer == expected
|
141
110
|
end
|
142
111
|
|
143
|
-
def listen(prompt = nil, **options)
|
144
|
-
prompt << " " if prompt
|
145
|
-
tell("#{prompt}> ", **options, newline: false)
|
146
|
-
read_line.strip
|
147
|
-
end
|
148
|
-
|
149
|
-
def confirm(*text)
|
150
|
-
ask(*text) or raise(Aborted)
|
151
|
-
end
|
152
|
-
|
153
|
-
def new_progress(label)
|
154
|
-
Progress.new(label, self)
|
155
|
-
end
|
156
|
-
|
157
|
-
def start_progress(label)
|
158
|
-
new_progress(label).tap(&:start)
|
159
|
-
end
|
160
|
-
|
161
112
|
def background_other_threads
|
162
113
|
return if @foreground_thread
|
163
114
|
|
@@ -165,22 +116,8 @@ module Infopark
|
|
165
116
|
@foreground_thread = Thread.current
|
166
117
|
end
|
167
118
|
|
168
|
-
def
|
169
|
-
|
170
|
-
|
171
|
-
@background_data.each(&$stdout.method(:write))
|
172
|
-
@foreground_thread = nil
|
173
|
-
# take over line_pending from background
|
174
|
-
@line_pending[false] = @line_pending[true]
|
175
|
-
@line_pending[true] = false
|
176
|
-
end
|
177
|
-
|
178
|
-
def <<(msg)
|
179
|
-
tell(msg.chomp, newline: msg.end_with?("\n"))
|
180
|
-
end
|
181
|
-
|
182
|
-
def tty?
|
183
|
-
$stdout.tty?
|
119
|
+
def confirm(*text, **options)
|
120
|
+
ask(*text, **options) or raise(Aborted)
|
184
121
|
end
|
185
122
|
|
186
123
|
def edit_file(kind_of_data, filename = nil, template: nil)
|
@@ -200,6 +137,26 @@ module Infopark
|
|
200
137
|
File.read(filename)
|
201
138
|
end
|
202
139
|
|
140
|
+
def foreground
|
141
|
+
return unless @foreground_thread
|
142
|
+
|
143
|
+
@background_data.each(&$stdout.method(:write))
|
144
|
+
@foreground_thread = nil
|
145
|
+
# take over line_pending from background
|
146
|
+
@line_pending[false] = @line_pending[true]
|
147
|
+
@line_pending[true] = false
|
148
|
+
end
|
149
|
+
|
150
|
+
def listen(prompt = nil, **options)
|
151
|
+
prompt << " " if prompt
|
152
|
+
tell("#{prompt}> ", **options, newline: false)
|
153
|
+
read_line.strip
|
154
|
+
end
|
155
|
+
|
156
|
+
def new_progress(label)
|
157
|
+
Progress.new(label, self)
|
158
|
+
end
|
159
|
+
|
203
160
|
def select(description, items, item_describer: :to_s, default: nil)
|
204
161
|
return if items.empty?
|
205
162
|
|
@@ -247,50 +204,54 @@ module Infopark
|
|
247
204
|
choice
|
248
205
|
end
|
249
206
|
|
250
|
-
|
251
|
-
|
252
|
-
def background?
|
253
|
-
!!@foreground_thread && @foreground_thread != Thread.current
|
207
|
+
def start_progress(label)
|
208
|
+
new_progress(label).tap(&:start)
|
254
209
|
end
|
255
210
|
|
256
|
-
def
|
257
|
-
|
258
|
-
end
|
211
|
+
def tell(*texts, newline: true, **line_options)
|
212
|
+
lines = texts.flatten.map {|text| text.to_s.split("\n", -1) }.flatten
|
259
213
|
|
260
|
-
|
261
|
-
|
214
|
+
lines[0...-1].each {|line| tell_line(line, **line_options) }
|
215
|
+
tell_line(lines.last, newline:, **line_options)
|
262
216
|
end
|
263
217
|
|
264
|
-
def
|
265
|
-
|
266
|
-
|
267
|
-
$stdin.gets.chomp
|
218
|
+
def tell_error(e, **options)
|
219
|
+
tell(e, **options, color: :red, bright: true)
|
220
|
+
tell(e.backtrace, **options, color: :red) if Exception === e
|
268
221
|
end
|
269
222
|
|
270
|
-
def
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
223
|
+
def tell_pty_stream(stream, **color_options)
|
224
|
+
color_prefix, color_postfix = compute_color(**color_options)
|
225
|
+
write_raw(output_prefix) unless line_pending?
|
226
|
+
write_raw(color_prefix)
|
227
|
+
nl_pending = false
|
228
|
+
uncolored_prefix = "#{color_postfix}#{output_prefix}#{color_prefix}"
|
229
|
+
until stream.eof?
|
230
|
+
chunk = stream.read_nonblock(100)
|
231
|
+
next if chunk.empty?
|
276
232
|
|
277
|
-
|
233
|
+
write_raw("\n#{uncolored_prefix}") if nl_pending
|
234
|
+
chunk.chop! if (nl_pending = chunk.end_with?("\n"))
|
235
|
+
chunk.gsub!(/([\r\n])/, "\\1#{uncolored_prefix}")
|
236
|
+
write_raw(chunk)
|
237
|
+
end
|
238
|
+
write_raw("\n") if nl_pending
|
239
|
+
write_raw(color_postfix)
|
240
|
+
line_pending!(false)
|
278
241
|
end
|
279
242
|
|
280
|
-
def
|
281
|
-
|
282
|
-
@background_data << bytes
|
283
|
-
else
|
284
|
-
$stdout.write(bytes)
|
285
|
-
end
|
243
|
+
def tty?
|
244
|
+
$stdout.tty?
|
286
245
|
end
|
287
246
|
|
288
|
-
def
|
289
|
-
|
247
|
+
def warn(*texts, **options)
|
248
|
+
tell(*texts, **options, color: :yellow, bright: true)
|
290
249
|
end
|
291
250
|
|
292
|
-
|
293
|
-
|
251
|
+
private
|
252
|
+
|
253
|
+
def background?
|
254
|
+
!!@foreground_thread && @foreground_thread != Thread.current
|
294
255
|
end
|
295
256
|
|
296
257
|
def compute_color(**options)
|
@@ -305,11 +266,39 @@ module Infopark
|
|
305
266
|
"\033[#{parameters.join(';')}#{function}"
|
306
267
|
end
|
307
268
|
|
269
|
+
def line_pending?
|
270
|
+
@line_pending[background?]
|
271
|
+
end
|
272
|
+
|
273
|
+
def line_pending!(value)
|
274
|
+
@line_pending[background?] = value
|
275
|
+
end
|
276
|
+
|
277
|
+
def output_prefix
|
278
|
+
@output_prefix || @output_prefix_proc&.call
|
279
|
+
end
|
280
|
+
|
281
|
+
def read_line
|
282
|
+
wait_for_foreground if background?
|
283
|
+
@line_pending[false] = false
|
284
|
+
$stdin.gets.chomp
|
285
|
+
end
|
286
|
+
|
308
287
|
# SGR: Select Graphic Rendition … far too long for a function name ;)
|
309
288
|
def sgr_sequence(*parameters)
|
310
289
|
control_sequence(*parameters, :m)
|
311
290
|
end
|
312
291
|
|
292
|
+
def tell_line(line, newline: true, prefix: true, **color_options)
|
293
|
+
line_prefix, line_postfix = compute_color(**color_options)
|
294
|
+
prefix = false if line_pending?
|
295
|
+
|
296
|
+
out_line = "#{output_prefix if prefix}#{line_prefix}#{line}#{line_postfix}#{"\n" if newline}"
|
297
|
+
write_raw(out_line)
|
298
|
+
|
299
|
+
line_pending!(!newline)
|
300
|
+
end
|
301
|
+
|
313
302
|
def text_color(color: nil, bright: nil, faint: nil, italic: nil, underline: nil)
|
314
303
|
return if color.nil? && bright.nil?
|
315
304
|
|
@@ -348,6 +337,18 @@ module Infopark
|
|
348
337
|
end
|
349
338
|
sgr_sequence(*sequence)
|
350
339
|
end
|
340
|
+
|
341
|
+
def wait_for_foreground
|
342
|
+
sleep(0.1) while background?
|
343
|
+
end
|
344
|
+
|
345
|
+
def write_raw(bytes)
|
346
|
+
if background?
|
347
|
+
@background_data << bytes
|
348
|
+
else
|
349
|
+
$stdout.write(bytes)
|
350
|
+
end
|
351
|
+
end
|
351
352
|
end
|
352
353
|
end
|
353
354
|
|
data/spec/user_io_spec.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "securerandom"
|
4
|
+
|
3
5
|
module Infopark
|
4
6
|
RSpec.describe(UserIO) do
|
5
7
|
let(:options) { {} }
|
@@ -28,19 +30,35 @@ module Infopark
|
|
28
30
|
end
|
29
31
|
|
30
32
|
describe "#acknowledge" do
|
31
|
-
|
33
|
+
subject(:acknowledge) { user_io.acknowledge(*ack_texts, **ack_options) }
|
32
34
|
|
33
|
-
|
35
|
+
before do
|
36
|
+
allow($stdin).to(receive(:gets).and_return("\n"))
|
37
|
+
allow(user_io).to(receive(:tell))
|
38
|
+
end
|
34
39
|
|
35
|
-
|
40
|
+
let(:ack_options) do
|
41
|
+
[
|
42
|
+
{newline: true},
|
43
|
+
{prefix: "foo", newline: false},
|
44
|
+
{},
|
45
|
+
].sample
|
46
|
+
end
|
47
|
+
let(:ack_texts) do
|
48
|
+
[
|
49
|
+
["Some important statement."],
|
50
|
+
["Some", "important", "statement!"],
|
51
|
+
[],
|
52
|
+
].sample
|
53
|
+
end
|
36
54
|
|
37
|
-
it "
|
38
|
-
expect(
|
55
|
+
it "tells the message (colorized)" do
|
56
|
+
expect(user_io).to(receive(:tell).with(*ack_texts, **ack_options, color: :cyan, bright: true))
|
39
57
|
acknowledge
|
40
58
|
end
|
41
59
|
|
42
60
|
it "asks for pressing “Enter”" do
|
43
|
-
expect(
|
61
|
+
expect(user_io).to(receive(:tell).with("Please press ENTER to continue.", **ack_options))
|
44
62
|
acknowledge
|
45
63
|
end
|
46
64
|
|
@@ -48,21 +66,73 @@ module Infopark
|
|
48
66
|
expect($stdin).to(receive(:gets).and_return("\n"))
|
49
67
|
acknowledge
|
50
68
|
end
|
69
|
+
|
70
|
+
it "returns nil" do
|
71
|
+
expect(acknowledge).to(be_nil)
|
72
|
+
end
|
51
73
|
end
|
52
74
|
|
53
75
|
describe "#ask" do
|
54
|
-
|
76
|
+
subject(:ask) { user_io.ask(*ask_texts, **ask_options.merge(tell_options)) }
|
55
77
|
|
78
|
+
before do
|
79
|
+
allow($stdin).to(receive(:gets).and_return("#{answer}\n"))
|
80
|
+
allow(user_io).to(receive(:tell))
|
81
|
+
end
|
82
|
+
|
83
|
+
let(:answer) { "yes" }
|
84
|
+
let(:ask_texts) do
|
85
|
+
[
|
86
|
+
["do you want to?"],
|
87
|
+
["do", "you", "want", "to?"],
|
88
|
+
[],
|
89
|
+
].sample
|
90
|
+
end
|
56
91
|
let(:ask_options) { {} }
|
57
|
-
let(:
|
92
|
+
let(:tell_options) do
|
93
|
+
[
|
94
|
+
{italic: true},
|
95
|
+
{prefix: "foo", bold: false},
|
96
|
+
{},
|
97
|
+
].sample
|
98
|
+
end
|
58
99
|
|
59
|
-
|
100
|
+
shared_examples_for "any question" do |invert_answer: false|
|
101
|
+
it "tells the message (colorized)" do
|
102
|
+
expect(user_io).to(receive(:tell).with(*ask_texts, **tell_options, color: :cyan, bright: true))
|
103
|
+
ask
|
104
|
+
end
|
105
|
+
|
106
|
+
it "requests input" do
|
107
|
+
expect($stdin).to(receive(:gets).and_return("\n"))
|
108
|
+
ask
|
109
|
+
end
|
110
|
+
|
111
|
+
context "when answer is “yes”" do
|
112
|
+
let(:answer) { "yes" }
|
113
|
+
|
114
|
+
it { is_expected.to(be(invert_answer ? false : true))}
|
115
|
+
end
|
116
|
+
|
117
|
+
context "when answer is “no”" do
|
118
|
+
let(:answer) { "no" }
|
119
|
+
|
120
|
+
it { is_expected.to(be(invert_answer ? true : false))}
|
121
|
+
end
|
60
122
|
|
61
|
-
shared_examples_for "any question" do
|
62
123
|
# TODO
|
63
124
|
# it_behaves_like "handling valid answer"
|
64
125
|
# it_behaves_like "handling invalid input"
|
65
|
-
|
126
|
+
end
|
127
|
+
|
128
|
+
it "asks for answer" do
|
129
|
+
expect(user_io).to(receive(:tell).with("(yes/no) > ", **tell_options, newline: false))
|
130
|
+
ask
|
131
|
+
end
|
132
|
+
|
133
|
+
it "returns “false” on empty input" do
|
134
|
+
expect($stdin).to(receive(:gets).and_return("\n"))
|
135
|
+
expect(ask).to(be(false))
|
66
136
|
end
|
67
137
|
|
68
138
|
context "with default" do
|
@@ -72,7 +142,7 @@ module Infopark
|
|
72
142
|
let(:default_value) { true }
|
73
143
|
|
74
144
|
it "presents default answer “yes”" do
|
75
|
-
expect(
|
145
|
+
expect(user_io).to(receive(:tell).with("(yes/no) [yes] > ", **tell_options, newline: false))
|
76
146
|
ask
|
77
147
|
end
|
78
148
|
|
@@ -88,7 +158,7 @@ module Infopark
|
|
88
158
|
let(:default_value) { false }
|
89
159
|
|
90
160
|
it "presents default answer “no”" do
|
91
|
-
expect(
|
161
|
+
expect(user_io).to(receive(:tell).with("(yes/no) [no] > ", **tell_options, newline: false))
|
92
162
|
ask
|
93
163
|
end
|
94
164
|
|
@@ -127,7 +197,7 @@ module Infopark
|
|
127
197
|
context "“no”" do
|
128
198
|
let(:expected_value) { "no" }
|
129
199
|
|
130
|
-
it_behaves_like "any question"
|
200
|
+
it_behaves_like "any question", invert_answer: true
|
131
201
|
|
132
202
|
it "returns “true” when answering “no”" do
|
133
203
|
expect($stdin).to(receive(:gets).and_return("no\n"))
|
@@ -146,6 +216,53 @@ module Infopark
|
|
146
216
|
end
|
147
217
|
end
|
148
218
|
|
219
|
+
describe "#confirm" do
|
220
|
+
subject(:confirm) { user_io.confirm(*confirm_texts, **confirm_options) }
|
221
|
+
|
222
|
+
before { allow(user_io).to(receive(:ask)).and_return(ask_result) }
|
223
|
+
|
224
|
+
let(:ask_result) { true }
|
225
|
+
let(:confirm_options) do
|
226
|
+
[
|
227
|
+
{expected: "yes"},
|
228
|
+
{default: "foo", expected: "bar"},
|
229
|
+
{},
|
230
|
+
].sample
|
231
|
+
end
|
232
|
+
let(:confirm_texts) do
|
233
|
+
[
|
234
|
+
%w[foo bar],
|
235
|
+
%w[baz],
|
236
|
+
[],
|
237
|
+
].sample
|
238
|
+
end
|
239
|
+
|
240
|
+
it "delegates to #ask" do
|
241
|
+
confirm
|
242
|
+
if confirm_texts.empty? && confirm_options.empty?
|
243
|
+
expect(user_io).to(have_received(:ask).with(no_args))
|
244
|
+
else
|
245
|
+
expect(user_io).to(have_received(:ask).with(*confirm_texts, **confirm_options))
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
context "when #ask returns truthy" do
|
250
|
+
let(:ask_result) { [SecureRandom.hex, 1, true].sample }
|
251
|
+
|
252
|
+
it "returns the result" do
|
253
|
+
expect(confirm).to(be(ask_result))
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
context "when #ask returns falsey" do
|
258
|
+
let(:ask_result) { [nil, false].sample }
|
259
|
+
|
260
|
+
it "aborts" do
|
261
|
+
expect { confirm }.to(raise_error(UserIO::Aborted))
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
149
266
|
describe "#select" do
|
150
267
|
before { allow($stdin).to(receive(:gets).and_return("1\n")) }
|
151
268
|
|
@@ -206,7 +323,7 @@ module Infopark
|
|
206
323
|
describe "#tell_pty_stream" do
|
207
324
|
let(:color_options) { {} }
|
208
325
|
let(:stream) { instance_double(IO) }
|
209
|
-
let(:data) { "test data" }
|
326
|
+
let(:data) { +"test data" }
|
210
327
|
|
211
328
|
subject(:tell) { user_io.tell_pty_stream(stream, **color_options) }
|
212
329
|
|
@@ -221,7 +338,7 @@ module Infopark
|
|
221
338
|
it "tells all data from stream in non blocking chunks" do
|
222
339
|
expect(stream).to(receive(:eof?).and_return(false, false, false, true))
|
223
340
|
expect(stream).to(receive(:read_nonblock).with(100)
|
224
|
-
.and_return("first\nchunk", "second chunk", "\nlast chunk"))
|
341
|
+
.and_return(+"first\nchunk", +"second chunk", +"\nlast chunk"))
|
225
342
|
expect($stdout).to(receive(:write).with("first\nchunk"))
|
226
343
|
expect($stdout).to(receive(:write).with("second chunk"))
|
227
344
|
expect($stdout).to(receive(:write).with("\nlast chunk"))
|
@@ -261,7 +378,7 @@ module Infopark
|
|
261
378
|
end
|
262
379
|
|
263
380
|
context "when stream contains carriage return" do
|
264
|
-
let(:data) { "some\rdata\rwith\rCRs" }
|
381
|
+
let(:data) { +"some\rdata\rwith\rCRs" }
|
265
382
|
|
266
383
|
it "writes the prefix right after the CR" do
|
267
384
|
expect($stdout).to(receive(:write).with("[the prefix] ").ordered)
|
@@ -289,7 +406,7 @@ module Infopark
|
|
289
406
|
end
|
290
407
|
|
291
408
|
context "when stream contains newline" do
|
292
|
-
let(:data) { "some\ndata\nwith\nNLs" }
|
409
|
+
let(:data) { +"some\ndata\nwith\nNLs" }
|
293
410
|
|
294
411
|
it "writes the prefix right after the NL" do
|
295
412
|
expect($stdout).to(receive(:write).with("[the prefix] ").ordered)
|
@@ -317,7 +434,7 @@ module Infopark
|
|
317
434
|
|
318
435
|
context "when stream ends with newline" do
|
319
436
|
# includes an empty chunk to verify, that they don't consume the pending NL
|
320
|
-
let(:data) { ["some\n", "data\n", "with\n", "", "NLs\n", ""] }
|
437
|
+
let(:data) { [+"some\n", +"data\n", +"with\n", +"", +"NLs\n", +""] }
|
321
438
|
|
322
439
|
it "does not write prefix after the last newline" do
|
323
440
|
expect($stdout).to(receive(:write).with("[the prefix] ").ordered)
|
@@ -354,7 +471,7 @@ module Infopark
|
|
354
471
|
end
|
355
472
|
|
356
473
|
context "when data does not end with newline" do
|
357
|
-
let(:data) { "foo" }
|
474
|
+
let(:data) { +"foo" }
|
358
475
|
|
359
476
|
it "writes prefix on next output nevertheless" do
|
360
477
|
expect($stdout).to(receive(:write).with("[the prefix] ").ordered)
|
@@ -388,20 +505,23 @@ module Infopark
|
|
388
505
|
context "when in background" do
|
389
506
|
let(:color_options) { {color: :yellow} }
|
390
507
|
let(:options) { {output_prefix: "foo"} }
|
391
|
-
let(:data) { ["data\n", "in\nchunks", "", "yo\n", ""] }
|
508
|
+
let(:data) { [+"data\n", +"in\nchunks", +"", +"yo\n", +""] }
|
392
509
|
|
393
|
-
|
394
|
-
@
|
395
|
-
Thread.new
|
510
|
+
before do
|
511
|
+
@fg_in = Thread::Queue.new
|
512
|
+
@fg_out = Thread::Queue.new
|
513
|
+
@fg_thread = Thread.new do
|
396
514
|
user_io.background_other_threads
|
397
|
-
|
515
|
+
@fg_out.push(:other_backgrounded)
|
516
|
+
@fg_in.pop
|
398
517
|
user_io.foreground
|
399
518
|
end
|
400
519
|
end
|
401
520
|
|
402
|
-
after {
|
521
|
+
after { @fg_thread.kill.join }
|
403
522
|
|
404
523
|
it "holds back the output until coming back to foreground" do
|
524
|
+
@fg_out.pop(timeout: 1)
|
405
525
|
expect($stdout).to_not(receive(:write))
|
406
526
|
tell
|
407
527
|
RSpec::Mocks.space.proxy_for($stdout).reset
|
@@ -413,10 +533,37 @@ module Infopark
|
|
413
533
|
expect($stdout).to(receive(:write).with("yo").ordered)
|
414
534
|
expect($stdout).to(receive(:write).with("\n").ordered)
|
415
535
|
expect($stdout).to(receive(:write).with("\e[22;39m").ordered)
|
416
|
-
@
|
417
|
-
|
536
|
+
@fg_in.push(:background_done)
|
537
|
+
@fg_thread.join
|
418
538
|
end
|
419
539
|
end
|
420
540
|
end
|
541
|
+
|
542
|
+
describe "#warn" do
|
543
|
+
subject(:warn) { user_io.warn(*warn_texts, **warn_options) }
|
544
|
+
|
545
|
+
before { allow(user_io).to(receive(:tell)).and_return(tell_result) }
|
546
|
+
|
547
|
+
let(:tell_result) { [SecureRandom.hex, nil].sample }
|
548
|
+
let(:warn_options) do
|
549
|
+
[
|
550
|
+
{newline: true},
|
551
|
+
{prefix: "foo", newline: false},
|
552
|
+
{},
|
553
|
+
].sample
|
554
|
+
end
|
555
|
+
let(:warn_texts) do
|
556
|
+
[
|
557
|
+
%w[foo bar],
|
558
|
+
%w[baz],
|
559
|
+
[],
|
560
|
+
].sample
|
561
|
+
end
|
562
|
+
|
563
|
+
it "delegates to #tell" do
|
564
|
+
warn
|
565
|
+
expect(user_io).to(have_received(:tell).with(*warn_texts, **warn_options, color: :yellow, bright: true))
|
566
|
+
end
|
567
|
+
end
|
421
568
|
end
|
422
569
|
end
|