gorillib 0.5.0 → 0.5.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/.gitmodules +3 -0
- data/.travis.yml +11 -0
- data/Gemfile +2 -16
- data/Rakefile +2 -74
- data/away/aliasing_spec.rb +180 -0
- data/away/confidence.rb +17 -0
- data/away/stub_module.rb +33 -0
- data/gorillib.gemspec +31 -246
- data/lib/gorillib/collection/model_collection.rb +1 -0
- data/lib/gorillib/data_munging.rb +0 -1
- data/lib/gorillib/hashlike/slice.rb +2 -0
- data/lib/gorillib/model/field.rb +2 -2
- data/lib/gorillib/model/serialization.rb +9 -4
- data/lib/gorillib/model/serialization/csv.rb +1 -0
- data/lib/gorillib/model/serialization/lines.rb +2 -0
- data/lib/gorillib/model/serialization/tsv.rb +1 -0
- data/lib/gorillib/pathname.rb +1 -1
- data/lib/gorillib/pathname/utils.rb +6 -0
- data/lib/gorillib/string/inflector.rb +1 -1
- data/lib/gorillib/system.rb +1 -0
- data/lib/gorillib/system/runner.rb +36 -0
- data/lib/gorillib/type/ip_address.rb +2 -2
- data/lib/gorillib/version.rb +3 -0
- data/old/lib/gorillib/hash/indifferent_access.rb +207 -0
- data/old/lib/gorillib/hash/tree_merge.rb +4 -0
- data/old/lib/gorillib/hashlike/tree_merge.rb +49 -0
- data/old/lib/gorillib/metaprogramming/cattr_accessor.rb +79 -0
- data/old/lib/gorillib/metaprogramming/mattr_accessor.rb +61 -0
- data/old/lib/gorillib/receiver.rb +402 -0
- data/old/lib/gorillib/receiver/active_model_shim.rb +32 -0
- data/old/lib/gorillib/receiver/acts_as_hash.rb +195 -0
- data/old/lib/gorillib/receiver/acts_as_loadable.rb +42 -0
- data/old/lib/gorillib/receiver/locale/en.yml +27 -0
- data/old/lib/gorillib/receiver/tree_diff.rb +74 -0
- data/old/lib/gorillib/receiver/validations.rb +30 -0
- data/old/lib/gorillib/receiver_model.rb +21 -0
- data/old/lib/gorillib/struct/acts_as_hash.rb +108 -0
- data/old/lib/gorillib/struct/hashlike_iteration.rb +0 -0
- data/old/spec/gorillib/hash/indifferent_access_spec.rb +391 -0
- data/old/spec/gorillib/metaprogramming/cattr_accessor_spec.rb +43 -0
- data/old/spec/gorillib/metaprogramming/mattr_accessor_spec.rb +45 -0
- data/old/spec/gorillib/receiver/receiver/acts_as_hash_spec.rb +295 -0
- data/old/spec/gorillib/receiver_spec.rb +551 -0
- data/old/spec/gorillib/struct/acts_as_hash_fuzz_spec.rb +71 -0
- data/old/spec/gorillib/struct/acts_as_hash_spec.rb +422 -0
- data/spec/gorillib/array/compact_blank_spec.rb +2 -2
- data/spec/gorillib/collection_spec.rb +6 -6
- data/spec/gorillib/factories_spec.rb +2 -2
- data/spec/gorillib/hashlike_spec.rb +2 -1
- data/spec/gorillib/model/defaults_spec.rb +3 -3
- data/spec/gorillib/model/serialization/csv_spec.rb +35 -0
- data/spec/gorillib/model/serialization/tsv_spec.rb +20 -4
- data/spec/gorillib/model/serialization_spec.rb +3 -3
- data/spec/spec_helper.rb +6 -1
- data/spec/support/factory_test_helpers.rb +2 -2
- data/spec/support/gorillib_test_helpers.rb +4 -4
- data/spec/support/hashlike_fuzzing_helper.rb +1 -15
- data/spec/support/hashlike_helper.rb +5 -1
- data/spec/support/model_test_helpers.rb +12 -1
- metadata +192 -168
- data/notes/HOWTO.md +0 -22
- data/notes/bucket.md +0 -155
- data/notes/builder.md +0 -170
- data/notes/collection.md +0 -81
- data/notes/factories.md +0 -86
- data/notes/model-overlay.md +0 -209
- data/notes/model.md +0 -135
- data/notes/structured-data-classes.md +0 -127
@@ -16,6 +16,8 @@ module Gorillib
|
|
16
16
|
# valid_keys = [:mass, :velocity, :time]
|
17
17
|
# search(options.slice(*valid_keys))
|
18
18
|
#
|
19
|
+
# Note: Compatible with Rails 4.0 Active Support
|
20
|
+
#
|
19
21
|
# @return key/value pairs for keys in self and allowed
|
20
22
|
def slice(*allowed)
|
21
23
|
allowed.map!{|key| convert_key(key) } if respond_to?(:convert_key, true)
|
data/lib/gorillib/model/field.rb
CHANGED
@@ -45,8 +45,8 @@ module Gorillib
|
|
45
45
|
@model = model
|
46
46
|
@name = name.to_sym
|
47
47
|
@type = Gorillib::Factory.factory_for(type, type_opts)
|
48
|
-
|
49
|
-
@visibilities =
|
48
|
+
default_visibilities = visibilities
|
49
|
+
@visibilities = default_visibilities.merge( options.extract!(*default_visibilities.keys).compact )
|
50
50
|
@doc = options.delete(:name){ "#{name} field" }
|
51
51
|
receive!(options)
|
52
52
|
end
|
@@ -1,13 +1,16 @@
|
|
1
|
+
require_relative '../serialization/to_wire'
|
2
|
+
|
1
3
|
class Array
|
2
4
|
def to_tsv
|
3
|
-
join("\t")
|
5
|
+
to_wire.join("\t")
|
4
6
|
end
|
5
7
|
end
|
6
8
|
|
7
9
|
module Gorillib
|
8
10
|
module Model
|
11
|
+
|
9
12
|
def to_wire(options={})
|
10
|
-
|
13
|
+
compact_attributes.merge(:_type => self.class.typename).inject({}) do |acc, (key,attr)|
|
11
14
|
acc[key] = attr.respond_to?(:to_wire) ? attr.to_wire(options) : attr
|
12
15
|
acc
|
13
16
|
end
|
@@ -18,8 +21,10 @@ module Gorillib
|
|
18
21
|
MultiJson.dump(to_wire(options), options)
|
19
22
|
end
|
20
23
|
|
21
|
-
def to_tsv
|
22
|
-
|
24
|
+
def to_tsv(options={})
|
25
|
+
attributes.map do |key, attr|
|
26
|
+
attr.respond_to?(:to_wire) ? attr.to_wire(options) : attr
|
27
|
+
end.join("\t")
|
23
28
|
end
|
24
29
|
|
25
30
|
module ClassMethods
|
data/lib/gorillib/pathname.rb
CHANGED
@@ -66,7 +66,7 @@ module Gorillib
|
|
66
66
|
#
|
67
67
|
def relpath_to(*pathsegs)
|
68
68
|
ArgumentError.arity_at_least!(pathsegs, 1)
|
69
|
-
pathsegs = pathsegs.map{|ps| expand_pathseg(ps) }.flatten
|
69
|
+
pathsegs = pathsegs.flatten.map{|ps| expand_pathseg(ps) }.flatten
|
70
70
|
self.new(File.join(*pathsegs)).cleanpath(true)
|
71
71
|
end
|
72
72
|
alias_method :relative_path_to, :relpath_to
|
@@ -14,6 +14,12 @@ class Pathname
|
|
14
14
|
return self
|
15
15
|
end
|
16
16
|
|
17
|
+
# Like find, but returns an enumerable
|
18
|
+
#
|
19
|
+
def find_all
|
20
|
+
Enumerator.new{|yielder| find{|path| yielder << path } }
|
21
|
+
end
|
22
|
+
|
17
23
|
#
|
18
24
|
# Executes the block (passing the opened file) if the file does not
|
19
25
|
# exist. Ignores the block otherwise. The block is required.
|
@@ -114,7 +114,7 @@ module Gorillib::Inflector
|
|
114
114
|
#
|
115
115
|
def constantize(str)
|
116
116
|
unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ str
|
117
|
-
raise NameError, "#{
|
117
|
+
raise NameError, "#{str.inspect} is not a valid constant name!"
|
118
118
|
end
|
119
119
|
|
120
120
|
Object.module_eval("::#{$1}", __FILE__, __LINE__)
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'system/runner'
|
@@ -0,0 +1,36 @@
|
|
1
|
+
#You must include childprocess in your gemfile.
|
2
|
+
#Gorillib (intentionally) does not do so.
|
3
|
+
require 'childprocess'
|
4
|
+
require 'tempfile'
|
5
|
+
|
6
|
+
module Gorillib
|
7
|
+
module System
|
8
|
+
module Runner
|
9
|
+
extend self
|
10
|
+
|
11
|
+
def run(args, options={})
|
12
|
+
options = options.reverse_merge(mirror_io: false)
|
13
|
+
process = ChildProcess.build(*args)
|
14
|
+
out = Tempfile.new('gorillib-runner-out')
|
15
|
+
err = Tempfile.new('gorillib-runner-err')
|
16
|
+
process.io.stdout = out
|
17
|
+
process.io.stderr = err
|
18
|
+
process.start
|
19
|
+
process.wait
|
20
|
+
begin
|
21
|
+
out.rewind ; err.rewind
|
22
|
+
res = [out.read, err.read, process.exit_code]
|
23
|
+
if options[:mirror_io]
|
24
|
+
$stdout.write res[0]
|
25
|
+
$stderr.write res[1]
|
26
|
+
end
|
27
|
+
ensure
|
28
|
+
out.close ; err.close
|
29
|
+
out.unlink ; err.unlink
|
30
|
+
end
|
31
|
+
res
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
require 'gorillib/hash/keys'
|
2
|
+
|
3
|
+
# This class has dubious semantics and we only have it so that
|
4
|
+
# people can write <tt>params[:key]</tt> instead of <tt>params['key']</tt>
|
5
|
+
# and they get the same value for both keys.
|
6
|
+
|
7
|
+
module Gorillib
|
8
|
+
class HashWithIndifferentAccess < Hash
|
9
|
+
def extractable_options?
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
def with_indifferent_access
|
14
|
+
dup
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(constructor = {})
|
18
|
+
if constructor.is_a?(Hash)
|
19
|
+
super()
|
20
|
+
update(constructor)
|
21
|
+
else
|
22
|
+
super(constructor)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def default(key = nil)
|
27
|
+
if include?(converted = convert_key(key))
|
28
|
+
self[converted]
|
29
|
+
else
|
30
|
+
super
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.new_from_hash_copying_default(hash)
|
35
|
+
new(hash).tap do |new_hash|
|
36
|
+
new_hash.default = hash.default
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
alias_method(:regular_writer, :[]=) unless method_defined?(:regular_writer)
|
41
|
+
alias_method :regular_update, :update unless method_defined?(:regular_update)
|
42
|
+
|
43
|
+
# Assigns a new value to the hash:
|
44
|
+
#
|
45
|
+
# hash = HashWithIndifferentAccess.new
|
46
|
+
# hash[:key] = "value"
|
47
|
+
#
|
48
|
+
def []=(key, value)
|
49
|
+
regular_writer(convert_key(key), convert_value(value))
|
50
|
+
end
|
51
|
+
|
52
|
+
alias_method :store, :[]=
|
53
|
+
|
54
|
+
# Updates the instantized hash with values from the second:
|
55
|
+
#
|
56
|
+
# hash_1 = HashWithIndifferentAccess.new
|
57
|
+
# hash_1[:key] = "value"
|
58
|
+
#
|
59
|
+
# hash_2 = HashWithIndifferentAccess.new
|
60
|
+
# hash_2[:key] = "New Value!"
|
61
|
+
#
|
62
|
+
# hash_1.update(hash_2) # => {"key"=>"New Value!"}
|
63
|
+
#
|
64
|
+
def update(other_hash)
|
65
|
+
raise TypeError, "can't convert #{other_hash.nil? ? 'nil' : other_hash.class} into Hash" unless other_hash.respond_to?(:each_pair)
|
66
|
+
if other_hash.is_a? HashWithIndifferentAccess
|
67
|
+
super(other_hash)
|
68
|
+
else
|
69
|
+
other_hash.each_pair{|key, value| regular_writer(convert_key(key), convert_value(value)) }
|
70
|
+
self
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
alias_method :merge!, :update
|
75
|
+
|
76
|
+
# Checks the hash for a key matching the argument passed in:
|
77
|
+
#
|
78
|
+
# hash = HashWithIndifferentAccess.new
|
79
|
+
# hash["key"] = "value"
|
80
|
+
# hash.key? :key # => true
|
81
|
+
# hash.key? "key" # => true
|
82
|
+
#
|
83
|
+
def key?(key)
|
84
|
+
super(convert_key(key))
|
85
|
+
end
|
86
|
+
|
87
|
+
alias_method :include?, :key?
|
88
|
+
alias_method :has_key?, :key?
|
89
|
+
alias_method :member?, :key?
|
90
|
+
|
91
|
+
# Fetches the value for the specified key, same as doing hash[key]
|
92
|
+
def fetch(key, *extras)
|
93
|
+
super(convert_key(key), *extras)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns an array of the values at the specified indices:
|
97
|
+
#
|
98
|
+
# hash = HashWithIndifferentAccess.new
|
99
|
+
# hash[:a] = "x"
|
100
|
+
# hash[:b] = "y"
|
101
|
+
# hash.values_at("a", "b") # => ["x", "y"]
|
102
|
+
#
|
103
|
+
def values_at(*indices)
|
104
|
+
indices.collect {|key| self[convert_key(key)]}
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns an exact copy of the hash.
|
108
|
+
def dup
|
109
|
+
self.class.new(self).tap do |new_hash|
|
110
|
+
new_hash.default = default
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Merges the instantized and the specified hashes together, giving precedence to the values from the second hash
|
115
|
+
# Does not overwrite the existing hash.
|
116
|
+
def merge(hash)
|
117
|
+
self.dup.update(hash)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Performs the opposite of merge, with the keys and values from the first hash taking precedence over the second.
|
121
|
+
# This overloaded definition prevents returning a regular hash, if reverse_merge is called on a <tt>HashWithDifferentAccess</tt>.
|
122
|
+
def reverse_merge(other_hash)
|
123
|
+
super self.class.new_from_hash_copying_default(other_hash)
|
124
|
+
end
|
125
|
+
|
126
|
+
def reverse_merge!(other_hash)
|
127
|
+
replace(reverse_merge( other_hash ))
|
128
|
+
end
|
129
|
+
|
130
|
+
# Removes a specified key from the hash.
|
131
|
+
def delete(key)
|
132
|
+
super(convert_key(key))
|
133
|
+
end
|
134
|
+
|
135
|
+
def stringify_keys!; self end
|
136
|
+
def stringify_keys; dup end
|
137
|
+
undef_method :symbolize_keys! if method_defined?(:symbolize_keys!)
|
138
|
+
def symbolize_keys; to_hash.symbolize_keys end
|
139
|
+
def to_options!; self end
|
140
|
+
|
141
|
+
# Convert to a Hash with String keys.
|
142
|
+
def to_hash
|
143
|
+
Hash.new(default).merge!(self)
|
144
|
+
end
|
145
|
+
|
146
|
+
def assoc key
|
147
|
+
key = convert_key(key)
|
148
|
+
return unless has_key?(key)
|
149
|
+
[key, self[key]]
|
150
|
+
end
|
151
|
+
|
152
|
+
def rassoc val
|
153
|
+
key = key(val) or return
|
154
|
+
[key, self[key]]
|
155
|
+
end
|
156
|
+
|
157
|
+
protected
|
158
|
+
def convert_key(key)
|
159
|
+
key.kind_of?(Symbol) ? key.to_s : key
|
160
|
+
end
|
161
|
+
|
162
|
+
def convert_value(value)
|
163
|
+
if value.is_a? Hash
|
164
|
+
value.nested_under_indifferent_access
|
165
|
+
elsif value.is_a?(Array)
|
166
|
+
value.dup.replace(value.map{|e| convert_value(e) })
|
167
|
+
else
|
168
|
+
value
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
|
175
|
+
module Gorillib
|
176
|
+
class HashWithIndifferentSymbolKeys < Gorillib::HashWithIndifferentAccess
|
177
|
+
|
178
|
+
def convert_key key
|
179
|
+
return key if key.is_a?(Fixnum)
|
180
|
+
key.respond_to?(:to_sym) ? key.to_sym : key
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
class Hash
|
186
|
+
|
187
|
+
# Returns an +ActiveSupport::HashWithIndifferentAccess+ out of its receiver:
|
188
|
+
#
|
189
|
+
# {:a => 1}.with_indifferent_access["a"] # => 1
|
190
|
+
#
|
191
|
+
def with_indifferent_access
|
192
|
+
Gorillib::HashWithIndifferentAccess.new_from_hash_copying_default(self)
|
193
|
+
end
|
194
|
+
|
195
|
+
# Called when object is nested under an object that receives
|
196
|
+
# #with_indifferent_access. This method with be called on the current object
|
197
|
+
# by the enclosing object and is aliased to #with_indifferent_access by
|
198
|
+
# default. Subclasses of Hash may overwrite this method to return +self+ if
|
199
|
+
# converting to an +ActiveSupport::HashWithIndifferentAccess+ would not be
|
200
|
+
# desirable.
|
201
|
+
#
|
202
|
+
# b = {:b => 1}
|
203
|
+
# {:a => b}.with_indifferent_access["a"] # calls b.nested_under_indifferent_access
|
204
|
+
#
|
205
|
+
alias nested_under_indifferent_access with_indifferent_access
|
206
|
+
end
|
207
|
+
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Gorillib
|
2
|
+
module Hashlike
|
3
|
+
module TreeMerge
|
4
|
+
|
5
|
+
# Recursively merges hashlike objects
|
6
|
+
#
|
7
|
+
# For each key in keys,
|
8
|
+
# * if block_given? and yield(key,self_val,other_val) returns non-nil, set that
|
9
|
+
# * if self is missing value for key, receive the attribute.
|
10
|
+
# * if self's attribute is an Array, append to it.
|
11
|
+
# * if self's value responds to tree_merge!, deep merge it.
|
12
|
+
# * if self's value responds_to merge!, merge! it.
|
13
|
+
# * otherwise, receive the value from other_hash
|
14
|
+
#
|
15
|
+
def tree_merge!(other_hash)
|
16
|
+
return self if other_hash.blank?
|
17
|
+
[self.keys, other_hash.keys].flatten.uniq.each do |key|
|
18
|
+
# get other's val if any
|
19
|
+
if other_hash.has_key?(key.to_sym) then other_val = other_hash[key.to_sym]
|
20
|
+
elsif other_hash.has_key?(key.to_s) then other_val = other_hash[key.to_s]
|
21
|
+
else next ; end
|
22
|
+
# get self val if any
|
23
|
+
self_val = self[key]
|
24
|
+
# get block resolved result if any
|
25
|
+
if block_given? && yield(key, self_val, other_val)
|
26
|
+
next
|
27
|
+
end
|
28
|
+
# p ['hash tree_merge', key, self_val.respond_to?(:tree_merge!), self_val, '***************', other_val]
|
29
|
+
#
|
30
|
+
case
|
31
|
+
when other_val.nil? then next
|
32
|
+
when (not has_key?(key)) then self[key] = other_val
|
33
|
+
when self_val.is_a?(Array) then self[key] += other_val
|
34
|
+
when self_val.respond_to?(:tree_merge!) then self[key] = self_val.tree_merge!(other_val)
|
35
|
+
when self_val.respond_to?(:merge!) then self[key] = self_val.merge!(other_val)
|
36
|
+
else self[key] = other_val
|
37
|
+
end
|
38
|
+
end
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
class Hash
|
48
|
+
include Gorillib::Hashlike::TreeMerge
|
49
|
+
end
|