config_parser 0.1.0 → 0.2.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 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