clip 0.0.1
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/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
|
+
|