config_parser 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|