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 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 (cli_root + 'VERSION').read
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.1
1
+ 0.2.0
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "cli"
8
- s.version = "0.1.1"
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-19"
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",
@@ -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
+
@@ -1,9 +1,6 @@
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'
3
+ require 'pathname'
7
4
  require 'yaml'
8
5
 
9
6
  options = CLI.new do
@@ -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 (cli_root + 'VERSION').read
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
- out.puts " #{a} - #{a.description}"
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
 
@@ -6,5 +6,9 @@ class CLI::Arguments < Array
6
6
  def mandatory
7
7
  select{|a| a.mandatory?}
8
8
  end
9
+
10
+ def multiple
11
+ select{|a| a.multiple?}
12
+ end
9
13
  end
10
14
 
@@ -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
@@ -10,7 +10,7 @@ describe CLI do
10
10
  ps.log.should == '/tmp'
11
11
  end
12
12
 
13
- it "non empty, non optional with class casting" do
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 "non empty, non optional with builtin class casting" do
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
@@ -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: 25
4
+ hash: 23
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 1
9
- - 1
10
- version: 0.1.1
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-19 00:00:00 Z
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