clip 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/README.txt +94 -0
- data/bin/sample_parser +18 -0
- data/lib/clip.rb +324 -0
- data/spec/clip_spec.rb +269 -0
- metadata +68 -0
data/History.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
= clip
|
2
|
+
|
3
|
+
== DESCRIPTION:
|
4
|
+
|
5
|
+
Yeah yeah yeah. Why in heaven's name do we need yet another
|
6
|
+
command-line parser? Well, OptionParser is all well and good, but
|
7
|
+
doesn't grease the skids as much as I'd like. So I wrote this little
|
8
|
+
library... driven completely by specs.
|
9
|
+
|
10
|
+
Cheers!
|
11
|
+
|
12
|
+
== FEATURES
|
13
|
+
|
14
|
+
You like command-line parsing, but you hate all of the bloat. Why
|
15
|
+
should you have to create a Hash, then create a parser, then fill
|
16
|
+
that Hash out then throw the parser away (unless you want to print
|
17
|
+
out a usage message) and deal with a Hash? Why, for Pete's sake, should
|
18
|
+
the parser and the parsed values be handled by two different objects?
|
19
|
+
|
20
|
+
Well, now they don't...
|
21
|
+
|
22
|
+
== SYNOPSIS:
|
23
|
+
|
24
|
+
And it goes a little something like this...
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "clip"
|
28
|
+
|
29
|
+
parser = Clip do |p|
|
30
|
+
p.optional 's', 'server', :desc => 'The server name', :default => 'localhost'
|
31
|
+
p.optional 'p', 'port', :desc => 'The port', :default => 8080
|
32
|
+
p.required 'f', 'files', :multi => true, :desc => 'Files to send'
|
33
|
+
p.flag 'v', 'verbose', :desc => 'Make it chatty'
|
34
|
+
end
|
35
|
+
|
36
|
+
if parser.valid?
|
37
|
+
if parser.verbose?
|
38
|
+
puts parser.host
|
39
|
+
puts parser.port
|
40
|
+
puts 'files:'
|
41
|
+
parser.files.each do |f|
|
42
|
+
puts "\t#{f}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
else
|
46
|
+
# print error message(s) and usage
|
47
|
+
$stderr.puts parser.to_s
|
48
|
+
end
|
49
|
+
|
50
|
+
The names of the options and flags that you declare in the block are accessible
|
51
|
+
as methods on the returned object, reducing the amount of objects you have to
|
52
|
+
deal with when you're parsing command-line parameters.
|
53
|
+
|
54
|
+
Simply invoking the <tt>to_s</tt> method on a parser instance will dump both the
|
55
|
+
correct usage and any errors encountered during parsing. No need for you to manage
|
56
|
+
the state of what's required and what isn't by yourself. Also, '--help' and '-h'
|
57
|
+
will automatically trigger Clip to dump out usage and exit.
|
58
|
+
|
59
|
+
Sometimes you have additional arguments you need to process that don't require
|
60
|
+
a named option or flag. Whatever remains on the command line that doesn't fit
|
61
|
+
either a flag or an option/value pair will be made available via the
|
62
|
+
<tt>remainder</tt> method of the returned object.
|
63
|
+
|
64
|
+
== PROBLEMS/DEFICIENCIES:
|
65
|
+
|
66
|
+
OK, some of your favorite <tt>OptionParser</tt> features are simply not here.
|
67
|
+
You know that cool thing you can do where you tell <tt>OptionParser</tt> the
|
68
|
+
class of the kind of object you would like to get for a particular argument?
|
69
|
+
Do you like that one? Well, too bad. We don't have that one. Deal with it.
|
70
|
+
|
71
|
+
== LICENSE:
|
72
|
+
|
73
|
+
(The MIT License)
|
74
|
+
|
75
|
+
Copyright (c) 2008 Alex Vollmer
|
76
|
+
|
77
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
78
|
+
a copy of this software and associated documentation files (the
|
79
|
+
'Software'), to deal in the Software without restriction, including
|
80
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
81
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
82
|
+
permit persons to whom the Software is furnished to do so, subject to
|
83
|
+
the following conditions:
|
84
|
+
|
85
|
+
The above copyright notice and this permission notice shall be
|
86
|
+
included in all copies or substantial portions of the Software.
|
87
|
+
|
88
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
89
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
90
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
91
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
92
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
93
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
94
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/bin/sample_parser
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require File.join(File.dirname(__FILE__), "../lib/clip")
|
4
|
+
|
5
|
+
##
|
6
|
+
# This is a very simple example of how to use Clip
|
7
|
+
p = Clip do |c|
|
8
|
+
c.optional 's', 'server', :desc => 'The server name', :default => 'localhost'
|
9
|
+
c.optional 'p', 'port', :desc => 'The port', :default => 8080
|
10
|
+
c.optional 'f', 'files', :desc => 'Files to upload', :multi => true
|
11
|
+
c.flag 'v', 'verbose', :desc => 'Make it verbose, man'
|
12
|
+
end
|
13
|
+
|
14
|
+
printf("%10s %-20s\n", "attribute", "value")
|
15
|
+
[:server, :port, :verbose?].each do |att|
|
16
|
+
printf("%10s %-20s\n", att, p.send(att))
|
17
|
+
end
|
18
|
+
printf("%10s %-20s\n", "files", p.files.inspect)
|
data/lib/clip.rb
ADDED
@@ -0,0 +1,324 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
##
|
4
|
+
# Parse arguments (defaults to <tt>ARGV</tt>) with the Clip::Parser
|
5
|
+
# configured in the given block. This is the main method you
|
6
|
+
# call to get the ball rolling.
|
7
|
+
def Clip(args=ARGV)
|
8
|
+
parser = Clip::Parser.new
|
9
|
+
raise "Dontcha wanna configure your parser?" unless block_given?
|
10
|
+
yield parser
|
11
|
+
parser.parse(args)
|
12
|
+
parser
|
13
|
+
end
|
14
|
+
|
15
|
+
module Clip
|
16
|
+
VERSION = "0.0.1"
|
17
|
+
|
18
|
+
##
|
19
|
+
# Indicates that the parser was incorrectly configured in the
|
20
|
+
# block yielded by the +parse+ method.
|
21
|
+
class IllegalConfiguration < Exception
|
22
|
+
end
|
23
|
+
|
24
|
+
class Parser
|
25
|
+
##
|
26
|
+
# Returns any remaining command line arguments that were not parsed
|
27
|
+
# because they were neither flags or option/value pairs
|
28
|
+
attr_reader :remainder
|
29
|
+
|
30
|
+
##
|
31
|
+
# Declare an optional parameter for your parser. This creates an accessor
|
32
|
+
# method matching the <tt>long</tt> parameter. The <tt>short</tt> parameter
|
33
|
+
# indicates the single-letter equivalent. Options that use the '-'
|
34
|
+
# character as a word separator are converted to method names using
|
35
|
+
# '_'. For example the name 'exclude-files' would create a method named
|
36
|
+
# <tt>exclude_files</tt>.
|
37
|
+
#
|
38
|
+
# When the <tt>:multi</tt> option is enabled, the associated accessor
|
39
|
+
# method will return an <tt>Array</tt> instead of a single scalar value.
|
40
|
+
# === options
|
41
|
+
# Valid options include:
|
42
|
+
# * <tt>desc</tt>: a helpful description (used for printing usage)
|
43
|
+
# * <tt>default</tt>: a default value to provide if one is not given
|
44
|
+
# * <tt>multi</tt>: indicates that mulitple values are okay for this param.
|
45
|
+
#
|
46
|
+
# Note that specifying the <tt>:multi</tt> option means that the parameter
|
47
|
+
# can be specified several times with different values, or that a single
|
48
|
+
# comma-separated value can be specified which will then be broken up into
|
49
|
+
# separate tokens.
|
50
|
+
def optional(short, long, options={})
|
51
|
+
short = short.to_sym
|
52
|
+
long = long.to_sym
|
53
|
+
check_args(short, long)
|
54
|
+
|
55
|
+
eval <<-EOF
|
56
|
+
def #{long}=(val)
|
57
|
+
@#{long} = val
|
58
|
+
end
|
59
|
+
|
60
|
+
def #{long}
|
61
|
+
@#{long}
|
62
|
+
end
|
63
|
+
EOF
|
64
|
+
|
65
|
+
self.options[long] = Option.new(short, long, options)
|
66
|
+
self.options[short] = self.options[long]
|
67
|
+
self.order << self.options[long]
|
68
|
+
end
|
69
|
+
|
70
|
+
alias_method :opt, :optional
|
71
|
+
|
72
|
+
##
|
73
|
+
# Declare a required parameter for your parser. If this parameter
|
74
|
+
# is not provided in the parsed content, the parser instance
|
75
|
+
# will be invalid (i.e. where valid? returns <tt>false</tt>).
|
76
|
+
#
|
77
|
+
# This method takes the same options as the optional method.
|
78
|
+
def required(short, long, options={})
|
79
|
+
optional(short, long, options.merge({ :required => true }))
|
80
|
+
end
|
81
|
+
|
82
|
+
alias_method :req, :required
|
83
|
+
|
84
|
+
##
|
85
|
+
# Declare a parameter as a simple boolean flag. This declaration
|
86
|
+
# will create a "question" method matching the given <tt>long</tt>.
|
87
|
+
# For example, declaring with the name of 'verbose' will create a
|
88
|
+
# method on your parser called <tt>verbose?</tt>.
|
89
|
+
# === options
|
90
|
+
# Valid options are:
|
91
|
+
# * <tt>desc</tt>: Descriptive text for the flag
|
92
|
+
def flag(short, long, options={})
|
93
|
+
short = short.to_sym
|
94
|
+
long = long.to_sym
|
95
|
+
|
96
|
+
check_args(short, long)
|
97
|
+
|
98
|
+
eval <<-EOF
|
99
|
+
def flag_#{long}
|
100
|
+
@#{long} = true
|
101
|
+
end
|
102
|
+
|
103
|
+
def #{long}?
|
104
|
+
return @#{long} || false
|
105
|
+
end
|
106
|
+
EOF
|
107
|
+
|
108
|
+
self.options[long] = Flag.new(short, long, options)
|
109
|
+
self.options[short] = self.options[long]
|
110
|
+
self.order << self.options[long]
|
111
|
+
end
|
112
|
+
|
113
|
+
def initialize # :nodoc:
|
114
|
+
@errors = {}
|
115
|
+
@valid = true
|
116
|
+
end
|
117
|
+
|
118
|
+
##
|
119
|
+
# Parse the given <tt>args</tt> and set the corresponding instance
|
120
|
+
# fields to the given values. If any errors occurred during parsing
|
121
|
+
# you can get them from the <tt>Hash</tt> returned by the +errors+ method.
|
122
|
+
def parse(args)
|
123
|
+
@valid = true
|
124
|
+
args = args.split(/\s+/) unless args.kind_of?(Array)
|
125
|
+
consumed = []
|
126
|
+
if args.member?("--help")
|
127
|
+
puts help
|
128
|
+
exit 0
|
129
|
+
end
|
130
|
+
param, value = nil, nil
|
131
|
+
|
132
|
+
args.each do |token|
|
133
|
+
case token
|
134
|
+
when /^-(-)?\w/
|
135
|
+
consumed << token
|
136
|
+
param = token.sub(/^-(-)?/, '').sub('-', '_').to_sym
|
137
|
+
value = nil
|
138
|
+
else
|
139
|
+
if param
|
140
|
+
consumed << token
|
141
|
+
value = token
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
option = options[param]
|
146
|
+
if option
|
147
|
+
if (value.nil? && option.kind_of?(Flag)) || value
|
148
|
+
option.process(self, value)
|
149
|
+
end
|
150
|
+
else
|
151
|
+
@errors[param] = "Unrecoginzed parameter"
|
152
|
+
@valid = false
|
153
|
+
next
|
154
|
+
end
|
155
|
+
|
156
|
+
unless value.nil?
|
157
|
+
param = nil
|
158
|
+
value = nil
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
@remainder = args - consumed
|
163
|
+
|
164
|
+
# Find required options that are missing arguments
|
165
|
+
options.each do |param, opt|
|
166
|
+
if opt.kind_of?(Option) and self.send(opt.long).nil?
|
167
|
+
if opt.required?
|
168
|
+
@valid = false
|
169
|
+
@errors[opt.long.to_sym] = "Missing required parameter: #{opt.long}"
|
170
|
+
elsif opt.has_default?
|
171
|
+
opt.process(self, opt.default)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
##
|
178
|
+
# Indicates whether or not the parsing process succeeded. If this
|
179
|
+
# returns <tt>false</tt> you probably just want to print out a call
|
180
|
+
# to the to_s method.
|
181
|
+
def valid?
|
182
|
+
@valid
|
183
|
+
end
|
184
|
+
|
185
|
+
##
|
186
|
+
# Returns a <tt>Hash</tt> of errors (by the long name) of any errors
|
187
|
+
# encountered during parsing. If you simply want to display error
|
188
|
+
# messages to the user, you can just print out a call to the
|
189
|
+
# to_s method.
|
190
|
+
def errors
|
191
|
+
@errors
|
192
|
+
end
|
193
|
+
|
194
|
+
##
|
195
|
+
# Returns a formatted <tt>String</tt> indicating the usage of the parser
|
196
|
+
def help
|
197
|
+
out = ""
|
198
|
+
out << "Usage:\n"
|
199
|
+
order.each do |option|
|
200
|
+
out << "#{option.usage}\n"
|
201
|
+
end
|
202
|
+
out
|
203
|
+
end
|
204
|
+
|
205
|
+
##
|
206
|
+
# Returns a formatted <tt>String</tt> of the +help+ method prefixed by
|
207
|
+
# any parsing errors. Either way you have _one_ method to call to
|
208
|
+
# let your users know what to do.
|
209
|
+
def to_s
|
210
|
+
out = ""
|
211
|
+
unless valid?
|
212
|
+
out << "Errors:\n"
|
213
|
+
errors.each do |field, msg|
|
214
|
+
out << "#{field}: #{msg}\n"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
out << help
|
218
|
+
end
|
219
|
+
|
220
|
+
def options # :nodoc:
|
221
|
+
(@options ||= {})
|
222
|
+
end
|
223
|
+
|
224
|
+
def order # :nodoc:
|
225
|
+
(@order ||= [])
|
226
|
+
end
|
227
|
+
|
228
|
+
private
|
229
|
+
def check_args(short, long)
|
230
|
+
short = short.to_sym
|
231
|
+
long = long.to_sym
|
232
|
+
|
233
|
+
if long == :help
|
234
|
+
raise IllegalConfiguration.new("You cannot override the built-in 'help' parameter")
|
235
|
+
end
|
236
|
+
|
237
|
+
if short == :h
|
238
|
+
raise IllegalConfiguration.new("You cannot override the built-in 'h' parameter")
|
239
|
+
end
|
240
|
+
|
241
|
+
if self.options.has_key?(long)
|
242
|
+
raise IllegalConfiguration.new("You have already defined a parameter/flag for #{long}")
|
243
|
+
end
|
244
|
+
|
245
|
+
if self.options.has_key?(short)
|
246
|
+
raise IllegalConfiguration.new("You already have a defined parameter/flag for the short key '#{short}")
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
class Option # :nodoc:
|
252
|
+
attr_accessor :long, :short, :description, :default, :required, :multi
|
253
|
+
|
254
|
+
def initialize(short, long, options)
|
255
|
+
@short = short
|
256
|
+
@long = long
|
257
|
+
@description = options[:desc]
|
258
|
+
@default = options[:default]
|
259
|
+
@required = options[:required]
|
260
|
+
@multi = options[:multi]
|
261
|
+
end
|
262
|
+
|
263
|
+
def process(parser, value)
|
264
|
+
if @multi
|
265
|
+
current = parser.send(@long) || []
|
266
|
+
current.concat(value.split(','))
|
267
|
+
parser.send("#{@long}=".to_sym, current)
|
268
|
+
else
|
269
|
+
parser.send("#{@long}=".to_sym, value)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def required?
|
274
|
+
@required == true
|
275
|
+
end
|
276
|
+
|
277
|
+
def has_default?
|
278
|
+
not @default.nil?
|
279
|
+
end
|
280
|
+
|
281
|
+
def multi?
|
282
|
+
@multi == true
|
283
|
+
end
|
284
|
+
|
285
|
+
def usage
|
286
|
+
out = sprintf('-%-2s --%-10s %s',
|
287
|
+
@short,
|
288
|
+
@long.to_s.gsub('_', '-').to_sym,
|
289
|
+
@description)
|
290
|
+
out << " (defaults to '#{@default}')" if @default
|
291
|
+
out << " REQUIRED" if @required
|
292
|
+
out
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
class Flag # :nodoc:
|
297
|
+
|
298
|
+
attr_accessor :long, :short, :description
|
299
|
+
|
300
|
+
##
|
301
|
+
# nodoc
|
302
|
+
def initialize(short, long, options)
|
303
|
+
@short = short
|
304
|
+
@long = long
|
305
|
+
@description = options[:desc]
|
306
|
+
end
|
307
|
+
|
308
|
+
def process(parser, value)
|
309
|
+
parser.send("flag_#{@long}".to_sym)
|
310
|
+
end
|
311
|
+
|
312
|
+
def required?
|
313
|
+
false
|
314
|
+
end
|
315
|
+
|
316
|
+
def has_default?
|
317
|
+
false
|
318
|
+
end
|
319
|
+
|
320
|
+
def usage
|
321
|
+
sprintf('-%-2s --%-10s %s', @short, @long, @description)
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
data/spec/clip_spec.rb
ADDED
@@ -0,0 +1,269 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/../lib/clip"
|
2
|
+
require "rubygems"
|
3
|
+
require "spec"
|
4
|
+
|
5
|
+
class HaveErrors
|
6
|
+
|
7
|
+
def matches?(target)
|
8
|
+
@target = target
|
9
|
+
not @target.errors.empty?
|
10
|
+
end
|
11
|
+
|
12
|
+
def failure_message
|
13
|
+
"expected #{@target} to have errors"
|
14
|
+
end
|
15
|
+
|
16
|
+
def negative_failure_message
|
17
|
+
"expected #{@target} to have no errors, but... #{@target.errors.inspect}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def have_errors
|
22
|
+
HaveErrors.new
|
23
|
+
end
|
24
|
+
|
25
|
+
class HaveErrorsOn
|
26
|
+
def initialize(expected)
|
27
|
+
@expected = expected
|
28
|
+
end
|
29
|
+
|
30
|
+
def matches?(target)
|
31
|
+
@target = target
|
32
|
+
not @target.errors[@expected.to_sym].nil?
|
33
|
+
end
|
34
|
+
|
35
|
+
def failure_message
|
36
|
+
"expected error message for #{@expected} on #{@target}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def negative_failure_message
|
40
|
+
"unexpected error message for #{@expected} on #{@target}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def have_errors_on(expected)
|
45
|
+
HaveErrorsOn.new(expected)
|
46
|
+
end
|
47
|
+
|
48
|
+
describe Clip do
|
49
|
+
|
50
|
+
def parse(line)
|
51
|
+
Clip(line) do |p|
|
52
|
+
p.flag 'v', 'verbose', :desc => 'Provide verbose output'
|
53
|
+
p.optional 's', 'server', :desc => 'The hostname', :default => 'localhost'
|
54
|
+
p.optional 'p', 'port', :desc => 'The port number', :default => 8080
|
55
|
+
p.required 'f', 'files', :desc => 'Files to upload', :multi => true
|
56
|
+
p.optional 'e', 'exclude_from', :desc => 'Directories to exclude'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "When long command-line parameters are parsed" do
|
61
|
+
|
62
|
+
it "should create accessor methods for declarations" do
|
63
|
+
parser = parse('')
|
64
|
+
parser.should respond_to(:server)
|
65
|
+
parser.should respond_to(:server=)
|
66
|
+
parser.should respond_to(:port)
|
67
|
+
parser.should respond_to(:port)
|
68
|
+
parser.should respond_to(:files)
|
69
|
+
parser.should respond_to(:files=)
|
70
|
+
parser.should respond_to(:verbose?)
|
71
|
+
parser.should respond_to(:flag_verbose)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should set fields for flags to 'true'" do
|
75
|
+
parser = parse('--verbose --files foo')
|
76
|
+
parser.should be_verbose
|
77
|
+
parser.should be_valid
|
78
|
+
parser.should_not have_errors
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should set fields for flags with the given values" do
|
82
|
+
parser = parse('--server localhost --port 8080 --files foo')
|
83
|
+
parser.server.should eql("localhost")
|
84
|
+
parser.port.should eql("8080")
|
85
|
+
parser.should be_valid
|
86
|
+
parser.should_not have_errors
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should map flags with '-' to methods with '_'" do
|
90
|
+
parser = parse('--exclude-from /Users --files foo')
|
91
|
+
parser.exclude_from.should eql("/Users")
|
92
|
+
parser.should be_valid
|
93
|
+
parser.should_not have_errors
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should be invalid for unknown flags" do
|
97
|
+
parser = parse('--non-existent')
|
98
|
+
parser.should_not be_valid
|
99
|
+
parser.should have_errors_on(:non_existent)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "When short (single-letter) command-line parse are parsed" do
|
104
|
+
|
105
|
+
it "should set flags to true" do
|
106
|
+
parser = parse("-v --files foo")
|
107
|
+
parser.should be_verbose
|
108
|
+
parser.should_not have_errors
|
109
|
+
parser.should be_valid
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should set fields for short options" do
|
113
|
+
parser = parse("-s localhost -p 8080 --files foo")
|
114
|
+
parser.should_not have_errors
|
115
|
+
parser.should be_valid
|
116
|
+
parser.server.should eql("localhost")
|
117
|
+
parser.port.should eql("8080")
|
118
|
+
parser.should_not be_verbose
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe "When parameters are marked as required" do
|
123
|
+
|
124
|
+
it "should be invalid when there are missing arguments" do
|
125
|
+
parser = parse('--server localhost')
|
126
|
+
parser.should_not be_valid
|
127
|
+
parser.should have_errors_on(:files)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe "When parameters are marked with defaults" do
|
132
|
+
|
133
|
+
it "should provide default parameter values when none are parsed" do
|
134
|
+
parser = parse('--files foo')
|
135
|
+
parser.should be_valid
|
136
|
+
parser.should_not have_errors
|
137
|
+
parser.server.should eql("localhost")
|
138
|
+
parser.port.should eql(8080)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe "Multi-valued parameters" do
|
143
|
+
|
144
|
+
it "should handle multiple value for the same parameter" do
|
145
|
+
parser = parse("--files foo --files bar --files baz")
|
146
|
+
parser.should be_valid
|
147
|
+
parser.should_not have_errors
|
148
|
+
parser.files.should == %w[foo bar baz]
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should handle comma-separated values as multiples" do
|
152
|
+
parser = parse("--files foo,bar,baz")
|
153
|
+
parser.should be_valid
|
154
|
+
parser.should_not have_errors
|
155
|
+
parser.files.should == %w[foo bar baz]
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe "Help output" do
|
160
|
+
it "should print out some sensible usage info for to_s" do
|
161
|
+
out = parse('--files foo').to_s.split("\n")
|
162
|
+
out[0].should match(/Usage/)
|
163
|
+
out[1].should match(/-v\s+--verbose\s+Provide verbose output/)
|
164
|
+
out[2].should match(/-s\s+--server\s+The hostname.*default.*localhost/)
|
165
|
+
out[3].should match(/-p\s+--port\s+The port number/)
|
166
|
+
out[4].should match(/-f\s+--files\s+Files to upload.*REQUIRED/)
|
167
|
+
out[5].should match(/-e\s+--exclude-from\s+Directories to exclude/)
|
168
|
+
end
|
169
|
+
|
170
|
+
it "should include error messages in to_s" do
|
171
|
+
parser = parse('')
|
172
|
+
out = parser.to_s.split("\n")
|
173
|
+
out[0].should match(/Error/)
|
174
|
+
out[1].should match(/missing required.*files/i)
|
175
|
+
out[2..-1].join("\n").strip.should == parser.help.strip
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
describe "Additional arguments" do
|
180
|
+
it "should be made available" do
|
181
|
+
parser = parse('--files foo alpha bravo')
|
182
|
+
parser.files.should == %w[foo]
|
183
|
+
parser.remainder.should == %w[alpha bravo]
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
describe "Declaring bad options and flags" do
|
188
|
+
|
189
|
+
def misconfig_parser
|
190
|
+
lambda do
|
191
|
+
Clip("foo") do |c|
|
192
|
+
yield c
|
193
|
+
end
|
194
|
+
end.should raise_error(Clip::IllegalConfiguration)
|
195
|
+
end
|
196
|
+
|
197
|
+
it "should reject :help as a flag name" do
|
198
|
+
misconfig_parser { |c| c.flag 'x', 'help' }
|
199
|
+
end
|
200
|
+
|
201
|
+
it "should reject :help as an optional name" do
|
202
|
+
misconfig_parser { |c| c.optional 'x', 'help' }
|
203
|
+
end
|
204
|
+
|
205
|
+
it "should reject 'h' as a short flag name" do
|
206
|
+
misconfig_parser { |c| c.flag 'h', 'foo' }
|
207
|
+
end
|
208
|
+
|
209
|
+
it "should reject 'h' as a short parameter name" do
|
210
|
+
misconfig_parser { |c| c.optional 'h', 'foo' }
|
211
|
+
end
|
212
|
+
|
213
|
+
it "should reject redefining an existing long name for two options" do
|
214
|
+
misconfig_parser do |c|
|
215
|
+
c.optional 'f', 'foo'
|
216
|
+
c.optional 'x', 'foo'
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
it "should reject redefining an existing long name for an option & flag" do
|
221
|
+
misconfig_parser do |c|
|
222
|
+
c.optional 'f', 'foo'
|
223
|
+
c.flag 'x', 'foo'
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
it "should reject redefining the same flag" do
|
228
|
+
misconfig_parser do |c|
|
229
|
+
c.flag 'f', 'foo'
|
230
|
+
c.flag 'x', 'foo'
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
it "should reject defining a flag with an option" do
|
235
|
+
misconfig_parser do |c|
|
236
|
+
c.flag 'f', 'foo'
|
237
|
+
c.optional 'x', 'foo'
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
it "should reject redefining an existing short name for options" do
|
242
|
+
misconfig_parser do |c|
|
243
|
+
c.optional 'f', 'foo'
|
244
|
+
c.optional 'f', 'files'
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
it "should reject redefining a short option with a flag" do
|
249
|
+
misconfig_parser do |c|
|
250
|
+
c.optional 'f', 'foo'
|
251
|
+
c.flag 'f', 'fail'
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
it "should reject redefining a short flag with a flag" do
|
256
|
+
misconfig_parser do |c|
|
257
|
+
c.flag 'f', 'fail'
|
258
|
+
c.flag 'f', 'foo'
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
it "should reject redefining a flag with an optional" do
|
263
|
+
misconfig_parser do |c|
|
264
|
+
c.flag 'f', 'fail'
|
265
|
+
c.optional 'f', 'foo'
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: clip
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alex Vollmer
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-04-10 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: hoe
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.5.1
|
23
|
+
version:
|
24
|
+
description: Command-line parsing made short and sweet
|
25
|
+
email:
|
26
|
+
- alex.vollmer@gmail.com
|
27
|
+
executables:
|
28
|
+
- sample_parser
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- History.txt
|
33
|
+
- README.txt
|
34
|
+
files:
|
35
|
+
- History.txt
|
36
|
+
- README.txt
|
37
|
+
- bin/sample_parser
|
38
|
+
- lib/clip.rb
|
39
|
+
- spec/clip_spec.rb
|
40
|
+
has_rdoc: true
|
41
|
+
homepage: http://clip.rubyforge.org
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options:
|
44
|
+
- --main
|
45
|
+
- README.txt
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
version:
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
version:
|
60
|
+
requirements: []
|
61
|
+
|
62
|
+
rubyforge_project: clip
|
63
|
+
rubygems_version: 1.1.0
|
64
|
+
signing_key:
|
65
|
+
specification_version: 2
|
66
|
+
summary: Command-line parsing made short and sweet
|
67
|
+
test_files: []
|
68
|
+
|