command_kit 0.1.0.pre1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +15 -0
  3. data/.rubocop.yml +138 -0
  4. data/ChangeLog.md +34 -2
  5. data/Gemfile +3 -0
  6. data/README.md +135 -214
  7. data/Rakefile +3 -2
  8. data/command_kit.gemspec +4 -4
  9. data/examples/colors.rb +30 -0
  10. data/examples/command.rb +65 -0
  11. data/examples/pager.rb +30 -0
  12. data/gemspec.yml +10 -2
  13. data/lib/command_kit/arguments/argument.rb +16 -44
  14. data/lib/command_kit/arguments/argument_value.rb +3 -30
  15. data/lib/command_kit/arguments.rb +66 -20
  16. data/lib/command_kit/colors.rb +253 -45
  17. data/lib/command_kit/command.rb +50 -3
  18. data/lib/command_kit/command_name.rb +9 -0
  19. data/lib/command_kit/commands/auto_load/subcommand.rb +3 -0
  20. data/lib/command_kit/commands/auto_load.rb +16 -0
  21. data/lib/command_kit/commands/auto_require.rb +16 -0
  22. data/lib/command_kit/commands/command.rb +3 -0
  23. data/lib/command_kit/commands/help.rb +2 -0
  24. data/lib/command_kit/commands/parent_command.rb +7 -0
  25. data/lib/command_kit/commands/subcommand.rb +15 -0
  26. data/lib/command_kit/commands.rb +40 -4
  27. data/lib/command_kit/description.rb +15 -2
  28. data/lib/command_kit/env/home.rb +9 -0
  29. data/lib/command_kit/env/path.rb +15 -0
  30. data/lib/command_kit/env.rb +4 -0
  31. data/lib/command_kit/examples.rb +15 -2
  32. data/lib/command_kit/exception_handler.rb +4 -0
  33. data/lib/command_kit/help/man.rb +74 -47
  34. data/lib/command_kit/help.rb +10 -1
  35. data/lib/command_kit/inflector.rb +49 -17
  36. data/lib/command_kit/interactive.rb +239 -0
  37. data/lib/command_kit/main.rb +20 -9
  38. data/lib/command_kit/man.rb +44 -0
  39. data/lib/command_kit/open_app.rb +69 -0
  40. data/lib/command_kit/options/option.rb +36 -9
  41. data/lib/command_kit/options/option_value.rb +42 -3
  42. data/lib/command_kit/options/parser.rb +44 -17
  43. data/lib/command_kit/options/quiet.rb +3 -0
  44. data/lib/command_kit/options/verbose.rb +5 -0
  45. data/lib/command_kit/options/version.rb +6 -0
  46. data/lib/command_kit/options.rb +59 -10
  47. data/lib/command_kit/os/linux.rb +157 -0
  48. data/lib/command_kit/os.rb +165 -11
  49. data/lib/command_kit/package_manager.rb +200 -0
  50. data/lib/command_kit/pager.rb +84 -9
  51. data/lib/command_kit/printing/indent.rb +25 -2
  52. data/lib/command_kit/printing.rb +23 -0
  53. data/lib/command_kit/program_name.rb +7 -0
  54. data/lib/command_kit/stdio.rb +24 -0
  55. data/lib/command_kit/sudo.rb +40 -0
  56. data/lib/command_kit/terminal.rb +159 -0
  57. data/lib/command_kit/usage.rb +14 -0
  58. data/lib/command_kit/version.rb +1 -1
  59. data/lib/command_kit/xdg.rb +21 -1
  60. data/lib/command_kit.rb +1 -0
  61. data/spec/arguments/argument_spec.rb +5 -41
  62. data/spec/arguments/argument_value_spec.rb +1 -61
  63. data/spec/arguments_spec.rb +8 -25
  64. data/spec/colors_spec.rb +277 -13
  65. data/spec/command_name_spec.rb +1 -1
  66. data/spec/command_spec.rb +4 -1
  67. data/spec/commands/auto_load/subcommand_spec.rb +1 -1
  68. data/spec/commands/auto_load_spec.rb +1 -1
  69. data/spec/commands/auto_require_spec.rb +2 -2
  70. data/spec/commands/help_spec.rb +1 -1
  71. data/spec/commands/parent_command_spec.rb +1 -1
  72. data/spec/commands/subcommand_spec.rb +1 -1
  73. data/spec/commands_spec.rb +2 -2
  74. data/spec/description_spec.rb +1 -25
  75. data/spec/env/home_spec.rb +1 -1
  76. data/spec/env/path_spec.rb +1 -1
  77. data/spec/examples_spec.rb +1 -25
  78. data/spec/exception_handler_spec.rb +1 -1
  79. data/spec/help/man_spec.rb +316 -0
  80. data/spec/help_spec.rb +0 -25
  81. data/spec/inflector_spec.rb +71 -9
  82. data/spec/interactive_spec.rb +415 -0
  83. data/spec/main_spec.rb +7 -7
  84. data/spec/man_spec.rb +46 -0
  85. data/spec/open_app_spec.rb +85 -0
  86. data/spec/options/option_spec.rb +48 -9
  87. data/spec/options/option_value_spec.rb +53 -4
  88. data/spec/options_spec.rb +1 -1
  89. data/spec/os/linux_spec.rb +154 -0
  90. data/spec/os_spec.rb +201 -14
  91. data/spec/package_manager_spec.rb +806 -0
  92. data/spec/pager_spec.rb +78 -15
  93. data/spec/printing/indent_spec.rb +1 -1
  94. data/spec/printing_spec.rb +10 -2
  95. data/spec/program_name_spec.rb +1 -1
  96. data/spec/spec_helper.rb +0 -3
  97. data/spec/sudo_spec.rb +51 -0
  98. data/spec/{console_spec.rb → terminal_spec.rb} +65 -35
  99. data/spec/usage_spec.rb +2 -2
  100. data/spec/xdg_spec.rb +1 -1
  101. metadata +32 -13
  102. data/lib/command_kit/arguments/usage.rb +0 -6
  103. data/lib/command_kit/console.rb +0 -141
  104. data/lib/command_kit/options/usage.rb +0 -6
