transmogrifier 0.0.1
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 +15 -0
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +112 -0
- data/Rakefile +1 -0
- data/lib/transmogrifier.rb +5 -0
- data/lib/transmogrifier/engine.rb +28 -0
- data/lib/transmogrifier/nodes.rb +1 -0
- data/lib/transmogrifier/nodes/array_node.rb +36 -0
- data/lib/transmogrifier/nodes/hash_node.rb +27 -0
- data/lib/transmogrifier/nodes/node.rb +30 -0
- data/lib/transmogrifier/nodes/value_node.rb +18 -0
- data/lib/transmogrifier/rules.rb +1 -0
- data/lib/transmogrifier/rules/append.rb +19 -0
- data/lib/transmogrifier/rules/delete.rb +20 -0
- data/lib/transmogrifier/rules/move.rb +29 -0
- data/lib/transmogrifier/selector.rb +20 -0
- data/lib/transmogrifier/version.rb +3 -0
- data/spec/engine_spec.rb +46 -0
- data/spec/nodes/array_node_spec.rb +84 -0
- data/spec/nodes/hash_node_spec.rb +94 -0
- data/spec/nodes/node_spec.rb +19 -0
- data/spec/nodes/value_node_spec.rb +46 -0
- data/spec/rules/append_spec.rb +29 -0
- data/spec/rules/delete_spec.rb +38 -0
- data/spec/rules/move_spec.rb +110 -0
- data/spec/selector_spec.rb +25 -0
- data/spec/transmogrifier_spec.rb +225 -0
- data/transmogrifier.gemspec +37 -0
- metadata +125 -0
data/spec/engine_spec.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require "transmogrifier"
|
2
|
+
|
3
|
+
describe Transmogrifier::Engine do
|
4
|
+
subject(:engine) { described_class.from_rules_array(rules) }
|
5
|
+
|
6
|
+
describe "#run" do
|
7
|
+
let(:rules) do
|
8
|
+
[
|
9
|
+
{
|
10
|
+
"type" => "append",
|
11
|
+
"selector" => "top",
|
12
|
+
"object" => {"some" => "attributes"}
|
13
|
+
},
|
14
|
+
|
15
|
+
{
|
16
|
+
"type" => "move",
|
17
|
+
"selector" => "top",
|
18
|
+
"from" => "key1",
|
19
|
+
"to" => "key2",
|
20
|
+
},
|
21
|
+
|
22
|
+
{
|
23
|
+
"type" => "delete",
|
24
|
+
"selector" => "top",
|
25
|
+
"name" => "key3"
|
26
|
+
}
|
27
|
+
]
|
28
|
+
end
|
29
|
+
|
30
|
+
it "runs all the rules" do
|
31
|
+
input_hash = {
|
32
|
+
"top" => {
|
33
|
+
"key1" => "value1",
|
34
|
+
"key3" => "value2"
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
expect(engine.run(input_hash)).to eq({
|
39
|
+
"top" => {
|
40
|
+
"some" => "attributes",
|
41
|
+
"key2" => "value1",
|
42
|
+
}
|
43
|
+
})
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require "transmogrifier"
|
2
|
+
|
3
|
+
module Transmogrifier
|
4
|
+
describe ArrayNode do
|
5
|
+
describe "#raw" do
|
6
|
+
it "returns the underlying array" do
|
7
|
+
array = [{"name" => "object1"}, {"name" => "object2"}]
|
8
|
+
node = ArrayNode.new(array)
|
9
|
+
expect(node.raw).to eq(array)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#find_all" do
|
14
|
+
it "returns wildcard matches" do
|
15
|
+
array = [
|
16
|
+
{"name" => "object1", "nested" => {"key1" => "value1"}},
|
17
|
+
{"name" => "object2", "nested" => {"key2" => "value2"}}
|
18
|
+
]
|
19
|
+
node = ArrayNode.new(array)
|
20
|
+
|
21
|
+
expect(node.find_all([[], "nested"]).map(&:raw)).to eq([
|
22
|
+
{"key1" => "value1"},
|
23
|
+
{"key2" => "value2"},
|
24
|
+
])
|
25
|
+
end
|
26
|
+
|
27
|
+
it "filters by attributes" do
|
28
|
+
array = [
|
29
|
+
{"type" => "object", "key1" => "value1"},
|
30
|
+
{"type" => "object", "key2" => "value2"},
|
31
|
+
]
|
32
|
+
node = ArrayNode.new(array)
|
33
|
+
|
34
|
+
expect(node.find_all([["type", "object"]]).map(&:raw)).to eq(array)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#delete" do
|
39
|
+
context "when one node matches the criteria" do
|
40
|
+
it "deletes the node from the array" do
|
41
|
+
array = [
|
42
|
+
{"name" => "object1"},
|
43
|
+
{"name" => "object2"},
|
44
|
+
]
|
45
|
+
node = ArrayNode.new(array)
|
46
|
+
expect(node.delete([["name", "object1"]])).to eq({"name" => "object1"})
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "when more than one node matches the criteria" do
|
51
|
+
it "raises an error" do
|
52
|
+
array = [
|
53
|
+
{"name" => "object1", "common_key" => "common_value"},
|
54
|
+
{"name" => "object2", "common_key" => "common_value"},
|
55
|
+
]
|
56
|
+
node = ArrayNode.new(array)
|
57
|
+
expect {
|
58
|
+
node.delete([["common_key", "common_value"]])
|
59
|
+
}.to raise_error
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context "when no nodes match the criteria" do
|
64
|
+
it "returns nil" do
|
65
|
+
array = [
|
66
|
+
{"name" => "object1"},
|
67
|
+
{"name" => "object2"},
|
68
|
+
]
|
69
|
+
node = ArrayNode.new(array)
|
70
|
+
expect(node.delete([["name", "not_present"]])).to be_nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "#append" do
|
76
|
+
it "appends the node to the array" do
|
77
|
+
array = [{"name" => "object1"}]
|
78
|
+
node = ArrayNode.new(array)
|
79
|
+
node.append("name" => "object2")
|
80
|
+
expect(node.raw).to eq([{"name" => "object1"}, {"name" => "object2"}])
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require "transmogrifier"
|
2
|
+
|
3
|
+
module Transmogrifier
|
4
|
+
describe HashNode do
|
5
|
+
describe "#raw" do
|
6
|
+
it "returns the passed in hash" do
|
7
|
+
node = HashNode.new({"key" => "value"})
|
8
|
+
expect(node.raw).to eq({"key" => "value"})
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#find_all" do
|
13
|
+
context "given an empty set of selector keys" do
|
14
|
+
it "returns itself in an array" do
|
15
|
+
hash = {
|
16
|
+
"key" => "value",
|
17
|
+
"extra_key" => "other_value",
|
18
|
+
}
|
19
|
+
node = HashNode.new(hash)
|
20
|
+
|
21
|
+
expect(node.find_all([]).map(&:raw)).to eq([{
|
22
|
+
"key" => "value",
|
23
|
+
"extra_key" => "other_value",
|
24
|
+
}])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "given multiple selector keys" do
|
29
|
+
let(:keys) { ["key", [["k", "v"]]] }
|
30
|
+
|
31
|
+
context "when the first key is a key in the hash" do
|
32
|
+
let(:hash) {{
|
33
|
+
"key" => [
|
34
|
+
{"k" => "v", "a" => "b"},
|
35
|
+
{"k" => "not_v", "c" => "d"},
|
36
|
+
{"k" => "v", "e" => "f"},
|
37
|
+
]
|
38
|
+
}}
|
39
|
+
|
40
|
+
it "finds all children of the first key's value satisfying the remaining selector keys" do
|
41
|
+
node = HashNode.new(hash)
|
42
|
+
|
43
|
+
expect(node.find_all(keys).map(&:raw)).to eq([
|
44
|
+
{"k" => "v", "a" => "b"},
|
45
|
+
{"k" => "v", "e" => "f"}
|
46
|
+
])
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "when the first key is not a key in the hash" do
|
51
|
+
let(:hash) {{
|
52
|
+
"not_key" => [
|
53
|
+
{"k" => "v", "a" => "b"},
|
54
|
+
{"k" => "not_v", "c" => "d"},
|
55
|
+
{"k" => "v", "e" => "f"}
|
56
|
+
]
|
57
|
+
}}
|
58
|
+
|
59
|
+
it "returns an empty array" do
|
60
|
+
node = HashNode.new(hash)
|
61
|
+
|
62
|
+
expect(node.find_all(keys).map(&:raw)).to eq([])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "#delete" do
|
69
|
+
it "deletes the given key" do
|
70
|
+
hash = {"key" => "value", "extra_key" => "other_value"}
|
71
|
+
node = HashNode.new(hash)
|
72
|
+
node.delete("extra_key")
|
73
|
+
|
74
|
+
expect(node.raw).to eq({"key" => "value"})
|
75
|
+
end
|
76
|
+
|
77
|
+
it "returns the node that was deleted" do
|
78
|
+
hash = {"key" => "value", "extra_key" => "other_value"}
|
79
|
+
node = HashNode.new(hash)
|
80
|
+
|
81
|
+
expect(node.delete("extra_key")).to eq("other_value")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "#append" do
|
86
|
+
it "appends the given node at the key" do
|
87
|
+
hash = {"key" => "value"}
|
88
|
+
node = HashNode.new(hash)
|
89
|
+
node.append({ "extra_key" => "extra_value"})
|
90
|
+
expect(node.raw).to eq({"key" => "value", "extra_key" => "extra_value"})
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "transmogrifier"
|
2
|
+
|
3
|
+
module Transmogrifier
|
4
|
+
describe Node do
|
5
|
+
describe ".for" do
|
6
|
+
it "returns a node value" do
|
7
|
+
Node.for("value").should be_a(ValueNode)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "returns a hash value" do
|
11
|
+
Node.for("key" => "value").should be_a(HashNode)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "returns an array value" do
|
15
|
+
Node.for(["value"]).should be_a(ArrayNode)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require "transmogrifier"
|
2
|
+
|
3
|
+
module Transmogrifier
|
4
|
+
describe ValueNode do
|
5
|
+
describe "#raw" do
|
6
|
+
it "returns the passed in value" do
|
7
|
+
node = ValueNode.new("hello")
|
8
|
+
expect(node.raw).to eq("hello")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#find_all" do
|
13
|
+
context "when given empty keys" do
|
14
|
+
it "returns an array of itself" do
|
15
|
+
node = ValueNode.new("hello")
|
16
|
+
expect(node.find_all([])).to eq([node])
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "when given non-empty keys" do
|
21
|
+
it "raises an error" do
|
22
|
+
node = ValueNode.new("hello")
|
23
|
+
expect {
|
24
|
+
node.find_all(["foo.bar"])
|
25
|
+
}.to raise_error
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#delete" do
|
31
|
+
it "raises a NotImplementedError" do
|
32
|
+
expect {
|
33
|
+
ValueNode.new("hello").delete("key")
|
34
|
+
}.to raise_error(NotImplementedError)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#append" do
|
39
|
+
it "raises a NotImplementedError" do
|
40
|
+
expect {
|
41
|
+
ValueNode.new("hello").append("value")
|
42
|
+
}.to raise_error(NotImplementedError)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "transmogrifier"
|
2
|
+
|
3
|
+
describe Transmogrifier::Rules::Append do
|
4
|
+
let(:input_hash) do
|
5
|
+
{ "key" => "value", "array" => [] }
|
6
|
+
end
|
7
|
+
|
8
|
+
context "when the selector finds a HashNode" do
|
9
|
+
subject(:append) { described_class.new("", {"new_key" => "new_value"}) }
|
10
|
+
it "appends to the hash" do
|
11
|
+
expect(append.apply!(input_hash)).to eq({
|
12
|
+
"key" => "value",
|
13
|
+
"array" => [],
|
14
|
+
"new_key" => "new_value",
|
15
|
+
})
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "when the selector finds an ArrayNode" do
|
20
|
+
subject(:append) { described_class.new("array", {"new_key" => "new_value"}) }
|
21
|
+
|
22
|
+
it "appends to the array" do
|
23
|
+
expect(append.apply!(input_hash)).to eq({
|
24
|
+
"key" => "value",
|
25
|
+
"array" => [{"new_key" => "new_value"}],
|
26
|
+
})
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "transmogrifier"
|
2
|
+
|
3
|
+
describe Transmogrifier::Rules::Delete do
|
4
|
+
let(:input_hash) do
|
5
|
+
{
|
6
|
+
"key" => "value",
|
7
|
+
"array" => [{"inside" => "value"}],
|
8
|
+
"nested" => {
|
9
|
+
"key" => "value"
|
10
|
+
},
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
context "when the selector finds a HashNode" do
|
15
|
+
subject(:delete) { described_class.new("", "nested") }
|
16
|
+
|
17
|
+
it "deletes the hash from the parent" do
|
18
|
+
expect(delete.apply!(input_hash)).to eq({
|
19
|
+
"key" => "value",
|
20
|
+
"array" => [{"inside" => "value"}],
|
21
|
+
})
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "when the selector finds an ArrayNode" do
|
26
|
+
subject(:delete) { described_class.new("array", "[inside=value]") }
|
27
|
+
|
28
|
+
it "deletes the array from the parent" do
|
29
|
+
expect(delete.apply!(input_hash)).to eq({
|
30
|
+
"key" => "value",
|
31
|
+
"array" => [],
|
32
|
+
"nested" => {
|
33
|
+
"key" => "value"
|
34
|
+
},
|
35
|
+
})
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require "transmogrifier"
|
2
|
+
|
3
|
+
describe Transmogrifier::Rules::Move do
|
4
|
+
let(:input_hash) do
|
5
|
+
{
|
6
|
+
"key" => "value",
|
7
|
+
"array" => [{"inside" => "value"}],
|
8
|
+
"nested" => {
|
9
|
+
"key" => "value",
|
10
|
+
},
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
context "when the selector finds a HashNode" do
|
15
|
+
context "when the target key exists" do
|
16
|
+
subject(:move) { described_class.new("", "array.[inside=value]", "nested") }
|
17
|
+
|
18
|
+
it "moves the hash to the to selector" do
|
19
|
+
expect(move.apply!(input_hash)).to eq({
|
20
|
+
"key" => "value",
|
21
|
+
"array" => [],
|
22
|
+
"nested" => {
|
23
|
+
"key" => "value",
|
24
|
+
"inside" => "value",
|
25
|
+
},
|
26
|
+
})
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "when the target key doesn't exist" do
|
31
|
+
subject(:move) { described_class.new("", "array.[inside=value]", "nested.nested_again") }
|
32
|
+
|
33
|
+
it "moves the hash to a new child" do
|
34
|
+
expect(move.apply!(input_hash)).to eq({
|
35
|
+
"key" => "value",
|
36
|
+
"array" => [],
|
37
|
+
"nested" => {
|
38
|
+
"key" => "value",
|
39
|
+
"nested_again" => {
|
40
|
+
"inside" => "value",
|
41
|
+
}
|
42
|
+
},
|
43
|
+
})
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "when the from selector has a wildcard" do
|
48
|
+
let(:input_hash) do
|
49
|
+
{
|
50
|
+
"list_of_things" => [
|
51
|
+
{
|
52
|
+
"name" => "object1",
|
53
|
+
"property" => "property1",
|
54
|
+
"nested" => {}
|
55
|
+
},
|
56
|
+
{
|
57
|
+
"name" => "object2",
|
58
|
+
"property" => "property2",
|
59
|
+
"nested" => {}
|
60
|
+
},
|
61
|
+
]
|
62
|
+
}
|
63
|
+
end
|
64
|
+
subject(:move) { described_class.new("list_of_things.[]", "property", "nested.property") }
|
65
|
+
|
66
|
+
it "moves the matched hash to the correct place" do
|
67
|
+
expect(move.apply!(input_hash)).to eq({
|
68
|
+
"list_of_things" => [
|
69
|
+
{
|
70
|
+
"name" => "object1",
|
71
|
+
"nested" => { "property" => "property1" }
|
72
|
+
},
|
73
|
+
{
|
74
|
+
"name" => "object2",
|
75
|
+
"nested" => { "property" => "property2" }
|
76
|
+
},
|
77
|
+
]
|
78
|
+
})
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context "when the selector finds an ArrayNode" do
|
84
|
+
subject(:move) { described_class.new("", "array", "nested.array") }
|
85
|
+
|
86
|
+
it "moves the array to the to selector" do
|
87
|
+
expect(move.apply!(input_hash)).to eq({
|
88
|
+
"key" => "value",
|
89
|
+
"nested" => {
|
90
|
+
"key" => "value",
|
91
|
+
"array" => [{"inside" => "value"}],
|
92
|
+
},
|
93
|
+
})
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context "using move as a rename" do
|
98
|
+
subject(:rename) { described_class.new("", "array", "new_array") }
|
99
|
+
|
100
|
+
it "renames the node" do
|
101
|
+
expect(rename.apply!(input_hash)).to eq({
|
102
|
+
"key" => "value",
|
103
|
+
"new_array" => [{"inside" => "value"}],
|
104
|
+
"nested" => {
|
105
|
+
"key" => "value",
|
106
|
+
},
|
107
|
+
})
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|