cli 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +31 -8
- data/VERSION +1 -1
- data/cli.gemspec +2 -2
- data/examples/ls +5 -3
- data/lib/cli.rb +45 -16
- data/lib/cli/dsl.rb +21 -9
- data/lib/cli/options.rb +4 -0
- data/spec/option_spec.rb +41 -0
- data/spec/usage_spec.rb +2 -2
- metadata +4 -4
data/README.md
CHANGED
@@ -2,11 +2,13 @@
|
|
2
2
|
|
3
3
|
Command Line Interface gem allows you to quickly specify command argument parser that will automatically handle usage rendering, casting, default values and other stuff for you.
|
4
4
|
|
5
|
-
CLI supports
|
5
|
+
CLI supports following specifiers:
|
6
6
|
|
7
|
-
*
|
8
|
-
*
|
9
|
-
*
|
7
|
+
* switch - (`--verbose` or `-v`) binary operators, by default set to nil, when specified set to true
|
8
|
+
* option - (`--name John` or `-n John`) switches that take value; default value can be specified, otherwise defaults to nil
|
9
|
+
* options - (`-n John -n Frank`) like option but can be used multiple times on command line; default value or array of values can be given, otherwise defaults to empty array
|
10
|
+
* argument - (`John`) capture single command; default value can be specified; raises error if not given
|
11
|
+
* arguments - (`John Frank`) capture multiple command arguments; defaults to empty array
|
10
12
|
* stdin - if standard input is to be handled it can be mentioned in usage output; also stdin data casting is supported
|
11
13
|
|
12
14
|
Each element can have description that will be visible in the usage output.
|
@@ -142,22 +144,27 @@ The `options` variable will contain:
|
|
142
144
|
The `arguments` specifier matched value will always be an array of casted elements.
|
143
145
|
Default and mandatory arguments will have priority on matching values (see specs for examples).
|
144
146
|
|
147
|
+
`options` specifier can be used to allow specifing same option multiple times.
|
148
|
+
The `options` specifier matched value will always be an array of casted elements or empty if option not specified.
|
149
|
+
|
145
150
|
```ruby
|
146
151
|
require 'cli'
|
147
152
|
require 'pathname'
|
148
153
|
|
149
|
-
|
154
|
+
values = CLI.new do
|
150
155
|
description 'Lists content of directories'
|
151
156
|
switch :long, :short => :l, :description => 'use long listing'
|
157
|
+
options :exclude, :short => :e, :description => 'exclude files from listing'
|
152
158
|
arguments :directories, :cast => Pathname, :default => '.', :description => 'directories to list content of'
|
153
159
|
end.parse!
|
154
160
|
|
155
|
-
|
161
|
+
values.directories.each do |dir|
|
156
162
|
next unless dir.directory?
|
157
163
|
dir.each_entry do |e|
|
158
164
|
next if e.to_s == '.' or e.to_s == '..'
|
159
165
|
e = dir + e
|
160
|
-
if
|
166
|
+
next if values.exclude.include? e.to_s
|
167
|
+
if values.long
|
161
168
|
puts "#{e.stat.uid}:#{e.stat.gid} #{e}"
|
162
169
|
else
|
163
170
|
puts e
|
@@ -168,11 +175,13 @@ end
|
|
168
175
|
|
169
176
|
Example help message:
|
170
177
|
|
171
|
-
Usage: ls [switches] [--] directories*
|
178
|
+
Usage: ls [switches|options] [--] directories*
|
172
179
|
Lists content of directories
|
173
180
|
Switches:
|
174
181
|
--long (-l) - use long listing
|
175
182
|
--help (-h) - display this help message
|
183
|
+
Options:
|
184
|
+
--exclude* (-e) - exclude files from listing
|
176
185
|
Arguments:
|
177
186
|
directories* [.] - directories to list content of
|
178
187
|
|
@@ -192,6 +201,20 @@ Prints:
|
|
192
201
|
features
|
193
202
|
...
|
194
203
|
|
204
|
+
Excluding .git and .gitignore:
|
205
|
+
|
206
|
+
examples/ls -e .git -e .gitignore
|
207
|
+
|
208
|
+
Prints:
|
209
|
+
|
210
|
+
.document
|
211
|
+
.README.md.swp
|
212
|
+
.rspec
|
213
|
+
cli.gemspec
|
214
|
+
examples
|
215
|
+
features
|
216
|
+
...
|
217
|
+
|
195
218
|
With directory list:
|
196
219
|
|
197
220
|
examples/ls *
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.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.3.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-21"
|
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 = [
|
data/examples/ls
CHANGED
@@ -2,18 +2,20 @@
|
|
2
2
|
require 'cli'
|
3
3
|
require 'pathname'
|
4
4
|
|
5
|
-
|
5
|
+
values = CLI.new do
|
6
6
|
description 'Lists content of directories'
|
7
7
|
switch :long, :short => :l, :description => 'use long listing'
|
8
|
+
options :exclude, :short => :e, :description => 'exclude files from listing'
|
8
9
|
arguments :directories, :cast => Pathname, :default => '.', :description => 'directories to list content of'
|
9
10
|
end.parse!
|
10
11
|
|
11
|
-
|
12
|
+
values.directories.each do |dir|
|
12
13
|
next unless dir.directory?
|
13
14
|
dir.each_entry do |e|
|
14
15
|
next if e.to_s == '.' or e.to_s == '..'
|
15
16
|
e = dir + e
|
16
|
-
if
|
17
|
+
next if values.exclude.include? e.to_s
|
18
|
+
if values.long
|
17
19
|
puts "#{e.stat.uid}:#{e.stat.gid} #{e}"
|
18
20
|
else
|
19
21
|
puts e
|
data/lib/cli.rb
CHANGED
@@ -94,6 +94,12 @@ class CLI
|
|
94
94
|
send((argument.name.to_s + '=').to_sym, value)
|
95
95
|
end
|
96
96
|
|
97
|
+
def append(argument, value)
|
98
|
+
v = (send(argument.name.to_s) or [])
|
99
|
+
v << value
|
100
|
+
send((argument.name.to_s + '=').to_sym, v)
|
101
|
+
end
|
102
|
+
|
97
103
|
def set(argument)
|
98
104
|
value(argument, true)
|
99
105
|
end
|
@@ -162,6 +168,17 @@ class CLI
|
|
162
168
|
@options << option_dsl
|
163
169
|
end
|
164
170
|
|
171
|
+
def options(name, options = {})
|
172
|
+
option_dsl = DSL::Options.new(name, options)
|
173
|
+
|
174
|
+
raise ParserError::LongNameSpecifiedTwiceError.new('option', option_dsl) if @options.has_long?(option_dsl)
|
175
|
+
raise ParserError::LongNameSpecifiedTwiceError.new('switch and option', option_dsl) if @switches.has_long?(option_dsl)
|
176
|
+
raise ParserError::ShortNameSpecifiedTwiceError.new('option', option_dsl) if @options.has_short?(option_dsl)
|
177
|
+
raise ParserError::ShortNameSpecifiedTwiceError.new('switch and option', option_dsl) if @switches.has_short?(option_dsl)
|
178
|
+
|
179
|
+
@options << option_dsl
|
180
|
+
end
|
181
|
+
|
165
182
|
def parse(_argv = ARGV, stdin = STDIN, stderr = STDERR)
|
166
183
|
values = Values.new
|
167
184
|
argv = _argv.dup
|
@@ -181,6 +198,11 @@ class CLI
|
|
181
198
|
end
|
182
199
|
end
|
183
200
|
|
201
|
+
# initialize multi options
|
202
|
+
@options.multiple.each do |o|
|
203
|
+
values.value(o, [])
|
204
|
+
end
|
205
|
+
|
184
206
|
# set defaults
|
185
207
|
@options.defaults.each do |o|
|
186
208
|
values.value(o, o.cast(o.default))
|
@@ -196,7 +218,11 @@ class CLI
|
|
196
218
|
values.set(switch)
|
197
219
|
elsif option = @options.find(arg)
|
198
220
|
value = argv.shift or raise ParsingError::MissingOptionValueError.new(option)
|
199
|
-
|
221
|
+
if option.multiple?
|
222
|
+
values.append(option, option.cast(value))
|
223
|
+
else
|
224
|
+
values.value(option, option.cast(value))
|
225
|
+
end
|
200
226
|
mandatory_options.delete(option)
|
201
227
|
else
|
202
228
|
raise ParsingError::UnknownSwitchError.new(arg) unless switch
|
@@ -210,24 +236,24 @@ class CLI
|
|
210
236
|
|
211
237
|
# process arguments
|
212
238
|
arguments = @arguments.dup
|
213
|
-
mandatory_arguments_left = @arguments.mandatory.length
|
214
|
-
|
215
239
|
while argument = arguments.shift
|
216
|
-
value = if
|
217
|
-
argument.default
|
240
|
+
value = if arguments.mandatory.length >= argv.length and argument.has_default?
|
241
|
+
argument.default
|
218
242
|
else
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
243
|
+
raise ParsingError::MandatoryArgumentNotSpecifiedError.new(argument) if argv.empty?
|
244
|
+
arg = argv.shift
|
245
|
+
|
246
|
+
if argument.multiple?
|
247
|
+
v = [arg]
|
248
|
+
while argv.length > arguments.length
|
249
|
+
v << argv.shift
|
250
|
+
end
|
251
|
+
v
|
252
|
+
else
|
253
|
+
arg
|
226
254
|
end
|
227
255
|
end
|
228
256
|
|
229
|
-
mandatory_arguments_left -= 1 if argument.mandatory?
|
230
|
-
|
231
257
|
values.value(argument, argument.cast(value))
|
232
258
|
end
|
233
259
|
|
@@ -291,8 +317,11 @@ class CLI
|
|
291
317
|
unless @options.empty?
|
292
318
|
out.puts "Options:"
|
293
319
|
@options.each do |o|
|
294
|
-
|
295
|
-
|
320
|
+
unless o.multiple?
|
321
|
+
out.print " #{o.switch}"
|
322
|
+
else
|
323
|
+
out.print " #{o.switch}*"
|
324
|
+
end
|
296
325
|
out.print " (#{o.switch_short})" if o.has_short?
|
297
326
|
out.print " [%s]" % o.default if o.has_default?
|
298
327
|
out.print " - #{o.description}" if o.description?
|
data/lib/cli/dsl.rb
CHANGED
@@ -66,6 +66,13 @@ class CLI
|
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
69
|
+
module MultiDefault
|
70
|
+
def default
|
71
|
+
value = @options[:default]
|
72
|
+
value.is_a?(Array) ? value.map{|v| v.to_s} : [value.to_s]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
69
76
|
class Input < DSL::Base
|
70
77
|
include DSL::Cast
|
71
78
|
include DSL::Description
|
@@ -90,6 +97,8 @@ class CLI
|
|
90
97
|
end
|
91
98
|
|
92
99
|
class Arguments < Argument
|
100
|
+
include DSL::MultiDefault
|
101
|
+
|
93
102
|
def cast(values)
|
94
103
|
out = []
|
95
104
|
values.each do |v|
|
@@ -98,15 +107,6 @@ class CLI
|
|
98
107
|
out
|
99
108
|
end
|
100
109
|
|
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
110
|
def multiple?
|
111
111
|
true
|
112
112
|
end
|
@@ -151,6 +151,18 @@ class CLI
|
|
151
151
|
def mandatory?
|
152
152
|
not has_default? and @options[:required]
|
153
153
|
end
|
154
|
+
|
155
|
+
def multiple?
|
156
|
+
false
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
class Options < Option
|
161
|
+
include DSL::MultiDefault
|
162
|
+
|
163
|
+
def multiple?
|
164
|
+
true
|
165
|
+
end
|
154
166
|
end
|
155
167
|
end
|
156
168
|
end
|
data/lib/cli/options.rb
CHANGED
data/spec/option_spec.rb
CHANGED
@@ -26,6 +26,14 @@ describe CLI do
|
|
26
26
|
ps.size.should == 24
|
27
27
|
end
|
28
28
|
|
29
|
+
it "should support casting of multiple options" do
|
30
|
+
ps = CLI.new do
|
31
|
+
options :size, :cast => Integer
|
32
|
+
end.parse(['--size', '24', '--size', '10'])
|
33
|
+
ps.size.should be_a Array
|
34
|
+
ps.size.should == [24, 10]
|
35
|
+
end
|
36
|
+
|
29
37
|
it "should support casting with lambda" do
|
30
38
|
ps = CLI.new do
|
31
39
|
option :size, :cast => lambda{|v| v.to_i + 2}
|
@@ -79,6 +87,13 @@ describe CLI do
|
|
79
87
|
ps.gold.should be_nil
|
80
88
|
end
|
81
89
|
|
90
|
+
it "not given option that can be specified multiple times should be an empty array" do
|
91
|
+
ps = CLI.new do
|
92
|
+
options :size, :cast => Integer
|
93
|
+
end.parse([])
|
94
|
+
ps.size.should == []
|
95
|
+
end
|
96
|
+
|
82
97
|
it "should handle multiple long and short intermixed options" do
|
83
98
|
ps = CLI.new do
|
84
99
|
option :location, :short => :l
|
@@ -96,6 +111,32 @@ describe CLI do
|
|
96
111
|
ps.gold.should be_nil
|
97
112
|
end
|
98
113
|
|
114
|
+
it "should support options that can be specified multiple times" do
|
115
|
+
ps = CLI.new do
|
116
|
+
options :power_up, :short => :p
|
117
|
+
end.parse(['--power-up', 'fire'])
|
118
|
+
ps.power_up.should == ['fire']
|
119
|
+
|
120
|
+
ps = CLI.new do
|
121
|
+
options :power_up, :short => :p
|
122
|
+
end.parse(['--power-up', 'fire', '-p', 'water', '--power-up', 'air', '-p', 'ground'])
|
123
|
+
ps.power_up.should == ['fire', 'water', 'air', 'ground']
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should support options that can be specified multiple times can have single default" do
|
127
|
+
ps = CLI.new do
|
128
|
+
options :power_up, :short => :p, :default => 'fire'
|
129
|
+
end.parse([])
|
130
|
+
ps.power_up.should == ['fire']
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should support options that can be specified multiple times can have multiple defaults" do
|
134
|
+
ps = CLI.new do
|
135
|
+
options :power_up, :short => :p, :default => ['fire', 'air']
|
136
|
+
end.parse([])
|
137
|
+
ps.power_up.should == ['fire', 'air']
|
138
|
+
end
|
139
|
+
|
99
140
|
it "should raise error if not symbol and optional hash is passed" do
|
100
141
|
lambda {
|
101
142
|
ps = CLI.new do
|
data/spec/usage_spec.rb
CHANGED
@@ -261,7 +261,7 @@ describe CLI do
|
|
261
261
|
switch :run
|
262
262
|
option :location, :short => :r, :description => "place where server is located"
|
263
263
|
option :group, :default => 'red'
|
264
|
-
|
264
|
+
options :power_up, :short => :p
|
265
265
|
option :speed, :short => :s, :cast => Integer
|
266
266
|
option :the_number_of_the_beast, :short => :b, :cast => Integer, :default => 666, :description => "The number of the beast"
|
267
267
|
option :size
|
@@ -289,7 +289,7 @@ Switches:
|
|
289
289
|
Options:
|
290
290
|
--location (-r) - place where server is located
|
291
291
|
--group [red]
|
292
|
-
--power-up (-p)
|
292
|
+
--power-up* (-p)
|
293
293
|
--speed (-s)
|
294
294
|
--the-number-of-the-beast (-b) [666] - The number of the beast
|
295
295
|
--size
|
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: 19
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 3
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.3.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-21 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
type: :development
|