clin 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -137,4 +137,44 @@ RSpec.describe Clin::Option do
137
137
  it { expect(subject.argument).to eq(nil) }
138
138
  end
139
139
  end
140
+
141
+ describe '.parse' do
142
+ let(:name) { Faker::Lorem.name }
143
+ context 'when no argument' do
144
+ subject { Clin::Option.parse(name, '-e --eko') }
145
+
146
+ it { expect(subject.name).to eq(name) }
147
+ it { expect(subject.short).to eq('-e') }
148
+ it { expect(subject.long).to eq('--eko') }
149
+ it { expect(subject.argument).to be nil }
150
+ end
151
+
152
+ context 'when argument' do
153
+ subject { Clin::Option.parse(name, '-e --eko=message') }
154
+
155
+ it { expect(subject.name).to eq(name) }
156
+ it { expect(subject.short).to eq('-e') }
157
+ it { expect(subject.long).to eq('--eko') }
158
+ it { expect(subject.argument).to eq('message') }
159
+ end
160
+
161
+ context 'when argument method 2' do
162
+ subject { Clin::Option.parse(name, '-e --eko=<message>') }
163
+
164
+ it { expect(subject.name).to eq(name) }
165
+ it { expect(subject.short).to eq('-e') }
166
+ it { expect(subject.long).to eq('--eko') }
167
+ it { expect(subject.argument).to eq('message') }
168
+ end
169
+
170
+ context 'when optional argument' do
171
+ subject { Clin::Option.parse(name, '-e --eko=[message]') }
172
+
173
+ it { expect(subject.name).to eq(name) }
174
+ it { expect(subject.short).to eq('-e') }
175
+ it { expect(subject.long).to eq('--eko') }
176
+ it { expect(subject.argument).to eq('message') }
177
+ it { expect(subject.optional_argument).to be true }
178
+ end
179
+ end
140
180
  end
