clin 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.lint-ci.yml +2 -0
  3. data/.simplecov +5 -0
  4. data/.travis.yml +8 -0
  5. data/CHANGELOG.md +11 -0
  6. data/README.md +5 -4
  7. data/benchmarks/bench.rb +21 -0
  8. data/benchmarks/text_bench.rb +78 -0
  9. data/clin.gemspec +2 -1
  10. data/examples/reusable_options.rb +19 -0
  11. data/examples/simple.rb +8 -3
  12. data/examples/test.rb +5 -5
  13. data/examples/text_builder.rb +40 -0
  14. data/lib/clin/argument.rb +19 -2
  15. data/lib/clin/command_mixin/core.rb +13 -18
  16. data/lib/clin/command_mixin/options.rb +37 -26
  17. data/lib/clin/command_parser.rb +46 -57
  18. data/lib/clin/common/help_options.rb +1 -0
  19. data/lib/clin/errors.rb +50 -4
  20. data/lib/clin/line_reader/basic.rb +38 -0
  21. data/lib/clin/line_reader/readline.rb +53 -0
  22. data/lib/clin/line_reader.rb +16 -0
  23. data/lib/clin/option.rb +24 -11
  24. data/lib/clin/option_parser.rb +159 -0
  25. data/lib/clin/shell.rb +36 -15
  26. data/lib/clin/shell_interaction/choose.rb +19 -11
  27. data/lib/clin/shell_interaction/file_conflict.rb +4 -1
  28. data/lib/clin/shell_interaction/select.rb +44 -0
  29. data/lib/clin/shell_interaction.rb +1 -0
  30. data/lib/clin/text/table.rb +270 -0
  31. data/lib/clin/text.rb +152 -0
  32. data/lib/clin/version.rb +1 -1
  33. data/lib/clin.rb +10 -1
  34. data/spec/clin/command_dispacher_spec.rb +1 -1
  35. data/spec/clin/command_mixin/options_spec.rb +38 -15
  36. data/spec/clin/command_parser_spec.rb +27 -51
  37. data/spec/clin/line_reader/basic_spec.rb +54 -0
  38. data/spec/clin/line_reader/readline_spec.rb +64 -0
  39. data/spec/clin/line_reader_spec.rb +17 -0
  40. data/spec/clin/option_parser_spec.rb +217 -0
  41. data/spec/clin/option_spec.rb +5 -7
  42. data/spec/clin/shell_interaction/choose_spec.rb +30 -0
  43. data/spec/clin/shell_interaction/file_interaction_spec.rb +18 -0
  44. data/spec/clin/shell_interaction/select_spec.rb +96 -0
  45. data/spec/clin/shell_spec.rb +42 -0
  46. data/spec/clin/text/table_cell_spec.rb +72 -0
  47. data/spec/clin/text/table_row_spec.rb +74 -0
  48. data/spec/clin/text/table_separator_row_spec.rb +82 -0
  49. data/spec/clin/text/table_spec.rb +259 -0
  50. data/spec/clin/text_spec.rb +158 -0
  51. data/spec/examples/list_option_spec.rb +6 -2
  52. data/spec/examples/reusable_options_spec.rb +21 -0
  53. data/spec/examples/simple_spec.rb +9 -9
  54. data/spec/spec_helper.rb +3 -2
  55. metadata +54 -3
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ describe Clin::LineReader::Basic do
4
+ let(:shell) { Clin::Shell.new }
5
+ describe '#readline' do
6
+ it 'uses $stdin and $stdout to get input from the user' do
7
+ expect(shell.out).to receive(:print).with('Where are you from? ')
8
+ expect(shell.in).to receive(:gets).and_return('France')
9
+ expect(shell.in).not_to receive(:noecho)
10
+ reader = Clin::LineReader::Basic.new(shell, 'Where are you from? ', {})
11
+ expect(reader.readline).to eq('France')
12
+ end
13
+
14
+ it 'disables echo when asked to' do
15
+ expect(shell.out).to receive(:print).with('Where are you from? ')
16
+ noecho_stdin = double('noecho_stdin')
17
+ expect(noecho_stdin).to receive(:gets).and_return('Asgard')
18
+ expect(shell.in).to receive(:noecho).and_yield(noecho_stdin)
19
+ reader = Clin::LineReader::Basic.new(shell, 'Where are you from? ', echo: false)
20
+ expect(reader.readline).to eq('Asgard')
21
+ end
22
+ end
23
+
24
+ describe '.available?' do
25
+ it 'returns is always available' do
26
+ expect(Clin::LineReader::Basic).to be_available
27
+ end
28
+ end
29
+
30
+ describe '#scan' do
31
+ it 'call in#gets when echo is true' do
32
+ reader = Clin::LineReader::Basic.new(shell, 'Where are you from? ', {echo: true})
33
+ expect(shell.in).to receive(:gets)
34
+ expect(shell.in).not_to receive(:noecho)
35
+ reader.send(:scan)
36
+ end
37
+
38
+ it 'call in#noecho(&:gets) when echo is false' do
39
+ reader = Clin::LineReader::Basic.new(shell, 'Where are you from? ', {echo: false})
40
+ expect(shell.in).not_to receive(:gets)
41
+ expect(shell.in).to receive(:noecho)
42
+ reader.send(:scan)
43
+ end
44
+
45
+ it 'call in#gets if the console does not support noecho' do
46
+ reader = Clin::LineReader::Basic.new(shell, 'Where are you from? ', {echo: false})
47
+ expect(shell.in).to receive(:noecho) do
48
+ fail Errno::EBADF
49
+ end
50
+ expect(shell.in).to receive(:gets)
51
+ reader.send(:scan)
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+
3
+ describe Clin::LineReader::Readline do
4
+ let(:shell) { Clin::Shell.new }
5
+ describe '#readline' do
6
+ it 'call Readline.readline' do
7
+ expect(Readline).to receive(:readline).with('> ', true).and_return('val')
8
+ expect(Readline).not_to receive(:completion_proc=)
9
+ subject = Clin::LineReader::Readline.new(shell, '> ', {})
10
+ expect(subject.readline).to eq('val')
11
+ end
12
+
13
+ it 'provides tab completion when given a limited_to option' do
14
+ countries = ['United States', 'France', 'United Kingdom']
15
+ expect(Readline).to receive(:readline)
16
+ expect(Readline).to receive(:completion_proc=) do |proc|
17
+ expect(proc.call('')).to eq countries
18
+ expect(proc.call('U')).to eq ['United States', 'United Kingdom']
19
+ expect(proc.call('United S')).to eq ['United States']
20
+ end
21
+
22
+ subject = Clin::LineReader::Readline.new(shell, 'Where are you from? ',
23
+ autocomplete: countries)
24
+ subject.readline
25
+ end
26
+
27
+ it 'default back to the basic when echo is false' do
28
+ expect(shell.out).to receive(:print).with('Where are you from? ')
29
+ noecho_stdin = double('noecho_stdin')
30
+ expect(noecho_stdin).to receive(:gets).and_return('Asgard')
31
+ expect(shell.in).to receive(:noecho).and_yield(noecho_stdin)
32
+ subject = Clin::LineReader::Readline.new(shell, 'Where are you from? ', echo: false)
33
+ expect(subject.readline).to eq('Asgard')
34
+ end
35
+ end
36
+
37
+ describe '.available?' do
38
+ it 'returns is available by default' do
39
+ expect(Clin::LineReader::Readline).to be_available
40
+ end
41
+
42
+ it 'returns not available if disable ' do
43
+ allow(Clin).to receive(:use_readline?).and_return(false)
44
+ expect(Clin::LineReader::Readline).not_to be_available
45
+ end
46
+ end
47
+
48
+ describe '#autocomplete?' do
49
+ it 'return true if defined' do
50
+ subject = Clin::LineReader::Readline.new(shell, '> ', autocomplete: [''])
51
+ expect(subject.send(:autocomplete?)).to be_truthy
52
+ end
53
+
54
+ it 'return false if false' do
55
+ subject = Clin::LineReader::Readline.new(shell, '> ', autocomplete: false)
56
+ expect(subject.send(:autocomplete?)).to be_falsey
57
+ end
58
+
59
+ it 'return false if not defined' do
60
+ subject = Clin::LineReader::Readline.new(shell, '> ')
61
+ expect(subject.send(:autocomplete?)).to be_falsey
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Clin::LineReader do
4
+ let(:shell) { Clin::Shell.new }
5
+ it 'use readline when use_readline? is true' do
6
+ expect_any_instance_of(Clin::LineReader::Readline).to receive(:readline)
7
+ expect_any_instance_of(Clin::LineReader::Basic).not_to receive(:readline)
8
+ Clin::LineReader.scan(shell, '> ')
9
+ end
10
+
11
+ it 'use basic when use_readline? is false' do
12
+ allow(Clin).to receive(:use_readline?).and_return(false)
13
+ expect_any_instance_of(Clin::LineReader::Readline).not_to receive(:readline)
14
+ expect_any_instance_of(Clin::LineReader::Basic).to receive(:readline)
15
+ Clin::LineReader.scan(shell, '> ')
16
+ end
17
+ end
@@ -0,0 +1,217 @@
1
+ require 'clin'
2
+
3
+ RSpec.describe Clin::OptionParser do
4
+ describe '#handle_unknown_option' do
5
+ let(:name) { :verbose }
6
+ let(:value) { Faker::Lorem.word }
7
+
8
+ context 'when command does not allow unknown options' do
9
+ let(:command) { double(:command, skip_options?: false) }
10
+ subject { Clin::OptionParser.new(command, []) }
11
+
12
+ before do
13
+ subject.handle_unknown_option(name, nil)
14
+ end
15
+
16
+ it { expect(subject.errors.size).to be 1 }
17
+ it { expect(subject.errors.first).to be_a Clin::UnknownOptionError }
18
+ it { expect(subject.skipped_options).to be_empty }
19
+ end
20
+
21
+ context 'when command allow unknown options and value is given' do
22
+ let(:command) { double(:command, skip_options?: true) }
23
+ subject { Clin::OptionParser.new(command, []) }
24
+
25
+ before do
26
+ subject.handle_unknown_option(name, value)
27
+ end
28
+
29
+ it { expect(subject.skipped_options.size).to eq 2 }
30
+ it { expect(subject.skipped_options).to eq [name, value] }
31
+ end
32
+
33
+ context 'when command allow unknown options and value is nil' do
34
+ let(:command) { double(:command, skip_options?: true) }
35
+ subject { Clin::OptionParser.new(command, []) }
36
+
37
+ before do
38
+ allow(subject).to receive(:complete).and_return(value)
39
+ subject.handle_unknown_option(name, nil)
40
+ end
41
+
42
+ it { expect(subject.skipped_options.size).to eq 2 }
43
+ it { expect(subject.skipped_options).to eq [name, value] }
44
+ end
45
+ end
46
+
47
+ describe '#complete' do
48
+ let(:command) { double(:command) }
49
+ let(:value) { Faker::Lorem.word }
50
+ it 'keep the value when the value exists' do
51
+ parser = Clin::OptionParser.new(command, ['some'])
52
+ expect(parser.complete(value)).to eq(value)
53
+ expect(parser.instance_variable_get(:@argv)).to eq(['some'])
54
+ end
55
+
56
+ it 'get next argument value when the value is nil' do
57
+ parser = Clin::OptionParser.new(command, [value])
58
+ expect(parser.complete(nil)).to eq(value)
59
+ expect(parser.instance_variable_get(:@argv)).to eq([])
60
+ end
61
+
62
+ it 'return nil when value is nil and next argument is an option' do
63
+ parser = Clin::OptionParser.new(command, ['-o'])
64
+ expect(parser.complete(nil)).to be nil
65
+ expect(parser.instance_variable_get(:@argv)).to eq(['-o'])
66
+ end
67
+ end
68
+
69
+ describe '#parse_next' do
70
+ let(:command) { double(:command) }
71
+ before do
72
+ allow(subject).to receive(:parse_short)
73
+ allow(subject).to receive(:parse_long)
74
+ subject.parse_next
75
+ end
76
+ context 'when option is a long option' do
77
+ subject { Clin::OptionParser.new(command, ['--opt=val']) }
78
+ it { expect(subject).to have_received(:parse_long).with('--opt', 'val') }
79
+ it { expect(subject).not_to have_received(:parse_short) }
80
+ it { expect(subject.instance_variable_get(:@argv)).to be_empty }
81
+ it { expect(subject.instance_variable_get(:@arguments)).to be_empty }
82
+ end
83
+
84
+ context 'when option is a short option' do
85
+ subject { Clin::OptionParser.new(command, ['-oval']) }
86
+ it { expect(subject).not_to have_received(:parse_long) }
87
+ it { expect(subject).to have_received(:parse_short).with('-o', 'val') }
88
+ it { expect(subject.instance_variable_get(:@argv)).to be_empty }
89
+ it { expect(subject.instance_variable_get(:@arguments)).to be_empty }
90
+ end
91
+
92
+ context 'when option is not an option' do
93
+ subject { Clin::OptionParser.new(command, ['not opt']) }
94
+ it { expect(subject).not_to have_received(:parse_short) }
95
+ it { expect(subject).not_to have_received(:parse_long) }
96
+ it { expect(subject.instance_variable_get(:@argv)).to be_empty }
97
+ it { expect(subject.instance_variable_get(:@arguments)).to eq(['not opt']) }
98
+ end
99
+ end
100
+
101
+ describe '#parse' do
102
+ let(:command) { double(:command) }
103
+ subject { Clin::OptionParser.new(command, []) }
104
+
105
+ it 'call parse_next until it return false' do
106
+ expect(subject).to receive(:parse_next).and_return(true, true, false).exactly(3).times
107
+ subject.parse
108
+ end
109
+ end
110
+
111
+ describe '#parse_option' do
112
+ let(:command) { double(:command) }
113
+ subject { Clin::OptionParser.new(command, []) }
114
+
115
+
116
+ before do
117
+ allow(subject).to receive(:handle_unknown_option)
118
+ allow(subject).to receive(:parse_flag_option)
119
+ subject.parse_option(option, '-o', value, true)
120
+ end
121
+
122
+ context 'when option is nil' do
123
+ let(:value) { 'val' }
124
+ let(:option) { nil }
125
+ it { expect(subject).to have_received(:handle_unknown_option) }
126
+ it { expect(subject).not_to have_received(:parse_flag_option) }
127
+ end
128
+
129
+ context 'when option is a flag' do
130
+ let(:value) { 'val' }
131
+ let(:option) { double(:option, flag?: true) }
132
+ it { expect(subject).not_to have_received(:handle_unknown_option) }
133
+ it { expect(subject).to have_received(:parse_flag_option) }
134
+ end
135
+
136
+ context 'when value is nil and argument is required' do
137
+ let(:value) { nil }
138
+ let(:option) { double(:option, flag?: false, argument_optional?: false) }
139
+
140
+ it { expect(subject).not_to have_received(:handle_unknown_option) }
141
+ it { expect(subject).not_to have_received(:parse_flag_option) }
142
+ it { expect(subject.errors.first).to be_a(Clin::MissingOptionArgumentError) }
143
+ end
144
+
145
+ context 'when argument is optional or value is defined' do
146
+ let(:value) { nil }
147
+ let(:option) { double(:option, flag?: false, argument_optional?: true, trigger: true) }
148
+
149
+ it { expect(subject).not_to have_received(:handle_unknown_option) }
150
+ it { expect(subject).not_to have_received(:parse_flag_option) }
151
+ it { expect(subject.errors).to be_empty }
152
+ it { expect(option).to have_received(:trigger).with(subject, subject.options, true) }
153
+ end
154
+ end
155
+
156
+ describe '#parse_flag_option' do
157
+ let(:command) { double(:command) }
158
+ subject { Clin::OptionParser.new(command, []) }
159
+ let(:option) { double(:option, flag?: true, trigger: true) }
160
+
161
+ before do
162
+ allow(subject).to receive(:parse_compact_flag_options)
163
+ subject.parse_flag_option(option, value, short?)
164
+ end
165
+
166
+ context 'when value is nil' do
167
+ let(:value) { nil }
168
+ let(:short?) { false }
169
+ it { expect(option).to have_received(:trigger).with(subject, subject.options, true) }
170
+ end
171
+
172
+ context 'when value exist and name is long' do
173
+ let(:value) { 'val' }
174
+ let(:short?) { false }
175
+ it { expect(option).not_to have_received(:trigger) }
176
+ it { expect(subject.errors.first).to be_a(Clin::OptionUnexpectedArgumentError) }
177
+ end
178
+
179
+ context 'when value exist and name is short' do
180
+ let(:value) { 'abc' }
181
+ let(:short?) { true }
182
+ it { expect(option).to have_received(:trigger).with(subject, subject.options, true) }
183
+ it { expect(subject).to have_received(:parse_compact_flag_options) }
184
+ end
185
+ end
186
+
187
+ describe '#parse_compact_flag_options' do
188
+ let(:command) { double(:command) }
189
+ subject { Clin::OptionParser.new(command, []) }
190
+
191
+ let (:option1) { double(:option, flag?: true) }
192
+ let (:option2) { double(:option, flag?: true) }
193
+ let (:invalid_option) { double(:option, flag?: false) }
194
+
195
+ before do
196
+ allow(subject).to receive(:parse_flag_option)
197
+ allow(command).to receive(:find_option_by).and_return(*options)
198
+ subject.parse_compact_flag_options(options_arg)
199
+ end
200
+
201
+ context 'when options are only valid flag options' do
202
+ let(:options_arg) { 'ab' }
203
+ let(:options) { [option1, option2] }
204
+
205
+ it { expect(subject).to have_received(:parse_flag_option).twice }
206
+ it { expect(subject.errors).to be_empty }
207
+ end
208
+
209
+ context 'when 1 option is not a flag' do
210
+ let(:options_arg) { 'abc' }
211
+ let(:options) { [option1, invalid_option, option2] }
212
+
213
+ it { expect(subject).to have_received(:parse_flag_option).once }
214
+ it { expect(subject.errors.first).to be_a Clin::OptionError }
215
+ end
216
+ end
217
+ end
@@ -19,7 +19,7 @@ RSpec.describe Clin::Option do
19
19
  end
20
20
  end
21
21
 
22
- describe '#extract' do
22
+ describe '#trigger' do
23
23
  let(:out) { Hash.new }
24
24
  let(:opts) { double(:opts) }
25
25
  let(:value) { 'some' }
@@ -32,20 +32,18 @@ RSpec.describe Clin::Option do
32
32
  context 'when initializing with name' do
33
33
  subject { Clin::Option.new(:my_option, 'This is my option!') }
34
34
  before do
35
- subject.register(opts, out)
35
+ subject.trigger(opts, out, value)
36
36
  end
37
- it { expect(opts).to have_received(:on).once }
38
37
  it { expect(out[:my_option]).to eq(value) }
39
38
  end
40
39
 
41
40
  context 'when initializing with block' do
42
- let(:block) { proc { |_opts, out, value| out[:some] = value } }
41
+ let(:block) { proc { |_opts, out, value| out[:some] = "new val #{value}" } }
43
42
  subject { Clin::Option.new(:my_option, 'This is my option!', &block) }
44
43
  before do
45
- subject.register(opts, out)
44
+ subject.trigger(opts, out, value)
46
45
  end
47
- it { expect(opts).to have_received(:on).once }
48
- it { expect(out[:some]).to eq(value) }
46
+ it { expect(out[:some]).to eq("new val #{value}") }
49
47
 
