cli 0.1.1 → 0.2.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.
- 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
|