@@ -0,0 +1,54 @@
1
+ require 'clin'
2
+
3
+ RSpec.describe Clin::ShellInteraction::Choose do
4
+ let(:shell) { Clin::Shell.new }
5
+ subject { Clin::ShellInteraction::Choose.new(shell) }
6
+
7
+ def expects_scan(message, *outputs)
8
+ expect(shell)
9
+ .to receive(:scan).with(message, any_args).and_return(*outputs).exactly(outputs.size).times
10
+ end
11
+
12
+ describe '#choice_message' do
13
+ let(:options) { {yes: '', no: '', maybe: ''} }
14
+ it { expect(subject.send(:choice_message, options)).to eq('[yes,no,maybe]') }
15
+ it { expect(subject.send(:choice_message, options, default: :yes)).to eq('[YES,no,maybe]') }
16
+ it { expect(subject.send(:choice_message, options, default: :no)).to eq('[yes,NO,maybe]') }
17
+ it { expect(subject.send(:choice_message, options, initials: true)).to eq('[ynm]') }
18
+ it { expect(subject.send(:choice_message, options, default: :yes, initials: true)).to eq('[Ynm]') }
19
+ it { expect(subject.send(:choice_message, options, default: :maybe, initials: true)).to eq('[ynM]') }
20
+ end
21
+
22
+ describe '#prepare_question' do
23
+ let(:options) { {yes: '', no: '', maybe: ''} }
24
+ it do
25
+ expect(subject.send(:prepare_question, 'Is it true?', options))
26
+ .to eq('Is it true? [yes,no,maybe]')
27
+ end
28
+ end
29
+
30
+ describe '#run' do
31
+ let(:countries) { %w(usa france germany italy) }
32
+
33
+ it 'ask for a choice and return the user reply' do
34
+ expects_scan('Where are you from? [usa,france,germany,italy]', 'France')
35
+ expect(subject.run('Where are you from?', countries)).to eq('france')
36
+ end
37
+
38
+ it 'ask for a choice and return the default' do
39
+ expects_scan('Where are you from? [usa,france,GERMANY,italy]', '')
40
+ expect(subject.run('Where are you from?', countries, default: 'germany')).to eq('germany')
41
+ end
42
+
43
+ it 'ask for a choice and user can reply with initials' do
44
+ expects_scan('Where are you from? [ufgi]', 'i')
45
+ expect(subject.run('Where are you from?', countries, allow_initials: true)).to eq('italy')
46
+ end
47
+
48
+ it 'keep asking until the answer is valid' do
49
+ expects_scan('Where are you from? [usa,france,germany,italy]', 'spain', 'russia', 'france')
50
+ expect(subject).to receive(:print_choices_help).twice
51
+ expect(subject.run('Where are you from?', countries)).to eq('france')
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,147 @@
1
+ require 'clin'
2
+
3
+ RSpec.describe Clin::Shell do
4
+ def expects_scan(message, *outputs)
5
+ expect(subject).to receive(:scan).with(message, any_args).and_return(*outputs).exactly(outputs.size).times
6
+ end
7
+
8
+ describe '#ask' do
9
+ it 'ask for a question and return user reply' do
10
+ expects_scan('What is your name?', 'Smith')
11
+ expect(subject.ask('What is your name?')).to eq('Smith')
12
+ end
13
+
14
+ it 'ask for a question and return default' do
15
+ expects_scan('What is your name?', '')
16
+ expect(subject.ask('What is your name?', default: 'Brown')).to eq('Brown')
17
+ end
18
+ end
19
+
20
+ describe '#choose' do
21
+ let(:countries) { %w(usa france germany italy) }
22
+
23
+ it 'ask for a choice and return the user reply' do
24
+ expects_scan('Where are you from? [usa,france,germany,italy]', 'France')
25
+ expect(subject.choose('Where are you from?', countries)).to eq('france')
26
+ end
27
+
28
+ it 'ask for a choice and return the default' do
29
+ expects_scan('Where are you from? [usa,france,GERMANY,italy]', '')
30
+ expect(subject.choose('Where are you from?', countries, default: 'germany')).to eq('germany')
31
+ end
32
+
33
+ it 'ask for a choice and user can reply with initials' do
34
+ expects_scan('Where are you from? [ufgi]', 'i')
35
+ expect(subject.choose('Where are you from?', countries, allow_initials: true)).to eq('italy')
36
+ end
37
+ end
38
+
39
+ describe '#yes_or_no' do
40
+ it 'asks the user and returns true if the user replies y' do
41
+ expects_scan('Is earth round? [yn]', 'y')
42
+ expect(subject.yes_or_no('Is earth round?')).to be true
43
+ end
44
+
45
+ it 'asks the user and returns true if the user replies yes' do
46
+ expects_scan('Is earth round? [yn]', 'yes')
47
+ expect(subject.yes_or_no('Is earth round?')).to be true
48
+ end
49
+
50
+ it 'asks the user and returns false if the user replies n' do
51
+ expects_scan('Is earth flat? [yn]', 'n')
52
+ expect(subject.yes_or_no('Is earth flat?')).to be false
53
+ end
54
+
55
+ it 'asks the user and returns false if the user replies no' do
56
+ expects_scan('Is earth flat? [yn]', 'no')
57
+ expect(subject.yes_or_no('Is earth flat?')).to be false
58
+ end
59
+
60
+ it 'asks the user and returns true if the user replies nothing' do
61
+ expects_scan('Is earth round? [Yn]', '')
62
+ expect(subject.yes_or_no('Is earth round?', default: 'yes')).to be true
63
+ end
64
+
65
+ it 'ask the user only once when he reply always' do
66
+ expects_scan('Is earth round? [yna]', 'a')
67
+ expect(subject.yes_or_no('Is earth round?', persist: true)).to be true
68
+ expect(subject.yes_or_no('Is earth round?', persist: true)).to be true
69
+ expect(subject.yes_or_no('Is earth round?', persist: true)).to be true
70
+ end
71
+ end
72
+
73
+ describe '#yes?' do
74
+ it 'asks the user and returns true if the user replies yes' do
75
+ expects_scan('Is earth round? [Yn]', 'y')
76
+ expect(subject.yes?('Is earth round?')).to be true
77
+ end
78
+
79
+ it 'asks the user and returns false if the user replies no' do
80
+ expects_scan('Is earth flat? [Yn]', 'n')
81
+ expect(subject.yes?('Is earth flat?')).to be false
82
+ end
83
+
84
+ it 'ask the user and return true if the user replies nothing' do
85
+ expects_scan('Is the earth not flat? [Yn]', '')
86
+ expect(subject.yes?('Is the earth not flat?')).to be true
87
+ end
88
+ end
89
+
90
+ describe '#no?' do
91
+ it 'asks the user and returns true if the user replies yes' do
92
+ expects_scan('Is earth round? [yN]', 'y')
93
+ expect(subject.no?('Is earth round?')).to be true
94
+ end
95
+
96
+ it 'asks the user and returns false if the user replies no' do
97
+ expects_scan('Is earth flat? [yN]', 'n')
98
+ expect(subject.no?('Is earth flat?')).to be false
99
+ end
100
+
101
+ it 'ask the user and return false if the user replies nothing' do
102
+ expects_scan('Is the earth flat? [yN]', '')
103
+ expect(subject.no?('Is the earth flat?')).to be false
104
+ end
105
+ end
106
+
107
+ describe '#overwrite?' do
108
+ it 'ask the user and return true when he reply yes' do
109
+ expects_scan("Overwrite 'some.txt'? [Ynaqh]", 'y')
110
+ expect(subject.overwrite?('some.txt')).to be true
111
+ end
112
+
113
+ it 'ask the user and return false when he reply false' do
114
+ expects_scan("Overwrite 'some.txt'? [Ynaqh]", 'n')
115
+ expect(subject.overwrite?('some.txt')).to be false
116
+ end
117
+
118
+ it 'ask the user only once when he reply always' do
119
+ expects_scan("Overwrite 'some1.txt'? [Ynaqh]", 'a')
120
+ expect(subject.overwrite?('some1.txt')).to be true
121
+ expect(subject.overwrite?('some2.txt')).to be true
122
+ expect(subject.overwrite?('some3.txt')).to be true
123
+ end
124
+
125
+ it 'ask the user and quit when he reply quit' do
126
+ expects_scan("Overwrite 'some.txt'? [Ynaqh]", 'q')
127
+ expect { subject.overwrite?('some.txt') }.to raise_error(SystemExit)
128
+ end
129
+
130
+ it 'ask the user and show diff when he reply diff' do
131
+ expects_scan("Overwrite 'some.txt'? [Ynaqdh]", 'd', 'y')
132
+ expect_any_instance_of(Clin::ShellInteraction::FileConflict)
133
+ .to receive(:show_diff).with('some.txt', 'new_text')
134
+ result = subject.overwrite?('some.txt') do
135
+ 'new_text'
136
+ end
137
+ expect(result).to be true
138
+ end
139
+ end
140
+
141
+ describe '#keep?' do
142
+ it 'ask the user and return true when he reply yes' do
143
+ expects_scan("Overwrite 'some.txt'? [yNaqh]", '')
144
+ expect(subject.keep?('some.txt')).to be false
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+ require 'examples/auto_option'
3
+
4
+ RSpec.describe 'auto_option.rb' do
5
+ suppress_puts
6
+ it { expect(AutoOptionCommand.parse('-e Lorem').params).to eq(echo: 'Lorem') }
7
+ it { expect(AutoOptionCommand.parse('--eko Lorem').params).to eq(echo: 'Lorem') }
8
+ it { expect(AutoOptionCommand.parse('--eko="Lorem ipsum"').params).to eq(echo: 'Lorem ipsum') }
9
+
10
+ it { expect { AutoOptionCommand.parse('--echo Value').params }.to raise_error(Clin::HelpError) }
11
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: clin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Timothee Guerin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-28 00:00:00.000000000 Z
11
+ date: 2015-06-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: '3.0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: '0'
68
+ version: '3.0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: coveralls
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -111,6 +111,7 @@ files:
111
111
  - README.md