50
48
  end
51
49
  end
@@ -51,4 +51,34 @@ RSpec.describe Clin::ShellInteraction::Choose do
51
51
  expect(subject.run('Where are you from?', countries)).to eq('france')
52
52
  end
53
53
  end
54
+
55
+ describe '#choice_help' do
56
+ def same?(value, actual)
57
+ expect(value.to_s.split("\n").map(&:rstrip)).to eq(actual.to_s.split("\n").map(&:rstrip))
58
+ end
59
+
60
+ let(:choices) { {yes: 'You want it!', no: "No you don't!", yeeahno: "I can't make up my mind!"} }
61
+ it 'get help without initials' do
62
+ help = subject.choice_help(choices).to_s
63
+ expected = <<help
64
+ Choose from:
65
+ yes You want it!
66
+ no No you don't!
67
+ yeeahno I can't make up my mind!
68
+ help
69
+ same?(help, expected)
70
+ end
71
+
72
+ it 'get help with initials' do
73
+ help = subject.choice_help(choices, allow_initials: true).to_s
74
+ expected = <<help
75
+ Choose from:
76
+ y - yes You want it!
77
+ n - no No you don't!
78
+ yeeahno I can't make up my mind!
79
+ help
80
+ same?(help, expected)
81
+ # expect(help).to eq(expected)
82
+ end
83
+ end
54
84
  end
