ejt_command_line 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MDQzM2QzNDU0NGE4MzhiOWZhMDc1ZGE0YjA3MDQzZTNkMDQ3Y2MzZA==
5
+ data.tar.gz: !binary |-
6
+ NmIxZDE2MTE0ZjZiNzQ5OWI4MjFhZTU0MGQwZTU2M2IyMDYxZTBlNA==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ NDQ2MDc2ZmEzZjY2ODZjNTAyNTQwNzNlMjM3YThjYWFiM2IwNjc0ZmQwNGQ4
10
+ MDdhODFjYjM4YTk5N2M1ZWM3ODRjNGEzZGM2YmRiMzU1MTNiYjRlNjEwN2U2
11
+ YTQ2MGRmOGMyM2FhMDI4ZTU4NjkyMjBmZGM0NTJiYjQ4OTkwMzg=
12
+ data.tar.gz: !binary |-
13
+ NTdlNzU2NTYwMmNiY2RmZDIxM2I5ZjNiYmZiOWJkZDY3ODc2ZTA0OGZhYTJh
14
+ MWY5MDllZDNkNWVlZjVjYzZlNjE5Y2NjN2UzMDU4YTM2NDBkNGI0ODZkNzAz
15
+ YWU2OTJlYWI4ZGYzMTViMjIxNzcwYWQzMWQ3MjU1YTdmMzc2NTU=
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ejt_command_line.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Joe Thornber
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # EjtCommandLine
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'ejt_command_line'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install ejt_command_line
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new('spec')
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ejt_command_line/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "ejt_command_line"
8
+ spec.version = EjtCommandLine::VERSION
9
+ spec.authors = ["Joe Thornber"]
10
+ spec.email = ["ejt@redhat.com"]
11
+ spec.description = %q{Yet another command line parser. Allows you define argument types}
12
+ spec.summary = %q{Command line parser}
13
+ spec.homepage = ""
14
+ spec.license = "GPL"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+ end
@@ -0,0 +1,3 @@
1
+ module EjtCommandLine
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,268 @@
1
+ require "ejt_command_line/version"
2
+ require 'set'
3
+
4
+ #----------------------------------------------------------------
5
+
6
+ module CommandLine
7
+ # from prelude
8
+ def bracket_(release)
9
+ r = nil
10
+ begin
11
+ r = yield
12
+ ensure
13
+ release.call
14
+ end
15
+ r
16
+ end
17
+
18
+ #----------------------------------------------------------------
19
+
20
+ class CommandLineError < StandardError
21
+ end
22
+
23
+ class ParseError < CommandLineError
24
+ end
25
+
26
+ class ConfigureError < CommandLineError
27
+ end
28
+
29
+ #----------------------------------------------------------------
30
+
31
+ def simple_switch_parser(*aliases)
32
+ ArrayParser::choice(*aliases)
33
+ end
34
+
35
+ def value_switch_parser(*aliases, &block)
36
+ p = sequence(choice(*aliases), value(&block))
37
+ end
38
+
39
+ #----------------------------------------------------------------
40
+
41
+ class Switch
42
+ attr_reader :flags, :parser
43
+
44
+ def initialize(flags, parser = nil)
45
+ @flags = flags
46
+ @parser = parser
47
+ end
48
+
49
+ def has_flag?(flag)
50
+ @flags.member?(flag)
51
+ end
52
+ end
53
+
54
+ class Command
55
+ attr_reader :switches
56
+
57
+ def initialize
58
+ @switches = []
59
+ @mutually_exclusive_sets = [] # list of lists of syms
60
+ @mandatory = []
61
+ end
62
+
63
+ def add_switches(syms)
64
+ @switches += syms
65
+ end
66
+
67
+ def add_mutually_exclusive_set(syms)
68
+ @mutually_exclusive_sets << syms.to_set
69
+ end
70
+
71
+ def add_mandatory_switch(sym)
72
+ @mandatory << sym
73
+ end
74
+
75
+ def check_mutual_exclusion(syms)
76
+ nr_sets = @mutually_exclusive_sets.size
77
+ set_counts = Array.new(nr_sets, [])
78
+
79
+ syms.each do |s|
80
+ # is it in an exclusive set?
81
+ 0.upto(nr_sets - 1) do |n|
82
+ if @mutually_exclusive_sets[n].member?(s)
83
+ set_counts[n] << s
84
+ end
85
+ end
86
+ end
87
+
88
+ 0.upto(nr_sets - 1) do |n|
89
+ if set_counts[n].size > 1
90
+ msg = "mutually exclusive options used:\n"
91
+ set_counts[n].each {|sym| msg += " #{sym}\n"}
92
+ raise ParseError, msg
93
+ end
94
+ end
95
+ end
96
+
97
+ def check_mandatory(syms)
98
+ missing = []
99
+
100
+ @mandatory.each do |m|
101
+ unless syms.member?(m)
102
+ missing << m
103
+ end
104
+ end
105
+
106
+ if missing.size > 0
107
+ msg = "missing mandatory switches:\n"
108
+ missing.each do |m|
109
+ msg += " #{m}\n"
110
+ end
111
+
112
+ raise ParseError, msg
113
+ end
114
+ end
115
+ end
116
+
117
+ class Parser
118
+ GLOBAL_SYM = :global__
119
+
120
+ def initialize(&block)
121
+ @switches = {}
122
+ @global_switches = []
123
+ @value_types = {}
124
+ @commands = Hash.new {|hash, key| Command.new}
125
+ @current_command = @commands[GLOBAL_SYM]
126
+
127
+ configure(&block) if block
128
+ end
129
+
130
+ def configure(&block)
131
+ self.instance_eval(&block)
132
+ end
133
+
134
+ def value_type(sym, &parser)
135
+ if @value_types.member?(sym)
136
+ raise ConfigureError, "duplicate value type '#{sym}'"
137
+ end
138
+
139
+ @value_types[sym] = parser
140
+ end
141
+
142
+ def simple_switch(sym, *flags)
143
+ @switches[sym] = Switch.new(flags)
144
+ end
145
+
146
+ def value_switch(sym, value_sym, *flags)
147
+ @switches[sym] = Switch.new(flags, get_value_parser(value_sym))
148
+ end
149
+
150
+ def global(&block)
151
+ command(GLOBAL_SYM, &block)
152
+ end
153
+
154
+ def command(sym, &block)
155
+ old = @current_command
156
+ @current_command = @commands[sym] = Command.new
157
+
158
+ if block
159
+ release = lambda {@current_command = old}
160
+ bracket_(release) do
161
+ self.instance_eval(&block)
162
+ end
163
+ end
164
+ end
165
+
166
+ def switches(*syms)
167
+ check_switches_are_defined(syms)
168
+ @current_command.add_switches(syms)
169
+ end
170
+
171
+ def one_of(*syms)
172
+ check_switches_are_defined(syms)
173
+ @current_command.add_switches(syms)
174
+ @current_command.add_mutually_exclusive_set(syms)
175
+ end
176
+
177
+ def mandatory(sym)
178
+ syms = [sym]
179
+ check_switches_are_defined(syms)
180
+ @current_command.add_switches(syms)
181
+ @current_command.add_mandatory_switch(sym)
182
+ end
183
+
184
+ def parse(handler, *args)
185
+ command, opts, plain_args = parse_(args)
186
+ handler.send(command, opts, plain_args)
187
+ end
188
+
189
+ private
190
+ def parse_value(arg, s, args)
191
+ if s.parser
192
+ if args.size == 0
193
+ raise ParseError, "no value specified for switch '#{arg}'"
194
+ end
195
+
196
+ value = args.shift
197
+ begin
198
+ s.parser.call(value)
199
+ rescue => e
200
+ raise ParseError, "couldn't parse value '#{arg}=#{value}'\n#{e}"
201
+ end
202
+ else
203
+ true
204
+ end
205
+ end
206
+
207
+ def parse_(args)
208
+ in_command = false
209
+ opts = {}
210
+ plain_args = []
211
+ valid_switches = @commands[GLOBAL_SYM].switches
212
+ command = :global_command
213
+
214
+ while args.size > 0 do
215
+ arg = args.shift
216
+
217
+ if arg =~ /^-/
218
+ sym, s = find_switch(valid_switches, arg)
219
+ opts[sym] = parse_value(arg, s, args)
220
+
221
+ else
222
+ cmd = arg.intern
223
+
224
+ if !in_command && @commands.member?(cmd)
225
+ command = cmd
226
+ valid_switches = @commands[cmd].switches
227
+ in_command = true
228
+ else
229
+ plain_args << arg
230
+ end
231
+ end
232
+ end
233
+
234
+ @commands[command].check_mutual_exclusion(opts.keys)
235
+ @commands[command].check_mandatory(opts.keys)
236
+ [command, opts, plain_args]
237
+ end
238
+
239
+ def check_switches_are_defined(syms)
240
+ syms.each do |sym|
241
+ raise ConfigureError, "unknown switch '#{sym}'" unless @switches.member?(sym)
242
+ end
243
+ end
244
+
245
+ def find_switch(valid_switches, switch)
246
+ catch :found do
247
+ valid_switches.each do |sym|
248
+ s = @switches[sym]
249
+ if s.has_flag?(switch)
250
+ throw :found, [sym, s]
251
+ end
252
+ end
253
+
254
+ raise ParseError, "unexpected switch '#{switch}'"
255
+ end
256
+ end
257
+
258
+ def get_value_parser(sym)
259
+ if @value_types.member?(sym)
260
+ @value_types[sym]
261
+ else
262
+ raise ConfigureError, "unknown value type '#{sym}'"
263
+ end
264
+ end
265
+ end
266
+ end
267
+
268
+ #----------------------------------------------------------------
@@ -0,0 +1,358 @@
1
+ require 'spec_helper'
2
+
3
+ include CommandLine
4
+
5
+ #----------------------------------------------------------------
6
+
7
+ describe "Parser" do
8
+ before :each do
9
+ @clh = Parser.new
10
+ end
11
+
12
+ def help_switch
13
+ @clh.simple_switch :help, '--help', '-h'
14
+ end
15
+
16
+ describe "creation" do
17
+ it "should take a config block" do
18
+ block_watcher = mock()
19
+ block_watcher.should_receive(:executed)
20
+
21
+ clh = Parser.new do
22
+ value_type :string do |str|
23
+ str
24
+ end
25
+
26
+ block_watcher.executed
27
+ end
28
+ end
29
+ end
30
+
31
+ describe "value types" do
32
+ it "should allow you to register new value types" do
33
+ @clh.configure do
34
+ value_type :string do |str|
35
+ str
36
+ end
37
+
38
+ value_type :int do |str|
39
+ str.to_i
40
+ end
41
+ end
42
+ end
43
+
44
+ it "should fail it you try and define a duplicate value type" do
45
+ @clh.value_type :string do |str|
46
+ str
47
+ end
48
+
49
+ expect do
50
+ @clh.value_type :string do |str|
51
+ str
52
+ end
53
+ end.to raise_error(ConfigureError, /string/)
54
+ end
55
+ end
56
+
57
+ describe "switches are defined separately from commands" do
58
+ it "should let you define binary switch" do
59
+ help_switch
60
+ end
61
+
62
+ it "should fail if you try and define a switch that takes an unknown value type" do
63
+ expect {@clh.value_switch :resize_to, :volume_size, '--resize-to'}.to raise_error(ConfigureError, /volume_size/)
64
+ end
65
+
66
+ it "should let you define an option that takes a single value" do
67
+ @clh.configure do
68
+ value_type :volume_size do |str|
69
+ str.to_i
70
+ end
71
+
72
+ value_switch :resize_to, :volume_size, '--resize-to'
73
+ end
74
+ end
75
+ end
76
+
77
+ describe "global switches" do
78
+ it "should let you bind global switch" do
79
+ help_switch
80
+ @clh.configure do
81
+ global do
82
+ switches :help
83
+ end
84
+ end
85
+ end
86
+
87
+ it "should raise an error if the switch hasn't been previously defined" do
88
+ expect do
89
+ @clh.global do
90
+ switches :become_sentient
91
+ end
92
+ end.to raise_error(ConfigureError)
93
+ end
94
+ end
95
+
96
+ describe "sub commands" do
97
+ it "should let you register a command" do
98
+ @clh.configure do
99
+ command(:create) {}
100
+ end
101
+ end
102
+
103
+ it "should let you omit the block for a command" do
104
+ @clh.configure do
105
+ command :create
106
+ end
107
+ end
108
+
109
+ it "should let you bind switches" do
110
+ @clh.configure do
111
+ simple_switch :grow_to, '--grow-to'
112
+ simple_switch :grow_by, '--grow-by'
113
+ simple_switch :shrink_to, '--shrink-to'
114
+ simple_switch :shrink_by, '--shrink-by'
115
+
116
+ command :resize do
117
+ switches :grow_to, :grow_by, :shrink_to, :shrink_by
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ describe "parsing" do
124
+ describe "global command" do
125
+ it "should handle no switches" do
126
+ handler = mock()
127
+ handler.should_receive(:global_command).with({}, [])
128
+ @clh.parse(handler)
129
+ end
130
+
131
+ it "should raise a ParseError if an unrecognised switch is used" do
132
+ handler = mock()
133
+ expect {@clh.parse(handler, '--go-back-in-time')}.to raise_error(ParseError, /--go-back-in-time/)
134
+ end
135
+
136
+ it "should handle binary switches" do
137
+ handler = mock()
138
+ handler.should_receive(:global_command).with({:help => true}, [])
139
+
140
+ @clh.configure do
141
+ simple_switch :help, '--help', '-h'
142
+ global do
143
+ switches :help
144
+ end
145
+ end
146
+
147
+ @clh.parse(handler, '-h')
148
+ end
149
+
150
+ it "should handle multiple binary switches" do
151
+ handler = mock()
152
+ handler.should_receive(:global_command).with({:help => true, :ro => true}, [])
153
+
154
+ @clh.configure do
155
+ simple_switch :help, '--help', '-h'
156
+ simple_switch :ro, '--read-only', '-r'
157
+
158
+ global do
159
+ switches :help, :ro
160
+ end
161
+ end
162
+
163
+ @clh.parse(handler, '-h', '--read-only')
164
+ end
165
+
166
+ it "should handle valued switches" do
167
+ handler = mock()
168
+
169
+ @clh.configure do
170
+ value_type :int do |str|
171
+ str.to_i
172
+ end
173
+
174
+ value_switch :count, :int, '--count', '-c'
175
+
176
+ global do
177
+ switches :count
178
+ end
179
+ end
180
+
181
+ handler.should_receive(:global_command).
182
+ with({:count => 17}, [])
183
+ @clh.parse(handler, '--count', '17')
184
+
185
+ handler.should_receive(:global_command).
186
+ with({:count => 17}, ['one', 'two'])
187
+ @clh.parse(handler, 'one', '-c', '17', 'two')
188
+ end
189
+
190
+ it "should raise an ArgumentError if no value is given for a valued switch" do
191
+ handler = mock()
192
+
193
+ @clh.configure do
194
+ value_type :int do |str|
195
+ str.to_i
196
+ end
197
+
198
+ value_switch :count, :int, '--count', '-c'
199
+
200
+ global do
201
+ switches :count
202
+ end
203
+ end
204
+
205
+ expect do
206
+ @clh.parse(handler, '--count')
207
+ end.to raise_error(ParseError, /count/)
208
+ end
209
+
210
+ it "should filter non-switches out" do
211
+ handler = mock()
212
+ handler.should_receive(:global_command).
213
+ with({:help => true, :ro => true}, ['my_file', 'my_other_file'])
214
+
215
+ @clh.configure do
216
+ simple_switch :help, '--help', '-h'
217
+ simple_switch :ro, '--read-only', '-r'
218
+
219
+ global do
220
+ switches :help, :ro
221
+ end
222
+ end
223
+
224
+ @clh.parse(handler, '-h', 'my_file', '--read-only', 'my_other_file')
225
+ end
226
+ end
227
+
228
+ describe "simple commands" do
229
+ it "should handle commands that take no switches" do
230
+ @clh.configure do
231
+ command :create do
232
+ end
233
+ end
234
+
235
+ handler = mock()
236
+ handler.should_receive(:create).with({}, ['fred'])
237
+ @clh.parse(handler, 'create', 'fred')
238
+ end
239
+ end
240
+
241
+ describe "commands" do
242
+ before :each do
243
+ @clh.configure do
244
+ value_type :int do |str|
245
+ str.to_i
246
+ end
247
+
248
+ value_switch :grow_to, :int, '--grow-to'
249
+ value_switch :grow_by, :int, '--grow-by'
250
+ value_switch :shrink_to, :int, '--shrink-to'
251
+ value_switch :shrink_by, :int, '--shrink-by'
252
+
253
+ command :resize do
254
+ switches :grow_to, :grow_by, :shrink_to, :shrink_by
255
+ end
256
+
257
+ command :shrink do
258
+ switches :grow_to, :grow_by, :shrink_to, :shrink_by
259
+ end
260
+
261
+ command :grow do
262
+ switches :grow_to, :grow_by, :shrink_to, :shrink_by
263
+ end
264
+ end
265
+ end
266
+
267
+ it "should allow you to define a sub command" do
268
+ handler = mock()
269
+ handler.should_receive(:resize).with({:grow_to => 12345}, ['fred'])
270
+ @clh.parse(handler, 'resize', '--grow-to', '12345', 'fred')
271
+ end
272
+
273
+ it "should prevent you calling two sub commands on the same line" do
274
+ handler = mock()
275
+ handler.should_receive(:resize).
276
+ with({:grow_to => 1234, :shrink_to => 2345}, ['shrink', 'fred'])
277
+ @clh.parse(handler, 'resize', '--grow-to', '1234', 'shrink', '--shrink-to', '2345', 'fred')
278
+ end
279
+ end
280
+
281
+ describe "exclusive switches" do
282
+ before :each do
283
+ @clh.configure do
284
+ value_type :int do |str|
285
+ str.to_i
286
+ end
287
+
288
+ value_switch :grow_to, :int, '--grow-to'
289
+ value_switch :grow_by, :int, '--grow-by'
290
+ value_switch :shrink_to, :int, '--shrink-to'
291
+ value_switch :shrink_by, :int, '--shrink-by'
292
+
293
+ command :resize do
294
+ one_of :grow_to, :grow_by, :shrink_to, :shrink_by
295
+ end
296
+ end
297
+ end
298
+
299
+ it "should parse one exclusive switch" do
300
+ handler = mock()
301
+ handler.should_receive(:resize).
302
+ with({:grow_to => 1234}, ['fred'])
303
+ @clh.parse(handler, 'resize', '--grow-to', '1234', 'fred')
304
+ end
305
+
306
+ it "should raise a ParseError if more than one switch from an exclusive set is defined" do
307
+ handler = mock()
308
+ expect do
309
+ @clh.parse(handler, 'resize', '--grow-to', '1234', '--shrink-by', '2345', 'fred')
310
+ end.to raise_error(ParseError, /mutually exclusive/)
311
+ # FIXME: would be nice to see the actual flags in the exception
312
+ end
313
+
314
+ it "should let you define more than one exclusive set" do
315
+ pending
316
+ end
317
+ end
318
+
319
+ it "should handle --foo=<value>" do
320
+ pending "todo"
321
+ end
322
+ end
323
+
324
+ describe "mandatory switches" do
325
+ before :each do
326
+ @clh.configure do
327
+ value_type :int do |str|
328
+ str.to_i
329
+ end
330
+
331
+ value_switch :grow_to, :int, '--grow-to'
332
+ value_switch :grow_by, :int, '--grow-by'
333
+ value_switch :shrink_to, :int, '--shrink-to'
334
+ value_switch :shrink_by, :int, '--shrink-by'
335
+
336
+ command :resize do
337
+ mandatory :grow_to
338
+ end
339
+ end
340
+ end
341
+
342
+ it "should parse ok if mandatory switch is given" do
343
+ handler = mock()
344
+ handler.should_receive(:resize).
345
+ with({:grow_to => 3}, ['fred'])
346
+ @clh.parse(handler, 'resize', '--grow-to', '3', 'fred')
347
+ end
348
+
349
+ it "should raise a ParseError if a mandatory switch is omitted" do
350
+ handler = mock()
351
+ expect do
352
+ @clh.parse(handler, 'resize', 'fred')
353
+ end.to raise_error(ParseError, /grow_to/)
354
+ end
355
+ end
356
+ end
357
+
358
+ #----------------------------------------------------------------
@@ -0,0 +1,359 @@
1
+ require 'spec_helper'
2
+ require 'lib/command_line'
3
+
4
+ include CommandLine
5
+
6
+ #----------------------------------------------------------------
7
+
8
+ describe "Parser" do
9
+ before :each do
10
+ @clh = Parser.new
11
+ end
12
+
13
+ def help_switch
14
+ @clh.simple_switch :help, '--help', '-h'
15
+ end
16
+
17
+ describe "creation" do
18
+ it "should take a config block" do
19
+ block_watcher = mock()
20
+ block_watcher.should_receive(:executed)
21
+
22
+ clh = Parser.new do
23
+ value_type :string do |str|
24
+ str
25
+ end
26
+
27
+ block_watcher.executed
28
+ end
29
+ end
30
+ end
31
+
32
+ describe "value types" do
33
+ it "should allow you to register new value types" do
34
+ @clh.configure do
35
+ value_type :string do |str|
36
+ str
37
+ end
38
+
39
+ value_type :int do |str|
40
+ str.to_i
41
+ end
42
+ end
43
+ end
44
+
45
+ it "should fail it you try and define a duplicate value type" do
46
+ @clh.value_type :string do |str|
47
+ str
48
+ end
49
+
50
+ expect do
51
+ @clh.value_type :string do |str|
52
+ str
53
+ end
54
+ end.to raise_error(ConfigureError, /string/)
55
+ end
56
+ end
57
+
58
+ describe "switches are defined separately from commands" do
59
+ it "should let you define binary switch" do
60
+ help_switch
61
+ end
62
+
63
+ it "should fail if you try and define a switch that takes an unknown value type" do
64
+ expect {@clh.value_switch :resize_to, :volume_size, '--resize-to'}.to raise_error(ConfigureError, /volume_size/)
65
+ end
66
+
67
+ it "should let you define an option that takes a single value" do
68
+ @clh.configure do
69
+ value_type :volume_size do |str|
70
+ str.to_i
71
+ end
72
+
73
+ value_switch :resize_to, :volume_size, '--resize-to'
74
+ end
75
+ end
76
+ end
77
+
78
+ describe "global switches" do
79
+ it "should let you bind global switch" do
80
+ help_switch
81
+ @clh.configure do
82
+ global do
83
+ switches :help
84
+ end
85
+ end
86
+ end
87
+
88
+ it "should raise an error if the switch hasn't been previously defined" do
89
+ expect do
90
+ @clh.global do
91
+ switches :become_sentient
92
+ end
93
+ end.to raise_error(ConfigureError)
94
+ end
95
+ end
96
+
97
+ describe "sub commands" do
98
+ it "should let you register a command" do
99
+ @clh.configure do
100
+ command(:create) {}
101
+ end
102
+ end
103
+
104
+ it "should let you omit the block for a command" do
105
+ @clh.configure do
106
+ command :create
107
+ end
108
+ end
109
+
110
+ it "should let you bind switches" do
111
+ @clh.configure do
112
+ simple_switch :grow_to, '--grow-to'
113
+ simple_switch :grow_by, '--grow-by'
114
+ simple_switch :shrink_to, '--shrink-to'
115
+ simple_switch :shrink_by, '--shrink-by'
116
+
117
+ command :resize do
118
+ switches :grow_to, :grow_by, :shrink_to, :shrink_by
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ describe "parsing" do
125
+ describe "global command" do
126
+ it "should handle no switches" do
127
+ handler = mock()
128
+ handler.should_receive(:global_command).with({}, [])
129
+ @clh.parse(handler)
130
+ end
131
+
132
+ it "should raise a ParseError if an unrecognised switch is used" do
133
+ handler = mock()
134
+ expect {@clh.parse(handler, '--go-back-in-time')}.to raise_error(ParseError, /--go-back-in-time/)
135
+ end
136
+
137
+ it "should handle binary switches" do
138
+ handler = mock()
139
+ handler.should_receive(:global_command).with({:help => true}, [])
140
+
141
+ @clh.configure do
142
+ simple_switch :help, '--help', '-h'
143
+ global do
144
+ switches :help
145
+ end
146
+ end
147
+
148
+ @clh.parse(handler, '-h')
149
+ end
150
+
151
+ it "should handle multiple binary switches" do
152
+ handler = mock()
153
+ handler.should_receive(:global_command).with({:help => true, :ro => true}, [])
154
+
155
+ @clh.configure do
156
+ simple_switch :help, '--help', '-h'
157
+ simple_switch :ro, '--read-only', '-r'
158
+
159
+ global do
160
+ switches :help, :ro
161
+ end
162
+ end
163
+
164
+ @clh.parse(handler, '-h', '--read-only')
165
+ end
166
+
167
+ it "should handle valued switches" do
168
+ handler = mock()
169
+
170
+ @clh.configure do
171
+ value_type :int do |str|
172
+ str.to_i
173
+ end
174
+
175
+ value_switch :count, :int, '--count', '-c'
176
+
177
+ global do
178
+ switches :count
179
+ end
180
+ end
181
+
182
+ handler.should_receive(:global_command).
183
+ with({:count => 17}, [])
184
+ @clh.parse(handler, '--count', '17')
185
+
186
+ handler.should_receive(:global_command).
187
+ with({:count => 17}, ['one', 'two'])
188
+ @clh.parse(handler, 'one', '-c', '17', 'two')
189
+ end
190
+
191
+ it "should raise an ArgumentError if no value is given for a valued switch" do
192
+ handler = mock()
193
+
194
+ @clh.configure do
195
+ value_type :int do |str|
196
+ str.to_i
197
+ end
198
+
199
+ value_switch :count, :int, '--count', '-c'
200
+
201
+ global do
202
+ switches :count
203
+ end
204
+ end
205
+
206
+ expect do
207
+ @clh.parse(handler, '--count')
208
+ end.to raise_error(ParseError, /count/)
209
+ end
210
+
211
+ it "should filter non-switches out" do
212
+ handler = mock()
213
+ handler.should_receive(:global_command).
214
+ with({:help => true, :ro => true}, ['my_file', 'my_other_file'])
215
+
216
+ @clh.configure do
217
+ simple_switch :help, '--help', '-h'
218
+ simple_switch :ro, '--read-only', '-r'
219
+
220
+ global do
221
+ switches :help, :ro
222
+ end
223
+ end
224
+
225
+ @clh.parse(handler, '-h', 'my_file', '--read-only', 'my_other_file')
226
+ end
227
+ end
228
+
229
+ describe "simple commands" do
230
+ it "should handle commands that take no switches" do
231
+ @clh.configure do
232
+ command :create do
233
+ end
234
+ end
235
+
236
+ handler = mock()
237
+ handler.should_receive(:create).with({}, ['fred'])
238
+ @clh.parse(handler, 'create', 'fred')
239
+ end
240
+ end
241
+
242
+ describe "commands" do
243
+ before :each do
244
+ @clh.configure do
245
+ value_type :int do |str|
246
+ str.to_i
247
+ end
248
+
249
+ value_switch :grow_to, :int, '--grow-to'
250
+ value_switch :grow_by, :int, '--grow-by'
251
+ value_switch :shrink_to, :int, '--shrink-to'
252
+ value_switch :shrink_by, :int, '--shrink-by'
253
+
254
+ command :resize do
255
+ switches :grow_to, :grow_by, :shrink_to, :shrink_by
256
+ end
257
+
258
+ command :shrink do
259
+ switches :grow_to, :grow_by, :shrink_to, :shrink_by
260
+ end
261
+
262
+ command :grow do
263
+ switches :grow_to, :grow_by, :shrink_to, :shrink_by
264
+ end
265
+ end
266
+ end
267
+
268
+ it "should allow you to define a sub command" do
269
+ handler = mock()
270
+ handler.should_receive(:resize).with({:grow_to => 12345}, ['fred'])
271
+ @clh.parse(handler, 'resize', '--grow-to', '12345', 'fred')
272
+ end
273
+
274
+ it "should prevent you calling two sub commands on the same line" do
275
+ handler = mock()
276
+ handler.should_receive(:resize).
277
+ with({:grow_to => 1234, :shrink_to => 2345}, ['shrink', 'fred'])
278
+ @clh.parse(handler, 'resize', '--grow-to', '1234', 'shrink', '--shrink-to', '2345', 'fred')
279
+ end
280
+ end
281
+
282
+ describe "exclusive switches" do
283
+ before :each do
284
+ @clh.configure do
285
+ value_type :int do |str|
286
+ str.to_i
287
+ end
288
+
289
+ value_switch :grow_to, :int, '--grow-to'
290
+ value_switch :grow_by, :int, '--grow-by'
291
+ value_switch :shrink_to, :int, '--shrink-to'
292
+ value_switch :shrink_by, :int, '--shrink-by'
293
+
294
+ command :resize do
295
+ one_of :grow_to, :grow_by, :shrink_to, :shrink_by
296
+ end
297
+ end
298
+ end
299
+
300
+ it "should parse one exclusive switch" do
301
+ handler = mock()
302
+ handler.should_receive(:resize).
303
+ with({:grow_to => 1234}, ['fred'])
304
+ @clh.parse(handler, 'resize', '--grow-to', '1234', 'fred')
305
+ end
306
+
307
+ it "should raise a ParseError if more than one switch from an exclusive set is defined" do
308
+ handler = mock()
309
+ expect do
310
+ @clh.parse(handler, 'resize', '--grow-to', '1234', '--shrink-by', '2345', 'fred')
311
+ end.to raise_error(ParseError, /mutually exclusive/)
312
+ # FIXME: would be nice to see the actual flags in the exception
313
+ end
314
+
315
+ it "should let you define more than one exclusive set" do
316
+ pending
317
+ end
318
+ end
319
+
320
+ it "should handle --foo=<value>" do
321
+ pending "todo"
322
+ end
323
+ end
324
+
325
+ describe "mandatory switches" do
326
+ before :each do
327
+ @clh.configure do
328
+ value_type :int do |str|
329
+ str.to_i
330
+ end
331
+
332
+ value_switch :grow_to, :int, '--grow-to'
333
+ value_switch :grow_by, :int, '--grow-by'
334
+ value_switch :shrink_to, :int, '--shrink-to'
335
+ value_switch :shrink_by, :int, '--shrink-by'
336
+
337
+ command :resize do
338
+ mandatory :grow_to
339
+ end
340
+ end
341
+ end
342
+
343
+ it "should parse ok if mandatory switch is given" do
344
+ handler = mock()
345
+ handler.should_receive(:resize).
346
+ with({:grow_to => 3}, ['fred'])
347
+ @clh.parse(handler, 'resize', '--grow-to', '3', 'fred')
348
+ end
349
+
350
+ it "should raise a ParseError if a mandatory switch is omitted" do
351
+ handler = mock()
352
+ expect do
353
+ @clh.parse(handler, 'resize', 'fred')
354
+ end.to raise_error(ParseError, /grow_to/)
355
+ end
356
+ end
357
+ end
358
+
359
+ #----------------------------------------------------------------
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'ejt_command_line'
5
+
6
+ RSpec.configure do |config|
7
+
8
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ejt_command_line
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Joe Thornber
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-05-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Yet another command line parser. Allows you define argument types
56
+ email:
57
+ - ejt@redhat.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - .gitignore
63
+ - .rspec
64
+ - Gemfile
65
+ - LICENSE.txt
66
+ - README.md
67
+ - Rakefile
68
+ - ejt_command_line.gemspec
69
+ - lib/ejt_command_line.rb
70
+ - lib/ejt_command_line/version.rb
71
+ - spec/command_line_spec.rb
72
+ - spec/command_line_spec.rb~
73
+ - spec/spec_helper.rb
74
+ homepage: ''
75
+ licenses:
76
+ - GPL
77
+ metadata: {}
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ! '>='
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ requirements: []
93
+ rubyforge_project:
94
+ rubygems_version: 2.0.3
95
+ signing_key:
96
+ specification_version: 4
97
+ summary: Command line parser
98
+ test_files:
99
+ - spec/command_line_spec.rb
100
+ - spec/command_line_spec.rb~
101
+ - spec/spec_helper.rb