data/spec/help_spec.rb CHANGED
@@ -90,30 +90,5 @@ describe CommandKit::Help do
90
90
  subject.help()
91
91
  end
92
92
  end
93
-
94
- context "when the superclass defines it's own #help method" do
95
- module TestHelp
96
- class SuperclassWithHelp
97
-
98
- def help
99
- puts 'superclass'
100
- end
101
-
102
- end
103
-
104
- class SubclassOfSuperclassWithHelp < SuperclassWithHelp
105
- include CommandKit::Help
106
- end
107
- end
108
-
109
- let(:command_superclass) { TestHelp::SuperclassWithHelp }
110
- let(:command_class) { TestHelp::SubclassOfSuperclassWithHelp }
111
-
112
- it "must call the superclass'es #help" do
113
- expect_any_instance_of(command_superclass).to receive(:help)
114
-
115
- subject.help()
116
- end
117
- end
118
93
  end
119
94
  end
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
  require 'command_kit/inflector'
3
3
 
4
- describe Inflector do
4
+ describe CommandKit::Inflector do
5
5
  describe ".demodularize" do
6
6
  context "when given a single class or module name" do
7
7
  let(:name) { 'Foo' }
@@ -28,6 +28,10 @@ describe Inflector do
28
28
  end
29
29
  end
30
30
 
31
+ ascii = (0..255).map(&:chr)
32
+ alpha = ('a'..'z').to_a + ('A'..'Z').to_a
33
+ numeric = ('0'..'9').to_a
34
+
31
35
  describe ".underscore" do
32
36
  it "must convert CamelCase to camel_case" do
