abstractivator 0.0.22 → 0.0.23

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ed70b3e745e17ec7a24ef81a0f8734a88408f6ee
4
- data.tar.gz: 7f463093462a219c2f055da69e14f867ad45d5cd
3
+ metadata.gz: 5cf5eda70ec67c313b2df05ad3280682c8785f40
4
+ data.tar.gz: 9a0d1adaf5762a97cbf854086bb09edff5577169
5
5
  SHA512:
6
- metadata.gz: 36cc3d351e63c62cb1a84cd073747a734a145b1712111fad30431df3f82c59b5eb7cf4eca6c096d8eb824131c2b953a28eedcf0b83cc8ba1bc327ff8260aa1f2
7
- data.tar.gz: aabc33b65cd3f7133f78828e2f14e379c360f64accbccd2df9d6f399594d88730b8ccfd1d29904ec763f83a12f212435fd74d5873889e56606088174dccc8e9d
6
+ metadata.gz: b04ee0b54f862440283a251cb940d05c55412d6dd9312286935302c9d26b9aeeeb892e3632cfdb433f3d7b9c65c7c804ca9a78e3080039d3cd19b709d928b4e3
7
+ data.tar.gz: 6d3f90b0717a52ef4ef069dea162070b3b0cf4bd7e96d36911475afeec513ef79a3b933152bcc3f488a300cf7caa4b1f46ec3b8858755b7e814bf2a18d20f7f5
@@ -1,220 +1,3 @@
1
- require 'active_support/core_ext/object/deep_dup'
2
- require 'abstractivator/trees/block_collector'
3
- require 'sourcify'
4
- require 'delegate'
5
- require 'set'
6
-
7
- module Abstractivator
8
- module Trees
9
-
10
- SetMask = Struct.new(:items, :get_key)
11
- def set_mask(items, get_key)
12
- SetMask.new(items, get_key)
13
- end
14
-
15
- def tree_compare(tree, mask, path=[], index=nil)
16
- if mask == [:*] && tree.is_a?(Enumerable)
17
- []
18
- elsif mask == :+ && tree != :__missing__
19
- []
20
- elsif mask == :- && tree != :__missing__
21
- [diff(path, tree, :__absent__)]
22
- elsif mask.respond_to?(:call)
23
- comparable = mask.call(tree)
24
- comparable ? [] : [diff(path, tree, mask)]
25
- else
26
- case mask
27
- when Hash
28
- if tree.is_a?(Hash)
29
- mask.each_pair.flat_map do |k, v|
30
- tree_compare(tree.fetch(k, :__missing__), v, push_path(path, k))
31
- end
32
- else
33
- [diff(path, tree, mask)]
34
- end
35
- when SetMask # must check this before Enumerable because Structs are enumerable
36
- if tree.is_a?(Enumerable)
37
- # convert the enumerables to hashes, then compare those hashes
38
- tree_items = tree
39
- mask_items = mask.items.dup
40
- get_key = mask.get_key
41
-
42
- be_strict = !mask_items.delete(:*)
43
- new_tree = hashify_set(tree_items, get_key)
44
- new_mask = hashify_set(mask_items, get_key)
45
- tree_keys = Set.new(new_tree.keys)
46
- mask_keys = Set.new(new_mask.keys)
47
- tree_only = tree_keys - mask_keys
48
-
49
- # report duplicate keys
50
- if new_tree.size < tree_items.size
51
- diff(path, [:__duplicate_keys__, duplicates(tree_items.map(&get_key))], nil)
52
- elsif new_mask.size < mask_items.size
53
- diff(path, nil, [:__duplicate_keys__, duplicates(mask_items.map(&get_key))])
54
- # hash comparison allows extra values in the tree.
55
- # report extra values in the tree unless there was a :* in the mask
56
- elsif be_strict && tree_only.any?
57
- tree_only.map{|k| diff(push_path(path, k), new_tree[k], :__absent__)}
58
- else # compare as hashes
59
- tree_compare(new_tree, new_mask, path, index)
60
- end
61
- else
62
- [diff(path, tree, mask.items)]
63
- end
64
- when Enumerable
65
- if tree.is_a?(Enumerable)
66
- index ||= 0
67
- if !tree.any? && !mask.any?
68
- []
69
- elsif !tree.any?
70
- [diff(push_path(path, index.to_s), :__missing__, mask)]
71
- elsif !mask.any?
72
- [diff(push_path(path, index.to_s), tree, :__absent__)]
73
- else
74
- # if the mask is programmatically generated (unlikely), then
75
- # the mask might be really big and this could blow the stack.
76
- # don't support this case for now.
77
- tree_compare(tree.first, mask.first, push_path(path, index.to_s)) +
78
- tree_compare(tree.drop(1), mask.drop(1), path, index + 1)
79
- end
80
- else
81
- [diff(path, tree, mask)]
82
- end
83
- else
84
- tree == mask ? [] : [diff(path, tree, mask)]
85
- end
86
- end
87
- end
88
-
89
- private
90
-
91
- def hashify_set(items, get_key)
92
- Hash[items.map{|x| [get_key.call(x), x] }]
93
- end
94
-
95
- def duplicates(xs)
96
- xs.group_by{|x| x}.each_pair.select{|_k, v| v.size > 1}.map(&:first)
97
- end
98
-
99
- def push_path(path, name)
100
- path + [name]
101
- end
102
-
103
- def path_string(path)
104
- path.join('/')
105
- end
106
-
107
- def diff(path, tree, mask)
108
- {path: path_string(path), tree: tree, mask: massage_mask_for_diff(mask)}
109
- end
110
-
111
- def massage_mask_for_diff(mask)
112
- if mask.respond_to?(:call)
113
- massaged = :__predicate__
114
- begin
115
- massaged = mask.to_source
116
- rescue Exception => e
117
- raise unless e.class.name.start_with?('Sourcify')
118
- end
119
- massaged
120
- else
121
- mask
122
- end
123
- end
124
-
125
- public
126
-
127
- def tree_map(h)
128
- raise ArgumentError.new('Must provide a transformer block') unless block_given?
129
- config = BlockCollector.new
130
- yield(config)
131
- TransformTreeClosure.new.do_obj(h, config.get_path_tree)
132
- end
133
-
134
- class TransformTreeClosure
135
- def initialize
136
- @bias = 0 # symbol = +, string = -
137
- end
138
-
139
- def do_obj(obj, path_tree)
140
- case obj
141
- when nil; nil
142
- when Array; do_array(obj, path_tree)
143
- else; do_hash(obj, path_tree)
144
- end
145
- end
146
-
147
- private
148
-
149
- def do_hash(h, path_tree)
150
- h = h.dup
151
- path_tree.each_pair do |name, path_tree|
152
- if path_tree.respond_to?(:call)
153
- if (hash_name = try_get_hash_name(name))
154
- hash_name, old_fh = get_key_and_value(h, hash_name)
155
- unless old_fh.nil?
156
- h[hash_name] = old_fh.each_with_object(old_fh.dup) do |(key, value), fh|
157
- fh[key] = path_tree.call(value.deep_dup)
158
- end
159
- end
160
- elsif (array_name = try_get_array_name(name))
161
- array_name, value = get_key_and_value(h, array_name)
162
- unless value.nil?
163
- h[array_name] = value.map(&:deep_dup).map(&path_tree)
164
- end
165
- else
166
- name, value = get_key_and_value(h, name)
167
- h[name] = path_tree.call(value.deep_dup)
168
- end
169
- else
170
- name, value = get_key_and_value(h, name)
171
- h[name] = do_obj(value, path_tree)
172
- end
173
- end
174
- h
175
- end
176
-
177
- def get_key_and_value(h, string_key)
178
- tried_symbol = @bias >= 0
179
- trial_key = tried_symbol ? string_key.to_sym : string_key
180
- value = h[trial_key]
181
-
182
- if value.nil? # failed
183
- @bias += (tried_symbol ? -1 : 1)
184
- key = tried_symbol ? string_key : string_key.to_sym
185
- [key, h[key]]
186
- else
187
- @bias += (tried_symbol ? 1 : -1)
188
- [trial_key, value]
189
- end
190
- end
191
-
192
- def do_array(a, path_tree)
193
- a.map{|x| do_obj(x, path_tree)}
194
- end
195
-
196
- def try_get_hash_name(p)
197
- p =~ /(.+)\{\}$/ ? $1 : nil
198
- end
199
-
200
- def try_get_array_name(p)
201
- p =~ /(.+)\[\]$/ ? $1 : nil
202
- end
203
- end
204
-
205
- public
206
-
207
- def recursive_delete!(hash, keys)
208
- x = hash # hash is named 'hash' for documentation purposes but may be anything
209
- case x
210
- when Hash
211
- keys.each{|k| x.delete(k)}
212
- x.each_value{|v| recursive_delete!(v, keys)}
213
- when Array
214
- x.each{|v| recursive_delete!(v, keys)}
215
- end
216
- x
217
- end
218
-
219
- end
220
- end
1
+ require 'abstractivator/trees/tree_map'
2
+ require 'abstractivator/trees/tree_compare'
3
+ require 'abstractivator/trees/recursive_delete'
@@ -18,6 +18,10 @@ module Abstractivator
18
18
  path_tree