112
112
  - Rakefile
113
113
  - clin.gemspec
114
+ - examples/auto_option.rb
114
115
  - examples/dispatcher.rb
115
116
  - examples/list_option.rb
116
117
  - examples/nested_dispatcher.rb
@@ -121,23 +122,35 @@ files:
121
122
  - lib/clin/argument.rb
122
123
  - lib/clin/command.rb
123
124
  - lib/clin/command_dispatcher.rb
124
- - lib/clin/command_options_mixin.rb
125
+ - lib/clin/command_mixin.rb
126
+ - lib/clin/command_mixin/core.rb
127
+ - lib/clin/command_mixin/dispatcher.rb
128
+ - lib/clin/command_mixin/options.rb
125
129
  - lib/clin/command_parser.rb
126
130
  - lib/clin/common/help_options.rb
127
131
  - lib/clin/errors.rb
128
132
  - lib/clin/general_option.rb
129
133
  - lib/clin/option.rb
130
134
  - lib/clin/option_list.rb
135
+ - lib/clin/shell.rb
136
+ - lib/clin/shell_interaction.rb
137
+ - lib/clin/shell_interaction/choose.rb
138
+ - lib/clin/shell_interaction/file_conflict.rb
139
+ - lib/clin/shell_interaction/yes_or_no.rb
131
140
  - lib/clin/version.rb
