collapsium-config 0.2.1 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 831b6eb064c405db8797886058469fb8f613c0fc
4
- data.tar.gz: c38ef78dd89d8a009e01719aa6860c4b6d953697
3
+ metadata.gz: 6f59285701d071aae2421be7c500cf4b814c346d
4
+ data.tar.gz: efe7bc99ba90a5b7a0508047a7e61ff5618eede9
5
5
  SHA512:
6
- metadata.gz: f97ddbf8c8973f5d2590d45da0ab07778295dccc7078836002d899bc41920e75b8e423440af56ec516567fabbf71f3c35b9310634f2e640d6e75f50ec87e36d2
7
- data.tar.gz: f0181fba285ca14f6153713316683a0b218bb7709814d01d89039b07c8d74f3f308f1bc71cf5d30c456b6033b1074eba3943cacbf41f8f8416035eb556e8ad3a
6
+ metadata.gz: 051281284616335b1776dc9001f07d5da473f3ce6873c3d477eb2b8c74c62396d6b57f30943c288123fbe76e149a5f50b63b83957bc30946b700ed1f584fce67
7
+ data.tar.gz: 26a6b17ffcb4af8aed8368adaf8f9686800f61dded3e3d1566b121ccf3b7e3618af8fbc94ecedcfb5fff22d2e05f6e0bd2730975cbf246913dcfc261c8820649
data/Gemfile.lock CHANGED
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- collapsium-config (0.2.1)
5
- collapsium (~> 0.4)
4
+ collapsium-config (0.3.0)
5
+ collapsium (~> 0.5)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
@@ -10,20 +10,20 @@ GEM
10
10
  ast (2.3.0)
11
11
  codeclimate-test-reporter (0.6.0)
12
12
  simplecov (>= 0.7.1, < 1.0.0)
13
- collapsium (0.4.1)
13
+ collapsium (0.5.0)
14
14
  diff-lcs (1.2.5)
15
15
  docile (1.1.5)
16
16
  json (2.0.2)
17
- parser (2.3.1.2)
17
+ parser (2.3.1.4)
18
18
  ast (~> 2.2)
19
19
  powerpack (0.1.1)
20
20
  rainbow (2.1.0)
21
- rake (11.2.2)
21
+ rake (11.3.0)
22
22
  rspec (3.5.0)
23
23
  rspec-core (~> 3.5.0)
24
24
  rspec-expectations (~> 3.5.0)
25
25
  rspec-mocks (~> 3.5.0)
26
- rspec-core (3.5.2)
26
+ rspec-core (3.5.4)
27
27
  rspec-support (~> 3.5.0)
28
28
  rspec-expectations (3.5.0)
29
29
  diff-lcs (>= 1.2.0, < 2.0)
@@ -32,7 +32,7 @@ GEM
32
32
  diff-lcs (>= 1.2.0, < 2.0)
33
33
  rspec-support (~> 3.5.0)
34
34
  rspec-support (3.5.0)
35
- rubocop (0.42.0)
35
+ rubocop (0.44.1)
36
36
  parser (>= 2.3.1.1, < 3.0)
37
37
  powerpack (~> 0.1)
38
38
  rainbow (>= 1.99.1, < 3.0)
@@ -44,7 +44,7 @@ GEM
44
44
  json (>= 1.8, < 3)
45
45
  simplecov-html (~> 0.10.0)
46
46
  simplecov-html (0.10.0)
47
- unicode-display_width (1.1.0)
47
+ unicode-display_width (1.1.1)
48
48
  yard (0.9.5)
49
49
 
50
50
  PLATFORMS
@@ -54,9 +54,9 @@ DEPENDENCIES
54
54
  bundler (~> 1.12)
55
55
  codeclimate-test-reporter
56
56
  collapsium-config!
57
- rake (~> 11.2)
57
+ rake (~> 11.3)
58
58
  rspec (~> 3.5)
