yamload 0.0.6 → 0.1.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/CHANGELOG.md +48 -0
- data/README.md +1 -1
- data/lib/yamload/conversion/array.rb +20 -0
- data/lib/yamload/conversion/hash.rb +33 -0
- data/lib/yamload/conversion/object.rb +29 -0
- data/lib/yamload/conversion.rb +8 -0
- data/lib/yamload/defaults/hash.rb +26 -0
- data/lib/yamload/defaults.rb +6 -0
- data/lib/yamload/loader.rb +43 -35
- data/lib/yamload/loading/yaml.rb +41 -0
- data/lib/yamload/loading.rb +6 -0
- data/lib/yamload/validation/hash.rb +28 -0
- data/lib/yamload/validation/result.rb +16 -0
- data/lib/yamload/validation.rb +7 -0
- data/lib/yamload/version.rb +1 -1
- data/lib/yamload.rb +0 -1
- data/spec/conversion_spec.rb +55 -0
- data/spec/fixtures/array.yml +4 -0
- data/spec/fixtures/empty.yml +0 -1
- data/spec/fixtures/string.yml +7 -0
- data/spec/fixtures/test.yml +1 -1
- data/spec/loader_spec.rb +216 -139
- metadata +20 -3
- data/lib/yamload/hash_to_immutable_object.rb +0 -40
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 831e871ab4c0d0dbe55c9465bad82ba229730d87
|
4
|
+
data.tar.gz: 52dbd15b58e1adc00928261b9490da7761bbc607
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 36e4c622fabf2326406df940a8caa541268372cbedc3f1b829b3fa3fdbf9b5766d1d8322817cab0fce255c936b3e75df49ed09fd8312d720d1a35229849c47a9
|
7
|
+
data.tar.gz: ca1f592c5e75d2f1a34aa12385a9b8b16dd333bbc6d1f99d550ddf9a8569ae2475a3000eac9301ccc1354ad2c10ed6b37492b2031a32dea9c4963fc02e722ac4
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
## 0.1.0 (2015-02-17)
|
2
|
+
|
3
|
+
- use proper semantic versioning (semver.org)
|
4
|
+
|
5
|
+
Features:
|
6
|
+
|
7
|
+
- support loading valid yml files which don't define a hash
|
8
|
+
- deprecates `Yamload::Loader#loaded_hash` in favour of `Yamload::Loader#content`
|
9
|
+
|
10
|
+
## 0.0.6 (2015-02-04)
|
11
|
+
|
12
|
+
Bugfixes:
|
13
|
+
|
14
|
+
- check for directory existence
|
15
|
+
|
16
|
+
## 0.0.5 (2015-02-04)
|
17
|
+
|
18
|
+
Bugfixes:
|
19
|
+
|
20
|
+
- check for file existence
|
21
|
+
- check for validity of file contents
|
22
|
+
|
23
|
+
## 0.0.4 (2015-02-04)
|
24
|
+
|
25
|
+
Features:
|
26
|
+
|
27
|
+
- implement `Yamload::Loader#exist?` to check for file presence
|
28
|
+
|
29
|
+
## 0.0.3 (2015-02-04)
|
30
|
+
|
31
|
+
Features:
|
32
|
+
|
33
|
+
- use a faster schema validation engine with no dependencies
|
34
|
+
|
35
|
+
## 0.0.2 (2015-02-04)
|
36
|
+
|
37
|
+
Features:
|
38
|
+
|
39
|
+
- freeze the loaded hash
|
40
|
+
|
41
|
+
## 0.0.1 (2015-02-04)
|
42
|
+
|
43
|
+
Features:
|
44
|
+
|
45
|
+
- load valid yml files defining a hash
|
46
|
+
- conversion of loaded hash to immutable object
|
47
|
+
- default values
|
48
|
+
- schema validation
|
data/README.md
CHANGED
@@ -0,0 +1,20 @@
|
|
1
|
+
module Yamload
|
2
|
+
module Conversion
|
3
|
+
class Array
|
4
|
+
def initialize(array)
|
5
|
+
fail ArgumentError, "#{array} is not an Array" unless array.is_a?(::Array)
|
6
|
+
@array = array
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_immutable
|
10
|
+
convert_elements.freeze
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def convert_elements
|
16
|
+
@array.map { |element| Object.new(element).to_immutable }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'facets/hash/rekey'
|
2
|
+
require 'anima'
|
3
|
+
|
4
|
+
module Yamload
|
5
|
+
module Conversion
|
6
|
+
class Hash
|
7
|
+
def initialize(hash)
|
8
|
+
fail ArgumentError, "#{array} is not a Hash" unless hash.is_a?(::Hash)
|
9
|
+
@hash = hash.rekey
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_immutable
|
13
|
+
immutable_objects_factory.new(converted_hash)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def immutable_objects_factory
|
19
|
+
anima = Anima.new(*@hash.keys)
|
20
|
+
Class.new do
|
21
|
+
include Adamantium
|
22
|
+
include anima
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def converted_hash
|
27
|
+
@hash.map.with_object({}) { |(key, value), hash|
|
28
|
+
hash[key] = Object.new(value).to_immutable
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'facets/object/dup'
|
2
|
+
require 'ice_nine'
|
3
|
+
|
4
|
+
module Yamload
|
5
|
+
module Conversion
|
6
|
+
class Object
|
7
|
+
def initialize(object)
|
8
|
+
@object = object.clone? ? object.clone : object
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_immutable
|
12
|
+
convert
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def convert
|
18
|
+
case @object
|
19
|
+
when ::Array
|
20
|
+
Array.new(@object).to_immutable
|
21
|
+
when ::Hash
|
22
|
+
Hash.new(@object).to_immutable
|
23
|
+
else
|
24
|
+
IceNine.deep_freeze!(@object)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'facets/hash/deep_merge'
|
2
|
+
|
3
|
+
module Yamload
|
4
|
+
module Defaults
|
5
|
+
class Hash
|
6
|
+
attr_reader :defaults
|
7
|
+
|
8
|
+
def initialize(defaults = nil)
|
9
|
+
self.defaults = defaults
|
10
|
+
end
|
11
|
+
|
12
|
+
def defaults=(defaults)
|
13
|
+
unless defaults.is_a?(::Hash) || defaults.nil?
|
14
|
+
fail ArgumentError, "#{defaults} is not a hash"
|
15
|
+
end
|
16
|
+
@defaults = defaults
|
17
|
+
end
|
18
|
+
|
19
|
+
def merge(hash)
|
20
|
+
return hash if @defaults.nil?
|
21
|
+
fail ArgumentError, "#{hash} is not a hash" unless hash.is_a?(::Hash)
|
22
|
+
@defaults.deep_merge(hash)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/yamload/loader.rb
CHANGED
@@ -1,76 +1,84 @@
|
|
1
1
|
require 'yaml'
|
2
|
-
require '
|
3
|
-
require '
|
2
|
+
require 'ice_nine'
|
3
|
+
require 'yamload/loading'
|
4
|
+
require 'yamload/conversion'
|
5
|
+
require 'yamload/defaults'
|
6
|
+
require 'yamload/validation'
|
4
7
|
|
5
8
|
module Yamload
|
6
9
|
class Loader
|
7
10
|
def initialize(file, dir = Yamload.dir)
|
8
|
-
@
|
9
|
-
@dir = dir
|
11
|
+
@loader = Loading::Yaml.new(file, dir)
|
10
12
|
end
|
11
13
|
|
12
14
|
def exist?
|
13
|
-
|
15
|
+
@loader.exist?
|
14
16
|
end
|
15
17
|
|
18
|
+
# <b>DEPRECATED:</b> Please use <tt>content</tt> instead.
|
16
19
|
def loaded_hash
|
17
|
-
|
20
|
+
warn '[DEPRECATION] `loaded_hash` is deprecated. Please use `content` instead.'
|
21
|
+
content
|
22
|
+
end
|
23
|
+
|
24
|
+
def content
|
25
|
+
@content ||= IceNine.deep_freeze(content_with_defaults)
|
18
26
|
end
|
19
27
|
|
20
28
|
def obj
|
21
|
-
@immutable_obj ||=
|
29
|
+
@immutable_obj ||= Conversion::Object.new(content).to_immutable
|
22
30
|
end
|
23
31
|
|
24
32
|
def reload
|
25
|
-
@
|
26
|
-
|
33
|
+
@content = @immutable_obj = nil
|
34
|
+
@loader.reload
|
35
|
+
content
|
27
36
|
end
|
28
37
|
|
29
|
-
|
38
|
+
def defaults=(defaults)
|
39
|
+
defaults_merger.defaults = defaults
|
40
|
+
end
|
30
41
|
|
31
|
-
def
|
32
|
-
|
42
|
+
def defaults
|
43
|
+
defaults_merger.defaults
|
33
44
|
end
|
34
45
|
|
35
|
-
|
46
|
+
def schema=(schema)
|
47
|
+
validator.schema = schema
|
48
|
+
end
|
36
49
|
|
37
|
-
def
|
38
|
-
|
50
|
+
def schema
|
51
|
+
validator.schema
|
39
52
|
end
|
40
53
|
|
41
54
|
def valid?
|
42
|
-
|
43
|
-
true
|
44
|
-
rescue SchemaError
|
45
|
-
false
|
55
|
+
validation_result.valid?
|
46
56
|
end
|
47
57
|
|
48
58
|
def validate!
|
49
|
-
|
50
|
-
ClassyHash.validate(loaded_hash, schema)
|
51
|
-
rescue RuntimeError => e
|
52
|
-
@error = e.message
|
53
|
-
raise SchemaError, @error
|
59
|
+
fail SchemaError, validation_result.error unless validation_result.valid?
|
54
60
|
end
|
55
61
|
|
56
62
|
def error
|
57
|
-
|
58
|
-
@error
|
63
|
+
validation_result.error
|
59
64
|
end
|
60
65
|
|
61
66
|
private
|
62
67
|
|
63
|
-
def
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
+
def content_with_defaults
|
69
|
+
defaults_merger.merge(@loader.content)
|
70
|
+
end
|
71
|
+
|
72
|
+
def defaults_merger
|
73
|
+
@defaults_merger ||= Defaults::Hash.new
|
74
|
+
end
|
75
|
+
|
76
|
+
def validator
|
77
|
+
@validator ||= Validation::Hash.new
|
68
78
|
end
|
69
79
|
|
70
|
-
def
|
71
|
-
|
72
|
-
fail IOError, "#{@dir} is not a valid directory" unless File.directory?(@dir)
|
73
|
-
File.join(@dir, "#{@file}.yml")
|
80
|
+
def validation_result
|
81
|
+
validator.validate(content)
|
74
82
|
end
|
75
83
|
end
|
76
84
|
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'facets/kernel'
|
2
|
+
require 'ice_nine'
|
3
|
+
|
4
|
+
module Yamload
|
5
|
+
module Loading
|
6
|
+
class Yaml
|
7
|
+
def initialize(file, dir)
|
8
|
+
@file = file
|
9
|
+
@dir = dir
|
10
|
+
end
|
11
|
+
|
12
|
+
def exist?
|
13
|
+
File.exist?(filepath)
|
14
|
+
end
|
15
|
+
|
16
|
+
def content
|
17
|
+
@content ||= IceNine.deep_freeze(load)
|
18
|
+
end
|
19
|
+
|
20
|
+
def reload
|
21
|
+
@content = @immutable_obj = nil
|
22
|
+
content
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def load
|
28
|
+
fail IOError, "#{@file}.yml could not be found" unless exist?
|
29
|
+
YAML.load_file(filepath).tap do |content|
|
30
|
+
fail IOError, "#{@file}.yml is blank" if content.blank?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def filepath
|
35
|
+
fail IOError, 'No yml files directory specified' if @dir.nil?
|
36
|
+
fail IOError, "#{@dir} is not a valid directory" unless File.directory?(@dir)
|
37
|
+
File.join(@dir, "#{@file}.yml")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'classy_hash'
|
2
|
+
|
3
|
+
module Yamload
|
4
|
+
module Validation
|
5
|
+
class Hash
|
6
|
+
attr_reader :schema
|
7
|
+
|
8
|
+
def initialize(schema = nil)
|
9
|
+
self.schema = schema
|
10
|
+
end
|
11
|
+
|
12
|
+
def schema=(schema)
|
13
|
+
unless schema.is_a?(::Hash) || schema.nil?
|
14
|
+
fail ArgumentError, "#{schema} is not a hash"
|
15
|
+
end
|
16
|
+
@schema = schema
|
17
|
+
end
|
18
|
+
|
19
|
+
def validate(hash)
|
20
|
+
fail ArgumentError, "#{hash} is not a hash" unless hash.is_a?(::Hash)
|
21
|
+
ClassyHash.validate(hash, @schema) unless @schema.nil?
|
22
|
+
Result.new(true)
|
23
|
+
rescue RuntimeError => e
|
24
|
+
Result.new(false, e.message)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/yamload/version.rb
CHANGED
data/lib/yamload.rb
CHANGED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'yamload/conversion'
|
4
|
+
|
5
|
+
describe Yamload::Conversion do
|
6
|
+
let(:number) { 42 }
|
7
|
+
let(:string) { 'a string' }
|
8
|
+
let(:array) { [number, string] }
|
9
|
+
let(:hash) {
|
10
|
+
{
|
11
|
+
string: string,
|
12
|
+
array: array,
|
13
|
+
sub_hash: { something: 'else' }
|
14
|
+
}
|
15
|
+
}
|
16
|
+
|
17
|
+
subject!(:immutable_object) { converter.to_immutable }
|
18
|
+
|
19
|
+
context 'when converting a number' do
|
20
|
+
let(:converter) { Yamload::Conversion::Object.new(number) }
|
21
|
+
specify { is_expected.to eq number }
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'when converting a string' do
|
25
|
+
let(:converter) { Yamload::Conversion::Object.new(string) }
|
26
|
+
specify { expect(string).not_to be_frozen }
|
27
|
+
specify { is_expected.to be_frozen }
|
28
|
+
specify { is_expected.to eq string }
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'when converting an array' do
|
32
|
+
let(:converter) { Yamload::Conversion::Object.new(array) }
|
33
|
+
specify { expect(array).not_to be_frozen }
|
34
|
+
specify { is_expected.to be_frozen }
|
35
|
+
specify { is_expected.to be_an Array }
|
36
|
+
specify { expect(immutable_object.size).to eq 2 }
|
37
|
+
specify { expect(immutable_object[0]).to eq number }
|
38
|
+
specify { expect(immutable_object[1]).to be_frozen }
|
39
|
+
specify { expect(immutable_object[1]).to eq string }
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'when converting a hash' do
|
43
|
+
let(:converter) { Yamload::Conversion::Object.new(hash) }
|
44
|
+
specify { expect(hash).not_to be_frozen }
|
45
|
+
specify { is_expected.to be_frozen }
|
46
|
+
specify { expect(immutable_object.string).to eq string }
|
47
|
+
specify { expect(immutable_object.array.size).to eq 2 }
|
48
|
+
specify { expect(immutable_object.array[0]).to eq number }
|
49
|
+
specify { expect(immutable_object.array[1]).to be_frozen }
|
50
|
+
specify { expect(immutable_object.array[1]).to eq string }
|
51
|
+
specify { expect(immutable_object.sub_hash).to be_frozen }
|
52
|
+
specify { expect(immutable_object.sub_hash.something).to be_frozen }
|
53
|
+
specify { expect(immutable_object.sub_hash.something).to eq 'else' }
|
54
|
+
end
|
55
|
+
end
|
data/spec/fixtures/empty.yml
CHANGED
@@ -1 +0,0 @@
|
|
1
|
-
|
data/spec/fixtures/test.yml
CHANGED
data/spec/loader_spec.rb
CHANGED
@@ -2,201 +2,278 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
require 'yamload'
|
4
4
|
|
5
|
-
describe Yamload do
|
5
|
+
describe Yamload::Loader do
|
6
6
|
let(:file) { :test }
|
7
7
|
let(:loader) { Yamload::Loader.new(file) }
|
8
|
-
|
9
|
-
specify { expect(loader).to exist }
|
8
|
+
let(:content) { loader.content }
|
10
9
|
|
11
10
|
context 'if the directory is not specified' do
|
12
11
|
let(:loader) { Yamload::Loader.new(file, nil) }
|
13
|
-
specify { expect {
|
12
|
+
specify { expect { content }.to raise_error IOError, 'No yml files directory specified' }
|
14
13
|
end
|
15
14
|
|
16
15
|
context 'if the directory is invalid' do
|
17
16
|
let(:current_file_dir) { File.expand_path(File.dirname(__FILE__)) }
|
18
17
|
let(:invalid_dir) { File.join(current_file_dir, 'invalid') }
|
19
18
|
let(:loader) { Yamload::Loader.new(file, invalid_dir) }
|
20
|
-
specify { expect {
|
19
|
+
specify { expect { content }.to raise_error IOError, "#{invalid_dir} is not a valid directory" }
|
21
20
|
end
|
22
21
|
|
23
22
|
context 'with a non existing file' do
|
24
23
|
let(:file) { :non_existing }
|
25
24
|
specify { expect(loader).not_to exist }
|
26
|
-
specify { expect {
|
25
|
+
specify { expect { content }.to raise_error IOError, 'non_existing.yml could not be found' }
|
27
26
|
end
|
28
27
|
|
29
|
-
let(:config) { loader.loaded_hash }
|
30
|
-
|
31
28
|
context 'with an empty file' do
|
32
29
|
let(:file) { :empty }
|
33
30
|
specify { expect(loader).to exist }
|
34
|
-
specify { expect {
|
31
|
+
specify { expect { content }.to raise_error IOError, 'empty.yml is blank' }
|
35
32
|
end
|
36
33
|
|
37
|
-
|
38
|
-
{
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
'country' => 'Testalia'
|
51
|
-
},
|
52
|
-
'email' => 'testy.tester@test.com'
|
53
|
-
},
|
54
|
-
{
|
55
|
-
'first_name' => 'Speccy',
|
56
|
-
'last_name' => 'Speccer',
|
57
|
-
'address' => {
|
58
|
-
'address_line_1' => 'Unit 1',
|
59
|
-
'address_line_2' => '42 Spec Street',
|
60
|
-
'city' => 'Specwood',
|
61
|
-
'state' => 'SPC',
|
62
|
-
'post_code' => 5678,
|
63
|
-
'country' => 'Specland'
|
64
|
-
},
|
65
|
-
'email' => 'speccy.speccer@spec.com'
|
66
|
-
}
|
67
|
-
],
|
68
|
-
'settings' => {
|
69
|
-
'remote_access' => true
|
34
|
+
context 'with a file defining an array' do
|
35
|
+
let(:file) { :array }
|
36
|
+
let(:expected_content) { %w(first second third) }
|
37
|
+
specify { expect(loader).to exist }
|
38
|
+
specify { expect { content }.not_to raise_error }
|
39
|
+
specify { expect(content).to eq expected_content }
|
40
|
+
|
41
|
+
context 'when defaults are defined' do
|
42
|
+
let(:defaults) { { test: true } }
|
43
|
+
before { loader.defaults = defaults }
|
44
|
+
specify {
|
45
|
+
expect { content }
|
46
|
+
.to raise_error ArgumentError, "#{expected_content} is not a hash"
|
70
47
|
}
|
71
|
-
}
|
72
|
-
}
|
73
|
-
|
74
|
-
specify { expect(config).to eq expected_config }
|
75
|
-
|
76
|
-
let(:config_obj) { loader.obj }
|
77
|
-
|
78
|
-
specify { expect(config_obj.test).to eq true }
|
79
|
-
specify { expect(config_obj.users[0].first_name).to eq 'Testy' }
|
80
|
-
specify { expect(config_obj.users[0].last_name).to eq 'Tester' }
|
81
|
-
specify { expect(config_obj.users[0].address.address_line_1).to eq '1 Test Avenue' }
|
82
|
-
specify { expect(config_obj.users[0].address.address_line_2).to eq nil }
|
83
|
-
specify { expect(config_obj.users[0].address.city).to eq 'Testville' }
|
84
|
-
specify { expect(config_obj.users[0].address.state).to eq 'TST' }
|
85
|
-
specify { expect(config_obj.users[0].address.post_code).to eq 1234 }
|
86
|
-
specify { expect(config_obj.users[0].address.country).to eq 'Testalia' }
|
87
|
-
specify { expect(config_obj.users[0].email).to eq 'testy.tester@test.com' }
|
88
|
-
specify { expect(config_obj.users[1].first_name).to eq 'Speccy' }
|
89
|
-
specify { expect(config_obj.users[1].last_name).to eq 'Speccer' }
|
90
|
-
specify { expect(config_obj.users[1].address.address_line_1).to eq 'Unit 1' }
|
91
|
-
specify { expect(config_obj.users[1].address.address_line_2).to eq '42 Spec Street' }
|
92
|
-
specify { expect(config_obj.users[1].address.city).to eq 'Specwood' }
|
93
|
-
specify { expect(config_obj.users[1].address.state).to eq 'SPC' }
|
94
|
-
specify { expect(config_obj.users[1].address.post_code).to eq 5678 }
|
95
|
-
specify { expect(config_obj.users[1].address.country).to eq 'Specland' }
|
96
|
-
specify { expect(config_obj.users[1].email).to eq 'speccy.speccer@spec.com' }
|
97
|
-
specify { expect(config_obj.settings.remote_access).to eq true }
|
98
|
-
|
99
|
-
context 'when trying to modify the loaded hash' do
|
100
|
-
let(:new_user) { double('new user') }
|
101
|
-
specify 'the hash should be immutable' do
|
102
|
-
expect { config['users'] << new_user }
|
103
|
-
.to raise_error RuntimeError, "can't modify frozen Array"
|
104
|
-
expect(config['users']).not_to include new_user
|
105
48
|
end
|
106
|
-
end
|
107
49
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
50
|
+
context 'when a schema is defined' do
|
51
|
+
let(:schema) { { test: true } }
|
52
|
+
before { loader.schema = schema }
|
53
|
+
specify {
|
54
|
+
expect { loader.valid? }
|
55
|
+
.to raise_error ArgumentError, "#{expected_content} is not a hash"
|
56
|
+
}
|
114
57
|
end
|
115
58
|
end
|
116
59
|
|
117
|
-
context '
|
118
|
-
|
119
|
-
|
120
|
-
specify { expect
|
60
|
+
context 'with a file defining a string' do
|
61
|
+
let(:file) { :string }
|
62
|
+
let(:expected_content) { '1 first 2 second 3 third' }
|
63
|
+
specify { expect(loader).to exist }
|
64
|
+
specify { expect { content }.not_to raise_error }
|
65
|
+
specify { expect(content).to eq expected_content }
|
66
|
+
|
67
|
+
context 'when defaults are defined' do
|
68
|
+
let(:defaults) { { test: true } }
|
69
|
+
before { loader.defaults = defaults }
|
70
|
+
specify {
|
71
|
+
expect { content }
|
72
|
+
.to raise_error ArgumentError, "#{expected_content} is not a hash"
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'when a schema is defined' do
|
77
|
+
let(:schema) { { test: true } }
|
78
|
+
before { loader.schema = schema }
|
79
|
+
specify {
|
80
|
+
expect { loader.valid? }
|
81
|
+
.to raise_error ArgumentError, "#{expected_content} is not a hash"
|
82
|
+
}
|
83
|
+
end
|
121
84
|
end
|
122
85
|
|
123
|
-
context '
|
124
|
-
|
86
|
+
context 'with a file defining a hash' do
|
87
|
+
specify { expect(loader).to exist }
|
88
|
+
|
89
|
+
let(:expected_content) {
|
125
90
|
{
|
126
|
-
'test'
|
127
|
-
'users'
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
'
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
91
|
+
'test' => true,
|
92
|
+
'users' => [
|
93
|
+
{
|
94
|
+
'first_name' => 'Testy',
|
95
|
+
'last_name' => 'Tester',
|
96
|
+
'address' => {
|
97
|
+
'address_line_1' => '1 Test Avenue',
|
98
|
+
'address_line_2' => nil,
|
99
|
+
'city' => 'Testville',
|
100
|
+
'state' => 'TST',
|
101
|
+
'post_code' => 1234,
|
102
|
+
'country' => 'Testalia'
|
103
|
+
},
|
104
|
+
'email' => 'testy.tester@test.com'
|
105
|
+
},
|
106
|
+
{
|
107
|
+
'first_name' => 'Speccy',
|
108
|
+
'last_name' => 'Speccer',
|
109
|
+
'address' => {
|
110
|
+
'address_line_1' => 'Unit 1',
|
111
|
+
'address_line_2' => '42 Spec Street',
|
112
|
+
'city' => 'Specwood',
|
113
|
+
'state' => 'SPC',
|
114
|
+
'post_code' => 5678,
|
115
|
+
'country' => 'Specland'
|
116
|
+
},
|
117
|
+
'email' => 'speccy.speccer@spec.com'
|
118
|
+
}
|
143
119
|
],
|
144
120
|
'settings' => {
|
145
|
-
'remote_access' =>
|
121
|
+
'remote_access' => true
|
146
122
|
}
|
147
123
|
}
|
148
124
|
}
|
149
125
|
|
150
|
-
|
126
|
+
specify 'deprecated `loaded_hash` still works' do
|
127
|
+
expect(loader.loaded_hash).to eq loader.content
|
128
|
+
end
|
129
|
+
|
130
|
+
specify { expect(content).to eq expected_content }
|
131
|
+
|
132
|
+
let(:content_obj) { loader.obj }
|
151
133
|
|
152
|
-
specify { expect(
|
153
|
-
specify { expect(
|
154
|
-
specify { expect
|
134
|
+
specify { expect(content_obj.test).to eq true }
|
135
|
+
specify { expect(content_obj.users[0].first_name).to eq 'Testy' }
|
136
|
+
specify { expect(content_obj.users[0].last_name).to eq 'Tester' }
|
137
|
+
specify { expect(content_obj.users[0].address.address_line_1).to eq '1 Test Avenue' }
|
138
|
+
specify { expect(content_obj.users[0].address.address_line_2).to eq nil }
|
139
|
+
specify { expect(content_obj.users[0].address.city).to eq 'Testville' }
|
140
|
+
specify { expect(content_obj.users[0].address.state).to eq 'TST' }
|
141
|
+
specify { expect(content_obj.users[0].address.post_code).to eq 1234 }
|
142
|
+
specify { expect(content_obj.users[0].address.country).to eq 'Testalia' }
|
143
|
+
specify { expect(content_obj.users[0].email).to eq 'testy.tester@test.com' }
|
144
|
+
specify { expect(content_obj.users[1].first_name).to eq 'Speccy' }
|
145
|
+
specify { expect(content_obj.users[1].last_name).to eq 'Speccer' }
|
146
|
+
specify { expect(content_obj.users[1].address.address_line_1).to eq 'Unit 1' }
|
147
|
+
specify { expect(content_obj.users[1].address.address_line_2).to eq '42 Spec Street' }
|
148
|
+
specify { expect(content_obj.users[1].address.city).to eq 'Specwood' }
|
149
|
+
specify { expect(content_obj.users[1].address.state).to eq 'SPC' }
|
150
|
+
specify { expect(content_obj.users[1].address.post_code).to eq 5678 }
|
151
|
+
specify { expect(content_obj.users[1].address.country).to eq 'Specland' }
|
152
|
+
specify { expect(content_obj.users[1].email).to eq 'speccy.speccer@spec.com' }
|
153
|
+
specify { expect(content_obj.settings.remote_access).to eq true }
|
155
154
|
|
156
|
-
context 'when
|
155
|
+
context 'when trying to modify the loaded hash' do
|
156
|
+
let(:new_user) { double('new user') }
|
157
|
+
specify 'the hash should be immutable' do
|
158
|
+
expect { content['users'] << new_user }
|
159
|
+
.to raise_error RuntimeError, "can't modify frozen Array"
|
160
|
+
expect(content['users']).not_to include new_user
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
context 'when trying to modify the content object' do
|
165
|
+
let(:new_user) { double('new user') }
|
166
|
+
specify 'the object should be immutable' do
|
167
|
+
expect { content_obj.users << new_user }
|
168
|
+
.to raise_error RuntimeError, "can't modify frozen Array"
|
169
|
+
expect(content_obj.users).not_to include new_user
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context 'when no schema is defined' do
|
174
|
+
specify { expect(loader).to be_valid }
|
175
|
+
specify { expect(loader.error).to be_nil }
|
176
|
+
specify { expect { loader.validate! }.not_to raise_error }
|
177
|
+
end
|
178
|
+
|
179
|
+
context 'when the schema is not a hash' do
|
180
|
+
let(:schema) { 'not a hash' }
|
181
|
+
specify {
|
182
|
+
expect { loader.schema = schema }
|
183
|
+
.to raise_error ArgumentError, "#{schema} is not a hash"
|
184
|
+
}
|
185
|
+
end
|
186
|
+
|
187
|
+
context 'when a schema is defined' do
|
157
188
|
let(:schema) {
|
158
189
|
{
|
159
|
-
'
|
190
|
+
'test' => TrueClass,
|
191
|
+
'users' => [
|
160
192
|
[
|
161
193
|
{
|
162
|
-
'
|
194
|
+
'first_name' => String,
|
195
|
+
'last_name' => String,
|
196
|
+
'address' => {
|
197
|
+
'address_line_1' => String,
|
198
|
+
'address_line_2' => [:optional, String, NilClass],
|
199
|
+
'city' => String,
|
200
|
+
'state' => String,
|
201
|
+
'post_code' => Integer,
|
202
|
+
'country' => String
|
203
|
+
},
|
204
|
+
'email' => String
|
163
205
|
}
|
164
206
|
]
|
165
|
-
]
|
207
|
+
],
|
208
|
+
'settings' => {
|
209
|
+
'remote_access' => TrueClass
|
210
|
+
}
|
166
211
|
}
|
167
212
|
}
|
168
213
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
214
|
+
before do
|
215
|
+
loader.schema = schema
|
216
|
+
end
|
217
|
+
|
218
|
+
specify { expect(loader.schema).to eq schema }
|
219
|
+
specify { expect(loader).to be_valid }
|
220
|
+
specify { expect(loader.error).to be_nil }
|
221
|
+
specify { expect { loader.validate! }.not_to raise_error }
|
222
|
+
|
223
|
+
context 'when the schema is not matched' do
|
224
|
+
let(:schema) {
|
225
|
+
{
|
226
|
+
'users' => [
|
227
|
+
[
|
228
|
+
{
|
229
|
+
'expected_attribute' => String
|
230
|
+
}
|
231
|
+
]
|
232
|
+
]
|
233
|
+
}
|
234
|
+
}
|
235
|
+
|
236
|
+
let(:expected_error) { '"users"[0]["expected_attribute"] is not present' }
|
237
|
+
specify { expect(loader).not_to be_valid }
|
238
|
+
specify { expect(loader.error).to eq expected_error }
|
239
|
+
specify { expect { loader.validate! }.to raise_error Yamload::SchemaError, expected_error }
|
240
|
+
end
|
173
241
|
end
|
174
|
-
end
|
175
242
|
|
176
|
-
|
177
|
-
|
178
|
-
{
|
179
|
-
|
180
|
-
|
181
|
-
|
243
|
+
context 'when the defaults object is not a hash' do
|
244
|
+
let(:defaults) { 'not a hash' }
|
245
|
+
specify {
|
246
|
+
expect { loader.defaults = defaults }
|
247
|
+
.to raise_error ArgumentError, "#{defaults} is not a hash"
|
248
|
+
}
|
249
|
+
end
|
250
|
+
|
251
|
+
context 'when defaults are defined' do
|
252
|
+
let(:defaults) {
|
253
|
+
{
|
254
|
+
'settings' => {
|
255
|
+
'remember_user' => false,
|
256
|
+
'remote_access' => false
|
257
|
+
}
|
182
258
|
}
|
183
259
|
}
|
184
|
-
}
|
185
260
|
|
186
|
-
|
187
|
-
|
188
|
-
|
261
|
+
before do
|
262
|
+
loader.defaults = defaults
|
263
|
+
end
|
189
264
|
|
190
|
-
|
191
|
-
|
192
|
-
|
265
|
+
specify { expect(loader.defaults).to eq defaults }
|
266
|
+
specify { expect(content_obj.settings.remember_user).to eq false }
|
267
|
+
specify { expect(content_obj.settings.remote_access).to eq true }
|
268
|
+
end
|
193
269
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
270
|
+
context 'when reloading' do
|
271
|
+
let(:original_hash) { loader.content }
|
272
|
+
before do
|
273
|
+
original_hash
|
274
|
+
loader.reload
|
275
|
+
end
|
276
|
+
specify { expect(loader.content).not_to be original_hash }
|
199
277
|
end
|
200
|
-
specify { expect(loader.loaded_hash).not_to be original_hash }
|
201
278
|
end
|
202
279
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yamload
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alessandro Berardi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-02-
|
11
|
+
date: 2015-02-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: anima
|
@@ -162,15 +162,29 @@ files:
|
|
162
162
|
- ".rubocop.yml"
|
163
163
|
- ".ruby-version"
|
164
164
|
- ".travis.yml"
|
165
|
+
- CHANGELOG.md
|
165
166
|
- Gemfile
|
166
167
|
- LICENSE.txt
|
167
168
|
- README.md
|
168
169
|
- Rakefile
|
169
170
|
- lib/yamload.rb
|
170
|
-
- lib/yamload/
|
171
|
+
- lib/yamload/conversion.rb
|
172
|
+
- lib/yamload/conversion/array.rb
|
173
|
+
- lib/yamload/conversion/hash.rb
|
174
|
+
- lib/yamload/conversion/object.rb
|
175
|
+
- lib/yamload/defaults.rb
|
176
|
+
- lib/yamload/defaults/hash.rb
|
171
177
|
- lib/yamload/loader.rb
|
178
|
+
- lib/yamload/loading.rb
|
179
|
+
- lib/yamload/loading/yaml.rb
|
180
|
+
- lib/yamload/validation.rb
|
181
|
+
- lib/yamload/validation/hash.rb
|
182
|
+
- lib/yamload/validation/result.rb
|
172
183
|
- lib/yamload/version.rb
|
184
|
+
- spec/conversion_spec.rb
|
185
|
+
- spec/fixtures/array.yml
|
173
186
|
- spec/fixtures/empty.yml
|
187
|
+
- spec/fixtures/string.yml
|
174
188
|
- spec/fixtures/test.yml
|
175
189
|
- spec/loader_spec.rb
|
176
190
|
- spec/spec_helper.rb
|
@@ -200,7 +214,10 @@ signing_key:
|
|
200
214
|
specification_version: 4
|
201
215
|
summary: YAML files loader
|
202
216
|
test_files:
|
217
|
+
- spec/conversion_spec.rb
|
218
|
+
- spec/fixtures/array.yml
|
203
219
|
- spec/fixtures/empty.yml
|
220
|
+
- spec/fixtures/string.yml
|
204
221
|
- spec/fixtures/test.yml
|
205
222
|
- spec/loader_spec.rb
|
206
223
|
- spec/spec_helper.rb
|
@@ -1,40 +0,0 @@
|
|
1
|
-
require 'facets/hash/rekey'
|
2
|
-
require 'anima'
|
3
|
-
|
4
|
-
module Yamload
|
5
|
-
class HashToImmutableObject
|
6
|
-
def initialize(hash)
|
7
|
-
@hash = hash.rekey
|
8
|
-
end
|
9
|
-
|
10
|
-
def call
|
11
|
-
immutable_objects_factory.new(converted_hash)
|
12
|
-
end
|
13
|
-
|
14
|
-
private
|
15
|
-
|
16
|
-
def immutable_objects_factory
|
17
|
-
anima = Anima.new(*@hash.keys)
|
18
|
-
Class.new do
|
19
|
-
include Adamantium
|
20
|
-
include anima
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def converted_hash
|
25
|
-
@hash.map.with_object({}) { |(key, value), hash|
|
26
|
-
hash[key] = convert(value)
|
27
|
-
}
|
28
|
-
end
|
29
|
-
|
30
|
-
def convert(value)
|
31
|
-
if value.is_a?(Hash)
|
32
|
-
self.class.new(value).call
|
33
|
-
elsif value.is_a?(Array)
|
34
|
-
value.map { |element| convert(element) }
|
35
|
-
else
|
36
|
-
value
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|