transmogrifier 0.0.1 → 0.0.3

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 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