33
37
  expect(subject.underscore('CamelCase')).to eq('camel_case')
@@ -37,18 +41,48 @@ describe Inflector do
37
41
  expect(subject.underscore('camelCase')).to eq('camel_case')
38
42
  end
39
43
 
40
- it "must convert Camel to camel" do
41
- expect(subject.underscore('Camel')).to eq('camel')
44
+ it "must convert Camelcase to camelcase" do
45
+ expect(subject.underscore('Camelcase')).to eq('camelcase')
42
46
  end
43
47
 
44
- it "must convert CAMEL to camel" do
45
- expect(subject.underscore('CAMEL')).to eq('camel')
48
+ it "must convert CAMELCASE to camelcase" do
49
+ expect(subject.underscore('CAMELCASE')).to eq('camelcase')
46
50
  end
47
51
 
48
- context "when given a String containing '/' characters" do
49
- it "must replace dashes with underscores" do
50
- expect(subject.underscore('foo-bar')).to eq('foo_bar')
51
- end
52
+ it "must convert CAMELCase to camel_case" do
53
+ expect(subject.underscore('CAMELCase')).to eq('camel_case')
54
+ end
55
+
56
+ it "must convert CamelCASE to camel_case" do
57
+ expect(subject.underscore('CAMELCase')).to eq('camel_case')
58
+ end
59
+
60
+ it "must convert FooBARBaz to foo_bar_baz" do
61
+ expect(subject.underscore('FooBARBaz')).to eq('foo_bar_baz')
62
+ end
63
+
64
+ it "must convert foo_bar_baz to foo_bar_baz" do
65
+ expect(subject.underscore('foo_bar_baz')).to eq('foo_bar_baz')
66
+ end
67
+
68
+ it "must convert foo___bar___baz to foo___bar___baz" do
69
+ expect(subject.underscore('foo___bar___baz')).to eq('foo___bar___baz')
70
+ end
71
+
72
+ it "must convert foo-bar-baz to foo_bar_baz" do
73
+ expect(subject.underscore('foo-bar-baz')).to eq('foo_bar_baz')
74
+ end
75
+
76
+ it "must convert foo---bar---baz to foo___bar___baz" do
77
+ expect(subject.underscore('foo---bar---baz')).to eq('foo___bar___baz')
78
+ end
79
+
80
+ it "must convert foo_BAR_baz to foo_bar_baz" do
81
+ expect(subject.underscore('foo_BAR_baz')).to eq('foo_bar_baz')
82
+ end
83
+
84
+ it "must convert foo-BAR-baz to foo_bar_baz" do
85
+ expect(subject.underscore('foo-BAR-baz')).to eq('foo_bar_baz')
52
86
  end
53
87
 
54
88
  context "when given a non-String" do
@@ -56,6 +90,20 @@ describe Inflector do
56
90
  expect(subject.underscore(:CamelCase)).to eq('camel_case')
57
91
  end
58
92
  end
93
+
94
+ separators = %w[_ -]
95
+
96
+ (ascii - alpha - numeric - separators).each do |char|
97
+ context "when the given String contains a #{char.inspect} character" do
98
+ let(:string) { "Foo#{char}Bar" }
99
+
100
+ it do
101
+ expect {
102
+ subject.underscore(string)
103
+ }.to raise_error(ArgumentError,"cannot convert string to underscored: #{string.inspect}")
104
+ end
105
+ end
106
+ end
59
107
  end
60
108
 
61
109
  describe ".dasherize" do
@@ -100,5 +148,19 @@ describe Inflector do
100
148
  expect(subject.camelize(:foo_bar)).to eq('FooBar')
101
149
  end
102
150
  end
151
+
152
+ separators = %w[_ - /]
153
+
154
+ (ascii - alpha - numeric - separators).each do |char|
155
+ context "when the given String contains a #{char.inspect} character" do
156
+ let(:string) { "foo#{char}bar" }
157
+
158
+ it do
159
+ expect {
160
+ subject.camelize(string)
161
+ }.to raise_error(ArgumentError,"cannot convert string to CamelCase: #{string.inspect}")
162
+ end
163
+ end
164
+ end
103
165
  end
