epitools 0.5.1 → 0.5.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Guardfile +16 -0
- data/VERSION +1 -1
- data/epitools.gemspec +13 -6
- data/lib/epitools.rb +48 -6
- data/lib/epitools/autoloads.rb +14 -3
- data/lib/epitools/browser.rb +8 -1
- data/lib/epitools/core_ext.rb +207 -0
- data/lib/epitools/core_ext/array.rb +98 -0
- data/lib/epitools/core_ext/enumerable.rb +306 -0
- data/lib/epitools/core_ext/hash.rb +201 -0
- data/lib/epitools/core_ext/numbers.rb +254 -0
- data/lib/epitools/core_ext/object.rb +195 -0
- data/lib/epitools/core_ext/string.rb +338 -0
- data/lib/epitools/core_ext/truthiness.rb +64 -0
- data/lib/epitools/iter.rb +22 -8
- data/lib/epitools/mimemagic.rb +0 -1
- data/lib/epitools/path.rb +149 -36
- data/lib/epitools/rash.rb +49 -37
- data/lib/epitools/term.rb +5 -1
- data/spec/{basetypes_spec.rb → core_ext_spec.rb} +190 -37
- data/spec/iter_spec.rb +9 -27
- data/spec/path_spec.rb +104 -46
- data/spec/permutations_spec.rb +3 -2
- data/spec/rash_spec.rb +21 -7
- data/spec/spec_helper.rb +36 -5
- metadata +34 -12
- data/lib/epitools/basetypes.rb +0 -1135
- data/lib/epitools/string_to_proc.rb +0 -78
@@ -0,0 +1,306 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Enumerable
|
4
|
+
|
5
|
+
#
|
6
|
+
# 'true' if the Enumerable has no elements
|
7
|
+
#
|
8
|
+
def blank?
|
9
|
+
not any?
|
10
|
+
end
|
11
|
+
|
12
|
+
#
|
13
|
+
# `.all` is more fun to type than `.to_a`
|
14
|
+
#
|
15
|
+
alias_method :all, :to_a
|
16
|
+
|
17
|
+
#
|
18
|
+
# `includes?` is gramatically correct.
|
19
|
+
#
|
20
|
+
alias_method :includes?, :include?
|
21
|
+
|
22
|
+
|
23
|
+
#
|
24
|
+
# Skip the first n elements and return an Enumerator for the rest, or pass them
|
25
|
+
# in succession to the block, if given. This is like "drop", but returns an enumerator
|
26
|
+
# instead of converting the whole thing to an array.
|
27
|
+
#
|
28
|
+
def skip(n)
|
29
|
+
if block_given?
|
30
|
+
each do |x|
|
31
|
+
if n > 0
|
32
|
+
n -= 1
|
33
|
+
else
|
34
|
+
yield x
|
35
|
+
end
|
36
|
+
end
|
37
|
+
else
|
38
|
+
enum_for :skip, n
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# Split this enumerable into chunks, given some boundary condition. (Returns an array of arrays.)
|
44
|
+
#
|
45
|
+
# Options:
|
46
|
+
# :include_boundary => true #=> include the element that you're splitting at in the results
|
47
|
+
# (default: false)
|
48
|
+
# :after => true #=> split after the matched element (only has an effect when used with :include_boundary)
|
49
|
+
# (default: false)
|
50
|
+
# :once => flase #=> only perform one split (default: false)
|
51
|
+
#
|
52
|
+
# Examples:
|
53
|
+
# [1,2,3,4,5].split{ |e| e == 3 }
|
54
|
+
# #=> [ [1,2], [4,5] ]
|
55
|
+
#
|
56
|
+
# [1,2,3,4,5].split(:include_boundary=>true) { |e| e == 3 }
|
57
|
+
# #=> [ [1,2], [3,4,5] ]
|
58
|
+
#
|
59
|
+
# chapters = File.read("ebook.txt").split(/Chapter \d+/, :include_boundary=>true)
|
60
|
+
# #=> [ ["Chapter 1", ...], ["Chapter 2", ...], etc. ]
|
61
|
+
#
|
62
|
+
def split_at(matcher=nil, options={}, &block)
|
63
|
+
# TODO: Ruby 1.9 returns Enumerators for everything now. Maybe use that?
|
64
|
+
|
65
|
+
return self unless self.any?
|
66
|
+
|
67
|
+
include_boundary = options[:include_boundary] || false
|
68
|
+
|
69
|
+
if matcher.nil?
|
70
|
+
boundary_test_proc = block
|
71
|
+
else
|
72
|
+
if matcher.is_a? String or matcher.is_a? Regexp
|
73
|
+
boundary_test_proc = proc { |element| element[matcher] rescue nil }
|
74
|
+
else
|
75
|
+
boundary_test_proc = proc { |element| element == matcher }
|
76
|
+
#raise "I don't know how to split with #{matcher}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
chunks = []
|
81
|
+
current_chunk = []
|
82
|
+
|
83
|
+
splits = 0
|
84
|
+
max_splits = options[:once] == true ? 1 : options[:max_splits]
|
85
|
+
|
86
|
+
each do |e|
|
87
|
+
|
88
|
+
if boundary_test_proc.call(e) and (max_splits == nil or splits < max_splits)
|
89
|
+
|
90
|
+
if current_chunk.empty? and not include_boundary
|
91
|
+
next # hit 2 boundaries in a row... just keep moving, people!
|
92
|
+
end
|
93
|
+
|
94
|
+
if options[:after]
|
95
|
+
# split after boundary
|
96
|
+
current_chunk << e if include_boundary # include the boundary, if necessary
|
97
|
+
chunks << current_chunk # shift everything after the boundary into the resultset
|
98
|
+
current_chunk = [] # start a new result
|
99
|
+
else
|
100
|
+
# split before boundary
|
101
|
+
chunks << current_chunk # shift before the boundary into the resultset
|
102
|
+
current_chunk = [] # start a new result
|
103
|
+
current_chunk << e if include_boundary # include the boundary, if necessary
|
104
|
+
end
|
105
|
+
|
106
|
+
splits += 1
|
107
|
+
|
108
|
+
else
|
109
|
+
current_chunk << e
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
chunks << current_chunk if current_chunk.any?
|
115
|
+
|
116
|
+
chunks # resultset
|
117
|
+
end
|
118
|
+
|
119
|
+
#
|
120
|
+
# Split the array into chunks, cutting between the matched element and the next element.
|
121
|
+
#
|
122
|
+
# Example:
|
123
|
+
# [1,2,3,4].split_after{|e| e == 3 } #=> [ [1,2,3], [4] ]
|
124
|
+
#
|
125
|
+
def split_after(matcher=nil, options={}, &block)
|
126
|
+
options[:after] ||= true
|
127
|
+
options[:include_boundary] ||= true
|
128
|
+
split_at(matcher, options, &block)
|
129
|
+
end
|
130
|
+
|
131
|
+
#
|
132
|
+
# Split the array into chunks, cutting between the matched element and the previous element.
|
133
|
+
#
|
134
|
+
# Example:
|
135
|
+
# [1,2,3,4].split_before{|e| e == 3 } #=> [ [1,2], [3,4] ]
|
136
|
+
#
|
137
|
+
def split_before(matcher=nil, options={}, &block)
|
138
|
+
options[:include_boundary] ||= true
|
139
|
+
split_at(matcher, options, &block)
|
140
|
+
end
|
141
|
+
|
142
|
+
#
|
143
|
+
# Sum the elements
|
144
|
+
#
|
145
|
+
def sum
|
146
|
+
if block_given?
|
147
|
+
inject(0) { |total,elem| total + yield(elem) }
|
148
|
+
else
|
149
|
+
inject(0) { |total,elem| total + elem }
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
#
|
154
|
+
# Average the elements
|
155
|
+
#
|
156
|
+
def average
|
157
|
+
count = 0
|
158
|
+
sum = inject(0) { |total,n| count += 1; total + n }
|
159
|
+
sum / count.to_f
|
160
|
+
end
|
161
|
+
|
162
|
+
#
|
163
|
+
# The same as "map", except that if an element is an Array or Enumerable, map is called
|
164
|
+
# recursively on that element.
|
165
|
+
#
|
166
|
+
# Example:
|
167
|
+
# [ [1,2], [3,4] ].deep_map{|e| e ** 2 } #=> [ [1,4], [9,16] ]
|
168
|
+
#
|
169
|
+
def deep_map(depth=nil, &block)
|
170
|
+
map do |obj|
|
171
|
+
|
172
|
+
case obj
|
173
|
+
when Enumerable
|
174
|
+
obj.deep_map(&block)
|
175
|
+
else
|
176
|
+
block.call(obj)
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
alias_method :recursive_map, :deep_map
|
183
|
+
alias_method :map_recursively, :deep_map
|
184
|
+
alias_method :map_recursive, :deep_map
|
185
|
+
|
186
|
+
#
|
187
|
+
# The same as "select", except that if an element is an Array or Enumerable, select is called
|
188
|
+
# recursively on that element.
|
189
|
+
#
|
190
|
+
# Example:
|
191
|
+
# [ [1,2], [3,4] ].deep_map{|e| e ** 2 } #=> [ [1,4], [9,16] ]
|
192
|
+
#
|
193
|
+
def deep_select(depth=nil, &block)
|
194
|
+
select do |e|
|
195
|
+
if (e.is_a? Array or e.is_a? Enumerable) and (depth && depth > 0)
|
196
|
+
e.select(depth-1, &block)
|
197
|
+
else
|
198
|
+
block.call(e)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
alias_method :recursive_select, :deep_select
|
204
|
+
|
205
|
+
|
206
|
+
#
|
207
|
+
# Identical to "reduce" in ruby1.9 (or foldl in haskell.)
|
208
|
+
#
|
209
|
+
# Example:
|
210
|
+
# array.foldl{|a,b| a + b } == array[1..-1].inject(array[0]){|a,b| a + b }
|
211
|
+
#
|
212
|
+
def foldl(methodname=nil, &block)
|
213
|
+
result = nil
|
214
|
+
|
215
|
+
raise "Error: pass a parameter OR a block, not both!" unless !!methodname ^ block_given?
|
216
|
+
|
217
|
+
if methodname
|
218
|
+
|
219
|
+
each_with_index do |e,i|
|
220
|
+
if i == 0
|
221
|
+
result = e
|
222
|
+
next
|
223
|
+
end
|
224
|
+
|
225
|
+
result = result.send(methodname, e)
|
226
|
+
end
|
227
|
+
|
228
|
+
else
|
229
|
+
|
230
|
+
each_with_index do |e,i|
|
231
|
+
if i == 0
|
232
|
+
result = e
|
233
|
+
next
|
234
|
+
end
|
235
|
+
|
236
|
+
result = block.call(result, e)
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|
240
|
+
|
241
|
+
result
|
242
|
+
end
|
243
|
+
|
244
|
+
#
|
245
|
+
# Returns the powerset of the Enumerable
|
246
|
+
#
|
247
|
+
# Example:
|
248
|
+
# [1,2].powerset #=> [[], [1], [2], [1, 2]]
|
249
|
+
#
|
250
|
+
def powerset
|
251
|
+
# the bit pattern of the numbers from 0..2^(elements)-1 can be used to select the elements of the set...
|
252
|
+
a = to_a
|
253
|
+
(0...2**a.size).map do |bitmask|
|
254
|
+
a.select.with_index{ |e, i| bitmask[i] == 1 }
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
#
|
259
|
+
# Does the opposite of #zip -- converts [ [:a, 1], [:b, 2] ] to [ [:a, :b], [1, 2] ]
|
260
|
+
#
|
261
|
+
def unzip
|
262
|
+
# TODO: make it work for arrays containing uneven-length contents
|
263
|
+
to_a.transpose
|
264
|
+
end
|
265
|
+
|
266
|
+
#
|
267
|
+
# Associative grouping; groups all elements who share something in common with each other.
|
268
|
+
# You supply a block which takes two elements, and have it return true if they are "neighbours"
|
269
|
+
# (eg: belong in the same group).
|
270
|
+
#
|
271
|
+
# Example:
|
272
|
+
# [1,2,5,6].group_neighbours_by { |a,b| b-a <= 1 } #=> [ [1,2], [5,6] ]
|
273
|
+
#
|
274
|
+
# (Note: This is a very fast one-pass algorithm -- therefore, the groups must be pre-sorted.)
|
275
|
+
#
|
276
|
+
def group_neighbours_by(&block)
|
277
|
+
result = []
|
278
|
+
cluster = [first]
|
279
|
+
each_cons(2) do |a,b|
|
280
|
+
if yield(a,b)
|
281
|
+
cluster << b
|
282
|
+
else
|
283
|
+
result << cluster
|
284
|
+
cluster = [b]
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
result << cluster if cluster.any?
|
289
|
+
|
290
|
+
result
|
291
|
+
end
|
292
|
+
alias_method :group_neighbors_by, :group_neighbours_by
|
293
|
+
|
294
|
+
|
295
|
+
#
|
296
|
+
# Convert the array into a stable iterator (Iter) object.
|
297
|
+
#
|
298
|
+
def to_iter
|
299
|
+
Iter.new(to_a)
|
300
|
+
end
|
301
|
+
alias_method :iter, :to_iter
|
302
|
+
|
303
|
+
|
304
|
+
end
|
305
|
+
|
306
|
+
|
@@ -0,0 +1,201 @@
|
|
1
|
+
|
2
|
+
class Hash
|
3
|
+
|
4
|
+
#
|
5
|
+
# 'true' if the Hash has no entries
|
6
|
+
#
|
7
|
+
def blank?
|
8
|
+
not any?
|
9
|
+
end
|
10
|
+
|
11
|
+
#
|
12
|
+
# Runs "remove_blank_values" on self.
|
13
|
+
#
|
14
|
+
def remove_blank_values!
|
15
|
+
delete_if{|k,v| v.blank?}
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
# Returns a new Hash where blank values have been removed.
|
21
|
+
# (It checks if the value is blank by calling #blank? on it)
|
22
|
+
#
|
23
|
+
def remove_blank_values
|
24
|
+
dup.remove_blank_values!
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# Runs map_values on self.
|
29
|
+
#
|
30
|
+
def map_values!(&block)
|
31
|
+
keys.each do |key|
|
32
|
+
value = self[key]
|
33
|
+
self[key] = yield(value)
|
34
|
+
end
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# Transforms the values of the hash by passing them into the supplied
|
40
|
+
# block, and then using the block's result as the new value.
|
41
|
+
#
|
42
|
+
def map_values(&block)
|
43
|
+
dup.map_values!(&block)
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# Runs map_keys on self.
|
48
|
+
#
|
49
|
+
def map_keys!(&block)
|
50
|
+
keys.each do |key|
|
51
|
+
value = delete(key)
|
52
|
+
self[yield(key)] = value
|
53
|
+
end
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# Transforms the keys of the hash by passing them into the supplied block,
|
59
|
+
# and then using the blocks result as the new key.
|
60
|
+
#
|
61
|
+
def map_keys(&block)
|
62
|
+
dup.map_keys!(&block)
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# Returns a new Hash whose values default to empty arrays. (Good for collecting things!)
|
67
|
+
#
|
68
|
+
# eg:
|
69
|
+
# Hash.of_arrays[:yays] << "YAY!"
|
70
|
+
#
|
71
|
+
def self.of_arrays
|
72
|
+
new {|h,k| h[k] = [] }
|
73
|
+
end
|
74
|
+
|
75
|
+
#
|
76
|
+
# Returns a new Hash whose values default to 0. (Good for counting things!)
|
77
|
+
#
|
78
|
+
# eg:
|
79
|
+
# Hash.of_integers[:yays] += 1
|
80
|
+
#
|
81
|
+
def self.of_integers
|
82
|
+
new(0)
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# Hash keys become methods, kinda like OpenStruct. These methods have the lowest priority,
|
87
|
+
# so be careful. They will be overridden by any methods on Hash.
|
88
|
+
#
|
89
|
+
def self.lazy!
|
90
|
+
Hash.class_eval do
|
91
|
+
def method_missing(name, *args)
|
92
|
+
if args.any?
|
93
|
+
super
|
94
|
+
else
|
95
|
+
self[name] || self[name.to_s]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
#
|
102
|
+
# `key?` and `includes?` is an alias for `include?`
|
103
|
+
#
|
104
|
+
alias_method :key?, :include?
|
105
|
+
alias_method :includes?, :include?
|
106
|
+
|
107
|
+
#
|
108
|
+
# Makes each element in the `path` array point to a hash containing the next element in the `path`.
|
109
|
+
# Useful for turning a bunch of strings (paths, module names, etc.) into a tree.
|
110
|
+
#
|
111
|
+
# Example:
|
112
|
+
# h = {}
|
113
|
+
# h.mkdir_p(["a", "b", "c"]) #=> {"a"=>{"b"=>{"c"=>{}}}}
|
114
|
+
# h.mkdir_p(["a", "b", "whoa"]) #=> {"a"=>{"b"=>{"c"=>{}, "whoa"=>{}}}}
|
115
|
+
#
|
116
|
+
def mkdir_p(path)
|
117
|
+
return if path.empty?
|
118
|
+
dir = path.first
|
119
|
+
self[dir] ||= {}
|
120
|
+
self[dir].mkdir_p(path[1..-1])
|
121
|
+
self
|
122
|
+
end
|
123
|
+
|
124
|
+
#
|
125
|
+
# Turn some nested hashes into a tree (returns an array of strings, padded on the left with indents.)
|
126
|
+
#
|
127
|
+
def tree(level=0, indent=" ")
|
128
|
+
result = []
|
129
|
+
dent = indent * level
|
130
|
+
each do |key, val|
|
131
|
+
result << dent+key
|
132
|
+
result += val.tree(level+1) if val.any?
|
133
|
+
end
|
134
|
+
result
|
135
|
+
end
|
136
|
+
|
137
|
+
#
|
138
|
+
# Print the result of `tree`
|
139
|
+
#
|
140
|
+
def print_tree
|
141
|
+
tree.each { |row| puts row }
|
142
|
+
nil
|
143
|
+
end
|
144
|
+
|
145
|
+
#
|
146
|
+
# Convert the hash into a GET query.
|
147
|
+
#
|
148
|
+
def to_query
|
149
|
+
params = ''
|
150
|
+
stack = []
|
151
|
+
|
152
|
+
each do |k, v|
|
153
|
+
if v.is_a?(Hash)
|
154
|
+
stack << [k,v]
|
155
|
+
else
|
156
|
+
params << "#{k}=#{v}&"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
stack.each do |parent, hash|
|
161
|
+
hash.each do |k, v|
|
162
|
+
if v.is_a?(Hash)
|
163
|
+
stack << ["#{parent}[#{k}]", v]
|
164
|
+
else
|
165
|
+
params << "#{parent}[#{k}]=#{v}&"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
params.chop! # trailing &
|
171
|
+
params
|
172
|
+
end
|
173
|
+
|
174
|
+
#
|
175
|
+
# Query a hash using MQL (see: http://wiki.freebase.com/wiki/MQL_operators for reference)
|
176
|
+
#
|
177
|
+
# Examples:
|
178
|
+
# > query(name: /steve/)
|
179
|
+
# > query(/title/ => ??)
|
180
|
+
# > query(articles: [{title: ??}])
|
181
|
+
# > query(responses: [])
|
182
|
+
# > query("date_of_birth<" => "2000")
|
183
|
+
#
|
184
|
+
def query(template)
|
185
|
+
results = []
|
186
|
+
template.each do |key,val|
|
187
|
+
case key
|
188
|
+
when Regexp, String
|
189
|
+
when Array
|
190
|
+
when Hash
|
191
|
+
results += hash.query(template)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
map do |key,val|
|
196
|
+
end
|
197
|
+
end
|
198
|
+
alias_method :mql, :query
|
199
|
+
|
200
|
+
end
|
201
|
+
|