clip 0.0.6 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,7 @@
1
+ === 0.0.7 / 2008-07-14
2
+
3
+ * remainder now works with Clip.hash method.
4
+
1
5
  === 0.0.6 / 2008-07-10
2
6
 
3
7
  * Fixed a bug with getting the 'remainder' when only flags are declared.
@@ -0,0 +1,7 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.rdoc
4
+ Rakefile
5
+ clip.gemspec
6
+ lib/clip.rb
7
+ spec/clip_spec.rb
@@ -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
- p.flag 'v', 'verbose', :desc => 'Make it chatty'
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
@@ -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
@@ -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
@@ -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.6"
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
- if block
64
- self.class.send(:define_method, "#{long}=".to_sym) do |v|
65
- instance_variable_set(var_name, block.call(v))
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
- else
68
- self.class.send(:define_method, "#{long}=".to_sym) do |v|
69
- instance_variable_set(var_name, v)
86
+
87
+ define_method(long.to_sym) do
88
+ instance_variable_get(var_name)
70
89
  end
71
90
  end
72
91
 
73
- self.class.send(:define_method, long.to_sym) do
74
- instance_variable_get(var_name)
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
- eval <<-EOF
111
- def flag_#{long}
112
- @#{long} = true
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
- def #{long}?
116
- return @#{long} || false
131
+ define_method("#{long}?") do
132
+ instance_variable_get("@#{long}")
117
133
  end
118
- EOF
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.split(/\s+/) unless args.kind_of?(Array)
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(/^-(-)?/, '').sub('-', '_').to_sym
172
+ param = token.sub(/^-(-)?/, '').gsub('-', '_').to_sym
149
173
  option = options[param]
150
- unless option
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
- out << "#{option.usage}\n"
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 == :h
250
- raise IllegalConfiguration.new("You cannot override the built-in 'h' parameter")
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 = /^--?\w+/
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
- def self.hash(argv = ARGV.dup, values = [])
347
- @hash ||= begin
348
- argv.shift until argv.first =~ HASHER_REGEX or argv.empty?
349
- while argv.first =~ HASHER_REGEX and argv.size >= 2 do
350
- values += [argv.shift.sub(/^--?/, ''), argv.shift]
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 Clip.reset_hash!; @hash = nil end
444
+ def self.reset_hash!; @hash = nil end
359
445
  end
@@ -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 flags with the given values" do
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 flags with '-' to methods with '_'" do
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 flags" do
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 parameters are marked as required" do
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 parameters are marked with defaults" do
132
-
133
- it "should provide default parameter values when none are parsed" do
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 parameters" do
160
+ describe "Multi-valued options" do
143
161
 
144
- it "should handle multiple value for the same parameter" do
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
- 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/)
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 made available" do
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 'h' as a short flag name" do
236
- misconfig_parser { |c| c.flag 'h', 'foo' }
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 'h' as a short parameter name" do
240
- misconfig_parser { |c| c.optional 'h', 'foo' }
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 only use pairs of dash + value args" do
320
- Clip.hash(['-c', 'config.yml',
321
- '-d']).should == { 'c' => 'config.yml' }
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.6
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-07-10 00:00:00 -07:00
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
- - README.txt
34
+ - Manifest.txt
35
35
  files:
36
36
  - History.txt
37
- - README.txt
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