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 CHANGED
@@ -1,3 +1,7 @@
1
+ == 0.2.0 / 2010-08-01
2
+
3
+ Release candidate with cleaner implementation and accurate documentation.
4
+
1
5
  == 0.1.0 / 2010-07-25
2
6
 
3
7
  Initial release after extraction and cleanup from Configurable. Intended as a
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 that places less emphasis on the
11
- conversion of inputs to objects, preferring instead to delegate that
12
- responsibility to whatever consumes the hash.
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 itself can
17
- be used as a delegate to a config hash.
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(:key, 'default')
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 --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'}
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
- 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.
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, '-o', '--one', 'by args') {|value| value.upcase }
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 => 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.
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 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
- #
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 configurations produced by parse.
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 argument
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 opt to options and mapping the
164
- # opt flags. Raises an error for conflicting flags.
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
- option
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. Args may
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. Either the short or long switch may signal that the option
196
- # should take an argument by providing an argument name.
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
- # # this option takes an argument
201
- # psr.on('-s', '--long ARG_NAME', 'description') do |value|
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
- # # so does this one
206
- # psr.on('-o ARG_NAME', 'description') do |value|
119
+ # # the argname can be specified on a short
120
+ # psr.on('-o ARG_NAME') do |arg|
207
121
  # # ...
208
122
  # end
209
- #
210
- # # this option does not
211
- # psr.on('-f', '--flag') do
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
- # 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.
130
+ # # flags specify no argument, and the
131
+ # # block takes no argument
132
+ # psr.on('-f', '--flag') do
133
+ # # ...
134
+ # end
218
135
  #
219
- # psr.on('--[no-]switch') do |value|
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
- # Args may also contain a trailing hash defining all or part of the option:
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', :desc => 'description')
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
- register new_option(args, &block)
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 switches.
157
+ # Same as on, but overrides options with overlapping flags.
234
158
  def on!(*args, &block)
235
- register new_option(args, &block), true
159
+ option = new_option(args, &block)
160
+ register option, true
161
+ option
236
162
  end
237
163
 
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.
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
- # 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'}
168
+ # These are equivalent:
250
169
  #
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
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
- # 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)
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
- args = []
315
- remainder = scan(argv) {|arg| args << arg }
316
- args.concat(remainder)
317
- argv.replace(args)
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
- argv
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 arg.kind_of?(String) && arg[0] == ?-
328
- yield(arg)
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 for
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 = attrs.merge parse_attrs(argv)
287
+ attrs = parse_attrs(argv).merge(attrs)
391
288
  option_class(attrs).new(attrs, &block)
392
289
  end
393
290
  end
@@ -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 = 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
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 non-nil flags mapping to self (ie [long, short]).
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
- # 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.
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
- # Note this is the entry point for handling different types of
44
- # configuration flags.
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: #{flag}"
85
+ raise "value specified for #{flag}: #{value.inspect}"
74
86
  end
75
87
  end
76
88
 
77
- assign(config, callback ? callback.call : default.nil? ? false : default)
89
+ value = (default.nil? ? true : !default)
90
+ assign(config, process(value))
78
91
  end
79
92
 
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
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
@@ -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
- DEFAULT_SPLIT = ','
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 sequence on which to split single values into multiple values. Set
13
- # to nil to prevent split.
14
- attr_reader :split
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
- @limit = attrs[:n]
19
- @split = attrs.has_key?(:split) ? attrs[:split] : DEFAULT_SPLIT
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
- # 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)
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
- array = (config[key] ||= [])
28
- array.concat(split ? value.split(split) : [value])
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: #{key.inspect}"
42
+ raise "too many assignments for #{key.inspect}"
32
43
  end
33
44
  end
34
45
 
35
- value
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
@@ -2,37 +2,46 @@ require 'config_parser/switch'
2
2
 
3
3
  class ConfigParser
4
4
 
5
- # Represents an option registered with ConfigParser.
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. Calls the callback
19
- # with the value, if specified, and assigns the result.
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
- raise "no value provided for: #{flag}"
32
+ if optional
33
+ value = default
34
+ else
35
+ raise "no value provided for: #{flag}"
36
+ end
24
37
  end
25
38
  end
26
- assign(config, callback ? callback.call(value) : value)
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
@@ -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 long flag, determined from long if not set otherwise.
10
- attr_reader :negative_long
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
- @negative_long = attrs[:negative_long] || prefix_long(long, 'no-')
20
+ @prefix = attrs[:prefix] || 'no'
21
+ @nolong = prefix_long(long, "#{prefix}-")
18
22
  end
19
23
 
20
- # Returns an array of non-nil flags mapping to self (ie [long,
21
- # negative_long, short]).
24
+ # Returns an array of flags mapping to self (ie [long, nolong, short]).
22
25
  def flags
23
- [long, negative_long, short].compact
26
+ [long, nolong, short].compact
24
27
  end
25
28
 
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)
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
- long ? prefix_long(long, '[no-]') : ''
42
+ prefix_long(long, "[#{prefix}-]")
41
43
  end
42
44
  end
43
45
  end
@@ -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 declaration (ex: '--[no-]opt', '--nest:[no-]opt').
20
- # After the match:
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 long flag name ('opt')
26
+ # $2:: the nolong prefix ('no')
27
+ # $3:: the long flag name ('opt')
24
28
  #
25
- SWITCH = /\A--(.*?)\[no-\](.+)\z/
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[0] == ?-
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[0] == ?-
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
- if arg[0] != ?-
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}#{$2}"
104
- attrs[:negative_long] = "--#{$1}no-#{$2}"
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
- def guess_type(attrs) # :nodoc:
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[:negative_long]
180
+ when attrs.has_key?(:prefix)
127
181
  :switch
128
- when attrs[:arg_name] || attrs[:default]
129
- :option
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
@@ -1,6 +1,6 @@
1
1
  class ConfigParser
2
2
  MAJOR = 0
3
- MINOR = 1
3
+ MINOR = 2
4
4
  TINY = 0
5
5
 
6
6
  VERSION="#{MAJOR}.#{MINOR}.#{TINY}"
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 1
7
+ - 2
8
8
  - 0
9
- version: 0.1.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-07-25 00:00:00 -06:00
17
+ date: 2010-08-01 00:00:00 -06:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency