This commit is contained in:
@@ -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.
|
||||
@@ -0,0 +1,190 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class 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."
|
||||
|
||||
return unless block_given?
|
||||
|
||||
block.arity == 1 ? yield(self) : instance_eval(&block)
|
||||
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
|
||||
elsif (opts = commands['default'])
|
||||
opts.parse! items
|
||||
elsif config[:strict] && items[0]
|
||||
raise InvalidCommandError, "Unknown command `#{items[0]}`"
|
||||
end
|
||||
execute_global_opts! items
|
||||
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? }
|
||||
helps['Global options'] = globals.to_s if globals && globals.options.any?
|
||||
helps['Other options'] = defaults.to_s if defaults && defaults.options.any?
|
||||
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)
|
||||
return unless (global_opts = commands['global'])
|
||||
|
||||
global_opts.parse!(items)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,210 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
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
|
||||
}.freeze
|
||||
|
||||
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 { @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?(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.casecmp('array') == 0
|
||||
@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
|
||||
|
||||
return value if [true, false, nil].include?(value) && config[:as].to_s != 'count'
|
||||
|
||||
type = config[:as]
|
||||
if type.respond_to?(:call)
|
||||
type.call(value)
|
||||
elsif (callable = types[type.to_s.downcase.to_sym])
|
||||
callable.call(value)
|
||||
else
|
||||
value
|
||||
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(Regexp.last_match(1).to_i, Regexp.last_match(1).to_i)
|
||||
when /\A(-?\d+?)(\.\.\.?|-|,)(-?\d+)\z/
|
||||
Range.new(
|
||||
Regexp.last_match(1).to_i,
|
||||
Regexp.last_match(3).to_i,
|
||||
Regexp.last_match(2) == '...'
|
||||
)
|
||||
else
|
||||
if @slop.strict?
|
||||
raise InvalidArgumentError, "#{value} could not be coerced into Range"
|
||||
end
|
||||
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user