config_mapper 1.6.0 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: cad2a71b7850554a0a981b18d91bb1198cd16f8a
4
- data.tar.gz: 305d415a5243d35b97d4a9b881fe51fad55217c5
2
+ SHA256:
3
+ metadata.gz: 893366e1a4af52a813c5b92b4053ce74266f04ff2ae5714d901567cd2cc6e82e
4
+ data.tar.gz: b0b1583750a6b2a5951d87dda67e7a919e82ae4eac49af820b2841140a24b459
5
5
  SHA512:
6
- metadata.gz: 91509bfa9e31c03bb8093c434942980cbd8616fffb22b7d0b4bf21f64edd01b750b7e9dd270c2d491452ae191455dcdf9c49724feb1d9f5076c3421c54d16c6d
7
- data.tar.gz: 423dcae0bc0c14b13979fbf1a10980b43c5e669a23fa521ad8b864fba965f83f3e5ee5bab1c569a8b6c2c47de017570964bd08ae7ef3b0cd36f37cb20c3da6b1
6
+ metadata.gz: ec06636f7a51b8c0a8843f3fdaeb538f5b1fad50db5f990b1e2f34d4e1af6c131ef29a78921af31c6d72b9ecec3abde5fa7f96b620ecdc702d54e71803b5d6a3
7
+ data.tar.gz: c69ea2e26bd80e145179afd3800d6bf2efde4ecb8701d47e6c27e83149619006388df4ccd629ff4105aa09d5d90c403c114f89d16acc0ad563c10a945ae4a8fd
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # ConfigMapper
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/config_mapper.svg)](https://badge.fury.io/rb/config_mapper)
4
- [![Build Status](https://travis-ci.org/mdub/config_mapper.svg?branch=master)](https://travis-ci.org/mdub/config_mapper)
4
+ [![Build Status](https://github.com/mdub/config_mapper/actions/workflows/test.yaml/badge.svg?branch=master)](https://github.com/mdub/config_mapper/actions/workflows/test.yaml)
5
5
 
6
6
  ConfigMapper maps configuration data onto Ruby objects.
7
7
 
@@ -72,21 +72,6 @@ state.orientation #=> "North"
72
72
  state.position.x #=> 2
73
73
  ```
74
74
 
75
- It can even populate Hashes of objects, e.g.
76
-
77
- ```ruby
78
- positions = Hash.new { |h,k| h[k] = Position.new }
79
-
80
- config_data = {
81
- "fred" => { "x" => 2, "y" => 4 },
82
- "mary" => { "x" => 3, "y" => 5 }
83
- }
84
-
85
- ConfigMapper.configure_with(config_data, positions)
86
- positions["fred"].x #=> 2
87
- positions["mary"].y #=> 5
88
- ```
89
-
90
75
  ### Target object
91
76
 
92
77
  Given
@@ -134,7 +119,7 @@ makes it even easier to declare configuration data-structures.
134
119
 
135
120
  ### Attributes
136
121
 
137
- The `attribute` method is similar to `attr_accessor`, defining both reader and writer methods for the named attribute.
122
+ The `attribute` method is similar to `attr_accessor`, defining both reader and writer methods for the named attribute.
138
123
 
139
124
  ```ruby
140
125
  require "config_mapper/config_struct"
@@ -204,7 +189,7 @@ Specify a default value of `nil` to mark an attribute as optional. Attributes wi
204
189
 
205
190
  ### Sub-components
206
191
 
207
- The `component` method defines a nested component object, itself a `ConfigStruct`.
192
+ The `component` method defines a nested component object, itself a `ConfigStruct`.
208
193
 
209
194
  ```ruby
210
195
  class State < ConfigMapper::ConfigStruct
@@ -217,6 +202,35 @@ class State < ConfigMapper::ConfigStruct
217
202
  end
218
203
  ```
219
204
 
205
+ `component_list` declares a nested list of configurable objects, indexed by position.
206
+
207
+ ```ruby
208
+ class Polygon < ConfigMapper::ConfigStruct
209
+
210
+ component_list :points do
211
+ attribute :x
212
+ attribute :y
213
+ end
214
+
215
+ end
216
+
217
+ ```
218
+
219
+ `component_dict` declares a dictionary (map) of configurable objects, indexed by an arbitrary key.
220
+
221
+ ```ruby
222
+ class Cargo < ConfigMapper::ConfigStruct
223
+
224
+ component_dict :packages do
225
+ attribute :contents
226
+ attribute :weight, Float
227
+ end
228
+
229
+ end
230
+ ```
231
+
232
+ In both cases, new collection entries pop into existance the first time they are accessed.
233
+
220
234
  ### Semantic errors
221
235
 
222
236
  `ConfigStruct#config_errors` returns errors for each unset mandatory attribute.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "config_mapper/mapper"
2
4
 
3
5
  module ConfigMapper
@@ -22,7 +24,7 @@ module ConfigMapper
22
24
  @hash[key] = value
23
25
  end
24
26
 
25
- def can_set?(key)
27
+ def can_set?(_key)
26
28
  @hash.respond_to?("[]=")
27
29
  end
28
30
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "config_mapper/factory"
2
4
  require "config_mapper/validator"
3
5
  require "forwardable"
@@ -22,6 +24,7 @@ module ConfigMapper
22
24
 
23
25
  def config_doc
24
26
  return {} unless entry_factory.respond_to?(:config_doc)
27
+
25
28
  {}.tap do |result|
26
29
  entry_factory.config_doc.each do |path, doc|
27
30
  result["[X]#{path}"] = doc
@@ -55,6 +58,7 @@ module ConfigMapper
55
58
  each do |key, value|
56
59
  prefix = "[#{key.inspect}]"
57
60
  next unless value.respond_to?(:config_errors)
61
+
58
62
  value.config_errors.each do |path, path_errors|
59
63
  errors["#{prefix}#{path}"] = path_errors
60
64
  end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "config_mapper"
4
+ require "config_mapper/config_struct"
5
+ require "config_mapper/factory"
6
+ require "config_mapper/validator"
7
+ require "forwardable"
8
+
9
+ module ConfigMapper
10
+ class ConfigList
11
+
12
+ class Factory
13
+
14
+ def initialize(entry_factory)
15
+ @entry_factory = ConfigMapper::Factory.resolve(entry_factory)
16
+ end
17
+
18
+ attr_reader :entry_factory
19
+
20
+ def new
21
+ ConfigList.new(@entry_factory)
22
+ end
23
+
24
+ def config_doc
25
+ return {} unless entry_factory.respond_to?(:config_doc)
26
+
27
+ {}.tap do |result|
28
+ entry_factory.config_doc.each do |path, doc|
29
+ result["[N]#{path}"] = doc
30
+ end
31
+ end
32
+ end
33
+
34
+ end
35
+
36
+ def initialize(entry_factory)
37
+ @entry_factory = entry_factory
38
+ @entries = []
39
+ end
40
+
41
+ def [](index)
42
+ @entries[index] ||= @entry_factory.new
43
+ end
44
+
45
+ def to_a
46
+ map do |element|
47
+ if element.respond_to?(:to_h) then element.to_h
48
+ elsif element.respond_to?(:to_a) then element.to_a
49
+ else element
50
+ end
51
+ end
52
+ end
53
+
54
+ def config_errors
55
+ {}.tap do |errors|
56
+ each_with_index do |element, index|
57
+ next unless element.respond_to?(:config_errors)
58
+
59
+ prefix = "[#{index}]"
60
+ element.config_errors.each do |path, path_errors|
61
+ errors["#{prefix}#{path}"] = path_errors
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ extend Forwardable
68
+
69
+ def_delegators :@entries, :each, :each_with_index, :empty?, :map, :size
70
+
71
+ include Enumerable
72
+
73
+ end
74
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "config_mapper"
2
4
  require "config_mapper/config_dict"
3
5
  require "config_mapper/factory"
@@ -36,8 +38,7 @@ module ConfigMapper
36
38
  # @param default default value
37
39
  # @yield type-coercion block
38
40
  #
39
- def attribute(name, type = nil, default: :no_default, description: nil, &type_block)
40
-
41
+ def attribute(name, type = nil, default: :no_default, description: nil, &type_block) # rubocop:disable Metrics/PerceivedComplexity
41
42
  attribute = attribute!(name)
42
43
  attribute.description = description
43
44
 
@@ -52,12 +53,11 @@ module ConfigMapper
52
53
  define_method("#{attribute.name}=") do |value|
53
54
  if value.nil?
54
55
  raise NoValueProvided if attribute.required
55
- else
56
- value = attribute.validator.call(value) if attribute.validator
56
+ elsif attribute.validator
57
+ value = attribute.validator.call(value)
57
58
  end
58
59
  instance_variable_set("@#{attribute.name}", value)
59
60
  end
60
-
61
61
  end
62
62
 
63
63
  # Defines a sub-component.
@@ -86,7 +86,20 @@ module ConfigMapper
86
86
  #
87
87
  def component_dict(name, type: ConfigStruct, key_type: nil, description: nil, &block)
88
88
  type = Class.new(type, &block) if block
89
- component(name, type: ConfigDict::Factory.new(type, key_type), description: description)
89
+ component(name, :type => ConfigDict::Factory.new(type, key_type), :description => description)
90
+ end
91
+
92
+ # Defines an array of sub-components.
93
+ #
94
+ # If a block is be provided, it will be `class_eval`ed to define the
95
+ # sub-components class.
96
+ #
97
+ # @param name [Symbol] list attribute name
98
+ # @param type [Class] base-class for component values
99
+ #
100
+ def component_list(name, type: ConfigStruct, description: nil, &block)
101
+ type = Class.new(type, &block) if block
102
+ component(name, :type => ConfigList::Factory.new(type), :description => description)
90
103
  end
91
104
 
92
105
  # Generate documentation, as Ruby data.
@@ -106,8 +119,10 @@ module ConfigMapper
106
119
 
107
120
  def each_attribute(&action)
108
121
  return enum_for(:each_attribute) unless action
122
+
109
123
  ancestors.each do |klass|
110
124
  next unless klass.respond_to?(:attributes)
125
+
111
126
  klass.attributes.each(&action)
112
127
  end
113
128
  end
@@ -157,8 +172,10 @@ module ConfigMapper
157
172
  {}.tap do |result|
158
173
  self.class.each_attribute do |attribute|
159
174
  value = send(attribute.name)
160
- if value && value.respond_to?(:to_h) && !value.is_a?(Array)
175
+ if value&.respond_to?(:to_h) && !value.is_a?(Array) && !value.is_a?(ConfigList)
161
176
  value = value.to_h
177
+ elsif value&.respond_to?(:to_a)
178
+ value = value.to_a
162
179
  end
163
180
  result[attribute.name.to_s] = value
164
181
  end
@@ -171,6 +188,7 @@ module ConfigMapper
171
188
  {}.tap do |result|
172
189
  self.class.each_attribute do |a|
173
190
  next unless a.factory
191
+
174
192
  result[".#{a.name}"] = instance_variable_get("@#{a.name}")
175
193
  end
176
194
  end
@@ -187,9 +205,7 @@ module ConfigMapper
187
205
  def missing_required_attribute_errors
188
206
  {}.tap do |errors|
189
207
  self.class.each_attribute do |a|
190
- if a.required && instance_variable_get("@#{a.name}").nil?
191
- errors[".#{a.name}"] = NoValueProvided.new
192
- end
208
+ errors[".#{a.name}"] = NoValueProvided.new if a.required && instance_variable_get("@#{a.name}").nil?
193
209
  end
194
210
  end
195
211
  end
@@ -198,6 +214,7 @@ module ConfigMapper
198
214
  {}.tap do |errors|
199
215
  components.each do |component_path, component_value|
200
216
  next unless component_value.respond_to?(:config_errors)
217
+
201
218
  component_value.config_errors.each do |path, value|
202
219
  errors["#{component_path}#{path}"] = value
203
220
  end
@@ -212,15 +229,16 @@ module ConfigMapper
212
229
  end
213
230
 
214
231
  attr_reader :name
232
+ attr_reader :factory
215
233
 
216
234
  attr_accessor :description
217
- attr_accessor :factory
218
235
  attr_accessor :validator
219
236
  attr_accessor :default
220
237
  attr_accessor :required
221
238
 
222
239
  def initial_value
223
240
  return factory.new if factory
241
+
224
242
  default
225
243
  end
226
244
 
@@ -246,6 +264,7 @@ module ConfigMapper
246
264
 
247
265
  def type_doc
248
266
  return {} unless factory.respond_to?(:config_doc)
267
+
249
268
  factory.config_doc.each_with_object({}) do |(path, doc), result|
250
269
  result[".#{name}#{path}"] = doc
251
270
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ConfigMapper
2
4
 
3
5
  module Factory
@@ -5,6 +7,7 @@ module ConfigMapper
5
7
  def self.resolve(arg)
6
8
  return arg if arg.respond_to?(:new)
7
9
  return ProcFactory.new(arg) if arg.respond_to?(:call)
10
+
8
11
  raise ArgumentError, "invalid factory"
9
12
  end
10
13
 
@@ -12,7 +15,7 @@ module ConfigMapper
12
15
 
13
16
  class ProcFactory
14
17
 
15
- def initialize(f)
18
+ def initialize(f) # rubocop:disable Naming/MethodParameterName
16
19
  @f = f
17
20
  end
18
21
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ConfigMapper
2
4
 
3
5
  # Something that accepts configuration.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ConfigMapper
2
4
 
3
5
  # Thrown to indicate a problem parsing config.
@@ -16,7 +18,7 @@ module ConfigMapper
16
18
  def generate_message
17
19
  result = "configuration error"
18
20
  errors_by_field.each do |field, error|
19
- result << "\n #{field[1..-1]} - #{error}"
21
+ result += "\n #{field[1..-1]} - #{error}"
20
22
  end
21
23
  result
22
24
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "config_mapper/mapper"
2
4
 
3
5
  module ConfigMapper
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ConfigMapper
2
4
 
3
5
  module Validator
@@ -8,6 +10,7 @@ module ConfigMapper
8
10
  # looks like a primitive class -- find the corresponding coercion method
9
11
  return Kernel.method(arg.name)
10
12
  end
13
+
11
14
  arg
12
15
  end
13
16
 
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ConfigMapper
2
4
 
3
- VERSION = "1.6.0".freeze
5
+ VERSION = "1.8.0"
4
6
 
5
7
  end
data/lib/config_mapper.rb CHANGED
@@ -1,5 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "config_mapper/collection_mapper"
2
4
  require "config_mapper/config_dict"
5
+ require "config_mapper/config_list"
3
6
  require "config_mapper/object_mapper"
4
7
 
5
8
  # Supports marshalling of plain-old data (e.g. loaded from
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.6.0
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Williams
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-10-13 00:00:00.000000000 Z
11
+ date: 2023-02-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -16,43 +16,57 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.10'
19
+ version: '2.2'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.10'
26
+ version: '2.2'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '13.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '13.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: 3.12.0
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ">="
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 3.12.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 1.46.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
53
67
  - !ruby/object:Gem::Version
54
- version: '0'
55
- description:
68
+ version: 1.46.0
69
+ description:
56
70
  email:
57
71
  - mdub@dogbiscuit.org
58
72
  executables: []
@@ -64,6 +78,7 @@ files:
64
78
  - lib/config_mapper.rb
65
79
  - lib/config_mapper/collection_mapper.rb
66
80
  - lib/config_mapper/config_dict.rb
81
+ - lib/config_mapper/config_list.rb
67
82
  - lib/config_mapper/config_struct.rb
68
83
  - lib/config_mapper/factory.rb
69
84
  - lib/config_mapper/mapper.rb
@@ -75,7 +90,7 @@ homepage: https://github.com/mdub/config_mapper
75
90
  licenses:
76
91
  - MIT
77
92
  metadata: {}
78
- post_install_message:
93
+ post_install_message:
79
94
  rdoc_options: []
80
95
  require_paths:
81
96
  - lib
@@ -83,16 +98,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
83
98
  requirements:
84
99
  - - ">="
85
100
  - !ruby/object:Gem::Version
86
- version: '2.0'
101
+ version: '2.7'
87
102
  required_rubygems_version: !ruby/object:Gem::Requirement
88
103
  requirements:
89
104
  - - ">="
90
105
  - !ruby/object:Gem::Version
91
106
  version: '0'
92
107
  requirements: []
93
- rubyforge_project:
94
- rubygems_version: 2.6.13
95
- signing_key:
108
+ rubygems_version: 3.3.7
109
+ signing_key:
96
110
  specification_version: 4
97
111
  summary: Maps config data onto plain old objects
98
112
  test_files: []