transmogrifier 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|