132
141
  - spec/clin/argument_spec.rb
133
142
  - spec/clin/command_dispacher_spec.rb
134
- - spec/clin/command_options_mixin_spec.rb
143
+ - spec/clin/command_mixin/core_spec.rb
144
+ - spec/clin/command_mixin/options_spec.rb
135
145
  - spec/clin/command_parser_spec.rb
136
146
  - spec/clin/command_spec.rb
137
147
  - spec/clin/common/help_options_spec.rb
138
148
  - spec/clin/option_list_spec.rb
139
149
  - spec/clin/option_spec.rb
150
+ - spec/clin/shell_interaction/choose_spec.rb
151
+ - spec/clin/shell_spec.rb
140
152
  - spec/errors_spec.rb
153
+ - spec/examples/auto_option_spec.rb
141
154
  - spec/examples/list_option_spec.rb
142
155
  - spec/examples/nested_dispatcher_spec.rb
143
156
  - spec/examples/simple_spec.rb
@@ -169,13 +182,17 @@ summary: Clin provide an advance way to define complex command line interface.
169
182
  test_files:
170
183
  - spec/clin/argument_spec.rb
171
184
  - spec/clin/command_dispacher_spec.rb
172
- - spec/clin/command_options_mixin_spec.rb
185
+ - spec/clin/command_mixin/core_spec.rb
186
+ - spec/clin/command_mixin/options_spec.rb
173
187
  - spec/clin/command_parser_spec.rb
174
188
  - spec/clin/command_spec.rb
175
189
  - spec/clin/common/help_options_spec.rb
176
190
  - spec/clin/option_list_spec.rb
177
191
  - spec/clin/option_spec.rb
192
+ - spec/clin/shell_interaction/choose_spec.rb
193
+ - spec/clin/shell_spec.rb
178
194
  - spec/errors_spec.rb
195
+ - spec/examples/auto_option_spec.rb
179
196
  - spec/examples/list_option_spec.rb
180
197
  - spec/examples/nested_dispatcher_spec.rb
181
198
  - spec/examples/simple_spec.rb
