cli 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +96 -1
- data/VERSION +1 -1
- data/cli.gemspec +3 -2
- data/examples/ls +23 -0
- data/examples/processor +2 -5
- data/examples/sinatra +2 -6
- data/lib/cli.rb +32 -2
- data/lib/cli/arguments.rb +4 -0
- data/lib/cli/dsl.rb +27 -0
- data/spec/argument_spec.rb +123 -2
- data/spec/usage_spec.rb +4 -2
- metadata +6 -5
data/README.md
CHANGED
@@ -27,7 +27,7 @@ require 'ip'
|
|
27
27
|
|
28
28
|
options = CLI.new do
|
29
29
|
description 'Example CLI usage for Sinatra server application'
|
30
|
-
version
|
30
|
+
version "1.0.0"
|
31
31
|
switch :no_bind, :description => "Do not bind to TCP socket - useful with -s fastcgi option"
|
32
32
|
switch :no_logging, :description => "Disable logging"
|
33
33
|
switch :debug, :description => "Enable debugging"
|
@@ -96,6 +96,7 @@ Example version output:
|
|
96
96
|
|
97
97
|
```ruby
|
98
98
|
require 'cli'
|
99
|
+
require 'pathname'
|
99
100
|
require 'yaml'
|
100
101
|
|
101
102
|
options = CLI.new do
|
@@ -135,6 +136,100 @@ The `options` variable will contain:
|
|
135
136
|
|
136
137
|
#<CLI::Values location="Singapore", stdin={:parser=>{:failures=>0, :successes=>41}}, jekyll_dir=#<Pathname:/var/lib/vhs/jekyll>, csv_dir=#<Pathname:csv>>
|
137
138
|
|
139
|
+
### Ls like utility
|
140
|
+
|
141
|
+
`arguments` specifier can be used to match multiple arguments.
|
142
|
+
The `arguments` specifier matched value will always be an array of casted elements.
|
143
|
+
Default and mandatory arguments will have priority on matching values (see specs for examples).
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
require 'cli'
|
147
|
+
require 'pathname'
|
148
|
+
|
149
|
+
options = CLI.new do
|
150
|
+
description 'Lists content of directories'
|
151
|
+
switch :long, :short => :l, :description => 'use long listing'
|
152
|
+
arguments :directories, :cast => Pathname, :default => '.', :description => 'directories to list content of'
|
153
|
+
end.parse!
|
154
|
+
|
155
|
+
options.directories.each do |dir|
|
156
|
+
next unless dir.directory?
|
157
|
+
dir.each_entry do |e|
|
158
|
+
next if e.to_s == '.' or e.to_s == '..'
|
159
|
+
e = dir + e
|
160
|
+
if options.long
|
161
|
+
puts "#{e.stat.uid}:#{e.stat.gid} #{e}"
|
162
|
+
else
|
163
|
+
puts e
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
```
|
168
|
+
|
169
|
+
Example help message:
|
170
|
+
|
171
|
+
Usage: ls [switches] [--] directories*
|
172
|
+
Lists content of directories
|
173
|
+
Switches:
|
174
|
+
--long (-l) - use long listing
|
175
|
+
--help (-h) - display this help message
|
176
|
+
Arguments:
|
177
|
+
directories* [.] - directories to list content of
|
178
|
+
|
179
|
+
Example usage:
|
180
|
+
|
181
|
+
examples/ls
|
182
|
+
|
183
|
+
Prints:
|
184
|
+
|
185
|
+
.document
|
186
|
+
.git
|
187
|
+
.gitignore
|
188
|
+
.README.md.swp
|
189
|
+
.rspec
|
190
|
+
cli.gemspec
|
191
|
+
examples
|
192
|
+
features
|
193
|
+
...
|
194
|
+
|
195
|
+
With directory list:
|
196
|
+
|
197
|
+
examples/ls *
|
198
|
+
|
199
|
+
Prints:
|
200
|
+
|
201
|
+
examples/.ls.swp
|
202
|
+
examples/ls
|
203
|
+
examples/processor
|
204
|
+
examples/sinatra
|
205
|
+
features/cli.feature
|
206
|
+
features/step_definitions
|
207
|
+
features/support
|
208
|
+
lib/cli
|
209
|
+
lib/cli.rb
|
210
|
+
pkg/cli-0.0.1.gem
|
211
|
+
pkg/cli-0.0.2.gem
|
212
|
+
...
|
213
|
+
|
214
|
+
Long printout:
|
215
|
+
|
216
|
+
examples/ls -l *
|
217
|
+
|
218
|
+
Prints:
|
219
|
+
|
220
|
+
501:20 examples/.ls.swp
|
221
|
+
501:20 examples/ls
|
222
|
+
501:20 examples/processor
|
223
|
+
501:20 examples/sinatra
|
224
|
+
501:20 features/cli.feature
|
225
|
+
501:20 features/step_definitions
|
226
|
+
501:20 features/support
|
227
|
+
501:20 lib/cli
|
228
|
+
501:20 lib/cli.rb
|
229
|
+
501:20 pkg/cli-0.0.1.gem
|
230
|
+
501:20 pkg/cli-0.0.2.gem
|
231
|
+
...
|
232
|
+
|
138
233
|
## Contributing to CLI
|
139
234
|
|
140
235
|
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/cli.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "cli"
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Jakub Pastuszek"]
|
12
|
-
s.date = "2011-12-
|
12
|
+
s.date = "2011-12-20"
|
13
13
|
s.description = "Provides DSL for command-line options, switches and arguments parser and stdin handling with generated usage printer"
|
14
14
|
s.email = "jpastuszek@gmail.com"
|
15
15
|
s.extra_rdoc_files = [
|
@@ -26,6 +26,7 @@ Gem::Specification.new do |s|
|
|
26
26
|
"Rakefile",
|
27
27
|
"VERSION",
|
28
28
|
"cli.gemspec",
|
29
|
+
"examples/ls",
|
29
30
|
"examples/processor",
|
30
31
|
"examples/sinatra",
|
31
32
|
"features/cli.feature",
|
data/examples/ls
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/usr/bin/ruby -rrubygems
|
2
|
+
require 'cli'
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
options = CLI.new do
|
6
|
+
description 'Lists content of directories'
|
7
|
+
switch :long, :short => :l, :description => 'use long listing'
|
8
|
+
arguments :directories, :cast => Pathname, :default => '.', :description => 'directories to list content of'
|
9
|
+
end.parse!
|
10
|
+
|
11
|
+
options.directories.each do |dir|
|
12
|
+
next unless dir.directory?
|
13
|
+
dir.each_entry do |e|
|
14
|
+
next if e.to_s == '.' or e.to_s == '..'
|
15
|
+
e = dir + e
|
16
|
+
if options.long
|
17
|
+
puts "#{e.stat.uid}:#{e.stat.gid} #{e}"
|
18
|
+
else
|
19
|
+
puts e
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
data/examples/processor
CHANGED
data/examples/sinatra
CHANGED
@@ -1,14 +1,10 @@
|
|
1
|
-
#!/usr/bin/ruby
|
2
|
-
require 'pathname'
|
3
|
-
cli_root = Pathname.new(__FILE__).dirname + '..'
|
4
|
-
$LOAD_PATH.unshift(cli_root + 'lib')
|
5
|
-
|
1
|
+
#!/usr/bin/ruby -rrubygems
|
6
2
|
require 'cli'
|
7
3
|
require 'ip'
|
8
4
|
|
9
5
|
options = CLI.new do
|
10
6
|
description 'Example CLI usage for Sinatra server application'
|
11
|
-
version
|
7
|
+
version "1.0.0"
|
12
8
|
switch :no_bind, :description => "Do not bind to TCP socket - useful with -s fastcgi option"
|
13
9
|
switch :no_logging, :description => "Disable logging"
|
14
10
|
switch :debug, :description => "Enable debugging"
|
data/lib/cli.rb
CHANGED
@@ -49,6 +49,12 @@ class CLI
|
|
49
49
|
super("short name for #{switch_dsl.switch} has to be one letter symbol, got #{short.inspect}")
|
50
50
|
end
|
51
51
|
end
|
52
|
+
|
53
|
+
class MultipleArgumentsSpecifierError < ParserError
|
54
|
+
def initialize(arguments_dsl)
|
55
|
+
super("only one 'arguments' specifier can be used, got: #{arguments_dsl.join(', ')}")
|
56
|
+
end
|
57
|
+
end
|
52
58
|
end
|
53
59
|
|
54
60
|
class ParsingError < ArgumentError
|
@@ -124,6 +130,16 @@ class CLI
|
|
124
130
|
@arguments << argument_dsl
|
125
131
|
end
|
126
132
|
|
133
|
+
def arguments(name, options = {})
|
134
|
+
arguments_dsl = DSL::Arguments.new(name, options)
|
135
|
+
|
136
|
+
raise ParserError::ArgumentNameSpecifiedTwice.new(arguments_dsl.name) if @arguments.has?(arguments_dsl)
|
137
|
+
|
138
|
+
@arguments << arguments_dsl
|
139
|
+
|
140
|
+
raise ParserError::MultipleArgumentsSpecifierError.new(@arguments.multiple) if @arguments.multiple.length > 1
|
141
|
+
end
|
142
|
+
|
127
143
|
def switch(name, options = {})
|
128
144
|
switch_dsl = DSL::Switch.new(name, options)
|
129
145
|
|
@@ -203,6 +219,13 @@ class CLI
|
|
203
219
|
argv.shift or raise ParsingError::MandatoryArgumentNotSpecifiedError.new(argument)
|
204
220
|
end
|
205
221
|
|
222
|
+
if argument.multiple?
|
223
|
+
value = [value] unless value.is_a? Array
|
224
|
+
while argv.length > arguments.length
|
225
|
+
value << argv.shift
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
206
229
|
mandatory_arguments_left -= 1 if argument.mandatory?
|
207
230
|
|
208
231
|
values.value(argument, argument.cast(value))
|
@@ -243,7 +266,7 @@ class CLI
|
|
243
266
|
out.print ' [switches]' if not @switches.empty? and @options.empty?
|
244
267
|
out.print ' [options]' if @switches.empty? and not @options.empty?
|
245
268
|
out.print ' [--]' if not @arguments.empty? and (not @switches.empty? or not @options.empty?)
|
246
|
-
out.print ' ' + @arguments.map{|a| a.to_s}.join(' ') unless @arguments.empty?
|
269
|
+
out.print ' ' + @arguments.map{|a| a.multiple? ? a.to_s + '*': a.to_s}.join(' ') unless @arguments.empty?
|
247
270
|
out.print " < #{@stdin}" if @stdin
|
248
271
|
|
249
272
|
out.puts
|
@@ -281,7 +304,14 @@ class CLI
|
|
281
304
|
unless described_arguments.empty?
|
282
305
|
out.puts "Arguments:"
|
283
306
|
described_arguments.each do |a|
|
284
|
-
|
307
|
+
unless a.multiple?
|
308
|
+
out.print " #{a}"
|
309
|
+
else
|
310
|
+
out.print " #{a}*"
|
311
|
+
end
|
312
|
+
out.print " [%s]" % (a.default.is_a?(Array) ? a.default.join(' ') : a.default) if a.has_default?
|
313
|
+
out.print " - #{a.description}"
|
314
|
+
out.puts
|
285
315
|
end
|
286
316
|
end
|
287
317
|
|
data/lib/cli/arguments.rb
CHANGED
data/lib/cli/dsl.rb
CHANGED
@@ -83,6 +83,33 @@ class CLI
|
|
83
83
|
def to_s
|
84
84
|
name.to_s.tr('_', '-')
|
85
85
|
end
|
86
|
+
|
87
|
+
def multiple?
|
88
|
+
false
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class Arguments < Argument
|
93
|
+
def cast(values)
|
94
|
+
out = []
|
95
|
+
values.each do |v|
|
96
|
+
out << super(v)
|
97
|
+
end
|
98
|
+
out
|
99
|
+
end
|
100
|
+
|
101
|
+
def default
|
102
|
+
value = @options[:default]
|
103
|
+
if value.is_a? Array
|
104
|
+
value.map{|v| v.to_s}
|
105
|
+
else
|
106
|
+
value.to_s
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def multiple?
|
111
|
+
true
|
112
|
+
end
|
86
113
|
end
|
87
114
|
|
88
115
|
class Switch < DSL::Base
|
data/spec/argument_spec.rb
CHANGED
@@ -10,7 +10,7 @@ describe CLI do
|
|
10
10
|
ps.log.should == '/tmp'
|
11
11
|
end
|
12
12
|
|
13
|
-
it "
|
13
|
+
it "should cast mandatory argument" do
|
14
14
|
ps = CLI.new do
|
15
15
|
argument :log, :cast => Pathname
|
16
16
|
end.parse(['/tmp'])
|
@@ -18,7 +18,7 @@ describe CLI do
|
|
18
18
|
ps.log.to_s.should == '/tmp'
|
19
19
|
end
|
20
20
|
|
21
|
-
it "
|
21
|
+
it "should cast mandatory argument to numerical class" do
|
22
22
|
ps = CLI.new do
|
23
23
|
argument :number, :cast => Integer
|
24
24
|
end.parse(['123'])
|
@@ -32,6 +32,49 @@ describe CLI do
|
|
32
32
|
ps.number.should == 123.0
|
33
33
|
end
|
34
34
|
|
35
|
+
it "should cast default value" do
|
36
|
+
ps = CLI.new do
|
37
|
+
argument :number, :cast => Integer, :default => '123'
|
38
|
+
end.parse([])
|
39
|
+
ps.number.should be_a Integer
|
40
|
+
ps.number.should == 123
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should cast value of multiple arguments argument" do
|
44
|
+
ps = CLI.new do
|
45
|
+
arguments :numbers, :cast => Integer
|
46
|
+
end.parse(['1', '2', '3'])
|
47
|
+
ps.numbers.should be_a Array
|
48
|
+
ps.numbers[0].should be_a Integer
|
49
|
+
ps.numbers[0].should == 1
|
50
|
+
ps.numbers[1].should be_a Integer
|
51
|
+
ps.numbers[1].should == 2
|
52
|
+
ps.numbers[2].should be_a Integer
|
53
|
+
ps.numbers[2].should == 3
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should cast single default value of multiple arguments argument" do
|
57
|
+
ps = CLI.new do
|
58
|
+
arguments :numbers, :cast => Integer, :default => '1'
|
59
|
+
end.parse([])
|
60
|
+
ps.numbers.should be_a Array
|
61
|
+
ps.numbers[0].should be_a Integer
|
62
|
+
ps.numbers[0].should == 1
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should cast default value array of multiple arguments argument" do
|
66
|
+
ps = CLI.new do
|
67
|
+
arguments :numbers, :cast => Integer, :default => ['1', '2', '3']
|
68
|
+
end.parse([])
|
69
|
+
ps.numbers.should be_a Array
|
70
|
+
ps.numbers[0].should be_a Integer
|
71
|
+
ps.numbers[0].should == 1
|
72
|
+
ps.numbers[1].should be_a Integer
|
73
|
+
ps.numbers[1].should == 2
|
74
|
+
ps.numbers[2].should be_a Integer
|
75
|
+
ps.numbers[2].should == 3
|
76
|
+
end
|
77
|
+
|
35
78
|
it "should handle multiple arguments" do
|
36
79
|
ps = CLI.new do
|
37
80
|
argument :log, :cast => Pathname
|
@@ -43,6 +86,20 @@ describe CLI do
|
|
43
86
|
ps.test.should == 'hello'
|
44
87
|
end
|
45
88
|
|
89
|
+
it "should handle multi arguments" do
|
90
|
+
ps = CLI.new do
|
91
|
+
argument :log, :cast => Pathname
|
92
|
+
arguments :words
|
93
|
+
end.parse(['/tmp', 'hello', 'world', 'test'])
|
94
|
+
ps.log.should be_a Pathname
|
95
|
+
ps.log.to_s.should == '/tmp'
|
96
|
+
|
97
|
+
ps.words.should be_a Array
|
98
|
+
ps.words[0].should == 'hello'
|
99
|
+
ps.words[1].should == 'world'
|
100
|
+
ps.words[2].should == 'test'
|
101
|
+
end
|
102
|
+
|
46
103
|
it "should raise error if not symbol and optional hash is passed" do
|
47
104
|
lambda {
|
48
105
|
ps = CLI.new do
|
@@ -83,6 +140,16 @@ describe CLI do
|
|
83
140
|
}.should raise_error CLI::ParsingError::CastError, "failed to cast: 'log' to type: IP: invalid address"
|
84
141
|
end
|
85
142
|
|
143
|
+
it "should raise error if multiple artuments argument defined twice" do
|
144
|
+
lambda {
|
145
|
+
ps = CLI.new do
|
146
|
+
arguments :test1
|
147
|
+
argument :test2
|
148
|
+
arguments :test3
|
149
|
+
end
|
150
|
+
}.should raise_error CLI::ParserError::MultipleArgumentsSpecifierError, "only one 'arguments' specifier can be used, got: test1, test3"
|
151
|
+
end
|
152
|
+
|
86
153
|
describe "with defaults" do
|
87
154
|
it "when not enought arguments given it should fill required arguments only with defaults" do
|
88
155
|
ps = CLI.new do
|
@@ -127,6 +194,60 @@ describe CLI do
|
|
127
194
|
ps.test.should == 'world'
|
128
195
|
ps.code.should == 123
|
129
196
|
end
|
197
|
+
|
198
|
+
it "should fill multiple argumets argument with remaining arguments after filling mandatory and default arguments" do
|
199
|
+
ps = CLI.new do
|
200
|
+
argument :log, :cast => Pathname
|
201
|
+
argument :magick, :default => 'word'
|
202
|
+
argument :test
|
203
|
+
arguments :words
|
204
|
+
argument :test2
|
205
|
+
argument :code, :cast => Integer, :default => '123'
|
206
|
+
end.parse(['/tmp', 'number', 'test', 'hello', 'world', 'abc', 'test2', '42'])
|
207
|
+
|
208
|
+
ps.log.to_s.should == '/tmp'
|
209
|
+
ps.magick.should == 'number'
|
210
|
+
ps.test.should == 'test'
|
211
|
+
ps.words.should == ['hello', 'world', 'abc']
|
212
|
+
ps.test2.should == 'test2'
|
213
|
+
ps.code.should == 42
|
214
|
+
end
|
215
|
+
|
216
|
+
it "should use default single value for multiple arguments argument when not enought arguments given" do
|
217
|
+
ps = CLI.new do
|
218
|
+
argument :log, :cast => Pathname
|
219
|
+
argument :magick, :default => 'word'
|
220
|
+
argument :test
|
221
|
+
arguments :words, :default => 'hello'
|
222
|
+
argument :test2
|
223
|
+
argument :code, :cast => Integer, :default => '123'
|
224
|
+
end.parse(['/tmp', 'test', 'test2'])
|
225
|
+
|
226
|
+
ps.log.to_s.should == '/tmp'
|
227
|
+
ps.magick.should == 'word'
|
228
|
+
ps.test.should == 'test'
|
229
|
+
ps.words.should == ['hello']
|
230
|
+
ps.test2.should == 'test2'
|
231
|
+
ps.code.should == 123
|
232
|
+
end
|
233
|
+
|
234
|
+
it "should use default array of values for multiple arguments argument when not enought arguments given" do
|
235
|
+
ps = CLI.new do
|
236
|
+
argument :log, :cast => Pathname
|
237
|
+
argument :magick, :default => 'word'
|
238
|
+
argument :test
|
239
|
+
arguments :words, :default => ['hello', 'world', 'abc']
|
240
|
+
argument :test2
|
241
|
+
argument :code, :cast => Integer, :default => '123'
|
242
|
+
end.parse(['/tmp', 'test', 'test2'])
|
243
|
+
|
244
|
+
ps.log.to_s.should == '/tmp'
|
245
|
+
ps.magick.should == 'word'
|
246
|
+
ps.test.should == 'test'
|
247
|
+
ps.words.should == ['hello', 'world', 'abc']
|
248
|
+
ps.test2.should == 'test2'
|
249
|
+
ps.code.should == 123
|
250
|
+
end
|
130
251
|
end
|
131
252
|
end
|
132
253
|
end
|
data/spec/usage_spec.rb
CHANGED
@@ -272,10 +272,11 @@ describe CLI do
|
|
272
272
|
argument :number, :cast => Integer
|
273
273
|
argument :code, :cast => Integer, :default => '123', :description => "secret code"
|
274
274
|
argument :illegal_prime, :cast => Integer, :description => "prime number that represents information that it is forbidden to possess or distribute"
|
275
|
+
arguments :files, :cast => Pathname, :default => ['test', '1', '2'], :description => "files to process"
|
275
276
|
end.usage
|
276
277
|
|
277
278
|
u.should == <<EOS
|
278
|
-
Usage: rspec [switches|options] [--] log magick string number code illegal-prime < log-data
|
279
|
+
Usage: rspec [switches|options] [--] log magick string number code illegal-prime files* < log-data
|
279
280
|
Log file processor
|
280
281
|
Input:
|
281
282
|
log-data - YAML formatted log data
|
@@ -294,8 +295,9 @@ Options:
|
|
294
295
|
--size
|
295
296
|
Arguments:
|
296
297
|
log - log file to process
|
297
|
-
code - secret code
|
298
|
+
code [123] - secret code
|
298
299
|
illegal-prime - prime number that represents information that it is forbidden to possess or distribute
|
300
|
+
files* [test 1 2] - files to process
|
299
301
|
EOS
|
300
302
|
end
|
301
303
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 0.2.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Jakub Pastuszek
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-12-
|
18
|
+
date: 2011-12-20 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
type: :development
|
@@ -142,6 +142,7 @@ files:
|
|
142
142
|
- Rakefile
|
143
143
|
- VERSION
|
144
144
|
- cli.gemspec
|
145
|
+
- examples/ls
|
145
146
|
- examples/processor
|
146
147
|
- examples/sinatra
|
147
148
|
- features/cli.feature
|