transmogrifier 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NGEzMmI4YjQ5ODEyZGE3NDJiYWE4OGU5MTg1MDY4NDk5YmI2MDFmNA==
4
+ MDE3MWU3Yjc2NTU5ZjQ5NTNmMzhmZDU0YWU0N2NjZWExMjQzNmQwMA==
5
5
  data.tar.gz: !binary |-
6
- NjA2ZGNlOGQzMWQ2MmYyMWZmMjVlNzg3MjZjYWMwYjY1YTQ4ZTliOQ==
7
- !binary "U0hBNTEy":
6
+ ZTk1NTkyN2JlZTRkMzI2MTgxZTM3ZjY2OWFmZTY4YjRjYmUxZTQwOA==
7
+ SHA512:
8
8
  metadata.gz: !binary |-
9
- ZDg5MWEwOGE4YTkwMmUzNzA0ZjliMjJiZDJjYzhlOGEwNzI0OGRkMTMyYTM4
10
- NjI0YjE1N2NhZmZkYTE1MWEwYmEwYjBiZGVmNzUyNDlmODQyZDQ0YmI2ZWVm
11
- NTNmZTFlOGRjMzZkOWU1NWRlZjQwZGZlNzVlMWZhOTFhZWVhYjA=
9
+ ZDQ3MDFkYzkyZTE2NDk1M2RlYmM1MjFlOTRhNzIxN2JmZWE4MWFlNDk5MDcw
10
+ NjhkMWVlZTBjZDllZTNhZjg2MWU1OWU2N2QxNDNmYWQyYzk3MWE2YjVjNDIy
11
+ YzJjNTM3NWVjNmU1YTQ3YjgxNGRhNTBlZTIxMzY4NTM4NGQzYTk=
12
12
  data.tar.gz: !binary |-
13
- Y2JiMTQ5ZDhjNjU1Y2YwYzk4ZWYwYzlmMmQxMTkzNTU3MGFlY2M5ZGQ0NTU3
14
- NjE4NDYyNDRmZDRlMmMwMDU3ZGEwMDEwZTZlN2Q2Mjk3MzMyYjljMDJhNDMy
15
- MDY4OTEyZWZlZWUyNGFkYWEwMjgwM2ZhMmE0ZDU5NmU2MjNkMTE=
13
+ MDdhNWI1ODUwNDcxNDQ0YTFmZDBmMjI4MzMwNTgzMjFiNDhlMGYxZDQ2MTVl
14
+ NjJhMDhkZTMzOTE3NDBhMjk5YzgwNjY0OTMyYWI1MWIxYTFkZmE0NjllOTgy
15
+ ZjdjZWM5YThjY2ZiMWYzNmNkNmJiZWE0MzA4ZDRjNzFhZDg5MTQ=
data/README.md CHANGED
@@ -17,6 +17,19 @@ output_hash = engine.run(input_hash)
17
17
  # output_hash => {"key" => "value", "new_key" => "new_value"}
18
18
  ```
19
19
 
20
+ #### Copying a key
21
+ ```ruby
22
+ engine = Transmogrifier::Engine.new
23
+ move = Transmogrifier::Rules::Copy.new("", "key", "nested")
24
+
25
+ engine.add_rule(:copy, "", "key", "nested")
26
+
27
+ input_hash = {"key" => "value", "nested" => {"nested_key" => "nested_value"}}
28
+ output_hash = transmogrifier.run(input_hash)
29
+
30
+ # output_hash => {"key" => "value", "nested" => {"nested_key" => "nested_value", "key" => "value"}}
31
+ ```
32
+
20
33
  #### Deleting a key
21
34
  ```ruby
22
35
  engine = Transmogrifier::Engine.new
@@ -30,6 +43,19 @@ output_hash = engine.run(input_hash)
30
43
  # output_hash => {"key" => "value"}
31
44
  ```
32
45
 
46
+ #### Modifying a value
47
+ ```ruby
48
+ engine = Transmogrifier::Engine.new
49
+ move = Transmogrifier::Rules::Modify.new("", "key", "nested")
50
+
51
+ engine.add_rule(:modify, "key", "al", "og")
52
+
53
+ input_hash = {"key" => "value", "nested" => {"nested_key" => "nested_value"}}
54
+ output_hash = transmogrifier.run(input_hash)
55
+
56
+ # output_hash => {"key" => "vogue", "nested" => {"nested_key" => "nested_value"}}
57
+ ```
58
+
33
59
  #### Moving a key
34
60
  ```ruby
35
61
  engine = Transmogrifier::Engine.new
@@ -109,4 +135,4 @@ the selector `nested.second_level.deep` will apply to `buried_value`. Rules can
109
135
  ],
110
136
  }
