configurable 0.1.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History +9 -0
- data/MIT-LICENSE +1 -1
- data/README +40 -142
- data/lib/cdoc.rb +413 -0
- data/lib/cdoc/cdoc_html_generator.rb +38 -0
- data/lib/cdoc/cdoc_html_template.rb +42 -0
- data/lib/config_parser.rb +302 -52
- data/lib/config_parser/option.rb +70 -21
- data/lib/config_parser/switch.rb +25 -10
- data/lib/config_parser/utils.rb +41 -27
- data/lib/configurable.rb +64 -40
- data/lib/configurable/class_methods.rb +245 -100
- data/lib/configurable/delegate.rb +18 -2
- data/lib/configurable/delegate_hash.rb +112 -69
- data/lib/configurable/indifferent_access.rb +21 -8
- data/lib/configurable/utils.rb +193 -0
- data/lib/configurable/validation.rb +112 -112
- metadata +16 -15
@@ -1,7 +1,7 @@
|
|
1
|
-
require 'lazydoc
|
1
|
+
require 'lazydoc'
|
2
2
|
require 'configurable/delegate_hash'
|
3
|
-
require 'configurable/validation'
|
4
3
|
require 'configurable/indifferent_access'
|
4
|
+
require 'configurable/validation'
|
5
5
|
|
6
6
|
autoload(:ConfigParser, 'config_parser')
|
7
7
|
|
@@ -12,61 +12,63 @@ module Configurable
|
|
12
12
|
module ClassMethods
|
13
13
|
include Lazydoc::Attributes
|
14
14
|
|
15
|
-
# A hash
|
15
|
+
# A hash of (key, Delegate) pairs defining the class configurations.
|
16
16
|
attr_reader :configurations
|
17
17
|
|
18
18
|
def self.extended(base) # :nodoc:
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
when Lazydoc::CALLER_REGEXP
|
23
|
-
base.instance_variable_set(:@source_file, File.expand_path($1))
|
24
|
-
break
|
25
|
-
end
|
19
|
+
unless base.instance_variable_defined?(:@source_file)
|
20
|
+
caller[2] =~ Lazydoc::CALLER_REGEXP
|
21
|
+
base.instance_variable_set(:@source_file, File.expand_path($1))
|
26
22
|
end
|
27
|
-
|
28
|
-
|
29
|
-
base.instance_variable_set(:@configurations, configurations)
|
23
|
+
|
24
|
+
base.send(:initialize_configurations).extend(IndifferentAccess)
|
30
25
|
end
|
31
26
|
|
32
27
|
def inherited(child) # :nodoc:
|
33
28
|
unless child.instance_variable_defined?(:@source_file)
|
34
|
-
caller
|
29
|
+
caller[0] =~ Lazydoc::CALLER_REGEXP
|
35
30
|
child.instance_variable_set(:@source_file, File.expand_path($1))
|
36
31
|
end
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
32
|
+
|
33
|
+
# deep duplicate configurations
|
34
|
+
unless child.instance_variable_defined?(:@configurations)
|
35
|
+
duplicate = child.instance_variable_set(:@configurations, configurations.dup)
|
36
|
+
duplicate.each_pair {|key, config| duplicate[key] = config.dup }
|
37
|
+
duplicate.extend(IndifferentAccess) if configurations.kind_of?(IndifferentAccess)
|
38
|
+
end
|
42
39
|
super
|
43
40
|
end
|
44
|
-
|
45
|
-
|
41
|
+
|
42
|
+
# Parses configurations from argv in a non-destructive manner by generating
|
43
|
+
# a ConfigParser using the configurations for self. Parsed configs are
|
44
|
+
# added to config (note that you must keep a separate reference to
|
45
|
+
# config as it is not returned by parse). The parser will is yielded to the
|
46
|
+
# block, if given, to register additonal options. Returns an array of the
|
47
|
+
# arguments that remain after parsing.
|
48
|
+
#
|
49
|
+
# See ConfigParser#parse for more information.
|
50
|
+
def parse(argv=ARGV, config={})
|
46
51
|
ConfigParser.new do |parser|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
parser.define(key, config.default, config.attributes)
|
51
|
-
end
|
52
|
-
end
|
52
|
+
parser.add(configurations)
|
53
|
+
yield(parser) if block_given?
|
54
|
+
end.parse(argv, config)
|
53
55
|
end
|
54
|
-
|
55
|
-
#
|
56
|
-
|
57
|
-
|
58
|
-
# the last check prevents YAML from auto-loading itself for empty files
|
59
|
-
return {} if path == nil || !File.file?(path) || File.size(path) == 0
|
60
|
-
YAML.load_file(path) || {}
|
56
|
+
|
57
|
+
# Same as parse, but removes parsed args from argv.
|
58
|
+
def parse!(argv=ARGV, config={})
|
59
|
+
argv.replace(parse(argv, config))
|
61
60
|
end
|
62
61
|
|
63
62
|
protected
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
63
|
+
|
64
|
+
# Sets configurations to symbolize keys for AGET ([]) and ASET([]=)
|
65
|
+
# operations, or not. By default, configurations will use
|
66
|
+
# indifferent access.
|
67
|
+
def use_indifferent_access(input=true)
|
68
|
+
if input
|
69
|
+
@configurations.extend(IndifferentAccess)
|
70
|
+
else
|
71
|
+
@configurations = configurations.dup
|
70
72
|
end
|
71
73
|
end
|
72
74
|
|
@@ -93,19 +95,16 @@ module Configurable
|
|
93
95
|
# end
|
94
96
|
# end
|
95
97
|
#
|
96
|
-
def config(key, value=nil,
|
97
|
-
|
98
|
-
options[:desc] ||= Lazydoc.register_caller
|
98
|
+
def config(key, value=nil, attributes={}, &block)
|
99
|
+
attributes = merge_attributes(block, attributes)
|
99
100
|
|
100
101
|
if block_given?
|
101
|
-
options = default_options(block).merge!(options)
|
102
|
-
|
103
102
|
instance_variable = "@#{key}".to_sym
|
104
|
-
config_attr(key, value,
|
103
|
+
config_attr(key, value, attributes) do |input|
|
105
104
|
instance_variable_set(instance_variable, yield(input))
|
106
105
|
end
|
107
106
|
else
|
108
|
-
config_attr(key, value,
|
107
|
+
config_attr(key, value, attributes)
|
109
108
|
end
|
110
109
|
end
|
111
110
|
|
@@ -134,11 +133,11 @@ module Configurable
|
|
134
133
|
# end
|
135
134
|
# end
|
136
135
|
#
|
137
|
-
def config_attr(key, value=nil,
|
138
|
-
|
139
|
-
|
136
|
+
def config_attr(key, value=nil, attributes={}, &block)
|
137
|
+
attributes = merge_attributes(block, attributes)
|
138
|
+
|
140
139
|
# define the default public reader method
|
141
|
-
reader =
|
140
|
+
reader = attributes.delete(:reader)
|
142
141
|
|
143
142
|
case reader
|
144
143
|
when true
|
@@ -150,7 +149,7 @@ module Configurable
|
|
150
149
|
end
|
151
150
|
|
152
151
|
# define the default public writer method
|
153
|
-
writer =
|
152
|
+
writer = attributes.delete(:writer)
|
154
153
|
|
155
154
|
if block_given? && writer != true
|
156
155
|
raise ArgumentError, "a block may not be specified without writer == true"
|
@@ -165,10 +164,7 @@ module Configurable
|
|
165
164
|
writer = "#{key}="
|
166
165
|
end
|
167
166
|
|
168
|
-
|
169
|
-
options[:desc] ||= Lazydoc.register_caller
|
170
|
-
|
171
|
-
configurations[key] = Delegate.new(reader, writer, value, options)
|
167
|
+
configurations[key] = Delegate.new(reader, writer, value, attributes)
|
172
168
|
end
|
173
169
|
|
174
170
|
# Adds a configuration to self accessing the configurations for the
|
@@ -197,10 +193,9 @@ module Configurable
|
|
197
193
|
# b = B.new
|
198
194
|
# b.config[:a] # => {:key => 'value'}
|
199
195
|
#
|
200
|
-
# Nest may be provided a block which
|
201
|
-
#
|
202
|
-
#
|
203
|
-
# created and access becomes quite natural.
|
196
|
+
# Nest may be provided a block which initializes an instance of
|
197
|
+
# configurable_class. In this case accessors for the instance
|
198
|
+
# are created and access becomes quite natural.
|
204
199
|
#
|
205
200
|
# class C
|
206
201
|
# include Configurable
|
@@ -222,33 +217,114 @@ module Configurable
|
|
222
217
|
#
|
223
218
|
# c.config[:a] = {:key => 'three'}
|
224
219
|
# c.a.key # => "three"
|
225
|
-
#
|
226
|
-
# Nesting with an initialization block creates private methods
|
227
|
-
# that config[:a] uses to read and write the instance configurations;
|
228
|
-
# these methods are "#{key}_config" and "#{key}_config=" by default,
|
229
|
-
# but they may be renamed using the :reader and :writer options.
|
230
220
|
#
|
231
|
-
#
|
232
|
-
#
|
221
|
+
# The initialize block executes in class context, much like config.
|
222
|
+
#
|
223
|
+
# # An equivalent class to illustrate class-context
|
224
|
+
# class EquivalentClass
|
225
|
+
# attr_reader :a, A
|
226
|
+
#
|
227
|
+
# INITIALIZE_BLOCK = lambda {|overrides| A.new(overrides) }
|
228
|
+
#
|
229
|
+
# def initialize(overrides={})
|
230
|
+
# @a = INITIALIZE_BLOCK.call(overrides[:a] || {})
|
231
|
+
# end
|
232
|
+
# end
|
233
|
+
#
|
234
|
+
# Nest checks for recursive nesting and raises an error if a recursive nest
|
235
|
+
# is detected.
|
236
|
+
#
|
237
|
+
# ==== Attributes
|
238
|
+
#
|
239
|
+
# Nesting with an initialization block creates the public accessor for the
|
240
|
+
# instance, private methods to read and write the instance configurations,
|
241
|
+
# and a private method to initialize the instance. The default names
|
242
|
+
# for these methods are listed with the attributes to override them:
|
243
|
+
#
|
244
|
+
# :instance_reader key
|
245
|
+
# :instance_writer "#{key}="
|
246
|
+
# :instance_initializer "#{key}_initialize"
|
247
|
+
# :reader "#{key}_config_reader"
|
248
|
+
# :writer "#{key}_config_writer"
|
249
|
+
#
|
250
|
+
# These attributes are ignored if no block is given; true/false/nil
|
251
|
+
# values are meaningless and will be treated as the default.
|
252
|
+
#
|
253
|
+
def nest(key, configurable_class, attributes={}, &block)
|
254
|
+
attributes = merge_attributes(block, attributes)
|
255
|
+
|
256
|
+
if block_given?
|
257
|
+
instance_variable = "@#{key}".to_sym
|
258
|
+
nest_attr(key, configurable_class, attributes) do |input|
|
259
|
+
instance_variable_set(instance_variable, yield(input))
|
260
|
+
end
|
261
|
+
else
|
262
|
+
nest_attr(key, configurable_class, attributes)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# Same as nest, except the initialize block executes in instance-context.
|
267
|
+
#
|
268
|
+
# class C
|
269
|
+
# include Configurable
|
270
|
+
# nest(:a, A) {|overrides| A.new(overrides) }
|
271
|
+
#
|
272
|
+
# def initialize(overrides={})
|
273
|
+
# initialize_config(overrides)
|
274
|
+
# end
|
275
|
+
# end
|
233
276
|
#
|
234
|
-
|
277
|
+
# # An equivalent class to illustrate instance-context
|
278
|
+
# class EquivalentClass
|
279
|
+
# attr_reader :a, A
|
280
|
+
#
|
281
|
+
# def a_initialize(overrides)
|
282
|
+
# A.new(overrides)
|
283
|
+
# end
|
284
|
+
#
|
285
|
+
# def initialize(overrides={})
|
286
|
+
# @a = send(:a_initialize, overrides[:a] || {})
|
287
|
+
# end
|
288
|
+
# end
|
289
|
+
#
|
290
|
+
def nest_attr(key, configurable_class, attributes={}, &block)
|
235
291
|
unless configurable_class.kind_of?(Configurable::ClassMethods)
|
236
292
|
raise ArgumentError, "not a Configurable class: #{configurable_class}"
|
237
293
|
end
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
294
|
+
|
295
|
+
attributes = merge_attributes(block, attributes)
|
296
|
+
|
297
|
+
# add some tracking attributes
|
298
|
+
attributes[:receiver] ||= configurable_class
|
299
|
+
|
300
|
+
# remove method attributes
|
301
|
+
instance_reader = attributes.delete(:instance_reader)
|
302
|
+
instance_writer = attributes.delete(:instance_writer)
|
303
|
+
initializer = attributes.delete(:instance_initializer)
|
304
|
+
reader = attributes.delete(:reader)
|
305
|
+
writer = attributes.delete(:writer)
|
306
|
+
|
242
307
|
if block_given?
|
243
308
|
# define instance accessor methods
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
309
|
+
instance_reader = boolean_select(instance_reader, key)
|
310
|
+
instance_writer = boolean_select(instance_writer, "#{key}=")
|
311
|
+
instance_var = "@#{instance_reader}".to_sym
|
312
|
+
|
313
|
+
initializer = boolean_select(reader, "#{key}_initialize")
|
314
|
+
reader = boolean_select(reader, "#{key}_config_reader")
|
315
|
+
writer = boolean_select(writer, "#{key}_config_writer")
|
316
|
+
|
248
317
|
# the public accessor
|
249
|
-
attr_reader
|
250
|
-
|
251
|
-
|
318
|
+
attr_reader instance_reader
|
319
|
+
|
320
|
+
define_method(instance_writer) do |value|
|
321
|
+
instance_variable_set(instance_var, value)
|
322
|
+
end
|
323
|
+
public(instance_reader, instance_writer)
|
324
|
+
|
325
|
+
# the initializer
|
326
|
+
define_method(initializer, &block)
|
327
|
+
|
252
328
|
# the reader returns the config for the instance
|
253
329
|
define_method(reader) do
|
254
330
|
instance_variable_get(instance_var).config
|
@@ -260,19 +336,16 @@ module Configurable
|
|
260
336
|
if instance_variable_defined?(instance_var)
|
261
337
|
instance_variable_get(instance_var).reconfigure(value)
|
262
338
|
else
|
263
|
-
instance_variable_set(instance_var,
|
339
|
+
instance_variable_set(instance_var, send(initializer, value))
|
264
340
|
end
|
265
341
|
end
|
266
342
|
private(reader, writer)
|
267
343
|
else
|
268
344
|
reader = writer = nil
|
269
345
|
end
|
270
|
-
|
271
|
-
# register with Lazydoc
|
272
|
-
options[:desc] ||= Lazydoc.register_caller
|
273
346
|
|
274
|
-
value = DelegateHash.new(configurable_class.configurations)
|
275
|
-
configurations[key] = Delegate.new(reader, writer, value,
|
347
|
+
value = DelegateHash.new(configurable_class.configurations)
|
348
|
+
configurations[key] = Delegate.new(reader, writer, value, attributes)
|
276
349
|
|
277
350
|
check_infinite_nest(configurable_class.configurations)
|
278
351
|
end
|
@@ -284,25 +357,97 @@ module Configurable
|
|
284
357
|
|
285
358
|
private
|
286
359
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
360
|
+
# a helper to select a value or the default, if the default is true,
|
361
|
+
# false, or nil. used by nest_attr to handle attributes
|
362
|
+
def boolean_select(value, default) # :nodoc:
|
363
|
+
case value
|
364
|
+
when true, false, nil then default
|
365
|
+
else value
|
366
|
+
end
|
292
367
|
end
|
293
368
|
|
294
|
-
# helper to
|
295
|
-
#
|
296
|
-
def
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
369
|
+
# a helper to initialize configurations for the first time,
|
370
|
+
# mainly implemented as a hook for OrderedHashPatch
|
371
|
+
def initialize_configurations # :nodoc:
|
372
|
+
@configurations ||= {}
|
373
|
+
end
|
374
|
+
|
375
|
+
# a helper method to merge the default attributes for the block with
|
376
|
+
# the input attributes. also registers a Trailer description.
|
377
|
+
def merge_attributes(block, attributes) # :nodoc:
|
378
|
+
defaults = DEFAULT_ATTRIBUTES[nil].dup
|
379
|
+
defaults.merge!(DEFAULT_ATTRIBUTES[block]) if block
|
380
|
+
defaults.merge!(attributes)
|
381
|
+
|
382
|
+
# register with Lazydoc
|
383
|
+
defaults[:desc] ||= Lazydoc.register_caller(Lazydoc::Trailer, 2)
|
384
|
+
|
385
|
+
defaults
|
386
|
+
end
|
301
387
|
|
302
|
-
|
303
|
-
|
388
|
+
# helper to recursively check for an infinite nest
|
389
|
+
def check_infinite_nest(delegates) # :nodoc:
|
390
|
+
raise "infinite nest detected" if delegates == self.configurations
|
391
|
+
|
392
|
+
delegates.each_pair do |key, delegate|
|
393
|
+
if delegate.is_nest?
|
394
|
+
check_infinite_nest(delegate.default(false).delegates)
|
304
395
|
end
|
305
396
|
end
|
306
397
|
end
|
307
398
|
end
|
308
|
-
end
|
399
|
+
end
|
400
|
+
|
401
|
+
module Configurable
|
402
|
+
|
403
|
+
# Beginning with ruby 1.9, Hash tracks the order of insertion and methods
|
404
|
+
# like each_pair return pairs in order. Configurable leverages this feature
|
405
|
+
# to keep configurations in order for the command line documentation produced
|
406
|
+
# by ConfigParser.
|
407
|
+
#
|
408
|
+
# Pre-1.9 ruby implementations require a patched Hash that tracks insertion
|
409
|
+
# order. This very thin subclass of hash does that for ASET insertions and
|
410
|
+
# each_pair. OrderedHashPatches are used as the configurations object in
|
411
|
+
# Configurable classes for pre-1.9 ruby implementations and for nothing else.
|
412
|
+
class OrderedHashPatch < Hash
|
413
|
+
def initialize
|
414
|
+
super
|
415
|
+
@insertion_order = []
|
416
|
+
end
|
417
|
+
|
418
|
+
# ASET insertion, tracking insertion order.
|
419
|
+
def []=(key, value)
|
420
|
+
@insertion_order << key unless @insertion_order.include?(key)
|
421
|
+
super
|
422
|
+
end
|
423
|
+
|
424
|
+
# Keys, sorted into insertion order
|
425
|
+
def keys
|
426
|
+
super.sort_by do |key|
|
427
|
+
@insertion_order.index(key) || length
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
# Yields each key-value pair to the block in insertion order.
|
432
|
+
def each_pair
|
433
|
+
keys.each do |key|
|
434
|
+
yield(key, fetch(key))
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
# Ensures the insertion order of duplicates is separate from parents.
|
439
|
+
def initialize_copy(orig)
|
440
|
+
super
|
441
|
+
@insertion_order = orig.instance_variable_get(:@insertion_order).dup
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
module ClassMethods
|
446
|
+
undef_method :initialize_configurations
|
447
|
+
|
448
|
+
# applies OrderedHashPatch
|
449
|
+
def initialize_configurations # :nodoc:
|
450
|
+
@configurations ||= OrderedHashPatch.new
|
451
|
+
end
|
452
|
+
end
|
453
|
+
end if RUBY_VERSION < '1.9'
|
@@ -22,9 +22,9 @@ module Configurable
|
|
22
22
|
# The writer method, by default key=
|
23
23
|
attr_reader :writer
|
24
24
|
|
25
|
-
# An
|
25
|
+
# An hash of metadata for self, used to present the
|
26
26
|
# delegate in different contexts (ex on the command
|
27
|
-
# line or
|
27
|
+
# line, in a web form, or a desktop app).
|
28
28
|
attr_reader :attributes
|
29
29
|
|
30
30
|
# Initializes a new Delegate with the specified key
|
@@ -36,6 +36,17 @@ module Configurable
|
|
36
36
|
|
37
37
|
@attributes = attributes
|
38
38
|
end
|
39
|
+
|
40
|
+
# Sets the value of an attribute.
|
41
|
+
def []=(key, value)
|
42
|
+
attributes[key] = value
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns the value for the specified attribute, or
|
46
|
+
# default, if the attribute is unspecified.
|
47
|
+
def [](key, default=nil)
|
48
|
+
attributes.has_key?(key) ? attributes[key] : default
|
49
|
+
end
|
39
50
|
|
40
51
|
# Sets the default value for self.
|
41
52
|
def default=(value)
|
@@ -61,6 +72,11 @@ module Configurable
|
|
61
72
|
def writer=(value)
|
62
73
|
@writer = value == nil ? value : value.to_sym
|
63
74
|
end
|
75
|
+
|
76
|
+
# Returns true if the default value is a kind of DelegateHash.
|
77
|
+
def is_nest?
|
78
|
+
@default.kind_of?(DelegateHash)
|
79
|
+
end
|
64
80
|
|
65
81
|
# True if another is a kind of Delegate with the same
|
66
82
|
# reader, writer, and default value. Attributes are
|