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.
- 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
|