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
|
module Slop
class Options
include Enumerable
DEFAULT_CONFIG = {
suppress_errors: false,
type: "null",
banner: true,
}
# 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
def initialize(**config)
@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.last << "\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(&:help?).sort_by(&:tail).each_with_index 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"
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
|