@@ -0,0 +1,18 @@
1
+ require 'clin'
2
+
3
+ RSpec.describe Clin::ShellInteraction::FileConflict do
4
+ let(:shell) { Clin::Shell.new }
5
+ subject { Clin::ShellInteraction::FileConflict.new(shell) }
6
+
7
+ describe 'show_diff' do
8
+ it 'open a tmp file' do
9
+ file = double(:file, path: 'tmp/filename.txt')
10
+ expect(file).to receive(:write).with('new_content')
11
+ expect(file).to receive(:rewind)
12
+ expect(Tempfile).to receive(:open).with('filename.txt').and_yield(file)
13
+ expect_any_instance_of(Kernel)
14
+ .to receive(:system).with('diff -u "path/filename.txt" "tmp/filename.txt"')
15
+ subject.send(:show_diff, 'path/filename.txt', 'new_content')
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,96 @@
1
+ require 'clin'
2
+
3
+ RSpec.describe Clin::ShellInteraction::Select do
4
+ let(:shell) { Clin::Shell.new }
5
+ subject { Clin::ShellInteraction::Select.new(shell) }
6
+
7
+ def expects_scan(*outputs)
8
+ expect(shell).to receive(:scan).with('>', any_args)
9
+ .and_return(*outputs).exactly(outputs.size).times
10
+ end
11
+
12
+ describe '#run' do
13
+ suppress_puts
14
+ let(:choices) { %w(usa france germany italy) }
15
+
16
+ it 'ask for a choice and return the user reply' do
17
+ expects_scan('france')
18
+ expect(subject.run('Where are you from?', choices)).to eq('france')
19
+ end
20
+
21
+ it 'ask for a choice and return the default' do
22
+ expects_scan('')
23
+ expect(subject).to receive(:choice_help).once
24
+ expect(subject.run('Where are you from?', choices, default: 'germany')).to eq('germany')
25
+ end
26
+
27
+ it 'get the value using index' do
28
+ expects_scan('4')
29
+ expect(subject).to receive(:choice_help).once
30
+ expect(subject.run('Where are you from?', choices)).to eq('italy')
31
+ end
32
+
33
+ it 'keep asking until the answer is valid' do
34
+ expects_scan('spain', 'russia', 'france')
35
+ expect(subject).to receive(:choice_help).exactly(3).times
36
+ expect(subject.run('Where are you from?', choices)).to eq('france')
37
+ end
38
+
39
+ it 'keep asking until the answer is valid' do
40
+ expects_scan('0', '10', '2')
41
+ expect(subject).to receive(:choice_help).exactly(3).times
42
+ expect(subject.run('Where are you from?', choices)).to eq('france')
43
+ end
44
+ end
45
+
46
+ describe '#get_choice' do
47
+ let(:choices) { ['Choice A', 'Choice B', 'Choice C', 'Choice D'] }
48
+
49
+ it 'get choice with its value' do
50
+ expect(subject.get_choice(choices, 'Choice B', 1)).to eq('Choice B')
51
+ end
52
+
53
+ it 'get choice with its index' do
54
+ expect(subject.get_choice(choices, '3', 1)).to eq('Choice C')
55
+ end
56
+
57
+ it 'return nil when value is less' do
58
+ expect(subject.get_choice(choices, '0', 1)).to be nil
59
+ end
60
+ it 'return nil when value is less' do
61
+ expect(subject.get_choice(choices, '5', 1)).to be nil
62
+ end
63
+ end
64
+
65
+ describe '#choice_help' do
66
+ def same?(value, actual)
67
+ expect(value.to_s.split("\n").map(&:rstrip)).to eq(actual.to_s.split("\n").map(&:rstrip))
68
+ end
69
+
70
+ let(:choices) { ['Choice A', 'Choice B', 'Choice C'] }
71
+ let(:choices_desc) { {'Choice A' => 'This is choice A',
72
+ 'Choice Long' => 'This is choice B',
73
+ 'Choice C' => 'This is choice C'} }
74
+ it 'get help without description' do
75
+ help = subject.choice_help(choices, 1).to_s
76
+ expected = <<help
77
+ 1. Choice A
78
+ 2. Choice B
79
+ 3. Choice C
80
+ help
81
+ # same?(help, expected)
82
+ expect(help).to eq(expected)
83
+ end
84
+
85
+ it 'get help with description' do
86
+ help = subject.choice_help(choices_desc, 0).to_s
87
+ expected = <<help
88
+ 0. Choice A, This is choice A
89
+ 1. Choice Long, This is choice B
90
+ 2. Choice C, This is choice C
91
+ help
92
+ # same?(help, expected)
93
+ expect(help).to eq(expected)
94
+ end
95
+ end
96
+ end
@@ -5,6 +5,22 @@ RSpec.describe Clin::Shell do
5
5
  expect(subject).to receive(:scan).with(message, any_args).and_return(*outputs).exactly(outputs.size).times
