Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge branch 'master' of git@github.com:alexvollmer/clip

Conflicts:
	spec/clip_spec.rb
  • Loading branch information...
commit 53d4fcc833b3bb7f4c2c9fb1bb9adea42b2f713e 2 parents 7eb0d7c + d8a8656
@alexvollmer authored
View
11 History.txt
@@ -1,3 +1,14 @@
+=== 1.0.0 / 2008-09-19
+
+* Added support for mapping dashes to underscores for flags
+* Define Clip.hash.remainder as a singleton method instead of reopening Hash
+* remainder works with Clip.hash now
+* Reimplemented Clip.hash to use a parser.
+
+=== 0.0.7 / 2008-07-14
+
+* remainder now works with Clip.hash method.
+
=== 0.0.6 / 2008-07-10
* Fixed a bug with getting the 'remainder' when only flags are declared.
View
3  Manifest.txt
@@ -1,4 +1,7 @@
History.txt
+Manifest.txt
README.txt
+Rakefile
+clip.gemspec
lib/clip.rb
spec/clip_spec.rb
View
8 Rakefile
@@ -8,7 +8,7 @@ Hoe.new('clip', Clip::VERSION) do |p|
p.name = 'clip'
p.developer('Alex Vollmer', 'alex.vollmer@gmail.com')
p.description = p.paragraphs_of('README.txt', 5..5).join("\n\n")
- p.summary = 'Command-line parsing made short and sweet'
+ p.summary = 'Command-line parsing made short and sweet'
p.url = 'http://clip.rubyforge.org'
p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
p.remote_rdoc_dir = ''
@@ -39,4 +39,10 @@ task :sync_there do
#{HERE}/ #{THERE}})
end
+desc "Code statistics"
+task :stats do
+ require 'code_statistics'
+ CodeStatistics.new(['lib'], ['spec']).to_s
+end
+
task :default => :spec
View
14 clip.gemspec
@@ -1,14 +1,14 @@
Gem::Specification.new do |s|
s.name = %q{clip}
- s.version = "0.0.6"
+ s.version = "1.0.0"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Alex Vollmer"]
- s.date = %q{2008-07-14}
+ s.date = %q{2008-09-19}
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?}
s.email = ["alex.vollmer@gmail.com"]
- s.extra_rdoc_files = ["History.txt", "README.txt"]
- s.files = ["History.txt", "README.txt", "lib/clip.rb", "spec/clip_spec.rb"]
+ s.extra_rdoc_files = ["History.txt", "Manifest.txt"]
+ s.files = ["History.txt", "Manifest.txt", "README.txt", "Rakefile", "clip.gemspec", "lib/clip.rb", "spec/clip_spec.rb"]
s.has_rdoc = true
s.homepage = %q{http://clip.rubyforge.org}
s.rdoc_options = ["--main", "README.txt"]
@@ -22,11 +22,11 @@ Gem::Specification.new do |s|
s.specification_version = 2
if current_version >= 3 then
- s.add_runtime_dependency(%q<hoe>, [">= 1.6.0"])
+ s.add_development_dependency(%q<hoe>, [">= 1.7.0"])
else
- s.add_dependency(%q<hoe>, [">= 1.6.0"])
+ s.add_dependency(%q<hoe>, [">= 1.7.0"])
end
else
- s.add_dependency(%q<hoe>, [">= 1.6.0"])
+ s.add_dependency(%q<hoe>, [">= 1.7.0"])
end
end
View
70 lib/clip.rb
@@ -15,7 +15,7 @@ def Clip(args=ARGV)
end
module Clip
- VERSION = "0.0.6"
+ VERSION = "1.0.0"
##
# Indicates that the parser was incorrectly configured in the
@@ -36,6 +36,16 @@ class Parser
attr_accessor :banner
##
+ # Override the flag to trigger help usage. By default the short
+ # flag '-h' and long flag '--help' will trigger displaying usage.
+ # If you need to override this, particularly in the case of '-h',
+ # call this method
+ def help_with(short, long="--help")
+ @help_short = short
+ @help_long = long
+ end
+
+ ##
# Declare an optional parameter for your parser. This creates an accessor
# method matching the <tt>long</tt> parameter. The <tt>short</tt> parameter
# indicates the single-letter equivalent. Options that use the '-'
@@ -79,8 +89,9 @@ def optional(short, long, options={}, &block)
end
end
- self.options[long] = Option.new(short, long, options)
- self.options[short] = self.options[long]
+ self.options[short] = self.options[long] =
+ Option.new(short, long, options)
+
self.order << self.options[long]
check_longest(long)
end
@@ -112,15 +123,15 @@ def flag(short, long, options={})
short = short.to_sym
long = long.gsub('-', '_').to_sym
- eval <<-EOF
- def flag_#{long}
- @#{long} = true
+ self.class.class_eval do
+ define_method("flag_#{long}") do
+ instance_variable_set("@#{long}", true)
end
- def #{long}?
- return @#{long} || false
+ define_method("#{long}?") do
+ instance_variable_get("@#{long}")
end
- EOF
+ end
self.options[long] = Flag.new(short, long, options)
self.options[short] = self.options[long]
@@ -132,6 +143,8 @@ def initialize # :nodoc:
@errors = {}
@valid = true
@longest = 10
+ @help_long = "--help"
+ @help_short = "-h"
end
##
@@ -146,7 +159,7 @@ def parse(args)
args.each do |token|
case token
- when '--help', '-?'
+ when @help_long, @help_short
puts help
exit 0
@@ -158,7 +171,7 @@ def parse(args)
consumed << token
param = token.sub(/^-(-)?/, '').gsub('-', '_').to_sym
option = options[param]
- unless option
+ if option.nil?
@errors[param] = "Unrecognized parameter"
@valid = false
next
@@ -290,7 +303,7 @@ def check_args(short, long)
raise IllegalConfiguration.new("Illegal option: #{short}. Option names can only use [a-zA-Z_-]")
end
- if long !~ /\A\w[\w-]+\z/
+ if long !~ /\A\w[\w-]*\z/
raise IllegalConfiguration.new("Illegal option: #{long}'. Parameter names can only use [a-zA-Z_-]")
end
@@ -390,7 +403,7 @@ def has_default?
end
end
- HASHER_REGEX = /^--?\w+/
+ HASHER_REGEX = /^--?(\w+)/
##
# Turns ARGV into a hash.
#
@@ -399,17 +412,34 @@ def has_default?
# my_clip_script com -c config.yml -d # Clip.hash == { 'c' => 'config.yml' }
# my_clip_script -c config.yml --mode optimistic
# # Clip.hash == { 'c' => 'config.yml', 'mode' => 'optimistic' }
- def self.hash(argv = ARGV.dup, values = [])
- @hash ||= begin
- argv.shift until argv.first =~ HASHER_REGEX or argv.empty?
- while argv.first =~ HASHER_REGEX and argv.size >= 2 do
- values += [argv.shift.sub(/^--?/, ''), argv.shift]
+ #
+ # The returned hash also has a +remainder+ method that contains
+ # unparsed values.
+ #
+ def self.hash(argv = ARGV.dup, keys = [])
+ return @hash if @hash # used the cached value if available
+
+ opts = Clip(argv) do |clip|
+ keys = argv.select{ |a| a =~ HASHER_REGEX }.map do |a|
+ a = a.sub(HASHER_REGEX, '\\1')
+ clip.optional(a[0,1], a); a
end
- Hash[*values]
end
+
+ # The "|| true" on the end is for when no value is found for a
+ # key; it's assumed that a flag was meant instead of an optional
+ # argument, so it's set to true. A bit weird-looking, but more useful.
+ @hash = keys.inject({}) { |h, key| h.merge(key => opts.send(key) || true) }
+
+ # module_eval is necessary to define a singleton method using a closure =\
+ (class << @hash; self; end).module_eval do
+ define_method(:remainder) { opts.remainder }
+ end
+
+ return @hash
end
##
# Clear the cached hash value. Probably only useful for tests, but whatever.
- def Clip.reset_hash!; @hash = nil end
+ def self.reset_hash!; @hash = nil end
end
View
70 spec/clip_spec.rb
@@ -56,8 +56,8 @@ def parse(line)
p.required 'f', 'files', :desc => 'Files to upload', :multi => true
p.optional 'e', 'exclude_from', :desc => 'Directories to exclude'
p.optional 'x', 'exclude_from_all', :desc => 'Directories to exclude'
- p.optional 'd', 'allow-dashes', :desc => 'Dashes allowed in definition'
- p.optional 'z', 'allow-dashes-all', :desc => 'Dashes allowed in definition'
+ p.flag 'd', 'allow-dashes', :desc => 'Dashes allowed in definition'
+ p.flag 'z', 'allow-dashes-all', :desc => 'Dashes allowed in definition'
end
end
@@ -73,8 +73,8 @@ def parse(line)
parser.should respond_to(:files=)
parser.should respond_to(:verbose?)
parser.should respond_to(:flag_verbose)
- parser.should respond_to(:allow_dashes)
- parser.should respond_to(:allow_dashes_all)
+ parser.should respond_to(:allow_dashes?)
+ parser.should respond_to(:allow_dashes_all?)
end
it "should set fields for flags to 'true'" do
@@ -92,21 +92,27 @@ def parse(line)
parser.should_not have_errors
end
- it "should map flags with '-' to methods with '_'" do
+ it "should map options with '-' to methods with '_'" do
parser = parse('--exclude-from /Users --files foo')
parser.exclude_from.should eql("/Users")
parser.should be_valid
parser.should_not have_errors
end
- it "should map flags with multiple '-' to methods with '_'" do
+ it "should map flags with '-' to methods with '_'" do
+ parser = parse('--allow-dashes')
+ parser.should be_allow_dashes
+ parser.should_not be_allow_dashes_all
+ end
+
+ it "should map options with multiple '-' to methods with '_'" do
parser = parse('--exclude-from-all /Users --files foo')
parser.exclude_from_all.should eql("/Users")
parser.should be_valid
parser.should_not have_errors
end
- it "should be invalid for unknown flags" do
+ it "should be invalid for unknown options" do
parser = parse('--non-existent')
parser.should_not be_valid
parser.should have_errors_on(:non_existent)
@@ -141,6 +147,7 @@ def parse(line)
end
end
+
describe "When parameters are marked with defaults" do
it "should provide default parameter values when none are parsed" do
@@ -152,9 +159,9 @@ def parse(line)
end
end
- describe "Multi-valued parameters" do
+ describe "Multi-valued options" do
- it "should handle multiple value for the same parameter" do
+ it "should handle multiple value for the same option" do
parser = parse("--files foo --files bar --files baz")
parser.should be_valid
parser.should_not have_errors
@@ -243,15 +250,31 @@ def opts(args=nil)
help[2].should == "-l --live-site Query live wikipedia site for wiki text instead of DB"
help[3].should == " session"
end
+
+ it "should support overriding help flags" do
+ opts = Clip('-?') do |p|
+ p.opt 'h', 'host', :desc => 'The hostname'
+ p.help_with '?'
+ end
+ help = opts.to_s.split("\n")
+ help[0].should match(/Usage/)
+ help[1].should match(/-h\s+--host\s+The hostname/)
+ end
end
describe "Remaining arguments" do
- it "should be made available" do
+ it "should be taken following parsed arguments" do
parser = parse('--files foo alpha bravo')
parser.files.should == %w[foo]
parser.remainder.should == %w[alpha bravo]
end
+ it "should be taken preceeding parsed arguments" do
+ parser = parse('alpha bravo --files foo')
+ parser.files.should == %w[foo]
+ parser.remainder.should == %w[alpha bravo]
+ end
+
it "should be available when only flags are declared" do
opts = Clip('foobar') do |p|
p.flag 'v', 'verbose'
@@ -277,6 +300,27 @@ def opts(args=nil)
opts.remainder.should include('param 1', 'param 2', 'param 3')
end
end
+ describe "Remaining arguments for Clip.hash" do
+ setup { Clip.reset_hash! }
+
+ it "should be populated" do
+ Clip.hash(['captain', 'lieutenant', '-c', 'jorge']).remainder.
+ should == ['captain', 'lieutenant']
+ end
+
+ it "should be empty for an empty arg list" do
+ Clip.hash([]).remainder.should be_empty
+ end
+
+ it "should be empty for a completely-parsed arg list" do
+ Clip.hash(['-c', '/etc/clip.yml']).remainder.should be_empty
+ end
+
+ it "should be the arg list for an unparsed arg list" do
+ Clip.hash(['git', 'bzr', 'hg', 'darcs', 'arch']).remainder.
+ should == ['git', 'bzr', 'hg', 'darcs', 'arch']
+ end
+ end
describe "Declaring bad options and flags" do
@@ -410,9 +454,9 @@ def misconfig_parser
Clip.hash(['-c', 'config.yml']).should == { 'c' => 'config.yml' }
end
- it "should only use pairs of dash + value args" do
- Clip.hash(['-c', 'config.yml',
- '-d']).should == { 'c' => 'config.yml' }
+ it "should treat flag-style arguments as booleans" do
+ Clip.hash(['-f', '-c', 'config.yml', '-d']).
+ should == { 'c' => 'config.yml', 'd' => true, 'f' => true }
end
it "should ignore leading/trailing non-dashed arguments" do
Please sign in to comment.
Something went wrong with that request. Please try again.