cli-modular_options 0.0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5b7e5cd4033fe5f65cc4058d851f2b37ecac55bf
4
+ data.tar.gz: 76f08750cf87f2ffa3834bfc9b09639a6783780b
5
+ SHA512:
6
+ metadata.gz: 79a6af03599049f0210d6156facac70150d4c07b77354795ae12c868e2e4680afb9a436abb49dc4ea00a0bac45ca62620b910a58959556202ab75114fda4cb11
7
+ data.tar.gz: d05bfefea65b7bed219f580b6deaee53dd667fd1ddb6ca284eda58ba2233150fedb279d5565b22cd809e9320cd72eebc07adbae146d9166242c77ae88aa5ee1f
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Erik Elmore
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,96 @@
1
+ Modular CLI Options
2
+ ===================
3
+
4
+ Facilitates modular application design by allowing you to declare command-line
5
+ options in the context of a class or module and consume them in the context of
6
+ an object instance using conventional ruby inheritance semantics.
7
+
8
+ ### Install:
9
+
10
+ gem install cli-modular_options
11
+
12
+ ### Example:
13
+ ```ruby
14
+ require 'cli/modular_options'
15
+
16
+ module DatabaseStuff
17
+ extend CLI::WithOptions
18
+
19
+ # Your mixin methods might go here
20
+
21
+ cli_options do |parser|
22
+ p.separator 'Database Options:'
23
+ p.on '--db-user', 'Database username' do |v|
24
+ cli_opts[:db_user] = v
25
+ end
26
+ p.on '--db-pass', 'Database password' do |v|
27
+ cli_opts[:db_pass] = v
28
+ end
29
+ end
30
+ end
31
+
32
+ module StandardOptions
33
+ extend CLI::WithOptions
34
+ cli_options do |parser|
35
+ p.on_head '-h', '--help', 'Display help' do
36
+ cli_opts[:show_help] = true
37
+ cli_opts[:parser] = parser
38
+ end
39
+ end
40
+ end
41
+
42
+ class MyApp
43
+ include CLI::ModularOptions
44
+ include DatabaseStuff
45
+ include StandardOptions
46
+
47
+ def show_help
48
+ puts cli_opts[:parser]
49
+ Process.exit 0
50
+ end
51
+
52
+ def initialize( argv = ARGV.map(&:dup) )
53
+ parse_options! argv
54
+ # Your options are available in cli_opts
55
+ show_help if cli_opts[:show_help]
56
+ end
57
+ end
58
+
59
+ # You can inherit options in a subclass
60
+ class MyDerivedApp < MyApp
61
+ # Maybe you prefer to let the caller decide what to do about --help
62
+ def show_help
63
+ throw :help, cli_opts[:parser]
64
+ end
65
+ end
66
+
67
+ # You can append more options directly to a class
68
+ class MyVerboseApp < MyApp
69
+ extend CLI::WithOptions
70
+ cli_options do |parser|
71
+ parser.separator 'Options added by MyVerboseApp:'
72
+ cli_opts[:verbose] = false
73
+ parser.on '--verbose', 'Enable verbose output' do
74
+ cli_opts[:verbose] = true
75
+ end
76
+ end
77
+ end
78
+
79
+ # The order in which callbacks are invoked is well-defined.
80
+ # Your cli_options blocks are called in a natural order that aligns
81
+ # with normal ruby inheritance semantics.
82
+ class MyQuietApp < MyVerboseApp
83
+ cli_options do |parser|
84
+ # This appears on the list of options after those declared in MyVerboseApp
85
+ parser.on '--no-verbose', 'Disable verbose output' do
86
+ cli_opts[:verbose] = false
87
+ end
88
+ end
89
+ end
90
+ ```
91
+
92
+ ### Copyrights
93
+ Copyright (C) 2014 Erik Elmore <erik@erikelmore.com>
94
+
95
+ ### License
96
+ MIT License. See LICENSE file for full text.
@@ -0,0 +1,44 @@
1
+ require 'optparse'
2
+
3
+ module CLI
4
+
5
+ module WithOptions
6
+ def cli_options( &block )
7
+ raise ArgumentError, 'Block required but not given' unless block_given?
8
+ const_set :CLI_OPTS_HOOKS, Array.new unless const_defined? :CLI_OPTS_HOOKS, false
9
+ const_get(:CLI_OPTS_HOOKS) << block
10
+ end
11
+ end # WithOptions
12
+
13
+ module ModularOptions
14
+ def cli_opts
15
+ @cli_opts ||= Hash.new
16
+ end
17
+
18
+ def new_options_parser
19
+ OptionParser.new do |p|
20
+ self.class.cli_hooks.each{ |b| instance_exec p, cli_opts, &b }
21
+ end
22
+ end
23
+
24
+ def parse_options!( argv )
25
+ cli_opts[:positional] = new_options_parser.parse! argv
26
+ end
27
+
28
+ def self.included( base )
29
+ base.extend ClassMethods
30
+ super
31
+ end
32
+
33
+ module ClassMethods
34
+ def ancestors_with_cli_hooks
35
+ ancestors.select{ |m| m.const_defined? :CLI_OPTS_HOOKS, false }
36
+ end
37
+
38
+ def cli_hooks
39
+ ancestors_with_cli_hooks.map{ |m| m::CLI_OPTS_HOOKS }.reverse.flatten
40
+ end
41
+ end # ClassMethods
42
+ end # ModularOptions
43
+
44
+ end # CLI
@@ -0,0 +1,58 @@
1
+ require 'cli/modular_options'
2
+
3
+ module TestModel
4
+ def self.new_cli_hook( mod, name )
5
+ k = name.to_s.downcase.to_sym
6
+ mod.instance_eval do
7
+ cli_options do |p, cfg|
8
+ cfg[:context] << self.class
9
+ cfg[:call_order] << name.to_s
10
+ cfg[k] = false
11
+ p.on "--#{k}" do
12
+ cfg[k] = true
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ def self.new_module( args = {} )
19
+ args = {
20
+ :name => 'TestMod',
21
+ :add_options => true
22
+ }.merge args
23
+ Module.new do
24
+ extend CLI::WithOptions
25
+ TestModel.new_cli_hook self, args[:name] if args[:add_options]
26
+ end
27
+ end
28
+
29
+ def self.new_class( args = {} )
30
+ args = {
31
+ :base => Object,
32
+ :modular_options => false,
33
+ :with_options => false,
34
+ :name => 'TestClass',
35
+ :feature_modules => Array.new
36
+ }.merge args
37
+
38
+ Class.new args[:base] {
39
+ if args[:modular_options]
40
+ include CLI::ModularOptions
41
+ def initialize( argv = [] )
42
+ cli_opts[:context] = Array.new
43
+ cli_opts[:call_order] = Array.new
44
+ parse_options! argv
45
+ end
46
+ end
47
+
48
+ args[:feature_modules].each do |m|
49
+ include m.kind_of?(Module)? m : TestModel.new_module(:name => m)
50
+ end
51
+
52
+ if args[:with_options]
53
+ extend CLI::WithOptions
54
+ TestModel.new_cli_hook self, args[:name]
55
+ end
56
+ }
57
+ end
58
+ end
@@ -0,0 +1,248 @@
1
+ require 'cli/modular_options'
2
+ require 'helpers/test_model'
3
+
4
+ describe CLI::ModularOptions do
5
+ shared_examples_for TestModel do
6
+ it 'Inherits included cli_hooks' do
7
+ expect(klass.cli_hooks.size).to be call_order.size
8
+ end
9
+
10
+ it 'Invokes cli_hooks in the context of self' do
11
+ expect(klass.new.cli_opts[:context]).to all be(klass)
12
+ end
13
+
14
+ it 'Invokes cli_hooks in the correct order' do
15
+ expect(klass.new.cli_opts[:call_order]).to eq call_order
16
+ end
17
+ end
18
+
19
+ context 'When no options' do
20
+ let(:klass){ TestModel.new_class modular_options: true }
21
+ let(:call_order){ Array.new }
22
+ it_behaves_like TestModel
23
+ end
24
+
25
+ context 'When included modules have options' do
26
+ let(:klass){ TestModel.new_class(
27
+ :modular_options => true,
28
+ :feature_modules => ['One', 'Two', 'Three']
29
+ )}
30
+ let(:call_order){ ['One', 'Two', 'Three'] }
31
+ it_behaves_like TestModel
32
+ end
33
+
34
+ context 'When both a class and its included modules have options' do
35
+ let(:klass){ TestModel.new_class(
36
+ :name => 'Base',
37
+ :modular_options => true,
38
+ :with_options => true,
39
+ :feature_modules => ['One', 'Two', 'Three']
40
+ )}
41
+ let(:call_order){ ['One', 'Two', 'Three', 'Base'] }
42
+ it_behaves_like TestModel
43
+ end
44
+
45
+ context 'When base class has options' do
46
+ let(:klass){ TestModel.new_class(
47
+ :name => 'Sub',
48
+ :base => TestModel.new_class(
49
+ :name => 'Base',
50
+ :modular_options => true,
51
+ :with_options => true
52
+ )
53
+ )}
54
+ let(:call_order){ ['Base'] }
55
+ it_behaves_like TestModel
56
+ end
57
+
58
+ context 'When base class includes modules with options' do
59
+ let(:klass){ TestModel.new_class(
60
+ :base => TestModel.new_class(
61
+ :modular_options => true,
62
+ :feature_modules => ['One', 'Two', 'Three']
63
+ )
64
+ )}
65
+ let(:call_order){ ['One', 'Two', 'Three'] }
66
+ it_behaves_like TestModel
67
+ end
68
+
69
+ context 'When base class both has and includes options' do
70
+ let(:klass){ TestModel.new_class(
71
+ :name => 'Sub',
72
+ :base => TestModel.new_class(
73
+ :name => 'Base',
74
+ :modular_options => true,
75
+ :with_options => true,
76
+ :feature_modules => ['One', 'Two', 'Three']
77
+ )
78
+ )}
79
+ let(:call_order){ ['One', 'Two', 'Three', 'Base'] }
80
+ it_behaves_like TestModel
81
+ end
82
+
83
+ context 'When both base class and derived class have and include options' do
84
+ let(:klass){ TestModel.new_class(
85
+ :name => 'Sub',
86
+ :with_options => true,
87
+ :feature_modules => ['Four', 'Five', 'Six'],
88
+ :base => TestModel.new_class(
89
+ :name => 'Base',
90
+ :modular_options => true,
91
+ :with_options => true,
92
+ :feature_modules => ['One', 'Two', 'Three']
93
+ )
94
+ )}
95
+ let(:call_order){ Array[
96
+ 'One', 'Two', 'Three', 'Base',
97
+ 'Four', 'Five', 'Six', 'Sub'
98
+ ]}
99
+ it_behaves_like TestModel
100
+ end
101
+
102
+ context 'When re-opening base class to include module with options' do
103
+ let(:klass){
104
+ base = TestModel.new_class(
105
+ :name => 'Base',
106
+ :modular_options => true,
107
+ :with_options => true,
108
+ :feature_modules => ['One', 'Two', 'Three']
109
+ )
110
+ sub = TestModel.new_class(
111
+ :name => 'Sub',
112
+ :with_options => true,
113
+ :feature_modules => ['Four', 'Five', 'Six'],
114
+ :base => base
115
+ )
116
+ base.send :include, TestModel.new_module(:name => 'Added')
117
+ sub
118
+ }
119
+ let(:call_order){ Array[
120
+ 'One', 'Two', 'Three', 'Added', 'Base',
121
+ 'Four', 'Five', 'Six', 'Sub'
122
+ ]}
123
+ it_behaves_like TestModel
124
+ end
125
+
126
+ context 'When re-opening base class to declare options directly' do
127
+ let(:klass){
128
+ base = TestModel.new_class(
129
+ :name => 'Base',
130
+ :modular_options => true,
131
+ :with_options => true,
132
+ :feature_modules => ['One', 'Two', 'Three']
133
+ )
134
+ sub = TestModel.new_class(
135
+ :name => 'Sub',
136
+ :with_options => true,
137
+ :feature_modules => ['Four', 'Five', 'Six'],
138
+ :base => base
139
+ )
140
+ TestModel.new_cli_hook base, 'BaseAgain'
141
+ sub
142
+ }
143
+ let(:call_order){ Array[
144
+ 'One', 'Two', 'Three', 'Base', 'BaseAgain',
145
+ 'Four', 'Five', 'Six', 'Sub'
146
+ ]}
147
+ it_behaves_like TestModel
148
+ end
149
+
150
+ context 'When re-opening derived class to include module with options' do
151
+ let(:klass){
152
+ base = TestModel.new_class(
153
+ :name => 'Base',
154
+ :modular_options => true,
155
+ :with_options => true,
156
+ :feature_modules => ['One', 'Two', 'Three']
157
+ )
158
+ sub = TestModel.new_class(
159
+ :name => 'Sub',
160
+ :with_options => true,
161
+ :feature_modules => ['Four', 'Five', 'Six'],
162
+ :base => base
163
+ )
164
+ sub.send :include, TestModel.new_module(:name => 'Added')
165
+ sub
166
+ }
167
+ let(:call_order){ Array[
168
+ 'One', 'Two', 'Three', 'Base',
169
+ 'Four', 'Five', 'Six', 'Added', 'Sub'
170
+ ]}
171
+ it_behaves_like TestModel
172
+ end
173
+
174
+ context 'When re-opening derived class to declare options directly' do
175
+ let(:klass){
176
+ base = TestModel.new_class(
177
+ :name => 'Base',
178
+ :modular_options => true,
179
+ :with_options => true,
180
+ :feature_modules => ['One', 'Two', 'Three']
181
+ )
182
+ sub = TestModel.new_class(
183
+ :name => 'Sub',
184
+ :with_options => true,
185
+ :feature_modules => ['Four', 'Five', 'Six'],
186
+ :base => base
187
+ )
188
+ TestModel.new_cli_hook sub, 'SubAgain'
189
+ sub
190
+ }
191
+ let(:call_order){ Array[
192
+ 'One', 'Two', 'Three', 'Base',
193
+ 'Four', 'Five', 'Six', 'Sub', 'SubAgain'
194
+ ]}
195
+ it_behaves_like TestModel
196
+ end
197
+
198
+ context 'When adding cli_hooks to module already included by base class' do
199
+ let(:klass){
200
+ mod = TestModel.new_module :name => 'Two'
201
+ base = TestModel.new_class(
202
+ :name => 'Base',
203
+ :modular_options => true,
204
+ :with_options => true,
205
+ :feature_modules => ['One', mod, 'Three']
206
+ )
207
+ sub = TestModel.new_class(
208
+ :name => 'Sub',
209
+ :with_options => true,
210
+ :feature_modules => ['Four', 'Five', 'Six'],
211
+ :base => base
212
+ )
213
+ TestModel.new_cli_hook mod, 'TwoAgain'
214
+ sub
215
+ }
216
+ let(:call_order){ Array[
217
+ 'One', 'Two', 'TwoAgain', 'Three', 'Base',
218
+ 'Four', 'Five', 'Six', 'Sub'
219
+ ]}
220
+ it_behaves_like TestModel
221
+ end
222
+
223
+ context 'When adding cli_hooks to module already included by derived class' do
224
+ let(:klass){
225
+ mod = TestModel.new_module :name => 'Five'
226
+ base = TestModel.new_class(
227
+ :name => 'Base',
228
+ :modular_options => true,
229
+ :with_options => true,
230
+ :feature_modules => ['One', 'Two', 'Three']
231
+ )
232
+ sub = TestModel.new_class(
233
+ :name => 'Sub',
234
+ :with_options => true,
235
+ :feature_modules => ['Four', mod, 'Six'],
236
+ :base => base
237
+ )
238
+ TestModel.new_cli_hook mod, 'FiveAgain'
239
+ sub
240
+ }
241
+ let(:call_order){ Array[
242
+ 'One', 'Two', 'Three', 'Base',
243
+ 'Four', 'Five', 'FiveAgain', 'Six', 'Sub'
244
+ ]}
245
+ it_behaves_like TestModel
246
+ end
247
+
248
+ end
@@ -0,0 +1,2 @@
1
+ RSpec.configure do |config|
2
+ end
@@ -0,0 +1,24 @@
1
+ require 'cli/modular_options'
2
+ require 'helpers/test_model'
3
+
4
+ describe CLI::WithOptions do
5
+ let(:mod){ TestModel.new_module :add_options => false }
6
+
7
+ it "Doesn't create CLI_OPTS_HOOKS until necessary" do
8
+ expect(mod.const_defined? :CLI_OPTS_HOOKS).to eq false
9
+ end
10
+
11
+ it 'Requires a block to be given with cli_options' do
12
+ expect{ mod.cli_options }.to raise_error ArgumentError
13
+ end
14
+
15
+ it 'Adds hooks to modules' do
16
+ mod.cli_options do; end
17
+ expect(mod::CLI_OPTS_HOOKS).to be_an Array
18
+ end
19
+
20
+ it 'Adds more hooks to modules' do
21
+ 5.times{ mod.cli_options do; end }
22
+ expect(mod::CLI_OPTS_HOOKS.size).to be 5
23
+ end
24
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cli-modular_options
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Erik Elmore
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3'
27
+ description: Facilitates modular application design by allowing you to declare CLI
28
+ options in the context of a class or module and consume them in the context of an
29
+ object instance using conventional ruby inheritance semantics.
30
+ email: erik@erikelmore.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - LICENSE
36
+ - README.md
37
+ - lib/cli/modular_options.rb
38
+ - spec/helpers/test_model.rb
39
+ - spec/modular_options_spec.rb
40
+ - spec/spec_helper.rb
41
+ - spec/with_options_spec.rb
42
+ homepage: https://github.com/IronSavior/ModularOptions
43
+ licenses:
44
+ - MIT
45
+ metadata: {}
46
+ post_install_message:
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 1.9.3
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ requirements: []
61
+ rubyforge_project:
62
+ rubygems_version: 2.2.2
63
+ signing_key:
64
+ specification_version: 4
65
+ summary: Modular Command-Line Options
66
+ test_files:
67
+ - spec/spec_helper.rb
68
+ - spec/modular_options_spec.rb
69
+ - spec/helpers/test_model.rb
70
+ - spec/with_options_spec.rb