collapsium-config 0.2.1 → 0.3.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
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 "