Permalink
Browse files

initial commit

  • Loading branch information...
0 parents commit 1c9367b2e765bb6e6f215f4bcc9c9b7bf1456166 Lee Jarvis committed Nov 26, 2010
Showing with 506 additions and 0 deletions.
  1. +3 −0 .gitignore
  2. +20 −0 LICENSE
  3. +98 −0 README.md
  4. +147 −0 lib/slop.rb
  5. +115 −0 lib/slop/option.rb
  6. +14 −0 slop.gemspec
  7. +5 −0 spec/option_spec.rb
  8. +104 −0 spec/slop_spec.rb
3 .gitignore
@@ -0,0 +1,3 @@
+.yardoc
+doc
+*.swp
20 LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2010 Lee Jarvis
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
98 README.md
@@ -0,0 +1,98 @@
+Slop
+====
+
+Slop is a simple option parser with an easy to remember syntax and friendly API.
+
+Installation
+------------
+
+### Rubygems
+
+ gem install slop
+
+### GitHub
+
+ git clone git://github.com/injekt/slop.git
+ cd slop
+ gem build slop.gemspec
+ gem install slop-<version>.gem
+
+Usage
+-----
+
+ s = Slop.parse(ARGV) do
+ option :v, :verbose, "Enable verbose mode", :default => false
+ option :n, :name, "Your name", true # compulsory argument
+ option :c, :country, "Your country", argument => true # the same thing
+
+ option :a, :age, "Your age", true, :optional => true # optional argument
+ option :address, "Your address", :optional => true # the same
+
+ # shortcut option aliases
+ opt :height, "Your height"
+ o :weight, "Your weight
+ end
+
+ # using `--name Lee -a 100`
+ s.options_hash #=> {:verbose=>false, :name=>"Lee", :age=>"100", :address=>nil}
+ s.value_for(:name) #=> "Lee"
+ option = s.option_for(:name)
+ option.description #=> "Your name"
+
+ # You can also use switch values to set options according to arguments
+ s = Slop.parse(ARGV) do
+ option :v, :verbose, :default => false, :switch => true
+ option :applicable_age, :default => 10, :switch => 20
+ end
+
+ # without `-v`
+ s.value_for(:verbose) #=> false
+
+ # using `-v`
+ s.value_for(:verbose) #=> true
+
+ # using `--applicable_age`
+ s.value_for(:applicable_age) #=> 20
+
+Casting
+-------
+
+If you want to return values of specific types, for example a Symbol or Integer
+you can pass the `:as` attribute to your option.
+
+ s = Slop.parse("--age 20") do
+ opt :age, true, :as => Integer # :int/:integer both also work
+ end
+ s.value_for(:age) #=> 20 # not "20"
+
+Slop will also check your default attributes type to see if it can cast the new
+value to the same type.
+
+ s = Slop.parse("--port 110") do
+ opt :port, true, :default => 80
+ end
+ s.value_for(:port) #=> 110
+
+Lists
+-----
+
+You can of course also parse lists into options. Here's how:
+
+ s = Slop.parse("--people lee,injekt") do
+ opt :people, true, :as => Array
+ end
+ s.value_for(:people) #=> ["lee", "injekt"]
+
+You can also change both the split delimiter and limit
+
+ s = Slop.parse("--people lee:injekt:bob") do
+ opt :people, true, :as => Array, :delimiter => ':', :limit => 2
+ end
+ s.value_for(:people) #=> ["lee", "injekt:bob"]
+
+Contributing
+------------
+
+If you'd like to contribute to Slop (it's **really** appreciated) please fork
+the GitHub repository, create your feature/bugfix branch, add specs, and send
+me a pull request. I'd be more than happy to look at it.
147 lib/slop.rb
@@ -0,0 +1,147 @@
+require 'set'
+
+require 'slop/option'
+
+class Slop
+ VERSION = '0.1.0'
+
+ class MissingArgumentError < ArgumentError; end
+
+ # @return [Set]
+ attr_reader :options
+
+ def self.parse(values=[], &blk)
+ new(&blk).parse(values)
+ end
+
+ def initialize(&blk)
+ @options = Set.new
+ instance_eval(&blk) if block_given?
+ end
+
+ # add an option
+ def option(*args, &blk)
+ opts = args.pop if args.last.is_a?(Hash)
+ opts ||= {}
+
+ if args.size > 4
+ raise ArgumentError, "Argument size must be no more than 4"
+ end
+
+ args.unshift nil if args.first.size > 1
+ args.push nil if args.size == 2
+ args.push false if args.size == 3
+ if args[2] == true
+ args[2] = nil
+ args[3] = true
+ end
+
+ attributes = [:flag, :option, :description, :argument]
+ options = Hash[attributes.zip(args)]
+ options.merge!(opts)
+ options[:as] = options[:default].class if options.key?(:default)
+ yield options if block_given?
+
+ @options << Option.new(options)
+ end
+ alias :opt :option
+ alias :o :option
+
+ # add an argument
+ def argument(*args)
+
+ end
+ alias :arg :argument
+ alias :args :argument
+ alias :arguments :argument
+
+ # Parse an Array (usually ARGV) of options
+ #
+ # @param [Array, #split] Array or String of options to parse
+ def parse(values=[])
+ values = values.split(/\s+/) if values.respond_to?(:split)
+
+ values.each do |value|
+ if value[1] == '-' or value[0] == '-'
+ opt = value.size == 2 ? value[1] : value[2..-1]
+ next unless option = option_for(opt) # skip unknown values for now
+ index = values.index(value)
+
+ if option.has_switch?
+ option.switch_argument_value
+ end
+
+ if option.requires_argument?
+ value = values.at(index + 1)
+
+ unless option.optional_argument?
+ if not value or value[0] == '-' or value[1] == '-'
+ raise MissingArgumentError,
+ "#{option.key} requires a compulsory argument, none given"
+ end
+ end
+
+ unless !value or value[0] == '-' or value[1] == '-'
+ option.argument_value = values.delete_at(values.index(value))
+ end
+ end
+ else
+ # not a flag or option, parse as an argument
+ end
+ end
+
+ self
+ end
+
+ # traverse options
+ def each_option
+ @options.each {|o| yield o }
+ end
+
+ # A simple Hash of options with option labels or flags as keys
+ # and option values as.. values.
+ #
+ # @return [Hash]
+ def options_hash
+ out = {}
+ each_option do |opt|
+ if opt.requires_argument? or opt.has_default?
+ out[opt.key] = opt.argument_value || opt.default
+ end
+ end
+ out
+ end
+ alias :to_hash :options_hash
+
+ # Find an option using its flag or label
+ #
+ # @example
+ # s = Slop.new do
+ # option :n, :name, "Your name"
+ # end
+ #
+ # s.option_for(:name).description #=> "Your name"
+ #
+ # @return [Option] the option flag or label
+ def option_for(flag)
+ @options.find do |opt|
+ opt.has_flag?(flag) || opt.has_option?(flag)
+ end
+ end
+
+ # Find an options argument using the option name.
+ # Essentially this is the same as `s.options_hash[:name]`
+ #
+ # @example When passing --name Lee
+ # s = Slop.new do
+ # option :n, :name, true
+ # end
+ #
+ # s.value_for(:name) #=> "Lee"
+ #
+ #
+ def value_for(flag)
+ return unless option = option_for(flag)
+ option.argument_value
+ end
+end
115 lib/slop/option.rb
@@ -0,0 +1,115 @@
+class Slop
+ class Option
+
+ attr_reader :flag
+ attr_reader :option
+ attr_reader :description
+ attr_reader :argument_value
+ attr_reader :default
+
+ def initialize(options={}, &blk)
+ @options = options
+ @flag = options[:flag]
+ @option = options[:option] || options[:opt]
+ @description = options[:description] || options[:desc]
+ @argument = options[:argument] || false
+ @optional = options[:optional] || options[:optional_argument]
+ @argument ||= @optional
+ @default = options[:default]
+ @as = options[:as]
+
+ # Array properties
+ @delimiter = options[:delimiter] || ','
+ @limit = options[:limit] || 1
+
+ @argument_value = nil
+ end
+
+ # Set the argument value
+ # @param [Object] value
+ def argument_value=(value)
+ @argument_value = value
+ end
+
+ # @return [Object] the argument value after it's been cast
+ # according to the `:as` option
+ def argument_value
+ @argument_value ||= @default
+ return unless @argument_value
+
+ case @as.to_s
+ when 'array', 'Array'; @argument_value.split(@delimiter, @limit)
+ when 'integer', 'int', 'Integer'; @argument_value.to_i
+ when 'symbol', 'sym', 'Symbol' ; @argument_value.to_sym
+ else
+ @argument_value
+ end
+ end
+
+ # @param [to_s] flag
+ # @return [Boolean] true if this option contains a flag
+ def has_flag?(flag)
+ @flag.to_s == flag.to_s
+ end
+
+ # @param [to_s] option
+ # @return [Boolean] true if this option contains an option label
+ def has_option?(option)
+ @option.to_s == option.to_s
+ end
+
+ # @return [Boolean] true if this option has a default value
+ def has_default?
+ !@default.nil?
+ end
+
+ # @return [Boolean] true if this option has a switch value
+ def has_switch?
+ !!@options[:switch]
+ end
+
+ # does the option require an argument?
+ # @return [Boolean]
+ def requires_argument?
+ !!@argument
+ end
+
+ # Is the argument optional?
+ # @return [Boolean]
+ def optional_argument?
+ @options[:optional]
+ end
+
+ # Replace options argument value with the switch value supplied, used
+ # when supplying the `switch` option making switch flags easy to alter
+ #
+ # @example
+ # option :v, :verbose, :default => false, :switch => true
+ #
+ # Now when the `-v` or `--verbose` option is supplied, verbose will
+ # be set to `true`, rather than the default `false` option
+ def switch_argument_value
+ @argument_value = @option[:switch]
+ end
+
+ # return a key for an option, prioritize
+ # option before flag as it's more descriptive
+ def key
+ @option || @flag
+ end
+
+ def to_s
+ str = "\t"
+ str << "-#{@flag}" if @flag
+ str << "\t"
+ str << "--#{@option}\t\t" if @option
+ str << "#{@description}" if @description
+ str << "\n"
+ end
+
+ def inspect
+ "#<#{self.class}: #{@options}>"
+ end
+
+ end
+end
14 slop.gemspec
@@ -0,0 +1,14 @@
+Gem::Specification.new do |s|
+ s.name = 'slop'
+ s.version = '0.1.0'
+ s.summary = 'Option parsing made easy'
+ s.description = ''
+ s.author = 'Lee Jarvis'
+ s.email = 'lee@jarvis.co'
+ s.homepage = 'http://rubydoc.info/github/injekt/slop'
+ s.required_ruby_version = '>= 1.9.1'
+ s.files = ['LICENSE', 'README.md', 'lib/slop.rb',
+ 'lib/slop/option.rb', 'spec/slop_spec.rb', 'spec/option_spec.rb']
+
+ s.add_development_dependency('rspec', '= 2.1.0')
+end
5 spec/option_spec.rb
@@ -0,0 +1,5 @@
+require File.expand_path('../../lib/slop', __FILE__)
+
+describe Slop::Option do
+
+end
104 spec/slop_spec.rb
@@ -0,0 +1,104 @@
+require File.expand_path('../../lib/slop', __FILE__)
+
+describe Slop do
+ before :all do
+ @slop = Slop.new do
+ option :v, :verbose, "Enable verbose mode"
+ end
+ end
+
+ describe "option" do
+ it "adds an option" do
+ @slop.options.find do |opt|
+ opt.flag == :v
+ end.should be_kind_of(Slop::Option)
+ end
+
+ it "adds an option with a block to alter option attributes" do
+ s = Slop.new do
+ option :n, :name, "Set your name!", true do |o|
+ o[:default] = "Lee"
+ end
+ end
+ s.option_for(:name).default.should == "Lee"
+ end
+
+ it "takes no more than 4 arguments" do
+ lambda do
+ Slop.new { option :a, :b, :c, :d, :e }
+ end.should raise_error(ArgumentError, "Argument size must be no more than 4")
+ end
+
+ it "does not parse option values unless option.argument is true" do
+ Slop.parse("--name Lee") { opt :name }.value_for(:name).should be_nil
+ Slop.parse("--name Lee") { opt :name, true }.value_for(:name).should == "Lee"
+ Slop.parse("--name Lee") { opt :name, :argument => true }.value_for(:name).should == "Lee"
+ end
+ end
+
+ describe "option_for" do
+ it "returns an option" do
+ @slop.option_for(:v).should be_kind_of(Slop::Option)
+ end
+
+ it "returns nil otherwise" do
+ @slop.option_for(:nothing).should be_nil
+ end
+ end
+
+ describe "value_for" do
+ it "returns the value of an option" do
+ s = Slop.parse("--name Lee") do
+ opt :n, :name, "Your name", true
+ end
+ s.value_for(:name).should == "Lee"
+ end
+
+ it "returns a default option if none is given" do
+ Slop.new { opt :name, true, :default => "Lee" }.value_for(:name).should == "Lee"
+ end
+
+ it "returns nil if an option does not exist" do
+ Slop.new.value_for(:name).should be_nil
+ end
+ end
+
+ describe "parse" do
+ it "returns self (Slop)" do
+ Slop.parse.should be_kind_of(Slop)
+ end
+
+ it "parses a string" do
+ Slop.parse("--name Lee") { opt :name, true }.value_for(:name).should == "Lee"
+ end
+
+ it "parses an array" do
+ Slop.parse(%w"--name Lee") { opt :name, true }.value_for(:name).should == "Lee"
+ end
+
+ it "raises MissingArgumentError if no argument is given to a compulsory option" do
+ lambda { Slop.parse("--name") { opt :name, true } }.should raise_error(Slop::MissingArgumentError, /name/)
+ end
+
+ it "does not raise MissingArgumentError if the optional attribute is true" do
+ Slop.parse("--name") { opt :name, true, :optional => true }.value_for(:name).should be_nil
+ end
+
+ it "does not require argument to be true if optional is true" do
+ Slop.parse("--name Lee") { opt :name, :optional => true }.value_for(:name).should == "Lee"
+ end
+ end
+
+ describe "options" do
+ it "returns a set" do
+ @slop.options.should be_kind_of Set
+ end
+
+ it "contains a set of Slop::Option" do
+ @slop.options.each do |opt|
+ opt.should be_kind_of(Slop::Option)
+ end
+ end
+ end
+
+end

0 comments on commit 1c9367b

Please sign in to comment.