yamload 0.0.6 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|