configurable 0.7.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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