1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
|
module Slop
class Options
include Enumerable
DEFAULT_CONFIG = {
suppress_errors: false,
type: "null",
banner: true,
underscore_flags: true,
validate_types: false,
}
# The Array of Option instances we've created.
attr_reader :options
# An Array of separators used for the help text.
attr_reader :separators
# Our Parser instance.
attr_reader :parser
# A Hash of configuration options.
attr_reader :config
# The String banner prefixed to the help string.
attr_accessor :banner
# Whether we should validate types of values provided by the user
attr_accessor :validate_types
def initialize(**config, &block)
@options = []
@separators = []
@banner = config[:banner].is_a?(String) ? config[:banner] : config.fetch(:banner, "usage: #{$0} [options]")
@config = DEFAULT_CONFIG.merge(config)
@parser = Parser.new(self, **@config)
yield self if block_given?
end
# Add a new option. This method is an alias for adding a NullOption
# (i.e an option with an ignored return value).
#
# Example:
#
# opts = Slop.parse do |o|
# o.on '--version' do
# puts Slop::VERSION
# end
# end
#
# opts.to_hash #=> {}
#
# Returns the newly created Option subclass.
def on(*flags, **config, &block)
desc = flags.pop unless flags.last.start_with?('-')
config = self.config.merge(config)
klass = Slop.string_to_option_class(config[:type].to_s)
option = klass.new(flags, desc, **config, &block)
add_option option
end
# Add a separator between options. Used when displaying
# the help text.
def separator(string = "")
if separators[options.size]
separators[-1] += "\n#{string}"
else
separators[options.size] = string
end
end
# Sugar to avoid `options.parser.parse(x)`.
def parse(strings)
parser.parse(strings)
end
# Implements the Enumerable interface.
def each(&block)
options.each(&block)
end
# Handle custom option types. Will fall back to raising an
# exception if an option is not defined.
def method_missing(name, *args, **config, &block)
if respond_to_missing?(name)
config[:type] = name
on(*args, **config, &block)
else
super
end
end
def respond_to_missing?(name, include_private = false)
Slop.option_defined?(name) || super
end
# Return a copy of our options Array.
def to_a
options.dup
end
# Returns the help text for this options. Used by Result#to_s.
def to_s(prefix: " " * 4)
str = config[:banner] ? "#{banner}\n" : ""
len = longest_flag_length
options.select.each_with_index.sort_by{ |o,i| [o.tail, i] }.each do |opt, i|
# use the index to fetch an associated separator
if sep = separators[i]
str += "#{sep}\n"
end
str += "#{prefix}#{opt.to_s(offset: len)}\n" if opt.help?
end
if sep = separators[options.size]
str += "#{sep}\n"
end
str
end
private
def longest_flag_length
(o = longest_option) && o.flag.length || 0
end
def longest_option
options.max { |a, b| a.flag.length <=> b.flag.length }
end
def add_option(option)
options.each do |o|
flags = o.flags & option.flags
# Raise an error if we found an existing option with the same
# flags. I can't immediately see a use case for this..
if flags.any?
raise ArgumentError, "duplicate flags: #{flags}"
end
end
options << option
option
end
end
end
|