19
19
  end
20
20
 
21
+ def delete
22
+ @delete ||= Object.new
23
+ end
24
+
21
25
  private
22
26
 
23
27
  def set_hash_path(h, names, block)
@@ -31,4 +35,4 @@ module Abstractivator
31
35
  end
32
36
 
33
37
  end
34
- end
38
+ end
@@ -0,0 +1,21 @@
1
+ require 'active_support/core_ext/object/deep_dup'
2
+ require 'abstractivator/trees/block_collector'
3
+ require 'sourcify'
4
+ require 'delegate'
5
+ require 'set'
6
+
7
+ module Abstractivator
8
+ module Trees
9
+ def recursive_delete!(hash, keys)
10
+ x = hash # hash is named 'hash' for documentation purposes but may be anything
11
+ case x
12
+ when Hash
13
+ keys.each{|k| x.delete(k)}
14
+ x.each_value{|v| recursive_delete!(v, keys)}
15
+ when Array
16
+ x.each{|v| recursive_delete!(v, keys)}
17
+ end
18
+ x
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,125 @@
1
+ require 'active_support/core_ext/object/deep_dup'
2
+ require 'abstractivator/trees/block_collector'
3
+ require 'sourcify'
4
+ require 'delegate'
5
+ require 'set'
6
+
7
+ module Abstractivator
8
+ module Trees
9
+
10
+ SetMask = Struct.new(:items, :get_key)
11
+ def set_mask(items, get_key)
12
+ SetMask.new(items, get_key)
13
+ end
14
+
15
+ def tree_compare(tree, mask, path=[], index=nil)
16
+ if mask == [:*] && tree.is_a?(Enumerable)
17
+ []
18
+ elsif mask == :+ && tree != :__missing__
19
+ []
20
+ elsif mask == :- && tree != :__missing__
21
+ [diff(path, tree, :__absent__)]
22
+ elsif mask.respond_to?(:call)
23
+ are_equivalent = mask.call(tree)
24
+ are_equivalent ? [] : [diff(path, tree, mask)]
25
+ else
26
+ case mask
27
+ when Hash
28
+ if tree.is_a?(Hash)
29
+ mask.each_pair.flat_map do |k, v|
30
+ tree_compare(tree.fetch(k, :__missing__), v, push_path(path, k))
31
+ end
32
+ else
33
+ [diff(path, tree, mask)]
34
+ end
35
+ when SetMask # must check this before Enumerable because Structs are enumerable
36
+ if tree.is_a?(Enumerable)
37
+ # convert the enumerables to hashes, then compare those hashes
38
+ tree_items = tree
39
+ mask_items = mask.items.dup
40
+ get_key = mask.get_key
41
+
42
+ be_strict = !mask_items.delete(:*)
43
+ new_tree = hashify_set(tree_items, get_key)
44
+ new_mask = hashify_set(mask_items, get_key)
45
+ tree_keys = Set.new(new_tree.keys)
46
+ mask_keys = Set.new(new_mask.keys)
47
+ tree_only = tree_keys - mask_keys
48
+
49
+ # report duplicate keys
50
+ if new_tree.size < tree_items.size
51
+ diff(path, [:__duplicate_keys__, duplicates(tree_items.map(&get_key))], nil)
52
+ elsif new_mask.size < mask_items.size
53
+ diff(path, nil, [:__duplicate_keys__, duplicates(mask_items.map(&get_key))])
54
+ # hash comparison allows extra values in the tree.
55
+ # report extra values in the tree unless there was a :* in the mask
56
+ elsif be_strict && tree_only.any?
57
+ tree_only.map{|k| diff(push_path(path, k), new_tree[k], :__absent__)}
58
+ else # compare as hashes
59
+ tree_compare(new_tree, new_mask, path, index)
60
+ end
61
+ else
62
+ [diff(path, tree, mask.items)]
63
+ end
64
+ when Enumerable
65
+ if tree.is_a?(Enumerable)
66
+ index ||= 0
67
+ if !tree.any? && !mask.any?
68
+ []
69
+ elsif !tree.any?
70
+ [diff(push_path(path, index.to_s), :__missing__, mask)]
71
+ elsif !mask.any?
72
+ [diff(push_path(path, index.to_s), tree, :__absent__)]
73
+ else
74
+ # if the mask is programmatically generated (unlikely), then
75
+ # the mask might be really big and this could blow the stack.
76
+ # don't support this case for now.
77
+ tree_compare(tree.first, mask.first, push_path(path, index.to_s)) +
78
+ tree_compare(tree.drop(1), mask.drop(1), path, index + 1)
79
+ end
80
+ else
81
+ [diff(path, tree, mask)]
82
+ end
83
+ else
84
+ tree == mask ? [] : [diff(path, tree, mask)]
85
+ end
86
+ end
87
+ end
88
+
89
+ private
90
+
91
+ def hashify_set(items, get_key)
92
+ Hash[items.map{|x| [get_key.call(x), x] }]
93
+ end
94
+
95
+ def duplicates(xs)
96
+ xs.group_by{|x| x}.each_pair.select{|_k, v| v.size > 1}.map(&:first)
97
+ end
98
+
99
+ def push_path(path, name)
100
+ path + [name]
101
+ end
102
+
103
+ def path_string(path)
104
+ path.join('/')
105
+ end
106
+
107
+ def diff(path, tree, mask)
108
+ {path: path_string(path), tree: tree, mask: massage_mask_for_diff(mask)}
109
+ end
110
+
111
+ def massage_mask_for_diff(mask)
112
+ if mask.respond_to?(:call)
113
+ massaged = :__predicate__
114
+ begin
115
+ massaged = mask.to_source
116
+ rescue Exception => e
117
+ raise unless e.class.name.start_with?('Sourcify')
118
+ end
119
+ massaged
120
+ else
121
+ mask
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,121 @@
1
+ require 'active_support/core_ext/object/deep_dup'
2
+ require 'abstractivator/trees/block_collector'
3
+ require 'sourcify'
4
+ require 'delegate'
5
+ require 'set'
6
+
7
+ module Abstractivator
8
+ module Trees
9
+
10
+ # Transforms a tree at certain paths.
11
+ # The transform is non-destructive and reuses untouched substructure.
12
+ # For efficiency, it first builds a "path_tree" that describes
13
+ # which paths to transform. This path_tree is then used as input
14
+ # for a data-driven algorithm.
15
+ def tree_map(h)
16
+ raise ArgumentError.new('Must provide a transformer block') unless block_given?
17
+ config = BlockCollector.new
18
+ yield(config)
19
+ TransformTreeClosure.new(config).do_obj(h, config.get_path_tree)
20
+ end
21
+
22
+ class TransformTreeClosure
23
+ def initialize(config)
24
+ @config = config
25
+ @bias = 0 # symbol = +, string = -
26
+ @no_value = Object.new
27
+ end
28
+
29
+ def do_obj(obj, path_tree)
30
+ case obj
31
+ when nil; nil
32
+ when Array; do_array(obj, path_tree)
33
+ else; do_hash(obj, path_tree)
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def do_hash(h, path_tree)
40
+ h = h.dup
41
+ path_tree.each_pair do |name, path_tree|
42
+ if leaf?(path_tree)
43
+ if hash_name = try_get_hash_name(name)
44
+ hash_name, old_fh = get_key_and_value(h, hash_name)
45
+ unless old_fh == @no_value || old_fh.nil?
46
+ old_fh.is_a?(Hash) or raise "Expected a hash but got #{old_fh.class.name}: #{old_fh.inspect}"
47
+ h[hash_name] = old_fh.each_with_object(old_fh.dup) do |(key, value), fh|
48
+ replacement = path_tree.call(value.deep_dup, key)
49
+ if deleted?(replacement)
50
+ fh.delete(key)
51
+ else
52
+ fh[key] = replacement
53
+ end
54
+ end
55
+ end
56
+ elsif array_name = try_get_array_name(name)
57
+ array_name, value = get_key_and_value(h, array_name)
58
+ unless value == @no_value || value.nil?
59
+ value.is_a?(Array) or raise "Expected an array but got #{value.class.name}: #{value.inspect}"
60
+ h[array_name] = value.map(&:deep_dup).each_with_index.map(&path_tree).reject(&method(:deleted?))
61
+ end
62
+ else
63
+ name, value = get_key_and_value(h, name)
64
+ unless value == @no_value
65
+ replacement = path_tree.call(value.deep_dup)
66
+ if deleted?(replacement)
67
+ h.delete(name)
68
+ else
69
+ h[name] = replacement
70
+ end
71
+ end
72
+ end
73
+ else # not leaf
74
+ name, value = get_key_and_value(h, name)
75
+ h[name] = do_obj(value, path_tree) unless value == @no_value
76
+ end
77
+ end
78
+ h
79
+ end
80
+
81
+ def leaf?(path_tree)
82
+ path_tree.respond_to?(:call)
83
+ end
84
+
85
+ def deleted?(value)
86
+ value == @config.delete
87
+ end
88
+
89
+ def get_key_and_value(h, string_key)
90
+ tried_symbol = @bias >= 0
91
+ trial_key = tried_symbol ? string_key.to_sym : string_key
92
+ value = try_fetch(h, trial_key)
93
+
94
+ if value == @no_value # failed
95
+ @bias += (tried_symbol ? -1 : 1)
96
+ key = tried_symbol ? string_key : string_key.to_sym
97
+ [key, try_fetch(h, key)]
98
+ else
99
+ @bias += (tried_symbol ? 1 : -1)
100
+ [trial_key, value]
101
+ end
102
+ end
103
+
104
+ def try_fetch(h, trial_key)
105
+ h.fetch(trial_key, @no_value)
106
+ end
107
+
108
+ def do_array(a, path_tree)
109
+ a.map{|x| do_obj(x, path_tree)}
110
+ end
111
+
112
+ def try_get_hash_name(p)
113
+ p =~ /(.+)\{\}$/ ? $1 : nil
114
+ end
115
+
116
+ def try_get_array_name(p)
117
+ p =~ /(.+)\[\]$/ ? $1 : nil
118
+ end
119
+ end
120
+ end
121
+ end
@@ -1,3 +1,3 @@
1
1
  module Abstractivator
