clin 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.lint-ci.yml +2 -0
- data/.simplecov +5 -0
- data/.travis.yml +8 -0
- data/CHANGELOG.md +11 -0
- data/README.md +5 -4
- data/benchmarks/bench.rb +21 -0
- data/benchmarks/text_bench.rb +78 -0
- data/clin.gemspec +2 -1
- data/examples/reusable_options.rb +19 -0
- data/examples/simple.rb +8 -3
- data/examples/test.rb +5 -5
- data/examples/text_builder.rb +40 -0
- data/lib/clin/argument.rb +19 -2
- data/lib/clin/command_mixin/core.rb +13 -18
- data/lib/clin/command_mixin/options.rb +37 -26
- data/lib/clin/command_parser.rb +46 -57
- data/lib/clin/common/help_options.rb +1 -0
- data/lib/clin/errors.rb +50 -4
- data/lib/clin/line_reader/basic.rb +38 -0
- data/lib/clin/line_reader/readline.rb +53 -0
- data/lib/clin/line_reader.rb +16 -0
- data/lib/clin/option.rb +24 -11
- data/lib/clin/option_parser.rb +159 -0
- data/lib/clin/shell.rb +36 -15
- data/lib/clin/shell_interaction/choose.rb +19 -11
- data/lib/clin/shell_interaction/file_conflict.rb +4 -1
- data/lib/clin/shell_interaction/select.rb +44 -0
- data/lib/clin/shell_interaction.rb +1 -0
- data/lib/clin/text/table.rb +270 -0
- data/lib/clin/text.rb +152 -0
- data/lib/clin/version.rb +1 -1
- data/lib/clin.rb +10 -1
- data/spec/clin/command_dispacher_spec.rb +1 -1
- data/spec/clin/command_mixin/options_spec.rb +38 -15
- data/spec/clin/command_parser_spec.rb +27 -51
- data/spec/clin/line_reader/basic_spec.rb +54 -0
- data/spec/clin/line_reader/readline_spec.rb +64 -0
- data/spec/clin/line_reader_spec.rb +17 -0
- data/spec/clin/option_parser_spec.rb +217 -0
- data/spec/clin/option_spec.rb +5 -7
- data/spec/clin/shell_interaction/choose_spec.rb +30 -0
- data/spec/clin/shell_interaction/file_interaction_spec.rb +18 -0
- data/spec/clin/shell_interaction/select_spec.rb +96 -0
- data/spec/clin/shell_spec.rb +42 -0
- data/spec/clin/text/table_cell_spec.rb +72 -0
- data/spec/clin/text/table_row_spec.rb +74 -0
- data/spec/clin/text/table_separator_row_spec.rb +82 -0
- data/spec/clin/text/table_spec.rb +259 -0
- data/spec/clin/text_spec.rb +158 -0
- data/spec/examples/list_option_spec.rb +6 -2
- data/spec/examples/reusable_options_spec.rb +21 -0
- data/spec/examples/simple_spec.rb +9 -9
- data/spec/spec_helper.rb +3 -2
- 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
|
data/spec/clin/option_spec.rb
CHANGED
@@ -19,7 +19,7 @@ RSpec.describe Clin::Option do
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
describe '#
|
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.
|
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.
|
44
|
+
subject.trigger(opts, out, value)
|
46
45
|
end
|
47
|
-
it { expect(
|
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
|
data/spec/clin/shell_spec.rb
CHANGED
@@ -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
|