unobtainium 0.5.1 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +5 -1
- data/README.md +3 -2
- data/lib/unobtainium.rb +0 -1
- data/lib/unobtainium/driver.rb +7 -1
- data/lib/unobtainium/runtime.rb +2 -0
- data/lib/unobtainium/support/runner.rb +0 -4
- data/lib/unobtainium/version.rb +1 -1
- data/lib/unobtainium/world.rb +37 -16
- data/media/screenshot.jpg +0 -0
- data/media/video.ogv +0 -0
- data/spec/driver_spec.rb +40 -28
- data/spec/runner_spec.rb +19 -0
- data/spec/spec_helper.rb +1 -0
- data/unobtainium.gemspec +1 -0
- metadata +18 -31
- data/lib/unobtainium/config.rb +0 -304
- data/lib/unobtainium/pathed_hash.rb +0 -199
- data/lib/unobtainium/recursive_merge.rb +0 -55
- data/spec/config_spec.rb +0 -119
- data/spec/data/array.yaml +0 -3
- data/spec/data/arraymerge-local.yaml +0 -2
- data/spec/data/arraymerge.yaml +0 -3
- data/spec/data/empty.yml +0 -1
- data/spec/data/hash.yml +0 -3
- data/spec/data/hashmerge-local.yml +0 -4
- data/spec/data/hashmerge.yml +0 -7
- data/spec/data/mergefail-local.yaml +0 -2
- data/spec/data/mergefail.yaml +0 -5
- data/spec/data/test.json +0 -4
- data/spec/data/world.yml +0 -5
- data/spec/pathed_hash_spec.rb +0 -192
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e3149f2511fbd2ecbbd5af9d19bb097251bdcac9
|
4
|
+
data.tar.gz: 95e5ab479135ada464f851c6ebaaea775033d440
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3a8d32ef7e4162537270294802f1c57ad6a18e736b5c6d0a93dba3e586b8c54d18d184ae611a3892da14cb4c76b28cc4c10ee8d646f3bd4b2b4310000b973bca
|
7
|
+
data.tar.gz: 19c21152cb0c4b0268695cdb577f230da3c42994f48b2b457d29df8a8e5e733c8f5d3b3abb81c7ae9d8e6496694ef7b17b3f5c42e51b0f5d7e653ec539b30f75
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
unobtainium (0.
|
4
|
+
unobtainium (0.6.0)
|
5
|
+
collapsium-config (~> 0.1)
|
5
6
|
sys-proctable (~> 1.0)
|
6
7
|
|
7
8
|
GEM
|
@@ -20,6 +21,9 @@ GEM
|
|
20
21
|
ffi (~> 1.0, >= 1.0.11)
|
21
22
|
codeclimate-test-reporter (0.5.0)
|
22
23
|
simplecov (>= 0.7.1, < 1.0.0)
|
24
|
+
collapsium (0.1.0)
|
25
|
+
collapsium-config (0.1.1)
|
26
|
+
collapsium (~> 0.1)
|
23
27
|
cucumber (2.3.3)
|
24
28
|
builder (>= 2.1.2)
|
25
29
|
cucumber-core (~> 1.4.0)
|
data/README.md
CHANGED
@@ -46,8 +46,9 @@ the `Unobtainium::World` module.
|
|
46
46
|
- The `Driver` class, of course, wraps either of Appium or Selenium drivers:
|
47
47
|
|
48
48
|
```ruby
|
49
|
-
drv = Driver.create(:firefox) # uses Selenium
|
50
|
-
drv = Driver.create(:android) # uses Appium
|
49
|
+
drv = Driver.create(:firefox) # uses Selenium and Firefox
|
50
|
+
drv = Driver.create(:android) # uses Appium (browser or device)
|
51
|
+
drv = Driver.create(:phantomjs) # use Selenium and PhantomJS
|
51
52
|
|
52
53
|
drv.navigate.to "..." # delegates to Selenium or Appium
|
53
54
|
```
|
data/lib/unobtainium.rb
CHANGED
data/lib/unobtainium/driver.rb
CHANGED
@@ -125,7 +125,7 @@ module Unobtainium
|
|
125
125
|
label = label.to_sym
|
126
126
|
|
127
127
|
if not opts.nil?
|
128
|
-
if not
|
128
|
+
if not opts.is_a? Hash
|
129
129
|
raise ArgumentError, "The second argument is expected to be an "\
|
130
130
|
"options Hash!"
|
131
131
|
end
|
@@ -166,11 +166,15 @@ module Unobtainium
|
|
166
166
|
klass = Object.const_get(klassname)
|
167
167
|
Driver.register_implementation(klass, fpath)
|
168
168
|
rescue LoadError => err
|
169
|
+
# :nocov:
|
169
170
|
raise LoadError, "#{err.message}: unknown problem loading driver, "\
|
170
171
|
"aborting!"
|
172
|
+
# :nocov:
|
171
173
|
rescue NameError => err
|
174
|
+
# :nocov:
|
172
175
|
raise LoadError, "#{err.message}: unknown problem loading driver, "\
|
173
176
|
"aborting!"
|
177
|
+
# :nocov:
|
174
178
|
end
|
175
179
|
end
|
176
180
|
end
|
@@ -222,7 +226,9 @@ module Unobtainium
|
|
222
226
|
if not @impl.nil? and @impl.respond_to?(meth)
|
223
227
|
return @impl.send(meth.to_s, *args, &block)
|
224
228
|
end
|
229
|
+
# :nocov:
|
225
230
|
return super
|
231
|
+
# :nocov:
|
226
232
|
end
|
227
233
|
|
228
234
|
private
|
data/lib/unobtainium/runtime.rb
CHANGED
@@ -131,10 +131,6 @@ module Unobtainium
|
|
131
131
|
to_send += children.collect(&:pid)
|
132
132
|
end
|
133
133
|
|
134
|
-
if to_send.empty?
|
135
|
-
raise "This should not happen. I have no pids to send a signal to!"
|
136
|
-
end
|
137
|
-
|
138
134
|
# Alright, send the signal!
|
139
135
|
to_send.each do |pid|
|
140
136
|
# rubocop:disable Lint/HandleExceptions
|
data/lib/unobtainium/version.rb
CHANGED
data/lib/unobtainium/world.rb
CHANGED
@@ -8,8 +8,9 @@
|
|
8
8
|
#
|
9
9
|
require 'unobtainium'
|
10
10
|
|
11
|
+
require 'collapsium-config'
|
12
|
+
|
11
13
|
require 'unobtainium/driver'
|
12
|
-
require 'unobtainium/config'
|
13
14
|
require 'unobtainium/runtime'
|
14
15
|
|
15
16
|
module Unobtainium
|
@@ -17,32 +18,50 @@ module Unobtainium
|
|
17
18
|
# The World module combines other modules, defining simpler entry points
|
18
19
|
# into the gem's functionality.
|
19
20
|
module World
|
21
|
+
|
20
22
|
##
|
21
|
-
# Modules can have class methods, too
|
23
|
+
# Modules can have class methods, too, but it's a little more verbose to
|
24
|
+
# provide them.
|
22
25
|
module ClassMethods
|
23
|
-
# Set the
|
26
|
+
# Set the configuration file
|
24
27
|
def config_file=(name)
|
25
|
-
|
28
|
+
::Collapsium::Config.config_file = name
|
26
29
|
end
|
27
30
|
|
28
31
|
# @return [String] the config file path, defaulting to 'config/config.yml'
|
29
32
|
def config_file
|
30
|
-
return
|
33
|
+
return ::Collapsium::Config.config_file
|
31
34
|
end
|
32
|
-
end # module ClassMethods
|
33
|
-
extend ClassMethods
|
34
35
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
36
|
+
# In order for Unobtainium::World to include Collapsium::Config
|
37
|
+
# functionality, it has to be inherited when the former is
|
38
|
+
# included...
|
39
|
+
def included(klass)
|
40
|
+
set_config_path_default
|
41
|
+
|
42
|
+
klass.class_eval do
|
43
|
+
include ::Collapsium::Config
|
43
44
|
end
|
44
45
|
end
|
45
|
-
|
46
|
+
|
47
|
+
# ... and when it's extended.
|
48
|
+
def extended(world)
|
49
|
+
# :nocov:
|
50
|
+
set_config_path_default
|
51
|
+
|
52
|
+
world.extend(::Collapsium::Config)
|
53
|
+
# :nocov:
|
54
|
+
end
|
55
|
+
|
56
|
+
def set_config_path_default
|
57
|
+
# Override collapsium-config's default config path
|
58
|
+
if ::Collapsium::Config.config_file == \
|
59
|
+
::Collapsium::Config::DEFAULT_CONFIG_PATH
|
60
|
+
::Collapsium::Config.config_file = 'config/config.yml'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end # module ClassMethods
|
64
|
+
extend ClassMethods
|
46
65
|
|
47
66
|
##
|
48
67
|
# (see Driver#create)
|
@@ -89,12 +108,14 @@ module Unobtainium
|
|
89
108
|
# gets created or not.
|
90
109
|
at_end = config.fetch("at_end", "quit")
|
91
110
|
dtor = proc do |the_driver|
|
111
|
+
# :nocov:
|
92
112
|
if the_driver.nil?
|
93
113
|
return
|
94
114
|
end
|
95
115
|
|
96
116
|
meth = at_end.to_sym
|
97
117
|
the_driver.send(meth)
|
118
|
+
# :nocov:
|
98
119
|
end
|
99
120
|
return ::Unobtainium::Runtime.instance.store_with_if(key, dtor) do
|
100
121
|
::Unobtainium::Driver.create(label, options)
|
Binary file
|
data/media/video.ogv
ADDED
Binary file
|
data/spec/driver_spec.rb
CHANGED
@@ -37,17 +37,19 @@ describe ::Unobtainium::Driver do
|
|
37
37
|
::Unobtainium::Driver.register_implementation(MockDriver, "mock_driver.rb")
|
38
38
|
end
|
39
39
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
40
|
+
describe "driver registration" do
|
41
|
+
it "refuses to register a driver with missing methods" do
|
42
|
+
expect do
|
43
|
+
::Unobtainium::Driver.register_implementation(FakeDriver, __FILE__)
|
44
|
+
end.to raise_error(LoadError)
|
45
|
+
end
|
45
46
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
47
|
+
it "refuses to register the same driver twice from different locations" do
|
48
|
+
expect do
|
49
|
+
::Unobtainium::Driver.register_implementation(MockDriver, __FILE__ + '1')
|
50
|
+
::Unobtainium::Driver.register_implementation(MockDriver, __FILE__ + '2')
|
51
|
+
end.to raise_error(LoadError)
|
52
|
+
end
|
51
53
|
end
|
52
54
|
|
53
55
|
it "verifies arguments" do
|
@@ -66,29 +68,39 @@ describe ::Unobtainium::Driver do
|
|
66
68
|
end.to raise_error(ArgumentError)
|
67
69
|
end
|
68
70
|
|
69
|
-
|
70
|
-
|
71
|
-
|
71
|
+
describe "driver creation" do
|
72
|
+
it "creates no driver with an unknown label" do
|
73
|
+
expect { ::Unobtainium::Driver.create(:nope) }.to raise_error(LoadError)
|
74
|
+
end
|
72
75
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
76
|
+
it "fails preconditions correctly" do
|
77
|
+
expect do
|
78
|
+
::Unobtainium::Driver.create(:raise_mock)
|
79
|
+
end.to raise_error(RuntimeError)
|
80
|
+
end
|
78
81
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
+
it "creates a driver correctly" do
|
83
|
+
::Unobtainium::Driver.create(:mock)
|
84
|
+
end
|
82
85
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
86
|
+
it "does not create a driver with a nil label" do
|
87
|
+
expect do
|
88
|
+
::Unobtainium::Driver.create(nil)
|
89
|
+
end.to raise_error(ArgumentError)
|
90
|
+
end
|
87
91
|
end
|
88
92
|
|
89
|
-
|
90
|
-
|
91
|
-
|
93
|
+
describe "driver behaviour" do
|
94
|
+
it "delegates to created driver class" do
|
95
|
+
drv = ::Unobtainium::Driver.create(:mock, foo: 42)
|
96
|
+
expect(drv.respond_to?(:passed_options)).to be_truthy
|
97
|
+
_ = drv.passed_options
|
98
|
+
end
|
99
|
+
|
100
|
+
it "passes options through correctly" do
|
101
|
+
drv = ::Unobtainium::Driver.create(:mock, foo: 42)
|
102
|
+
expect(drv.passed_options).to eql foo: 42
|
103
|
+
end
|
92
104
|
end
|
93
105
|
|
94
106
|
describe 'modules' do
|
data/spec/runner_spec.rb
CHANGED
@@ -63,4 +63,23 @@ describe ::Unobtainium::Support::Runner do
|
|
63
63
|
expect { runner.start }.to raise_error(RuntimeError)
|
64
64
|
runner.wait
|
65
65
|
end
|
66
|
+
|
67
|
+
it "kills when destroyed" do
|
68
|
+
runner = ::Unobtainium::Support::Runner.new("foo", %w(sleep 30))
|
69
|
+
runner.start
|
70
|
+
expect(runner.pid).not_to be_nil
|
71
|
+
runner.destroy
|
72
|
+
expect(runner.pid).to be_nil
|
73
|
+
end
|
74
|
+
|
75
|
+
it "cannot be killed twice" do
|
76
|
+
runner = ::Unobtainium::Support::Runner.new("foo", %w(sleep 30))
|
77
|
+
runner.start
|
78
|
+
expect(runner.pid).not_to be_nil
|
79
|
+
runner.kill
|
80
|
+
|
81
|
+
expect do
|
82
|
+
runner.kill
|
83
|
+
end.to raise_error
|
84
|
+
end
|
66
85
|
end
|
data/spec/spec_helper.rb
CHANGED
data/unobtainium.gemspec
CHANGED
@@ -54,6 +54,7 @@ Gem::Specification.new do |spec|
|
|
54
54
|
spec.add_development_dependency "cucumber"
|
55
55
|
|
56
56
|
spec.add_dependency "sys-proctable", "~> 1.0"
|
57
|
+
spec.add_dependency "collapsium-config", "~> 0.1"
|
57
58
|
end
|
58
59
|
# rubocop:enable Style/SpaceAroundOperators
|
59
60
|
# rubocop:enable Style/UnneededPercentQ, Style/ExtraSpacing
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unobtainium
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.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-05-
|
11
|
+
date: 2016-05-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -164,6 +164,20 @@ dependencies:
|
|
164
164
|
- - "~>"
|
165
165
|
- !ruby/object:Gem::Version
|
166
166
|
version: '1.0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: collapsium-config
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - "~>"
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0.1'
|
174
|
+
type: :runtime
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - "~>"
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0.1'
|
167
181
|
description: "\n Unobtainium wraps Selenium and Appium in a simple driver abstraction
|
168
182
|
so that\n test code can more easily cover desktop browsers, mobile browsers and
|
169
183
|
mobile\n apps.\n\n Some additional useful functionality for the maintenance
|
@@ -192,35 +206,21 @@ files:
|
|
192
206
|
- features/support/env.rb
|
193
207
|
- features/world.feature
|
194
208
|
- lib/unobtainium.rb
|
195
|
-
- lib/unobtainium/config.rb
|
196
209
|
- lib/unobtainium/driver.rb
|
197
210
|
- lib/unobtainium/drivers/appium.rb
|
198
211
|
- lib/unobtainium/drivers/phantom.rb
|
199
212
|
- lib/unobtainium/drivers/selenium.rb
|
200
|
-
- lib/unobtainium/pathed_hash.rb
|
201
|
-
- lib/unobtainium/recursive_merge.rb
|
202
213
|
- lib/unobtainium/runtime.rb
|
203
214
|
- lib/unobtainium/support/port_scanner.rb
|
204
215
|
- lib/unobtainium/support/runner.rb
|
205
216
|
- lib/unobtainium/support/util.rb
|
206
217
|
- lib/unobtainium/version.rb
|
207
218
|
- lib/unobtainium/world.rb
|
208
|
-
-
|
209
|
-
-
|
210
|
-
- spec/data/arraymerge-local.yaml
|
211
|
-
- spec/data/arraymerge.yaml
|
219
|
+
- media/screenshot.jpg
|
220
|
+
- media/video.ogv
|
212
221
|
- spec/data/driverconfig.yml
|
213
|
-
- spec/data/empty.yml
|
214
|
-
- spec/data/hash.yml
|
215
|
-
- spec/data/hashmerge-local.yml
|
216
|
-
- spec/data/hashmerge.yml
|
217
|
-
- spec/data/mergefail-local.yaml
|
218
|
-
- spec/data/mergefail.yaml
|
219
|
-
- spec/data/test.json
|
220
|
-
- spec/data/world.yml
|
221
222
|
- spec/driver_spec.rb
|
222
223
|
- spec/mock_driver.rb
|
223
|
-
- spec/pathed_hash_spec.rb
|
224
224
|
- spec/port_scanner_spec.rb
|
225
225
|
- spec/runner_spec.rb
|
226
226
|
- spec/runtime_spec.rb
|
@@ -257,22 +257,9 @@ test_files:
|
|
257
257
|
- features/step_definitions/steps.rb
|
258
258
|
- features/support/env.rb
|
259
259
|
- features/world.feature
|
260
|
-
- spec/config_spec.rb
|
261
|
-
- spec/data/array.yaml
|
262
|
-
- spec/data/arraymerge-local.yaml
|
263
|
-
- spec/data/arraymerge.yaml
|
264
260
|
- spec/data/driverconfig.yml
|
265
|
-
- spec/data/empty.yml
|
266
|
-
- spec/data/hash.yml
|
267
|
-
- spec/data/hashmerge-local.yml
|
268
|
-
- spec/data/hashmerge.yml
|
269
|
-
- spec/data/mergefail-local.yaml
|
270
|
-
- spec/data/mergefail.yaml
|
271
|
-
- spec/data/test.json
|
272
|
-
- spec/data/world.yml
|
273
261
|
- spec/driver_spec.rb
|
274
262
|
- spec/mock_driver.rb
|
275
|
-
- spec/pathed_hash_spec.rb
|
276
263
|
- spec/port_scanner_spec.rb
|
277
264
|
- spec/runner_spec.rb
|
278
265
|
- spec/runtime_spec.rb
|
data/lib/unobtainium/config.rb
DELETED
@@ -1,304 +0,0 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
#
|
3
|
-
# unobtainium
|
4
|
-
# https://github.com/jfinkhaeuser/unobtainium
|
5
|
-
#
|
6
|
-
# Copyright (c) 2016 Jens Finkhaeuser and other unobtainium contributors.
|
7
|
-
# All rights reserved.
|
8
|
-
#
|
9
|
-
|
10
|
-
require 'pathname'
|
11
|
-
|
12
|
-
require 'unobtainium/pathed_hash'
|
13
|
-
|
14
|
-
module Unobtainium
|
15
|
-
##
|
16
|
-
# The Config class extends PathedHash by two main pieces of functionality:
|
17
|
-
#
|
18
|
-
# - it loads configuration files and turns them into pathed hashes, and
|
19
|
-
# - it treats environment variables as overriding anything contained in
|
20
|
-
# the configuration file.
|
21
|
-
#
|
22
|
-
# For configuration file loading, a named configuration file will be laoaded
|
23
|
-
# if present. A file with the same name but `-local` appended before the
|
24
|
-
# extension will be loaded as well, overriding any values in the original
|
25
|
-
# configuration file.
|
26
|
-
#
|
27
|
-
# For environment variable support, any environment variable named like a
|
28
|
-
# path into the configuration hash, but with separators transformed to
|
29
|
-
# underscore and all letters capitalized will override values from the
|
30
|
-
# configuration files under that path, i.e. `FOO_BAR` will override `'foo.bar'`.
|
31
|
-
#
|
32
|
-
# Environment variables can contain JSON *only*; if the value can be parsed
|
33
|
-
# as JSON, it becomes a Hash in the configuration tree. If it cannot be parsed
|
34
|
-
# as JSON, it remains a string.
|
35
|
-
#
|
36
|
-
# **Note:** if your configuration file's top-level structure is an array, it
|
37
|
-
# will be returned as a hash with a 'config' key that maps to your file's
|
38
|
-
# contents.
|
39
|
-
# That means that if you are trying to merge a hash with an array config, the
|
40
|
-
# result may be unexpected.
|
41
|
-
class Config < PathedHash
|
42
|
-
# @api private
|
43
|
-
# Very simple YAML parser
|
44
|
-
class YAMLParser
|
45
|
-
require 'yaml'
|
46
|
-
|
47
|
-
# @return parsed string
|
48
|
-
def self.parse(string)
|
49
|
-
YAML.load(string)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
private_constant :YAMLParser
|
53
|
-
|
54
|
-
# @api private
|
55
|
-
# Very simple JSON parser
|
56
|
-
class JSONParser
|
57
|
-
require 'json'
|
58
|
-
|
59
|
-
# @return parsed string
|
60
|
-
def self.parse(string)
|
61
|
-
JSON.parse(string)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
private_constant :JSONParser
|
65
|
-
|
66
|
-
PathedHash::READ_METHODS.each do |method|
|
67
|
-
# Wrap all read functions into something that checks for environment
|
68
|
-
# variables first.
|
69
|
-
define_method(method) do |*args, &block|
|
70
|
-
# If there are no arguments, there's nothing to do with paths. Just
|
71
|
-
# delegate to the hash.
|
72
|
-
if args.empty?
|
73
|
-
return super(*args, &block)
|
74
|
-
end
|
75
|
-
|
76
|
-
# We'll make it rather simple: since the first argument is a key, we
|
77
|
-
# will just transform it to the matching environment variable name,
|
78
|
-
# and see if that environment variable is set.
|
79
|
-
env_name = args[0].to_s.upcase.gsub(split_pattern, '_')
|
80
|
-
contents = nil
|
81
|
-
if env_name != '_'
|
82
|
-
contents = ENV[env_name]
|
83
|
-
end
|
84
|
-
|
85
|
-
# No environment variable set? Fine, just do the usual thing.
|
86
|
-
if contents.nil? or contents.empty?
|
87
|
-
return super(*args, &block)
|
88
|
-
end
|
89
|
-
|
90
|
-
# With an environment variable, we will try to parse it as JSON first.
|
91
|
-
begin
|
92
|
-
return JSONParser.parse(contents)
|
93
|
-
rescue JSON::ParserError
|
94
|
-
return contents
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
class << self
|
100
|
-
# @api private
|
101
|
-
# Mapping of file name extensions to parser types.
|
102
|
-
FILE_TO_PARSER = {
|
103
|
-
'.yml' => YAMLParser,
|
104
|
-
'.yaml' => YAMLParser,
|
105
|
-
'.json' => JSONParser,
|
106
|
-
}.freeze
|
107
|
-
private_constant :FILE_TO_PARSER
|
108
|
-
|
109
|
-
# @api private
|
110
|
-
# If the config file contains an Array, this is what they key of the
|
111
|
-
# returned Hash will be.
|
112
|
-
ARRAY_KEY = 'config'.freeze
|
113
|
-
private_constant :ARRAY_KEY
|
114
|
-
|
115
|
-
##
|
116
|
-
# Loads a configuration file with the given file name. The format is
|
117
|
-
# detected based on one of the extensions in FILE_TO_PARSER.
|
118
|
-
#
|
119
|
-
# @param path [String] the path of the configuration file to load.
|
120
|
-
# @param resolve_extensions [Boolean] flag whether to resolve configuration
|
121
|
-
# hash extensions. (see `#resolve_extensions`)
|
122
|
-
def load_config(path, resolve_extensions = true)
|
123
|
-
# Load base and local configuration files
|
124
|
-
base, config = load_base_config(path)
|
125
|
-
_, local_config = load_local_config(base)
|
126
|
-
|
127
|
-
# Merge local configuration
|
128
|
-
config.recursive_merge!(local_config)
|
129
|
-
|
130
|
-
# Create config from the result
|
131
|
-
cfg = Config.new(config)
|
132
|
-
|
133
|
-
# Now resolve config hashes that extend other hashes.
|
134
|
-
if resolve_extensions
|
135
|
-
cfg.resolve_extensions!
|
136
|
-
end
|
137
|
-
|
138
|
-
return cfg
|
139
|
-
end
|
140
|
-
|
141
|
-
private
|
142
|
-
|
143
|
-
def load_base_config(path)
|
144
|
-
# Make sure the format is recognized early on.
|
145
|
-
base = Pathname.new(path)
|
146
|
-
formats = FILE_TO_PARSER.keys
|
147
|
-
if not formats.include?(base.extname)
|
148
|
-
raise ArgumentError, "Files with extension '#{base.extname}' are not"\
|
149
|
-
" recognized; please use one of #{formats}!"
|
150
|
-
end
|
151
|
-
|
152
|
-
# Don't check the path whether it exists - loading a nonexistent
|
153
|
-
# file will throw a nice error for the user to catch.
|
154
|
-
file = base.open
|
155
|
-
contents = file.read
|
156
|
-
|
157
|
-
# Parse the contents.
|
158
|
-
config = FILE_TO_PARSER[base.extname].parse(contents)
|
159
|
-
|
160
|
-
return base, PathedHash.new(hashify(config))
|
161
|
-
end
|
162
|
-
|
163
|
-
def load_local_config(base)
|
164
|
-
# Now construct a file name for a local override.
|
165
|
-
local = Pathname.new(base.dirname)
|
166
|
-
local = local.join(base.basename(base.extname).to_s + "-local" +
|
167
|
-
base.extname)
|
168
|
-
if not local.exist?
|
169
|
-
return local, nil
|
170
|
-
end
|
171
|
-
|
172
|
-
# We know the local override file exists, but we do want to let any errors
|
173
|
-
# go through that come with reading or parsing it.
|
174
|
-
file = local.open
|
175
|
-
contents = file.read
|
176
|
-
|
177
|
-
local_config = FILE_TO_PARSER[base.extname].parse(contents)
|
178
|
-
|
179
|
-
return local, PathedHash.new(hashify(local_config))
|
180
|
-
end
|
181
|
-
|
182
|
-
def hashify(data)
|
183
|
-
if data.nil?
|
184
|
-
return {}
|
185
|
-
end
|
186
|
-
if data.is_a? Array
|
187
|
-
data = { ARRAY_KEY => data }
|
188
|
-
end
|
189
|
-
return data
|
190
|
-
end
|
191
|
-
end # class << self
|
192
|
-
|
193
|
-
##
|
194
|
-
# Resolve extensions in configuration hashes. If your hash contains e.g.:
|
195
|
-
#
|
196
|
-
# ```yaml
|
197
|
-
# foo:
|
198
|
-
# bar:
|
199
|
-
# some: value
|
200
|
-
# baz:
|
201
|
-
# extends: bar
|
202
|
-
# ```
|
203
|
-
#
|
204
|
-
# Then `'foo.baz.some'` will equal `'value'` after resolving extensions. Note
|
205
|
-
# that `:load_config` calls this function, so normally you don't need to call
|
206
|
-
# it yourself. You can switch this behaviour off in `:load_config`.
|
207
|
-
#
|
208
|
-
# Note that this process has some intended side-effects:
|
209
|
-
#
|
210
|
-
# 1. If a hash can't be extended because the base cannot be found, an error
|
211
|
-
# is raised.
|
212
|
-
# 1. If a hash got successfully extended, the `extends` keyword itself is
|
213
|
-
# removed from the hash.
|
214
|
-
# 1. In a successfully extended hash, an `base` keyword, which contains
|
215
|
-
# the name of the base. In case of multiple recursive extensions, the
|
216
|
-
# final base is stored here.
|
217
|
-
#
|
218
|
-
# Also note that all of this means that :extends and :base are reserved
|
219
|
-
# keywords that cannot be used in configuration files other than for this
|
220
|
-
# purpose!
|
221
|
-
def resolve_extensions!
|
222
|
-
recursive_merge("", "")
|
223
|
-
end
|
224
|
-
|
225
|
-
##
|
226
|
-
# Same as `dup.resolve_extensions!`
|
227
|
-
def resolve_extensions
|
228
|
-
dup.resolve_extensions!
|
229
|
-
end
|
230
|
-
|
231
|
-
private
|
232
|
-
|
233
|
-
def recursive_merge(parent, key)
|
234
|
-
loop do
|
235
|
-
full_key = "#{parent}#{separator}#{key}"
|
236
|
-
|
237
|
-
# Recurse down to the remaining root of the hierarchy
|
238
|
-
base = full_key
|
239
|
-
derived = nil
|
240
|
-
loop do
|
241
|
-
new_base, new_derived = resolve_extension(parent, base)
|
242
|
-
|
243
|
-
if new_derived.nil?
|
244
|
-
break
|
245
|
-
end
|
246
|
-
|
247
|
-
base = new_base
|
248
|
-
derived = new_derived
|
249
|
-
end
|
250
|
-
|
251
|
-
# If recursion found nothing to merge, we're done!
|
252
|
-
if derived.nil?
|
253
|
-
break
|
254
|
-
end
|
255
|
-
|
256
|
-
# Otherwise, merge what needs merging and continue
|
257
|
-
merge_extension(base, derived)
|
258
|
-
end
|
259
|
-
end
|
260
|
-
|
261
|
-
def resolve_extension(grandparent, parent)
|
262
|
-
fetch(parent, {}).each do |key, value|
|
263
|
-
# Recurse into hash values
|
264
|
-
if value.is_a? Hash
|
265
|
-
recursive_merge(parent, key)
|
266
|
-
end
|
267
|
-
|
268
|
-
# No hash, ignore any keys other than the special "extends" key
|
269
|
-
if key != "extends"
|
270
|
-
next
|
271
|
-
end
|
272
|
-
|
273
|
-
# If the key is "extends", return a normalized version of its value.
|
274
|
-
full_value = value.dup
|
275
|
-
if not full_value.start_with?(separator)
|
276
|
-
full_value = "#{grandparent}#{separator}#{value}"
|
277
|
-
end
|
278
|
-
|
279
|
-
if full_value == parent
|
280
|
-
next
|
281
|
-
end
|
282
|
-
return full_value, parent
|
283
|
-
end
|
284
|
-
|
285
|
-
return nil, nil
|
286
|
-
end
|
287
|
-
|
288
|
-
def merge_extension(base, derived)
|
289
|
-
# Remove old 'extends' key, but remember the value
|
290
|
-
extends = self[derived]["extends"]
|
291
|
-
self[derived].delete("extends")
|
292
|
-
|
293
|
-
# Recursively merge base into derived without overwriting
|
294
|
-
self[derived].extend(::Unobtainium::RecursiveMerge)
|
295
|
-
self[derived].recursive_merge!(self[base], false)
|
296
|
-
|
297
|
-
# Then set the "base" keyword, but only if it's not yet set.
|
298
|
-
if not self[derived]["base"].nil?
|
299
|
-
return
|
300
|
-
end
|
301
|
-
self[derived]["base"] = extends
|
302
|
-
end
|
303
|
-
end # class Config
|
304
|
-
end # module Unobtainium
|