-
Notifications
You must be signed in to change notification settings - Fork 609
/
options.rb
254 lines (216 loc) · 6.55 KB
/
options.rb
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
module Rubinius
# A simple command line option parser.
class Options
# A single command line option.
class Option
attr_reader :short
attr_reader :long
attr_reader :arg
attr_reader :description
attr_reader :block
def initialize(short, long, arg, description, block)
@short = short
@long = long
@arg = arg
@description = description
@block = block
end
private :initialize
def arg?
@arg != nil
end
def optional?
@arg[0] == ?[ and @arg[-1] == ?]
end
def match?(opt)
opt == @short or opt == @long
end
end
# Raised if incorrect or incomplete formats are passed to #on.
class OptionError < Exception; end
# Raised if an unrecognized option is encountered.
class ParseError < Exception; end
attr_accessor :config
attr_accessor :banner
attr_accessor :width
attr_accessor :options
def initialize(banner="", width=30, config=nil)
@parse = true
@banner = banner
@config = config
@width = width
@options = []
@doc = []
@extra = []
@align = true
@on_extra = lambda { |x|
raise ParseError, "Unrecognized option: #{x}" if x[0] == ?-
@extra << x
}
yield self if block_given?
end
private :initialize
# Documentation for options is left aligned. For example,
#
# -a ARG Some description
# --big Another bit of info
# -c, --class Yet more info
#
# See +option_align+.
def left_align
@align = nil
end
# Documentation for options is aligned by option type. For example,
#
# -a ARG Some description
# --big Another bit of info
# -c, --class Yet more info
#
# See +left_align+
def option_align
@align = true
end
# Registers an option. Acceptable formats for arguments are:
#
# on "-a", "description"
# on "-a", "--abdc", "description"
# on "-a", "ARG", "description"
# on "--abdc", "ARG", "description"
# on "-a", "--abdc", "ARG", "description"
# on "-a", "[ARG]", "description"
#
# The [ARG] form specifies an optional argument. The argument
# must be attached to the option. In the case of a short option,
# the form "-aARG" must be used. For a long option, the form
# '--opt=ARG' must be used.
#
# If an block is passed, it will be invoked when the option is
# matched. Not passing a block is permitted, but nonsensical.
def on(*args, &block)
raise OptionError, "option and description are required" if args.size < 2
description = args.pop
short, long, argument = nil
args.each do |arg|
if arg[0] == ?-
if arg[1] == ?-
long = arg
else
short = arg
end
else
argument = arg
end
end
add short, long, argument, description, block
end
# Adds documentation text for an option and adds an +Option+
# instance to the list of registered options.
def add(short, long, arg, description, block)
pad = @align ? " " : ""
s = short ? short.dup : pad
s << (short ? ", " : pad) if long
doc " #{s}#{long} #{arg}".ljust(@width-1) + " #{description}"
@options << Option.new(short, long, arg, description, block)
end
# Searches all registered options to find a match for +opt+. Returns
# +nil+ if no registered options match.
def match?(opt)
@options.find { |o| o.match? opt }
end
# Processes an option. Calles the #on_extra block (or default) for
# unrecognized options. For registered options, possibly fetches an
# argument and invokes the option's block if it is not nil.
def process(argv, entry, opt, arg)
unless option = match?(opt)
@on_extra[entry]
else
if option.arg?
if arg.nil?
unless option.optional?
arg = argv.shift
end
end
unless arg or option.optional?
raise ParseError, "No argument provided for #{opt}"
end
option.block[arg] if option.block
else
option.block[] if option.block
end
end
option
end
# Splits a string at +n+ characters into the +opt+ and the +rest+.
# The +arg+ is set to +nil+ if +rest+ is an empty string.
def split(str, n)
opt = str[0, n]
rest = str[n, str.size] || ""
arg = rest == "" ? nil : rest
return opt, arg, rest
end
# Parses an array of command line entries, calling blocks for
# registered options.
def parse(argv=ARGV)
argv = Array(argv)
while @parse and entry = argv.shift
# collect everything that is not an option
if entry[0] != ?-
@on_extra[entry]
next
end
# this is a long option
if entry[1] == ?-
opt, arg = entry.split "="
process argv, entry, opt, arg
next
end
# disambiguate short option group from short option with argument
opt, arg, rest = split entry, 2
# process first option
option = process argv, entry, opt, arg
next unless option and not option.arg?
# process the rest of the options
while rest.size > 0
opt, arg, rest = split rest, 1
opt = "-" + opt
option = process argv, opt, opt, arg
break if option.arg?
end
end
@extra
rescue ParseError => e
puts self
puts e
exit 1
end
def start_parsing
@parse = true
end
def stop_parsing
@parse = false
end
# Adds a string of documentation text inline in the text generated
# from the options. See #on and #add.
def doc(str)
@doc << str
end
# Convenience method for providing -v, --version options.
def version(version, &block)
show = block || lambda { puts "#{File.basename $0} #{version}"; exit }
on "-v", "--version", "Show version", &show
end
# Convenience method for providing -h, --help options.
def help(&block)
help = block || lambda { puts self; exit 1 }
on "-h", "--help", "Show this message", &help
end
# Stores a block that will be called with unrecognized options
def on_extra(&block)
@on_extra = block
end
# Returns a string representation of the options and doc strings.
def to_s
@banner + "\n\n" + @doc.join("\n") + "\n"
end
end
end