clin 0.2.0 → 0.3.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.
@@ -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