config_mapper 1.0.0 → 1.1.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.
- checksums.yaml +4 -4
- data/Gemfile +5 -0
- data/Guardfile +43 -0
- data/README.md +101 -10
- data/lib/config_mapper/config_struct.rb +189 -0
- data/lib/config_mapper/hash_target.rb +27 -0
- data/lib/config_mapper/object_target.rb +27 -0
- data/lib/config_mapper/target.rb +39 -0
- data/lib/config_mapper/version.rb +1 -1
- data/lib/config_mapper.rb +30 -14
- metadata +8 -6
- data/lib/config_mapper/attribute_sink.rb +0 -42
- data/lib/config_mapper/error_proxy.rb +0 -23
- data/lib/config_mapper/object_as_hash.rb +0 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b3eceb23aa75978730fec0b82c9a01185852d056
|
4
|
+
data.tar.gz: 155083970129df5b800a6085957a21c98e869f1e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4c959618d4f898cc3e49c3512042c043b5b5a4b26cb7a9287e6620832fc53be5de77950fc7e7d551695c203ef8a8235a361e20db5f5d557bad0834f2e63075c1
|
7
|
+
data.tar.gz: 128a784d48f8933c27c83d55e754889b43f9eb6e61451ca6a4e23162e88175a6b53ad8e1a8cb27f156bac796767e6715d08b07c82f632b50433825b172842837
|
data/Gemfile
CHANGED
data/Guardfile
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
## Uncomment and set this to only include directories you want to watch
|
5
|
+
# directories %w(app lib config test spec features) \
|
6
|
+
# .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
|
7
|
+
|
8
|
+
## Note: if you are using the `directories` clause above and you are not
|
9
|
+
## watching the project directory ('.'), then you will want to move
|
10
|
+
## the Guardfile to a watched dir and symlink it back, e.g.
|
11
|
+
#
|
12
|
+
# $ mkdir config
|
13
|
+
# $ mv Guardfile config/
|
14
|
+
# $ ln -s config/Guardfile .
|
15
|
+
#
|
16
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
17
|
+
|
18
|
+
# Note: The cmd option is now required due to the increasing number of ways
|
19
|
+
# rspec may be run, below are examples of the most common uses.
|
20
|
+
# * bundler: 'bundle exec rspec'
|
21
|
+
# * bundler binstubs: 'bin/rspec'
|
22
|
+
# * spring: 'bin/rspec' (This will use spring if running and you have
|
23
|
+
# installed the spring binstubs per the docs)
|
24
|
+
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
25
|
+
# * 'just' rspec: 'rspec'
|
26
|
+
|
27
|
+
guard :rspec, :cmd => "bundle exec rspec" do
|
28
|
+
require "guard/rspec/dsl"
|
29
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
30
|
+
|
31
|
+
# Feel free to open issues for suggestions and improvements
|
32
|
+
|
33
|
+
# RSpec files
|
34
|
+
rspec = dsl.rspec
|
35
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
36
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
37
|
+
watch(rspec.spec_files)
|
38
|
+
|
39
|
+
# Ruby files
|
40
|
+
ruby = dsl.ruby
|
41
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
42
|
+
|
43
|
+
end
|
data/README.md
CHANGED
@@ -20,7 +20,7 @@ end
|
|
20
20
|
class State
|
21
21
|
|
22
22
|
def initialize
|
23
|
-
|
23
|
+
@position = Position.new
|
24
24
|
end
|
25
25
|
|
26
26
|
attr_reader :position
|
@@ -37,8 +37,8 @@ and wish to populate/modify it, based on plain data:
|
|
37
37
|
config_data = {
|
38
38
|
"orientation" => "North",
|
39
39
|
"position" => {
|
40
|
-
|
41
|
-
|
40
|
+
"x" => 2,
|
41
|
+
"y" => 4
|
42
42
|
}
|
43
43
|
}
|
44
44
|
```
|
@@ -48,7 +48,7 @@ ConfigMapper will help you out:
|
|
48
48
|
```ruby
|
49
49
|
require 'config_mapper'
|
50
50
|
|
51
|
-
errors = ConfigMapper.
|
51
|
+
errors = ConfigMapper.configure_with(config_data, state)
|
52
52
|
state.orientation #=> "North"
|
53
53
|
state.position.x #=> 2
|
54
54
|
```
|
@@ -63,16 +63,38 @@ config_data = {
|
|
63
63
|
"mary" => { "x" => 3, "y" => 5 }
|
64
64
|
}
|
65
65
|
|
66
|
-
ConfigMapper.
|
66
|
+
ConfigMapper.configure_with(config_data, positions)
|
67
67
|
positions["fred"].x #=> 2
|
68
68
|
positions["mary"].y #=> 5
|
69
69
|
```
|
70
70
|
|
71
|
+
### Target object
|
72
|
+
|
73
|
+
Given
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
ConfigMapper.configure_with(config_data, target)
|
77
|
+
```
|
78
|
+
|
79
|
+
the `target` object is expected provide accessor-methods corresponding
|
80
|
+
to the attributes that you want to make configurable. For example, with:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
config_data = {
|
84
|
+
"orientation" => "North",
|
85
|
+
"position" => { "x" => 2, "y" => 4 }
|
86
|
+
}
|
87
|
+
```
|
88
|
+
|
89
|
+
it should have a `orientiation=` method, and a `position` method that
|
90
|
+
returns a `Position` object, which should in turn have `x=` and `y=`
|
91
|
+
methods.
|
92
|
+
|
93
|
+
ConfigMapper cannot and will not _create_ objects for you.
|
94
|
+
|
71
95
|
### Errors
|
72
96
|
|
73
|
-
`ConfigMapper.
|
74
|
-
onto objects. The errors are Exceptions (typically ArgumentError or NoMethodError),
|
75
|
-
keyed by a Array representing the path to the offending data. e.g.
|
97
|
+
`ConfigMapper.configure_with` returns a Hash of errors encountered while mapping data onto objects. The errors are Exceptions (typically ArgumentError or NoMethodError), keyed by the path to the offending data. e.g.
|
76
98
|
|
77
99
|
```ruby
|
78
100
|
config_data = {
|
@@ -81,8 +103,72 @@ config_data = {
|
|
81
103
|
}
|
82
104
|
}
|
83
105
|
|
84
|
-
errors = ConfigMapper.
|
85
|
-
errors #=> {
|
106
|
+
errors = ConfigMapper.configure_with(config_data, state)
|
107
|
+
errors #=> { ".position.bogus" => #<NoMethodError> }
|
108
|
+
```
|
109
|
+
|
110
|
+
## ConfigStruct
|
111
|
+
|
112
|
+
ConfigMapper works pretty well with plain old Ruby objects, but we
|
113
|
+
provide a base-class, `ConfigMapper::ConfigStruct`, with a DSL that
|
114
|
+
makes it even easier to declare configuration data-structures.
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
require "config_mapper/config_struct"
|
118
|
+
|
119
|
+
class State < ConfigMapper::ConfigStruct
|
120
|
+
|
121
|
+
component :position do
|
122
|
+
attribute(:x) { |arg| Integer(arg) }
|
123
|
+
attribute(:y) { |arg| Integer(arg) }
|
124
|
+
end
|
125
|
+
|
126
|
+
attribute :orientation
|
127
|
+
|
128
|
+
end
|
129
|
+
```
|
130
|
+
|
131
|
+
By default, declared attributes are assumed to be mandatory. The
|
132
|
+
`ConfigStruct#config_errors` method returns errors for each unset mandatory
|
133
|
+
attribute.
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
state = State.new
|
137
|
+
state.position.x = 3
|
138
|
+
state.position.y = 4
|
139
|
+
state.config_errors
|
140
|
+
#=> { ".orientation" => "no value provided" }
|
141
|
+
```
|
142
|
+
|
143
|
+
`#config_errors` can be overridden to provide custom semantic validation.
|
144
|
+
|
145
|
+
Attributes can be given default values. Provide an explicit `nil` default to
|
146
|
+
mark an attribute as optional, e.g.
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
class Address < ConfigMapper::ConfigStruct
|
150
|
+
|
151
|
+
attribute :host
|
152
|
+
attribute :port, :default => 80
|
153
|
+
attribute :path, :default => nil
|
154
|
+
|
155
|
+
end
|
156
|
+
```
|
157
|
+
|
158
|
+
`ConfigStruct#configure_with` maps data into the object, and combines mapping errors and
|
159
|
+
semantic errors (returned by `#config_errors`) into a single Hash:
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
data = {
|
163
|
+
"position" => { "x" => 3, "y" => "fore" },
|
164
|
+
"bogus" => "foobar"
|
165
|
+
}
|
166
|
+
state.configure_with(data)
|
167
|
+
#=> {
|
168
|
+
#=> ".orientation" => "no value provided",
|
169
|
+
#=> ".position.y" => #<ArgumentError: invalid value for Integer(): "fore">,
|
170
|
+
#=> ".bogus" => #<NoMethodError: undefined method `bogus=' for #<State:0x007fc8e9b12a60>>
|
171
|
+
#=> }
|
86
172
|
```
|
87
173
|
|
88
174
|
## License
|
@@ -92,3 +178,8 @@ The gem is available as open source under the terms of the [MIT License](http://
|
|
92
178
|
## Contributing
|
93
179
|
|
94
180
|
It's on GitHub; you know the drill.
|
181
|
+
|
182
|
+
## See also
|
183
|
+
|
184
|
+
* [ConfigHound](https://github.com/mdub/config_hound) is a great way to
|
185
|
+
load raw config-data, before throwing it to ConfigMapper.
|
@@ -0,0 +1,189 @@
|
|
1
|
+
require "config_mapper"
|
2
|
+
require "forwardable"
|
3
|
+
|
4
|
+
module ConfigMapper
|
5
|
+
|
6
|
+
# A set of configurable attributes.
|
7
|
+
#
|
8
|
+
class ConfigStruct
|
9
|
+
|
10
|
+
class << self
|
11
|
+
|
12
|
+
# Defines reader and writer methods for the specified attribute.
|
13
|
+
#
|
14
|
+
# A `:default` value may be specified; otherwise, the attribute is
|
15
|
+
# considered mandatory.
|
16
|
+
#
|
17
|
+
# If a block is provided, it will invoked in the writer-method to
|
18
|
+
# validate the argument.
|
19
|
+
#
|
20
|
+
# @param name [Symbol] attribute name
|
21
|
+
# @options options [String] :default (nil) default value
|
22
|
+
# @yield type-coercion block
|
23
|
+
#
|
24
|
+
def attribute(name, options = {}, &coerce_block)
|
25
|
+
name = name.to_sym
|
26
|
+
if options.key?(:default)
|
27
|
+
default_value = options.fetch(:default).freeze
|
28
|
+
attribute_initializers[name] = proc { default_value }
|
29
|
+
else
|
30
|
+
required_attributes << name
|
31
|
+
end
|
32
|
+
attr_reader(name)
|
33
|
+
if coerce_block
|
34
|
+
define_method("#{name}=") do |arg|
|
35
|
+
instance_variable_set("@#{name}", coerce_block.call(arg))
|
36
|
+
end
|
37
|
+
else
|
38
|
+
attr_writer(name)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Defines a sub-component.
|
43
|
+
#
|
44
|
+
# If a block is be provided, it will be `class_eval`ed to define the
|
45
|
+
# sub-components class.
|
46
|
+
#
|
47
|
+
# @param name [Symbol] component name
|
48
|
+
# @options options [String] :type (ConfigMapper::ConfigStruct)
|
49
|
+
# component base-class
|
50
|
+
#
|
51
|
+
def component(name, options = {}, &block)
|
52
|
+
name = name.to_sym
|
53
|
+
declared_components << name
|
54
|
+
type = options.fetch(:type, ConfigStruct)
|
55
|
+
type = Class.new(type, &block) if block
|
56
|
+
type = type.method(:new) if type.respond_to?(:new)
|
57
|
+
attribute_initializers[name] = type
|
58
|
+
attr_reader name
|
59
|
+
end
|
60
|
+
|
61
|
+
# Defines an associative array of sub-components.
|
62
|
+
#
|
63
|
+
# If a block is be provided, it will be `class_eval`ed to define the
|
64
|
+
# sub-components class.
|
65
|
+
#
|
66
|
+
# @param name [Symbol] dictionary attribute name
|
67
|
+
# @options options [Proc] :key_type
|
68
|
+
# function used to validate keys
|
69
|
+
# @options options [String] :type (ConfigMapper::ConfigStruct)
|
70
|
+
# base-class for sub-component values
|
71
|
+
#
|
72
|
+
def component_dict(name, options = {}, &block)
|
73
|
+
name = name.to_sym
|
74
|
+
declared_component_dicts << name
|
75
|
+
type = options.fetch(:type, ConfigStruct)
|
76
|
+
type = Class.new(type, &block) if block
|
77
|
+
type = type.method(:new) if type.respond_to?(:new)
|
78
|
+
key_type = options[:key_type]
|
79
|
+
key_type = key_type.method(:new) if key_type.respond_to?(:new)
|
80
|
+
attribute_initializers[name] = lambda do
|
81
|
+
ConfigDict.new(type, key_type)
|
82
|
+
end
|
83
|
+
attr_reader name
|
84
|
+
end
|
85
|
+
|
86
|
+
def required_attributes
|
87
|
+
@required_attributes ||= []
|
88
|
+
end
|
89
|
+
|
90
|
+
def attribute_initializers
|
91
|
+
@attribute_initializers ||= {}
|
92
|
+
end
|
93
|
+
|
94
|
+
def declared_components
|
95
|
+
@declared_components ||= []
|
96
|
+
end
|
97
|
+
|
98
|
+
def declared_component_dicts
|
99
|
+
@declared_component_dicts ||= []
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
def initialize
|
105
|
+
self.class.attribute_initializers.each do |name, initializer|
|
106
|
+
instance_variable_set("@#{name}", initializer.call)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def immediate_config_errors
|
111
|
+
missing_required_attribute_errors
|
112
|
+
end
|
113
|
+
|
114
|
+
def config_errors
|
115
|
+
immediate_config_errors.merge(component_config_errors)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Configure with data.
|
119
|
+
#
|
120
|
+
# @param attribute_values [Hash] attribute values
|
121
|
+
# @return [Hash] errors encountered, keyed by attribute path
|
122
|
+
#
|
123
|
+
def configure_with(attribute_values)
|
124
|
+
errors = ConfigMapper.configure_with(attribute_values, self)
|
125
|
+
config_errors.merge(errors)
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
def components
|
131
|
+
{}.tap do |result|
|
132
|
+
self.class.declared_components.each do |name|
|
133
|
+
result[".#{name}"] = instance_variable_get("@#{name}")
|
134
|
+
end
|
135
|
+
self.class.declared_component_dicts.each do |name|
|
136
|
+
instance_variable_get("@#{name}").each do |key, value|
|
137
|
+
result[".#{name}[#{key.inspect}]"] = value
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
NOT_SET = "no value provided".freeze
|
144
|
+
|
145
|
+
def missing_required_attribute_errors
|
146
|
+
{}.tap do |errors|
|
147
|
+
self.class.required_attributes.each do |name|
|
148
|
+
unless instance_variable_defined?("@#{name}")
|
149
|
+
errors[".#{name}"] = NOT_SET
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def component_config_errors
|
156
|
+
{}.tap do |errors|
|
157
|
+
components.each do |component_name, component_value|
|
158
|
+
next unless component_value.respond_to?(:config_errors)
|
159
|
+
component_value.config_errors.each do |key, value|
|
160
|
+
errors["#{component_name}#{key}"] = value
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
class ConfigDict
|
169
|
+
|
170
|
+
def initialize(entry_type, key_type = nil)
|
171
|
+
@entry_type = entry_type
|
172
|
+
@key_type = key_type
|
173
|
+
@entries = {}
|
174
|
+
end
|
175
|
+
|
176
|
+
def [](key)
|
177
|
+
key = @key_type.call(key) if @key_type
|
178
|
+
@entries[key] ||= @entry_type.call
|
179
|
+
end
|
180
|
+
|
181
|
+
extend Forwardable
|
182
|
+
|
183
|
+
def_delegators :@entries, :each, :empty?, :keys, :map, :size
|
184
|
+
|
185
|
+
include Enumerable
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "config_mapper/target"
|
2
|
+
|
3
|
+
module ConfigMapper
|
4
|
+
|
5
|
+
# Configuration proxy for a Hash.
|
6
|
+
#
|
7
|
+
class HashTarget < Target
|
8
|
+
|
9
|
+
def initialize(hash)
|
10
|
+
@hash = hash
|
11
|
+
end
|
12
|
+
|
13
|
+
def path(key)
|
14
|
+
"[#{key.inspect}]"
|
15
|
+
end
|
16
|
+
|
17
|
+
def get(key)
|
18
|
+
@hash[key]
|
19
|
+
end
|
20
|
+
|
21
|
+
def set(key, value)
|
22
|
+
@hash[key] = value
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "config_mapper/target"
|
2
|
+
|
3
|
+
module ConfigMapper
|
4
|
+
|
5
|
+
# Configuration proxy for an Object.
|
6
|
+
#
|
7
|
+
class ObjectTarget < Target
|
8
|
+
|
9
|
+
def initialize(object)
|
10
|
+
@object = object
|
11
|
+
end
|
12
|
+
|
13
|
+
def path(key)
|
14
|
+
".#{key}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def get(key)
|
18
|
+
@object.public_send(key)
|
19
|
+
end
|
20
|
+
|
21
|
+
def set(key, value)
|
22
|
+
@object.public_send("#{key}=", value)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module ConfigMapper
|
2
|
+
|
3
|
+
# Something that accepts configuration.
|
4
|
+
#
|
5
|
+
class Target
|
6
|
+
|
7
|
+
# Map configuration data onto the target.
|
8
|
+
#
|
9
|
+
# @return [Hash] exceptions encountered
|
10
|
+
#
|
11
|
+
def with(data)
|
12
|
+
errors = {}
|
13
|
+
data.each do |key, value|
|
14
|
+
configure_attribute(key, value, errors)
|
15
|
+
end
|
16
|
+
errors
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
# Set a single attribute.
|
22
|
+
#
|
23
|
+
def configure_attribute(key, value, errors)
|
24
|
+
attribute_path = path(key)
|
25
|
+
if value.is_a?(Hash) && !get(key).nil?
|
26
|
+
nested_errors = ConfigMapper.configure_with(value, get(key))
|
27
|
+
nested_errors.each do |nested_path, error|
|
28
|
+
errors["#{attribute_path}#{nested_path}"] = error
|
29
|
+
end
|
30
|
+
else
|
31
|
+
set(key, value)
|
32
|
+
end
|
33
|
+
rescue NoMethodError, ArgumentError => e
|
34
|
+
errors[attribute_path] = e
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
data/lib/config_mapper.rb
CHANGED
@@ -1,23 +1,39 @@
|
|
1
|
-
require "config_mapper/
|
1
|
+
require "config_mapper/hash_target"
|
2
|
+
require "config_mapper/object_target"
|
2
3
|
|
3
4
|
# Supports marshalling of plain-old data (e.g. loaded from
|
4
5
|
# YAML files) onto strongly-typed objects.
|
5
6
|
#
|
6
7
|
module ConfigMapper
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
9
|
+
class << self
|
10
|
+
|
11
|
+
# Set attributes of a target object based on configuration data.
|
12
|
+
#
|
13
|
+
# For simple, scalar values, set the attribute by calling the
|
14
|
+
# named writer-method on the target object.
|
15
|
+
#
|
16
|
+
# For Hash values, set attributes of the named sub-component.
|
17
|
+
#
|
18
|
+
# @param data configuration data
|
19
|
+
# @param [Object, Hash] target the object to configure
|
20
|
+
#
|
21
|
+
# @return [Hash] exceptions encountered
|
22
|
+
#
|
23
|
+
def configure_with(data, target)
|
24
|
+
target(target).with(data)
|
25
|
+
end
|
26
|
+
|
27
|
+
alias_method :set, :configure_with
|
28
|
+
|
29
|
+
def target(target)
|
30
|
+
if target.is_a?(Hash)
|
31
|
+
HashTarget.new(target)
|
32
|
+
else
|
33
|
+
ObjectTarget.new(target)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
21
37
|
end
|
22
38
|
|
23
39
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: config_mapper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-02-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -64,14 +64,16 @@ files:
|
|
64
64
|
- ".rubocop.yml"
|
65
65
|
- ".travis.yml"
|
66
66
|
- Gemfile
|
67
|
+
- Guardfile
|
67
68
|
- LICENSE.txt
|
68
69
|
- README.md
|
69
70
|
- Rakefile
|
70
71
|
- config_mapper.gemspec
|
71
72
|
- lib/config_mapper.rb
|
72
|
-
- lib/config_mapper/
|
73
|
-
- lib/config_mapper/
|
74
|
-
- lib/config_mapper/
|
73
|
+
- lib/config_mapper/config_struct.rb
|
74
|
+
- lib/config_mapper/hash_target.rb
|
75
|
+
- lib/config_mapper/object_target.rb
|
76
|
+
- lib/config_mapper/target.rb
|
75
77
|
- lib/config_mapper/version.rb
|
76
78
|
homepage: https://github.com/mdub/config_mapper
|
77
79
|
licenses:
|
@@ -93,7 +95,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
93
95
|
version: '0'
|
94
96
|
requirements: []
|
95
97
|
rubyforge_project:
|
96
|
-
rubygems_version: 2.
|
98
|
+
rubygems_version: 2.5.0
|
97
99
|
signing_key:
|
98
100
|
specification_version: 4
|
99
101
|
summary: Maps config data onto plain old objects
|
@@ -1,42 +0,0 @@
|
|
1
|
-
require "config_mapper/error_proxy"
|
2
|
-
require "config_mapper/object_as_hash"
|
3
|
-
|
4
|
-
module ConfigMapper
|
5
|
-
|
6
|
-
# Sets attributes on an object, collecting errors
|
7
|
-
#
|
8
|
-
class AttributeSink
|
9
|
-
|
10
|
-
def initialize(target, errors = {})
|
11
|
-
@target = ObjectAsHash[target]
|
12
|
-
@errors = errors
|
13
|
-
end
|
14
|
-
|
15
|
-
attr_reader :target
|
16
|
-
attr_reader :errors
|
17
|
-
|
18
|
-
# Set multiple attributes from a Hash.
|
19
|
-
#
|
20
|
-
def set_attributes(data)
|
21
|
-
data.each do |key, value|
|
22
|
-
set_attribute(key, value)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
# Set a single attribute.
|
27
|
-
#
|
28
|
-
def set_attribute(key, value)
|
29
|
-
if value.is_a?(Hash) && !target[key].nil?
|
30
|
-
nested_errors = ErrorProxy.new(errors, [key])
|
31
|
-
nested_mapper = self.class.new(target[key], nested_errors)
|
32
|
-
nested_mapper.set_attributes(value)
|
33
|
-
else
|
34
|
-
target[key] = value
|
35
|
-
end
|
36
|
-
rescue NoMethodError, ArgumentError => e
|
37
|
-
errors[[key]] = e
|
38
|
-
end
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
end
|
@@ -1,23 +0,0 @@
|
|
1
|
-
module ConfigMapper
|
2
|
-
|
3
|
-
# Wraps a Hash of errors, injecting prefixes
|
4
|
-
#
|
5
|
-
class ErrorProxy
|
6
|
-
|
7
|
-
def initialize(errors, prefix)
|
8
|
-
@errors = errors
|
9
|
-
@prefix = prefix
|
10
|
-
end
|
11
|
-
|
12
|
-
def []=(key, value)
|
13
|
-
errors[prefix + key] = value
|
14
|
-
end
|
15
|
-
|
16
|
-
private
|
17
|
-
|
18
|
-
attr_reader :errors
|
19
|
-
attr_reader :prefix
|
20
|
-
|
21
|
-
end
|
22
|
-
|
23
|
-
end
|
@@ -1,29 +0,0 @@
|
|
1
|
-
module ConfigMapper
|
2
|
-
|
3
|
-
# Wrap an object to make it look more like a Hash.
|
4
|
-
#
|
5
|
-
class ObjectAsHash
|
6
|
-
|
7
|
-
def self.[](target)
|
8
|
-
if target.is_a?(Hash)
|
9
|
-
target
|
10
|
-
else
|
11
|
-
ObjectAsHash.new(target)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
def initialize(target)
|
16
|
-
@target = target
|
17
|
-
end
|
18
|
-
|
19
|
-
def [](key)
|
20
|
-
@target.public_send(key)
|
21
|
-
end
|
22
|
-
|
23
|
-
def []=(key, value)
|
24
|
-
@target.public_send("#{key}=", value)
|
25
|
-
end
|
26
|
-
|
27
|
-
end
|
28
|
-
|
29
|
-
end
|