configurable 0.7.0 → 1.0.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.
Files changed (38) hide show
  1. data/Help/Command Line.rdoc +141 -0
  2. data/Help/Config Syntax.rdoc +229 -0
  3. data/Help/Config Types.rdoc +143 -0
  4. data/{History → History.rdoc} +9 -0
  5. data/MIT-LICENSE +1 -1
  6. data/README.rdoc +144 -0
  7. data/lib/configurable.rb +7 -270
  8. data/lib/configurable/class_methods.rb +344 -367
  9. data/lib/configurable/config_classes.rb +3 -0
  10. data/lib/configurable/config_classes/list_config.rb +26 -0
  11. data/lib/configurable/config_classes/nest_config.rb +50 -0
  12. data/lib/configurable/config_classes/scalar_config.rb +91 -0
  13. data/lib/configurable/config_hash.rb +87 -112
  14. data/lib/configurable/config_types.rb +6 -0
  15. data/lib/configurable/config_types/boolean_type.rb +22 -0
  16. data/lib/configurable/config_types/float_type.rb +11 -0
  17. data/lib/configurable/config_types/integer_type.rb +11 -0
  18. data/lib/configurable/config_types/nest_type.rb +39 -0
  19. data/lib/configurable/config_types/object_type.rb +58 -0
  20. data/lib/configurable/config_types/string_type.rb +15 -0
  21. data/lib/configurable/conversions.rb +91 -0
  22. data/lib/configurable/module_methods.rb +0 -1
  23. data/lib/configurable/version.rb +1 -5
  24. metadata +73 -30
  25. data/README +0 -207
  26. data/lib/cdoc.rb +0 -413
  27. data/lib/cdoc/cdoc_html_generator.rb +0 -38
  28. data/lib/cdoc/cdoc_html_template.rb +0 -42
  29. data/lib/config_parser.rb +0 -563
  30. data/lib/config_parser/option.rb +0 -108
  31. data/lib/config_parser/switch.rb +0 -44
  32. data/lib/config_parser/utils.rb +0 -177
  33. data/lib/configurable/config.rb +0 -97
  34. data/lib/configurable/indifferent_access.rb +0 -35
  35. data/lib/configurable/nest_config.rb +0 -78
  36. data/lib/configurable/ordered_hash_patch.rb +0 -85
  37. data/lib/configurable/utils.rb +0 -186
  38. data/lib/configurable/validation.rb +0 -768