111
137
  ```
112
- the hash with the name `this one!` can be operated on with `array.[name=this one!]`. Arrays can also wildcard match all children. For example to match both hashes in the array above, use the selector `array.[]`.
138
+ the hash with the name `this one!` can be operated on with `array.[name=this one!]` or `array.[name!=not me]`. Arrays can also wildcard match all children. For example to match both hashes in the array above, use the selector `array.[]`.
@@ -5,7 +5,14 @@ module Transmogrifier
5
5
  rules_array.map do |rule|
6
6
  type = rule["type"].capitalize
7
7
  selector = rule["selector"]
8
- options = [rule["object"], rule["from"], rule["to"], rule["name"]].compact
8
+ options = [
9
+ rule["object"],
10
+ rule["from"],
11
+ rule["to"],
12
+ rule["name"],
13
+ rule["pattern"],
14
+ rule["replacement"],
15
+ ].compact
9
16
  Transmogrifier::Rules.const_get(type).new(selector, *options)
10
17
  end
11
18
  )
@@ -18,10 +18,17 @@ module Transmogrifier
18
18
  end
19
19
  end
20
20
 
21
+ def clone(key)
22
+ matching_nodes = find_nodes(key)
23
+ raise "Multiple nodes match #{key}, clone criteria ambiguous" if matching_nodes.length > 1
24
+
25
+ Marshal.load(Marshal.dump(matching_nodes.first))
26
+ end
27
+
21
28
  def delete(key)
22
29
  matching_nodes = find_nodes(key)
23
- raise "Multiple nodes match #{key}, deletion criteria ambiguous" if matching_nodes.length > 1
24
- @array.delete(matching_nodes.first)
30
+ deleted_nodes = matching_nodes.each { |n| @array.delete(n) }
31
+ deleted_nodes.length > 1 ? deleted_nodes : deleted_nodes.first
25
32
  end
26
33
 
27
34
  def_delegator :@array, :<<, :append
@@ -30,7 +37,20 @@ module Transmogrifier
30
37
  private
31
38
 
32
39
  def find_nodes(attributes)
33
- @array.select { |node| node.merge(Hash[attributes]) == node }
40
+ return @array if attributes.empty?
41
+
42
+ filtered = @array.clone
43
+ attributes.each do |attr|
44
+ case attr[0]
45
+ when "="
46
+ filtered.select! { |node| node.merge(Hash[*attr[1..-1]]) == node }
47
+ when "!="
48
+ filtered.reject! { |node| node.merge(Hash[*attr[1..-1]]) == node }
49
+ else
50
+ raise "Unsupported attribute filter #{attr.inspect}"
51
+ end
52
+ end
53
+ filtered
34
54
  end
35
55
  end
36
56
  end
@@ -20,6 +20,13 @@ module Transmogrifier
20
20
  end
21
21
  end
22
22
 
23
+ def clone(key)
24
+ matching_node = @hash[key]
25
+ raise "Multiple nodes match #{key}, clone criteria ambiguous" if matching_node.nil?
26
+
27
+ Marshal.load(Marshal.dump(matching_node))
28
+ end
29
+
23
30
  def_delegator :@hash, :delete
24
31
  def_delegator :@hash, :merge!, :append
25
32
  def_delegator :@hash, :to_hash, :raw
@@ -19,6 +19,10 @@ module Transmogrifier
19
19
  raise NotImplementedError
20
20
  end
21
21
 
22
+ def clone(key_or_name)
23
+ raise NotImplementedError
24
+ end
25
+
22
26
  def delete(key_or_name)
23
27
  raise NotImplementedError
24
28
  end
@@ -26,5 +30,9 @@ module Transmogrifier
26
30
  def append(node)
27
31
  raise NotImplementedError
28
32
  end
33
+
34
+ def modify(pattern, replacement)
35
+ raise NotImplementedError
36
+ end
29
37
  end
30
38
  end
@@ -11,6 +11,11 @@ module Transmogrifier
11
11
  raise "cannot find children of ValueNode satisfying non-empty selector #{keys}"
12
12
  end
13
13
 
14
+ def modify(pattern, replacement)
15
+ raise "Value is not modifiable using pattern matching" unless @value.respond_to?(:gsub)
16
+ return @value.gsub!(Regexp.new(pattern), replacement)
17
+ end
18
+
14
19
  def raw
15
20
  @value
16
21
  end
@@ -0,0 +1,29 @@
1
+ module Transmogrifier
2
+ module Rules
3
+ class Copy
4
+ def initialize(parent_selector, from, to)
5
+ @parent_selector, @from, @to = parent_selector, from, to
6
+ end
7
+
8
+ def apply!(input_hash)
9
+ top = Node.for(input_hash)
10
+ *from_keys, from_key = Selector.from_string(@from).keys
11
+ *to_keys, to_key = Selector.from_string(@to).keys
12
+
13
+ parents = top.find_all(Selector.from_string(@parent_selector).keys)
14
+ parents.each do |parent|
15
+ to_parent = parent.find_all(to_keys).first
16
+ object = parent.find_all(from_keys).first.clone(from_key)
17
+
18
+ if to_child = to_parent.find_all([to_key]).first
19
+ to_child.append(object)
20
+ else
21
+ to_parent.append({to_key => object})
22
+ end
23
+ end
24
+
25
+ top.raw
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,19 @@
1
+ module Transmogrifier
2
+ module Rules
3
+ class Modify
4
+ def initialize(parent_selector, pattern, replacement)
5
+ @parent_selector, @pattern, @replacement = parent_selector, pattern, replacement
6
+ end
7
+
8
+ def apply!(input_hash)
9
+ top = Node.for(input_hash)
10
+ parent_keys = Selector.from_string(@parent_selector).keys
11
+
12
+ parents = top.find_all(parent_keys)
13
+ parents.each { |parent| parent.modify(@pattern, @replacement) }
14
+
15
+ top.raw
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,12 +1,13 @@
1
1
  module Transmogrifier
2
2
  class Selector
3
3
  FILTER_REGEX = /\[(.*)\]/
4
+ OPERATORS = ["!=", "="]
4
5
 
5
6
  def self.from_string(string)
6
7
  new(
7
8
  string.split(".").map do |str|
8
9
  match = str.scan(FILTER_REGEX).flatten.first
9
- match ? match.split(",").map { |s| s.split("=") } : str
10
+ match ? match.split(",").map {|s| to_array(s)} : str
10
11
  end
11
12
  )
12
13
  end
@@ -16,5 +17,15 @@ module Transmogrifier
16
17
  def initialize(keys)
17
18
  @keys = keys
18
19
  end
20
+
21
+ private
22
+
23
+ def self.to_array(string)
24
+ OPERATORS.each do |op|
25
+ next unless string.include?(op)
26
+ return string.split(op).insert(0, op)
27
+ end
28
+ [string]
29
+ end
19
30
  end
20
31
  end
@@ -1,3 +1,3 @@
1
1
  module Transmogrifier
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.3"
3
3
  end
data/spec/engine_spec.rb CHANGED
@@ -23,6 +23,20 @@ describe Transmogrifier::Engine do
23
23
  "type" => "delete",
24
24
  "selector" => "top",
25
25
  "name" => "key3"
26
+ },
27
+
28
+ {
29
+ "type" => "copy",
30
+ "selector" => "top",
31
+ "from" => "key4",
32
+ "to" => "key5",
33
+ },
34
+
35
+ {
36
+ "type" => "modify",
37
+ "selector" => "top.key5",
38
+ "pattern" => "\\d+",
39
+ "replacement" => ".num",
26
40
  }
27
41
  ]
28
42
  end
@@ -31,7 +45,8 @@ describe Transmogrifier::Engine do
31
45
  input_hash = {
32
46
  "top" => {
33
47
  "key1" => "value1",
34
- "key3" => "value2"
48
+ "key3" => "value2",
49
+ "key4" => "value4",
35
50
  }
36
51
  }
37
52
 
@@ -39,6 +54,8 @@ describe Transmogrifier::Engine do
39
54
  "top" => {
40
55
  "some" => "attributes",
41
56
  "key2" => "value1",
57
+ "key4" => "value4",
58
+ "key5" => "value.num",
42
59
  }
43
60
  })
44
61
  end
@@ -24,14 +24,78 @@ module Transmogrifier
24
24
  ])
25
25
  end
26
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)
27
+ context "when attribute filters are specified" do
28
+ it "filters using '='" do
29
+ array = [
30
+ {"type" => "object", "key1" => "value1"},
31
+ {"type" => "object", "key2" => "value2"},
32
+ ]
33
+ node = ArrayNode.new(array)
34
+
35
+ expect(node.find_all([[["=", "type", "object"]]]).map(&:raw)).to eq(array)
36
+ end
37
+
38
+ it "filters using '!='" do
39
+ array = [
40
+ {"type" => "object1", "key1" => "value1"},
41
+ {"type" => "object2", "key2" => "value2"},
42
+ ]
43
+ node = ArrayNode.new(array)
44
+
45
+ expect(node.find_all([[["!=", "type", "object1"]]]).map(&:raw)).to eq(array.slice(1,1))
46
+ end
47
+
48
+ it "filters using '!=' and '='" do
49
+ array = [
50
+ {"type" => "object1", "key1" => "value1"},
51
+ {"type" => "object2", "key2" => "value2"},
52
+ {"type" => "object3", "key3" => "value3"},
53
+ ]
54
+ node = ArrayNode.new(array)
55
+
56
+ expect(node.find_all([[["!=", "type", "object1"],["=", "type", "object2"]]]).map(&:raw)).to eq(array.slice(1,1))
57
+ end
58
+
59
+ it "raises ArgumentError for unknown operators" do
60
+ array = [
61
+ {"type" => "object1", "key1" => "value1"},
62
+ {"type" => "object2", "key2" => "value2"},
63
+ ]
64
+ node = ArrayNode.new(array)
65
+
66
+ expect{node.find_all([[["~", "type", "object1"]]])}.to raise_error
67
+ expect{node.find_all([[["value"]]])}.to raise_error
68
+ end
69
+ end
70
+ end
71
+
72
+ describe "#clone" do
73
+ context "when one node matches the criteria" do
74
+ it "returns the node from the array" do
75
+ value = {"name" => "object1"}
76
+ array = [value, {"name" => "object2"}]
77
+ node = ArrayNode.new(array)
78
+ expect(node.clone([["=", "name", "object1"]])).to eq(value)
79
+ expect(node.clone([["=", "name", "object1"]])).to_not be(value)
80
+ end
81
+ end
82
+
83
+ context "when more than one node matches the criteria" do
84
+ it "raises an error" do
85
+ array = [{"name" => "object1", "common_key" => "common_value"}, {"name" => "object2", "common_key" => "common_value"}]
86
+ node = ArrayNode.new(array)
87
+ expect {
88
+ node.clone([["=", "common_key", "common_value"]])
89
+ }.to raise_error
90
+ end
91
+ end
33
92
 
34
- expect(node.find_all([["type", "object"]]).map(&:raw)).to eq(array)
93
+ context "when no nodes match the criteria" do
94
+ it "returns nil" do
95
+ array = [{"name" => "object1"}, {"name" => "object2"}]
96
+ node = ArrayNode.new(array)
97
+ expect(node.clone([["=", "name", "not_present"]])).to be_nil
98
+ end
35
99
  end
36
100
  end
37
101
 
@@ -43,20 +107,18 @@ module Transmogrifier
43
107
  {"name" => "object2"},
44
108
  ]
45
109
  node = ArrayNode.new(array)
46
- expect(node.delete([["name", "object1"]])).to eq({"name" => "object1"})
110
+ expect(node.delete([["=", "name", "object1"]])).to eq({"name" => "object1"})
47
111
  end
48
112
  end
49
113
 
50
114
  context "when more than one node matches the criteria" do
51
- it "raises an error" do
115
+ it "deletes all matching nodes from array" do
52
116
  array = [
53
117
  {"name" => "object1", "common_key" => "common_value"},
54
118
  {"name" => "object2", "common_key" => "common_value"},
55
119
  ]
56
- node = ArrayNode.new(array)
57
- expect {
58
- node.delete([["common_key", "common_value"]])
59
- }.to raise_error
120
+ node = ArrayNode.new(array.clone)
121
+ expect(node.delete([["=", "common_key", "common_value"]])).to eq(array)
60
122
  end
61
123
  end
62
124
 
@@ -67,7 +129,7 @@ module Transmogrifier
67
129
  {"name" => "object2"},
68
130
  ]
69
131
  node = ArrayNode.new(array)
70
- expect(node.delete([["name", "not_present"]])).to be_nil
132
+ expect(node.delete([["=", "name", "not_present"]])).to be_nil
71
133
  end
72
134
  end
73
135
  end
@@ -80,5 +142,13 @@ module Transmogrifier
80
142
  expect(node.raw).to eq([{"name" => "object1"}, {"name" => "object2"}])
81
143
  end
82
144
  end
145
+
146
+ describe "#modify" do
147
+ it "raises a NotImplementedError" do
148
+ expect {
149
+ ArrayNode.new("hello").modify("value", "value2")
150
+ }.to raise_error(NotImplementedError)
151
+ end
152
+ end
83
153
  end
84
154
  end
@@ -26,7 +26,7 @@ module Transmogrifier
26
26
  end
27
27
 
28
28
  context "given multiple selector keys" do
29
- let(:keys) { ["key", [["k", "v"]]] }
29
+ let(:keys) { ["key", [["=", "k", "v"]]] }
30
30
 
31
31
  context "when the first key is a key in the hash" do
32
32
  let(:hash) {{
@@ -65,6 +65,18 @@ module Transmogrifier
65
65
  end
66
66
  end
67
67
 
68
+ describe "#clone" do
69
+ it "returns a copy of value" do
70
+ value = "other_value"
71
+ hash = {"key" => "value", "extra_key" => value}
72
+ node = HashNode.new(hash)
73
+
74
+ expect(node.clone("extra_key")).to eql(value)
75
+ expect(node.clone("extra_key")).to_not be(value)
76
+ expect(node.raw).to eq("key" => "value", "extra_key" => "other_value")
77
+ end
78
+ end
79
+
68
80
  describe "#delete" do
69
81
  it "deletes the given key" do
70
82
  hash = {"key" => "value", "extra_key" => "other_value"}
@@ -90,5 +102,13 @@ module Transmogrifier
90
102
  expect(node.raw).to eq({"key" => "value", "extra_key" => "extra_value"})
91
103
  end
92
104
  end
105
+
106
+ describe "#modify" do
107
+ it "raises a NotImplementedError" do
108
+ expect {
109
+ HashNode.new("hello").modify("value", "value2")
110
+ }.to raise_error(NotImplementedError)
111
+ end
112
+ end
93
113
  end
94
114
  end
@@ -27,6 +27,14 @@ module Transmogrifier
27
27
  end
28
28
  end
29
29
 
30
+ describe "#clone" do
31
+ it "raises a NotImplementedError" do
32
+ expect {
33
+ ValueNode.new("hello").clone("key")
34
+ }.to raise_error(NotImplementedError)
35
+ end
36
+ end
37
+
30
38
  describe "#delete" do
31
39
  it "raises a NotImplementedError" do
32
40
  expect {
@@ -42,5 +50,29 @@ module Transmogrifier
42
50
  }.to raise_error(NotImplementedError)
43
51
  end
44
52
  end
53
+
54
+ describe "#modify" do
55
+ context "when pattern matches" do
56
+ it "modifies the value based on simple pattern" do
57
+ node = ValueNode.new("value")
58
+ expect(node.modify("al", "og")).to eq("vogue")
59
+ expect(node.raw).to eq("vogue")
60
+ end
61
+
62
+ it "modifies the value based on complex pattern" do
63
+ node = ValueNode.new("valllue")
64
+ expect(node.modify("a.*u", "ogu")).to eq("vogue")
65
+ expect(node.raw).to eq("vogue")
66
+ end
67
+ end
68
+
69
+ context "when pattern does not match" do
70
+ it "does not modify value" do
71
+ node = ValueNode.new("value")
72
+ expect(node.modify("ss", "og")).to be_nil
73
+ expect(node.raw).to eq("value")
74
+ end
75
+ end
76
+ end
45
77
  end
46
78
  end
@@ -0,0 +1,99 @@
1
+ require "transmogrifier"
2
+
3
+ describe Transmogrifier::Rules::Copy 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(:copy) { described_class.new("", "array.[inside=value]", "nested") }
17
+
18
+ it "moves the hash to the to selector" do
19
+ expect(copy.apply!(input_hash)).to eq({
20
+ "key" => "value",
21
+ "array" => [{"inside" => "value"}],
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(:copy) { described_class.new("", "array.[inside=value]", "nested.nested_again") }
32
+
33
+ it "moves the hash to a new child" do
34
+ expect(copy.apply!(input_hash)).to eq({
35
+ "key" => "value",
36
+ "array" => [{"inside" => "value"}],
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(:copy) { described_class.new("list_of_things.[]", "property", "nested.property") }
65
+
66
+ it "moves the matched hash to the correct place" do
67
+ expect(copy.apply!(input_hash)).to eq({
68
+ "list_of_things" => [
69
+ {
70
+ "name" => "object1",
71
+ "property" => "property1",
72
+ "nested" => { "property" => "property1" }
73
+ },
74
+ {
75
+ "name" => "object2",
76
+ "property" => "property2",
77
+ "nested" => { "property" => "property2" }
78
+ },
79
+ ]
80
+ })
81
+ end
82
+ end
83
+ end
84
+
85
+ context "when the selector finds an ArrayNode" do
86
+ subject(:copy) { described_class.new("", "array", "nested.array") }
87
+
88
+ it "moves the array to the to selector" do
89
+ expect(copy.apply!(input_hash)).to eq({
90
+ "key" => "value",
91
+ "array" => [{"inside" => "value"}],
92
+ "nested" => {
93
+ "key" => "value",
94
+ "array" => [{"inside" => "value"}],
95
+ },
96
+ })
97
+ end
98
+ end
99
+ end
@@ -35,4 +35,20 @@ describe Transmogrifier::Rules::Delete do
35
35
  })
36
36
  end
37
37
  end
38
+
39
+ context "when the selector matches multiple nodes" do
40
+ subject(:delete) { described_class.new("array", "[inside=value]") }
41
+
42
+ before { input_hash["array"] << {"inside" => "value"} }
43
+
44
+ it "deletes all entries from the array" do
45
+ expect(delete.apply!(input_hash)).to eq({
46
+ "key" => "value",
47
+ "array" => [],
48
+ "nested" => {
49
+ "key" => "value"
50
+ },
51
+ })
52
+ end
53
+ end
38
54
  end
@@ -0,0 +1,34 @@
1
+ require "transmogrifier"
2
+
3
+ describe Transmogrifier::Rules::Modify do
4
+ let(:input_hash) do
5
+ { "key" => "value", "array" => [] }
6
+ end
7
+
8
+ context "when the selector finds a ValueNode" do
9
+ subject(:modify) { described_class.new("key", "al", "og") }
10
+
11
+ it "modifies the value" do
12
+ expect(modify.apply!(input_hash)).to eq({
13
+ "key" => "vogue",
14
+ "array" => [],
15
+ })
16
+ end
17
+ end
18
+
19
+ context "when the selector finds a HashNode" do
20
+ subject(:modify) { described_class.new("", "al", "og") }
21
+
22
+ it "appends to the hash" do
23
+ expect{modify.apply!(input_hash)}.to raise_error(NotImplementedError)
24
+ end
25
+ end
26
+
27
+ context "when the selector finds an ArrayNode" do
28
+ subject(:modify) { described_class.new("array", "al", "og") }
29
+
30
+ it "appends to the array" do
31
+ expect{modify.apply!(input_hash)}.to raise_error(NotImplementedError)
32
+ end
33
+ end
34
+ end
@@ -94,6 +94,23 @@ describe Transmogrifier::Rules::Move do
94
94
  end
95
95
  end
96
96
 
97
+ context "when the selector finds multiple nodes" do
98
+ subject(:move) { described_class.new("", "array.[inside=value]", "nested.array") }
99
+
100
+ before { input_hash["array"] << {"inside" => "value"} }
101
+
102
+ it "moves them all as an array to the selector" do
103
+ expect(move.apply!(input_hash)).to eq({
104
+ "key" => "value",
105
+ "array" => [],
106
+ "nested" => {
107
+ "key" => "value",
108
+ "array" => [{"inside" => "value"},{"inside" => "value"}],
109
+ },
110
+ })
111
+ end
112
+ end
113
+
97
114
  context "using move as a rename" do
98
115
  subject(:rename) { described_class.new("", "array", "new_array") }
99
116
 
@@ -11,15 +11,24 @@ describe Transmogrifier::Selector do
11
11
  end
12
12
 
13
13
  it "allows filtering by attribute" do
14
- described_class.from_string("[attr1=val1,attr2=val2]").keys.should == [[["attr1", "val1"],["attr2", "val2"]]]
14
+ described_class.from_string("[attr1=val1,attr2=val2]").keys.should == [[["=", "attr1", "val1"],["=", "attr2", "val2"]]]
15
+ end
16
+
17
+ it "allows filtering by attribute with not-equal comparison" do
18
+ described_class.from_string("[attr1!=val1,attr2!=val2]").keys.should == [[["!=", "attr1", "val1"],["!=", "attr2", "val2"]]]
15
19
  end
16
20
 
17
21
  it "allows empty array" do
18
22
  described_class.from_string("[]").keys.should == [[]]
19
23
  end
20
24
 
25
+ it "returns array filter with the whole value for unknown or missing operator" do
26
+ described_class.from_string("[attr1,attr2~val2]").keys.should == [[["attr1"], ["attr2~val2"]]]
27
+ end
28
+
21
29
  it "combines hash and array filtering" do
22
- described_class.from_string("foo.[attr=val]").keys.should == ["foo", [["attr", "val"]]]
30
+ described_class.from_string("foo.[attr=val,attr!=val1]").keys.should == ["foo", [["=", "attr", "val"],["!=", "attr", "val1"]]]
31
+ described_class.from_string("[attr=val,attr!=val1].bar").keys.should == [[["=", "attr", "val"],["!=", "attr", "val1"]], "bar"]
23
32
  end
24
33
  end
25
34
  end
@@ -12,19 +12,29 @@ describe Transmogrifier::Engine do
12
12
  "selector" => "top",
13
13
  "object" => {"some" => "attributes"}
14
14
  },
15
-
16
15
  {
17
16
  "type" => "move",
18
17
  "selector" => "top",
19
18
  "from" => "key1",
20
19
  "to" => "key2",
21
20
  },
22
-
23
21
  {
24
22
  "type" => "delete",
25
23
  "selector" => "top",
26
24
  "name" => "key3"
27
- }
25
+ },
26
+ {
27
+ "type" => "copy",
28
+ "selector" => "top",
29
+ "from" => "key4",
30
+ "to" => "key5",
31
+ },
32
+ {
33
+ "type" => "modify",
34
+ "selector" => "top.key5",
35
+ "pattern" => "\\d+",
36
+ "replacement" => ".num",
37
+ },
28
38
  ]
29
39
  end
30
40
 
@@ -32,7 +42,8 @@ describe Transmogrifier::Engine do
32
42
  input_hash = {
33
43
  "top" => {
34
44
  "key1" => "value1",
35
- "key3" => "value2"
45
+ "key3" => "value2",
46
+ "key4" => "value4",
36
47
  }
37
48
  }
38
49
 
@@ -40,6 +51,8 @@ describe Transmogrifier::Engine do
40
51
  "top" => {
41
52
  "some" => "attributes",
42
53
  "key2" => "value1",
54
+ "key4" => "value4",
55
+ "key5" => "value.num",
43
56
  }
44
57
  })
45
58
  end
@@ -75,6 +88,100 @@ describe Transmogrifier::Engine do
75
88
  end
76
89
  end
77
90
 
91
+ describe "copying keys" do
92
+ let(:input_hash) do
93
+ {
94
+ "key" => "value",
95
+ "array" => [{"inside" => "value"}],
96
+ "nested" => { "key" => "value" },
97
+ }
98
+ end
99
+
100
+ context "when the selector finds a HashNode" do
101
+ context "when the target key exists" do
102
+ let(:rules) { [ {"type" => "copy", "selector" => "", "from" => "array.[inside=value]", "to" => "nested"}]}
103
+
104
+ it "copies the hash to the to selector" do
105
+ expect(engine.run(input_hash)).to eq({
106
+ "key" => "value",
107
+ "array" => [{"inside" => "value"}],
108
+ "nested" => {
109
+ "key" => "value",
110
+ "inside" => "value",
111
+ },
112
+ })
113
+ end
114
+ end
115
+
116
+ context "when the target key doesn't exist" do
117
+ let(:rules) { [ {"type" => "copy", "selector" => "", "from" => "array.[inside=value]", "to" => "nested.nested_again"} ] }
118
+
119
+ it "copies the hash to a new child" do
120
+ expect(engine.run(input_hash)).to eq({
121
+ "key" => "value",
122
+ "array" => [{"inside" => "value"}],
123
+ "nested" => {
124
+ "key" => "value",
125
+ "nested_again" => { "inside" => "value" }
126
+ },
127
+ })
128
+ end
129
+ end
130
+
131
+ context "when the from selector has a wildcard" do
132
+ let(:input_hash) do
133
+ {
134
+ "list_of_things" => [
135
+ {
136
+ "name" => "object1",
137
+ "property" => "property1",
138
+ "nested" => {}
139
+ },
140
+ {
141
+ "name" => "object2",
142
+ "property" => "property2",
143
+ "nested" => {}
144
+ },
145
+ ]
146
+ }
147
+ end
148
+ let(:rules) { [ {"type" => "copy", "selector" => "list_of_things.[]", "from" => "property", "to" => "nested.property"} ] }
149
+
150
+ it "copies the matched hash to the correct place" do
151
+ expect(engine.run(input_hash)).to eq({
152
+ "list_of_things" => [
153
+ {
154
+ "name" => "object1",
155
+ "property" => "property1",
156
+ "nested" => { "property" => "property1" },
157
+ },
158
+ {
159
+ "name" => "object2",
160
+ "property" => "property2",
161
+ "nested" => { "property" => "property2" },
162
+ },
163
+ ]
164
+ })
165
+ end
166
+ end
167
+ end
168
+
169
+ context "when the selector finds an ArrayNode" do
170
+ let(:rules) { [ {"type" => "copy", "selector" => "", "from" => "array", "to" => "nested.array"} ] }
171
+
172
+ it "copies the array to the to selector" do
173
+ expect(engine.run(input_hash)).to eq({
174
+ "key" => "value",
175
+ "array" => [{"inside" => "value"}],
176
+ "nested" => {
177
+ "key" => "value",
178
+ "array" => [{"inside" => "value"}],
179
+ },
180
+ })
181
+ end
182
+ end
183
+ end
184
+
78
185
  describe "deleting keys" do
79
186
  let(:input_hash) do
80
187
  {
@@ -100,7 +207,23 @@ describe Transmogrifier::Engine do
100
207
  context "when the selector finds an ArrayNode" do
101
208
  let(:rules) { [ {"type" => "delete", "selector" => "array", "name" => "[inside=value]"} ] }
102
209
 
103
- it "deletes the array from the parent" do
210
+ it "deletes matching item from array" do
211
+ expect(engine.run(input_hash)).to eq({
212
+ "key" => "value",
213
+ "array" => [],
214
+ "nested" => {
215
+ "key" => "value"
216
+ },
217
+ })
218
+ end
219
+ end
220
+
221
+ context "when the selector matches multiple nodes" do
222
+ let(:rules) { [ {"type" => "delete", "selector" => "array", "name" => "[inside=value]"} ] }
223
+
224
+ before { input_hash["array"] << {"inside" => "value"} }
225
+
226
+ it "deletes all entries from the array" do
104
227
  expect(engine.run(input_hash)).to eq({
105
228
  "key" => "value",
106
229
  "array" => [],
@@ -206,6 +329,23 @@ describe Transmogrifier::Engine do
206
329
  end
207
330
  end
208
331
 
332
+ context "when the selector finds multiple nodes" do
333
+ let(:rules) { [ {"type" => "move", "selector" => "", "from" => "array.[inside=value]", "to" => "nested.array"} ] }
334
+
335
+ before { input_hash["array"] << {"inside" => "value"} }
336
+
337
+ it "moves them all as an array to the selector" do
338
+ expect(engine.run(input_hash)).to eq({
339
+ "key" => "value",
340
+ "array" => [],
341
+ "nested" => {
342
+ "key" => "value",
343
+ "array" => [{"inside" => "value"},{"inside" => "value"}],
344
+ },
345
+ })
346
+ end
347
+ end
348
+
209
349
  context "using move as a rename" do
210
350
  let(:rules) { [ {"type" => "move", "selector" => "", "from" => "array", "to" => "new_array"} ] }
211
351
 
@@ -220,6 +360,39 @@ describe Transmogrifier::Engine do
220
360
  end
221
361
  end
222
362
  end
363
+
364
+ describe "modifying value" do
365
+ let(:input_hash) do
366
+ { "key" => "value", "array" => [] }
367
+ end
368
+
369
+ context "when the selector finds a ValueNode" do
370
+ let(:rules) { [ {"type" => "modify", "selector" => "key", "pattern" => "al", "replacement" => "og"} ] }
371
+
372
+ it "modifies the value" do
373
+ expect(engine.run(input_hash)).to eq({
374
+ "key" => "vogue",
375
+ "array" => [],
376
+ })
377
+ end
378
+ end
379
+
380
+ context "when the selector finds a HashNode" do
381
+ let(:rules) { [ {"type" => "modify", "selector" => "", "pattern" => "al", "replacement" => "og"} ] }
382
+
383
+ it "appends to the hash" do
384
+ expect{engine.run(input_hash)}.to raise_error(NotImplementedError)
385
+ end
386
+ end
387
+
388
+ context "when the selector finds an ArrayNode" do
389
+ let(:rules) { [ {"type" => "modify", "selector" => "array", "pattern" => "al", "replacement" => "og"} ] }
390
+
391
+ it "appends to the array" do
392
+ expect{engine.run(input_hash)}.to raise_error(NotImplementedError)
393
+ end
394
+ end
395
+ end
223
396
  end
224
397
  end
225
398
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: transmogrifier
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Foley
@@ -59,11 +59,6 @@ executables: []
59
59
  extensions: []
60
60
  extra_rdoc_files: []
61
61
  files:
62
- - .gitignore
63
- - Gemfile
64
- - LICENSE.txt
65
- - README.md
66
- - Rakefile
67
62
  - lib/transmogrifier.rb
68
63
  - lib/transmogrifier/engine.rb
69
64
  - lib/transmogrifier/nodes.rb
@@ -73,21 +68,27 @@ files:
73
68
  - lib/transmogrifier/nodes/value_node.rb
74
69
  - lib/transmogrifier/rules.rb
75
70
  - lib/transmogrifier/rules/append.rb
71
+ - lib/transmogrifier/rules/copy.rb
76
72
  - lib/transmogrifier/rules/delete.rb
73
+ - lib/transmogrifier/rules/modify.rb
77
74
  - lib/transmogrifier/rules/move.rb
78
75
  - lib/transmogrifier/selector.rb
79
76
  - lib/transmogrifier/version.rb
77
+ - README.md
78
+ - LICENSE.txt
79
+ - Gemfile
80
80
  - spec/engine_spec.rb
81
81
  - spec/nodes/array_node_spec.rb
82
82
  - spec/nodes/hash_node_spec.rb
83
83
  - spec/nodes/node_spec.rb
84
84
  - spec/nodes/value_node_spec.rb
85
85
  - spec/rules/append_spec.rb
86
+ - spec/rules/copy_spec.rb
86
87
  - spec/rules/delete_spec.rb
88
+ - spec/rules/modify_spec.rb
87
89
  - spec/rules/move_spec.rb
88
90
  - spec/selector_spec.rb
89
91
  - spec/transmogrifier_spec.rb
90
- - transmogrifier.gemspec
91
92
  homepage: http://github.com/jfoley/transmogrifier
92
93
  licenses:
93
94
  - MIT
@@ -108,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
108
109
  version: '0'
109
110
  requirements: []
110
111
  rubyforge_project:
111
- rubygems_version: 2.0.7
112
+ rubygems_version: 2.1.11
112
113
  signing_key:
113
114
  specification_version: 4
114
115
  summary: A tool for manipulating schemas
@@ -119,7 +120,10 @@ test_files:
119
120
  - spec/nodes/node_spec.rb
120
121
  - spec/nodes/value_node_spec.rb
121
122
  - spec/rules/append_spec.rb
123
+ - spec/rules/copy_spec.rb
122
124
  - spec/rules/delete_spec.rb
125
+ - spec/rules/modify_spec.rb
123
126
  - spec/rules/move_spec.rb
124
127
  - spec/selector_spec.rb
125
128
  - spec/transmogrifier_spec.rb
129
+ has_rdoc:
data/.gitignore DELETED
@@ -1,18 +0,0 @@
1
- *.gem
2
- *.rbc
3
- .bundle
4
- .config
5
- .yardoc
6
- Gemfile.lock
7
- InstalledFiles
8
- _yardoc
9
- coverage
10
- doc/
11
- lib/bundler/man
12
- pkg
13
- rdoc
14
- spec/reports
15
- test/tmp
16
- test/version_tmp
17
- tmp
18
- .idea/
data/Rakefile DELETED
@@ -1 +0,0 @@
1
- require "bundler/gem_tasks"
@@ -1,37 +0,0 @@
1
- # -*- encoding: utf-8 -*-
2
-
3
- Gem::Specification.new do |s|
4
- s.name = "transmogrifier"
5
- s.version = "0.0.1"
6
-
7
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
- s.authors = ["John Foley"]
9
- s.date = "2013-11-06"
10
- s.description = "A tool for manipulating schemas"
11
- s.email = ["john@hisfoleyness.com"]
12
- s.files = [".gitignore", "Gemfile", "LICENSE.txt", "README.md", "Rakefile", "lib/transmogrifier.rb", "lib/transmogrifier/engine.rb", "lib/transmogrifier/nodes.rb", "lib/transmogrifier/nodes/array_node.rb", "lib/transmogrifier/nodes/hash_node.rb", "lib/transmogrifier/nodes/node.rb", "lib/transmogrifier/nodes/value_node.rb", "lib/transmogrifier/rules.rb", "lib/transmogrifier/rules/append.rb", "lib/transmogrifier/rules/delete.rb", "lib/transmogrifier/rules/move.rb", "lib/transmogrifier/selector.rb", "lib/transmogrifier/version.rb", "spec/engine_spec.rb", "spec/nodes/array_node_spec.rb", "spec/nodes/hash_node_spec.rb", "spec/nodes/node_spec.rb", "spec/nodes/value_node_spec.rb", "spec/rules/append_spec.rb", "spec/rules/delete_spec.rb", "spec/rules/move_spec.rb", "spec/selector_spec.rb", "spec/transmogrifier_spec.rb", "transmogrifier.gemspec"]
13
- s.homepage = "http://github.com/jfoley/transmogrifier"
14
- s.licenses = ["MIT"]
15
- s.require_paths = ["lib"]
16
- s.rubygems_version = "2.0.7"
17
- s.summary = "A tool for manipulating schemas"
18
- s.test_files = ["spec/engine_spec.rb", "spec/nodes/array_node_spec.rb", "spec/nodes/hash_node_spec.rb", "spec/nodes/node_spec.rb", "spec/nodes/value_node_spec.rb", "spec/rules/append_spec.rb", "spec/rules/delete_spec.rb", "spec/rules/move_spec.rb", "spec/selector_spec.rb", "spec/transmogrifier_spec.rb"]
19
-
20
- if s.respond_to? :specification_version then
21
- s.specification_version = 4
22
-
23
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
24
- s.add_development_dependency(%q<bundler>, ["~> 1.3"])
25
- s.add_development_dependency(%q<rake>, [">= 0"])
26
- s.add_development_dependency(%q<rspec>, [">= 0"])
27
- else
28
- s.add_dependency(%q<bundler>, ["~> 1.3"])
29
- s.add_dependency(%q<rake>, [">= 0"])
30
- s.add_dependency(%q<rspec>, [">= 0"])
31
- end
32
- else
33
- s.add_dependency(%q<bundler>, ["~> 1.3"])
34
- s.add_dependency(%q<rake>, [">= 0"])
35
- s.add_dependency(%q<rspec>, [">= 0"])
36
- end
37
- end