epitools 0.5.1 → 0.5.2
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.
- 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
|
+
|