6
6
  end
7
7
 
8
+ describe '#say' do
9
+ it 'call text#line' do
10
+ expect(subject.out).to receive(:puts).with(' Lorem Ipsum')
11
+ subject.say('Lorem Ipsum', indent: 2)
12
+ end
13
+ end
14
+
15
+ describe '#indent' do
16
+ it 'call text#line' do
17
+ expect(subject.out).to receive(:puts).with(' **Lorem Ipsum')
18
+ subject.indent 2 do
19
+ subject.say('Lorem Ipsum', indent: '**')
20
+ end
21
+ end
22
+ end
23
+
8
24
  describe '#ask' do
9
25
  it 'ask for a question and return user reply' do
10
26
  expects_scan('What is your name?', 'Smith')
@@ -17,6 +33,14 @@ RSpec.describe Clin::Shell do
17
33
  end
18
34
  end
19
35
 
36
+ describe '#password' do
37
+ it 'call ask with echo and add_to_history false' do
38
+ expect(subject).to receive(:ask)
39
+ .with('Password?', default: 'lorem', echo: false, add_to_history: false)
40
+ subject.password('Password?', default: 'lorem')
41
+ end
42
+ end
43
+
20
44
  describe '#choose' do
21
45
  let(:countries) { %w(usa france germany italy) }
