configurable 0.7.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Help/Command Line.rdoc +141 -0
- data/Help/Config Syntax.rdoc +229 -0
- data/Help/Config Types.rdoc +143 -0
- data/{History → History.rdoc} +9 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +144 -0
- data/lib/configurable.rb +7 -270
- data/lib/configurable/class_methods.rb +344 -367
- data/lib/configurable/config_classes.rb +3 -0
- data/lib/configurable/config_classes/list_config.rb +26 -0
- data/lib/configurable/config_classes/nest_config.rb +50 -0
- data/lib/configurable/config_classes/scalar_config.rb +91 -0
- data/lib/configurable/config_hash.rb +87 -112
- data/lib/configurable/config_types.rb +6 -0
- data/lib/configurable/config_types/boolean_type.rb +22 -0
- data/lib/configurable/config_types/float_type.rb +11 -0
- data/lib/configurable/config_types/integer_type.rb +11 -0
- data/lib/configurable/config_types/nest_type.rb +39 -0
- data/lib/configurable/config_types/object_type.rb +58 -0
- data/lib/configurable/config_types/string_type.rb +15 -0
- data/lib/configurable/conversions.rb +91 -0
- data/lib/configurable/module_methods.rb +0 -1
- data/lib/configurable/version.rb +1 -5
- metadata +73 -30
- data/README +0 -207
- data/lib/cdoc.rb +0 -413
- data/lib/cdoc/cdoc_html_generator.rb +0 -38
- data/lib/cdoc/cdoc_html_template.rb +0 -42
- data/lib/config_parser.rb +0 -563
- data/lib/config_parser/option.rb +0 -108
- data/lib/config_parser/switch.rb +0 -44
- data/lib/config_parser/utils.rb +0 -177
- data/lib/configurable/config.rb +0 -97
- data/lib/configurable/indifferent_access.rb +0 -35
- data/lib/configurable/nest_config.rb +0 -78
- data/lib/configurable/ordered_hash_patch.rb +0 -85
- data/lib/configurable/utils.rb +0 -186
- data/lib/configurable/validation.rb +0 -768
@@ -0,0 +1,26 @@
|
|
1
|
+
module Configurable
|
2
|
+
module ConfigClasses
|
3
|
+
class ListConfig < ScalarConfig
|
4
|
+
|
5
|
+
def initialize(key, attrs={})
|
6
|
+
unless attrs.has_key?(:default)
|
7
|
+
attrs[:default] = []
|
8
|
+
end
|
9
|
+
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def cast(values)
|
14
|
+
results = []
|
15
|
+
values.each {|value| results << super(value) }
|
16
|
+
results
|
17
|
+
end
|
18
|
+
|
19
|
+
def uncast(values)
|
20
|
+
results = []
|
21
|
+
values.each {|value| results << super(value) }
|
22
|
+
results
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Configurable
|
2
|
+
module ConfigClasses
|
3
|
+
|
4
|
+
# Represents a config where the input is expected to be Configurable.
|
5
|
+
class NestConfig < ScalarConfig
|
6
|
+
|
7
|
+
def initialize(key, attrs={})
|
8
|
+
unless attrs.has_key?(:default)
|
9
|
+
attrs[:default] = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
unless attrs.has_key?(:type)
|
13
|
+
attrs[:type] = NestType.new(attrs)
|
14
|
+
end
|
15
|
+
|
16
|
+
super
|
17
|
+
|
18
|
+
unless type.respond_to?(:init)
|
19
|
+
raise "invalid type for #{self}: #{type.inspect}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Calls the reader on the reciever to retreive an instance of the
|
24
|
+
# configurable_class and returns it's config. Returns nil if the reader
|
25
|
+
# returns nil.
|
26
|
+
def get(receiver)
|
27
|
+
if configurable = receiver.send(reader)
|
28
|
+
configurable.config
|
29
|
+
else
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Calls the reader on the reciever to retrieve an instance of the
|
35
|
+
# configurable_class, and reconfigures it with value. The instance will
|
36
|
+
# be initialized by init if necessary.
|
37
|
+
#
|
38
|
+
# If value is an instance of the configurable_class, then it will be set
|
39
|
+
# by calling writer.
|
40
|
+
def set(receiver, value)
|
41
|
+
unless value.respond_to?(:config)
|
42
|
+
value = default.merge(value)
|
43
|
+
value = type.init(value)
|
44
|
+
end
|
45
|
+
|
46
|
+
receiver.send(writer, value)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'configurable/config_types'
|
2
|
+
|
3
|
+
module Configurable
|
4
|
+
module ConfigClasses
|
5
|
+
# ConfigClasses are used by ConfigHash to delegate get/set configs on a
|
6
|
+
# receiver and to map configs between user interfaces.
|
7
|
+
class ScalarConfig
|
8
|
+
include ConfigTypes
|
9
|
+
|
10
|
+
# The config key, used as a hash key for access.
|
11
|
+
attr_reader :key
|
12
|
+
|
13
|
+
# The config name used in interfaces where only word-based names are
|
14
|
+
# appropriate. Names are strings consisting of only word characters.
|
15
|
+
attr_reader :name
|
16
|
+
|
17
|
+
# The reader method called on a receiver during get.
|
18
|
+
attr_reader :reader
|
19
|
+
|
20
|
+
# The writer method called on a receiver during set.
|
21
|
+
attr_reader :writer
|
22
|
+
|
23
|
+
# The default config value.
|
24
|
+
attr_reader :default
|
25
|
+
|
26
|
+
# The config type for self (defaults to an ObjectType)
|
27
|
+
attr_reader :type
|
28
|
+
|
29
|
+
# A hash of information used to render self in various contexts.
|
30
|
+
attr_reader :metadata
|
31
|
+
|
32
|
+
# Initializes a new Config. Specify attributes like default, reader,
|
33
|
+
# writer, type, etc. within attrs.
|
34
|
+
def initialize(key, attrs={})
|
35
|
+
@key = key
|
36
|
+
@name = attrs[:name] || @key.to_s
|
37
|
+
check_name(@name)
|
38
|
+
|
39
|
+
@default = attrs[:default]
|
40
|
+
@type = attrs[:type] || ObjectType.new
|
41
|
+
check_default(@default)
|
42
|
+
|
43
|
+
@reader = (attrs[:reader] || name).to_sym
|
44
|
+
@writer = (attrs[:writer] || "#{name}=").to_sym
|
45
|
+
@metadata = attrs[:metadata] || {}
|
46
|
+
end
|
47
|
+
|
48
|
+
def [](key)
|
49
|
+
metadata[key]
|
50
|
+
end
|
51
|
+
|
52
|
+
# Calls reader on the receiver and returns the result.
|
53
|
+
def get(receiver)
|
54
|
+
receiver.send(reader)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Calls writer on the receiver with the value.
|
58
|
+
def set(receiver, value)
|
59
|
+
receiver.send(writer, value)
|
60
|
+
end
|
61
|
+
|
62
|
+
def cast(input)
|
63
|
+
type.cast(input)
|
64
|
+
end
|
65
|
+
|
66
|
+
def uncast(value)
|
67
|
+
type.uncast(value)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns an inspect string.
|
71
|
+
def inspect
|
72
|
+
"#<#{self.class}:#{object_id} key=#{key} name=#{name} default=#{default.inspect} reader=#{reader} writer=#{writer} >"
|
73
|
+
end
|
74
|
+
|
75
|
+
protected
|
76
|
+
|
77
|
+
def check_name(name) # :nodoc
|
78
|
+
unless name.kind_of?(String)
|
79
|
+
raise "invalid name: #{name.inspect} (not a String)"
|
80
|
+
end
|
81
|
+
|
82
|
+
unless name =~ /\A\w+\z/
|
83
|
+
raise NameError.new("invalid name: #{name.inspect} (includes non-word characters)")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def check_default(default) # :nodoc:
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -1,110 +1,72 @@
|
|
1
|
-
require 'configurable/nest_config'
|
2
|
-
|
3
1
|
module Configurable
|
4
2
|
|
5
3
|
# ConfigHash acts like a hash that maps get and set operations as specified
|
6
|
-
# in a
|
7
|
-
#
|
8
|
-
# class Sample
|
9
|
-
# include Configurable
|
10
|
-
# config :key
|
11
|
-
# end
|
12
|
-
#
|
13
|
-
# sample = Sample.new
|
14
|
-
# sample.config.class # => ConfigHash
|
15
|
-
#
|
16
|
-
# sample.key = 'value'
|
17
|
-
# sample.config[:key] # => 'value'
|
18
|
-
#
|
19
|
-
# sample.config[:key] = 'another'
|
20
|
-
# sample.key # => 'another'
|
21
|
-
#
|
22
|
-
# Non-configuration keys are sent to an underlying data store:
|
23
|
-
#
|
24
|
-
# sample.config[:not_delegated] = 'value'
|
25
|
-
# sample.config[:not_delegated] # => 'value'
|
26
|
-
#
|
27
|
-
# sample.config.store # => {:not_delegated => 'value'}
|
28
|
-
# sample.config.to_hash # => {:key => 'another', :not_delegated => 'value'}
|
29
|
-
#
|
30
|
-
# ==== IndifferentAccess
|
31
|
-
#
|
32
|
-
# A ConfigHash uses the receiver class configurations to determine when and
|
33
|
-
# how to map get/set operations. In cases where multiple keys need to map
|
34
|
-
# in the same way (for example when you want indifferent access for strings
|
35
|
-
# and symbols), simply extend the class configurations so that the AGET ([])
|
36
|
-
# method returns the correct Config in all cases.
|
37
|
-
#
|
38
|
-
# ==== Inconsistency
|
39
|
-
#
|
40
|
-
# ConfigHashes can fall into an inconsistent state if you manually add values
|
41
|
-
# to store that would normally be mapped to the receiver. This is both easy
|
42
|
-
# to avoid and easy to repair.
|
43
|
-
#
|
44
|
-
# To avoid inconsistency, don't manually add values to the store and set
|
45
|
-
# import_store to true during initialization. To repair inconsistency,
|
46
|
-
# import the current store to self.
|
47
|
-
#
|
48
|
-
# config_hash = Sample.new.config
|
49
|
-
# config_hash[:key] = 'a'
|
50
|
-
# config_hash.store[:key] = 'b'
|
51
|
-
#
|
52
|
-
# config_hash[:key] # => 'a'
|
53
|
-
# config_hash.to_hash # => {:key => 'b'}
|
54
|
-
# config_hash.inconsistent? # => true
|
55
|
-
#
|
56
|
-
# config_hash.import(config_hash.store)
|
57
|
-
#
|
58
|
-
# config_hash[:key] # => 'b'
|
59
|
-
# config_hash.to_hash # => {:key => 'b'}
|
60
|
-
# config_hash.inconsistent? # => false
|
61
|
-
#
|
4
|
+
# in by a receiver's class configurations. Non-configuration keys are sent
|
5
|
+
# to an underlying data store.
|
62
6
|
class ConfigHash
|
63
|
-
|
7
|
+
# A frozen empty hash returned by configs for unbound config hashes.
|
8
|
+
EMPTY_HASH = {}.freeze
|
9
|
+
|
64
10
|
# The bound receiver
|
65
11
|
attr_reader :receiver
|
66
|
-
|
67
|
-
# The underlying data store; setting values in store directly
|
68
|
-
#
|
12
|
+
|
13
|
+
# The underlying data store; setting values in store directly can result
|
14
|
+
# in an inconsistent state. Use []= instead.
|
69
15
|
attr_reader :store
|
70
|
-
|
71
|
-
# Initializes a new ConfigHash.
|
72
|
-
|
73
|
-
# receiver.
|
74
|
-
#
|
75
|
-
# Setting import_store to false allows quick initialization but can result
|
76
|
-
# in an inconsistent state.
|
77
|
-
def initialize(receiver, store={}, import_store=true)
|
78
|
-
@receiver = receiver
|
16
|
+
|
17
|
+
# Initializes a new ConfigHash.
|
18
|
+
def initialize(store={}, receiver=nil)
|
79
19
|
@store = store
|
80
|
-
|
81
|
-
import(store) if import_store
|
82
|
-
end
|
83
|
-
|
84
|
-
# Returns receiver.class.configurations.
|
85
|
-
def configs
|
86
|
-
receiver.class.configurations
|
20
|
+
@receiver = receiver
|
87
21
|
end
|
88
22
|
|
89
|
-
#
|
90
|
-
# are removed from store
|
23
|
+
# Binds the configs in store to the receiver by setting each on the
|
24
|
+
# receiver (via config.set). Bound configs are removed from store.
|
91
25
|
#
|
92
|
-
#
|
93
|
-
def
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
26
|
+
# Unbinds self from the current receiver, if needed.
|
27
|
+
def bind(receiver)
|
28
|
+
unbind if bound?
|
29
|
+
|
30
|
+
@receiver = receiver
|
31
|
+
configs.each_pair do |key, config|
|
32
|
+
value = store.has_key?(key) ? store.delete(key) : config.default
|
33
|
+
config.set(receiver, value)
|
98
34
|
end
|
99
35
|
|
100
36
|
self
|
101
37
|
end
|
102
38
|
|
103
|
-
#
|
104
|
-
#
|
105
|
-
|
106
|
-
|
107
|
-
|
39
|
+
# Unbinds the configs set on receiver by getting each value (via
|
40
|
+
# config.get) and setting the result into store. Does nothing if no
|
41
|
+
# receiver is set.
|
42
|
+
def unbind
|
43
|
+
if bound?
|
44
|
+
configs.each_pair do |key, config|
|
45
|
+
store[key] = config.get(receiver)
|
46
|
+
end
|
47
|
+
@receiver = nil
|
48
|
+
end
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns true if bound to a receiver.
|
53
|
+
def bound?
|
54
|
+
@receiver ? true : false
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns the class configs for the bound receiver, or an empty hash if
|
58
|
+
# unbound (specifically EMPTY_HASH).
|
59
|
+
def configs
|
60
|
+
# Caching here is not necessary or preferred as configurations are
|
61
|
+
# cached on the class (which allows late inclusion of configurable
|
62
|
+
# modules to work properly).
|
63
|
+
bound? ? receiver.class.configs : EMPTY_HASH
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns true if bound to a receiver and no configs values are set in
|
67
|
+
# store (ie all config values are stored on the receiver).
|
68
|
+
def consistent?
|
69
|
+
bound? && (store.keys & configs.keys).empty?
|
108
70
|
end
|
109
71
|
|
110
72
|
# Retrieves the value for the key, either from the receiver or the store.
|
@@ -129,26 +91,23 @@ module Configurable
|
|
129
91
|
def keys
|
130
92
|
configs.keys | store.keys
|
131
93
|
end
|
132
|
-
|
94
|
+
|
133
95
|
# True if the key is a key in configs or store.
|
134
96
|
def has_key?(key)
|
135
|
-
configs
|
97
|
+
configs.has_key?(key) || store.has_key?(key)
|
136
98
|
end
|
137
99
|
|
138
100
|
# Merges another with self.
|
139
101
|
def merge!(another)
|
140
|
-
# cache configs and inline set as a significant optimization
|
141
102
|
configs = self.configs
|
142
|
-
|
143
|
-
next unless another.has_key?(key)
|
144
|
-
|
145
|
-
value = another[key]
|
103
|
+
another.each_pair do |key, value|
|
146
104
|
if config = configs[key]
|
147
105
|
config.set(receiver, value)
|
148
106
|
else
|
149
107
|
store[key] = value
|
150
108
|
end
|
151
109
|
end
|
110
|
+
self
|
152
111
|
end
|
153
112
|
|
154
113
|
# Calls block once for each key-value pair stored in self.
|
@@ -166,30 +125,46 @@ module Configurable
|
|
166
125
|
def ==(another)
|
167
126
|
another.respond_to?(:to_hash) && to_hash == another.to_hash
|
168
127
|
end
|
169
|
-
|
128
|
+
|
170
129
|
# Returns self as a hash. Any ConfigHash values are recursively
|
171
130
|
# hashified, to account for nesting.
|
172
|
-
def to_hash
|
131
|
+
def to_hash
|
173
132
|
hash = {}
|
174
133
|
each_pair do |key, value|
|
175
134
|
if value.kind_of?(ConfigHash)
|
176
|
-
value = value.to_hash
|
177
|
-
end
|
178
|
-
|
179
|
-
if scrub
|
180
|
-
config = configs[key]
|
181
|
-
next if config && config.default == value
|
135
|
+
value = value.to_hash
|
182
136
|
end
|
183
137
|
|
184
|
-
|
185
|
-
yield(hash, key, value)
|
186
|
-
else
|
187
|
-
hash[key] = value
|
188
|
-
end
|
138
|
+
hash[key] = value
|
189
139
|
end
|
190
140
|
hash
|
191
141
|
end
|
192
|
-
|
142
|
+
|
143
|
+
# Returns self exported as a hash of raw configs (ie name keys and uncast
|
144
|
+
# values).
|
145
|
+
def export
|
146
|
+
configs.export(to_hash)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Imports a hash of raw configs (ie name keys and uncast values) and
|
150
|
+
# merges the result with self.
|
151
|
+
def import(another)
|
152
|
+
merge! configs.import(another)
|
153
|
+
end
|
154
|
+
|
155
|
+
def parse(argv=ARGV, options={}, &block)
|
156
|
+
parse!(argv.dup, options, &block)
|
157
|
+
end
|
158
|
+
|
159
|
+
def parse!(argv=ARGV, options={}, &block)
|
160
|
+
parser(options, &block).parse!(argv)
|
161
|
+
end
|
162
|
+
|
163
|
+
def parser(options={}, &block)
|
164
|
+
options = {:assign_defaults => false}.merge(options)
|
165
|
+
configs.to_parser(self, options, &block)
|
166
|
+
end
|
167
|
+
|
193
168
|
# Returns an inspection string.
|
194
169
|
def inspect
|
195
170
|
"#<#{self.class}:#{object_id} to_hash=#{to_hash.inspect}>"
|
@@ -0,0 +1,6 @@
|
|
1
|
+
require 'configurable/config_types/object_type'
|
2
|
+
require 'configurable/config_types/string_type'
|
3
|
+
require 'configurable/config_types/boolean_type'
|
4
|
+
require 'configurable/config_types/integer_type'
|
5
|
+
require 'configurable/config_types/float_type'
|
6
|
+
require 'configurable/config_types/nest_type'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Configurable
|
2
|
+
module ConfigTypes
|
3
|
+
class BooleanType < ObjectType
|
4
|
+
matches TrueClass, FalseClass
|
5
|
+
|
6
|
+
# Casts the input to a boolean ie:
|
7
|
+
#
|
8
|
+
# true, 'true' => true
|
9
|
+
# false, 'false => false
|
10
|
+
#
|
11
|
+
# All other inputs raise an ArgumentError.
|
12
|
+
def cast(input)
|
13
|
+
case input
|
14
|
+
when true, false then input
|
15
|
+
when 'true' then true
|
16
|
+
when 'false' then false
|
17
|
+
else raise ArgumentError, "invalid value for boolean: #{input.inspect}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|