clin 0.3.0 → 0.4.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 (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