22
46
 
@@ -36,6 +60,16 @@ RSpec.describe Clin::Shell do
36
60
  end
37
61
  end
38
62
 
63
+ describe '#select' do
64
+ it 'call ask with echo and add_to_history false' do
65
+ select = double(:select_interaction)
66
+ choices = %w(France Germany Italy Spain)
67
+ expect(select).to receive(:run).with('Where are you from?', choices, default: 'France')
68
+ expect(Clin::ShellInteraction::Select).to receive(:new).and_return(select)
69
+ subject.select('Where are you from?', choices, default: 'France')
70
+ end
71
+ end
72
+
39
73
  describe '#yes_or_no' do
40
74
  it 'asks the user and returns true if the user replies y' do
41
75
  expects_scan('Is earth round? [yn]', 'y')
@@ -144,4 +178,12 @@ RSpec.describe Clin::Shell do
144
178
  expect(subject.keep?('some.txt')).to be false
145
179
  end
146
180
  end
181
+
182
+ describe '#scan' do
183
+ it 'call LineReader.scan' do
184
+ options = {opt1: 'val1', opt2: 'val2'}
185
+ expect(Clin::LineReader).to receive(:scan).with(subject, 'Where are you from? ', options)
186
+ subject.send(:scan, 'Where are you from?', options)
187
+ end
188
+ end
147
189
  end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Clin::Text::TableCell do
