abstractivator 0.0.15

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 47647350eb409b2ff52995a8e4f7349134cebb60
4
+ data.tar.gz: c2a3f24a61450361a6a8ccd6fc1265005970be91
5
+ SHA512:
6
+ metadata.gz: 9f0c2f1ad23e80a9e3633717456c3338b494b38591b557c58eecab7db713f4cdbc470367dd44d9c7e098d1f35c14e6e1c85e1a41b87ed83952036512343b52be
7
+ data.tar.gz: 407f9d10c8c0b83ec44ec33375a11cd2d6b1d8369ce664b0041b4dff8a4cf597ca5fcfd58d3dd249de68de8681dba4a86080360cf75caa5e118b4307cd2c6ddf
data/.gitignore ADDED
@@ -0,0 +1,23 @@
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
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ .idea
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in abstractivator.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem 'rspec'
8
+ gem 'json'
9
+ gem 'rails'
10
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Peter Winton
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Abstractivator
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'abstractivator'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install abstractivator
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it ( https://github.com/[my-github-username]/abstractivator/fork )
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'abstractivator/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'abstractivator'
8
+ spec.version = Abstractivator::VERSION
9
+ spec.authors = ['Peter Winton']
10
+ spec.email = ['pwinton@indigobio.com']
11
+ spec.summary = %q{Utilities}
12
+ spec.description = %q{Utilities}
13
+ spec.homepage = ''
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.6'
22
+ spec.add_development_dependency 'rake'
23
+ spec.add_runtime_dependency 'activesupport', '~> 4.0'
24
+ spec.add_runtime_dependency 'sourcify', '> 0'
25
+ end
@@ -0,0 +1,2 @@
1
+ require 'abstractivator/version'
2
+ require 'abstractivator/trees'
@@ -0,0 +1,22 @@
1
+ module Abstractivator
2
+ module Collections
3
+ def multizip(enumerables, pad_value=nil)
4
+ es = enumerables.map(&:each)
5
+ result = []
6
+ fail_count = 0
7
+ while fail_count < es.size do
8
+ fail_count = 0
9
+ heads = es.map do |e|
10
+ begin
11
+ e.next
12
+ rescue StopIteration
13
+ fail_count += 1
14
+ pad_value
15
+ end
16
+ end
17
+ result << heads if fail_count < es.size
18
+ end
19
+ result
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,58 @@
1
+ module Abstractivator
2
+ module Cons
3
+ Nil = Object.new
4
+
5
+ def empty_list
6
+ Nil
7
+ end
8
+
9
+ def cons(h, t)
10
+ Cell.new(h, t)
11
+ end
12
+
13
+ def head(p)
14
+ p.first
15
+ end
16
+
17
+ def tail(p)
18
+ p.last
19
+ end
20
+
21
+ def list_to_enum(list)
22
+ Enumerator.new do |y|
23
+ while list != Nil
24
+ y << head(list)
25
+ list = tail(list)
26
+ end
27
+ end
28
+ end
29
+
30
+ def enum_to_list(enum)
31
+ e = enum.reverse.each
32
+ result = Nil
33
+ begin
34
+ while true
35
+ result = cons(e.next, result)
36
+ end
37
+ rescue StopIteration
38
+ result
39
+ end
40
+ end
41
+
42
+ class Cell < Array
43
+ def initialize(h, t)
44
+ super([h, t])
45
+ end
46
+
47
+ def head
48
+ first
49
+ end
50
+
51
+ def tail
52
+ last
53
+ end
54
+ end
55
+
56
+ extend self
57
+ end
58
+ end
@@ -0,0 +1,94 @@
1
+ require 'set'
2
+
3
+ module Enumerable
4
+
5
+ # joins items from left with items from right based on their keys.
6
+ # get_{left,right}_key are callables which, given an item, return the item's key.
7
+ # the defaults are used to form a pair for items which have no match.
8
+ # returns an array of 2-element arrays, each of which is a left/right pair.
9
+ def self.outer_join(left, right, get_left_key, get_right_key, left_default, right_default)
10
+
11
+ ls = left.hash_map(get_left_key)
12
+ rs = right.hash_map(get_right_key)
13
+
14
+ raise 'duplicate left keys' if ls.size < left.size
15
+ raise 'duplicate right keys' if rs.size < right.size
16
+
17
+ result = []
18
+
19
+ ls.each_pair do |k, l|
20
+ r = rs[k]
21
+ if r
22
+ rs.delete(k)
23
+ else
24
+ r = get_default(right_default, l)
25
+ end
26
+ result.push [l, r]
27
+ end
28
+
29
+ rs.each_pair do |_, r|
30
+ result.push [get_default(left_default, r), r]
31
+ end
32
+
33
+ result
34
+ end
35
+
36
+ def self.inner_join(left, right, get_left_key, get_right_key)
37
+ sentinel = Object.new
38
+ result = self.outer_join(left, right, get_left_key, get_right_key, sentinel, sentinel)
39
+ result.reject { |pair| pair.first == sentinel || pair.last == sentinel }
40
+ end
41
+
42
+ def self.get_default(default, other_side_value)
43
+ proc?(default) ? default.(other_side_value) : default
44
+ end
45
+
46
+ def self.proc?(x)
47
+ x.respond_to?(:call)
48
+ end
49
+
50
+ def hash_map(get_key, &get_value)
51
+ Hash[self.map{|x| [get_key.(x), get_value ? get_value.(x) : x]}]
52
+ end
53
+
54
+ def outer_join(right, get_left_key, get_right_key, default_value)
55
+ Enumerable.outer_join(self, right, get_left_key, get_right_key, default_value, default_value)
56
+ end
57
+
58
+ def inner_join(right, get_left_key, get_right_key)
59
+ Enumerable.inner_join(self, right, get_left_key, get_right_key)
60
+ end
61
+
62
+ def uniq?
63
+ seen = Set.new
64
+ each_with_index do |x, i|
65
+ seen << (block_given? ? yield(x) : x)
66
+ return false if seen.size < i + 1
67
+ end
68
+ true
69
+ end
70
+
71
+ orig_detect = instance_method(:detect)
72
+ define_method :detect do |*args, &block|
73
+ detect = orig_detect.bind(self)
74
+
75
+ if args.size == 1 && !args.first.respond_to?(:call) && block
76
+ value = args.first
77
+ detect.call {|x| block.call(x) == value}
78
+ elsif args.size == 2 && !block
79
+ attr_name, value = args
80
+ detect.call {|x| x.send(attr_name) == value}
81
+ else
82
+ detect.call(*args, &block)
83
+ end
84
+ end
85
+
86
+ def inject_right(*args, &block)
87
+ self.reverse_each.inject(*args, &block) # reverse_each to avoid duplicating the enumerable, when possible
88
+ end
89
+
90
+ def pad_right(n, value=nil, &block)
91
+ block ||= proc { value }
92
+ self + ([n-self.size, 0].max).times.map(&block)
93
+ end
94
+ end
@@ -0,0 +1,52 @@
1
+ module MethodAndProcExtensions
2
+ def loosen_args
3
+ proc do |*args, &block|
4
+ Proc.loose_call(self, args, &block)
5
+ end
6
+ end
7
+ end
8
+
9
+ class Proc
10
+ include MethodAndProcExtensions
11
+
12
+ def compose(other)
13
+ proc{|x| self.call(other.call(x))}
14
+ end
15
+
16
+ def self.compose(*procs)
17
+ procs.inject_right(identity) { |inner, p| p.compose(inner) }
18
+ end
19
+
20
+ def self.identity
21
+ proc {|x| x}
22
+ end
23
+
24
+ def reverse_args
25
+ proc do |*args, &block|
26
+ self.call(*args.reverse, &block)
27
+ end
28
+ end
29
+
30
+ def self.loose_call(x, args, &block)
31
+ x.respond_to?(:call) ? x.call(*args.take(x.arity).pad_right(x.arity), &block) : x
32
+ end
33
+ end
34
+
35
+ class Method
36
+ include MethodAndProcExtensions
37
+ end
38
+
39
+ class UnboundMethod
40
+ def explicit_receiver
41
+ proc do |receiver, *args, &block|
42
+ self.bind(receiver).call(*args, &block)
43
+ end
44
+ end
45
+ end
46
+
47
+ class Array
48
+ def to_proc
49
+ raise 'size must be exactly one' unless size == 1
50
+ proc{|x| x[first]}
51
+ end
52
+ end
@@ -0,0 +1,220 @@
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