104
166
  end
@@ -0,0 +1,415 @@
1
+ require 'spec_helper'
2
+ require 'command_kit/interactive'
3
+
4
+ describe CommandKit::Interactive do
5
+ module TestInteractive
6
+ class TestCommand
7
+ include CommandKit::Interactive
8
+ end
9
+ end
10
+
11
+ let(:command_class) { TestInteractive::TestCommand }
12
+
13
+ describe "#included" do
14
+ subject { command_class }
15
+
16
+ it { expect(subject).to include(CommandKit::Stdio) }
17
+ end
18
+
19
+ let(:stdout) { StringIO.new }
20
+ let(:stdin) { StringIO.new }
21
+ let(:stderr) { StringIO.new }
22
+
23
+ subject do
24
+ command_class.new(stdout: stdout, stdin: stdin, stderr: stderr)
25
+ end
26
+
27
+ let(:prompt) { 'Prompt' }
28
+
29
+ describe "#ask" do
30
+ let(:input) { 'foo' }
31
+
32
+ it "must print a prompt, read input, and return the input" do
33
+ expect(stdout).to receive(:print).with("#{prompt}: ")
34
+ expect(stdin).to receive(:gets).and_return(input)
35
+
36
+ expect(subject.ask(prompt)).to eq(input)
37
+ end
38
+
39
+ it "must accept empty user input by default" do
40
+ expect(stdin).to receive(:gets).and_return("")
41
+
42
+ expect(subject.ask(prompt)).to eq("")
43
+ end
44
+
45
+ context "when Ctrl^C is entered" do
46
+ it "must return \"\"" do
47
+ expect(stdin).to receive(:gets).and_return(nil) # simulate Ctrl^C
48
+
49
+ expect(subject.ask(prompt)).to eq("")
50
+ end
51
+ end
52
+
53
+ context "when default: is given" do
54
+ let(:default) { 'bar' }
55
+
56
+ it "must include the default: value in the prompt" do
57
+ expect(stdout).to receive(:print).with("#{prompt} [#{default}]: ")
58
+ expect(stdin).to receive(:gets).and_return(input)
59
+
60
+ expect(subject.ask(prompt, default: default)).to eq(input)
61
+ end
62
+
63
+ context "and non-empty user input is given" do
64
+ it "must return the non-empty user input" do
65
+ expect(stdin).to receive(:gets).and_return(input)
66
+
67
+ expect(subject.ask(prompt, default: default)).to eq(input)
68
+ end
69
+ end
70
+
71
+ context "and empty user input is given" do
72
+ it "must return the default value" do
73
+ expect(stdin).to receive(:gets).and_return("")
74
+
75
+ expect(subject.ask(prompt, default: default)).to eq(default)
76
+ end
77
+ end
78
+ end
79
+
80
+ context "when required: is given" do
81
+ context "and empty user input is given" do
82
+ it "must ask for input again, until non-empty input is given" do
83
+ expect(stdin).to receive(:gets).and_return("")
84
+ expect(stdin).to receive(:gets).and_return("")
85
+ expect(stdin).to receive(:gets).and_return(input)
86
+
87
+ expect(subject.ask(prompt, required: true)).to eq(input)
88
+ end
89
+ end
90
+
91
+ context "and non-empty user input is given" do
92
+ it "must return the non-empty user input" do
93
+ expect(stdin).to receive(:gets).and_return(input)
94
+
95
+ expect(subject.ask(prompt, required: true)).to eq(input)
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+ describe "#ask_yes_or_no" do
102
+ let(:input) { 'Y' }
103
+
104
+ it "must print a prompt indicating Y/N, and then accept input" do
105
+ expect(stdout).to receive(:print).with("#{prompt} (Y/N): ")
106
+ expect(stdin).to receive(:gets).and_return(input)
107
+
108
+ subject.ask_yes_or_no(prompt)
109
+ end
110
+
111
+ context "when 'Y' is entered" do
112
+ let(:input) { 'Y' }
113
+
114
+ it "must return true" do
115
+ expect(stdin).to receive(:gets).and_return(input)
116
+
117
+ expect(subject.ask_yes_or_no(prompt)).to eq(true)
118
+ end
119
+ end
120
+
121
+ context "when 'y' is entered" do
122
+ let(:input) { 'y' }
123
+
124
+ it "must return true" do
125
+ expect(stdin).to receive(:gets).and_return(input)
126
+
127
+ expect(subject.ask_yes_or_no(prompt)).to eq(true)
128
+ end
129
+ end
130
+
131
+ context "when 'YES' is entered" do
132
+ let(:input) { 'YES' }
133
+
134
+ it "must return true" do
135
+ expect(stdin).to receive(:gets).and_return(input)
136
+
137
+ expect(subject.ask_yes_or_no(prompt)).to eq(true)
138
+ end
139
+ end
140
+
141
+ context "when 'Yes' is entered" do
142
+ let(:input) { 'Yes' }
143
+
144
+ it "must return true" do
145
+ expect(stdin).to receive(:gets).and_return(input)
146
+
147
+ expect(subject.ask_yes_or_no(prompt)).to eq(true)
148
+ end
149
+ end
150
+
151
+ context "when 'yes' is entered" do
152
+ let(:input) { 'yes' }
153
+
154
+ it "must return true" do
155
+ expect(stdin).to receive(:gets).and_return(input)
156
+
157
+ expect(subject.ask_yes_or_no(prompt)).to eq(true)
158
+ end
159
+ end
160
+
161
+ context "when 'N' is entered" do
162
+ let(:input) { 'N' }
163
+
164
+ it "must return false" do
165
+ expect(stdin).to receive(:gets).and_return(input)
166
+
167
+ expect(subject.ask_yes_or_no(prompt)).to eq(false)
168
+ end
169
+ end
170
+
171
+ context "when 'n' is entered" do
172
+ let(:input) { 'n' }
173
+
174
+ it "must return false" do
175
+ expect(stdin).to receive(:gets).and_return(input)
176
+
177
+ expect(subject.ask_yes_or_no(prompt)).to eq(false)
178
+ end
179
+ end
180
+
181
+ context "when 'NO' is entered" do
182
+ let(:input) { 'NO' }
183
+
184
+ it "must return false" do
185
+ expect(stdin).to receive(:gets).and_return(input)
186
+
187
+ expect(subject.ask_yes_or_no(prompt)).to eq(false)
188
+ end
189
+ end
190
+
191
+ context "when 'No' is entered" do
192
+ let(:input) { 'No' }
193
+
194
+ it "must return false" do
195
+ expect(stdin).to receive(:gets).and_return(input)
196
+
197
+ expect(subject.ask_yes_or_no(prompt)).to eq(false)
198
+ end
199
+ end
200
+
201
+ context "when 'no' is entered" do
202
+ let(:input) { 'no' }
203
+
204
+ it "must return false" do
205
+ expect(stdin).to receive(:gets).and_return(input)
206
+
207
+ expect(subject.ask_yes_or_no(prompt)).to eq(false)
208
+ end
209
+ end
210
+
211
+ context "when input besides y/n/yes/no is entered" do
212
+ let(:input) { 'jflksjfls' }
213
+
214
+ it "must return false" do
215
+ expect(stdin).to receive(:gets).and_return(input)
216
+
217
+ expect(subject.ask_yes_or_no(prompt)).to eq(false)
218
+ end
219
+ end
220
+
221
+ context "when defualt: is given" do
222
+ context "and is true" do
223
+ let(:default) { true }
224
+
225
+ it "must include [Y] in the prompt" do
226
+ expect(stdout).to receive(:print).with("#{prompt} (Y/N) [Y]: ")
227
+ expect(stdin).to receive(:gets).and_return(input)
228
+
229
+ subject.ask_yes_or_no(prompt, default: default)
230
+ end
231
+
232
+ context "and empty user-input is given" do
233
+ let(:input) { "" }
234
+
235
+ it "must return true" do
236
+ expect(stdout).to receive(:print).with("#{prompt} (Y/N) [Y]: ")
237
+ expect(stdin).to receive(:gets).and_return(input)
238
+
239
+ expect(subject.ask_yes_or_no(prompt, default: default)).to eq(default)
240
+ end
241
+ end
242
+ end
243
+
244
+ context "and is false" do
245
+ let(:default) { false }
246
+
247
+ it "must include [N] in the prompt" do
248
+ expect(stdout).to receive(:print).with("#{prompt} (Y/N) [N]: ")
249
+ expect(stdin).to receive(:gets).and_return(input)
250
+
251
+ subject.ask_yes_or_no(prompt, default: default)
252
+ end
253
+
254
+ context "and empty user-input is given" do
255
+ let(:input) { "" }
256
+
257
+ it "must return false" do
258
+ expect(stdout).to receive(:print).with("#{prompt} (Y/N) [N]: ")
259
+ expect(stdin).to receive(:gets).and_return(input)
260
+
261
+ expect(subject.ask_yes_or_no(prompt, default: default)).to eq(default)
262
+ end
263
+ end
264
+ end
265
+ end
266
+ end
267
+
268
+ describe "#ask_multiple_choice" do
269
+ context "when given an Array" do
270
+ let(:choices) do
271
+ [
272
+ "foo",
273
+ "bar",
274
+ "baz"
275
+ ]
276
+ end
277
+
278
+ let(:input) { "2" }
279
+
280
+ it "must print the numbered choices, a prompt with the choices, read input, and return the choice" do
281
+ expect(stdout).to receive(:puts).with(" 1) #{choices[0]}")
282
+ expect(stdout).to receive(:puts).with(" 2) #{choices[1]}")
283
+ expect(stdout).to receive(:puts).with(" 3) #{choices[2]}")
284
+ expect(stdout).to receive(:puts).with(no_args)
285
+ expect(stdout).to receive(:print).with("#{prompt} (1, 2, 3): ")
286
+ expect(stdin).to receive(:gets).and_return(input)
287
+
288
+ expect(subject.ask_multiple_choice(prompt,choices)).to eq(choices[input.to_i - 1])
289
+ end
290
+
291
+ context "when empty user-input is given" do
292
+ it "must ask for input again, until non-empty input is given" do
293
+ expect(stdin).to receive(:gets).and_return("")
294
+ expect(stdin).to receive(:gets).and_return("")
295
+ expect(stdin).to receive(:gets).and_return(input)
296
+
297
+ expect(subject.ask_multiple_choice(prompt,choices)).to eq(choices[input.to_i - 1])
298
+ end
299
+ end
300
+
301
+ context "and default: is given" do
302
+ let(:default) { '3' }
303
+
304
+ it "must include the default: choice in the prompt" do
305
+ expect(stdout).to receive(:print).with("#{prompt} (1, 2, 3) [#{default}]: ")
306
+ expect(stdin).to receive(:gets).and_return(input)
307
+
308
+ subject.ask_multiple_choice(prompt,choices, default: default)
309
+ end
310
+
311
+ context "and empty user-input is given" do
312
+ it "must return the value for the default choice" do
313
+ expect(stdin).to receive(:gets).and_return("")
314
+
315
+ expect(subject.ask_multiple_choice(prompt,choices, default: default)).to eq(choices[default.to_i - 1])
316
+ end
317
+ end
318
+ end
319
+ end
320
+
321
+ context "when given a Hash" do
322
+ let(:choices) do
323
+ {
324
+ "A" => "foo",
325
+ "B" => "bar",
326
+ "C" => "baz"
327
+ }
328
+ end
329
+
330
+ let(:input) { "B" }
331
+
332
+ it "must print the labeled choices, a prompt with the choices, read input, and return the choice" do
333
+ expect(stdout).to receive(:puts).with(" #{choices.keys[0]}) #{choices.values[0]}")
334
+ expect(stdout).to receive(:puts).with(" #{choices.keys[1]}) #{choices.values[1]}")
335
+ expect(stdout).to receive(:puts).with(" #{choices.keys[2]}) #{choices.values[2]}")
336
+ expect(stdout).to receive(:puts).with(no_args)
337
+ expect(stdout).to receive(:print).with("#{prompt} (#{choices.keys[0]}, #{choices.keys[1]}, #{choices.keys[2]}): ")
338
+ expect(stdin).to receive(:gets).and_return(input)
339
+
340
+ expect(subject.ask_multiple_choice(prompt,choices)).to eq(choices[input])
341
+ end
342
+
343
+ context "when empty user-input is given" do
344
+ it "must ask for input again, until non-empty input is given" do
345
+ expect(stdin).to receive(:gets).and_return("")
346
+ expect(stdin).to receive(:gets).and_return("")
347
+ expect(stdin).to receive(:gets).and_return(input)
348
+
349
+ expect(subject.ask_multiple_choice(prompt,choices)).to eq(choices[input])
350
+ end
351
+ end
352
+
353
+ context "and default: is given" do
354
+ let(:default) { 'C' }
355
+
356
+ it "must include the default: choice in the prompt" do
357
+ expect(stdout).to receive(:print).with("#{prompt} (#{choices.keys[0]}, #{choices.keys[1]}, #{choices.keys[2]}) [#{default}]: ")
358
+ expect(stdin).to receive(:gets).and_return(input)
359
+
360
+ subject.ask_multiple_choice(prompt,choices, default: default)
361
+ end
362
+
363
+ context "and empty user-input is given" do
364
+ it "must return the value for the default choice" do
365
+ expect(stdin).to receive(:gets).and_return("")
366
+
367
+ expect(subject.ask_multiple_choice(prompt,choices, default: default)).to eq(choices[default])
368
+ end
369
+ end
370
+ end
371
+ end
372
+ end
373
+
374
+ describe "#ask_secret" do
375
+ let(:input) { 's3cr3t' }
376
+
377
+ context "when stdin supports to #noecho" do
378
+ it "must call #noecho, read input, then return the input" do
379
+ allow(stdin).to receive(:noecho).and_yield
380
+ expect(stdin).to receive(:gets).and_return(input)
381
+
382
+ expect(subject.ask_secret(prompt)).to eq(input)
383
+ end
384
+ end
385
+
386
+ context "when stdin does not support #noecho" do
387
+ it "must fallback to reading input" do
388
+ expect(stdin).to receive(:gets).and_return(input)
389
+
390
+ expect(subject.ask_secret(prompt)).to eq(input)
391
+ end
392
+ end
393
+
394
+ context "when empty user-input is given" do
395
+ it "must ask for input again, until non-empty input is given" do
396
+ expect(stdin).to receive(:gets).and_return("")
397
+ expect(stdin).to receive(:gets).and_return("")
398
+ expect(stdin).to receive(:gets).and_return("")
399
+ expect(stdin).to receive(:gets).and_return(input)
400
+
401
+ expect(subject.ask_secret(prompt)).to eq(input)
402
+ end
403
+ end
404
+
405
+ context "when required: is false" do
406
+ context "and empty user-input is given" do
407
+ it "must return the empty user-input" do
408
+ expect(stdin).to receive(:gets).and_return("")
409
+
410
+ expect(subject.ask_secret(prompt, required: false)).to eq("")
411
+ end
412
+ end
413
+ end
414
+ end
415
+ end