1
0
Fork 0
mirror of https://github.com/pry/pry.git synced 2022-11-09 12:35:05 -05:00

Merge pull request #1498 from pry/pry-slop

Copy Slop 3.2 as Pry::Slop
This commit is contained in:
Ryan Fitzgerald 2015-12-10 22:48:34 -08:00
commit f0bbb268d6
14 changed files with 1106 additions and 21 deletions

View file

@ -1,4 +1,6 @@
### HEAD ### HEAD
* Remove Slop as a runtime dependency by vendoring v3.4 as Pry::Slop.
People can depend on Slop v4 and Pry at the same time without running into version conflicts. ([#1497](https://github.com/pry/pry/issues/1497))
* Fix auto-indentation of code that uses a single-line rescue ([#1450](https://github.com/pry/pry/issues/1450)) * Fix auto-indentation of code that uses a single-line rescue ([#1450](https://github.com/pry/pry/issues/1450))
* Remove "Pry::Config#refresh", please use "Pry::Config#clear" instead. * Remove "Pry::Config#refresh", please use "Pry::Config#clear" instead.
* Defining a method called "ls" no longer breaks the "ls" command ([#1407](https://github.com/pry/pry/issues/1407)) * Defining a method called "ls" no longer breaks the "ls" command ([#1407](https://github.com/pry/pry/issues/1407))

View file

@ -126,7 +126,7 @@ require 'shellwords'
require 'stringio' require 'stringio'
require 'strscan' require 'strscan'
require 'coderay' require 'coderay'
require 'slop' require 'pry/slop'
require 'rbconfig' require 'rbconfig'
require 'tempfile' require 'tempfile'
require 'pathname' require 'pathname'

View file

@ -19,7 +19,7 @@ class Pry
# as CLI options. # as CLI options.
attr_accessor :input_args attr_accessor :input_args
# Add another set of CLI options (a Slop block) # Add another set of CLI options (a Pry::Slop block)
def add_options(&block) def add_options(&block)
if options if options
old_options = options old_options = options
@ -68,16 +68,16 @@ class Pry
self.input_args = args self.input_args = args
begin begin
opts = Slop.parse!( opts = Pry::Slop.parse!(
args, args,
:help => true, :help => true,
:multiple_switches => false, :multiple_switches => false,
:strict => true, :strict => true,
&options &options
) )
rescue Slop::InvalidOptionError rescue Pry::Slop::InvalidOptionError
# Display help message on unknown switches and exit. # Display help message on unknown switches and exit.
puts Slop.new(&options) puts Pry::Slop.new(&options)
exit exit
end end
@ -124,7 +124,7 @@ end
# Bring in options defined by plugins # Bring in options defined by plugins
Slop.new do Pry::Slop.new do
on "no-plugins" do on "no-plugins" do
Pry.config.should_load_plugins = false Pry.config.should_load_plugins = false
end end

View file

@ -531,7 +531,7 @@ class Pry
# subclasses. # subclasses.
# #
# Create subclasses using {Pry::CommandSet#create_command}, and override the # Create subclasses using {Pry::CommandSet#create_command}, and override the
# `options(opt)` method to set up an instance of Slop, and the `process` # `options(opt)` method to set up an instance of Pry::Slop, and the `process`
# method to actually run the command. If necessary, you can also override # method to actually run the command. If necessary, you can also override
# `setup` which will be called before `options`, for example to require any # `setup` which will be called before `options`, for example to require any
# gems your command needs to run, or to set up state. # gems your command needs to run, or to set up state.
@ -607,15 +607,15 @@ class Pry
end end
end end
# Return the help generated by Slop for this command. # Return the help generated by Pry::Slop for this command.
def help def help
slop.help slop.help
end end
# Return an instance of Slop that can parse either subcommands or the # Return an instance of Pry::Slop that can parse either subcommands or the
# options that this command accepts. # options that this command accepts.
def slop def slop
Slop.new do |opt| Pry::Slop.new do |opt|
opt.banner(unindent(self.class.banner)) opt.banner(unindent(self.class.banner))
subcommands(opt) subcommands(opt)
options(opt) options(opt)
@ -644,7 +644,7 @@ class Pry
# end # end
def setup; end def setup; end
# A method to setup Slop commands so it can parse the subcommands your # A method to setup Pry::Slop commands so it can parse the subcommands your
# command expects. If you need to set up default values, use `setup` # command expects. If you need to set up default values, use `setup`
# instead. # instead.
# #
@ -679,7 +679,7 @@ class Pry
# end # end
def subcommands(cmd); end def subcommands(cmd); end
# A method to setup Slop so it can parse the options your command expects. # A method to setup Pry::Slop so it can parse the options your command expects.
# #
# @note Please don't do anything side-effecty in the main part of this # @note Please don't do anything side-effecty in the main part of this
# method, as it may be called by Pry at any time for introspection reasons. # method, as it may be called by Pry at any time for introspection reasons.
@ -696,7 +696,7 @@ class Pry
# The actual body of your command should go here. # The actual body of your command should go here.
# #
# The `opts` mehod can be called to get the options that Slop has passed, # The `opts` mehod can be called to get the options that Pry::Slop has passed,
# and `args` gives the remaining, unparsed arguments. # and `args` gives the remaining, unparsed arguments.
# #
# The return value of this method is discarded unless the command was # The return value of this method is discarded unless the command was

View file

@ -3,7 +3,7 @@ class Pry
module OptionsHelpers module OptionsHelpers
module_function module_function
# Add method options to the Slop instance # Add method options to the Pry::Slop instance
def method_options(opt) def method_options(opt)
@method_target = target @method_target = target
opt.on :M, "instance-methods", "Operate on instance methods." opt.on :M, "instance-methods", "Operate on instance methods."

View file

@ -435,7 +435,7 @@ class Pry
# @return [Boolean] `true` if `val` is a command, `false` otherwise # @return [Boolean] `true` if `val` is a command, `false` otherwise
def process_command_safely(val) def process_command_safely(val)
process_command(val) process_command(val)
rescue CommandError, Slop::InvalidOptionError, MethodSource::SourceNotFoundError => e rescue CommandError, Pry::Slop::InvalidOptionError, MethodSource::SourceNotFoundError => e
Pry.last_internal_error = e Pry.last_internal_error = e
output.puts "Error: #{e.message}" output.puts "Error: #{e.message}"
true true

661
lib/pry/slop.rb Normal file
View file

@ -0,0 +1,661 @@
class Pry::Slop
require_relative 'slop/option'
require_relative 'slop/commands'
include Enumerable
VERSION = '3.4.0'
# The main Error class, all Exception classes inherit from this class.
class Error < StandardError; end
# Raised when an option argument is expected but none are given.
class MissingArgumentError < Error; end
# Raised when an option is expected/required but not present.
class MissingOptionError < Error; end
# Raised when an argument does not match its intended match constraint.
class InvalidArgumentError < Error; end
# Raised when an invalid option is found and the strict flag is enabled.
class InvalidOptionError < Error; end
# Raised when an invalid command is found and the strict flag is enabled.
class InvalidCommandError < Error; end
# 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
}
class << self
# 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)
parse! items.dup, config, &block
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.
#
# Returns a new instance of Slop.
def parse!(items = ARGV, config = {}, &block)
config, items = items, ARGV if items.is_a?(Hash) && config.empty?
slop = Pry::Slop.new config, &block
slop.parse! items
slop
end
# 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)
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 long && long.end_with?('=')
long.sub!(/\=$/, '')
opt.config[:argument] = true
end
end
opts
end
end
# The Hash of configuration options for this Slop instance.
attr_reader :config
# The Array of Slop::Option objects tied to this Slop instance.
attr_reader :options
# Create a new instance of Slop and optionally build options via a block.
#
# config - A Hash of configuration options.
# block - An optional block used to specify options.
def initialize(config = {}, &block)
@config = DEFAULT_OPTIONS.merge(config)
@options = []
@commands = {}
@trash = []
@triggered_options = []
@unknown_options = []
@callbacks = {}
@separators = {}
@runner = nil
if block_given?
block.arity == 1 ? yield(self) : instance_eval(&block)
end
if config[:help]
on('-h', '--help', 'Display this help message.', :tail => true) do
$stderr.puts help
end
end
end
# Is strict mode enabled?
#
# Returns true if strict mode is enabled, false otherwise.
def strict?
config[:strict]
end
# Set the banner.
#
# banner - The String to set the banner.
def banner=(banner)
config[:banner] = banner
end
# Get or set the banner.
#
# banner - The String to set the banner.
#
# Returns the banner String.
def banner(banner = nil)
config[:banner] = banner if banner
config[:banner]
end
# Set the description (used for commands).
#
# desc - The String to set the description.
def description=(desc)
config[:description] = desc
end
# Get or set the description (used for commands).
#
# desc - The String to set the description.
#
# Returns the description String.
def description(desc = nil)
config[:description] = desc if desc
config[:description]
end
# Add a new command.
#
# command - The Symbol or String used to identify this command.
# options - A Hash of configuration options (see Slop::new)
#
# Returns a new instance of Slop mapped to this command.
def command(command, options = {}, &block)
@commands[command.to_s] = Pry::Slop.new(options, &block)
end
# 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.dup, &block
items
end
# 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)
if items.empty? && @callbacks[:empty]
@callbacks[:empty].each { |cb| cb.call(self) }
return items
end
if cmd = @commands[items[0]]
return cmd.parse! items[1..-1]
end
items.each_with_index do |item, index|
@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) }
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
@runner.call(self, items) if @runner.respond_to?(:call)
items
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 = fetch_option(key)
option.value if option
end
alias get []
# Returns a new Hash with option flags as keys and option values as values.
#
# include_commands - If true, merge options from all sub-commands.
def to_hash(include_commands = false)
hash = Hash[options.map { |opt| [opt.key.to_sym, opt.value] }]
if include_commands
@commands.each { |cmd, opts| hash.merge!(cmd.to_sym => opts.to_hash) }
end
hash
end
alias to_h to_hash
# Enumerable interface. Yields each Slop::Option.
def each(&block)
options.each(&block)
end
# Specify code to be executed when these options are parsed.
#
# callable - An object responding to a call method.
#
# yields - The instance of Slop parsing these options
# An Array of unparsed arguments
#
# Example:
#
# Slop.parse do
# on :v, :verbose
#
# run do |opts, args|
# puts "Arguments: #{args.inspect}" if opts.verbose?
# end
# end
def run(callable = nil, &block)
@runner = callable || block
unless @runner.respond_to?(:call)
raise ArgumentError, "You must specify a callable object or a block to #run"
end
end
# Check for an options presence.
#
# Examples:
#
# opts.parse %w( --foo )
# opts.present?(:foo) #=> true
# opts.present?(:bar) #=> false
#
# 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
# 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_missing?(method_name, include_private = false)
options.any? { |o| o.key == method_name.to_s.chop } || super
end
# Fetch a list of options which were missing from the parsed list.
#
# Examples:
#
# 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
# Fetch a Slop::Option object.
#
# 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
# Fetch a Slop object associated with this command.
#
# command - The String or Symbol name of the command.
#
# Examples:
#
# opts.command :foo do
# on :v, :verbose, 'Enable verbose mode'
# end
#
# # ruby run.rb foo -v
# opts.fetch_command(:foo).verbose? #=> true
def fetch_command(command)
@commands[command.to_s]
end
# Add a callback.
#
# label - The Symbol identifier to attach this callback.
#
# Returns nothing.
def add_callback(label, &block)
(@callbacks[label] ||= []) << block
end
# 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
# 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 @commands.any?
optstr << "\n" if !optstr.empty?
optstr << "\nAvailable commands:\n\n"
optstr << commands_to_help
optstr << "\n\nSee `<command> --help` for more information on a specific command."
end
banner = config[:banner]
banner = "Usage: #{File.basename($0, '.*')}#{' [command]' if @commands.any?} [options]" if banner.nil?
if banner
"#{banner}\n#{@separators[0] ? "#{@separators[0]}\n" : ''}#{optstr}"
else
optstr
end
end
alias help to_s
private
# Convenience method for present?(:option).
#
# Examples:
#
# opts.parse %( --verbose )
# opts.verbose? #=> true
# 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?('?')
meth.chop!
present?(meth) || present?(meth.gsub('_', '-'))
else
super
end
end
# Process a list item, figure out if it's an option, execute any
# callbacks, assign any option arguments, and do some sanity checks.
#
# 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
option.count += 1 unless item.start_with?('--no-')
option.count += 1 if option.key[0, 3] == "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
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
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
# 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
return
end
if argument
unless item && item.end_with?("=#{argument}")
@trash << index + 1 unless option.argument_in_value
end
option.value = argument
else
option.value = option.count > 0
end
if option.match? && !argument.match(option.config[:match])
raise InvalidArgumentError, "#{argument} is an invalid argument"
end
option.call(option.value)
end
# 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, nil, index)
argument.split('').each do |key|
next unless opt = fetch_option(key)
opt.count += 1
execute_option(opt, nil, 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 flag
when /\A--?([^=]+)=(.+)\z/, /\A-([a-zA-Z])(.+)\z/, /\A--no-(.+)\z/
option, argument = fetch_option($1), ($2 || false)
option.argument_in_value = true if option
end
end
[option, argument]
end
# 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]
if !fetch_option(flag) && !@trash.include?(index)
option = build_option(Array(flag))
argument = items[index + 1]
option.config[:argument] = (argument && argument !~ /\A--?/)
option.config[:autocreated] = true
options << option
end
end
# 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.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
# 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 flag.size == 2 && flag.end_with?('=')
config[:argument] ||= true
flag.chop!
end
if flag.size == 1
objects.shift
flag
end
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
def commands_to_help
padding = 0
@commands.each { |c, _| padding = c.size if c.size > padding }
@commands.map do |cmd, opts|
" #{cmd}#{' ' * (padding - cmd.size)} #{opts.description}"
end.join("\n")
end
end

20
lib/pry/slop/LICENSE Normal file
View file

@ -0,0 +1,20 @@
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.

196
lib/pry/slop/commands.rb Normal file
View file

@ -0,0 +1,196 @@
class Pry::Slop
class Commands
include Enumerable
attr_reader :config, :commands, :arguments
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
@triggered_command = nil
warn "[DEPRECATED] Slop::Commands is deprecated and will be removed in "\
"Slop version 4. Check out http://injekt.github.com/slop/#commands for "\
"a new implementation of commands."
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 []
# Check for a command presence.
#
# Examples:
#
# cmds.parse %w( foo )
# cmds.present?(:foo) #=> true
# cmds.present?(:bar) #=> false
#
# Returns true if the given key is present in the parsed arguments.
def present?(key)
key.to_s == @triggered_command
end
# Enumerable interface.
def each(&block)
@commands.each(&block)
end
# Parse a list of items.
#
# items - The Array of items to parse.
#
# Returns the original Array of items.
def parse(items = ARGV)
parse! items.dup
items
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)
if opts = commands[items[0].to_s]
@triggered_command = items.shift
execute_arguments! items
opts.parse! items
execute_global_opts! items
else
if opts = commands['default']
opts.parse! items
else
if config[:strict] && items[0]
raise InvalidCommandError, "Unknown command `#{items[0]}`"
end
end
execute_global_opts! items
end
items
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? }
if globals && globals.options.any?
helps.merge!('Global options' => globals.to_s)
end
if defaults && defaults.options.any?
helps.merge!('Other options' => defaults.to_s)
end
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
"#<Slop::Commands #{config.inspect} #{commands.values.map(&:inspect)}>"
end
private
# Returns nothing.
def execute_arguments!(items)
@arguments = items.take_while { |arg| !arg.start_with?('-') }
items.shift @arguments.size
end
# Returns nothing.
def execute_global_opts!(items)
if global_opts = commands['global']
global_opts.parse! items
end
end
end
end

208
lib/pry/slop/option.rb Normal file
View file

@ -0,0 +1,208 @@
class Pry::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, :argument_in_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]
@value = nil
@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) },
: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|
predicate = :"#{key}?"
unless self.class.method_defined? predicate
self.class.__send__(:define_method, predicate) { !!@config[key] }
end
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
# Set the new argument value for this option.
#
# We use this setter method to handle concatenating lists. That is,
# when an array type is specified and used more than once, values from
# both options will be grouped together and flattened into a single array.
def value=(new_value)
if config[:as].to_s.downcase == 'array'
@value ||= []
if new_value.respond_to?(:split)
@value.concat new_value.split(config[:delimiter], config[:limit])
end
else
@value = new_value
end
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 = " #{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
"#<Slop::Option [-#{short} | --#{long}" +
"#{'=' if expects_argument?}#{'=?' if accepts_optional_argument?}]" +
" (#{description}) #{config.inspect}"
end
private
# Convert an object to an Integer if possible.
#
# value - The Object we want to convert to an integer.
#
# Returns the Integer value if possible to convert, else a zero.
def value_to_integer(value)
if @slop.strict?
begin
Integer(value.to_s, 10)
rescue ArgumentError
raise InvalidArgumentError, "#{value} could not be coerced into Integer"
end
else
value.to_s.to_i
end
end
# Convert an object to a Float if possible.
#
# value - The Object we want to convert to a float.
#
# Returns the Float value if possible to convert, else a zero.
def value_to_float(value)
if @slop.strict?
begin
Float(value.to_s)
rescue ArgumentError
raise InvalidArgumentError, "#{value} could not be coerced into Float"
end
else
value.to_s.to_f
end
end
# Convert an object to a Range if possible.
#
# value - The Object we want to convert to a range.
#
# Returns the Range value if one could be found, else the original object.
def value_to_range(value)
case value.to_s
when /\A(\-?\d+)\z/
Range.new($1.to_i, $1.to_i)
when /\A(-?\d+?)(\.\.\.?|-|,)(-?\d+)\z/
Range.new($1.to_i, $3.to_i, $2 == '...')
else
if @slop.strict?
raise InvalidArgumentError, "#{value} could not be coerced into Range"
else
value
end
end
end
end
end

