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.
@@ -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