@@ -1,118 +0,0 @@
1
- require 'clin'
2
- require 'clin/option'
3
- require 'clin/option_list'
4
-
5
- # Template class for reusable options and commands
6
- # It provide the method to add options to a command
7
- class Clin::CommandOptionsMixin
8
- class_attribute :options
9
- class_attribute :general_options
10
- self.options = []
11
- self.general_options = {}
12
-
13
- # Add an option
14
- # @param args list of arguments.
15
- # * First argument must be the name if no block is given.
16
- # It will set automatically read the value into the hash with +name+ as key
17
- # * The remaining arguments are OptionsParser#on arguments
18
- # ```
19
- # option :require, '-r', '--require [LIBRARY]', 'Require the library'
20
- # option '-h', '--helper', 'Show the help' do
21
- # puts opts
22
- # exit
23
- # end
24
- # ```
25
- def self.opt_option(*args, &block)
26
- add_option Clin::Option.new(*args, &block)
27
- end
28
-
29
- # Add an option.
30
- # Helper method that just create a new Clin::Option with the argument then call add_option
31
- # ```
32
- # option :show, 'Show some message'
33
- # # => -s --show SHOW Show some message
34
- # option :require, 'Require a library', short: false, optional: true, argument: 'LIBRARY'
35
- # # => --require [LIBRARY] Require a library
36
- # option :help, 'Show the help', argument: false do
37
- # puts opts
38
- # exit
39
- # end
40
- # # => -h --help Show the help
41
- # ```
42
- def self.option(name, description, **config, &block)
43
- add_option Clin::Option.new(name, description, **config, &block)
44
- end
45
-
46
- # For an option that does not have an argument
47
- # Same as .option except it will default argument to false
48
- # ```
49
- # option :verbose, 'Use verbose' #=> -v --verbose will be added to the option of this command
50
- # ```
51
- def self.flag_option(name, description, **config, &block)
52
- add_option Clin::Option.new(name, description, **config.merge(argument: false), &block)
53
- end
54
-
55
- # Add a list option.
56
- # @see Clin::OptionList#initialize
57
- def self.list_option(name, description, **config)
58
- add_option Clin::OptionList.new(name, description, **config)
59
- end
60
-
61
- # Add a list options that don't take arguments
62
- # Same as .list_option but set +argument+ to false
63
- # @see Clin::OptionList#initialize
64
- def self.list_flag_option(name, description, **config)
65
- add_option Clin::OptionList.new(name, description, **config.merge(argument: false))
66
- end
67
-
68
- # Add a new option.
69
- # @param option [Clin::Option] option to add.
70
- def self.add_option(option)
71
- # Need to use += instead of << otherwise the parent class will also be changed
72
- self.options += [option]
73
- end
74
-
75
- # Add a general option
76
- # @param option_cls [Class<GeneralOption>] Class inherited from GeneralOption
77
- # @param config [Hash] General option config. Check the general option config.
78
- def self.general_option(option_cls, config = {})
79
- option_cls = option_cls.constantize if option_cls.is_a? String
80
- self.general_options = general_options.merge(option_cls => option_cls.new(config))
81
- end
82
-
83
- # Remove a general option
84
- # Might be useful if a parent added the option but is not needed in this child.
85
- def self.remove_general_option(option_cls)
86
- option_cls = option_cls.constantize if option_cls.is_a? String
87
- self.general_options = general_options.except(option_cls)
88
- end
89
-
90
- # To be called inside OptionParser block
91
- # Extract the option in the command line using the OptionParser and map it to the out map.
92
- # @param opts [OptionParser]
93
- # @param out [Hash] Where the options shall be extracted
94
- def self.register_options(opts, out)
95
- options.each do |option|
96
- option.register(opts, out)
97
- end
98
-
99
- general_options.each do |_cls, option|
100
- option.class.register_options(opts, out)
101
- end
102
- end
103
-
104
- # Call #execute on each of the general options.
105
- # This is called during the command initialization
106
- # e.g. A verbose general option execute would be:
107
- # ```
108
- # def execute(params)
109
- # MyApp.verbose = true if params[:verbose]
110
- # end
111
- # ```
112
- def self.execute_general_options(options)
113
- general_options.each do |_cls, gopts|
114
- gopts.execute(options)
115
- end
116
- end
117
-
118
- end