@@ -1,42 +0,0 @@
1
- require 'rdoc/generators/template/html/html'
2
-
3
- #
4
- # Add a template for documenting configurations. Do so by inserting in the
5
- # template into the content regions used to template html.
6
- # (see 'rdoc/generators/html_generator' line 864)
7
- #
8
- [
9
- RDoc::Page::BODY,
10
- RDoc::Page::FILE_PAGE,
11
- RDoc::Page::METHOD_LIST].each do |content|
12
-
13
- # this substitution method duplicates the attribute template for configurations
14
- # (see rdoc\generators\template\html line 523)
15
- #
16
- #IF:attributes
17
- # <div id="attribute-list">
18
- # <h3 class="section-bar">Attributes</h3>
19
- #
20
- # <div class="name-list">
21
- # <table>
22
- #START:attributes
23
- # <tr class="top-aligned-row context-row">
24
- # <td class="context-item-name">%name%</td>
25
- #IF:rw
26
- # <td class="context-item-value">&nbsp;[%rw%]&nbsp;</td>
27
- #ENDIF:rw
28
- #IFNOT:rw
29
- # <td class="context-item-value">&nbsp;&nbsp;</td>
30
- #ENDIF:rw
31
- # <td class="context-item-desc">%a_desc%</td>
32
- # </tr>
33
- #END:attributes
34
- # </table>
35
- # </div>
36
- # </div>
37
- #ENDIF:attributes
38
- #
39
- content.gsub!(/IF:attributes.*?ENDIF:attributes/m) do |match|
40
- match + "\n\n" + match.gsub(/attributes/, 'configurations').gsub(/Attributes/, 'Configurations')
41
- end
42
- end
@@ -1,563 +0,0 @@
1
- require 'config_parser/option'
2
- require 'config_parser/switch'
3
-
4
- autoload(:Shellwords, 'shellwords')
5
-
6
- # ConfigParser is the Configurable equivalent of
7
- # {OptionParser}[http://www.ruby-doc.org/core/classes/OptionParser.html]
8
- # and uses a similar, simplified (see below) syntax to declare options.
9
- #
10
- # opts = {}
11
- # parser = ConfigParser.new do |psr|
12
- # psr.on "-s", "--long LONG", "a standard option" do |value|
13
- # opts[:long] = value
14
- # end
15
- #
16
- # psr.on "--[no-]switch", "a switch" do |value|
17
- # opts[:switch] = value
18
- # end
19
- #
20
- # psr.on "--flag", "a flag" do
21
- # # note: no value is parsed; the block
22
- # # only executes if the flag is found
23
- # opts[:flag] = true
24
- # end
25
- # end
26
- #
27
- # parser.parse("a b --long arg --switch --flag c") # => ['a', 'b', 'c']
28
- # opts # => {:long => 'arg', :switch => true, :flag => true}
29
- #
30
- # ConfigParser formalizes this pattern of setting values in a hash as they
31
- # occur, and adds the ability to specify default values. The syntax is
32
- # not quite as friendly as for ordinary options, but meshes well with
33
- # Configurable classes:
34
- #
35
- # psr = ConfigParser.new
36
- # psr.define(:key, 'default', :desc => 'a standard option')
37
- #
38
- # psr.parse('a b --key option c') # => ['a', 'b', 'c']
39
- # psr.config # => {:key => 'option'}
40
- #
41
- # psr.parse('a b c') # => ['a', 'b', 'c']
42
- # psr.config # => {:key => 'default'}
43
- #
44
- # And now directly from a Configurable class, the equivalent of the
45
- # original example:
46
- #
47
- # class ConfigClass
48
- # include Configurable
49
- #
50
- # config :long, 'default', :short => 's' # a standard option
51
- # config :switch, false, &c.switch # a switch
52
- # config :flag, false, &c.flag # a flag
53
- # end
54
- #
55
- # psr = ConfigParser.new
56
- # psr.add(ConfigClass.configurations)
57
- #
58
- # psr.parse("a b --long arg --switch --flag c") # => ['a', 'b', 'c']
59
- # psr.config # => {:long => 'arg', :switch => true, :flag => true}
60
- #
61
- # psr.parse("a b --long=arg --no-switch c") # => ['a', 'b', 'c']
62
- # psr.config # => {:long => 'arg', :switch => false, :flag => false}
63
- #
64
- # psr.parse("a b -sarg c") # => ['a', 'b', 'c']
65
- # psr.config # => {:long => 'arg', :switch => false, :flag => false}
66
- #
67
- # As you might expect, config attributes are used by ConfigParser to
68
- # correctly build a corresponding option. In configurations like :switch,
69
- # the block implies the {:type => :switch} attribute and so the
70
- # config is made into a switch-style option by ConfigParser.
71
- #
72
- # Use the to_s method to convert a ConfigParser into command line
73
- # documentation:
74
- #
75
- # "\nconfigurations:\n#{psr.to_s}"
76
- # # => %q{
77
- # # configurations:
78
- # # -s, --long LONG a standard option
79
- # # --[no-]switch a switch
80
- # # --flag a flag
81
- # # }
82
- #
83
- # ==== Simplifications
84
- #
85
- # ConfigParser simplifies the OptionParser syntax for 'on'. ConfigParser does
86
- # not support automatic conversion of values, gets rid of 'optional' arguments
87
- # for options, and only supports a single description string. Hence:
88
- #
89
- # psr = ConfigParser.new
90
- #
91
- # # incorrect, raises error as this will look
92
- # # like multiple descriptions are specified
93
- # psr.on("--delay N",
94
- # Float,
95
- # "Delay N seconds before executing") # !> ArgumentError
96
- #
97
- # # correct
98
- # psr.on("--delay N", "Delay N seconds before executing") do |value|
99
- # value.to_f
100
- # end
101
- #
102
- # # this ALWAYS requires the argument and raises
103
- # # an error because multiple descriptions are
104
- # # specified
105
- # psr.on("-i", "--inplace [EXTENSION]",
106
- # "Edit ARGV files in place",
107
- # " (make backup if EXTENSION supplied)") # !> ArgumentError
108
- #
109
- # # correct
110
- # psr.on("-i", "--inplace EXTENSION",
111
- # "Edit ARGV files in place\n (make backup if EXTENSION supplied)")
112
- #
113
- #
114
- class ConfigParser
115
- class << self
116
- # Splits and nests compound keys of a hash.
117
- #
118
- # ConfigParser.nest('key' => 1, 'compound:key' => 2)
119
- # # => {
120
- # # 'key' => 1,
121
- # # 'compound' => {'key' => 2}
122
- # # }
123
- #
124
- # Nest does not do any consistency checking, so be aware that results will
125
- # be ambiguous for overlapping compound keys.
126
- #
127
- # ConfigParser.nest('key' => {}, 'key:overlap' => 'value')
128
- # # =? {'key' => {}}
129
- # # =? {'key' => {'overlap' => 'value'}}
130
- #
131
- def nest(hash, split_char=":")
132
- result = {}
133
- hash.each_pair do |compound_key, value|
134
- if compound_key.kind_of?(String)
135
- keys = compound_key.split(split_char)
136
-
137
- unless keys.length == 1
138
- nested_key = keys.pop
139
- nested_hash = keys.inject(result) {|target, key| target[key] ||= {}}
140
- nested_hash[nested_key] = value
141
- next
142
- end
143
- end
144
-
145
- result[compound_key] = value
146
- end
147
-
148
- result
149
- end
150
-
151
- # Generates a new parser bound to a specific config. All this really
152
- # means is that each time the parser calls parse, configurations will
153
- # be added to the config without clearing the config, or adding default
154
- # values. This can be useful in signaling situations where a config
155
- # needs to be updated multiple times.
156
- #
157
- # psr = ConfigParser.bind
158
- # psr.define('a', 'default')
159
- # psr.define('b', 'default')
160
- #
161
- # psr.parse %w{--a value}
162
- # psr.config # => {"a" => "value"}
163
- #
164
- # psr.parse %w{--b value}
165
- # psr.config # => {"a" => "value", "b" => "value"}
166
- #
167
- def bind(config={})
168
- parser = new(config, :clear_config => false, :add_defaults => false)
169
- yield(parser) if block_given?
170
- parser
171
- end
172
- end
173
-
174
- include Utils
175
-
176
- # Returns an array of the options registered with self, in the order in
177
- # which they were added. Separators are also stored in the registry.
178
- attr_reader :registry
179
-
180
- # A hash of (switch, Option) pairs mapping command line
181
- # switches like '-s' or '--long' to the Option that
182
- # handles them.
183
- attr_reader :switches
184
-
185
- # The hash receiving configurations produced by parse.
186
- attr_accessor :config
187
-
188
- # A hash of default configurations merged into config during parse. These
189
- # defaults are defined as options are added to self (via define, add, etc)
190
- # and do not need to be manually specified.
191
- attr_reader :defaults
192
-
193
- # A hash of default parsing options that adjust the behavior of parse
194
- # (see parse).
195
- attr_reader :default_parse_options
196
-
197
- # Initializes a new ConfigParser and passes it to the block, if given.
198
- def initialize(config={}, default_parse_options={})
199
- @registry = []
200
- @switches = {}
201
- @config = config
202
- @defaults = {}
203
- @default_parse_options = {
204
- :clear_config => true,
205
- :add_defaults => true,
206
- :ignore_unknown_options => false,
207
- :option_break => OPTION_BREAK,
208
- :keep_break => false
209
- }.merge(default_parse_options)
210
-
211
- yield(self) if block_given?
212
- end
213
-
214
- # Returns the config value for key.
215
- def [](key)
216
- config[key]
217
- end
218
-
219
- # Sets the config value for key.
220
- def []=(key, value)
221
- config[key] = value
222
- end
223
-
224
- # Returns the nested form of config (see ConfigParser.nest). Primarily
225
- # useful when nested configurations have been added with add.
226
- def nested_config
227
- ConfigParser.nest(config)
228
- end
229
-
230
- # Returns an array of the options registered with self.
231
- def options
232
- @registry.select do |opt|
233
- opt.kind_of?(Option)
234
- end
235
- end
236
-
237
- # Adds a separator string to self, used in to_s.
238
- def separator(str)
239
- @registry << str
240
- end
241
-
242
- # Registers the option with self by adding opt to options and mapping the
243
- # opt switches. Raises an error for conflicting switches.
244
- #
245
- # If override is specified, options with conflicting switches are removed
246
- # and no error is raised. Note that this may remove multiple options.
247
- def register(opt, override=false)
248
- if override
249
- existing = opt.switches.collect do |switch|
250
- @switches.delete(switch)
251
- end
252
- @registry -= existing
253
- end
254
-
255
- unless @registry.include?(opt)
256
- @registry << opt
257
- end
258
-
259
- opt.switches.each do |switch|
260
- case @switches[switch]
261
- when opt then next
262
- when nil then @switches[switch] = opt
263
- else raise ArgumentError, "switch is already mapped to a different option: #{switch}"
264
- end
265
- end
266
-
267
- opt
268
- end
269
-
270
- # Constructs an Option using args and registers it with self. Args may
271
- # contain (in any order) a short switch, a long switch, and a description
272
- # string. Either the short or long switch may signal that the option
273
- # should take an argument by providing an argument name.
274
- #
275
- # psr = ConfigParser.new
276
- #
277
- # # this option takes an argument
278
- # psr.on('-s', '--long ARG_NAME', 'description') do |value|
279
- # # ...
280
- # end
281
- #
282
- # # so does this one
283
- # psr.on('-o ARG_NAME', 'description') do |value|
284
- # # ...
285
- # end
286
- #
287
- # # this option does not
288
- # psr.on('-f', '--flag') do
289
- # # ...
290
- # end
291
- #
292
- # A switch-style option can be specified by prefixing the long switch with
293
- # '--[no-]'. Switch options will pass true to the block for the positive
294
- # form and false for the negative form.
295
- #
296
- # psr.on('--[no-]switch') do |value|
297
- # # ...
298
- # end
299
- #
300
- # Args may also contain a trailing hash defining all or part of the option:
301
- #
302
- # psr.on('-k', :long => '--key', :desc => 'description')
303
- # # ...
304
- # end
305
- #
306
- def on(*args, &block)
307
- register new_option(args, &block)
308
- end
309
-
310
- # Same as on, but overrides options with overlapping switches.
311
- def on!(*args, &block)
312
- register new_option(args, &block), true
313
- end
314
-
315
- # Defines and registers a config-style option with self. Define does not
316
- # take a block; the default value will be added to config, and any parsed
317
- # value will override the default. Normally the key will be turned into
318
- # the long switch; specify an alternate long, a short, description, etc
319
- # using attributes.
320
- #
321
- # psr = ConfigParser.new
322
- # psr.define(:one, 'default')
323
- # psr.define(:two, 'default', :long => '--long', :short => '-s')
324
- #
325
- # psr.parse("--one one --long two")
326
- # psr.config # => {:one => 'one', :two => 'two'}
327
- #
328
- # Define support several types of configurations that define a special
329
- # block to handle the values parsed from the command line. See the
330
- # 'setup_<type>' methods in Utils. Any type with a corresponding setup
331
- # method is valid:
332
- #
333
- # psr = ConfigParser.new
334
- # psr.define(:flag, false, :type => :flag)
335
- # psr.define(:switch, false, :type => :switch)
336
- # psr.define(:list, [], :type => :list)
337
- #
338
- # psr.parse("--flag --switch --list one --list two --list three")
339
- # psr.config # => {:flag => true, :switch => true, :list => ['one', 'two', 'three']}
340
- #
341
- # New, valid types may be added by implementing new setup_<type> methods
342
- # following this pattern:
343
- #
344
- # module SpecialType
345
- # def setup_special(key, default_value, attributes)
346
- # # modify attributes if necessary
347
- # attributes[:long] = "--#{key}"
348
- # attributes[:arg_name] = 'ARG_NAME'
349
- #
350
- # # return a block handling the input
351
- # lambda {|input| config[key] = input.reverse }
352
- # end
353
- # end
354
- #
355
- # psr = ConfigParser.new.extend SpecialType
356
- # psr.define(:opt, false, :type => :special)
357
- #
358
- # psr.parse("--opt value")
359
- # psr.config # => {:opt => 'eulav'}
360
- #
361
- # The :hidden type causes no configuration to be defined. Raises an error if
362
- # key is already set by a different option.
363
- def define(key, default_value=nil, attributes={})
364
- # check for conflicts and register
365
- if defaults.has_key?(key)
366
- raise ArgumentError, "already set by a different option: #{key.inspect}"
367
- end
368
- defaults[key] = default_value
369
-
370
- # ensure setup does not modifiy input attributes
371
- attributes = attributes.dup
372
-
373
- block = case attributes[:type]
374
- when :switch then setup_switch(key, default_value, attributes)
375
- when :flag then setup_flag(key, default_value, attributes)
376
- when :list, :list_select then setup_list(key, attributes)
377
- when :hidden then return nil
378
- else
379
- if respond_to?("setup_#{attributes[:type]}")
380
- send("setup_#{attributes[:type]}", key, default_value, attributes)
381
- else
382
- setup_option(key, attributes)
383
- end
384
- end
385
-
386
- on(attributes, &block)
387
- end
388
-
389
- # Adds a hash of delegates (for example the configurations for a Configurable
390
- # class) to self. Configs are added like:
391
- #
392
- # define(key, delegate.default, delegate.attributes)
393
- #
394
- # ==== Nesting
395
- #
396
- # When you nest Configurable classes, a special syntax is necessary to
397
- # specify nested configurations in a flat format compatible with the
398
- # command line. As such, nested delegates are recursively added with
399
- # their key as a prefix. For instance:
400
- #
401
- # class NestClass
402
- # include Configurable
403
- # nest :nest do
404
- # config :key, 'value'
405
- # end
406
- # end
407
- #
408
- # psr = ConfigParser.new
409
- # psr.add(NestClass.configurations)
410
- # psr.parse('--nest:key value')
411
- #
412
- # psr.config # => {'nest:key' => 'value'}
413
- # psr.nested_config # => {'nest' => {'key' => 'value'}}
414
- #
415
- # Side note: The fact that all the keys end up as strings underscores
416
- # the importance of having indifferent access for delegates. If you
417
- # set use_indifferent_access(false), be prepared to symbolize nested
418
- # keys as necessary.
419
- #
420
- def add(delegates, nesting=nil)
421
- delegates.each_pair do |key, delegate|
422
- key = nesting ? "#{nesting}:#{key}" : key
423
-
424
- case delegate[:type]
425
- when :hidden
426
- next
427
- when :nest
428
- add(delegate.nest_class.configurations, key)
429
- else
430
- define(key, delegate.default, delegate.attributes)
431
- end
432
- end
433
- end
434
-
435
- # Parses options from argv in a non-destructive manner and returns an
436
- # array of arguments remaining after options have been removed. If a
437
- # string argv is provided, it will be splits into an array using
438
- # Shellwords.
439
- #
440
- # ==== Options
441
- #
442
- # clear_config:: clears the currently parsed configs (true)
443
- # add_defaults:: adds the default values to config (true)
444
- # ignore_unknown_options:: causes unknown options to be ignored (false)
445
- #
446
- def parse(argv=ARGV, options={})
447
- argv = argv.dup unless argv.kind_of?(String)
448
- parse!(argv, options)
449
- end
450
-
451
- # Same as parse, but removes parsed args from argv.
452
- def parse!(argv=ARGV, options={})
453
- argv = Shellwords.shellwords(argv) if argv.kind_of?(String)
454
-
455
- args = []
456
- remainder = scan(argv, options) {|arg| args << arg}
457
- args.concat(remainder)
458
- argv.replace(args)
459
-
460
- argv
461
- end
462
-
463
- def scan(argv=ARGV, options={})
464
- options = default_parse_options.merge(options)
465
- config.clear if options[:clear_config]
466
-
467
- option_break = options[:option_break]
468
- while !argv.empty?
469
- arg = argv.shift
470
-
471
- # determine if the arg is an option
472
- unless arg.kind_of?(String) && arg[0] == ?-
473
- yield(arg)
474
- next
475
- end
476
-
477
- # add the remaining args and break
478
- # for the option break
479
- if option_break === arg
480
- argv.unshift(arg) if options[:keep_break]
481
- break
482
- end
483
-
484
- # split the arg...
485
- # switch= $1
486
- # value = $2
487
- arg =~ LONG_OPTION || arg =~ SHORT_OPTION || arg =~ ALT_SHORT_OPTION
488
-
489
- # lookup the option
490
- unless option = @switches[$1]
491
- raise "unknown option: #{$1 || arg}"
492
- end
493
-
494
- option.parse($1, $2, argv)
495
- end
496
-
497
- defaults.each_pair do |key, default|
498
- config[key] = default unless config.has_key?(key)
499
- end if options[:add_defaults]
500
-
501
- argv
502
- end
503
-
504
- def warn_ignored_args(args)
505
- if args && !args.empty?
506
- warn "ignoring args: #{args.inspect}"
507
- end
508
- end
509
-
510
- # Converts the options and separators in self into a help string suitable for
511
- # display on the command line.
512
- def to_s
513
- @registry.collect do |option|
514
- option.to_s.rstrip
515
- end.join("\n") + "\n"
516
- end
517
-
518
- protected
519
-
520
- # helper to parse an option from an argv. new_option is used
521
- # by on and on! to generate options
522
- def new_option(argv, &block) # :nodoc:
523
- attributes = argv.last.kind_of?(Hash) ? argv.pop : {}
524
- argv.each do |arg|
525
- # split switch arguments... descriptions
526
- # still won't match as a switch even
527
- # after a split
528
- switch, arg_name = arg.kind_of?(String) ? arg.split(' ', 2) : arg
529
-
530
- # determine the kind of argument specified
531
- key = case switch
532
- when SHORT_OPTION then :short
533
- when LONG_OPTION then :long
534
- else :desc
535
- end
536
-
537
- # check for conflicts
538
- if attributes[key]
539
- raise ArgumentError, "conflicting #{key} options: [#{attributes[key].inspect}, #{arg.inspect}]"
540
- end
541
-
542
- # set the option attributes
543
- case key
544
- when :long, :short
545
- attributes[key] = switch
546
- attributes[:arg_name] = arg_name if arg_name
547
- else
548
- attributes[key] = arg
549
- end
550
- end
551
-
552
- # check if a switch-style option is specified
553
- klass = case
554
- when attributes[:long].to_s =~ /^--\[no-\](.*)$/
555
- attributes[:long] = "--#{$1}"
556
- Switch
557
- else
558
- Option
559
- end
560
-
561
- klass.new(attributes, &block)
562
- end
563
- end