config_parser 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History +4 -0
- data/README +32 -21
- data/lib/config_parser.rb +99 -202
- data/lib/config_parser/flag.rb +52 -22
- data/lib/config_parser/list.rb +38 -15
- data/lib/config_parser/option.rb +20 -11
- data/lib/config_parser/switch.rb +18 -16
- data/lib/config_parser/utils.rb +94 -15
- data/lib/config_parser/version.rb +1 -1
- metadata +3 -3
data/History
CHANGED
data/README
CHANGED
@@ -7,14 +7,14 @@ Parse command-line options into a configuration hash.
|
|
7
7
|
ConfigParser is an analogue of
|
8
8
|
{OptionParser}[http://www.ruby-doc.org/core/classes/OptionParser.html] that
|
9
9
|
formalizes the pattern of setting parsed options into a hash. ConfigParser
|
10
|
-
uses a similar, simplified declaration syntax
|
11
|
-
|
12
|
-
|
10
|
+
uses a similar, simplified declaration syntax and provides an API that
|
11
|
+
integrates well with libraries like
|
12
|
+
{Configurable}[http://tap.rubyforge.org/configurable].
|
13
13
|
|
14
14
|
== Usage
|
15
15
|
|
16
|
-
ConfigParser can be used much like OptionParser, where the parser
|
17
|
-
|
16
|
+
ConfigParser can be used much like OptionParser, where the parser can set
|
17
|
+
values into a config hash.
|
18
18
|
|
19
19
|
parser = ConfigParser.new
|
20
20
|
parser.on '-s', '--long LONG', 'a standard option' do |value|
|
@@ -46,31 +46,42 @@ ConfigParser formalizes this pattern of setting values into a config hash as
|
|
46
46
|
they occur, and adds the ability to specify default values.
|
47
47
|
|
48
48
|
parser = ConfigParser.new
|
49
|
-
parser.add
|
49
|
+
parser.add :flag, false # false as a default makes a --flag
|
50
|
+
parser.add :switch, true # true makes a --[no-]switch
|
51
|
+
parser.add :list, [] # an array makes a list-style option
|
52
|
+
parser.add :opt, 'default' # all others make an ordinary option
|
50
53
|
|
51
|
-
parser.parse('a b
|
52
|
-
parser.config
|
53
|
-
|
54
|
-
|
55
|
-
|
54
|
+
parser.parse('a b c') # => ['a', 'b', 'c']
|
55
|
+
parser.config
|
56
|
+
# => {
|
57
|
+
# :flag => false,
|
58
|
+
# :switch => true,
|
59
|
+
# :list => [],
|
60
|
+
# :opt => 'default'
|
61
|
+
# }
|
62
|
+
|
63
|
+
args = %w{a b --flag --no-switch --list one --list two,three --opt value c}
|
64
|
+
parser.parse(args) # => ['a', 'b', 'c']
|
65
|
+
parser.config
|
66
|
+
# => {
|
67
|
+
# :flag => true,
|
68
|
+
# :switch => false,
|
69
|
+
# :list => ['one', 'two', 'three'],
|
70
|
+
# :opt => 'value'
|
71
|
+
# }
|
56
72
|
|
57
|
-
|
58
|
-
|
59
|
-
are set as configs.
|
73
|
+
Options can be defined using arguments just like on, or with an attributes
|
74
|
+
hash. A block can be given to process values before they are set as configs.
|
60
75
|
|
61
76
|
parser = ConfigParser.new
|
62
|
-
parser.add(:x, nil, '
|
77
|
+
parser.add(:x, nil, '--one', 'by args') {|value| value.upcase }
|
63
78
|
parser.add(:y, nil, :long => 'two', :desc => 'by hash')
|
64
79
|
|
65
|
-
parser.parse('a b --one value --two c')
|
80
|
+
parser.parse('a b --one value --two value c')
|
66
81
|
# => ['a', 'b', 'c']
|
67
82
|
|
68
83
|
parser.config
|
69
|
-
# => {:x => 'VALUE', :y =>
|
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.
|
84
|
+
# => {:x => 'VALUE', :y => 'value'}
|
74
85
|
|
75
86
|
== Installation
|
76
87
|
|
data/lib/config_parser.rb
CHANGED
@@ -1,114 +1,11 @@
|
|
1
1
|
require 'config_parser/list'
|
2
2
|
autoload(:Shellwords, 'shellwords')
|
3
3
|
|
4
|
-
# ConfigParser is the
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
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
|
-
#
|
4
|
+
# ConfigParser is an option parser that formalizes the pattern of setting
|
5
|
+
# parsed options into a hash. ConfigParser provides a similar declaration
|
6
|
+
# syntax as
|
7
|
+
# {OptionParser}[http://www.ruby-doc.org/core/classes/OptionParser.html] but
|
8
|
+
# additionally supports option declaration using an attributes hash.
|
112
9
|
class ConfigParser
|
113
10
|
include Utils
|
114
11
|
|
@@ -120,20 +17,24 @@ class ConfigParser
|
|
120
17
|
# '--long' to the Option that handles them.
|
121
18
|
attr_reader :options
|
122
19
|
|
123
|
-
# The hash receiving
|
20
|
+
# The hash receiving configs.
|
124
21
|
attr_accessor :config
|
125
22
|
|
126
23
|
# The argument to stop processing options
|
127
24
|
attr_accessor :option_break
|
128
25
|
|
129
|
-
# Set to true to preserve the break
|
26
|
+
# Set to true to preserve the option break
|
130
27
|
attr_accessor :preserve_option_break
|
131
28
|
|
29
|
+
# Set to true to assign config defaults on parse
|
30
|
+
attr_accessor :assign_defaults
|
31
|
+
|
132
32
|
# Initializes a new ConfigParser and passes it to the block, if given.
|
133
33
|
def initialize(config={}, opts={})
|
134
34
|
opts = {
|
135
35
|
:option_break => OPTION_BREAK,
|
136
|
-
:preserve_option_break => false
|
36
|
+
:preserve_option_break => false,
|
37
|
+
:assign_defaults => true
|
137
38
|
}.merge(opts)
|
138
39
|
|
139
40
|
@registry = []
|
@@ -141,6 +42,7 @@ class ConfigParser
|
|
141
42
|
@config = config
|
142
43
|
@option_break = opts[:option_break]
|
143
44
|
@preserve_option_break = opts[:preserve_option_break]
|
45
|
+
@assign_defaults = opts[:assign_defaults]
|
144
46
|
|
145
47
|
yield(self) if block_given?
|
146
48
|
end
|
@@ -160,8 +62,9 @@ class ConfigParser
|
|
160
62
|
@registry << str
|
161
63
|
end
|
162
64
|
|
163
|
-
# Registers the option with self by adding
|
164
|
-
#
|
65
|
+
# Registers the option with self by adding it to the registry and mapping
|
66
|
+
# the option flags into options. Raises an error for conflicting flags.
|
67
|
+
# Returns self.
|
165
68
|
#
|
166
69
|
# If override is specified, options with conflicting flags are removed and
|
167
70
|
# no error is raised. Note that this may remove multiple options.
|
@@ -187,102 +90,86 @@ class ConfigParser
|
|
187
90
|
@options[flag] = option
|
188
91
|
end
|
189
92
|
|
190
|
-
|
93
|
+
self
|
94
|
+
end
|
95
|
+
|
96
|
+
# Unregisters the option by removing it from the registry and options.
|
97
|
+
# Returns self.
|
98
|
+
def unregister(option)
|
99
|
+
@registry.delete(option)
|
100
|
+
@options.delete_if {|key, value| option == value }
|
101
|
+
self
|
191
102
|
end
|
192
103
|
|
193
|
-
# Constructs an Option using args and registers it with self.
|
104
|
+
# Constructs an Option using args and registers it with self. The args may
|
194
105
|
# contain (in any order) a short switch, a long switch, and a description
|
195
|
-
# string.
|
196
|
-
#
|
106
|
+
# string. A block may be provided to process values for the option.
|
107
|
+
#
|
108
|
+
# The option type (flag, switch, list, or option) is guessed from the args,
|
109
|
+
# and affects what is passed to the block.
|
197
110
|
#
|
198
111
|
# psr = ConfigParser.new
|
199
112
|
#
|
200
|
-
# #
|
201
|
-
#
|
113
|
+
# # options take an argument on the long
|
114
|
+
# # and receive the arg in the block
|
115
|
+
# psr.on('-s', '--long ARG_NAME', 'description') do |arg|
|
202
116
|
# # ...
|
203
117
|
# end
|
204
118
|
#
|
205
|
-
# #
|
206
|
-
# psr.on('-o ARG_NAME'
|
119
|
+
# # the argname can be specified on a short
|
120
|
+
# psr.on('-o ARG_NAME') do |arg|
|
207
121
|
# # ...
|
208
122
|
# end
|
209
|
-
#
|
210
|
-
# #
|
211
|
-
#
|
123
|
+
#
|
124
|
+
# # use an argname with commas to make a list,
|
125
|
+
# # an array of values is passed to the block
|
126
|
+
# psr.on('--list A,B,C') do |args|
|
212
127
|
# # ...
|
213
128
|
# end
|
214
129
|
#
|
215
|
-
#
|
216
|
-
#
|
217
|
-
#
|
130
|
+
# # flags specify no argument, and the
|
131
|
+
# # block takes no argument
|
132
|
+
# psr.on('-f', '--flag') do
|
133
|
+
# # ...
|
134
|
+
# end
|
218
135
|
#
|
219
|
-
#
|
136
|
+
# # switches look like this; they get true
|
137
|
+
# # or false in the block
|
138
|
+
# psr.on('--[no-]switch') do |bool|
|
220
139
|
# # ...
|
221
140
|
# end
|
222
141
|
#
|
223
|
-
#
|
142
|
+
# If this is too ambiguous (and at times it is), provide a trailing hash
|
143
|
+
# defining all or part of the option:
|
224
144
|
#
|
225
|
-
# psr.on('-k', :long => '--key', :
|
145
|
+
# psr.on('-k', 'description', :long => '--key', :type => :list) do |args|
|
226
146
|
# # ...
|
227
147
|
# end
|
228
|
-
#
|
148
|
+
#
|
149
|
+
# The trailing hash wins if there is any overlap in the parsed attributes
|
150
|
+
# and those provided by the hash.
|
229
151
|
def on(*args, &block)
|
230
|
-
|
152
|
+
option = new_option(args, &block)
|
153
|
+
register option
|
154
|
+
option
|
231
155
|
end
|
232
156
|
|
233
|
-
# Same as on, but overrides options with overlapping
|
157
|
+
# Same as on, but overrides options with overlapping flags.
|
234
158
|
def on!(*args, &block)
|
235
|
-
|
159
|
+
option = new_option(args, &block)
|
160
|
+
register option, true
|
161
|
+
option
|
236
162
|
end
|
237
163
|
|
238
|
-
#
|
239
|
-
#
|
240
|
-
#
|
241
|
-
# the long switch; specify an alternate long, a short, description, etc
|
242
|
-
# using attributes.
|
164
|
+
# An alternate syntax for on, where the key and default attributes are set
|
165
|
+
# by the first two args. Like on, add can define option attributes using a
|
166
|
+
# series of args or with a trailing hash.
|
243
167
|
#
|
244
|
-
#
|
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'}
|
168
|
+
# These are equivalent:
|
250
169
|
#
|
251
|
-
#
|
252
|
-
#
|
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
|
170
|
+
# add(:opt, 'value', '-s', '--long', :desc => 'description')
|
171
|
+
# on('-s', '--long', :desc => 'description', :key => :opt, :default => 'value')
|
277
172
|
#
|
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
173
|
def add(key, default=nil, *args, &block)
|
287
174
|
attrs = args.last.kind_of?(Hash) ? args.pop : {}
|
288
175
|
attrs = attrs.merge(:key => key, :default => default)
|
@@ -291,17 +178,25 @@ class ConfigParser
|
|
291
178
|
on(*args, &block)
|
292
179
|
end
|
293
180
|
|
294
|
-
#
|
295
|
-
#
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
181
|
+
# Removes options by key. Any options with the specified key are removed.
|
182
|
+
# Returns self.
|
183
|
+
def rm(key)
|
184
|
+
options.values.each do |option|
|
185
|
+
if option.key == key
|
186
|
+
unregister(option)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
self
|
191
|
+
end
|
192
|
+
|
193
|
+
# Parses options from argv in a non-destructive manner. Parsing stops if an
|
194
|
+
# argument matching option_break is reached. If preserve_option_break is
|
195
|
+
# specified then the option break is preserved in the remaining arguments.
|
196
|
+
# Returns an array of arguments remaining after options have been removed.
|
304
197
|
#
|
198
|
+
# If a string argv is provided, it will be splits into an array using
|
199
|
+
# Shellwords.
|
305
200
|
def parse(argv=ARGV)
|
306
201
|
argv = argv.dup unless argv.kind_of?(String)
|
307
202
|
parse!(argv)
|
@@ -311,21 +206,20 @@ class ConfigParser
|
|
311
206
|
def parse!(argv=ARGV)
|
312
207
|
argv = Shellwords.shellwords(argv) if argv.kind_of?(String)
|
313
208
|
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
209
|
+
if assign_defaults
|
210
|
+
registry.each do |option|
|
211
|
+
next unless option.respond_to?(:assign)
|
212
|
+
option.assign(config)
|
213
|
+
end
|
214
|
+
end
|
318
215
|
|
319
|
-
|
320
|
-
end
|
321
|
-
|
322
|
-
def scan(argv=ARGV)
|
216
|
+
args = []
|
323
217
|
while !argv.empty?
|
324
218
|
arg = argv.shift
|
325
219
|
|
326
220
|
# determine if the arg is an option
|
327
|
-
unless
|
328
|
-
|
221
|
+
unless option?(arg)
|
222
|
+
args << arg
|
329
223
|
next
|
330
224
|
end
|
331
225
|
|
@@ -357,11 +251,14 @@ class ConfigParser
|
|
357
251
|
option.parse(flag, value, argv, config)
|
358
252
|
end
|
359
253
|
|
254
|
+
args.concat(argv)
|
255
|
+
argv.replace(args)
|
256
|
+
|
360
257
|
argv
|
361
258
|
end
|
362
259
|
|
363
|
-
# Converts the options and separators in self into a help string suitable
|
364
|
-
# display on the command line.
|
260
|
+
# Converts the options and separators in self into a help string suitable
|
261
|
+
# for display on the command line.
|
365
262
|
def to_s
|
366
263
|
@registry.collect do |option|
|
367
264
|
option.to_s.rstrip
|
@@ -387,7 +284,7 @@ class ConfigParser
|
|
387
284
|
# by on and on! to generate options
|
388
285
|
def new_option(argv, &block) # :nodoc:
|
389
286
|
attrs = argv.last.kind_of?(Hash) ? argv.pop : {}
|
390
|
-
attrs =
|
287
|
+
attrs = parse_attrs(argv).merge(attrs)
|
391
288
|
option_class(attrs).new(attrs, &block)
|
392
289
|
end
|
393
290
|
end
|
data/lib/config_parser/flag.rb
CHANGED
@@ -1,12 +1,19 @@
|
|
1
1
|
require 'config_parser/utils'
|
2
2
|
|
3
3
|
class ConfigParser
|
4
|
+
|
5
|
+
# Represents a boolean flag-style option. Flag handles the parsing of
|
6
|
+
# specific flags, and provides hooks for processing the various types of
|
7
|
+
# options (Switch, Option, List).
|
4
8
|
class Flag
|
5
9
|
include Utils
|
6
10
|
|
7
11
|
# The config key
|
8
12
|
attr_reader :key
|
9
13
|
|
14
|
+
# The config nesting keys
|
15
|
+
attr_reader :nest_keys
|
16
|
+
|
10
17
|
# The default value
|
11
18
|
attr_reader :default
|
12
19
|
|
@@ -23,25 +30,30 @@ class ConfigParser
|
|
23
30
|
attr_reader :callback
|
24
31
|
|
25
32
|
def initialize(attrs={}, &callback)
|
26
|
-
@key
|
27
|
-
@
|
28
|
-
@
|
29
|
-
@
|
30
|
-
@
|
31
|
-
@
|
33
|
+
@key = attrs[:key]
|
34
|
+
@nest_keys = attrs[:nest_keys]
|
35
|
+
@default = attrs[:default]
|
36
|
+
@short = shortify(attrs[:short])
|
37
|
+
@long = longify(attrs.has_key?(:long) ? attrs[:long] : key)
|
38
|
+
@desc = attrs[:desc]
|
39
|
+
@callback = callback
|
32
40
|
end
|
33
41
|
|
34
|
-
# Returns an array of
|
42
|
+
# Returns an array of flags mapping to self (ie [long, short]).
|
35
43
|
def flags
|
36
44
|
[long, short].compact
|
37
45
|
end
|
38
46
|
|
39
|
-
#
|
40
|
-
#
|
41
|
-
# the
|
47
|
+
# Parse handles the parsing of flags, which happens in three steps:
|
48
|
+
#
|
49
|
+
# * determine the value (occurs in parse)
|
50
|
+
# * process the value
|
51
|
+
# * assign the result into config
|
42
52
|
#
|
43
|
-
#
|
44
|
-
#
|
53
|
+
# Flag uses !default as the value (such that the flag indicates true if
|
54
|
+
# the default is false) then passes the value to process, and then assign.
|
55
|
+
# Raises and error if provided a value directly (flags always determine
|
56
|
+
# their value based on the default).
|
45
57
|
#
|
46
58
|
#--
|
47
59
|
# Implementation Note
|
@@ -56,7 +68,7 @@ class ConfigParser
|
|
56
68
|
# -xvalue -y
|
57
69
|
# -y -xvalue
|
58
70
|
# -yxvalue
|
59
|
-
#
|
71
|
+
#
|
60
72
|
# Whereas this is not:
|
61
73
|
#
|
62
74
|
# -xyvalue # x receives 'yvalue' not 'value'
|
@@ -65,22 +77,42 @@ class ConfigParser
|
|
65
77
|
# 'xvalue'. Then '-y' determines whether or not it needs a values; if not
|
66
78
|
# '-xvalue' gets unshifted to argv and parsing continues as if '-y
|
67
79
|
# -xvalue' were the original arguments.
|
68
|
-
def parse(flag, value, argv=[], config={})
|
80
|
+
def parse(flag, value=nil, argv=[], config={})
|
69
81
|
unless value.nil?
|
70
82
|
if flag == short
|
71
83
|
argv.unshift "-#{value}"
|
72
84
|
else
|
73
|
-
raise "value specified for flag: #{
|
85
|
+
raise "value specified for #{flag}: #{value.inspect}"
|
74
86
|
end
|
75
87
|
end
|
76
88
|
|
77
|
-
|
89
|
+
value = (default.nil? ? true : !default)
|
90
|
+
assign(config, process(value))
|
78
91
|
end
|
79
92
|
|
80
|
-
#
|
81
|
-
|
82
|
-
|
83
|
-
value
|
93
|
+
# Process the value by calling the callback, if specified, with the value
|
94
|
+
# and returns the result. Returns value if no callback is specified.
|
95
|
+
def process(value)
|
96
|
+
callback ? callback.call(value) : value
|
97
|
+
end
|
98
|
+
|
99
|
+
# Assign the value to the config hash, if key is set. Returns config.
|
100
|
+
def assign(config, value=default)
|
101
|
+
if key
|
102
|
+
nest_config = nest(config)
|
103
|
+
nest_config[key] = value
|
104
|
+
end
|
105
|
+
|
106
|
+
config
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns the nested config hash for config, as specified by nest_keys.
|
110
|
+
def nest(config)
|
111
|
+
nest_keys.each do |key|
|
112
|
+
config = (config[key] ||= {})
|
113
|
+
end if nest_keys
|
114
|
+
|
115
|
+
config
|
84
116
|
end
|
85
117
|
|
86
118
|
# Formats self as a help string for use on the command line.
|
@@ -104,7 +136,6 @@ class ConfigParser
|
|
104
136
|
" #{short_str}#{long_str}"
|
105
137
|
end
|
106
138
|
|
107
|
-
# helper returning short formatted for to_s
|
108
139
|
def short_str # :nodoc:
|
109
140
|
case
|
110
141
|
when short && long then "#{short}, "
|
@@ -113,7 +144,6 @@ class ConfigParser
|
|
113
144
|
end
|
114
145
|
end
|
115
146
|
|
116
|
-
# helper returning long formatted for to_s
|
117
147
|
def long_str # :nodoc:
|
118
148
|
long
|
119
149
|
end
|
data/lib/config_parser/list.rb
CHANGED
@@ -1,38 +1,61 @@
|
|
1
1
|
require 'config_parser/option'
|
2
2
|
|
3
3
|
class ConfigParser
|
4
|
+
|
5
|
+
# List represents a special type of Option where multiple values may be
|
6
|
+
# assigned to the same key.
|
4
7
|
class List < Option
|
5
8
|
|
6
9
|
# The default split character for multiple values
|
7
|
-
|
10
|
+
DELIMITER = ','
|
8
11
|
|
9
|
-
# The maximum number of values that may be specified.
|
12
|
+
# The maximum number of values that may be specified; nil for unlimited.
|
10
13
|
attr_reader :limit
|
11
14
|
|
12
|
-
# The
|
13
|
-
#
|
14
|
-
attr_reader :
|
15
|
+
# The delimiter on which to split single values into multiple values; use
|
16
|
+
# nil to prevent splitting.
|
17
|
+
attr_reader :delimiter
|
15
18
|
|
16
19
|
def initialize(attrs={})
|
17
20
|
super
|
18
|
-
|
19
|
-
@
|
21
|
+
|
22
|
+
@delimiter = attrs.has_key?(:delimiter) ? attrs[:delimiter] : DELIMITER
|
23
|
+
@limit = attrs[:limit]
|
24
|
+
@default = split(@default)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Splits the value into multiple values, and then process as usual.
|
28
|
+
def process(value)
|
29
|
+
super split(value)
|
20
30
|
end
|
21
31
|
|
22
|
-
#
|
23
|
-
# directly setting
|
24
|
-
#
|
25
|
-
def assign(config,
|
32
|
+
# Assigns the values to config by concatenating onto an array, rather than
|
33
|
+
# directly setting into config. As usual, no value is assigned if key is
|
34
|
+
# not set.
|
35
|
+
def assign(config, values=default)
|
26
36
|
if key
|
27
|
-
|
28
|
-
array
|
37
|
+
nest_config = nest(config)
|
38
|
+
array = (nest_config[key] ||= [])
|
39
|
+
array.concat(values)
|
29
40
|
|
30
41
|
if limit && array.length > limit
|
31
|
-
raise "too many assignments
|
42
|
+
raise "too many assignments for #{key.inspect}"
|
32
43
|
end
|
33
44
|
end
|
34
45
|
|
35
|
-
|
46
|
+
config
|
47
|
+
end
|
48
|
+
|
49
|
+
# Splits string values along the delimiter, if specified. Returns array
|
50
|
+
# values directly, and an empty array for nil. All other values are
|
51
|
+
# arrayified like [obj].
|
52
|
+
def split(obj)
|
53
|
+
case obj
|
54
|
+
when Array then obj
|
55
|
+
when String then delimiter ? obj.split(delimiter) : [obj]
|
56
|
+
when nil then []
|
57
|
+
else [obj]
|
58
|
+
end
|
36
59
|
end
|
37
60
|
end
|
38
61
|
end
|
data/lib/config_parser/option.rb
CHANGED
@@ -2,37 +2,46 @@ require 'config_parser/switch'
|
|
2
2
|
|
3
3
|
class ConfigParser
|
4
4
|
|
5
|
-
#
|
5
|
+
# An Option represents a Flag that takes a value.
|
6
6
|
class Option < Flag
|
7
|
+
|
8
|
+
# The default argument name
|
7
9
|
DEFAULT_ARGNAME = 'VALUE'
|
8
10
|
|
11
|
+
# Matches optional argnames
|
12
|
+
OPTIONAL = /\A\[.*\]\z/
|
13
|
+
|
9
14
|
# The argument name printed by to_s.
|
10
15
|
attr_reader :arg_name
|
11
16
|
|
17
|
+
# Set to true to make the argument optional
|
18
|
+
attr_reader :optional
|
19
|
+
|
12
20
|
def initialize(attrs={})
|
13
21
|
super
|
14
22
|
@arg_name = attrs[:arg_name] || (key ? key.to_s.upcase : DEFAULT_ARGNAME)
|
23
|
+
@optional = (attrs.has_key?(:optional) ? attrs[:optional] : (arg_name =~ OPTIONAL ? true : false))
|
15
24
|
end
|
16
25
|
|
17
26
|
# Parse the flag and value. If no value is provided and a value is
|
18
|
-
# required, then a value is shifted off of argv.
|
19
|
-
#
|
20
|
-
def parse(flag, value, argv=[], config={})
|
27
|
+
# required, then a value is shifted off of argv. The value is then
|
28
|
+
# processed and assigned into config.
|
29
|
+
def parse(flag, value=nil, argv=[], config={})
|
21
30
|
if value.nil?
|
22
31
|
unless value = next_arg(argv)
|
23
|
-
|
32
|
+
if optional
|
33
|
+
value = default
|
34
|
+
else
|
35
|
+
raise "no value provided for: #{flag}"
|
36
|
+
end
|
24
37
|
end
|
25
38
|
end
|
26
|
-
|
39
|
+
|
40
|
+
assign(config, process(value))
|
27
41
|
end
|
28
42
|
|
29
43
|
private
|
30
44
|
|
31
|
-
def next_arg(argv) # :nodoc:
|
32
|
-
arg = argv[0]
|
33
|
-
(arg.kind_of?(String) && arg[0] == ?-) ? default : argv.shift
|
34
|
-
end
|
35
|
-
|
36
45
|
def header_str # :nodoc:
|
37
46
|
" #{short_str}#{long_str} #{arg_name}"
|
38
47
|
end
|
data/lib/config_parser/switch.rb
CHANGED
@@ -6,38 +6,40 @@ class ConfigParser
|
|
6
6
|
# and negative (--no-flag) flags map to self.
|
7
7
|
class Switch < Flag
|
8
8
|
|
9
|
-
# The negative
|
10
|
-
attr_reader :
|
9
|
+
# The negative mapping prefix, defaults to 'no'
|
10
|
+
attr_reader :prefix
|
11
|
+
|
12
|
+
# The negative long flag, determined from long and prefix.
|
13
|
+
attr_reader :nolong
|
11
14
|
|
12
15
|
def initialize(attrs={})
|
13
16
|
attrs[:default] = true unless attrs.has_key?(:default)
|
14
17
|
super
|
15
18
|
|
16
19
|
raise ArgumentError, "no long specified" unless long
|
17
|
-
@
|
20
|
+
@prefix = attrs[:prefix] || 'no'
|
21
|
+
@nolong = prefix_long(long, "#{prefix}-")
|
18
22
|
end
|
19
23
|
|
20
|
-
# Returns an array of
|
21
|
-
# negative_long, short]).
|
24
|
+
# Returns an array of flags mapping to self (ie [long, nolong, short]).
|
22
25
|
def flags
|
23
|
-
[long,
|
26
|
+
[long, nolong, short].compact
|
24
27
|
end
|
25
28
|
|
26
|
-
# Assigns
|
27
|
-
# flags.
|
28
|
-
#
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
value = (flag ==
|
33
|
-
assign(config,
|
29
|
+
# Assigns default into config for positive flags and !default for negative
|
30
|
+
# flags. The boolean value is then processed and assigned into config.
|
31
|
+
# Raises an error if a value is provided (switches take none).
|
32
|
+
def parse(flag, value=nil, argv=[], config={})
|
33
|
+
raise "value specified for #{flag}: #{value.inspect}" if value
|
34
|
+
|
35
|
+
value = (flag == nolong ? !default : default)
|
36
|
+
assign(config, process(value))
|
34
37
|
end
|
35
38
|
|
36
39
|
private
|
37
40
|
|
38
|
-
# helper returning long formatted for to_s
|
39
41
|
def long_str # :nodoc:
|
40
|
-
|
42
|
+
prefix_long(long, "[#{prefix}-]")
|
41
43
|
end
|
42
44
|
end
|
43
45
|
end
|
data/lib/config_parser/utils.rb
CHANGED
@@ -10,19 +10,30 @@ class ConfigParser
|
|
10
10
|
# The default option break
|
11
11
|
OPTION_BREAK = "--"
|
12
12
|
|
13
|
+
# Matches an option (long or short)
|
14
|
+
OPTION = /\A-./
|
15
|
+
|
13
16
|
# Matches a long flag
|
14
17
|
LONG_FLAG = /\A--.+\z/
|
15
18
|
|
16
19
|
# Matches a short flag
|
17
20
|
SHORT_FLAG = /\A-.\z/
|
18
21
|
|
19
|
-
# Matches a switch
|
20
|
-
#
|
21
|
-
#
|
22
|
+
# Matches a switch option (ex: '--[no-]opt', '--nest:[no-]opt'). After the
|
23
|
+
# match:
|
24
|
+
#
|
22
25
|
# $1:: the nesting prefix ('nest')
|
23
|
-
# $2:: the
|
26
|
+
# $2:: the nolong prefix ('no')
|
27
|
+
# $3:: the long flag name ('opt')
|
24
28
|
#
|
25
|
-
SWITCH = /\A--(.*?)\[
|
29
|
+
SWITCH = /\A--(.*?)\[(.*?)-\](.+)\z/
|
30
|
+
|
31
|
+
# Matches a nest option (ex: '--nest:opt'). After the match:
|
32
|
+
#
|
33
|
+
# $1:: the nesting prefix ('nest')
|
34
|
+
# $2:: the long option ('long')
|
35
|
+
#
|
36
|
+
NEST = /\A--(.*):(.+)\z/
|
26
37
|
|
27
38
|
# Turns the input into a short flag by prefixing '-' (as needed). Raises
|
28
39
|
# an error if the input doesn't result in a short flag. Nils are
|
@@ -35,7 +46,7 @@ class ConfigParser
|
|
35
46
|
return nil if str.nil?
|
36
47
|
|
37
48
|
str = str.to_s
|
38
|
-
str = "-#{str}" unless str
|
49
|
+
str = "-#{str}" unless option?(str)
|
39
50
|
|
40
51
|
unless str =~ SHORT_FLAG
|
41
52
|
raise ArgumentError, "invalid short flag: #{str}"
|
@@ -55,7 +66,7 @@ class ConfigParser
|
|
55
66
|
return nil if str.nil?
|
56
67
|
|
57
68
|
str = str.to_s
|
58
|
-
str = "--#{str}" unless str
|
69
|
+
str = "--#{str}" unless option?(str)
|
59
70
|
|
60
71
|
unless str =~ LONG_FLAG
|
61
72
|
raise ArgumentError, "invalid long flag: #{str}"
|
@@ -76,6 +87,17 @@ class ConfigParser
|
|
76
87
|
"--#{switch.join(':')}"
|
77
88
|
end
|
78
89
|
|
90
|
+
# Returns true if the object is a string and matches OPTION.
|
91
|
+
def option?(obj)
|
92
|
+
obj.kind_of?(String) && obj =~ OPTION ? true : false
|
93
|
+
end
|
94
|
+
|
95
|
+
# Shifts and returns the first argument off of argv if it is an argument
|
96
|
+
# (rather than an option) or returns the default value.
|
97
|
+
def next_arg(argv)
|
98
|
+
option?(argv.at(0)) ? nil : argv.shift
|
99
|
+
end
|
100
|
+
|
79
101
|
# A wrapping algorithm slightly modified from:
|
80
102
|
# http://blog.macromates.com/2006/wrapping-text-with-regular-expressions/
|
81
103
|
def wrap(line, cols=80, tabsize=2)
|
@@ -83,16 +105,39 @@ class ConfigParser
|
|
83
105
|
line.gsub(/(.{1,#{cols}})( +|$\r?\n?)|(.{1,#{cols}})/, "\\1\\3\n").split(/\s*?\n/)
|
84
106
|
end
|
85
107
|
|
108
|
+
# Parses the argv into an attributes hash for initializing an option.
|
109
|
+
# Heuristics are used to infer what an argument implies.
|
110
|
+
#
|
111
|
+
# Argument Implies
|
112
|
+
# -s :short => '-s'
|
113
|
+
# --long :long => '--long'
|
114
|
+
# --long ARG :long => '--long', :arg_name => 'ARG'
|
115
|
+
# --[no-]long :long => '--long', :prefix => 'no', :type => :switch
|
116
|
+
# --nest:long :long => '--nest:long', :nest_keys => ['nest']
|
117
|
+
# 'some string' :desc => 'some string'
|
118
|
+
#
|
119
|
+
# Usually you overlay these patterns, for example:
|
120
|
+
#
|
121
|
+
# -s ARG :short => '-s', :arg_name => 'ARG'
|
122
|
+
# --nest:[no-]long :long => '--nest:long', :nest_keys => ['nest'], :prefix => 'no', :type => :switch
|
123
|
+
#
|
124
|
+
# The goal of this method is to get things right most of the time, not to
|
125
|
+
# be clean, simple, or robust. Some errors in declarations (like an
|
126
|
+
# arg_name with a switch) can be detected... others not so much.
|
86
127
|
def parse_attrs(argv)
|
87
128
|
attrs={}
|
88
129
|
|
89
130
|
argv.each do |arg|
|
90
|
-
|
131
|
+
unless option?(arg)
|
91
132
|
attrs[:desc] = arg
|
92
133
|
next
|
93
134
|
end
|
94
135
|
|
95
136
|
flag, arg_name = arg.split(/\s+/, 2)
|
137
|
+
|
138
|
+
if flag =~ NEST
|
139
|
+
attrs[:nest_keys] = $1.split(':')
|
140
|
+
end
|
96
141
|
|
97
142
|
if arg_name
|
98
143
|
attrs[:arg_name] = arg_name
|
@@ -100,9 +145,9 @@ class ConfigParser
|
|
100
145
|
|
101
146
|
case flag
|
102
147
|
when SWITCH
|
103
|
-
attrs[:long] = "--#{$1}#{$
|
104
|
-
attrs[:
|
105
|
-
|
148
|
+
attrs[:long] = "--#{$1}#{$3}"
|
149
|
+
attrs[:prefix] = $2
|
150
|
+
|
106
151
|
if arg_name
|
107
152
|
raise ArgumentError, "arg_name specified for switch: #{arg_name}"
|
108
153
|
end
|
@@ -121,15 +166,49 @@ class ConfigParser
|
|
121
166
|
attrs
|
122
167
|
end
|
123
168
|
|
124
|
-
|
169
|
+
# Guesses an option type based on the attrs.
|
170
|
+
#
|
171
|
+
# if... then...
|
172
|
+
# prefix => :switch
|
173
|
+
# arg_name => guess_type_by_arg_name || guess_type_by_value
|
174
|
+
# default => (guess_type_by_value)
|
175
|
+
# all else => :flag
|
176
|
+
#
|
177
|
+
# A guess is just a guess; for certainty specify the type manually.
|
178
|
+
def guess_type(attrs)
|
125
179
|
case
|
126
|
-
when attrs
|
180
|
+
when attrs.has_key?(:prefix)
|
127
181
|
:switch
|
128
|
-
when attrs
|
129
|
-
:
|
182
|
+
when attrs.has_key?(:arg_name)
|
183
|
+
guess_type_by_arg_name(attrs[:arg_name]) || guess_type_by_value(attrs[:default])
|
184
|
+
when attrs.has_key?(:default)
|
185
|
+
guess_type_by_value(attrs[:default])
|
130
186
|
else
|
131
187
|
:flag
|
132
188
|
end
|
133
189
|
end
|
190
|
+
|
191
|
+
# Guesses :list if the arg_name has a comma, or nil.
|
192
|
+
def guess_type_by_arg_name(arg_name)
|
193
|
+
arg_name.to_s.include?(',') ? :list : nil
|
194
|
+
end
|
195
|
+
|
196
|
+
# Guesses an option type based on a value.
|
197
|
+
#
|
198
|
+
# if... then...
|
199
|
+
# true => :switch
|
200
|
+
# false => :flag
|
201
|
+
# Array => :list
|
202
|
+
# all else => :option
|
203
|
+
#
|
204
|
+
# A guess is just a guess; for certainty specify the type manually.
|
205
|
+
def guess_type_by_value(value)
|
206
|
+
case value
|
207
|
+
when true then :switch
|
208
|
+
when false then :flag
|
209
|
+
when Array then :list
|
210
|
+
else :option
|
211
|
+
end
|
212
|
+
end
|
134
213
|
end
|
135
214
|
end
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
7
|
+
- 2
|
8
8
|
- 0
|
9
|
-
version: 0.
|
9
|
+
version: 0.2.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Simon Chiang
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-
|
17
|
+
date: 2010-08-01 00:00:00 -06:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|