From 6bd162702283e2edcbf151d8db5ef52735959700 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 29 May 2012 20:08:27 -0700 Subject: [PATCH] Upgrade to Slop v3 --- lib/pry/slop.rb | 1366 ++++++++++++-------------------------- lib/pry/slop/commands.rb | 177 +++++ lib/pry/slop/option.rb | 192 ++++++ pry.gemspec | 3 - 4 files changed, 810 insertions(+), 928 deletions(-) create mode 100644 lib/pry/slop/commands.rb create mode 100644 lib/pry/slop/option.rb diff --git a/lib/pry/slop.rb b/lib/pry/slop.rb index 586c2b8b..918ed3bf 100644 --- a/lib/pry/slop.rb +++ b/lib/pry/slop.rb @@ -1,1081 +1,597 @@ -# The latest version of slop has broken backward compatibility with version 2.4.4. -# -# As Ruby has no mechanism for loading several versions of the same gem, we've included -# that copy below. -# -# Copyright (c) 2012 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. +require 'pry/slop/option' +require 'pry/slop/commands' class Pry class Slop include Enumerable - # @return [String] The current version string - VERSION = '2.4.4' + VERSION = '3.2.0' - # Slops standard Error class. All exception classes should - # inherit from this class + # The main Error class, all Exception classes inherit from this class. class Error < StandardError; end - # Raised when an option expects an argument and none is given + # Raised when an option argument is expected but none are given. class MissingArgumentError < Error; end - # Raised when an option is required but not given + # Raised when an option is expected/required but not present. class MissingOptionError < Error; end - # Raised when an option specifies the `:match` attribute and this - # options argument does not match this regexp + # Raised when an argument does not match its intended match constraint. class InvalidArgumentError < Error; end - # Raised when the `:strict` option is enabled and an unknown - # or unspecified option is used + # Raised when an invalid option is found and the strict flag is enabled. class InvalidOptionError < Error; end - # Each option specified in `Slop#opt` creates an instance of this class - class Option < Struct.new(:short_flag, :long_flag, :description, :tail, :match, :help, :required, :forced, :count) + # Raised when an invalid command is found and the strict flag is enabled. + class InvalidCommandError < Error; end - # @param [Slop] slop The Slop object this Option belongs to - # - # @param [String, #to_s] short The short flag representing this Option - # without prefix (ie: `a`) - # - # @param [String, #to_s] long The long flag representing this Option - # without the prefix (ie: `foo`) - # - # @param [String] description This options description - # - # @param [Boolean] argument True if this option takes an argument - # - # @option options [Boolean] :optional - # * When true, this option takes an optional argument, ie an argument - # does not **have** to be supplied. - # - # @option options [Boolean] :argument - # * True if this option takes an argument. - # - # @option options [Object] :default - # * The default value for this option when no argument is given - # - # @option options [Proc, #call] :callback - # * The callback object, used instead of passing a block to this option - # - # @option options [String, #to_s] :delimiter (',') - # * A delimiter string when processing this option as a list - # - # @option options [Integer] :limit (0) - # * A limit, used when processing this option as a list - # - # @option options [Boolean] :tail (false) - # * When true, this option will be grouped at the bottom of the help - # text instead of in order of processing - # - # @option options [Regexp] :match - # * A regular expression this option should match - # - # @option options [String, #to_s] :unless - # * Used by `omit_exec` for omitting execution of this options callback - # if another option exists - # - # @option options [Boolean, String] :help (true) - # * If this option is a string, it'll be appended to the long flag - # help text (before the description). When false, no help information - # will be displayed for this option - # - # @option options [Boolean] :required (false) - # * When true, this option is considered mandatory. That is, when not - # supplied, Slop will raise a `MissingOptionError` - def initialize(slop, short, long, description, argument, options, &blk) - @slop = slop + # Returns a default Hash of configuration options this Slop instance uses. + DEFAULT_OPTIONS = { + :strict => false, + :help => false, + :banner => nil, + :ignore_case => false, + :autocreate => false, + :arguments => false, + :optional_arguments => false, + :multiple_switches => true, + :longest_flag => 0 + } - self.short_flag = short - self.long_flag = long - self.description = description + class << self - @argument = argument - @options = options - - self.tail = @options[:tail] - self.match = @options[:match] - self.help = @options.fetch(:help, true) - self.required = @options[:required] - - @delimiter = @options.fetch(:delimiter, ',') - @limit = @options.fetch(:limit, 0) - - @argument_type = @options[:as] - @argument_value = nil - - self.forced = false - self.count = 0 - - @callback = block_given? ? blk : @options[:callback] - - if long_flag && long_flag.size > @slop.longest_flag - @slop.longest_flag = long_flag.size - @slop.longest_flag += help.size if help.respond_to?(:to_str) - end + # items - The Array of items to extract options from (default: ARGV). + # config - The Hash of configuration options to send to Slop.new(). + # block - An optional block used to add options. + # + # Examples: + # + # Slop.parse(ARGV, :help => true) do + # on '-n', '--name', 'Your username', :argument => true + # end + # + # Returns a new instance of Slop. + def parse(items = ARGV, config = {}, &block) + init_and_parse(items, false, config, &block) end - # @return [Boolean] true if this option expects an argument - def expects_argument? - @argument || @options[:argument] || @options[:optional] == false - end - - # @return [Boolean] true if this option accepts an optional argument - def accepts_optional_argument? - @options[:optional] || @options[:optional_argument] - end - - # @return [String] either the long or short flag for this option - def key - long_flag || short_flag - end - - # Set this options argument value. + # items - The Array of items to extract options from (default: ARGV). + # config - The Hash of configuration options to send to Slop.new(). + # block - An optional block used to add options. # - # If this options argument type is expected to be an Array, this - # method will split the value and concat elements into the original - # argument value - # - # @param [Object] value The value to set this options argument to - def argument_value=(value) - if @argument_type.to_s.downcase == 'array' - @argument_value ||= [] - - if value.respond_to?(:to_str) - @argument_value.concat value.split(@delimiter, @limit) - end - else - @argument_value = value - end + # Returns a new instance of Slop. + def parse!(items = ARGV, config = {}, &block) + init_and_parse(items, true, config, &block) end - # @return [Object] the argument value after it's been cast - # according to the `:as` option - def argument_value - return @argument_value if forced - type = @argument_type.to_s.downcase - # Check for count first to prefer 0 over nil - return count if type == 'count' + # Build a Slop object from a option specification. + # + # This allows you to design your options via a simple String rather + # than programatically. Do note though that with this method, you're + # unable to pass any advanced options to the on() method when creating + # options. + # + # string - The optspec String + # config - A Hash of configuration options to pass to Slop.new + # + # Examples: + # + # opts = Slop.optspec(<<-SPEC) + # ruby foo.rb [options] + # --- + # n,name= Your name + # a,age= Your age + # A,auth Sign in with auth + # p,passcode= Your secret pass code + # SPEC + # + # opts.fetch_option(:name).description #=> "Your name" + # + # Returns a new instance of Slop. + def optspec(string, config = {}) + config[:banner], optspec = string.split(/^--+$/, 2) if string[/^--+$/] + lines = optspec.split("\n").reject(&:empty?) + opts = Slop.new(config) - value = @argument_value || @options[:default] - return if value.nil? + lines.each do |line| + opt, description = line.split(' ', 2) + short, long = opt.split(',').map { |s| s.sub(/\A--?/, '') } + opt = opts.on(short, long, description) - if @argument_type.respond_to?(:call) - @argument_type.call(value) - else - case type - when 'array' - arg_value(@argument_value) - when 'range' - arg_value(value_to_range(value)) - when 'float' - arg_value(value.to_s.to_f) - when 'string', 'str' - arg_value(value.to_s) - when 'symbol', 'sym' - arg_value(value.to_s.to_sym) - when 'integer', 'int' - arg_value(value.to_s.to_i) - else - value + if long && long.end_with?('=') + long.sub!(/\=$/, '') + opt.config[:argument] = true end end - end - # Force an argument value, used when the desired argument value - # is negative (false or nil) - # - # @param [Object] value - def force_argument_value(value) - @argument_value = value - self.forced = true - end - - # Execute the block or callback object associated with this Option - # - # @param [Object] The object to be sent to `:call` - def call(obj=nil) - @callback.call(obj) if @callback.respond_to?(:call) - end - - # @param [Array] items The original array of objects passed to `Slop.new` - # @return [Boolean] true if this options `:unless` argument exists - # inside *items* - def omit_exec?(items) - items.any? do |item| - item.to_s.sub(/\A--?/, '') == @options[:unless].to_s.sub(/\A--?/, '') - end - end - - # This option in a nice pretty string, including a short flag, long - # flag, and description (if they exist). - # - # @see Slop#help - # @return [String] - def to_s - out = " " - out += short_flag ? "-#{short_flag}, " : ' ' * 4 - - if long_flag - out += "--#{long_flag}" - if help.respond_to? :to_str - out += " #{help}" - size = long_flag.size + help.size + 1 - else - size = long_flag.size - end - diff = @slop.longest_flag - size - out += " " * (diff + 6) - else - out += " " * (@slop.longest_flag + 8) - end - - "#{out}#{description}" - end - - # @return [String] - def inspect - "#" + opts end private - def arg_value(value) - value if accepts_optional_argument? || expects_argument? - end - - def value_to_range(value) - case value.to_s - when /\A(-?\d+?)(\.\.\.?|-|,)(-?\d+)\z/ - Range.new($1.to_i, $3.to_i, $2 == '...') - when /\A-?\d+\z/ - Range.new(value.to_i, value.to_i) - else - value - end - end - - end - - # Used to hold a list of Option objects. This class inherits from Array - # and overwrites `Array#[]` so we can fetch Option objects via their - # short or long flags - class Options < Array - - # Fetch an Option object. This method overrides Array#[] to provide - # a nicer interface for fetching options via their short or long flag. - # The reason we don't use a Hash here is because an option cannot be - # identified by a single label. Instead this method tests against - # a short flag first, followed by a long flag. When passing this - # method an Integer, it will work as an Array usually would, fetching - # the Slop::Option at this index. + # Convenience method used by ::parse and ::parse!. # - # @param [Object] flag The short/long flag representing the option - # @example - # opts = Slop.parse { on :v, "Verbose mode" } - # opts.options[:v] #=> Option - # opts.options[:v].description #=> "Verbose mode" - # @return [Option] the option assoiated with this flag - def [](flag) - if flag.is_a? Integer - super - else - find do |option| - [option.short_flag, option.long_flag].include? flag.to_s - end - end + # items - The Array of items to parse. + # delete - When true, executes #parse! over #parse. + # config - The Hash of configuration options to pass to Slop.new. + # block - The optional block to pass to Slop.new + # + # Returns a newly created instance of Slop. + def init_and_parse(items, delete, config, &block) + config, items = items, ARGV if items.is_a?(Hash) && config.empty? + slop = Slop.new(config, &block) + delete ? slop.parse!(items) : slop.parse(items) + slop end end - # Parses the items from a CLI format into a friendly object - # - # @param [Array] items Items to parse into options. - # @example Specifying three options to parse: - # opts = Slops.parse do - # on :v, :verbose, 'Enable verbose mode' - # on :n, :name, 'Your name' - # on :a, :age, 'Your age' - # end - # @return [Slop] Returns an instance of Slop - def self.parse(items=ARGV, options={}, &block) - initialize_and_parse items, false, options, &block - end + # The Hash of configuration options for this Slop instance. + attr_reader :config - # Identical to {Slop.parse}, but removes parsed options from the - # original Array - # - # @return [Slop] Returns an instance of Slop - def self.parse!(items=ARGV, options={}, &block) - initialize_and_parse items, true, options, &block - end - - # Build options from an optspec string - # - # @param [String] optspec The option spec string - # @param [Array] options A list of options to forward to Slop.new - # @return [Slop] A new instance of Slop - def self.optspec(optspec, *options) - if optspec[/^--+$/] - banner, optspec = optspec.split(/^--+$/, 2) - end - - lines = optspec.split("\n").reject(&:empty?) - opts = Slop.new(banner, *options) - - lines.each do |line| - opt, description = line.split(' ', 2) - short, long = opt.split(',').map { |s| s.sub(/\A--?/, '') } - argument = long && long[-1] == ?$ - long.sub!(/\=$/, '') if argument - opts.on short, long, description, argument - end - - opts - end - - # @return [Options] + # The Array of Slop::Option objects tied to this Slop instance. attr_reader :options - # @return [Hash] - attr_reader :commands - - # @overload banner=(string) - # Set the banner - # @param [String] string The text to set the banner to - attr_writer :banner - - # @overload summary=(string) - # Set the summary - # @param [String] string The text to set the summary to - attr_writer :summary - - # @overload description=(string) - # Set the description - # @param [String] string The text to set the description to - attr_writer :description - - # @return [Integer] The length of the longest flag slop knows of - attr_accessor :longest_flag - - # @return [Array] A list of aliases this command uses - attr_accessor :aliases - - # @option opts [Boolean] :help - # * Automatically add the `help` option + # Create a new instance of Slop and optionally build options via a block. # - # @option opts [Boolean] :strict - # * Raises when a non listed option is found, false by default - # - # @option opts [Boolean] :multiple_switches - # * Allows `-abc` to be processed as the options 'a', 'b', 'c' and will - # force their argument values to true. By default Slop with parse this - # as 'a' with the argument 'bc' - # - # @option opts [String] :banner - # * The banner text used for the help - # - # @option opts [Proc, #call] :on_empty - # * Any object that respondes to `call` which is executed when Slop has - # no items to parse - # - # @option opts [IO, #puts] :io ($stderr) - # * An IO object for writing to when :help => true is used - # - # @option opts [Boolean] :exit_on_help (true) - # * When false and coupled with the :help option, Slop will not exit - # inside of the `help` option - # - # @option opts [Boolean] :ignore_case (false) - # * Ignore options case - # - # @option opts [Proc, #call] :on_noopts - # * Trigger an event when no options are found - # - # @option opts [Boolean] :autocreate (false) - # * Autocreate options depending on the Array passed to {#parse} - # - # @option opts [Boolean] :arguments (false) - # * Set to true to enable all specified options to accept arguments - # by default - # - # @option opts [Array] :aliases ([]) - # * Primary uses by commands to implement command aliases - # - # @option opts [Boolean] :completion (true) - # * When true, commands will be auto completed. Ie `foobar` will be - # executed simply when `foo` `fo` or `foob` are used - # - # @option options [Boolean] :all_accept_arguments (false) - # * When true, every option added will take an argument, this saves - # having to enable it for every option - def initialize(*opts, &block) - sloptions = opts.last.is_a?(Hash) ? opts.pop : {} - sloptions[:banner] = opts.shift if opts[0].respond_to?(:to_str) - opts.each { |o| sloptions[o] = true } - - @options = Options.new - @commands = {} - @execution_block = nil - - @longest_flag = 0 - @invalid_options = [] - - @banner = sloptions[:banner] - @strict = sloptions[:strict] - @ignore_case = sloptions[:ignore_case] - @multiple_switches = sloptions.fetch(:multiple_switches, true) - @autocreate = sloptions[:autocreate] - @completion = sloptions.fetch(:completion, true) - @arguments = sloptions[:arguments] - @on_empty = sloptions[:on_empty] - @io = sloptions.fetch(:io, $stderr) - @on_noopts = sloptions[:on_noopts] || sloptions[:on_optionless] - @sloptions = sloptions + # config - A Hash of configuration options. + # block - An optional block used to specify options. + def initialize(config = {}, &block) + @config = DEFAULT_OPTIONS.merge(config) + @options = [] + @trash = [] + @triggered_options = [] + @unknown_options = [] + @callbacks = {} + @separators = {} if block_given? block.arity == 1 ? yield(self) : instance_eval(&block) end - if sloptions[:help] - on :h, :help, 'Print this help message', :tail => true do - @io.puts help - exit unless sloptions[:exit_on_help] == false + if config[:help] + on('-h', '--help', 'Display this help message.', :tail => true) do + $stderr.puts help end end end - # Set or return banner text + # Is strict mode enabled? # - # @param [String] text Displayed banner text - # @example - # opts = Slop.parse do - # banner "Usage - ruby foo.rb [arguments]" - # end - # @return [String] The current banner - def banner(text=nil) - @banner = text if text - @banner + # Returns true if strict mode is enabled, false otherwise. + def strict? + config[:strict] end - # Set or return the summary + # Set the banner. # - # @param [String] text Displayed summary text - # @example - # opts = Slop.parse do - # summary "do stuff with more stuff" - # end - # @return [String] The current summary - def summary(text=nil) - @summary = text if text - @summary - end - - # Set or return the description + # banner - The String to set the banner. # - # @param [String] text Displayed description text - # @example - # opts = Slop.parse do - # description "This command does a lot of stuff with other stuff." - # end - # @return [String] The current description - def description(text=nil) - @description = text if text - @description + # Returns nothing. + def banner=(banner) + config[:banner] = banner end - # Parse a list of options, leaving the original Array unchanged + # Get or set the banner. # - # @param [Array] items A list of items to parse - def parse(items=ARGV, &block) - parse_items items, &block - end - - # Parse a list of options, removing parsed options from the original Array + # banner - The String to set the banner. # - # @param [Array] items A list of items to parse - def parse!(items=ARGV, &block) - parse_items items, true, &block + # Returns the banner String. + def banner(banner = nil) + config[:banner] = banner if banner + config[:banner] end - # Enumerable interface - def each(&block) - @options.each(&block) + # Parse a list of items, executing and gathering options along the way. + # + # items - The Array of items to extract options from (default: ARGV). + # block - An optional block which when used will yield non options. + # + # Returns an Array of original items. + def parse(items = ARGV, &block) + parse_items(items, false, &block) end - # @param [Symbol] key Option symbol - # @example - # opts[:name] #=> "Emily" - # opts.get(:name) #=> "Emily" - # @return [Object] Returns the value associated with that option. If an - # option doesn't exist, a command will instead be searched for + # Parse a list of items, executing and gathering options along the way. + # unlike parse() this method will remove any options and option arguments + # from the original Array. + # + # items - The Array of items to extract options from (default: ARGV). + # block - An optional block which when used will yield non options. + # + # Returns an Array of original items with options removed. + def parse!(items = ARGV, &block) + parse_items(items, true, &block) + end + + # Add an Option. + # + # objects - An Array with an optional Hash as the last element. + # + # Examples: + # + # on '-u', '--username=', 'Your username' + # on :v, :verbose, 'Enable verbose mode' + # + # Returns the created instance of Slop::Option. + def on(*objects, &block) + option = build_option(objects, &block) + options << option + option + end + alias option on + alias opt on + + # Fetch an options argument value. + # + # key - The Symbol or String option short or long flag. + # + # Returns the Object value for this option, or nil. def [](key) - option = @options[key] - option ? option.argument_value : @commands[key] + option = fetch_option(key) + option.value if option end alias get [] - # Specify an option with a short or long version, description and type - # - # @param [*] args Option configuration. - # @option args [Symbol, String] :short_flag Short option name. - # @option args [Symbol, String] :long_flag Full option name. - # @option args [String] :description Option description for use in Slop#help - # @option args [Boolean] :argument Specifies whether this option requires - # an argument - # @option args [Hash] :options Optional option configurations. - # @example - # opts = Slop.parse do - # on :n, :name, 'Your username', true # Required argument - # on :a, :age, 'Your age (optional)', :optional => true - # on :g, :gender, 'Your gender', :optional => false - # on :V, :verbose, 'Run in verbose mode', :default => true - # on :P, :people, 'Your friends', true, :as => Array - # on :h, :help, 'Print this help screen' do - # puts help - # end - # end - # @return [Slop::Option] - def option(*args, &block) - options = args.last.is_a?(Hash) ? args.pop : {} - short, long, desc, arg, extras = clean_options(args) - - options.merge!(extras) - options[:argument] = true if @sloptions[:all_accept_arguments] - - option = Option.new(self, short, long, desc, arg, options, &block) - @options << option - - option - end - alias opt option - alias on option - - # Namespace options depending on what command is executed - # - # @param [Symbol, String] label - # @param [Hash] options - # @example - # opts = Slop.new do - # command :create do - # on :v, :verbose - # end - # end - # - # # ARGV is `create -v` - # opts.commands[:create].verbose? #=> true - # @since 1.5.0 - # @raise [ArgumentError] When this command already exists - # @return [Slop] a new instance of Slop namespaced to +label+ - def command(label, options={}, &block) - if @commands.key?(label) - raise ArgumentError, "command `#{label}` already exists" - end - - slop = Slop.new @sloptions.merge(options) - slop.aliases = Array(options.delete(:aliases) || options.delete(:alias)) - @commands[label] = slop - - slop.aliases.each { |a| @commands[a] = @commands[label] } - - if block_given? - block.arity == 1 ? yield(slop) : slop.instance_eval(&block) - end - - slop - end - - # Trigger an event when Slop has no values to parse - # - # @param [Object, #call] obj The object (which can be anything - # responding to `call`) - # @example - # Slop.parse do - # on_empty { puts 'No argument given!' } - # end - # @since 1.5.0 - def on_empty(obj=nil, &block) - @on_empty ||= (obj || block) - end - alias on_empty= on_empty - - # Trigger an event when the arguments contain no options - # - # @param [Object, #call] obj The object to be triggered (anything - # responding to `call`) - # @example - # Slop.parse do - # on_noopts { puts 'No options here!' } - # end - # @since 1.6.0 - def on_noopts(obj=nil, &block) - @on_noopts ||= (obj || block) - end - alias on_optionless on_noopts - - # Add an execution block (for commands) - # - # @example - # opts = Slop.new do - # command :foo do - # on :v, :verbose - # - # execute { |o| p o.verbose? } - # end - # end - # opts.parse %w[foo --verbose] #=> true - # - # @param [Array] args The list of arguments to send to this command - # is invoked - # @since 1.8.0 - # @yield [Slop] an instance of Slop for this command - def execute(args=[], &block) - if block_given? - @execution_block = block - elsif @execution_block.respond_to?(:call) - @execution_block.call(self, args) - end - end - - # Returns the parsed list into a option/value hash - # - # @example - # opts.to_hash #=> { :name => 'Emily' } - # - # # strings! - # opts.to_hash(false) #=> { 'name' => 'Emily' } - # @return [Hash] - def to_hash(symbols=true) - @options.reduce({}) do |hsh, option| - key = option.key - key = key.to_sym if symbols - hsh[key] = option.argument_value - hsh - end + # Returns a new Hash with option flags as keys and option values as values. + def to_hash + Hash[options.map { |opt| [opt.key.to_sym, opt.value] }] end alias to_h to_hash - # Return parsed items as a new Class - # - # @example - # opts = Slop.new do - # on :n, :name, 'Persons name', true - # on :a, :age, 'Persons age', true, :as => :int - # on :s, :sex, 'Persons sex m/f', true, :match => /^[mf]$/ - # on :A, :admin, 'Enable admin mode' - # end - # - # opts.parse %w[ --name Lee --age 22 -s m --admin ] - # - # person = opts.to_struct("Person") - # person.class #=> Struct::Person - # person.name #=> 'Lee' - # person.age #=> 22 - # person.sex #=> m - # person.admin #=> true - # - # @param [String] name The name of this class - # @return [Class] The new class, or nil if there are no options - # @since 2.0.0 - def to_struct(name=nil) - hash = to_hash - Struct.new(name, *hash.keys).new(*hash.values) unless hash.empty? + # Enumerable interface. Yields each Slop::Option. + def each(&block) + options.each(&block) end - # Fetch a list of options which were missing from the parsed list + # Check for an options presence. # - # @example - # opts = Slop.new do - # on :n, :name, 'Your name', true - # on :p, :password, 'Your password', true - # on :A, 'Use auth?' - # end + # Examples: # - # opts.parse %w[ --name Lee ] - # opts.missing #=> ['password', 'a'] + # opts.parse %w( --foo ) + # opts.present?(:foo) #=> true + # opts.present?(:bar) #=> false # - # @return [Array] A list of options missing from the parsed string - # @since 2.1.0 - def missing - @options.select { |opt| not present?(opt.key) }.map(&:key) + # Returns true if all of the keys are present in the parsed arguments. + def present?(*keys) + keys.all? { |key| (opt = fetch_option(key)) && opt.count > 0 } end - # Allows you to check whether an option was specified in the parsed list + # Convenience method for present?(:option). # - # Merely sugar for `present?` + # Examples: # - # @example - # #== ruby foo.rb -v + # opts.parse %( --verbose ) # opts.verbose? #=> true - # opts.name? #=> false - # @see Slop#present? - # @return [Boolean] true if this option is present, false otherwise - def method_missing(meth, *args, &block) - meth = meth.to_s - if meth[-1] == ?? + # opts.other? #=> false + # + # Returns true if this option is present. If this method does not end + # with a ? character it will instead call super(). + def method_missing(method, *args, &block) + meth = method.to_s + if meth.end_with?('?') present?(meth.chop) else super end end - # Override this method so we can check if an option? method exists + # Override this method so we can check if an option? method exists. + # + # Returns true if this option key exists in our list of options. def respond_to?(method) method = method.to_s - if method[-1] == ?? and @options.any? { |o| o.key == method.chop } + if method.end_with?('?') && options.any? { |o| o.key == method.chop } true else super end end - # Check if an option is specified in the parsed list + # Fetch a list of options which were missing from the parsed list. # - # Does the same as Slop#option? but a convenience method for unacceptable - # method names + # Examples: # - # @param [Object] The object name(s) to check - # @since 1.5.0 - # @return [Boolean] true if these options are present, false otherwise - def present?(*option_names) - option_names.all? { |opt| @options[opt] && @options[opt].count > 0 } + # opts = Slop.new do + # on :n, :name= + # on :p, :password= + # end + # + # opts.parse %w[ --name Lee ] + # opts.missing #=> ['password'] + # + # Returns an Array of Strings representing missing options. + def missing + (options - @triggered_options).map(&:key) end - # Returns the banner followed by available options listed on the next line + # Fetch a Slop::Option object. # - # @example - # opts = Slop.parse do - # banner "Usage - ruby foo.rb [arguments]" - # on :v, :verbose, "Enable verbose mode" - # end - # puts opts - # @return [String] Help text. - def to_s - parts = [] + # key - The Symbol or String option key. + # + # Examples: + # + # opts.on(:foo, 'Something fooey', :argument => :optional) + # opt = opts.fetch_option(:foo) + # opt.class #=> Slop::Option + # opt.accepts_optional_argument? #=> true + # + # Returns an Option or nil if none were found. + def fetch_option(key) + options.find { |option| [option.long, option.short].include?(clean(key)) } + end - parts << banner if banner - parts << summary if summary - parts << wrap_and_indent(description, 80, 4) if description + # Add a callback. + # + # label - The Symbol identifier to attach this callback. + # + # Returns nothing. + def add_callback(label, &block) + (@callbacks[label] ||= []) << block + end - if options.size > 0 - parts << "options:" - - heads = @options.reject(&:tail) - tails = @options.select(&:tail) - all = (heads + tails).select(&:help) - - parts << all.map(&:to_s).join("\n") + # Add string separators between options. + # + # text - The String text to print. + def separator(text) + if @separators[options.size] + @separators[options.size] << "\n#{text}" + else + @separators[options.size] = text end + end - parts.join("\n\n") + # Print a handy Slop help string. + # + # Returns the banner followed by available option help strings. + def to_s + heads = options.reject(&:tail?) + tails = (options - heads) + opts = (heads + tails).select(&:help).map(&:to_s) + optstr = opts.each_with_index.map { |o, i| + (str = @separators[i + 1]) ? [o, str].join("\n") : o + }.join("\n") + + if config[:banner] + config[:banner] << "\n" + config[:banner] << "#{@separators[0]}\n" if @separators[0] + config[:banner] + optstr + else + optstr + end end alias help to_s - # @return [String] This Slop object will options and configuration - # settings revealed + # Returns the String inspection text. def inspect - "#" + "#" end private - class << self - private - - def initialize_and_parse(items, delete, options, &block) - if items.is_a?(Hash) && options.empty? - options = items - items = ARGV - end - - slop = new(options, &block) - delete ? slop.parse!(items) : slop.parse(items) - slop - end - end - - # traverse through the list of items sent to parse() or parse!() and - # attempt to do the following: + # Parse a list of items and process their values. # - # * Find an option object - # * Assign an argument to this option - # * Validate an option and/or argument depending on configuration options - # * Remove non-parsed items if `delete` is true - # * Yield any non-options to the block (if one is given) - def parse_items(items, delete=false, &block) - if items.empty? and @on_empty.respond_to?(:call) - @on_empty.call self - return items - elsif not items.any? {|i| i.to_s[/\A--?/] } and @on_noopts.respond_to?(:call) - @on_noopts.call self - return items - elsif execute_command(items, delete) + # items - The Array of items to process. + # delete - True to remove any triggered options and arguments from the + # original list of items. + # block - An optional block which when passed will yields non-options. + # + # Returns the original Array of items. + def parse_items(items, delete, &block) + if items.empty? && @callbacks[:empty] + @callbacks[:empty].each { |cb| cb.call(self) } return items end - trash = [] - items.each_with_index do |item, index| - item = item.to_s - flag = item.sub(/\A--?/, '') + @trash << index && break if item == '--' + autocreate(items, index) if config[:autocreate] + process_item(items, index, &block) unless @trash.include?(index) + end + items.reject!.with_index { |item, index| @trash.include?(index) } if delete - if item == '--' - trash << index - break - end - - autocreate(flag, index, items) if @autocreate - option, argument = extract_option(item, flag) - - if @multiple_switches and item[/\A-[^-][A-Za-z]/] and not option - trash << index - next - end - - if option - option.count += 1 unless item[/\A--no-/] - trash << index - next if option.forced - option.argument_value = true - - if option.expects_argument? or option.accepts_optional_argument? - argument ||= items.at(index + 1) - trash << index + 1 - - if not option.accepts_optional_argument? and argument =~ /\A--?[a-zA-Z][a-zA-Z0-9_-]*\z/ - raise MissingArgumentError, "'#{option.key}' expects an argument, none given" - end - - if argument && argument !~ /^--?[a-zA-Z_-]+/ - if option.match and not argument.match(option.match) - raise InvalidArgumentError, "'#{argument}' does not match #{option.match.inspect}" - end - - option.argument_value = argument - option.call option.argument_value unless option.omit_exec?(items) - else - option.argument_value = nil - check_optional_argument!(option, flag) - end - else - option.call unless option.omit_exec?(items) - end - else - @invalid_options << flag if item[/\A--?/] and @strict - block.call(item) if block_given? and not trash.include?(index) - end + missing_options = options.select { |opt| opt.required? && opt.count < 1 } + if missing_options.any? + raise MissingOptionError, + "Missing required option(s): #{missing_options.map(&:key).join(', ')}" + end + + if @unknown_options.any? + raise InvalidOptionError, "Unknown options #{@unknown_options.join(', ')}" + end + + if @triggered_options.empty? && @callbacks[:no_options] + @callbacks[:no_options].each { |cb| cb.call(self) } end - items.reject!.with_index { |o, i| trash.include?(i) } if delete - raise_if_invalid_options! - raise_if_missing_required_options!(items) items end - def check_optional_argument!(option, flag) - if option.accepts_optional_argument? - option.call - else - raise MissingArgumentError, "'#{flag}' expects an argument, none given" - end - end - - def raise_if_invalid_options! - return if not @strict or @invalid_options.empty? - message = "Unknown option#{'s' if @invalid_options.size > 1}" - message << ' -- ' << @invalid_options.map { |o| "'#{o}'" }.join(', ') - raise InvalidOptionError, message - end - - def raise_if_missing_required_options!(items) - @options.select(&:required).each do |o| - if o.argument_value.nil? - raise MissingOptionError, "Expected option `#{o.key}` is required" - end - end - end - - # if multiple_switches is enabled, this method filters through an items - # characters and attempts to find an Option object for each flag. + # Process a list item, figure out if it's an option, execute any + # callbacks, assign any option arguments, and do some sanity checks. # - # Raises if a flag expects an argument or strict mode is enabled and a - # flag was not found - def enable_multiple_switches(item) - item[1..-1].each_char do |switch| - option = @options[switch] + # items - The Array of items to process. + # index - The current Integer index of the item we want to process. + # block - An optional block which when passed will yield non options. + # + # Returns nothing. + def process_item(items, index, &block) + return unless item = items[index] + option, argument = extract_option(item) if item.start_with?('-') - if option - if option.expects_argument? - raise MissingArgumentError, "'-#{switch}' expects an argument, used in multiple_switch context" + if option + option.count += 1 unless item.start_with?('--no-') + @trash << index + @triggered_options << option + + if option.expects_argument? + argument ||= items.at(index + 1) + + if !argument || argument =~ /\A--?[a-zA-Z][a-zA-Z0-9_-]*\z/ + raise MissingArgumentError, "#{option.key} expects an argument" end - option.argument_value = true - option.count += 1 + execute_option(option, argument, index, item) + elsif option.accepts_optional_argument? + argument ||= items.at(index + 1) + + if argument && argument =~ /\A([^-\-?]|-\d)+/ + execute_option(option, argument, index, item) + else + option.call(nil) + end + elsif config[:multiple_switches] && argument + execute_multiple_switches(option, argument, index) else - raise InvalidOptionError, "Unknown option '-#{switch}'" if @strict + option.value = option.count > 0 + option.call(nil) end + else + @unknown_options << item if strict? && item =~ /\A--?/ + block.call(item) if block && !@trash.include?(index) end end - def wrap_and_indent(string, width, indentation) - string.lines.map do |paragraph| - lines = [] - line = '' - - paragraph.split(/\s/).each do |word| - if (line + ' ' + word).length >= width - lines << line - line = '' - end - - line << (line == '' ? '' : ' ' ) + word + # Execute an option, firing off callbacks and assigning arguments. + # + # option - The Slop::Option object found by #process_item. + # argument - The argument Object to assign to this option. + # index - The current Integer index of the object we're processing. + # item - The optional String item we're processing. + # + # Returns nothing. + def execute_option(option, argument, index, item = nil) + if !option + if config[:multiple_switches] && strict? + raise InvalidOptionError, "Unknown option -#{item}" end - lines << line + return + end - lines.map { |l| ' ' * indentation + l }.join("\n") - end.join("\n") + @trash << index + 1 unless item && item.end_with?("=#{argument}") + option.value = argument + + if option.match? && !argument.match(option.config[:match]) + raise InvalidArgumentError, "#{argument} is an invalid argument" + end + + option.call(option.value) end - # attempt to extract an option from an argument, this method allows us - # to parse things like 'foo=bar' and '--no-value' for negative values - # returns an array of the Option object and an argument if one was found - def extract_option(item, flag) - if item[0, 1] == '-' - option = @options[flag] - option ||= @options[flag.downcase] if @ignore_case + # Execute a `-abc` type option where a, b and c are all options. This + # method is only executed if the multiple_switches argument is true. + # + # option - The first Option object. + # argument - The argument to this option. (Split into multiple Options). + # index - The index of the current item being processed. + # + # Returns nothing. + def execute_multiple_switches(option, argument, index) + execute_option(option, argument, index) + argument.split('').each do |key| + opt = fetch_option(key) + opt.count += 1 + execute_option(opt, argument, index, key) end + end + + # Extract an option from a flag. + # + # flag - The flag key used to extract an option. + # + # Returns an Array of [option, argument]. + def extract_option(flag) + option = fetch_option(flag) + option ||= fetch_option(flag.downcase) if config[:ignore_case] + option ||= fetch_option(flag.gsub(/([^-])-/, '\1_')) unless option - case item - when /\A-[^-]/ - if @multiple_switches - enable_multiple_switches(item) - else - flag, argument = flag.split('', 2) - option = @options[flag] - end - when /\A--([^=]+)=(.+)\z/ - option, argument = @options[$1], $2 - when /\A--no-(.+)\z/ - option = @options[$1] - option.force_argument_value(false) if option + case flag + when /\A--?([^=]+)=(.+)\z/, /\A-([a-zA-Z])(.+)\z/, /\A--no-(.+)\z/ + option, argument = fetch_option($1), ($2 || false) end end [option, argument] end - # attempt to execute a command if one exists, returns a positive (tru-ish) - # result if the command was found and executed. If completion is enabled - # and a flag is found to be ambiguous, this method prints an error message - # to the @io object informing the user - def execute_command(items, delete) - str = items[0] - - if str - command = @commands.keys.find { |c| c.to_s == str.to_s } - - if @completion and not command - cmds = @commands.keys.select { |c| c.to_s[0, str.length] == str } - - if cmds.size > 1 - @io.puts "Command '#{str}' is ambiguous:" - @io.puts " " + cmds.map(&:to_s).sort.join(', ') - else - command = cmds.shift - end - end - end - - if command - items.shift - opts = @commands[command] - delete ? opts.parse!(items) : opts.parse(items) - opts.execute(items.reject { |i| i == '--' }) + # Autocreate an option on the fly. See the :autocreate Slop config option. + # + # items - The Array of items we're parsing. + # index - The current Integer index for the item we're processing. + # + # Returns nothing. + def autocreate(items, index) + flag = items[index] + unless present?(flag) + option = build_option(Array(flag)) + argument = items[index + 1] + option.config[:argument] = (argument && argument !~ /\A--?/) + option.config[:autocreated] = true + options << option end end - # If autocreation is enabled this method simply generates an option - # and add's it to the existing list of options - def autocreate(flag, index, items) - return if present? flag - short, long = clean_options Array(flag) - arg = (items[index + 1] && items[index + 1] !~ /\A--?/) - option = Option.new(self, short, long, nil, arg, {}) - option.count = 1 - @options << option + # Build an option from a list of objects. + # + # objects - An Array of objects used to build this option. + # + # Returns a new instance of Slop::Option. + def build_option(objects, &block) + config = {} + config[:argument] = true if @config[:arguments] + config[:optional_argument] = true if @config[:optional_arguments] + + if objects.last.is_a?(Hash) + config = config.merge!(objects.last) + objects.pop + end + short = extract_short_flag(objects, config) + long = extract_long_flag(objects, config) + desc = objects[0].respond_to?(:to_str) ? objects.shift : nil + + Option.new(self, short, long, desc, config, &block) end - # Clean up arguments sent to `on` and return a list of 5 elements: - # * short flag (or nil) - # * long flag (or nil) - # * description (or nil) - # * true/false if this option takes an argument or not - # * extra options (ie: :as, :optional, and :help) - def clean_options(args) - options = [] - extras = {} + # Extract the short flag from an item. + # + # objects - The Array of objects passed from #build_option. + # config - The Hash of configuration options built in #build_option. + def extract_short_flag(objects, config) + flag = clean(objects.first) - if klass = args.find { |a| a.is_a?(Class) } - extras[:as] = klass - args.delete klass + if flag.size == 2 && flag.end_with?('=') + config[:argument] ||= true + flag.chop! end - short = args.first.to_s.sub(/\A--?/, '') - if short.size == 2 && short[-1, 1] == '=' - extras[:argument] = true - short.chop! + if flag.size == 1 + objects.shift + flag end - - if short.size == 1 - options.push short - args.shift - else - options.push nil - end - - long = args.first - if long.is_a?(TrueClass) || long.is_a?(FalseClass) - options.push nil - else - case long.to_s - when /\A(?:--?)?[a-z_-]+\s[A-Z\s\[\]]+\z/ - arg, help = args.shift.split(/ /, 2) - extras[:optional] = help[0, 1] == '[' && help[-1, 1] == ']' - extras[:help] = help - options.push arg.sub(/\A--?/, '') - when /\A(?:--?)?[a-zA-Z][a-zA-Z0-9_-]+\=?\z/ - extras[:argument] = true if long.to_s[-1, 1] == '=' - options.push args.shift.to_s.sub(/\A--?/, '').sub(/\=\z/, '') - else - options.push nil - end - end - - options.push args.first.respond_to?(:to_sym) ? args.shift : nil - options.push((@arguments || extras[:argument]) ? true : (args.shift ? true : false)) - options.push extras end + + # Extract the long flag from an item. + # + # objects - The Array of objects passed from #build_option. + # config - The Hash of configuration options built in #build_option. + def extract_long_flag(objects, config) + flag = objects.first.to_s + if flag =~ /\A(?:--?)?[a-zA-Z][a-zA-Z0-9_-]+\=?\??\z/ + config[:argument] ||= true if flag.end_with?('=') + config[:optional_argument] = true if flag.end_with?('=?') + objects.shift + clean(flag).sub(/\=\??\z/, '') + end + end + + # Remove any leading -- characters from a string. + # + # object - The Object we want to cast to a String and clean. + # + # Returns the newly cleaned String with leading -- characters removed. + def clean(object) + object.to_s.sub(/\A--?/, '') + end + end end diff --git a/lib/pry/slop/commands.rb b/lib/pry/slop/commands.rb new file mode 100644 index 00000000..47138a85 --- /dev/null +++ b/lib/pry/slop/commands.rb @@ -0,0 +1,177 @@ +class Pry +class Slop + class Commands + include Enumerable + + attr_reader :config, :commands + attr_writer :banner + + # Create a new instance of Slop::Commands and optionally build + # Slop instances via a block. Any configuration options used in + # this method will be the default configuration options sent to + # each Slop object created. + # + # config - An optional configuration Hash. + # block - Optional block used to define commands. + # + # Examples: + # + # commands = Slop::Commands.new do + # on :new do + # on '-o', '--outdir=', 'The output directory' + # on '-v', '--verbose', 'Enable verbose mode' + # end + # + # on :generate do + # on '--assets', 'Generate assets', :default => true + # end + # + # global do + # on '-D', '--debug', 'Enable debug mode', :default => false + # end + # end + # + # commands[:new].class #=> Slop + # commands.parse + # + def initialize(config = {}, &block) + @config = config + @commands = {} + @banner = nil + + if block_given? + block.arity == 1 ? yield(self) : instance_eval(&block) + end + end + + # Optionally set the banner for this command help output. + # + # banner - The String text to set the banner. + # + # Returns the String banner if one is set. + def banner(banner = nil) + @banner = banner if banner + @banner + end + + # Add a Slop instance for a specific command. + # + # command - A String or Symbol key used to identify this command. + # config - A Hash of configuration options to pass to Slop. + # block - An optional block used to pass options to Slop. + # + # Returns the newly created Slop instance mapped to command. + def on(command, config = {}, &block) + commands[command.to_s] = Slop.new(@config.merge(config), &block) + end + + # Add a Slop instance used when no other commands exist. + # + # config - A Hash of configuration options to pass to Slop. + # block - An optional block used to pass options to Slop. + # + # Returns the newly created Slop instance mapped to default. + def default(config = {}, &block) + on('default', config, &block) + end + + # Add a global Slop instance. + # + # config - A Hash of configuration options to pass to Slop. + # block - An optional block used to pass options to Slop. + # + # Returns the newly created Slop instance mapped to global. + def global(config = {}, &block) + on('global', config, &block) + end + + # Fetch the instance of Slop tied to a command. + # + # key - The String or Symbol key used to locate this command. + # + # Returns the Slop instance if this key is found, nil otherwise. + def [](key) + commands[key.to_s] + end + alias get [] + + # Parse a list of items. + # + # items - The Array of items to parse. + # + # Returns the original Array of items. + def parse(items = ARGV) + parse_items(items) + end + + # Enumerable interface. + def each(&block) + @commands.each(&block) + end + + # Parse a list of items, removing any options or option arguments found. + # + # items - The Array of items to parse. + # + # Returns the original Array of items with options removed. + def parse!(items = ARGV) + parse_items(items, true) + end + + # Returns a nested Hash with Slop options and values. See Slop#to_hash. + def to_hash + Hash[commands.map { |k, v| [k.to_sym, v.to_hash] }] + end + + # Returns the help String. + def to_s + defaults = commands.delete('default') + globals = commands.delete('global') + helps = commands.reject { |_, v| v.options.none? } + helps.merge!('Global options' => globals.to_s) if globals + helps.merge!('Other options' => defaults.to_s) if defaults + banner = @banner ? "#{@banner}\n" : "" + banner + helps.map { |key, opts| " #{key}\n#{opts}" }.join("\n\n") + end + alias help to_s + + # Returns the inspection String. + def inspect + "#" + end + + private + + # Parse a list of items. + # + # items - The Array of items to parse. + # bang - When true, #parse! will be called instead of #parse. + # + # Returns the Array of items (with options removed if bang == true). + def parse_items(items, bang = false) + if opts = commands[items[0].to_s] + items.shift + bang ? opts.parse!(items) : opts.parse(items) + execute_global_opts(items, bang) + else + if opts = commands['default'] + bang ? opts.parse!(items) : opts.parse(items) + else + if config[:strict] && items[0] + raise InvalidCommandError, "Unknown command `#{items[0]}`" + end + end + execute_global_opts(items, bang) + end + items + end + + def execute_global_opts(items, bang) + if global_opts = commands['global'] + bang ? global_opts.parse!(items) : global_opts.parse(items) + end + end + + end +end +end diff --git a/lib/pry/slop/option.rb b/lib/pry/slop/option.rb new file mode 100644 index 00000000..eeafd1a6 --- /dev/null +++ b/lib/pry/slop/option.rb @@ -0,0 +1,192 @@ +class Pry +class Slop + class Option + + # The default Hash of configuration options this class uses. + DEFAULT_OPTIONS = { + :argument => false, + :optional_argument => false, + :tail => false, + :default => nil, + :callback => nil, + :delimiter => ',', + :limit => 0, + :match => nil, + :optional => true, + :required => false, + :as => String, + :autocreated => false + } + + attr_reader :short, :long, :description, :config, :types + attr_accessor :count + attr_writer :value + + # Incapsulate internal option information, mainly used to store + # option specific configuration data, most of the meat of this + # class is found in the #value method. + # + # slop - The instance of Slop tied to this Option. + # short - The String or Symbol short flag. + # long - The String or Symbol long flag. + # description - The String description text. + # config - A Hash of configuration options. + # block - An optional block used as a callback. + def initialize(slop, short, long, description, config = {}, &block) + @slop = slop + @short = short + @long = long + @description = description + @config = DEFAULT_OPTIONS.merge(config) + @count = 0 + @callback = block_given? ? block : config[:callback] + + @types = { + :string => proc { |v| v.to_s }, + :symbol => proc { |v| v.to_sym }, + :integer => proc { |v| value_to_integer(v) }, + :float => proc { |v| value_to_float(v) }, + :array => proc { |v| v.split(@config[:delimiter], @config[:limit]) }, + :range => proc { |v| value_to_range(v) }, + :count => proc { |v| @count } + } + + if long && long.size > @slop.config[:longest_flag] + @slop.config[:longest_flag] = long.size + end + + @config.each_key do |key| + self.class.send(:define_method, "#{key}?") { !!@config[key] } + end + end + + # Returns true if this option expects an argument. + def expects_argument? + config[:argument] && config[:argument] != :optional + end + + # Returns true if this option accepts an optional argument. + def accepts_optional_argument? + config[:optional_argument] || config[:argument] == :optional + end + + # Returns the String flag of this option. Preferring the long flag. + def key + long || short + end + + # Call this options callback if one exists, and it responds to call(). + # + # Returns nothing. + def call(*objects) + @callback.call(*objects) if @callback.respond_to?(:call) + end + + # Fetch the argument value for this option. + # + # Returns the Object once any type conversions have taken place. + def value + value = @value.nil? ? config[:default] : @value + + if [true, false, nil].include?(value) && config[:as].to_s != 'count' + return value + end + + type = config[:as] + if type.respond_to?(:call) + type.call(value) + else + if callable = types[type.to_s.downcase.to_sym] + callable.call(value) + else + value + end + end + end + + # Returns the help String for this option. + def to_s + return config[:help] if config[:help].respond_to?(:to_str) + + out = " " + out += short ? "-#{short}, " : ' ' * 4 + + if long + out += "--#{long}" + size = long.size + diff = @slop.config[:longest_flag] - size + out += " " * (diff + 6) + else + out += " " * (@slop.config[:longest_flag] + 8) + end + + "#{out}#{description}" + end + alias help to_s + + # Returns the String inspection text. + def inspect + "#= Gem::Version.new('1.2.0') then s.add_runtime_dependency(%q, ["~> 1.0.5"]) - s.add_runtime_dependency(%q, ["< 3", ">= 2.4.4"]) s.add_runtime_dependency(%q, ["~> 0.7.1"]) s.add_development_dependency(%q, ["~> 1.1"]) s.add_development_dependency(%q, ["~> 1.3"]) s.add_development_dependency(%q, ["~> 0.9"]) else s.add_dependency(%q, ["~> 1.0.5"]) - s.add_dependency(%q, ["< 3", ">= 2.4.4"]) s.add_dependency(%q, ["~> 0.7.1"]) s.add_dependency(%q, ["~> 1.1"]) s.add_dependency(%q, ["~> 1.3"]) @@ -37,7 +35,6 @@ Gem::Specification.new do |s| end else s.add_dependency(%q, ["~> 1.0.5"]) - s.add_dependency(%q, ["< 3", ">= 2.4.4"]) s.add_dependency(%q, ["~> 0.7.1"]) s.add_dependency(%q, ["~> 1.1"]) s.add_dependency(%q, ["~> 1.3"])