2
- VERSION = '0.0.22'
2
+ VERSION = '0.0.23'
3
3
  end
@@ -0,0 +1,28 @@
1
+ require 'rspec'
2
+ require 'abstractivator/trees/recursive_delete'
3
+ require 'json'
4
+ require 'rails'
5
+ require 'pp'
6
+
7
+ describe Abstractivator::Trees do
8
+
9
+ include Abstractivator::Trees
10
+
11
+ describe '#recursive_delete!' do
12
+ it 'deletes keys in the root hash' do
13
+ h = {a: 1, b: 2}
14
+ recursive_delete!(h, [:a])
15
+ expect(h).to eql({b: 2})
16
+ end
17
+ it 'deletes keys in sub hashes' do
18
+ h = {a: 1, b: {c: 3, d: 4}}
19
+ recursive_delete!(h, [:c])
20
+ expect(h).to eql({a: 1, b: {d: 4}})
21
+ end
22
+ it 'deletes keys in hashes inside arrays' do
23
+ h = {a: [{b: 1, c: 2}, {b: 3, c: 4}]}
24
+ recursive_delete!(h, [:b])
25
+ expect(h).to eql({a: [{c: 2}, {c: 4}]})
26
+ end
27
+ end
28
+ end
@@ -1,5 +1,5 @@
1
1
  require 'rspec'
