unobtainium 0.5.1 → 0.6.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.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
@@ -1,199 +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 'unobtainium/recursive_merge'
|
11
|
-
|
12
|
-
module Unobtainium
|
13
|
-
|
14
|
-
##
|
15
|
-
# The PathedHash class wraps Hash by offering pathed access on top of
|
16
|
-
# regular access, i.e. instead of `h["first"]["second"]` you can write
|
17
|
-
# `h["first.second"]`.
|
18
|
-
#
|
19
|
-
# The main benefit is much simpler code for accessing nested structured.
|
20
|
-
# For any given path, PathedHash will return nil from `[]` if *any* of
|
21
|
-
# the path components do not exist.
|
22
|
-
#
|
23
|
-
# Similarly, intermediate nodes will be created when you write a value
|
24
|
-
# for a path.
|
25
|
-
#
|
26
|
-
# PathedHash also includes RecursiveMerge.
|
27
|
-
class PathedHash
|
28
|
-
include RecursiveMerge
|
29
|
-
|
30
|
-
DEFAULT_PROC = proc do |hash, key|
|
31
|
-
case key
|
32
|
-
when String
|
33
|
-
sym = key.to_sym
|
34
|
-
hash[sym] if hash.key?(sym)
|
35
|
-
when Symbol
|
36
|
-
str = key.to_s
|
37
|
-
hash[str] if hash.key?(str)
|
38
|
-
end
|
39
|
-
end.freeze
|
40
|
-
|
41
|
-
##
|
42
|
-
# Initializer. Accepts `nil`, hashes or pathed hashes.
|
43
|
-
#
|
44
|
-
# @param init [NilClass, Hash] initial values.
|
45
|
-
def initialize(init = nil)
|
46
|
-
if init.nil?
|
47
|
-
@data = {}
|
48
|
-
else
|
49
|
-
@data = init.dup
|
50
|
-
end
|
51
|
-
@separator = '.'
|
52
|
-
|
53
|
-
@data.default_proc = DEFAULT_PROC
|
54
|
-
end
|
55
|
-
|
56
|
-
# @return [String] the separator is the character or pattern splitting paths.
|
57
|
-
attr_accessor :separator
|
58
|
-
|
59
|
-
# @api private
|
60
|
-
# Methods redefined to support pathed read access.
|
61
|
-
READ_METHODS = [
|
62
|
-
:[], :default, :delete, :fetch, :has_key?, :include?, :key?, :member?,
|
63
|
-
].freeze
|
64
|
-
|
65
|
-
# @api private
|
66
|
-
# Methods redefined to support pathed write access.
|
67
|
-
WRITE_METHODS = [
|
68
|
-
:[]=, :store,
|
69
|
-
].freeze
|
70
|
-
|
71
|
-
##
|
72
|
-
# @return [RegExp] the pattern to split paths at; based on `separator`
|
73
|
-
def split_pattern
|
74
|
-
/(?<!\\)#{Regexp.escape(@separator)}/
|
75
|
-
end
|
76
|
-
|
77
|
-
(READ_METHODS + WRITE_METHODS).each do |method|
|
78
|
-
# Wrap all accessor functions to deal with paths
|
79
|
-
define_method(method) do |*args, &block|
|
80
|
-
# If there are no arguments, there's nothing to do with paths. Just
|
81
|
-
# delegate to the hash.
|
82
|
-
if args.empty?
|
83
|
-
return @data.send(method, *args, &block)
|
84
|
-
end
|
85
|
-
|
86
|
-
# With any of the dispatch methods, we know that the first argument has
|
87
|
-
# to be a key. We'll try to split it by the path separator.
|
88
|
-
components = args[0].to_s.split(split_pattern)
|
89
|
-
loop do
|
90
|
-
if components.empty? or not components[0].empty?
|
91
|
-
break
|
92
|
-
end
|
93
|
-
components.shift
|
94
|
-
end
|
95
|
-
|
96
|
-
# If there are no components, return self/the root
|
97
|
-
if components.empty?
|
98
|
-
return self
|
99
|
-
end
|
100
|
-
|
101
|
-
# This PathedHash is already the leaf-most Hash
|
102
|
-
if components.length == 1
|
103
|
-
# Weird edge case: if we didn't have to shift anything, then it's
|
104
|
-
# possible we inadvertently changed a symbol key into a string key,
|
105
|
-
# which could mean looking fails.
|
106
|
-
# We can detect that by comparing copy[0] to a symbolized version of
|
107
|
-
# components[0].
|
108
|
-
copy = args.dup
|
109
|
-
if copy[0] != components[0].to_sym
|
110
|
-
copy[0] = components[0]
|
111
|
-
end
|
112
|
-
return @data.send(method, *copy, &block)
|
113
|
-
end
|
114
|
-
|
115
|
-
# Deal with other paths. The frustrating part here is that for nested
|
116
|
-
# hashes, only this outermost one is guaranteed to know anything about
|
117
|
-
# path splitting, so we'll have to recurse down to the leaf here.
|
118
|
-
#
|
119
|
-
# For write methods, we need to create intermediary hashes.
|
120
|
-
leaf = recursive_fetch(components, @data,
|
121
|
-
create: WRITE_METHODS.include?(method))
|
122
|
-
if leaf.is_a? Hash
|
123
|
-
leaf.default_proc = DEFAULT_PROC
|
124
|
-
end
|
125
|
-
if leaf.nil?
|
126
|
-
leaf = @data
|
127
|
-
end
|
128
|
-
|
129
|
-
# If we have a leaf, we want to send the requested method to that
|
130
|
-
# leaf.
|
131
|
-
copy = args.dup
|
132
|
-
copy[0] = components.last
|
133
|
-
return leaf.send(method, *copy, &block)
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
# @return [String] string representation
|
138
|
-
def to_s
|
139
|
-
@data.to_s
|
140
|
-
end
|
141
|
-
|
142
|
-
# @return [PathedHash] duplicate, as `.dup` usually works
|
143
|
-
def dup
|
144
|
-
PathedHash.new(@data.dup)
|
145
|
-
end
|
146
|
-
|
147
|
-
# In place merge, as it usually works for hashes.
|
148
|
-
# @return [PathedHash] self
|
149
|
-
def merge!(*args, &block)
|
150
|
-
# FIXME: we may need other methods like this. This is used by
|
151
|
-
# RecursiveMerge, so we know it's required.
|
152
|
-
PathedHash.new(super)
|
153
|
-
end
|
154
|
-
|
155
|
-
##
|
156
|
-
# Map any missing method to the Hash implementation
|
157
|
-
def respond_to_missing?(meth, include_private = false)
|
158
|
-
if not @data.nil? and @data.respond_to?(meth, include_private)
|
159
|
-
return true
|
160
|
-
end
|
161
|
-
return super
|
162
|
-
end
|
163
|
-
|
164
|
-
##
|
165
|
-
# Map any missing method to the Hash implementation
|
166
|
-
def method_missing(meth, *args, &block)
|
167
|
-
if not @data.nil? and @data.respond_to?(meth)
|
168
|
-
return @data.send(meth.to_s, *args, &block)
|
169
|
-
end
|
170
|
-
return super
|
171
|
-
end
|
172
|
-
|
173
|
-
private
|
174
|
-
|
175
|
-
##
|
176
|
-
# Given the path components, recursively fetch any but the last key.
|
177
|
-
def recursive_fetch(path, data, options = {})
|
178
|
-
# For the leaf element, we do nothing because that's where we want to
|
179
|
-
# dispatch to.
|
180
|
-
if path.length == 1
|
181
|
-
return data
|
182
|
-
end
|
183
|
-
|
184
|
-
# Split path into head and tail; for the next iteration, we'll look use only
|
185
|
-
# head, and pass tail on recursively.
|
186
|
-
head = path[0]
|
187
|
-
tail = path.slice(1, path.length)
|
188
|
-
|
189
|
-
# If we're a write function, then we need to create intermediary objects,
|
190
|
-
# i.e. what's at head if nothing is there.
|
191
|
-
if options[:create] and data[head].nil?
|
192
|
-
data[head] = {}
|
193
|
-
end
|
194
|
-
|
195
|
-
# Ok, recurse.
|
196
|
-
return recursive_fetch(tail, data[head], options)
|
197
|
-
end
|
198
|
-
end # class PathedHash
|
199
|
-
end # module Unobtainium
|
@@ -1,55 +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
|
-
module Unobtainium
|
11
|
-
##
|
12
|
-
# Provides recursive merge functions for hashes. Used in PathedHash.
|
13
|
-
module RecursiveMerge
|
14
|
-
##
|
15
|
-
# Recursively merge `:other` into this Hash.
|
16
|
-
#
|
17
|
-
# This starts by merging the leaf-most Hash entries. Arrays are merged
|
18
|
-
# by addition.
|
19
|
-
#
|
20
|
-
# For everything that's neither Hash or Array, if the `:overwrite`
|
21
|
-
# parameter is true, the entry from `:other` is used. Otherwise the entry
|
22
|
-
# from `:self` is used.
|
23
|
-
#
|
24
|
-
# @param other [Hash] the hash to merge into `:self`
|
25
|
-
# @param overwrite [Boolean] see method description.
|
26
|
-
def recursive_merge!(other, overwrite = true)
|
27
|
-
if other.nil?
|
28
|
-
return self
|
29
|
-
end
|
30
|
-
|
31
|
-
merger = proc do |_, v1, v2|
|
32
|
-
# rubocop:disable Style/GuardClause
|
33
|
-
if v1.is_a? Hash and v2.is_a? Hash
|
34
|
-
next v1.merge(v2, &merger)
|
35
|
-
elsif v1.is_a? Array and v2.is_a? Array
|
36
|
-
next v1 + v2
|
37
|
-
end
|
38
|
-
if overwrite
|
39
|
-
next v2
|
40
|
-
else
|
41
|
-
next v1
|
42
|
-
end
|
43
|
-
# rubocop:enable Style/GuardClause
|
44
|
-
end
|
45
|
-
merge!(other, &merger)
|
46
|
-
end
|
47
|
-
|
48
|
-
##
|
49
|
-
# Same as `dup.recursive_merge!`
|
50
|
-
# @param (see #recursive_merge!)
|
51
|
-
def recursive_merge(other, overwrite = true)
|
52
|
-
dup.recursive_merge!(other, overwrite)
|
53
|
-
end
|
54
|
-
end # module RecursiveMerge
|
55
|
-
end # module Unobtainium
|
data/spec/config_spec.rb
DELETED
@@ -1,119 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require_relative '../lib/unobtainium/config'
|
3
|
-
|
4
|
-
describe ::Unobtainium::Config do
|
5
|
-
before :each do
|
6
|
-
@data_path = File.join(File.dirname(__FILE__), 'data')
|
7
|
-
end
|
8
|
-
|
9
|
-
it "fails to load a nonexistent file" do
|
10
|
-
expect { ::Unobtainium::Config.load_config("_nope_.yaml") }.to \
|
11
|
-
raise_error Errno::ENOENT
|
12
|
-
end
|
13
|
-
|
14
|
-
it "is asked to load an unrecognized extension" do
|
15
|
-
expect { ::Unobtainium::Config.load_config("_nope_.cfg") }.to \
|
16
|
-
raise_error ArgumentError
|
17
|
-
end
|
18
|
-
|
19
|
-
it "loads a yaml config with a top-level hash correctly" do
|
20
|
-
config = File.join(@data_path, 'hash.yml')
|
21
|
-
cfg = ::Unobtainium::Config.load_config(config)
|
22
|
-
|
23
|
-
expect(cfg["foo"]).to eql "bar"
|
24
|
-
expect(cfg["baz"]).to eql "quux"
|
25
|
-
end
|
26
|
-
|
27
|
-
it "loads a yaml config with a top-level array correctly" do
|
28
|
-
config = File.join(@data_path, 'array.yaml')
|
29
|
-
cfg = ::Unobtainium::Config.load_config(config)
|
30
|
-
|
31
|
-
expect(cfg["config"]).to eql %w(foo bar)
|
32
|
-
end
|
33
|
-
|
34
|
-
it "loads a JSON config correctly" do
|
35
|
-
config = File.join(@data_path, 'test.json')
|
36
|
-
cfg = ::Unobtainium::Config.load_config(config)
|
37
|
-
|
38
|
-
expect(cfg["foo"]).to eql "bar"
|
39
|
-
expect(cfg["baz"]).to eql 42
|
40
|
-
end
|
41
|
-
|
42
|
-
it "merges a hashed config correctly" do
|
43
|
-
config = File.join(@data_path, 'hashmerge.yml')
|
44
|
-
cfg = ::Unobtainium::Config.load_config(config)
|
45
|
-
|
46
|
-
expect(cfg["asdf"]).to eql 1
|
47
|
-
expect(cfg["foo.bar"]).to eql "baz"
|
48
|
-
expect(cfg["foo.quux"]).to eql [1, 42]
|
49
|
-
expect(cfg["foo.baz"]).to eql 3.14
|
50
|
-
expect(cfg["blargh"]).to eql false
|
51
|
-
end
|
52
|
-
|
53
|
-
it "merges an array config correctly" do
|
54
|
-
config = File.join(@data_path, 'arraymerge.yaml')
|
55
|
-
cfg = ::Unobtainium::Config.load_config(config)
|
56
|
-
|
57
|
-
expect(cfg["config"]).to eql %w(foo bar baz)
|
58
|
-
end
|
59
|
-
|
60
|
-
it "merges an array and hash config" do
|
61
|
-
config = File.join(@data_path, 'mergefail.yaml')
|
62
|
-
cfg = ::Unobtainium::Config.load_config(config)
|
63
|
-
|
64
|
-
expect(cfg["config"]).to eql %w(array in main config)
|
65
|
-
expect(cfg["local"]).to eql "override is a hash"
|
66
|
-
end
|
67
|
-
|
68
|
-
it "overrides configuration variables from the environment" do
|
69
|
-
config = File.join(@data_path, 'hash.yml')
|
70
|
-
cfg = ::Unobtainium::Config.load_config(config)
|
71
|
-
|
72
|
-
ENV["BAZ"] = "override"
|
73
|
-
expect(cfg["foo"]).to eql "bar"
|
74
|
-
expect(cfg["baz"]).to eql "override"
|
75
|
-
end
|
76
|
-
|
77
|
-
it "treats an empty YAML file as an empty hash" do
|
78
|
-
config = File.join(@data_path, 'empty.yml')
|
79
|
-
cfg = ::Unobtainium::Config.load_config(config)
|
80
|
-
expect(cfg).to be_empty
|
81
|
-
end
|
82
|
-
|
83
|
-
it "extends configuration hashes" do
|
84
|
-
config = File.join(@data_path, 'driverconfig.yml')
|
85
|
-
cfg = ::Unobtainium::Config.load_config(config)
|
86
|
-
|
87
|
-
# First, test for non-extended values
|
88
|
-
expect(cfg["drivers.mock.mockoption"]).to eql 42
|
89
|
-
expect(cfg["drivers.branch1.branch1option"]).to eql "foo"
|
90
|
-
expect(cfg["drivers.branch2.branch2option"]).to eql "bar"
|
91
|
-
expect(cfg["drivers.leaf.leafoption"]).to eql "baz"
|
92
|
-
|
93
|
-
# Now test extended values
|
94
|
-
expect(cfg["drivers.branch1.mockoption"]).to eql 42
|
95
|
-
expect(cfg["drivers.branch2.mockoption"]).to eql 42
|
96
|
-
expect(cfg["drivers.leaf.mockoption"]).to eql 42
|
97
|
-
|
98
|
-
expect(cfg["drivers.branch2.branch1option"]).to eql "foo"
|
99
|
-
expect(cfg["drivers.leaf.branch1option"]).to eql "override" # not "foo" !
|
100
|
-
|
101
|
-
expect(cfg["drivers.leaf.branch2option"]).to eql "bar"
|
102
|
-
|
103
|
-
# Also test that all levels go back to base == mock
|
104
|
-
expect(cfg["drivers.branch1.base"]).to eql 'mock'
|
105
|
-
expect(cfg["drivers.branch2.base"]).to eql 'mock'
|
106
|
-
expect(cfg["drivers.leaf.base"]).to eql 'mock'
|
107
|
-
end
|
108
|
-
|
109
|
-
it "extends configuration hashes when the base does not exist" do
|
110
|
-
config = File.join(@data_path, 'driverconfig.yml')
|
111
|
-
cfg = ::Unobtainium::Config.load_config(config)
|
112
|
-
|
113
|
-
# Ensure the hash contains its own value
|
114
|
-
expect(cfg["drivers.base_does_not_exist.some"]).to eql "value"
|
115
|
-
|
116
|
-
# Also ensure the "base" is set properly
|
117
|
-
expect(cfg["drivers.base_does_not_exist.base"]).to eql "nonexistent_base"
|
118
|
-
end
|
119
|
-
end
|
data/spec/data/array.yaml
DELETED
data/spec/data/arraymerge.yaml
DELETED
data/spec/data/empty.yml
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
---
|
data/spec/data/hash.yml
DELETED
data/spec/data/hashmerge.yml
DELETED
data/spec/data/mergefail.yaml
DELETED
data/spec/data/test.json
DELETED
data/spec/data/world.yml
DELETED
data/spec/pathed_hash_spec.rb
DELETED
@@ -1,192 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require_relative '../lib/unobtainium/pathed_hash'
|
3
|
-
|
4
|
-
describe ::Unobtainium::PathedHash do
|
5
|
-
describe "#initialize" do
|
6
|
-
it "can be constructed without values" do
|
7
|
-
ph = ::Unobtainium::PathedHash.new
|
8
|
-
expect(ph.empty?).to eql true
|
9
|
-
end
|
10
|
-
|
11
|
-
it "can be constructed with values" do
|
12
|
-
ph = ::Unobtainium::PathedHash.new(foo: 42)
|
13
|
-
expect(ph.empty?).to eql false
|
14
|
-
expect(ph[:foo]).to eql 42
|
15
|
-
end
|
16
|
-
|
17
|
-
it "can be constructed with a nil value" do
|
18
|
-
ph = ::Unobtainium::PathedHash.new(nil)
|
19
|
-
expect(ph.empty?).to eql true
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
describe "Hash-like" do
|
24
|
-
it "responds to Hash functions" do
|
25
|
-
ph = ::Unobtainium::PathedHash.new
|
26
|
-
[:invert, :delete, :fetch].each do |meth|
|
27
|
-
expect(ph.respond_to?(meth)).to eql true
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
it "can be used like a hash" do
|
32
|
-
ph = ::Unobtainium::PathedHash.new(foo: 42)
|
33
|
-
inverted = ph.invert
|
34
|
-
expect(inverted.empty?).to eql false
|
35
|
-
expect(inverted[42]).to eql :foo
|
36
|
-
end
|
37
|
-
|
38
|
-
it "delegates to Hash if it's nothing to do with paths" do
|
39
|
-
ph = ::Unobtainium::PathedHash.new(foo: 42)
|
40
|
-
expect(ph.default).to be_nil
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
it "can recursively read entries via a path" do
|
45
|
-
sample = {
|
46
|
-
"foo" => 42,
|
47
|
-
"bar" => {
|
48
|
-
"baz" => "quux",
|
49
|
-
"blah" => [1, 2],
|
50
|
-
}
|
51
|
-
}
|
52
|
-
ph = ::Unobtainium::PathedHash.new(sample)
|
53
|
-
|
54
|
-
expect(ph["foo"]).to eql 42
|
55
|
-
expect(ph["bar.baz"]).to eql "quux"
|
56
|
-
expect(ph["bar.blah"]).to eql [1, 2]
|
57
|
-
|
58
|
-
expect(ph["nope"]).to eql nil
|
59
|
-
expect(ph["bar.nope"]).to eql nil
|
60
|
-
end
|
61
|
-
|
62
|
-
it "behaves consistently if in a path the first node cannot be found" do
|
63
|
-
sample = {
|
64
|
-
"foo" => 42,
|
65
|
-
}
|
66
|
-
ph = ::Unobtainium::PathedHash.new(sample)
|
67
|
-
|
68
|
-
expect(ph["nope.bar"]).to eql nil
|
69
|
-
end
|
70
|
-
|
71
|
-
it "can be used with indifferent access from string key" do
|
72
|
-
sample = {
|
73
|
-
"foo" => 42,
|
74
|
-
}
|
75
|
-
ph = ::Unobtainium::PathedHash.new(sample)
|
76
|
-
|
77
|
-
expect(ph["foo"]).to eql 42
|
78
|
-
expect(ph[:foo]).to eql 42
|
79
|
-
end
|
80
|
-
|
81
|
-
it "can be used with indifferent access from symbol key" do
|
82
|
-
sample = {
|
83
|
-
foo: 42,
|
84
|
-
bar: {
|
85
|
-
baz: 'quux',
|
86
|
-
}
|
87
|
-
}
|
88
|
-
ph = ::Unobtainium::PathedHash.new(sample)
|
89
|
-
|
90
|
-
expect(ph["foo"]).to eql 42
|
91
|
-
expect(ph[:foo]).to eql 42
|
92
|
-
|
93
|
-
expect(ph['bar.baz']).to eql 'quux'
|
94
|
-
end
|
95
|
-
|
96
|
-
it "treats a single separator as the root" do
|
97
|
-
sample = { "foo" => 42 }
|
98
|
-
ph = ::Unobtainium::PathedHash.new(sample)
|
99
|
-
|
100
|
-
expect(ph[ph.separator]["foo"]).to eql 42
|
101
|
-
end
|
102
|
-
|
103
|
-
it "treats an empty path as the root" do
|
104
|
-
sample = { "foo" => 42 }
|
105
|
-
ph = ::Unobtainium::PathedHash.new(sample)
|
106
|
-
|
107
|
-
expect(ph[""]["foo"]).to eql 42
|
108
|
-
end
|
109
|
-
|
110
|
-
it "can recursively write entries via a path" do
|
111
|
-
ph = ::Unobtainium::PathedHash.new
|
112
|
-
ph["foo.bar"] = 42
|
113
|
-
expect(ph["foo.bar"]).to eql 42
|
114
|
-
end
|
115
|
-
|
116
|
-
it "has the same string representation as the hash it's initialized from" do
|
117
|
-
h = { foo: 42 }
|
118
|
-
ph = ::Unobtainium::PathedHash.new(h)
|
119
|
-
expect(ph.to_s).to eql h.to_s
|
120
|
-
end
|
121
|
-
|
122
|
-
it "understands absolute paths (starting with separator)" do
|
123
|
-
sample = {
|
124
|
-
"foo" => 42,
|
125
|
-
"bar" => {
|
126
|
-
"baz" => "quux",
|
127
|
-
"blah" => [1, 2],
|
128
|
-
}
|
129
|
-
}
|
130
|
-
ph = ::Unobtainium::PathedHash.new(sample)
|
131
|
-
|
132
|
-
expect(ph["bar.baz"]).to eql "quux"
|
133
|
-
expect(ph[".bar.baz"]).to eql "quux"
|
134
|
-
end
|
135
|
-
|
136
|
-
it "recursively merges with overwriting" do
|
137
|
-
sample1 = {
|
138
|
-
"foo" => {
|
139
|
-
"bar" => 42,
|
140
|
-
"baz" => "quux",
|
141
|
-
}
|
142
|
-
}
|
143
|
-
sample2 = {
|
144
|
-
"foo" => {
|
145
|
-
"baz" => "override"
|
146
|
-
}
|
147
|
-
}
|
148
|
-
|
149
|
-
ph1 = ::Unobtainium::PathedHash.new(sample1)
|
150
|
-
ph2 = ph1.recursive_merge(sample2)
|
151
|
-
|
152
|
-
expect(ph2["foo.bar"]).to eql 42
|
153
|
-
expect(ph2["foo.baz"]).to eql "override"
|
154
|
-
end
|
155
|
-
|
156
|
-
it "recursively merges without overwriting" do
|
157
|
-
sample1 = {
|
158
|
-
"foo" => {
|
159
|
-
"bar" => 42,
|
160
|
-
"baz" => "quux",
|
161
|
-
}
|
162
|
-
}
|
163
|
-
sample2 = {
|
164
|
-
"foo" => {
|
165
|
-
"baz" => "override"
|
166
|
-
}
|
167
|
-
}
|
168
|
-
|
169
|
-
ph1 = ::Unobtainium::PathedHash.new(sample1)
|
170
|
-
ph2 = ph1.recursive_merge(sample2, false)
|
171
|
-
|
172
|
-
expect(ph2["foo.bar"]).to eql 42
|
173
|
-
expect(ph2["foo.baz"]).to eql "quux"
|
174
|
-
end
|
175
|
-
|
176
|
-
it "can write with indifferent access without overwriting" do
|
177
|
-
sample = {
|
178
|
-
foo: {
|
179
|
-
bar: 42,
|
180
|
-
baz: 'quux',
|
181
|
-
}
|
182
|
-
}
|
183
|
-
ph = ::Unobtainium::PathedHash.new(sample)
|
184
|
-
|
185
|
-
expect(ph['foo.bar']).to eql 42
|
186
|
-
expect(ph['foo.baz']).to eql 'quux'
|
187
|
-
|
188
|
-
ph['foo.bar'] = 123
|
189
|
-
expect(ph['foo.bar']).to eql 123
|
190
|
-
expect(ph['foo.baz']).to eql 'quux'
|
191
|
-
end
|
192
|
-
end
|