clip 0.0.6 → 1.0.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/History.txt +4 -0
- data/Manifest.txt +7 -0
- data/{README.txt → README.rdoc} +10 -3
- data/Rakefile +48 -0
- data/clip.gemspec +32 -0
- data/lib/clip.rb +136 -50
- data/spec/clip_spec.rb +174 -32
- metadata +7 -4
data/History.txt
CHANGED
data/Manifest.txt
ADDED
data/{README.txt → README.rdoc}
RENAMED
@@ -31,8 +31,13 @@ And it goes a little something like this...
|
|
31
31
|
p.optional 'p', 'port', :desc => 'The port', :default => 8080 do |v|
|
32
32
|
v.to_i # always deal with integers
|
33
33
|
end
|
34
|
-
p.required 'f', 'files', :multi => true, :desc => 'Files to send'
|
35
|
-
|
34
|
+
p.required 'f', 'files', :multi => true, :desc => 'Files to send' do |files|
|
35
|
+
files.each do |f|
|
36
|
+
raise("unable to read file #{f}") unless File::readable? f
|
37
|
+
f
|
38
|
+
end
|
39
|
+
end
|
40
|
+
p.flag 'v', 'verbose', :desc => 'Make it chatty'
|
36
41
|
end
|
37
42
|
|
38
43
|
if options.valid?
|
@@ -56,7 +61,9 @@ deal with when you're parsing command-line parameters.
|
|
56
61
|
You can optionally process parsed arguments by passing a block to the
|
57
62
|
<tt>required</tt> or <tt>optional</tt> methods which will set the value of the
|
58
63
|
option to the result of the block. The block will receive the parsed value and
|
59
|
-
should return whatever transformed value that is appropriate to your use case.
|
64
|
+
should return whatever transformed value that is appropriate to your use case. If
|
65
|
+
the passed value fails validation you can raise an error which will be reported
|
66
|
+
correctly.
|
60
67
|
|
61
68
|
Simply invoking the <tt>to_s</tt> method on a parser instance will dump both the
|
62
69
|
correct usage and any errors encountered during parsing. No need for you to manage
|
data/Rakefile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hoe'
|
5
|
+
require './lib/clip.rb'
|
6
|
+
|
7
|
+
Hoe.new('clip', Clip::VERSION) do |p|
|
8
|
+
p.name = 'clip'
|
9
|
+
p.developer('Alex Vollmer', 'alex.vollmer@gmail.com')
|
10
|
+
p.description = p.paragraphs_of('README.rdoc', 5..5).join("\n\n")
|
11
|
+
p.summary = 'Command-line parsing made short and sweet'
|
12
|
+
p.url = 'http://clip.rubyforge.org'
|
13
|
+
p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
|
14
|
+
p.remote_rdoc_dir = ''
|
15
|
+
end
|
16
|
+
|
17
|
+
require "spec/rake/spectask"
|
18
|
+
Spec::Rake::SpecTask.new do |t|
|
19
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
20
|
+
end
|
21
|
+
|
22
|
+
HERE = "web"
|
23
|
+
THERE = "avollmer@clip.rubyforge.org:/var/www/gforge-projects/clip"
|
24
|
+
|
25
|
+
desc "Sync web files here"
|
26
|
+
task :sync_here do
|
27
|
+
puts %x(rsync \
|
28
|
+
--verbose \
|
29
|
+
--recursive \
|
30
|
+
#{THERE}/ #{HERE})
|
31
|
+
end
|
32
|
+
|
33
|
+
desc "Sync web files there"
|
34
|
+
task :sync_there do
|
35
|
+
puts %x(rsync \
|
36
|
+
--verbose \
|
37
|
+
--recursive \
|
38
|
+
--delete \
|
39
|
+
#{HERE}/ #{THERE}})
|
40
|
+
end
|
41
|
+
|
42
|
+
desc "Code statistics"
|
43
|
+
task :stats do
|
44
|
+
require 'code_statistics'
|
45
|
+
CodeStatistics.new(['lib'], ['spec']).to_s
|
46
|
+
end
|
47
|
+
|
48
|
+
task :default => :spec
|
data/clip.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = %q{clip}
|
3
|
+
s.version = "1.0.0"
|
4
|
+
|
5
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
6
|
+
s.authors = ["Alex Vollmer"]
|
7
|
+
s.date = %q{2008-09-19}
|
8
|
+
s.description = %q{You like command-line parsing, but you hate all of the bloat. Why should you have to create a Hash, then create a parser, fill the Hash out then throw the parser away (unless you want to print out a usage message) and deal with a Hash? Why, for Pete's sake, should the parser and the parsed values be handled by two different objects?}
|
9
|
+
s.email = ["alex.vollmer@gmail.com"]
|
10
|
+
s.extra_rdoc_files = ["History.txt", "Manifest.txt"]
|
11
|
+
s.files = ["History.txt", "Manifest.txt", "README.rdoc", "Rakefile", "clip.gemspec", "lib/clip.rb", "spec/clip_spec.rb"]
|
12
|
+
s.has_rdoc = true
|
13
|
+
s.homepage = %q{http://clip.rubyforge.org}
|
14
|
+
s.rdoc_options = ["--main", "README.rdoc"]
|
15
|
+
s.require_paths = ["lib"]
|
16
|
+
s.rubyforge_project = %q{clip}
|
17
|
+
s.rubygems_version = %q{1.2.0}
|
18
|
+
s.summary = %q{Command-line parsing made short and sweet}
|
19
|
+
|
20
|
+
if s.respond_to? :specification_version then
|
21
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
22
|
+
s.specification_version = 2
|
23
|
+
|
24
|
+
if current_version >= 3 then
|
25
|
+
s.add_development_dependency(%q<hoe>, [">= 1.7.0"])
|
26
|
+
else
|
27
|
+
s.add_dependency(%q<hoe>, [">= 1.7.0"])
|
28
|
+
end
|
29
|
+
else
|
30
|
+
s.add_dependency(%q<hoe>, [">= 1.7.0"])
|
31
|
+
end
|
32
|
+
end
|
data/lib/clip.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
require 'shellwords'
|
4
|
+
|
3
5
|
##
|
4
6
|
# Parse arguments (defaults to <tt>ARGV</tt>) with the Clip::Parser
|
5
7
|
# configured in the given block. This is the main method you
|
@@ -13,7 +15,7 @@ def Clip(args=ARGV)
|
|
13
15
|
end
|
14
16
|
|
15
17
|
module Clip
|
16
|
-
VERSION = "0.0
|
18
|
+
VERSION = "1.0.0"
|
17
19
|
|
18
20
|
##
|
19
21
|
# Indicates that the parser was incorrectly configured in the
|
@@ -33,6 +35,16 @@ module Clip
|
|
33
35
|
# If the value is set this completely replaces the default
|
34
36
|
attr_accessor :banner
|
35
37
|
|
38
|
+
##
|
39
|
+
# Override the flag to trigger help usage. By default the short
|
40
|
+
# flag '-h' and long flag '--help' will trigger displaying usage.
|
41
|
+
# If you need to override this, particularly in the case of '-h',
|
42
|
+
# call this method
|
43
|
+
def help_with(short, long="--help")
|
44
|
+
@help_short = short
|
45
|
+
@help_long = long
|
46
|
+
end
|
47
|
+
|
36
48
|
##
|
37
49
|
# Declare an optional parameter for your parser. This creates an accessor
|
38
50
|
# method matching the <tt>long</tt> parameter. The <tt>short</tt> parameter
|
@@ -55,28 +67,33 @@ module Clip
|
|
55
67
|
# comma-separated value can be specified which will then be broken up into
|
56
68
|
# separate tokens.
|
57
69
|
def optional(short, long, options={}, &block)
|
58
|
-
short = short.to_sym
|
59
|
-
long = long.to_sym
|
60
70
|
check_args(short, long)
|
61
71
|
|
72
|
+
short = short.to_sym
|
73
|
+
long = long.gsub('-', '_').to_sym
|
74
|
+
|
62
75
|
var_name = "@#{long}".to_sym
|
63
|
-
|
64
|
-
|
65
|
-
|
76
|
+
self.class.class_eval do
|
77
|
+
define_method("#{long}=".to_sym) do |v|
|
78
|
+
begin
|
79
|
+
v = yield(v) if block_given?
|
80
|
+
instance_variable_set(var_name, v)
|
81
|
+
rescue StandardError => e
|
82
|
+
@valid = false
|
83
|
+
@errors[long] = e.message
|
84
|
+
end
|
66
85
|
end
|
67
|
-
|
68
|
-
|
69
|
-
|
86
|
+
|
87
|
+
define_method(long.to_sym) do
|
88
|
+
instance_variable_get(var_name)
|
70
89
|
end
|
71
90
|
end
|
72
91
|
|
73
|
-
self.
|
74
|
-
|
75
|
-
end
|
92
|
+
self.options[short] = self.options[long] =
|
93
|
+
Option.new(short, long, options)
|
76
94
|
|
77
|
-
self.options[long] = Option.new(short, long, options)
|
78
|
-
self.options[short] = self.options[long]
|
79
95
|
self.order << self.options[long]
|
96
|
+
check_longest(long)
|
80
97
|
end
|
81
98
|
|
82
99
|
alias_method :opt, :optional
|
@@ -102,29 +119,32 @@ module Clip
|
|
102
119
|
# Valid options are:
|
103
120
|
# * <tt>desc</tt>: Descriptive text for the flag
|
104
121
|
def flag(short, long, options={})
|
105
|
-
short = short.to_sym
|
106
|
-
long = long.to_sym
|
107
|
-
|
108
122
|
check_args(short, long)
|
109
123
|
|
110
|
-
|
111
|
-
|
112
|
-
|
124
|
+
short = short.to_sym
|
125
|
+
long = long.gsub('-', '_').to_sym
|
126
|
+
self.class.class_eval do
|
127
|
+
define_method("flag_#{long}") do
|
128
|
+
instance_variable_set("@#{long}", true)
|
113
129
|
end
|
114
130
|
|
115
|
-
|
116
|
-
|
131
|
+
define_method("#{long}?") do
|
132
|
+
instance_variable_get("@#{long}")
|
117
133
|
end
|
118
|
-
|
134
|
+
end
|
119
135
|
|
120
136
|
self.options[long] = Flag.new(short, long, options)
|
121
137
|
self.options[short] = self.options[long]
|
122
138
|
self.order << self.options[long]
|
139
|
+
check_longest(long)
|
123
140
|
end
|
124
141
|
|
125
142
|
def initialize # :nodoc:
|
126
143
|
@errors = {}
|
127
144
|
@valid = true
|
145
|
+
@longest = 10
|
146
|
+
@help_long = "--help"
|
147
|
+
@help_short = "-h"
|
128
148
|
end
|
129
149
|
|
130
150
|
##
|
@@ -133,21 +153,25 @@ module Clip
|
|
133
153
|
# you can get them from the <tt>Hash</tt> returned by the +errors+ method.
|
134
154
|
def parse(args)
|
135
155
|
@valid = true
|
136
|
-
args = args
|
156
|
+
args = Shellwords::shellwords(args) unless args.kind_of?(Array)
|
137
157
|
consumed = []
|
138
|
-
if args.member?("--help")
|
139
|
-
puts help
|
140
|
-
exit 0
|
141
|
-
end
|
142
158
|
option = nil
|
143
|
-
|
159
|
+
|
144
160
|
args.each do |token|
|
145
161
|
case token
|
162
|
+
when @help_long, @help_short
|
163
|
+
puts help
|
164
|
+
exit 0
|
165
|
+
|
166
|
+
when /\A--\z/
|
167
|
+
consumed << token
|
168
|
+
break
|
169
|
+
|
146
170
|
when /^-(-)?\w/
|
147
171
|
consumed << token
|
148
|
-
param = token.sub(/^-(-)?/, '').
|
172
|
+
param = token.sub(/^-(-)?/, '').gsub('-', '_').to_sym
|
149
173
|
option = options[param]
|
150
|
-
|
174
|
+
if option.nil?
|
151
175
|
@errors[param] = "Unrecognized parameter"
|
152
176
|
@valid = false
|
153
177
|
next
|
@@ -199,7 +223,8 @@ module Clip
|
|
199
223
|
end
|
200
224
|
|
201
225
|
##
|
202
|
-
# Returns a formatted <tt>String</tt> indicating the usage of the parser
|
226
|
+
# Returns a formatted <tt>String</tt> indicating the usage of the parser,
|
227
|
+
# formatted to fit within 80 display columns.
|
203
228
|
def help
|
204
229
|
out = ""
|
205
230
|
if banner
|
@@ -209,7 +234,38 @@ module Clip
|
|
209
234
|
end
|
210
235
|
|
211
236
|
order.each do |option|
|
212
|
-
|
237
|
+
line = sprintf("-%-2s --%-#{@longest}s ",
|
238
|
+
option.short,
|
239
|
+
option.long.to_s.gsub('_', '-'))
|
240
|
+
|
241
|
+
out << line
|
242
|
+
if line.length + option.description.length <= 80
|
243
|
+
out << option.description
|
244
|
+
else
|
245
|
+
rem = 80 - line.length
|
246
|
+
desc = option.description
|
247
|
+
i = 0
|
248
|
+
while i < desc.length
|
249
|
+
out << "\n" if i > 0
|
250
|
+
j = [i + rem, desc.length - 1].min
|
251
|
+
while desc[j..j] =~ /[\w\d]/
|
252
|
+
j -= 1
|
253
|
+
end
|
254
|
+
chunk = desc[i..j].strip
|
255
|
+
out << " " * line.length if i > 0
|
256
|
+
out << chunk
|
257
|
+
i = j + 1
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
if option.has_default?
|
262
|
+
out << " (default: #{option.default})"
|
263
|
+
end
|
264
|
+
|
265
|
+
if option.required?
|
266
|
+
out << " REQUIRED"
|
267
|
+
end
|
268
|
+
out << "\n"
|
213
269
|
end
|
214
270
|
out
|
215
271
|
end
|
@@ -237,8 +293,20 @@ module Clip
|
|
237
293
|
(@order ||= [])
|
238
294
|
end
|
239
295
|
|
240
|
-
private
|
296
|
+
private
|
241
297
|
def check_args(short, long)
|
298
|
+
if short.size != 1
|
299
|
+
raise IllegalConfiguration.new("Short options must be a single character.")
|
300
|
+
end
|
301
|
+
|
302
|
+
if short !~ /[\w]+/
|
303
|
+
raise IllegalConfiguration.new("Illegal option: #{short}. Option names can only use [a-zA-Z_-]")
|
304
|
+
end
|
305
|
+
|
306
|
+
if long !~ /\A\w[\w-]*\z/
|
307
|
+
raise IllegalConfiguration.new("Illegal option: #{long}'. Parameter names can only use [a-zA-Z_-]")
|
308
|
+
end
|
309
|
+
|
242
310
|
short = short.to_sym
|
243
311
|
long = long.to_sym
|
244
312
|
|
@@ -246,8 +314,8 @@ module Clip
|
|
246
314
|
raise IllegalConfiguration.new("You cannot override the built-in 'help' parameter")
|
247
315
|
end
|
248
316
|
|
249
|
-
if short ==
|
250
|
-
raise IllegalConfiguration.new("You cannot override the built-in '
|
317
|
+
if short == '?'.to_sym
|
318
|
+
raise IllegalConfiguration.new("You cannot override the built-in '?' parameter")
|
251
319
|
end
|
252
320
|
|
253
321
|
if self.options.has_key?(long)
|
@@ -258,6 +326,11 @@ module Clip
|
|
258
326
|
raise IllegalConfiguration.new("You already have a defined parameter/flag for the short key '#{short}")
|
259
327
|
end
|
260
328
|
end
|
329
|
+
|
330
|
+
def check_longest(name)
|
331
|
+
l = name.to_s.length
|
332
|
+
@longest = l if l > @longest
|
333
|
+
end
|
261
334
|
end
|
262
335
|
|
263
336
|
class Option # :nodoc:
|
@@ -293,7 +366,7 @@ module Clip
|
|
293
366
|
def multi?
|
294
367
|
@multi == true
|
295
368
|
end
|
296
|
-
|
369
|
+
|
297
370
|
def usage
|
298
371
|
out = sprintf('-%-2s --%-10s %s',
|
299
372
|
@short,
|
@@ -306,7 +379,7 @@ module Clip
|
|
306
379
|
end
|
307
380
|
|
308
381
|
class Flag # :nodoc:
|
309
|
-
|
382
|
+
|
310
383
|
attr_accessor :long, :short, :description
|
311
384
|
|
312
385
|
##
|
@@ -328,13 +401,9 @@ module Clip
|
|
328
401
|
def has_default?
|
329
402
|
false
|
330
403
|
end
|
331
|
-
|
332
|
-
def usage
|
333
|
-
sprintf('-%-2s --%-10s %s', @short, @long, @description)
|
334
|
-
end
|
335
404
|
end
|
336
405
|
|
337
|
-
HASHER_REGEX =
|
406
|
+
HASHER_REGEX = /^--?(\w+)/
|
338
407
|
##
|
339
408
|
# Turns ARGV into a hash.
|
340
409
|
#
|
@@ -343,17 +412,34 @@ module Clip
|
|
343
412
|
# my_clip_script com -c config.yml -d # Clip.hash == { 'c' => 'config.yml' }
|
344
413
|
# my_clip_script -c config.yml --mode optimistic
|
345
414
|
# # Clip.hash == { 'c' => 'config.yml', 'mode' => 'optimistic' }
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
415
|
+
#
|
416
|
+
# The returned hash also has a +remainder+ method that contains
|
417
|
+
# unparsed values.
|
418
|
+
#
|
419
|
+
def self.hash(argv = ARGV.dup, keys = [])
|
420
|
+
return @hash if @hash # used the cached value if available
|
421
|
+
|
422
|
+
opts = Clip(argv) do |clip|
|
423
|
+
keys = argv.select{ |a| a =~ HASHER_REGEX }.map do |a|
|
424
|
+
a = a.sub(HASHER_REGEX, '\\1')
|
425
|
+
clip.optional(a[0,1], a); a
|
351
426
|
end
|
352
|
-
Hash[*values]
|
353
427
|
end
|
428
|
+
|
429
|
+
# The "|| true" on the end is for when no value is found for a
|
430
|
+
# key; it's assumed that a flag was meant instead of an optional
|
431
|
+
# argument, so it's set to true. A bit weird-looking, but more useful.
|
432
|
+
@hash = keys.inject({}) { |h, key| h.merge(key => opts.send(key) || true) }
|
433
|
+
|
434
|
+
# module_eval is necessary to define a singleton method using a closure =\
|
435
|
+
(class << @hash; self; end).module_eval do
|
436
|
+
define_method(:remainder) { opts.remainder }
|
437
|
+
end
|
438
|
+
|
439
|
+
return @hash
|
354
440
|
end
|
355
441
|
|
356
442
|
##
|
357
443
|
# Clear the cached hash value. Probably only useful for tests, but whatever.
|
358
|
-
def
|
444
|
+
def self.reset_hash!; @hash = nil end
|
359
445
|
end
|
data/spec/clip_spec.rb
CHANGED
@@ -54,10 +54,13 @@ describe Clip do
|
|
54
54
|
p.optional 'p', 'port', :desc => 'The port number', :default => 8080
|
55
55
|
p.required 'f', 'files', :desc => 'Files to upload', :multi => true
|
56
56
|
p.optional 'e', 'exclude_from', :desc => 'Directories to exclude'
|
57
|
+
p.optional 'x', 'exclude_from_all', :desc => 'Directories to exclude'
|
58
|
+
p.flag 'd', 'allow-dashes', :desc => 'Dashes allowed in definition'
|
59
|
+
p.flag 'z', 'allow-dashes-all', :desc => 'Dashes allowed in definition'
|
57
60
|
end
|
58
61
|
end
|
59
62
|
|
60
|
-
describe "When long command-line parameters are parsed" do
|
63
|
+
describe "When long command-line parameters are parsed" do
|
61
64
|
|
62
65
|
it "should create accessor methods for declarations" do
|
63
66
|
parser = parse('')
|
@@ -69,6 +72,8 @@ describe Clip do
|
|
69
72
|
parser.should respond_to(:files=)
|
70
73
|
parser.should respond_to(:verbose?)
|
71
74
|
parser.should respond_to(:flag_verbose)
|
75
|
+
parser.should respond_to(:allow_dashes?)
|
76
|
+
parser.should respond_to(:allow_dashes_all?)
|
72
77
|
end
|
73
78
|
|
74
79
|
it "should set fields for flags to 'true'" do
|
@@ -77,8 +82,8 @@ describe Clip do
|
|
77
82
|
parser.should be_valid
|
78
83
|
parser.should_not have_errors
|
79
84
|
end
|
80
|
-
|
81
|
-
it "should set fields for
|
85
|
+
|
86
|
+
it "should set fields for options with the given values" do
|
82
87
|
parser = parse('--server localhost --port 8080 --files foo')
|
83
88
|
parser.server.should eql("localhost")
|
84
89
|
parser.port.should eql("8080")
|
@@ -86,14 +91,27 @@ describe Clip do
|
|
86
91
|
parser.should_not have_errors
|
87
92
|
end
|
88
93
|
|
89
|
-
it "should map
|
94
|
+
it "should map options with '-' to methods with '_'" do
|
90
95
|
parser = parse('--exclude-from /Users --files foo')
|
91
96
|
parser.exclude_from.should eql("/Users")
|
92
97
|
parser.should be_valid
|
93
98
|
parser.should_not have_errors
|
94
99
|
end
|
100
|
+
|
101
|
+
it "should map flags with '-' to methods with '_'" do
|
102
|
+
parser = parse('--allow-dashes')
|
103
|
+
parser.should be_allow_dashes
|
104
|
+
parser.should_not be_allow_dashes_all
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should map options with multiple '-' to methods with '_'" do
|
108
|
+
parser = parse('--exclude-from-all /Users --files foo')
|
109
|
+
parser.exclude_from_all.should eql("/Users")
|
110
|
+
parser.should be_valid
|
111
|
+
parser.should_not have_errors
|
112
|
+
end
|
95
113
|
|
96
|
-
it "should be invalid for unknown
|
114
|
+
it "should be invalid for unknown options" do
|
97
115
|
parser = parse('--non-existent')
|
98
116
|
parser.should_not be_valid
|
99
117
|
parser.should have_errors_on(:non_existent)
|
@@ -101,14 +119,14 @@ describe Clip do
|
|
101
119
|
end
|
102
120
|
|
103
121
|
describe "When short (single-letter) command-line parse are parsed" do
|
104
|
-
|
122
|
+
|
105
123
|
it "should set flags to true" do
|
106
124
|
parser = parse("-v --files foo")
|
107
125
|
parser.should be_verbose
|
108
126
|
parser.should_not have_errors
|
109
127
|
parser.should be_valid
|
110
128
|
end
|
111
|
-
|
129
|
+
|
112
130
|
it "should set fields for short options" do
|
113
131
|
parser = parse("-s localhost -p 8080 --files foo")
|
114
132
|
parser.should_not have_errors
|
@@ -119,8 +137,8 @@ describe Clip do
|
|
119
137
|
end
|
120
138
|
end
|
121
139
|
|
122
|
-
describe "When
|
123
|
-
|
140
|
+
describe "When options are marked as required" do
|
141
|
+
|
124
142
|
it "should be invalid when there are missing arguments" do
|
125
143
|
parser = parse('--server localhost')
|
126
144
|
parser.should_not be_valid
|
@@ -128,9 +146,9 @@ describe Clip do
|
|
128
146
|
end
|
129
147
|
end
|
130
148
|
|
131
|
-
describe "When
|
132
|
-
|
133
|
-
it "should provide default
|
149
|
+
describe "When options are marked with defaults" do
|
150
|
+
|
151
|
+
it "should provide default option values when none are parsed" do
|
134
152
|
parser = parse('--files foo')
|
135
153
|
parser.should be_valid
|
136
154
|
parser.should_not have_errors
|
@@ -139,9 +157,9 @@ describe Clip do
|
|
139
157
|
end
|
140
158
|
end
|
141
159
|
|
142
|
-
describe "Multi-valued
|
160
|
+
describe "Multi-valued options" do
|
143
161
|
|
144
|
-
it "should handle multiple value for the same
|
162
|
+
it "should handle multiple value for the same option" do
|
145
163
|
parser = parse("--files foo --files bar --files baz")
|
146
164
|
parser.should be_valid
|
147
165
|
parser.should_not have_errors
|
@@ -157,14 +175,43 @@ describe Clip do
|
|
157
175
|
end
|
158
176
|
|
159
177
|
describe "Help output" do
|
178
|
+
def opts(args=nil)
|
179
|
+
Clip(args) do |o|
|
180
|
+
o.req 's', 'server', :desc => 'The server name'
|
181
|
+
o.opt 'p', 'port', :desc => 'The port number', :default => 80
|
182
|
+
o.flag 'v', 'verbose', :desc => 'Enables verbose output'
|
183
|
+
yield o if block_given?
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
160
187
|
it "should print out some sensible usage info for to_s" do
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
188
|
+
help = opts("-s localhost").to_s.split("\n")
|
189
|
+
help[0].should match(/Usage/)
|
190
|
+
help[1].should == "-s --server The server name REQUIRED"
|
191
|
+
help[2].should == "-p --port The port number (default: 80)"
|
192
|
+
help[3].should == "-v --verbose Enables verbose output"
|
193
|
+
end
|
194
|
+
|
195
|
+
it "should expand columns to largest option name" do
|
196
|
+
help = opts("-s localhost") do |o|
|
197
|
+
o.opt 'b', 'big-honking-file-list', :desc => 'The file list'
|
198
|
+
end.to_s.split("\n")
|
199
|
+
help[0].should match(/Usage/)
|
200
|
+
help[1].should == "-s --server The server name REQUIRED"
|
201
|
+
help[2].should == "-p --port The port number (default: 80)"
|
202
|
+
help[3].should == "-v --verbose Enables verbose output"
|
203
|
+
help[4].should == "-b --big-honking-file-list The file list"
|
204
|
+
end
|
205
|
+
|
206
|
+
it "should wrap column descriptions to fit 80 columns" do
|
207
|
+
opts = Clip do |o|
|
208
|
+
o.opt 'd', 'description',
|
209
|
+
:desc => 'An unending stream of words that goes on endlessly for an eternity until, at last, they stop.',
|
210
|
+
:default => 'wordy'
|
211
|
+
end
|
212
|
+
help = opts.to_s.split("\n")
|
213
|
+
help[1].should == "-d --description An unending stream of words that goes on endlessly for an"
|
214
|
+
help[2].should == " eternity until, at last, they stop. (default: wordy)"
|
168
215
|
end
|
169
216
|
|
170
217
|
it "should include error messages in to_s" do
|
@@ -184,15 +231,31 @@ describe Clip do
|
|
184
231
|
out = opts.to_s.split("\n")
|
185
232
|
out[0].should == 'USAGE foo bar baz'
|
186
233
|
end
|
234
|
+
|
235
|
+
it "should support overriding help flags" do
|
236
|
+
opts = Clip('-?') do |p|
|
237
|
+
p.opt 'h', 'host', :desc => 'The hostname'
|
238
|
+
p.help_with '?'
|
239
|
+
end
|
240
|
+
help = opts.to_s.split("\n")
|
241
|
+
help[0].should match(/Usage/)
|
242
|
+
help[1].should match(/-h\s+--host\s+The hostname/)
|
243
|
+
end
|
187
244
|
end
|
188
245
|
|
189
246
|
describe "Remaining arguments" do
|
190
|
-
it "should be
|
247
|
+
it "should be taken following parsed arguments" do
|
191
248
|
parser = parse('--files foo alpha bravo')
|
192
249
|
parser.files.should == %w[foo]
|
193
250
|
parser.remainder.should == %w[alpha bravo]
|
194
251
|
end
|
195
252
|
|
253
|
+
it "should be taken preceeding parsed arguments" do
|
254
|
+
parser = parse('alpha bravo --files foo')
|
255
|
+
parser.files.should == %w[foo]
|
256
|
+
parser.remainder.should == %w[alpha bravo]
|
257
|
+
end
|
258
|
+
|
196
259
|
it "should be available when only flags are declared" do
|
197
260
|
opts = Clip('foobar') do |p|
|
198
261
|
p.flag 'v', 'verbose'
|
@@ -212,6 +275,32 @@ describe Clip do
|
|
212
275
|
opts.should be_verbose
|
213
276
|
opts.should_not be_debug
|
214
277
|
end
|
278
|
+
|
279
|
+
it "Should handle quoted strings correctly" do
|
280
|
+
opts = Clip(%q(-- "param 1" 'param 2' param\ 3)) {|p|}
|
281
|
+
opts.remainder.should include('param 1', 'param 2', 'param 3')
|
282
|
+
end
|
283
|
+
end
|
284
|
+
describe "Remaining arguments for Clip.hash" do
|
285
|
+
setup { Clip.reset_hash! }
|
286
|
+
|
287
|
+
it "should be populated" do
|
288
|
+
Clip.hash(['captain', 'lieutenant', '-c', 'jorge']).remainder.
|
289
|
+
should == ['captain', 'lieutenant']
|
290
|
+
end
|
291
|
+
|
292
|
+
it "should be empty for an empty arg list" do
|
293
|
+
Clip.hash([]).remainder.should be_empty
|
294
|
+
end
|
295
|
+
|
296
|
+
it "should be empty for a completely-parsed arg list" do
|
297
|
+
Clip.hash(['-c', '/etc/clip.yml']).remainder.should be_empty
|
298
|
+
end
|
299
|
+
|
300
|
+
it "should be the arg list for an unparsed arg list" do
|
301
|
+
Clip.hash(['git', 'bzr', 'hg', 'darcs', 'arch']).remainder.
|
302
|
+
should == ['git', 'bzr', 'hg', 'darcs', 'arch']
|
303
|
+
end
|
215
304
|
end
|
216
305
|
|
217
306
|
describe "Declaring bad options and flags" do
|
@@ -224,6 +313,26 @@ describe Clip do
|
|
224
313
|
end.should raise_error(Clip::IllegalConfiguration)
|
225
314
|
end
|
226
315
|
|
316
|
+
it "should reject '-' as a short option" do
|
317
|
+
misconfig_parser { |c| c.flag '-', 'foo' }
|
318
|
+
end
|
319
|
+
|
320
|
+
it "should reject long parameters that do not start with a word character" do
|
321
|
+
misconfig_parser { |c| c.optional 'o', '-foo' }
|
322
|
+
misconfig_parser { |c| c.optional 'o', '-' }
|
323
|
+
end
|
324
|
+
|
325
|
+
it "should reject options with non-word characters '-' ok" do
|
326
|
+
misconfig_parser { |c| c.flag '#', 'count' }
|
327
|
+
misconfig_parser { |c| c.flag 'c', 'bang!' }
|
328
|
+
misconfig_parser { |c| c.optional '#', 'count' }
|
329
|
+
misconfig_parser { |c| c.optional 'c', 'bang!' }
|
330
|
+
end
|
331
|
+
|
332
|
+
it "should reject short params that aren't single characters" do
|
333
|
+
misconfig_parser { |c| c.optional 'foo', 'foo' }
|
334
|
+
end
|
335
|
+
|
227
336
|
it "should reject :help as a flag name" do
|
228
337
|
misconfig_parser { |c| c.flag 'x', 'help' }
|
229
338
|
end
|
@@ -232,12 +341,12 @@ describe Clip do
|
|
232
341
|
misconfig_parser { |c| c.optional 'x', 'help' }
|
233
342
|
end
|
234
343
|
|
235
|
-
it "should reject '
|
236
|
-
misconfig_parser { |c| c.flag '
|
344
|
+
it "should reject '?' as a short flag name" do
|
345
|
+
misconfig_parser { |c| c.flag '?', 'foo' }
|
237
346
|
end
|
238
347
|
|
239
|
-
it "should reject '
|
240
|
-
misconfig_parser { |c| c.optional '
|
348
|
+
it "should reject '?' as a short parameter name" do
|
349
|
+
misconfig_parser { |c| c.optional '?', 'foo' }
|
241
350
|
end
|
242
351
|
|
243
352
|
it "should reject redefining an existing long name for two options" do
|
@@ -304,21 +413,31 @@ describe Clip do
|
|
304
413
|
v.to_i
|
305
414
|
end
|
306
415
|
end
|
307
|
-
|
416
|
+
|
308
417
|
opts.value.should == 123
|
309
418
|
end
|
419
|
+
|
420
|
+
it "should trap exceptions from block and report error" do
|
421
|
+
opts = Clip("-v 123") do |c|
|
422
|
+
c.req('v', 'value') {|v| raise("a good error message") }
|
423
|
+
end
|
424
|
+
|
425
|
+
opts.should_not be_valid
|
426
|
+
opts.should have_errors
|
427
|
+
opts.should have_errors_on(:value)
|
428
|
+
end
|
310
429
|
end
|
311
430
|
|
312
431
|
describe "when parsing ARGV as a hash" do
|
313
432
|
setup { Clip.reset_hash! }
|
314
|
-
|
433
|
+
|
315
434
|
it "should make sense of '-c my_config.yml'" do
|
316
435
|
Clip.hash(['-c', 'config.yml']).should == { 'c' => 'config.yml' }
|
317
436
|
end
|
318
437
|
|
319
|
-
it "should
|
320
|
-
Clip.hash(['-c', 'config.yml',
|
321
|
-
|
438
|
+
it "should treat flag-style arguments as booleans" do
|
439
|
+
Clip.hash(['-f', '-c', 'config.yml', '-d']).
|
440
|
+
should == { 'c' => 'config.yml', 'd' => true, 'f' => true }
|
322
441
|
end
|
323
442
|
|
324
443
|
it "should ignore leading/trailing non-dashed arguments" do
|
@@ -330,9 +449,32 @@ describe Clip do
|
|
330
449
|
Clip.hash(['-c', 'config.yml', '--mode', 'optimistic']).
|
331
450
|
should == { 'c' => 'config.yml', 'mode' => 'optimistic' }
|
332
451
|
end
|
333
|
-
|
452
|
+
|
334
453
|
it "should return an empty hash for empty ARGV" do
|
335
454
|
Clip.hash([]).should == {}
|
336
455
|
end
|
337
456
|
end
|
457
|
+
|
458
|
+
describe "stopping parsing after finding --" do
|
459
|
+
it "should not blow up" do
|
460
|
+
opts = Clip('--') {|p|}
|
461
|
+
opts.should be_valid
|
462
|
+
opts.remainder.should be_empty
|
463
|
+
end
|
464
|
+
|
465
|
+
it "should not parse after --" do
|
466
|
+
opts = Clip('-- --help') {|p|}
|
467
|
+
opts.should be_valid
|
468
|
+
opts.remainder.should include('--help')
|
469
|
+
end
|
470
|
+
|
471
|
+
it "should parse args before --" do
|
472
|
+
opts = Clip('-v -- other stuff') do |p|
|
473
|
+
p.flag 'v', 'verbose'
|
474
|
+
end
|
475
|
+
opts.should be_valid
|
476
|
+
opts.should be_verbose
|
477
|
+
opts.remainder.should include('other', 'stuff')
|
478
|
+
end
|
479
|
+
end
|
338
480
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: clip
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alex Vollmer
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-
|
12
|
+
date: 2008-09-19 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -31,10 +31,13 @@ extensions: []
|
|
31
31
|
|
32
32
|
extra_rdoc_files:
|
33
33
|
- History.txt
|
34
|
-
-
|
34
|
+
- Manifest.txt
|
35
35
|
files:
|
36
36
|
- History.txt
|
37
|
-
-
|
37
|
+
- Manifest.txt
|
38
|
+
- README.rdoc
|
39
|
+
- Rakefile
|
40
|
+
- clip.gemspec
|
38
41
|
- lib/clip.rb
|
39
42
|
- spec/clip_spec.rb
|
40
43
|
has_rdoc: true
|