View file

@ -19,7 +19,6 @@ Gem::Specification.new do |s|
s.files = `git ls-files bin lib *.md LICENSE`.split("\n") s.files = `git ls-files bin lib *.md LICENSE`.split("\n")
s.add_dependency 'coderay', '~> 1.1.0' s.add_dependency 'coderay', '~> 1.1.0'
s.add_dependency 'slop', '~> 3.4'
s.add_dependency 'method_source', '~> 0.8.1' s.add_dependency 'method_source', '~> 0.8.1'
s.add_development_dependency 'bundler', '~> 1.0' s.add_development_dependency 'bundler', '~> 1.0'
end end

View file

@ -3,7 +3,7 @@ require_relative '../../helper'
describe Pry::Command::Cat::FileFormatter do describe Pry::Command::Cat::FileFormatter do
before do before do
@p = Pry.new @p = Pry.new
@opt = Slop.new @opt = Pry::Slop.new
end end
describe "#file_and_line" do describe "#file_and_line" do

View file

@ -7,13 +7,12 @@ describe "gem-list" do
it 'should work arglessly' do it 'should work arglessly' do
list = pry_eval('gem-list') list = pry_eval('gem-list')
expect(list).to match(/slop \(/)
expect(list).to match(/rspec \(/) expect(list).to match(/rspec \(/)
end end
it 'should find arg' do it 'should find arg' do
prylist = pry_eval('gem-list slop') prylist = pry_eval('gem-list method_source')
expect(prylist).to match(/slop \(/) expect(prylist).to match(/method_source \(/)
expect(prylist).not_to match(/rspec/) expect(prylist).not_to match(/rspec/)
end end

View file

@ -26,7 +26,7 @@ if ENV["SET_TRACE_FUNC"]
} }
end end
puts "Ruby v#{RUBY_VERSION} (#{defined?(RUBY_ENGINE) ? RUBY_ENGINE : "ruby"}), Pry v#{Pry::VERSION}, method_source v#{MethodSource::VERSION}, CodeRay v#{CodeRay::VERSION}, Slop v#{Slop::VERSION}" puts "Ruby v#{RUBY_VERSION} (#{defined?(RUBY_ENGINE) ? RUBY_ENGINE : "ruby"}), Pry v#{Pry::VERSION}, method_source v#{MethodSource::VERSION}, CodeRay v#{CodeRay::VERSION}, Pry::Slop v#{Pry::Slop::VERSION}"
RSpec.configure do |config| RSpec.configure do |config|
config.expect_with :rspec do |c| config.expect_with :rspec do |c|