4
+ def table_double(options)
5
+ double(:table, options.reverse_merge(column_length: {}, update_column_length: true))
6
+ end
7
+
8
+ describe '#length' do
9
+ let(:index) { 1 }
10
+ let(:table) { table_double(column_length: [1, 2, 3]) }
11
+ subject { Clin::Text::TableCell.new(table, index, 'val') }
12
+
13
+ it 'Get the length of the corresponding column' do
14
+ expect(subject.length).to eq(2)
15
+ end
16
+ end
17
+
18
+ describe '#align' do
19
+ let(:index) { 1 }
20
+ let(:table) { table_double(align: align) }
21
+ subject { Clin::Text::TableCell.new(table, 1, 'val') }
22
+
23
+ context 'when align was set using a symbol' do
24
+ let(:align) { :center }
25
+
26
+ it 'return the global align setting' do
27
+ expect(subject.align).to eq(align)
28
+ end
29
+ end
30
+
31
+ context 'when align was set column specific' do
32
+ let(:align) { [:center, :right, :center] }
33
+
34
+ it 'return the column specific setting' do
35
+ expect(subject.align).to eq(:right)
36
+ end
37
+ end
38
+
39
+ context 'when align is in wrong format' do
40
+ let(:align) { {left: true} }
41
+
42
+ it 'return the column specific setting' do
43
+ expect { subject.align }.to raise_error(Clin::Error)
44
+ end
45
+ end
46
+ end
47
+
48
+ describe '#to_s' do
49
+ let(:table) { table_double(column_length: {}) }
50
+ subject { Clin::Text::TableCell.new(table, 1, 'value') }
51
+
52
+ before do
53
+ allow(subject).to receive(:length).and_return(9)
54
+ allow(subject).to receive(:align).and_return(align)
55
+ end
56
+
57
+ context 'when align is left' do
58
+ let(:align) { :left }
59
+ it { expect(subject.to_s).to eq('value ') }
60
+ end
61
+
62
+ context 'when align is right' do
63
+ let(:align) { :right }
64
+ it { expect(subject.to_s).to eq(' value') }
65
+ end
66
+
67
+ context 'when align is center' do
68
+ let(:align) { :center }
69
+ it { expect(subject.to_s).to eq(' value ') }
70
+ end
71
+ end
72
+ end