2
- require 'abstractivator/trees'
2
+ require 'abstractivator/trees/tree_compare'
3
3
  require 'json'
4
4
  require 'rails'
5
5
  require 'pp'
@@ -8,161 +8,6 @@ describe Abstractivator::Trees do
8
8
 
9
9
  include Abstractivator::Trees
10
10
 
11
- describe '#tree_map' do
12
-
13
- context 'when no block is provided' do
14
- it 'raises an exception' do
15
- expect{ tree_map(hash) }.to raise_error ArgumentError, 'Must provide a transformer block'
16
- end
17
- end
18
-
19
- it 'handles both string and symbol keys' do
20
- h = {:a => 1, 'b' => 2}
21
- result = tree_map(h) do |t|
22
- t.when('a') {|v| v + 10}
23
- t.when('b') {|v| v + 10}
24
- end
25
- expect(result).to eql({:a => 11, 'b' => 12})
26
- end
27
-
28
- it 'replaces primitive-type hash fields' do
29
- h = {'a' => 1}
30
- result = transform_one_path(h, 'a') { 2 }
31
- expect(result).to eql({'a' => 2})
32
- end
33
-
34
- it 'replaces nil hash fields' do
35
- h = {'a' => nil}
36
- result = transform_one_path(h, 'a') {|v| v.to_s}
37
- expect(result).to eql({'a' => ''})
38
- end
39
-
40
- it 'replaces hash-type hash fields' do
41
- h = {'a' => {'b' => 1}}
42
- result = transform_one_path(h, 'a') { {'z' => 99} }
43
- expect(result).to eql({'a' => {'z' => 99}})
44
- end
45
-
46
- it 'replaces array-type hash fields' do
47
- h = {'a' => [1,2,3]}
48
- result = transform_one_path(h, 'a') {|v| v.reverse}
49
- expect(result).to eql({'a' => [3,2,1]})
50
- end
51
-
52
- it 'replaces primitive-type hash members' do
53
- h = {'a' => {'b' => 'foo', 'c' => 'bar'}}
54
- result = transform_one_path(h, 'a{}') {|v| v.reverse}
55
- expect(result).to eql({'a' => {'b' => 'oof', 'c' => 'rab'}})
56
- end
57
-
58
- it 'replaces hash-type hash members' do
59
- h = {'a' => {'b' => {'x' => 88}, 'c' => {'x' => 88}}}
60
- result = transform_one_path(h, 'a{}') {|v| {'y' => 99}}
61
- expect(result).to eql({'a' => {'b' => {'y' => 99}, 'c' => {'y' => 99}}})
62
- end
63
-
64
- it 'replaces array-type hash members' do
65
- h = {'a' => {'b' => [1,2,3], 'c' => [4,5,6]}}
66
- result = transform_one_path(h, 'a{}') {|v| v.reverse}
67
- expect(result).to eql({'a' => {'b' => [3,2,1], 'c' => [6,5,4]}})
68
- end
69
-
70
- it 'replaces primitive-type array members' do
71
- h = {'a' => [1, 2]}
72
- result = transform_one_path(h, 'a[]') {|v| v + 10}
73
- expect(result).to eql({'a' => [11, 12]})
74
- end
75
-
76
- it 'replaces hash-type array members' do
77
- h = {'a' => [{'b' => 1}, {'c' => 2}]}
78
- result = transform_one_path(h, 'a[]') { {'z' => 99} }
79
- expect(result).to eql({'a' => [{'z' => 99}, {'z' => 99}]})
80
- end
81
-
82
- it 'replaces array-type array members' do
83
- h = {'a' => [[1,2,3], [4,5,6]]}
84
- result = transform_one_path(h, 'a[]') {|v| v.reverse}
85
- expect(result).to eql({'a' => [[3,2,1], [6,5,4]]})
86
- end
87
-
88
- context 'when replacing array members' do
89
- it 'allows the array to be nil' do
90
- h = {'a' => nil}
91
- result = transform_one_path(h, 'a[]') {|v| v + 1}
92
- expect(result).to eql({'a' => nil})
93
- end
94
- end
95
-
96
- context 'when replacing hash members' do
97
- it 'allows the hash to be nil' do
98
- h = {'a' => nil}
99
- result = transform_one_path(h, 'a{}') {|v| v + 1}
100
- expect(result).to eql({'a' => nil})
101
- end
102
- end
103
-
104
- context 'mutation' do
105
- before(:each) do
106
- @old = {'a' => {'x' => 1, 'y' => 2}, 'b' => {'x' => 17, 'y' => 23}}
107
- @new = transform_one_path(@old,'a') {|v|
108
- v['z'] = v['x'] + v['y']
109
- v
110
- }
111
- end
112
- it 'does not mutate the input' do
113
- expect(@old).to eql({'a' => {'x' => 1, 'y' => 2}, 'b' => {'x' => 17, 'y' => 23}})
114
- expect(@new).to eql({'a' => {'x' => 1, 'y' => 2, 'z' => 3}, 'b' => {'x' => 17, 'y' => 23}})
115
- end
116
- it 'preserves unmodified substructure' do
117
- expect(@old['a'].equal?(@new['a'])).to be_falsey
118
- expect(@old['b'].equal?(@new['b'])).to be_truthy
119
- end
120
-
121
- #TODO: create a generic json file to use for this test
122
- # it 'really does not mutate the input' do
123
- # old = JSON.parse(File.read('assay.json'))
124
- # old2 = old.deep_dup
125
- # tree_map(old) do |t|
126
- # t.when('compound_methods/calibration/normalizers[]') {|v| v.to_s.reverse}
127
- # t.when('compound_methods/calibration/responses[]') {|v| v.to_s.reverse}
128
- # t.when('compound_methods/rule_settings{}') {|v| v.to_s.reverse}
129
- # t.when('compound_methods/chromatogram_methods/rule_settings{}') {|v| v.to_s.reverse}
130
- # t.when('compound_methods/chromatogram_methods/peak_integration/retention_time') do |ret_time|
131
- # if ret_time['reference_type_source'] == 'chromatogram'
132
- # ret_time['reference'] = ret_time['reference'].to_s.reverse
133
- # end
134
- # ret_time
135
- # end
136
- # end
137
- # expect(old).to eql old2
138
- # end
139
- end
140
-
141
- def transform_one_path(h, path, &block)
142
- tree_map(h) do |t|
143
- t.when(path, &block)
144
- end
145
- end
146
- end
147
-
148
- describe '#recursive_delete!' do
149
- it 'deletes keys in the root hash' do
150
- h = {a: 1, b: 2}
151
- recursive_delete!(h, [:a])
152
- expect(h).to eql({b: 2})
153
- end
154
- it 'deletes keys in sub hashes' do
155
- h = {a: 1, b: {c: 3, d: 4}}
156
- recursive_delete!(h, [:c])
157
- expect(h).to eql({a: 1, b: {d: 4}})
158
- end
159
- it 'deletes keys in hashes inside arrays' do
160
- h = {a: [{b: 1, c: 2}, {b: 3, c: 4}]}
161
- recursive_delete!(h, [:b])
162
- expect(h).to eql({a: [{c: 2}, {c: 4}]})
163
- end
164
- end
165
-
166
11
  describe '#tree_compare' do