59
- rubocop (~> 0.42)
59
+ rubocop (~> 0.44)
60
60
  simplecov (~> 0.12)
61
61
  yard (~> 0.9)
62
62
 
data/README.md CHANGED
@@ -18,11 +18,13 @@ various configuration sources into one configuration object.
18
18
  if that exists, and merges it's contents recursively into the main
19
19
  configuration.
20
20
  - Using the special `extends` configuration key, allows a configuration Hash
21
- to include all values from another configuration Hash.
21
+ to include all values from other configuration Hash(es).
22
22
  - Using the special, top-level `include` configuration key, allows a
23
23
  configuration file to be split into multiple included files.
24
+ - As of `v0.2`, configuration files are [ERB templates](http://ruby-doc.org/stdlib-2.3.1/libdoc/erb/rdoc/ERB.html).
25
+ Do your templating stuff as you'd usually do it.
24
26
 
25
- # Usage
27
+ # Basic Usage
26
28
 
27
29
  While you can use the `Configuration` class yourself, the simplest usage is to
28
30
  access a global configuration object:
@@ -33,3 +35,72 @@ include Collapsium::Config
33
35
 
34
36
  puts config["foo"] # loaded automatically from config.yml
35
37
  ```
38
+
39
+ # Advanced Usage
40
+
41
+ ## Configuration File Location
42
+
43
+ The friendly neighbour to the `#config` function introduced in the basic
44
+ usage section above is the `#config_file` accessor. Its value will default
45
+ to `config.yml`, but you can set it to something different, too:
46
+
47
+ ```ruby
48
+ config_file = 'foo.yaml'
49
+ puts config["foo"] # loaded automatically from foo.yaml
50
+ ```
51
+
52
+ ## Loading Configuration Files
53
+
54
+ All that `#config` and `#config_file` do is wrap `#load_config` such that
55
+ configuration is loaded only once. You can load configuration files manually,
56
+ too:
57
+
58
+ ```ruby
59
+ my_config = Collapsium::Config::Configuration.load_config('filename.yaml')
60
+ ```
61
+
62
+ ## Extension
63
+
64
+ Given the following configuration file:
65
+
66
+ ```yaml
67
+ base:
68
+ foo: 42
69
+
70
+ derived:
71
+ bar: value
72
+ extends: .base
73
+ ```
74
+
75
+ Then the special `extends` keyword is interpreted to merge all values from
76
+ the value at path `.base` into the value at path `.derived`. Additionally,
77
+ `.derived` will gain a new key `base` which is an Array containing all the
78
+ bases merged into the value.
79
+
80
+ - Absolute paths are preferred for values of `extends`.
81
+ - Relative paths for values of `extends` are looked up in the parent of the
82
+ value that contains the `extends` keyword, i.e. the root in the example
83
+ above. So in this minimal example, specifying `.base` and `base` is
84
+ equivalent.
85
+ - You can specify a comma-separated list of bases in the `extends` keyword.
86
+ Latter paths overwrite values in earlier paths.
87
+
88
+ ## Templating
89
+
90
+ ERB templating in configuration files works out-of-the-box, but one of the
91
+ more powerful features is of course to substitute some values in the template
92
+ which your loading code already knows. If you're using `#load_config`, you
93
+ can do that with the `data` keyword parameter:
94
+
95
+ ```ruby
96
+ my_data_hash = {}
97
+ my_config = Configuration.load_config('foo.yaml', data: my_data_hash)
98
+ ```
99
+
100
+ Note that the template has access to the entire hash under the `data` name,
101
+ not to its individual keys:
102
+
103
+ ```erb
104
+ <%= data[:some_key] %> # correct usage
105
+ <%= some_key %> # incorrect usage
106
+ ```
@@ -13,6 +13,7 @@ require 'collapsium-config/version'
13
13
 
14
14
  # rubocop:disable Style/UnneededPercentQ, Style/ExtraSpacing
15
15
  # rubocop:disable Style/SpaceAroundOperators
16
+ # rubocop:disable Metrics/BlockLength
16
17
  Gem::Specification.new do |spec|
17
18
  spec.name = "collapsium-config"
18
19
  spec.version = Collapsium::Config::VERSION
@@ -37,13 +38,14 @@ Gem::Specification.new do |spec|
37
38
  spec.required_ruby_version = '>= 2.0'
38
39
 
39
40
  spec.add_development_dependency "bundler", "~> 1.12"
40
- spec.add_development_dependency "rubocop", "~> 0.42"
41
- spec.add_development_dependency "rake", "~> 11.2"
41
+ spec.add_development_dependency "rubocop", "~> 0.44"
42
+ spec.add_development_dependency "rake", "~> 11.3"
42
43
  spec.add_development_dependency "rspec", "~> 3.5"
43
44
  spec.add_development_dependency "simplecov", "~> 0.12"
44
45
  spec.add_development_dependency "yard", "~> 0.9"
45
46
 
46
- spec.add_dependency 'collapsium', '~> 0.4'
47
+ spec.add_dependency 'collapsium', '~> 0.5'
47
48
  end
49
+ # rubocop:enable Metrics/BlockLength
48
50
  # rubocop:enable Style/SpaceAroundOperators
49
51
  # rubocop:enable Style/UnneededPercentQ, Style/ExtraSpacing
@@ -65,6 +65,10 @@ module Collapsium
65
65
  end
66
66
  private_constant :JSONParser
67
67
 
68
+ def initialize(*args)
69
+ super(*args)
70
+ end
71
+
68
72
  class << self
69
73
  # @api private
70
74
  # Mapping of file name extensions to parser types.
@@ -267,80 +271,90 @@ module Collapsium
267
271
  # keywords that cannot be used in configuration files other than for this
268
272
  # purpose!
269
273
  def resolve_extensions!
270
- recursive_merge("", "")
274
+ # The root object is always a Hash, so has keys, which can be processed
275
+ # recursively.
276
+ recursive_resolve(self)
271
277
  end
272
278
 
273
- private
274
-
275
- def recursive_merge(parent, key)
276
- loop do
277
- full_key = "#{parent}#{separator}#{key}"
279
+ def recursive_resolve(root, prefix = "")
280
+ # The self object is a Hash or an Array. Let's iterate over its children
281
+ # one by one. Defaulting to a Hash here is just convenience, it could
282
+ # equally be an Array.
283
+ children = root.fetch(prefix, {})
278
284
 
279
- # Recurse down to the remaining root of the hierarchy
280
- base = full_key
281
- derived = nil
282
- loop do
283
- new_base, new_derived = resolve_extension(parent, base)
285
+ merge_base(root, prefix, children)
284
286
 
285
- if new_derived.nil?
286
- break
287
- end
288
-
289
- base = new_base
290
- derived = new_derived
287
+ if children.is_a? Hash
288
+ children.each do |key, _|
289
+ full_key = normalize_path("#{prefix}#{separator}#{key}")
290
+ recursive_resolve(root, full_key)
291
291
  end
292
-
293
- # If recursion found nothing to merge, we're done!
294
- if derived.nil?
295
- break
292
+ elsif children.is_a? Array
293
+ children.each_with_index do |_, idx|
294
+ key = idx.to_s
295
+ full_key = normalize_path("#{prefix}#{separator}#{key}")
296
+ recursive_resolve(root, full_key)
296
297
  end
297
-
298
- # Otherwise, merge what needs merging and continue
299
- merge_extension(base, derived)
300
298
  end
301
299
  end
302
300
 
303
- def resolve_extension(grandparent, parent)
304
- fetch(parent, {}).each do |key, value|
305
- # Recurse into hash values
306
- if value.is_a? Hash
307
- recursive_merge(parent, key)
308
- end
301
+ def merge_base(root, path, value)
302
+ # If the value is not a Hash, we can't do anything here.
303
+ if not value.is_a? Hash
304
+ return
305
+ end
309
306
 
310
- # No hash, ignore any keys other than the special "extends" key
311
- if key != "extends"
312
- next
313
- end
307
+ # If the value contains an "extends" keyword, we can find the value's
308
+ # base. Otherwise there's nothing to do.
309
+ if not value.include? "extends"
310
+ return
311
+ end
314
312
 
315
- # If the key is "extends", return a normalized version of its value.
316
- full_value = value.dup
317
- if not full_value.start_with?(separator)
318
- full_value = "#{grandparent}#{separator}#{value}"
313
+ # Now to resolve the path to the base and remove the "extends" keyword.
314
+ base_paths = value["extends"]
315
+ base_paths = base_paths.split(/,/).map(&:strip)
316
+ bases = {}
317
+ base_paths.each do |base_path|
318
+ if not base_path.start_with?(separator)
319
+ parent = parent_path(path)
320
+ base_path = "#{parent}#{separator}#{base_path}"
319
321
  end
322
+ base_path = normalize_path(base_path)
320
323
 
321
- if full_value == parent
324
+ # Fetch the base value from the root. This makes full use of
325
+ # PathedAccess.
326
+ # We default to nil. Only Hash base values can be processed.
327
+ base_value = root.fetch(base_path, nil)
328
+ if not base_value.is_a? Hash
322
329
  next
323
330
  end
324
- return full_value, parent
331
+
332
+ bases[base_path] = base_value
325
333
  end
326
334
 
327
- return nil, nil
328
- end
335
+ # Only delete the "extends" keyword if we found all base.
336
+ if bases.length == base_paths.length
337
+ value.delete("extends")
338
+ end
329
339
 
330
- def merge_extension(base, derived)
331
- # Remove old 'extends' key, but remember the value
332
- extends = self[derived]["extends"]
333
- self[derived].delete("extends")
340
+ # We need to recursively resolve the base values before merging them into
341
+ # value. To preserve the override order, we need to overwrite values when
342
+ # merging bases...
343
+ merged_base = Configuration.new
344
+ bases.each do |base_path, base_value|
345
+ base_value.recursive_resolve(root, base_path)
346
+ merged_base.recursive_merge!(base_value, true)
347
+ end
334
348
 
335
- # Recursively merge base into derived without overwriting
336
- self[derived].extend(::Collapsium::RecursiveMerge)
337
- self[derived].recursive_merge!(self[base], false)
349
+ # ... but value needs to stay authoritative.
350
+ value.recursive_merge!(merged_base, false)
338
351
 
339
- # Then set the "base" keyword, but only if it's not yet set.
340
- if not self[derived]["base"].nil?
341
- return
352
+ # Set the base if all is well.
353
+ if value["base"].nil? and not bases.keys.empty?
354
+ value["base"] = bases.keys
342
355
  end
343
- self[derived]["base"] = extends
356
+
357
+ root[path] = value
344
358
  end
345
359
  end # class Configuration
346
360
  end # module Config
@@ -9,6 +9,6 @@
9
9
  module Collapsium
10
10
  module Config
11
11
  # The current release version
12
- VERSION = "0.2.1".freeze
12
+ VERSION = "0.3.0".freeze
13
13
  end # module Config
14
14
  end # module Collapsium
@@ -117,9 +117,25 @@ describe Collapsium::Config::Configuration do
117
117
  expect(cfg["drivers.leaf.branch2option"]).to eql "bar"
118
118
 
119
119
  # Also test that all levels go back to base == mock
120
- expect(cfg["drivers.branch1.base"]).to eql 'mock'
121
- expect(cfg["drivers.branch2.base"]).to eql 'mock'
122
- expect(cfg["drivers.leaf.base"]).to eql 'mock'
120
+ expect(cfg["drivers.branch1.base"]).to eql %w(.drivers.mock)
121
+ expect(cfg["drivers.branch2.base"]).to eql %w(.drivers.mock)
122
+ expect(cfg["drivers.leaf.base"]).to eql %w(.drivers.mock)
123
+
124
+ # We expect that 'derived' is extended with '.other.base', too
125
+ expect(cfg["derived.test.foo"]).to eql 'bar'
126
+ expect(cfg["derived.test.some"]).to eql 'option'
127
+
128
+ # Expect this to also work with list items
129
+ expect(cfg["derived.test2.0.foo"]).to eql 'bar'
130
+ expect(cfg["derived.test2.0.some"]).to eql 'option'
131
+
132
+ expect(cfg["derived.test3.foo"]).to eql 'bar'
133
+ expect(cfg["derived.test3.some2"]).to eql 'option2'
134
+
135
+ # Expect this to work with multiple inheritance
136
+ expect(cfg["derived.test4.foo"]).to eql 'bar'
137
+ expect(cfg["derived.test4.some"]).to eql 'option_override'
138
+ expect(cfg["derived.test4.some2"]).to eql 'option2'
123
139
  end
124
140
 
125
141
  it "extends configuration hashes when the base does not exist" do
@@ -129,8 +145,11 @@ describe Collapsium::Config::Configuration do
129
145
  # Ensure the hash contains its own value
130
146
  expect(cfg["drivers.base_does_not_exist.some"]).to eql "value"
131
147
 
132
- # Also ensure the "base" is set properly
133
- expect(cfg["drivers.base_does_not_exist.base"]).to eql "nonexistent_base"
148
+ # Also ensure the "base" is _not_ set properly
149
+ expect(cfg["drivers.base_does_not_exist.base"]).to be_nil
150
+
151
+ # On the other hand, "extends" should stay.
152
+ expect(cfg["drivers.base_does_not_exist.extends"]).to eql "nonexistent_base"
134
153
  end
135
154
 
136
155
  it "does nothing when a hash extends itself" do
@@ -24,3 +24,24 @@ drivers:
24
24
  extends: nonexistent_base
25
25
  some: value
26
26
  driver: leaf
27
+
28
+ other:
29
+ testbase:
30
+ some: option
31
+ testbase2:
32
+ - some2: option2
33
+ some: option_override
34
+
35
+ derived:
36
+ test:
37
+ foo: bar
38
+ extends: .other.testbase
39
+ test2:
40
+ - foo: bar
41
+ extends: .other.testbase
42
+ test3:
43
+ foo: bar
44
+ extends: .other.testbase2.0
45
+ test4:
46
+ foo: bar
47
+ extends: .other.testbase, .other.testbase2.0
data/spec/data/extend.yml CHANGED
@@ -4,3 +4,5 @@ foo_base:
4
4
  bar:
5
5
  extends: bar_base
6
6
  foo: something
7
+ merged:
8
+ second: 2
@@ -5,3 +5,5 @@ foo:
5
5
  bar: quux
6
6
  bar_base:
7
7
  baz: 42
8
+ merged:
9
+ first: 1
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: collapsium-config
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jens Finkhaeuser
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-08-18 00:00:00.000000000 Z
11
+ date: 2016-10-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -30,28 +30,28 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.42'
33
+ version: '0.44'
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: '0.42'
40
+ version: '0.44'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '11.2'
47
+ version: '11.3'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '11.2'
54
+ version: '11.3'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rspec
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -100,14 +100,14 @@ dependencies:
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: '0.4'
103
+ version: '0.5'
104
104
  type: :runtime
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: '0.4'
110
+ version: '0.5'
111
111
  description: "\n Using collapsium's UberHash class for easy access to configuration
112
112
  values,\n this gem reads and merges various configuration sources into one\n
113
113
  \ configuration object.\n "