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 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