167
12
 
168
13
  extend Abstractivator::Trees
@@ -0,0 +1,201 @@
1
+ require 'rspec'
2
+ require 'abstractivator/trees/tree_map'
3
+ require 'json'
4
+ require 'rails'
5
+ require 'pp'
6
+
7
+ describe Abstractivator::Trees do
8
+
9
+ include Abstractivator::Trees
10
+
11
+ describe '#tree_map' do
12
+
13
+ context 'when no block is provided' do
14
+ it 'raises an exception' do
15
+ expect{ tree_map(hash) }.to raise_error ArgumentError, 'Must provide a transformer block'
16
+ end
17
+ end
18
+
19
+ it 'handles both string and symbol keys' do
20
+ h = {:a => 1, 'b' => 2}
21
+ result = tree_map(h) do |t|
22
+ t.when('a') {|v| v + 10}
23
+ t.when('b') {|v| v + 10}
24
+ end
25
+ expect(result).to eql({:a => 11, 'b' => 12})
26
+ end
27
+
28
+ it 'replaces primitive-type hash fields' do
29
+ h = {'a' => 1}
30
+ result = transform_one_path(h, 'a') { 2 }
31
+ expect(result).to eql({'a' => 2})
32
+ end
33
+
34
+ it 'replaces nil hash fields' do
35
+ h = {'a' => nil}
36
+ result = transform_one_path(h, 'a') {|v| v.to_s}
37
+ expect(result).to eql({'a' => ''})
38
+ end
39
+
40
+ it 'replaces hash-type hash fields' do
41
+ h = {'a' => {'b' => 1}}
42
+ result = transform_one_path(h, 'a') { {'z' => 99} }
43
+ expect(result).to eql({'a' => {'z' => 99}})
44
+ end
45
+
46
+ it 'replaces array-type hash fields' do
47
+ h = {'a' => [1,2,3]}
48
+ result = transform_one_path(h, 'a') {|v| v.reverse}
49
+ expect(result).to eql({'a' => [3,2,1]})
50
+ end
51
+
52
+ it 'replaces primitive-type hash members' do
53
+ h = {'a' => {'b' => 'foo', 'c' => 'bar'}}
54
+ result = transform_one_path(h, 'a{}') {|v| v.reverse}
55
+ expect(result).to eql({'a' => {'b' => 'oof', 'c' => 'rab'}})
56
+ end
57
+
58
+ it 'replaces hash-type hash members' do
59
+ h = {'a' => {'b' => {'x' => 88}, 'c' => {'x' => 88}}}
60
+ result = transform_one_path(h, 'a{}') {|v| {'y' => 99}}
61
+ expect(result).to eql({'a' => {'b' => {'y' => 99}, 'c' => {'y' => 99}}})
62
+ end
63
+
64
+ it 'replaces array-type hash members' do
65
+ h = {'a' => {'b' => [1,2,3], 'c' => [4,5,6]}}
66
+ result = transform_one_path(h, 'a{}') {|v| v.reverse}
67
+ expect(result).to eql({'a' => {'b' => [3,2,1], 'c' => [6,5,4]}})
68
+ end
69
+
70
+ it 'replaces primitive-type array members' do
71
+ h = {'a' => [1, 2]}
72
+ result = transform_one_path(h, 'a[]') {|v| v + 10}
73
+ expect(result).to eql({'a' => [11, 12]})
74
+ end
75
+
76
+ it 'replaces hash-type array members' do
77
+ h = {'a' => [{'b' => 1}, {'c' => 2}]}
78
+ result = transform_one_path(h, 'a[]') { {'z' => 99} }
79
+ expect(result).to eql({'a' => [{'z' => 99}, {'z' => 99}]})
80
+ end
81
+
82
+ it 'replaces array-type array members' do
83
+ h = {'a' => [[1,2,3], [4,5,6]]}
84
+ result = transform_one_path(h, 'a[]') {|v| v.reverse}
85
+ expect(result).to eql({'a' => [[3,2,1], [6,5,4]]})
86
+ end
87
+
88
+ it 'deletes values' do
89
+ h = {
90
+ a: 1,
91
+ b: [4, 5, 6],
92
+ c: { d: 9, e: 10 },
93
+ f: { g: 101, h: 102, i: 103 }
94
+ }
95
+ result = tree_map(h) do |t|
96
+ t.when('a') { t.delete }
97
+ t.when('c/d') { t.delete }
98
+ t.when('b[]') { |x| x.even? ? t.delete : x }
99
+ t.when('f{}') { |x| x.even? ? t.delete : x }
100
+ end
101
+ expect(result).to eql({b: [5], c: {e: 10}, f: {g: 101, i: 103}})
102
+ end
103
+
104
+ context 'when replacing array members' do
105
+ it 'allows the array to be nil' do
106
+ h = {'a' => nil}
107
+ result = transform_one_path(h, 'a[]') {|v| v + 1}
108
+ expect(result).to eql({'a' => nil})
109
+ end
110
+ it 'passes the index as the second argument' do
111
+ h = {a: %w(one two)}
112
+ call_count = 0
113
+ transform_one_path(h, 'a[]') do |v, i|
114
+ expect(i).to eql 0 if v == 'one'
115
+ expect(i).to eql 1 if v == 'two'
116
+ call_count += 1
117
+ end
118
+ expect(call_count).to eql 2
119
+ end
120
+ it 'raises an error is the value is not an array' do
121
+ expect{transform_one_path({a: 1}, 'a[]') { |x| x }}.to raise_error 'Expected an array but got Fixnum: 1'
122
+ expect{transform_one_path({a: {b: 1}}, 'a[]') { |x| x }}.to raise_error 'Expected an array but got Hash: {:b=>1}'
123
+ end
124
+ end
125
+
126
+ context 'when replacing hash members' do
127
+ it 'allows the hash to be nil' do
128
+ h = {'a' => nil}
129
+ result = transform_one_path(h, 'a{}') {|v| v + 1}
130
+ expect(result).to eql({'a' => nil})
131
+ end
132
+ it 'passes the key as the second argument' do
133
+ h = {a: {b: 1, c: 2}}
134
+ call_count = 0
135
+ transform_one_path(h, 'a{}') do |v, k|
136
+ expect(k).to eql :b if v == 1
137
+ expect(k).to eql :c if v == 2
138
+ call_count += 1
139
+ end
140
+ expect(call_count).to eql 2
141
+ end
142
+ it 'raises an error is the value is not a hash' do
143
+ expect{transform_one_path({a: 1}, 'a{}') { |x| x }}.to raise_error 'Expected a hash but got Fixnum: 1'
144
+ expect{transform_one_path({a: [1, 2]}, 'a{}') { |x| x }}.to raise_error 'Expected a hash but got Array: [1, 2]'
145
+ end
146
+ end
147
+
148
+ it 'it does not add missing keys' do # regression test
149
+ result = tree_map({}) do |t|
150
+ t.when('foo') { |x| x }
151
+ t.when('bars[]') { |x| x }
152
+ t.when('others/baz') { |x| x }
153
+ end
154
+ expect(result).to eql({})
155
+ end
156
+
157
+ context 'mutation' do
158
+ before(:each) do
159
+ @old = {'a' => {'x' => 1, 'y' => 2}, 'b' => {'x' => 17, 'y' => 23}}
160
+ @new = transform_one_path(@old,'a') {|v|
161
+ v['z'] = v['x'] + v['y']
162
+ v
163
+ }
164
+ end
165
+ it 'does not mutate the input' do
166
+ expect(@old).to eql({'a' => {'x' => 1, 'y' => 2}, 'b' => {'x' => 17, 'y' => 23}})
167
+ expect(@new).to eql({'a' => {'x' => 1, 'y' => 2, 'z' => 3}, 'b' => {'x' => 17, 'y' => 23}})
168
+ end
169
+ it 'preserves unmodified substructure' do
170
+ expect(@old['a'].equal?(@new['a'])).to be_falsey
171
+ expect(@old['b'].equal?(@new['b'])).to be_truthy
172
+ end
173
+
174
+ #TODO: create a generic json file to use for this test
175
+ # it 'really does not mutate the input' do
176
+ # old = JSON.parse(File.read('assay.json'))
177
+ # old2 = old.deep_dup
178
+ # tree_map(old) do |t|
179
+ # t.when('compound_methods/calibration/normalizers[]') {|v| v.to_s.reverse}
180
+ # t.when('compound_methods/calibration/responses[]') {|v| v.to_s.reverse}
181
+ # t.when('compound_methods/rule_settings{}') {|v| v.to_s.reverse}
182
+ # t.when('compound_methods/chromatogram_methods/rule_settings{}') {|v| v.to_s.reverse}
183
+ # t.when('compound_methods/chromatogram_methods/peak_integration/retention_time') do |ret_time|
184
+ # if ret_time['reference_type_source'] == 'chromatogram'
185
+ # ret_time['reference'] = ret_time['reference'].to_s.reverse
186
+ # end
187
+ # ret_time
188
+ # end
189
+ # end
190
+ # expect(old).to eql old2
191
+ # end
192
+ end
193
+
194
+ def transform_one_path(h, path, &block)
195
+ tree_map(h) do |t|
196
+ t.when(path, &block)
197
+ end
198
+ end
199
+ end
200
+
201
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abstractivator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.22
4
+ version: 0.0.23
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Winton
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-13 00:00:00.000000000 Z
11
+ date: 2015-03-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -89,6 +89,9 @@ files:
89
89
  - lib/abstractivator/proc_ext.rb
