config_parser 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History +5 -0
- data/MIT-LICENSE +22 -0
- data/README +84 -0
- data/lib/config_parser.rb +393 -0
- data/lib/config_parser/flag.rb +121 -0
- data/lib/config_parser/list.rb +38 -0
- data/lib/config_parser/option.rb +40 -0
- data/lib/config_parser/switch.rb +43 -0
- data/lib/config_parser/utils.rb +135 -0
- data/lib/config_parser/version.rb +7 -0
- metadata +90 -0
data/History
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2010, Simon Chiang
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
= ConfigParser
|
2
|
+
|
3
|
+
Parse command-line options into a configuration hash.
|
4
|
+
|
5
|
+
== Description
|
6
|
+
|
7
|
+
ConfigParser is an analogue of
|
8
|
+
{OptionParser}[http://www.ruby-doc.org/core/classes/OptionParser.html] that
|
9
|
+
formalizes the pattern of setting parsed options into a hash. ConfigParser
|
10
|
+
uses a similar, simplified declaration syntax that places less emphasis on the
|
11
|
+
conversion of inputs to objects, preferring instead to delegate that
|
12
|
+
responsibility to whatever consumes the hash.
|
13
|
+
|
14
|
+
== Usage
|
15
|
+
|
16
|
+
ConfigParser can be used much like OptionParser, where the parser itself can
|
17
|
+
be used as a delegate to a config hash.
|
18
|
+
|
19
|
+
parser = ConfigParser.new
|
20
|
+
parser.on '-s', '--long LONG', 'a standard option' do |value|
|
21
|
+
parser[:long] = value
|
22
|
+
end
|
23
|
+
|
24
|
+
parser.on '--[no-]switch', 'a switch' do |value|
|
25
|
+
parser[:switch] = value
|
26
|
+
end
|
27
|
+
|
28
|
+
parser.on '--flag', 'a flag' do
|
29
|
+
parser[:flag] = true
|
30
|
+
end
|
31
|
+
|
32
|
+
parser.parse('a b --long arg --switch --flag c')
|
33
|
+
# => ['a', 'b', 'c']
|
34
|
+
|
35
|
+
parser.config
|
36
|
+
# => {:long => 'arg', :switch => true, :flag => true}
|
37
|
+
|
38
|
+
parser.to_s
|
39
|
+
# => %q{
|
40
|
+
# -s, --long LONG a standard option
|
41
|
+
# --[no-]switch a switch
|
42
|
+
# --flag a flag
|
43
|
+
# }
|
44
|
+
|
45
|
+
ConfigParser formalizes this pattern of setting values into a config hash as
|
46
|
+
they occur, and adds the ability to specify default values.
|
47
|
+
|
48
|
+
parser = ConfigParser.new
|
49
|
+
parser.add(:key, 'default')
|
50
|
+
|
51
|
+
parser.parse('a b --key option c') # => ['a', 'b', 'c']
|
52
|
+
parser.config # => {:key => 'option'}
|
53
|
+
|
54
|
+
parser.parse('a b c') # => ['a', 'b', 'c']
|
55
|
+
parser.config # => {:key => 'default'}
|
56
|
+
|
57
|
+
Config keys may be mapped to options using arguments like those given to 'on',
|
58
|
+
or using an options hash. A block can be given to processes values before they
|
59
|
+
are set as configs.
|
60
|
+
|
61
|
+
parser = ConfigParser.new
|
62
|
+
parser.add(:x, nil, '-o', '--one', 'by args') {|value| value.upcase }
|
63
|
+
parser.add(:y, nil, :long => 'two', :desc => 'by hash')
|
64
|
+
|
65
|
+
parser.parse('a b --one value --two c')
|
66
|
+
# => ['a', 'b', 'c']
|
67
|
+
|
68
|
+
parser.config
|
69
|
+
# => {:x => 'VALUE', :y => true}
|
70
|
+
|
71
|
+
ConfigParser integrates well with libraries like
|
72
|
+
{Configurable}[http://tap.rubyforge.org/configurable] that are designed to set
|
73
|
+
configurations via a config hash.
|
74
|
+
|
75
|
+
== Installation
|
76
|
+
|
77
|
+
ConfigParser is available as a gem via {Gemcutter}[http://rubygems.org/gems/config_parser].
|
78
|
+
|
79
|
+
% gem install config_parser
|
80
|
+
|
81
|
+
== Info
|
82
|
+
|
83
|
+
Copyright (c) 2010, Simon Chiang
|
84
|
+
License:: {MIT-Style}[link:files/MIT-LICENSE.html]
|
@@ -0,0 +1,393 @@
|
|
1
|
+
require 'config_parser/list'
|
2
|
+
autoload(:Shellwords, 'shellwords')
|
3
|
+
|
4
|
+
# ConfigParser is the Configurable equivalent of
|
5
|
+
# {OptionParser}[http://www.ruby-doc.org/core/classes/OptionParser.html]
|
6
|
+
# and uses a similar, simplified (see below) syntax to declare options.
|
7
|
+
#
|
8
|
+
# opts = {}
|
9
|
+
# parser = ConfigParser.new do |psr|
|
10
|
+
# psr.on "-s", "--long LONG", "a standard option" do |value|
|
11
|
+
# opts[:long] = value
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# psr.on "--[no-]switch", "a switch" do |value|
|
15
|
+
# opts[:switch] = value
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# psr.on "--flag", "a flag" do
|
19
|
+
# # note: no value is parsed; the block
|
20
|
+
# # only executes if the flag is found
|
21
|
+
# opts[:flag] = true
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# parser.parse("a b --long arg --switch --flag c") # => ['a', 'b', 'c']
|
26
|
+
# opts # => {:long => 'arg', :switch => true, :flag => true}
|
27
|
+
#
|
28
|
+
# ConfigParser formalizes this pattern of setting values in a hash as they
|
29
|
+
# occur, and adds the ability to specify default values. The syntax is
|
30
|
+
# not quite as friendly as for ordinary options, but meshes well with
|
31
|
+
# Configurable classes:
|
32
|
+
#
|
33
|
+
# psr = ConfigParser.new
|
34
|
+
# psr.define(:key, 'default', :desc => 'a standard option')
|
35
|
+
#
|
36
|
+
# psr.parse('a b --key option c') # => ['a', 'b', 'c']
|
37
|
+
# psr.config # => {:key => 'option'}
|
38
|
+
#
|
39
|
+
# psr.parse('a b c') # => ['a', 'b', 'c']
|
40
|
+
# psr.config # => {:key => 'default'}
|
41
|
+
#
|
42
|
+
# And now directly from a Configurable class, the equivalent of the
|
43
|
+
# original example:
|
44
|
+
#
|
45
|
+
# class ConfigClass
|
46
|
+
# include Configurable
|
47
|
+
#
|
48
|
+
# config :long, 'default', :short => 's' # a standard option
|
49
|
+
# config :switch, false, &c.switch # a switch
|
50
|
+
# config :flag, false, &c.flag # a flag
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# psr = ConfigParser.new
|
54
|
+
# psr.add(ConfigClass.configurations)
|
55
|
+
#
|
56
|
+
# psr.parse("a b --long arg --switch --flag c") # => ['a', 'b', 'c']
|
57
|
+
# psr.config # => {:long => 'arg', :switch => true, :flag => true}
|
58
|
+
#
|
59
|
+
# psr.parse("a b --long=arg --no-switch c") # => ['a', 'b', 'c']
|
60
|
+
# psr.config # => {:long => 'arg', :switch => false, :flag => false}
|
61
|
+
#
|
62
|
+
# psr.parse("a b -sarg c") # => ['a', 'b', 'c']
|
63
|
+
# psr.config # => {:long => 'arg', :switch => false, :flag => false}
|
64
|
+
#
|
65
|
+
# As you might expect, config attributes are used by ConfigParser to
|
66
|
+
# correctly build a corresponding option. In configurations like :switch,
|
67
|
+
# the block implies the {:type => :switch} attribute and so the
|
68
|
+
# config is made into a switch-style option by ConfigParser.
|
69
|
+
#
|
70
|
+
# Use the to_s method to convert a ConfigParser into command line
|
71
|
+
# documentation:
|
72
|
+
#
|
73
|
+
# "\nconfigurations:\n#{psr.to_s}"
|
74
|
+
# # => %q{
|
75
|
+
# # configurations:
|
76
|
+
# # -s, --long LONG a standard option
|
77
|
+
# # --[no-]switch a switch
|
78
|
+
# # --flag a flag
|
79
|
+
# # }
|
80
|
+
#
|
81
|
+
# ==== Simplifications
|
82
|
+
#
|
83
|
+
# ConfigParser simplifies the OptionParser syntax for 'on'. ConfigParser does
|
84
|
+
# not support automatic conversion of values, gets rid of 'optional' arguments
|
85
|
+
# for options, and only supports a single description string. Hence:
|
86
|
+
#
|
87
|
+
# psr = ConfigParser.new
|
88
|
+
#
|
89
|
+
# # incorrect, raises error as this will look
|
90
|
+
# # like multiple descriptions are specified
|
91
|
+
# psr.on("--delay N",
|
92
|
+
# Float,
|
93
|
+
# "Delay N seconds before executing") # !> ArgumentError
|
94
|
+
#
|
95
|
+
# # correct
|
96
|
+
# psr.on("--delay N", "Delay N seconds before executing") do |value|
|
97
|
+
# value.to_f
|
98
|
+
# end
|
99
|
+
#
|
100
|
+
# # this ALWAYS requires the argument and raises
|
101
|
+
# # an error because multiple descriptions are
|
102
|
+
# # specified
|
103
|
+
# psr.on("-i", "--inplace [EXTENSION]",
|
104
|
+
# "Edit ARGV files in place",
|
105
|
+
# " (make backup if EXTENSION supplied)") # !> ArgumentError
|
106
|
+
#
|
107
|
+
# # correct
|
108
|
+
# psr.on("-i", "--inplace EXTENSION",
|
109
|
+
# "Edit ARGV files in place\n (make backup if EXTENSION supplied)")
|
110
|
+
#
|
111
|
+
#
|
112
|
+
class ConfigParser
|
113
|
+
include Utils
|
114
|
+
|
115
|
+
# Returns an array of the options registered with self, in the order in
|
116
|
+
# which they were added. Separators are also stored in the registry.
|
117
|
+
attr_reader :registry
|
118
|
+
|
119
|
+
# A hash of (flag, Option) pairs mapping command line flags like '-s' or
|
120
|
+
# '--long' to the Option that handles them.
|
121
|
+
attr_reader :options
|
122
|
+
|
123
|
+
# The hash receiving configurations produced by parse.
|
124
|
+
attr_accessor :config
|
125
|
+
|
126
|
+
# The argument to stop processing options
|
127
|
+
attr_accessor :option_break
|
128
|
+
|
129
|
+
# Set to true to preserve the break argument
|
130
|
+
attr_accessor :preserve_option_break
|
131
|
+
|
132
|
+
# Initializes a new ConfigParser and passes it to the block, if given.
|
133
|
+
def initialize(config={}, opts={})
|
134
|
+
opts = {
|
135
|
+
:option_break => OPTION_BREAK,
|
136
|
+
:preserve_option_break => false
|
137
|
+
}.merge(opts)
|
138
|
+
|
139
|
+
@registry = []
|
140
|
+
@options = {}
|
141
|
+
@config = config
|
142
|
+
@option_break = opts[:option_break]
|
143
|
+
@preserve_option_break = opts[:preserve_option_break]
|
144
|
+
|
145
|
+
yield(self) if block_given?
|
146
|
+
end
|
147
|
+
|
148
|
+
# Returns the config value for key.
|
149
|
+
def [](key)
|
150
|
+
config[key]
|
151
|
+
end
|
152
|
+
|
153
|
+
# Sets the config value for key.
|
154
|
+
def []=(key, value)
|
155
|
+
config[key] = value
|
156
|
+
end
|
157
|
+
|
158
|
+
# Adds a separator string to self, used in to_s.
|
159
|
+
def separator(str)
|
160
|
+
@registry << str
|
161
|
+
end
|
162
|
+
|
163
|
+
# Registers the option with self by adding opt to options and mapping the
|
164
|
+
# opt flags. Raises an error for conflicting flags.
|
165
|
+
#
|
166
|
+
# If override is specified, options with conflicting flags are removed and
|
167
|
+
# no error is raised. Note that this may remove multiple options.
|
168
|
+
def register(option, override=false)
|
169
|
+
return if option.nil?
|
170
|
+
|
171
|
+
if override
|
172
|
+
existing = option.flags.collect {|flag| @options.delete(flag) }
|
173
|
+
@registry -= existing
|
174
|
+
end
|
175
|
+
|
176
|
+
unless @registry.include?(option)
|
177
|
+
@registry << option
|
178
|
+
end
|
179
|
+
|
180
|
+
option.flags.each do |flag|
|
181
|
+
current = @options[flag]
|
182
|
+
|
183
|
+
if current && current != option
|
184
|
+
raise ArgumentError, "already mapped to a different option: #{flag}"
|
185
|
+
end
|
186
|
+
|
187
|
+
@options[flag] = option
|
188
|
+
end
|
189
|
+
|
190
|
+
option
|
191
|
+
end
|
192
|
+
|
193
|
+
# Constructs an Option using args and registers it with self. Args may
|
194
|
+
# contain (in any order) a short switch, a long switch, and a description
|
195
|
+
# string. Either the short or long switch may signal that the option
|
196
|
+
# should take an argument by providing an argument name.
|
197
|
+
#
|
198
|
+
# psr = ConfigParser.new
|
199
|
+
#
|
200
|
+
# # this option takes an argument
|
201
|
+
# psr.on('-s', '--long ARG_NAME', 'description') do |value|
|
202
|
+
# # ...
|
203
|
+
# end
|
204
|
+
#
|
205
|
+
# # so does this one
|
206
|
+
# psr.on('-o ARG_NAME', 'description') do |value|
|
207
|
+
# # ...
|
208
|
+
# end
|
209
|
+
#
|
210
|
+
# # this option does not
|
211
|
+
# psr.on('-f', '--flag') do
|
212
|
+
# # ...
|
213
|
+
# end
|
214
|
+
#
|
215
|
+
# A switch-style option can be specified by prefixing the long switch with
|
216
|
+
# '--[no-]'. Switch options will pass true to the block for the positive
|
217
|
+
# form and false for the negative form.
|
218
|
+
#
|
219
|
+
# psr.on('--[no-]switch') do |value|
|
220
|
+
# # ...
|
221
|
+
# end
|
222
|
+
#
|
223
|
+
# Args may also contain a trailing hash defining all or part of the option:
|
224
|
+
#
|
225
|
+
# psr.on('-k', :long => '--key', :desc => 'description')
|
226
|
+
# # ...
|
227
|
+
# end
|
228
|
+
#
|
229
|
+
def on(*args, &block)
|
230
|
+
register new_option(args, &block)
|
231
|
+
end
|
232
|
+
|
233
|
+
# Same as on, but overrides options with overlapping switches.
|
234
|
+
def on!(*args, &block)
|
235
|
+
register new_option(args, &block), true
|
236
|
+
end
|
237
|
+
|
238
|
+
# Defines and registers a config-style option with self. Define does not
|
239
|
+
# take a block; the default value will be added to config, and any parsed
|
240
|
+
# value will override the default. Normally the key will be turned into
|
241
|
+
# the long switch; specify an alternate long, a short, description, etc
|
242
|
+
# using attributes.
|
243
|
+
#
|
244
|
+
# psr = ConfigParser.new
|
245
|
+
# psr.define(:one, 'default')
|
246
|
+
# psr.define(:two, 'default', :long => '--long', :short => '-s')
|
247
|
+
#
|
248
|
+
# psr.parse("--one one --long two")
|
249
|
+
# psr.config # => {:one => 'one', :two => 'two'}
|
250
|
+
#
|
251
|
+
# Define support several types of configurations that define a special
|
252
|
+
# block to handle the values parsed from the command line. See the
|
253
|
+
# 'setup_<type>' methods in Utils. Any type with a corresponding setup
|
254
|
+
# method is valid:
|
255
|
+
#
|
256
|
+
# psr = ConfigParser.new
|
257
|
+
# psr.define(:flag, false, :type => :flag)
|
258
|
+
# psr.define(:switch, false, :type => :switch)
|
259
|
+
# psr.define(:list, [], :type => :list)
|
260
|
+
#
|
261
|
+
# psr.parse("--flag --switch --list one --list two --list three")
|
262
|
+
# psr.config # => {:flag => true, :switch => true, :list => ['one', 'two', 'three']}
|
263
|
+
#
|
264
|
+
# New, valid types may be added by implementing new setup_<type> methods
|
265
|
+
# following this pattern:
|
266
|
+
#
|
267
|
+
# module SpecialType
|
268
|
+
# def setup_special(key, default_value, attributes)
|
269
|
+
# # modify attributes if necessary
|
270
|
+
# attributes[:long] = "--#{key}"
|
271
|
+
# attributes[:arg_name] = 'ARG_NAME'
|
272
|
+
#
|
273
|
+
# # return a block handling the input
|
274
|
+
# lambda {|input| config[key] = input.reverse }
|
275
|
+
# end
|
276
|
+
# end
|
277
|
+
#
|
278
|
+
# psr = ConfigParser.new.extend SpecialType
|
279
|
+
# psr.define(:opt, false, :type => :special)
|
280
|
+
#
|
281
|
+
# psr.parse("--opt value")
|
282
|
+
# psr.config # => {:opt => 'eulav'}
|
283
|
+
#
|
284
|
+
# The :hidden type causes no configuration to be defined. Raises an error if
|
285
|
+
# key is already set by a different option.
|
286
|
+
def add(key, default=nil, *args, &block)
|
287
|
+
attrs = args.last.kind_of?(Hash) ? args.pop : {}
|
288
|
+
attrs = attrs.merge(:key => key, :default => default)
|
289
|
+
args << attrs
|
290
|
+
|
291
|
+
on(*args, &block)
|
292
|
+
end
|
293
|
+
|
294
|
+
# Parses options from argv in a non-destructive manner and returns an
|
295
|
+
# array of arguments remaining after options have been removed. If a
|
296
|
+
# string argv is provided, it will be splits into an array using
|
297
|
+
# Shellwords.
|
298
|
+
#
|
299
|
+
# ==== Options
|
300
|
+
#
|
301
|
+
# clear_config:: clears the currently parsed configs (true)
|
302
|
+
# add_defaults:: adds the default values to config (true)
|
303
|
+
# ignore_unknown_options:: causes unknown options to be ignored (false)
|
304
|
+
#
|
305
|
+
def parse(argv=ARGV)
|
306
|
+
argv = argv.dup unless argv.kind_of?(String)
|
307
|
+
parse!(argv)
|
308
|
+
end
|
309
|
+
|
310
|
+
# Same as parse, but removes parsed args from argv.
|
311
|
+
def parse!(argv=ARGV)
|
312
|
+
argv = Shellwords.shellwords(argv) if argv.kind_of?(String)
|
313
|
+
|
314
|
+
args = []
|
315
|
+
remainder = scan(argv) {|arg| args << arg }
|
316
|
+
args.concat(remainder)
|
317
|
+
argv.replace(args)
|
318
|
+
|
319
|
+
argv
|
320
|
+
end
|
321
|
+
|
322
|
+
def scan(argv=ARGV)
|
323
|
+
while !argv.empty?
|
324
|
+
arg = argv.shift
|
325
|
+
|
326
|
+
# determine if the arg is an option
|
327
|
+
unless arg.kind_of?(String) && arg[0] == ?-
|
328
|
+
yield(arg)
|
329
|
+
next
|
330
|
+
end
|
331
|
+
|
332
|
+
# add the remaining args and break
|
333
|
+
# for the option break
|
334
|
+
if option_break === arg
|
335
|
+
argv.unshift(arg) if preserve_option_break
|
336
|
+
break
|
337
|
+
end
|
338
|
+
|
339
|
+
flag, value = arg, nil
|
340
|
+
|
341
|
+
# try the flag directly
|
342
|
+
unless option = @options[flag]
|
343
|
+
|
344
|
+
# then try --opt=value syntax
|
345
|
+
flag, value = flag.split('=', 2)
|
346
|
+
|
347
|
+
# then try -ovalue syntax
|
348
|
+
if value.nil? && flag[1] != ?-
|
349
|
+
flag, value = flag[0, 2], flag[2, flag.length - 2]
|
350
|
+
end
|
351
|
+
|
352
|
+
unless option = @options[flag]
|
353
|
+
raise "unknown option: #{flag}"
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
option.parse(flag, value, argv, config)
|
358
|
+
end
|
359
|
+
|
360
|
+
argv
|
361
|
+
end
|
362
|
+
|
363
|
+
# Converts the options and separators in self into a help string suitable for
|
364
|
+
# display on the command line.
|
365
|
+
def to_s
|
366
|
+
@registry.collect do |option|
|
367
|
+
option.to_s.rstrip
|
368
|
+
end.join("\n") + "\n"
|
369
|
+
end
|
370
|
+
|
371
|
+
protected
|
372
|
+
|
373
|
+
def option_class(attrs) # :nodoc:
|
374
|
+
type = attrs[:type] || guess_type(attrs)
|
375
|
+
|
376
|
+
case type
|
377
|
+
when :option then Option
|
378
|
+
when :flag then Flag
|
379
|
+
when :switch then Switch
|
380
|
+
when :list then List
|
381
|
+
when Class then type
|
382
|
+
else raise "unknown option type: #{type}"
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
# helper to parse an option from an argv. new_option is used
|
387
|
+
# by on and on! to generate options
|
388
|
+
def new_option(argv, &block) # :nodoc:
|
389
|
+
attrs = argv.last.kind_of?(Hash) ? argv.pop : {}
|
390
|
+
attrs = attrs.merge parse_attrs(argv)
|
391
|
+
option_class(attrs).new(attrs, &block)
|
392
|
+
end
|
393
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'config_parser/utils'
|
2
|
+
|
3
|
+
class ConfigParser
|
4
|
+
class Flag
|
5
|
+
include Utils
|
6
|
+
|
7
|
+
# The config key
|
8
|
+
attr_reader :key
|
9
|
+
|
10
|
+
# The default value
|
11
|
+
attr_reader :default
|
12
|
+
|
13
|
+
# The short flag mapping to self
|
14
|
+
attr_reader :short
|
15
|
+
|
16
|
+
# The long flag mapping to self
|
17
|
+
attr_reader :long
|
18
|
+
|
19
|
+
# The description printed by to_s
|
20
|
+
attr_reader :desc
|
21
|
+
|
22
|
+
# A callback for processing values
|
23
|
+
attr_reader :callback
|
24
|
+
|
25
|
+
def initialize(attrs={}, &callback)
|
26
|
+
@key = attrs[:key]
|
27
|
+
@default = attrs[:default]
|
28
|
+
@short = shortify(attrs[:short])
|
29
|
+
@long = longify(attrs.has_key?(:long) ? attrs[:long] : key)
|
30
|
+
@desc = attrs[:desc]
|
31
|
+
@callback = callback
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns an array of non-nil flags mapping to self (ie [long, short]).
|
35
|
+
def flags
|
36
|
+
[long, short].compact
|
37
|
+
end
|
38
|
+
|
39
|
+
# Assigns true into config and raises an error if a value is provided
|
40
|
+
# (flags take none). The callback will be called if specified to provide
|
41
|
+
# the assigned value.
|
42
|
+
#
|
43
|
+
# Note this is the entry point for handling different types of
|
44
|
+
# configuration flags.
|
45
|
+
#
|
46
|
+
#--
|
47
|
+
# Implementation Note
|
48
|
+
#
|
49
|
+
# The compact syntax for short flags is handled through parse by
|
50
|
+
# unshifting remaining shorts (ie value) back onto argv. This allows
|
51
|
+
# shorts that consume a value to take the remainder as needed. As an
|
52
|
+
# example to clarify, assume -x -y are flags where -x takes a value and -y
|
53
|
+
# does not. These are equivalent:
|
54
|
+
#
|
55
|
+
# -x value -y
|
56
|
+
# -xvalue -y
|
57
|
+
# -y -xvalue
|
58
|
+
# -yxvalue
|
59
|
+
#
|
60
|
+
# Whereas this is not:
|
61
|
+
#
|
62
|
+
# -xyvalue # x receives 'yvalue' not 'value'
|
63
|
+
#
|
64
|
+
# Parse handles the compact short syntax splitting '-yxvalue' into '-y',
|
65
|
+
# 'xvalue'. Then '-y' determines whether or not it needs a values; if not
|
66
|
+
# '-xvalue' gets unshifted to argv and parsing continues as if '-y
|
67
|
+
# -xvalue' were the original arguments.
|
68
|
+
def parse(flag, value, argv=[], config={})
|
69
|
+
unless value.nil?
|
70
|
+
if flag == short
|
71
|
+
argv.unshift "-#{value}"
|
72
|
+
else
|
73
|
+
raise "value specified for flag: #{flag}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
assign(config, callback ? callback.call : default.nil? ? false : default)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Assign the value to the config hash, if key is set. Returns value.
|
81
|
+
def assign(config, value)
|
82
|
+
config[key] = value if key
|
83
|
+
value
|
84
|
+
end
|
85
|
+
|
86
|
+
# Formats self as a help string for use on the command line.
|
87
|
+
def to_s
|
88
|
+
lines = wrap(desc.to_s, 43)
|
89
|
+
|
90
|
+
header = header_str
|
91
|
+
header = header.length > 36 ? header.ljust(80) : (LINE_FORMAT % [header, lines.shift])
|
92
|
+
|
93
|
+
if lines.empty?
|
94
|
+
header
|
95
|
+
else
|
96
|
+
lines.collect! {|line| LINE_FORMAT % [nil, line] }
|
97
|
+
"#{header}\n#{lines.join("\n")}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def header_str # :nodoc:
|
104
|
+
" #{short_str}#{long_str}"
|
105
|
+
end
|
106
|
+
|
107
|
+
# helper returning short formatted for to_s
|
108
|
+
def short_str # :nodoc:
|
109
|
+
case
|
110
|
+
when short && long then "#{short}, "
|
111
|
+
when short then "#{short}"
|
112
|
+
else ' '
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# helper returning long formatted for to_s
|
117
|
+
def long_str # :nodoc:
|
118
|
+
long
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'config_parser/option'
|
2
|
+
|
3
|
+
class ConfigParser
|
4
|
+
class List < Option
|
5
|
+
|
6
|
+
# The default split character for multiple values
|
7
|
+
DEFAULT_SPLIT = ','
|
8
|
+
|
9
|
+
# The maximum number of values that may be specified.
|
10
|
+
attr_reader :limit
|
11
|
+
|
12
|
+
# The sequence on which to split single values into multiple values. Set
|
13
|
+
# to nil to prevent split.
|
14
|
+
attr_reader :split
|
15
|
+
|
16
|
+
def initialize(attrs={})
|
17
|
+
super
|
18
|
+
@limit = attrs[:n]
|
19
|
+
@split = attrs.has_key?(:split) ? attrs[:split] : DEFAULT_SPLIT
|
20
|
+
end
|
21
|
+
|
22
|
+
# List assigns configs by pushing the value onto an array, rather than
|
23
|
+
# directly setting it onto config. As usual, no value is assigned if key
|
24
|
+
# is not set. Returns value (the input, not the array).
|
25
|
+
def assign(config, value)
|
26
|
+
if key
|
27
|
+
array = (config[key] ||= [])
|
28
|
+
array.concat(split ? value.split(split) : [value])
|
29
|
+
|
30
|
+
if limit && array.length > limit
|
31
|
+
raise "too many assignments: #{key.inspect}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
value
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'config_parser/switch'
|
2
|
+
|
3
|
+
class ConfigParser
|
4
|
+
|
5
|
+
# Represents an option registered with ConfigParser.
|
6
|
+
class Option < Flag
|
7
|
+
DEFAULT_ARGNAME = 'VALUE'
|
8
|
+
|
9
|
+
# The argument name printed by to_s.
|
10
|
+
attr_reader :arg_name
|
11
|
+
|
12
|
+
def initialize(attrs={})
|
13
|
+
super
|
14
|
+
@arg_name = attrs[:arg_name] || (key ? key.to_s.upcase : DEFAULT_ARGNAME)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Parse the flag and value. If no value is provided and a value is
|
18
|
+
# required, then a value is shifted off of argv. Calls the callback
|
19
|
+
# with the value, if specified, and assigns the result.
|
20
|
+
def parse(flag, value, argv=[], config={})
|
21
|
+
if value.nil?
|
22
|
+
unless value = next_arg(argv)
|
23
|
+
raise "no value provided for: #{flag}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
assign(config, callback ? callback.call(value) : value)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def next_arg(argv) # :nodoc:
|
32
|
+
arg = argv[0]
|
33
|
+
(arg.kind_of?(String) && arg[0] == ?-) ? default : argv.shift
|
34
|
+
end
|
35
|
+
|
36
|
+
def header_str # :nodoc:
|
37
|
+
" #{short_str}#{long_str} #{arg_name}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'config_parser/flag'
|
2
|
+
|
3
|
+
class ConfigParser
|
4
|
+
|
5
|
+
# Switch represents a special type of Option where both positive (--flag)
|
6
|
+
# and negative (--no-flag) flags map to self.
|
7
|
+
class Switch < Flag
|
8
|
+
|
9
|
+
# The negative long flag, determined from long if not set otherwise.
|
10
|
+
attr_reader :negative_long
|
11
|
+
|
12
|
+
def initialize(attrs={})
|
13
|
+
attrs[:default] = true unless attrs.has_key?(:default)
|
14
|
+
super
|
15
|
+
|
16
|
+
raise ArgumentError, "no long specified" unless long
|
17
|
+
@negative_long = attrs[:negative_long] || prefix_long(long, 'no-')
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns an array of non-nil flags mapping to self (ie [long,
|
21
|
+
# negative_long, short]).
|
22
|
+
def flags
|
23
|
+
[long, negative_long, short].compact
|
24
|
+
end
|
25
|
+
|
26
|
+
# Assigns true into config for positive flags and false for negative
|
27
|
+
# flags. If specified, the callback is called with the boolean to
|
28
|
+
# determine the assigned value. Raises an error if a value is provided
|
29
|
+
# (switches take none).
|
30
|
+
def parse(flag, value, argv=[], config={})
|
31
|
+
raise "value specified for switch: #{flag}" if value
|
32
|
+
value = (flag == negative_long ? !default : default)
|
33
|
+
assign(config, callback ? callback.call(value) : value)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# helper returning long formatted for to_s
|
39
|
+
def long_str # :nodoc:
|
40
|
+
long ? prefix_long(long, '[no-]') : ''
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
class ConfigParser
|
2
|
+
|
3
|
+
# A medly of methods used throughout the ConfigParser classes.
|
4
|
+
module Utils
|
5
|
+
module_function
|
6
|
+
|
7
|
+
# A format string used by to_s
|
8
|
+
LINE_FORMAT = "%-36s %-43s"
|
9
|
+
|
10
|
+
# The default option break
|
11
|
+
OPTION_BREAK = "--"
|
12
|
+
|
13
|
+
# Matches a long flag
|
14
|
+
LONG_FLAG = /\A--.+\z/
|
15
|
+
|
16
|
+
# Matches a short flag
|
17
|
+
SHORT_FLAG = /\A-.\z/
|
18
|
+
|
19
|
+
# Matches a switch declaration (ex: '--[no-]opt', '--nest:[no-]opt').
|
20
|
+
# After the match:
|
21
|
+
#
|
22
|
+
# $1:: the nesting prefix ('nest')
|
23
|
+
# $2:: the long flag name ('opt')
|
24
|
+
#
|
25
|
+
SWITCH = /\A--(.*?)\[no-\](.+)\z/
|
26
|
+
|
27
|
+
# Turns the input into a short flag by prefixing '-' (as needed). Raises
|
28
|
+
# an error if the input doesn't result in a short flag. Nils are
|
29
|
+
# returned directly.
|
30
|
+
#
|
31
|
+
# shortify('-o') # => '-o'
|
32
|
+
# shortify(:o) # => '-o'
|
33
|
+
#
|
34
|
+
def shortify(str)
|
35
|
+
return nil if str.nil?
|
36
|
+
|
37
|
+
str = str.to_s
|
38
|
+
str = "-#{str}" unless str[0] == ?-
|
39
|
+
|
40
|
+
unless str =~ SHORT_FLAG
|
41
|
+
raise ArgumentError, "invalid short flag: #{str}"
|
42
|
+
end
|
43
|
+
|
44
|
+
str
|
45
|
+
end
|
46
|
+
|
47
|
+
# Turns the input into a long flag by prefixing '--' (as needed). Raises
|
48
|
+
# an error if the input doesn't result in a long flag. Nils are
|
49
|
+
# returned directly.
|
50
|
+
#
|
51
|
+
# longify('--opt') # => '--opt'
|
52
|
+
# longify(:opt) # => '--opt'
|
53
|
+
#
|
54
|
+
def longify(str)
|
55
|
+
return nil if str.nil?
|
56
|
+
|
57
|
+
str = str.to_s
|
58
|
+
str = "--#{str}" unless str[0] == ?-
|
59
|
+
|
60
|
+
unless str =~ LONG_FLAG
|
61
|
+
raise ArgumentError, "invalid long flag: #{str}"
|
62
|
+
end
|
63
|
+
|
64
|
+
str
|
65
|
+
end
|
66
|
+
|
67
|
+
# Adds a prefix onto the last nested segment of a long option.
|
68
|
+
#
|
69
|
+
# prefix_long('--opt', 'no-') # => '--no-opt'
|
70
|
+
# prefix_long('--nested:opt', 'no-') # => '--nested:no-opt'
|
71
|
+
#
|
72
|
+
def prefix_long(switch, prefix, split_char=':')
|
73
|
+
switch = switch[2, switch.length-2] if switch =~ /^--/
|
74
|
+
switch = switch.split(split_char)
|
75
|
+
switch[-1] = "#{prefix}#{switch[-1]}"
|
76
|
+
"--#{switch.join(':')}"
|
77
|
+
end
|
78
|
+
|
79
|
+
# A wrapping algorithm slightly modified from:
|
80
|
+
# http://blog.macromates.com/2006/wrapping-text-with-regular-expressions/
|
81
|
+
def wrap(line, cols=80, tabsize=2)
|
82
|
+
line = line.gsub(/\t/, " " * tabsize) unless tabsize == nil
|
83
|
+
line.gsub(/(.{1,#{cols}})( +|$\r?\n?)|(.{1,#{cols}})/, "\\1\\3\n").split(/\s*?\n/)
|
84
|
+
end
|
85
|
+
|
86
|
+
def parse_attrs(argv)
|
87
|
+
attrs={}
|
88
|
+
|
89
|
+
argv.each do |arg|
|
90
|
+
if arg[0] != ?-
|
91
|
+
attrs[:desc] = arg
|
92
|
+
next
|
93
|
+
end
|
94
|
+
|
95
|
+
flag, arg_name = arg.split(/\s+/, 2)
|
96
|
+
|
97
|
+
if arg_name
|
98
|
+
attrs[:arg_name] = arg_name
|
99
|
+
end
|
100
|
+
|
101
|
+
case flag
|
102
|
+
when SWITCH
|
103
|
+
attrs[:long] = "--#{$1}#{$2}"
|
104
|
+
attrs[:negative_long] = "--#{$1}no-#{$2}"
|
105
|
+
|
106
|
+
if arg_name
|
107
|
+
raise ArgumentError, "arg_name specified for switch: #{arg_name}"
|
108
|
+
end
|
109
|
+
|
110
|
+
when LONG_FLAG
|
111
|
+
attrs[:long] = flag
|
112
|
+
|
113
|
+
when SHORT_FLAG
|
114
|
+
attrs[:short] = flag
|
115
|
+
|
116
|
+
else
|
117
|
+
raise ArgumentError.new("invalid flag: #{arg.inspect}")
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
attrs
|
122
|
+
end
|
123
|
+
|
124
|
+
def guess_type(attrs) # :nodoc:
|
125
|
+
case
|
126
|
+
when attrs[:negative_long]
|
127
|
+
:switch
|
128
|
+
when attrs[:arg_name] || attrs[:default]
|
129
|
+
:option
|
130
|
+
else
|
131
|
+
:flag
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
metadata
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: config_parser
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Simon Chiang
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-07-25 00:00:00 -06:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: lazydoc
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ~>
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 1
|
29
|
+
- 0
|
30
|
+
version: "1.0"
|
31
|
+
type: :runtime
|
32
|
+
version_requirements: *id001
|
33
|
+
description:
|
34
|
+
email: simon.a.chiang@gmail.com
|
35
|
+
executables: []
|
36
|
+
|
37
|
+
extensions: []
|
38
|
+
|
39
|
+
extra_rdoc_files:
|
40
|
+
- History
|
41
|
+
- README
|
42
|
+
- MIT-LICENSE
|
43
|
+
files:
|
44
|
+
- lib/config_parser.rb
|
45
|
+
- lib/config_parser/flag.rb
|
46
|
+
- lib/config_parser/list.rb
|
47
|
+
- lib/config_parser/option.rb
|
48
|
+
- lib/config_parser/switch.rb
|
49
|
+
- lib/config_parser/utils.rb
|
50
|
+
- lib/config_parser/version.rb
|
51
|
+
- History
|
52
|
+
- README
|
53
|
+
- MIT-LICENSE
|
54
|
+
has_rdoc: true
|
55
|
+
homepage: ""
|
56
|
+
licenses: []
|
57
|
+
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options:
|
60
|
+
- --main
|
61
|
+
- README
|
62
|
+
- -S
|
63
|
+
- -N
|
64
|
+
- --title
|
65
|
+
- ConfigParser
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
segments:
|
73
|
+
- 0
|
74
|
+
version: "0"
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
segments:
|
80
|
+
- 0
|
81
|
+
version: "0"
|
82
|
+
requirements: []
|
83
|
+
|
84
|
+
rubyforge_project: ""
|
85
|
+
rubygems_version: 1.3.6
|
86
|
+
signing_key:
|
87
|
+
specification_version: 3
|
88
|
+
summary: Parse command-line options into a configuration hash
|
89
|
+
test_files: []
|
90
|
+
|