90
90
  - lib/abstractivator/trees.rb
91
91
  - lib/abstractivator/trees/block_collector.rb
92
+ - lib/abstractivator/trees/recursive_delete.rb
93
+ - lib/abstractivator/trees/tree_compare.rb
94
+ - lib/abstractivator/trees/tree_map.rb
92
95
  - lib/abstractivator/version.rb
93
96
  - lib/enumerable_ext.rb
94
97
  - spec/lib/abstractivator/array_ext_spec.rb
@@ -97,7 +100,9 @@ files:
97
100
  - spec/lib/abstractivator/enum_spec.rb
98
101
  - spec/lib/abstractivator/enumerable_ext_spec.rb
99
102
  - spec/lib/abstractivator/proc_ext_spec.rb
100
- - spec/lib/abstractivator/tree_visitor_spec.rb
103
+ - spec/lib/abstractivator/trees/recursive_delete_spec.rb
104
+ - spec/lib/abstractivator/trees/tree_compare_spec.rb
105
+ - spec/lib/abstractivator/trees/tree_map_spec.rb
101
106
  - spec/lib/enumerable_ext_spec.rb
102
107
  homepage: ''
103
108
  licenses:
@@ -130,6 +135,8 @@ test_files:
130
135
  - spec/lib/abstractivator/enum_spec.rb
131
136
  - spec/lib/abstractivator/enumerable_ext_spec.rb
132
137
  - spec/lib/abstractivator/proc_ext_spec.rb
133
- - spec/lib/abstractivator/tree_visitor_spec.rb
138
+ - spec/lib/abstractivator/trees/recursive_delete_spec.rb
139
+ - spec/lib/abstractivator/trees/tree_compare_spec.rb
140
+ - spec/lib/abstractivator/trees/tree_map_spec.rb
134
141
  - spec/lib/enumerable_ext_spec.rb
135
142
  has_rdoc: