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
data/lib/epitools/basetypes.rb
DELETED
@@ -1,1135 +0,0 @@
|
|
1
|
-
require 'epitools'
|
2
|
-
|
3
|
-
## Alias "Enumerator" to "Enum"
|
4
|
-
|
5
|
-
if RUBY_VERSION["1.8"]
|
6
|
-
require 'enumerator'
|
7
|
-
Enumerator = Enumerable::Enumerator unless defined? Enumerator
|
8
|
-
end
|
9
|
-
|
10
|
-
unless defined? Enum
|
11
|
-
if defined? Enumerator
|
12
|
-
Enum = Enumerator
|
13
|
-
else
|
14
|
-
$stderr.puts "WARNING: Couldn't find the Enumerator class. Enum will not be available."
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
RbConfig = Config unless defined? RbConfig
|
19
|
-
|
20
|
-
class Object
|
21
|
-
|
22
|
-
#
|
23
|
-
# Slightly gross hack to add a class method.
|
24
|
-
#
|
25
|
-
def self.alias_class_method(dest, src)
|
26
|
-
metaclass.send(:alias_method, dest, src)
|
27
|
-
end
|
28
|
-
|
29
|
-
#
|
30
|
-
# Default "integer?" behaviour.
|
31
|
-
#
|
32
|
-
def integer?; false; end
|
33
|
-
|
34
|
-
#
|
35
|
-
# `truthy?` means `not blank?`
|
36
|
-
#
|
37
|
-
def truthy?
|
38
|
-
if respond_to? :blank?
|
39
|
-
not blank?
|
40
|
-
else
|
41
|
-
not nil?
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def marshal
|
46
|
-
Marshal.dump self
|
47
|
-
end
|
48
|
-
|
49
|
-
#
|
50
|
-
# Lets you say: `object.is_an? Array`
|
51
|
-
#
|
52
|
-
alias_method :is_an?, :is_a?
|
53
|
-
|
54
|
-
end
|
55
|
-
|
56
|
-
class TrueClass
|
57
|
-
def truthy?; true; end
|
58
|
-
end
|
59
|
-
|
60
|
-
class FalseClass
|
61
|
-
def truthy?; false; end
|
62
|
-
end
|
63
|
-
|
64
|
-
class Float
|
65
|
-
#
|
66
|
-
# 'true' if the float is 0.0
|
67
|
-
#
|
68
|
-
def blank?; self == 0.0; end
|
69
|
-
end
|
70
|
-
|
71
|
-
class NilClass
|
72
|
-
#
|
73
|
-
# Always 'true'; nil is considered blank.
|
74
|
-
#
|
75
|
-
def blank?; true; end
|
76
|
-
end
|
77
|
-
|
78
|
-
class Symbol
|
79
|
-
#
|
80
|
-
# Symbols are never blank.
|
81
|
-
#
|
82
|
-
def blank?; false; end
|
83
|
-
end
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
class Numeric
|
88
|
-
|
89
|
-
def integer?; true; end
|
90
|
-
|
91
|
-
def truthy?; self > 0; end
|
92
|
-
|
93
|
-
def commatize
|
94
|
-
to_s.gsub(/(\d)(?=\d{3}+(?:\.|$))(\d{3}\..*)?/,'\1,\2')
|
95
|
-
end
|
96
|
-
|
97
|
-
#
|
98
|
-
# Time methods
|
99
|
-
#
|
100
|
-
{
|
101
|
-
|
102
|
-
'second' => 1,
|
103
|
-
'minute' => 60,
|
104
|
-
'hour' => 60 * 60,
|
105
|
-
'day' => 60 * 60 * 24,
|
106
|
-
'week' => 60 * 60 * 24 * 7,
|
107
|
-
'month' => 60 * 60 * 24 * 30,
|
108
|
-
'year' => 60 * 60 * 24 * 364.25,
|
109
|
-
|
110
|
-
}.each do |unit, scale|
|
111
|
-
define_method(unit) { self * scale }
|
112
|
-
define_method(unit+'s') { self * scale }
|
113
|
-
end
|
114
|
-
|
115
|
-
def ago
|
116
|
-
Time.now - self
|
117
|
-
end
|
118
|
-
|
119
|
-
def from_now
|
120
|
-
Time.now + self
|
121
|
-
end
|
122
|
-
|
123
|
-
end
|
124
|
-
|
125
|
-
class Integer
|
126
|
-
|
127
|
-
#
|
128
|
-
# 'true' if the integer is 0
|
129
|
-
#
|
130
|
-
def blank?; self == 0; end
|
131
|
-
|
132
|
-
#
|
133
|
-
# Convert the number into a hexadecimal string representation.
|
134
|
-
# (Identical to to_s(16), except that numbers < 16 will have a 0 in front of them.)
|
135
|
-
#
|
136
|
-
def to_hex
|
137
|
-
"%0.2x" % self
|
138
|
-
end
|
139
|
-
|
140
|
-
#
|
141
|
-
# Convert the number to an array of bits (least significant digit first, or little-endian).
|
142
|
-
#
|
143
|
-
def to_bits
|
144
|
-
# TODO: Why does thos go into an infinite loop in 1.8.7?
|
145
|
-
("%b" % self).chars.to_a.reverse.map(&:to_i)
|
146
|
-
end
|
147
|
-
alias_method :bits, :to_bits
|
148
|
-
|
149
|
-
#
|
150
|
-
# Cached constants for base62 encoding
|
151
|
-
#
|
152
|
-
BASE62_DIGITS = ['0'..'9', 'A'..'Z', 'a'..'z'].map(&:to_a).flatten
|
153
|
-
BASE62_BASE = BASE62_DIGITS.size
|
154
|
-
|
155
|
-
#
|
156
|
-
# Convert a number to a string representation (in "base62" encoding).
|
157
|
-
#
|
158
|
-
# Base62 encoding represents the number using the characters: 0..9, A..Z, a..z
|
159
|
-
#
|
160
|
-
# It's the same scheme that url shorteners and YouTube uses for their
|
161
|
-
# ID strings. (eg: http://www.youtube.com/watch?v=dQw4w9WgXcQ)
|
162
|
-
#
|
163
|
-
def to_base62
|
164
|
-
result = []
|
165
|
-
remainder = self
|
166
|
-
max_power = ( Math.log(self) / Math.log(BASE62_BASE) ).floor
|
167
|
-
|
168
|
-
max_power.downto(0) do |power|
|
169
|
-
divisor = BASE62_BASE**power
|
170
|
-
#p [:div, divisor, :rem, remainder]
|
171
|
-
digit, remainder = remainder.divmod(divisor)
|
172
|
-
result << digit
|
173
|
-
end
|
174
|
-
|
175
|
-
result << remainder if remainder > 0
|
176
|
-
|
177
|
-
result.map{|digit| BASE62_DIGITS[digit]}.join ''
|
178
|
-
end
|
179
|
-
|
180
|
-
#
|
181
|
-
# Returns the all the prime factors of a number.
|
182
|
-
#
|
183
|
-
def factors
|
184
|
-
Prime # autoload the prime module
|
185
|
-
prime_division.map { |n,count| [n]*count }.flatten
|
186
|
-
end
|
187
|
-
|
188
|
-
end
|
189
|
-
|
190
|
-
|
191
|
-
#
|
192
|
-
# Monkeypatch [] into Bignum and Fixnum using class_eval.
|
193
|
-
#
|
194
|
-
# (This is necessary because [] is defined directly on the classes, and a mixin
|
195
|
-
# module will still be overridden by Big/Fixnum's native [] method.)
|
196
|
-
#
|
197
|
-
[Bignum, Fixnum].each do |klass|
|
198
|
-
|
199
|
-
klass.class_eval do
|
200
|
-
|
201
|
-
alias_method :bit, :"[]"
|
202
|
-
|
203
|
-
#
|
204
|
-
# Extends [] so that Integers can be sliced as if they were arrays.
|
205
|
-
#
|
206
|
-
def [](arg)
|
207
|
-
case arg
|
208
|
-
when Integer
|
209
|
-
self.bit(arg)
|
210
|
-
when Range
|
211
|
-
self.bits[arg]
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
end
|
216
|
-
|
217
|
-
end
|
218
|
-
|
219
|
-
|
220
|
-
class String
|
221
|
-
|
222
|
-
#
|
223
|
-
# Could this string be cast to an integer?
|
224
|
-
#
|
225
|
-
def integer?
|
226
|
-
strip.match(/^\d+$/) ? true : false
|
227
|
-
end
|
228
|
-
|
229
|
-
#
|
230
|
-
# 'true' if the string's length is 0 (after whitespace has been stripped from the ends)
|
231
|
-
#
|
232
|
-
def blank?
|
233
|
-
strip.size == 0
|
234
|
-
end
|
235
|
-
|
236
|
-
#
|
237
|
-
# Does this string contain something that means roughly "true"?
|
238
|
-
#
|
239
|
-
def truthy?
|
240
|
-
case strip.downcase
|
241
|
-
when "1", "true", "yes", "on", "enabled", "affirmative"
|
242
|
-
true
|
243
|
-
else
|
244
|
-
false
|
245
|
-
end
|
246
|
-
end
|
247
|
-
|
248
|
-
#
|
249
|
-
# Convert \r\n to \n
|
250
|
-
#
|
251
|
-
def to_unix
|
252
|
-
gsub("\r\n", "\n")
|
253
|
-
end
|
254
|
-
|
255
|
-
#
|
256
|
-
# Remove redundant whitespaces (not including newlines).
|
257
|
-
#
|
258
|
-
def tighten
|
259
|
-
gsub(/[\t ]+/,' ').strip
|
260
|
-
end
|
261
|
-
|
262
|
-
#
|
263
|
-
# Remove redundant whitespace AND newlines.
|
264
|
-
#
|
265
|
-
def dewhitespace
|
266
|
-
gsub(/\s+/,' ').strip
|
267
|
-
end
|
268
|
-
|
269
|
-
#
|
270
|
-
# Remove ANSI color codes.
|
271
|
-
#
|
272
|
-
def strip_color
|
273
|
-
gsub(/\e\[.*?(\d)+m/, '')
|
274
|
-
end
|
275
|
-
alias_method :strip_ansi, :strip_color
|
276
|
-
|
277
|
-
#
|
278
|
-
# Like #lines, but skips empty lines and removes \n's.
|
279
|
-
#
|
280
|
-
def nice_lines
|
281
|
-
# note: $/ is the platform's newline separator
|
282
|
-
split($/).select{|l| not l.blank? }
|
283
|
-
end
|
284
|
-
|
285
|
-
alias_method :nicelines, :nice_lines
|
286
|
-
alias_method :clean_lines, :nice_lines
|
287
|
-
|
288
|
-
#
|
289
|
-
# The Infamous Caesar-Cipher. Unbreakable to this day.
|
290
|
-
#
|
291
|
-
def rot13
|
292
|
-
tr('n-za-mN-ZA-M', 'a-zA-Z')
|
293
|
-
end
|
294
|
-
|
295
|
-
#
|
296
|
-
# Convert non-URI characters into %XXes.
|
297
|
-
#
|
298
|
-
def urlencode
|
299
|
-
URI.escape(self)
|
300
|
-
end
|
301
|
-
|
302
|
-
#
|
303
|
-
# Convert an URI's %XXes into regular characters.
|
304
|
-
#
|
305
|
-
def urldecode
|
306
|
-
URI.unescape(self)
|
307
|
-
end
|
308
|
-
|
309
|
-
#
|
310
|
-
# Convert a query string to a hash of params
|
311
|
-
#
|
312
|
-
def to_params
|
313
|
-
CGI.parse(self).map_values do |v|
|
314
|
-
# CGI.parse wraps every value in an array. Unwrap them!
|
315
|
-
if v.is_a?(Array) and v.size == 1
|
316
|
-
v.first
|
317
|
-
else
|
318
|
-
v
|
319
|
-
end
|
320
|
-
end
|
321
|
-
end
|
322
|
-
|
323
|
-
|
324
|
-
#
|
325
|
-
# Cached constants for base62 decoding.
|
326
|
-
#
|
327
|
-
BASE62_DIGITS = Hash[ Integer::BASE62_DIGITS.zip((0...Integer::BASE62_DIGITS.size).to_a) ]
|
328
|
-
BASE62_BASE = Integer::BASE62_BASE
|
329
|
-
|
330
|
-
#
|
331
|
-
# Convert a string (encoded in base16 "hex" -- for example, an MD5 or SHA1 hash)
|
332
|
-
# into "base62" format. (See Integer#to_base62 for more info.)
|
333
|
-
#
|
334
|
-
def to_base62
|
335
|
-
to_i(16).to_base62
|
336
|
-
end
|
337
|
-
|
338
|
-
#
|
339
|
-
# Convert a string encoded in base62 into an integer.
|
340
|
-
# (See Integer#to_base62 for more info.)
|
341
|
-
#
|
342
|
-
def from_base62
|
343
|
-
accumulator = 0
|
344
|
-
digits = chars.map { |c| BASE62_DIGITS[c] }.reverse
|
345
|
-
digits.each_with_index do |digit, power|
|
346
|
-
accumulator += (BASE62_BASE**power) * digit if digit > 0
|
347
|
-
end
|
348
|
-
accumulator
|
349
|
-
end
|
350
|
-
|
351
|
-
#
|
352
|
-
# Decode a mime64/base64 encoded string
|
353
|
-
#
|
354
|
-
def from_base64
|
355
|
-
Base64.decode64 self
|
356
|
-
end
|
357
|
-
alias_method :decode64, :from_base64
|
358
|
-
|
359
|
-
#
|
360
|
-
# Encode into a mime64/base64 string
|
361
|
-
#
|
362
|
-
def to_base64
|
363
|
-
Base64.encode64 self
|
364
|
-
end
|
365
|
-
alias_method :base64, :to_base64
|
366
|
-
alias_method :encode64, :to_base64
|
367
|
-
|
368
|
-
#
|
369
|
-
# MD5 the string
|
370
|
-
#
|
371
|
-
def md5
|
372
|
-
Digest::MD5.hexdigest self
|
373
|
-
end
|
374
|
-
|
375
|
-
#
|
376
|
-
# SHA1 the string
|
377
|
-
#
|
378
|
-
def sha1
|
379
|
-
Digest::SHA1.hexdigest self
|
380
|
-
end
|
381
|
-
|
382
|
-
#
|
383
|
-
# gzip the string
|
384
|
-
#
|
385
|
-
def gzip(level=nil)
|
386
|
-
zipped = StringIO.new
|
387
|
-
Zlib::GzipWriter.wrap(zipped, level) { |io| io.write(self) }
|
388
|
-
zipped.string
|
389
|
-
end
|
390
|
-
|
391
|
-
#
|
392
|
-
# gunzip the string
|
393
|
-
#
|
394
|
-
def gunzip
|
395
|
-
data = StringIO.new(self)
|
396
|
-
Zlib::GzipReader.new(data).read
|
397
|
-
end
|
398
|
-
|
399
|
-
#
|
400
|
-
# deflate the string
|
401
|
-
#
|
402
|
-
def deflate(level=nil)
|
403
|
-
Zlib::Deflate.deflate(self, level)
|
404
|
-
end
|
405
|
-
|
406
|
-
#
|
407
|
-
# inflate the string
|
408
|
-
#
|
409
|
-
def inflate
|
410
|
-
Zlib::Inflate.inflate(self)
|
411
|
-
end
|
412
|
-
|
413
|
-
# `true` if this string starts with the substring
|
414
|
-
#
|
415
|
-
def startswith(substring)
|
416
|
-
self[0...substring.size] == substring
|
417
|
-
end
|
418
|
-
|
419
|
-
#
|
420
|
-
# `true` if this string ends with the substring
|
421
|
-
#
|
422
|
-
def endswith(substring)
|
423
|
-
self[-substring.size..-1] == substring
|
424
|
-
end
|
425
|
-
|
426
|
-
#
|
427
|
-
# Parse object as JSON
|
428
|
-
#
|
429
|
-
def from_json
|
430
|
-
JSON.parse self
|
431
|
-
end
|
432
|
-
|
433
|
-
#
|
434
|
-
# Convert the string to a Path object.
|
435
|
-
#
|
436
|
-
def as_path
|
437
|
-
Path[self]
|
438
|
-
end
|
439
|
-
alias_method :to_p, :as_path
|
440
|
-
|
441
|
-
def unmarshal
|
442
|
-
Marshal.restore self
|
443
|
-
end
|
444
|
-
|
445
|
-
end
|
446
|
-
|
447
|
-
|
448
|
-
class Array
|
449
|
-
|
450
|
-
#
|
451
|
-
# flatten.compact.uniq
|
452
|
-
#
|
453
|
-
def squash
|
454
|
-
flatten.compact.uniq
|
455
|
-
end
|
456
|
-
|
457
|
-
#
|
458
|
-
# Removes the elements from the array for which the block evaluates to true.
|
459
|
-
# In addition, return the removed elements.
|
460
|
-
#
|
461
|
-
# For example, if you wanted to split an array into evens and odds:
|
462
|
-
#
|
463
|
-
# nums = [1,2,3,4,5,6,7,8,9,10,11,12]
|
464
|
-
# even = nums.remove_if { |n| n.even? } # remove all even numbers from the "nums" array and return them
|
465
|
-
# odd = nums # "nums" now only contains odd numbers
|
466
|
-
#
|
467
|
-
def remove_if(&block)
|
468
|
-
removed = []
|
469
|
-
|
470
|
-
delete_if do |x|
|
471
|
-
if block.call(x)
|
472
|
-
removed << x
|
473
|
-
true
|
474
|
-
else
|
475
|
-
false
|
476
|
-
end
|
477
|
-
end
|
478
|
-
|
479
|
-
removed
|
480
|
-
end
|
481
|
-
|
482
|
-
#
|
483
|
-
# zip from the right (or reversed zip.)
|
484
|
-
#
|
485
|
-
# eg:
|
486
|
-
# >> [5,39].rzip([:hours, :mins, :secs])
|
487
|
-
# => [ [:mins, 5], [:secs, 39] ]
|
488
|
-
#
|
489
|
-
def rzip(other)
|
490
|
-
# That's a lotta reverses!
|
491
|
-
reverse.zip(other.reverse).reverse
|
492
|
-
end
|
493
|
-
|
494
|
-
#
|
495
|
-
# Pick the middle element.
|
496
|
-
#
|
497
|
-
def middle
|
498
|
-
self[(size-1) / 2]
|
499
|
-
end
|
500
|
-
|
501
|
-
#
|
502
|
-
# XOR operator
|
503
|
-
#
|
504
|
-
def ^(other)
|
505
|
-
(self | other) - (self & other)
|
506
|
-
end
|
507
|
-
|
508
|
-
#
|
509
|
-
# Pick a random element.
|
510
|
-
#
|
511
|
-
def pick
|
512
|
-
self[rand(size)]
|
513
|
-
end
|
514
|
-
|
515
|
-
#
|
516
|
-
# Divide the array into n pieces.
|
517
|
-
#
|
518
|
-
def / pieces
|
519
|
-
piece_size = (size.to_f / pieces).ceil
|
520
|
-
each_slice(piece_size).to_a
|
521
|
-
end
|
522
|
-
|
523
|
-
|
524
|
-
alias_method :unzip, :transpose
|
525
|
-
|
526
|
-
end
|
527
|
-
|
528
|
-
|
529
|
-
module Enumerable
|
530
|
-
|
531
|
-
#
|
532
|
-
# 'true' if the Enumerable has no elements
|
533
|
-
#
|
534
|
-
def blank?
|
535
|
-
not any?
|
536
|
-
end
|
537
|
-
|
538
|
-
#
|
539
|
-
# I enjoy typing ".all" more than ".to_a"
|
540
|
-
#
|
541
|
-
alias_method :all, :to_a
|
542
|
-
|
543
|
-
#
|
544
|
-
# Split this enumerable into chunks, given some boundary condition. (Returns an array of arrays.)
|
545
|
-
#
|
546
|
-
# Options:
|
547
|
-
# :include_boundary => true #=> include the element that you're splitting at in the results
|
548
|
-
# (default: false)
|
549
|
-
# :after => true #=> split after the matched element (only has an effect when used with :include_boundary)
|
550
|
-
# (default: false)
|
551
|
-
# :once => flase #=> only perform one split (default: false)
|
552
|
-
#
|
553
|
-
# Examples:
|
554
|
-
# [1,2,3,4,5].split{ |e| e == 3 }
|
555
|
-
# #=> [ [1,2], [4,5] ]
|
556
|
-
#
|
557
|
-
# [1,2,3,4,5].split(:include_boundary=>true) { |e| e == 3 }
|
558
|
-
# #=> [ [1,2], [3,4,5] ]
|
559
|
-
#
|
560
|
-
# chapters = File.read("ebook.txt").split(/Chapter \d+/, :include_boundary=>true)
|
561
|
-
# #=> [ ["Chapter 1", ...], ["Chapter 2", ...], etc. ]
|
562
|
-
#
|
563
|
-
def split_at(matcher=nil, options={}, &block)
|
564
|
-
# TODO: Ruby 1.9 returns Enumerators for everything now. Maybe use that?
|
565
|
-
|
566
|
-
return self unless self.any?
|
567
|
-
|
568
|
-
include_boundary = options[:include_boundary] || false
|
569
|
-
|
570
|
-
if matcher.nil?
|
571
|
-
boundary_test_proc = block
|
572
|
-
else
|
573
|
-
if matcher.is_a? String or matcher.is_a? Regexp
|
574
|
-
boundary_test_proc = proc { |element| element[matcher] rescue nil }
|
575
|
-
else
|
576
|
-
boundary_test_proc = proc { |element| element == matcher }
|
577
|
-
#raise "I don't know how to split with #{matcher}"
|
578
|
-
end
|
579
|
-
end
|
580
|
-
|
581
|
-
chunks = []
|
582
|
-
current_chunk = []
|
583
|
-
|
584
|
-
splits = 0
|
585
|
-
max_splits = options[:once] == true ? 1 : options[:max_splits]
|
586
|
-
|
587
|
-
each do |e|
|
588
|
-
|
589
|
-
if boundary_test_proc.call(e) and (max_splits == nil or splits < max_splits)
|
590
|
-
|
591
|
-
if current_chunk.empty? and not include_boundary
|
592
|
-
next # hit 2 boundaries in a row... just keep moving, people!
|
593
|
-
end
|
594
|
-
|
595
|
-
if options[:after]
|
596
|
-
# split after boundary
|
597
|
-
current_chunk << e if include_boundary # include the boundary, if necessary
|
598
|
-
chunks << current_chunk # shift everything after the boundary into the resultset
|
599
|
-
current_chunk = [] # start a new result
|
600
|
-
else
|
601
|
-
# split before boundary
|
602
|
-
chunks << current_chunk # shift before the boundary into the resultset
|
603
|
-
current_chunk = [] # start a new result
|
604
|
-
current_chunk << e if include_boundary # include the boundary, if necessary
|
605
|
-
end
|
606
|
-
|
607
|
-
splits += 1
|
608
|
-
|
609
|
-
else
|
610
|
-
current_chunk << e
|
611
|
-
end
|
612
|
-
|
613
|
-
end
|
614
|
-
|
615
|
-
chunks << current_chunk if current_chunk.any?
|
616
|
-
|
617
|
-
chunks # resultset
|
618
|
-
end
|
619
|
-
|
620
|
-
#
|
621
|
-
# Split the array into chunks, cutting between the matched element and the next element.
|
622
|
-
#
|
623
|
-
# Example:
|
624
|
-
# [1,2,3,4].split_after{|e| e == 3 } #=> [ [1,2,3], [4] ]
|
625
|
-
#
|
626
|
-
def split_after(matcher=nil, options={}, &block)
|
627
|
-
options[:after] ||= true
|
628
|
-
options[:include_boundary] ||= true
|
629
|
-
split_at(matcher, options, &block)
|
630
|
-
end
|
631
|
-
|
632
|
-
#
|
633
|
-
# Split the array into chunks, cutting between the matched element and the previous element.
|
634
|
-
#
|
635
|
-
# Example:
|
636
|
-
# [1,2,3,4].split_before{|e| e == 3 } #=> [ [1,2], [3,4] ]
|
637
|
-
#
|
638
|
-
def split_before(matcher=nil, options={}, &block)
|
639
|
-
options[:include_boundary] ||= true
|
640
|
-
split_at(matcher, options, &block)
|
641
|
-
end
|
642
|
-
|
643
|
-
#
|
644
|
-
# Sum the elements
|
645
|
-
#
|
646
|
-
def sum
|
647
|
-
if block_given?
|
648
|
-
inject(0) { |total,elem| total + yield(elem) }
|
649
|
-
else
|
650
|
-
inject(0) { |total,elem| total + elem }
|
651
|
-
end
|
652
|
-
end
|
653
|
-
|
654
|
-
#
|
655
|
-
# Average the elements
|
656
|
-
#
|
657
|
-
def average
|
658
|
-
count = 0
|
659
|
-
sum = inject(0) { |total,n| count += 1; total + n }
|
660
|
-
sum / count.to_f
|
661
|
-
end
|
662
|
-
|
663
|
-
#
|
664
|
-
# The same as "map", except that if an element is an Array or Enumerable, map is called
|
665
|
-
# recursively on that element.
|
666
|
-
#
|
667
|
-
# Example:
|
668
|
-
# [ [1,2], [3,4] ].map_recursively{|e| e ** 2 } #=> [ [1,4], [9,16] ]
|
669
|
-
#
|
670
|
-
def recursive_map(*args, &block)
|
671
|
-
map(*args) do |e|
|
672
|
-
if e.is_a? Array or e.is_a? Enumerable
|
673
|
-
e.map(*args, &block)
|
674
|
-
else
|
675
|
-
block.call(e)
|
676
|
-
end
|
677
|
-
end
|
678
|
-
end
|
679
|
-
|
680
|
-
alias_method :map_recursively, :recursive_map
|
681
|
-
alias_method :map_recursive, :recursive_map
|
682
|
-
|
683
|
-
|
684
|
-
#
|
685
|
-
# Identical to "reduce" in ruby1.9 (or foldl in haskell.)
|
686
|
-
#
|
687
|
-
# Example:
|
688
|
-
# array.foldl{|a,b| a + b } == array[1..-1].inject(array[0]){|a,b| a + b }
|
689
|
-
#
|
690
|
-
def foldl(methodname=nil, &block)
|
691
|
-
result = nil
|
692
|
-
|
693
|
-
raise "Error: pass a parameter OR a block, not both!" unless !!methodname ^ block_given?
|
694
|
-
|
695
|
-
if methodname
|
696
|
-
|
697
|
-
each_with_index do |e,i|
|
698
|
-
if i == 0
|
699
|
-
result = e
|
700
|
-
next
|
701
|
-
end
|
702
|
-
|
703
|
-
result = result.send(methodname, e)
|
704
|
-
end
|
705
|
-
|
706
|
-
else
|
707
|
-
|
708
|
-
each_with_index do |e,i|
|
709
|
-
if i == 0
|
710
|
-
result = e
|
711
|
-
next
|
712
|
-
end
|
713
|
-
|
714
|
-
result = block.call(result, e)
|
715
|
-
end
|
716
|
-
|
717
|
-
end
|
718
|
-
|
719
|
-
result
|
720
|
-
end
|
721
|
-
|
722
|
-
#
|
723
|
-
# Returns the powerset of the Enumerable
|
724
|
-
#
|
725
|
-
# Example:
|
726
|
-
# [1,2].powerset #=> [[], [1], [2], [1, 2]]
|
727
|
-
#
|
728
|
-
def powerset
|
729
|
-
# the bit pattern of the numbers from 0..2^(elements)-1 can be used to select the elements of the set...
|
730
|
-
a = to_a
|
731
|
-
(0...2**a.size).map do |bitmask|
|
732
|
-
a.select.with_index{ |e, i| bitmask[i] == 1 }
|
733
|
-
end
|
734
|
-
end
|
735
|
-
|
736
|
-
#
|
737
|
-
# Does the opposite of #zip -- converts [ [:a, 1], [:b, 2] ] to [ [:a, :b], [1, 2] ]
|
738
|
-
#
|
739
|
-
def unzip
|
740
|
-
# TODO: make it work for arrays containing uneven-length contents
|
741
|
-
to_a.transpose
|
742
|
-
end
|
743
|
-
|
744
|
-
#
|
745
|
-
# Associative grouping; groups all elements who share something in common with each other.
|
746
|
-
# You supply a block which takes two elements, and have it return true if they are "neighbours"
|
747
|
-
# (eg: belong in the same group).
|
748
|
-
#
|
749
|
-
# Example:
|
750
|
-
# [1,2,5,6].group_neighbours_by { |a,b| b-a <= 1 } #=> [ [1,2], [5,6] ]
|
751
|
-
#
|
752
|
-
# (Note: This is a very fast one-pass algorithm -- therefore, the groups must be pre-sorted.)
|
753
|
-
#
|
754
|
-
def group_neighbours_by(&block)
|
755
|
-
result = []
|
756
|
-
cluster = [first]
|
757
|
-
each_cons(2) do |a,b|
|
758
|
-
if yield(a,b)
|
759
|
-
cluster << b
|
760
|
-
else
|
761
|
-
result << cluster
|
762
|
-
cluster = [b]
|
763
|
-
end
|
764
|
-
end
|
765
|
-
|
766
|
-
result << cluster if cluster.any?
|
767
|
-
|
768
|
-
result
|
769
|
-
end
|
770
|
-
alias_method :group_neighbors_by, :group_neighbours_by
|
771
|
-
|
772
|
-
end
|
773
|
-
|
774
|
-
|
775
|
-
class Object
|
776
|
-
|
777
|
-
#
|
778
|
-
# Gives you a copy of the object with its attributes changed to whatever was
|
779
|
-
# passed in the options hash.
|
780
|
-
#
|
781
|
-
# Example:
|
782
|
-
# >> cookie = Cookie.new(:size=>10, :chips=>200)
|
783
|
-
# => #<Cookie:0xffffffe @size=10, @chips=200>
|
784
|
-
# >> cookie.with(:chips=>50)
|
785
|
-
# => #<Cookie:0xfffffff @size=10, @chips=50>
|
786
|
-
#
|
787
|
-
# (All this method does is dup the object, then call "key=(value)" for each
|
788
|
-
# key/value in the options hash.)
|
789
|
-
#
|
790
|
-
def with(options={})
|
791
|
-
obj = dup
|
792
|
-
options.each { |key, value| obj.send "#{key}=", value }
|
793
|
-
obj
|
794
|
-
end
|
795
|
-
|
796
|
-
|
797
|
-
#
|
798
|
-
# Return a copy of the class with modules mixed into it.
|
799
|
-
#
|
800
|
-
def self.using(*args)
|
801
|
-
if block_given?
|
802
|
-
yield using(*args)
|
803
|
-
else
|
804
|
-
copy = self.dup
|
805
|
-
args.each { |arg| copy.send(:include, arg) }
|
806
|
-
copy
|
807
|
-
end
|
808
|
-
end
|
809
|
-
|
810
|
-
|
811
|
-
#
|
812
|
-
# Instead of:
|
813
|
-
# if cookie_jar.include? cookie
|
814
|
-
# Now you can do:
|
815
|
-
# if cookie.in? cookie_jar
|
816
|
-
#
|
817
|
-
def in?(enumerable)
|
818
|
-
enumerable.include? self
|
819
|
-
end
|
820
|
-
|
821
|
-
#
|
822
|
-
# Instead of:
|
823
|
-
# @person ? @person.name : nil
|
824
|
-
# Now you can do:
|
825
|
-
# @person.try(:name)
|
826
|
-
#
|
827
|
-
def try(method, *args, &block)
|
828
|
-
send(method, *args, &block) if respond_to? method
|
829
|
-
end
|
830
|
-
|
831
|
-
#
|
832
|
-
# Benchmark a block!
|
833
|
-
#
|
834
|
-
def bench(message=nil)
|
835
|
-
start = Time.now
|
836
|
-
result = yield
|
837
|
-
elapsed = Time.now - start
|
838
|
-
|
839
|
-
print "[#{message}] " if message
|
840
|
-
puts "elapsed time: %0.5fs" % elapsed
|
841
|
-
result
|
842
|
-
end
|
843
|
-
alias time bench
|
844
|
-
|
845
|
-
|
846
|
-
#
|
847
|
-
# A decorator that makes any block-accepting method return an
|
848
|
-
# Enumerator whenever the method is called without a block.
|
849
|
-
#
|
850
|
-
def self.enumerable *meths
|
851
|
-
meths.each do |meth|
|
852
|
-
alias_method "#{meth}_without_enumerator", meth
|
853
|
-
class_eval %{
|
854
|
-
def #{meth}(*args, &block)
|
855
|
-
return Enum.new(self, #{meth.inspect}, *args, &block) unless block_given?
|
856
|
-
#{meth}_without_enumerator(*args, &block)
|
857
|
-
end
|
858
|
-
}
|
859
|
-
end
|
860
|
-
end
|
861
|
-
|
862
|
-
end
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
class Hash
|
867
|
-
|
868
|
-
#
|
869
|
-
# 'true' if the Hash has no entries
|
870
|
-
#
|
871
|
-
def blank?
|
872
|
-
not any?
|
873
|
-
end
|
874
|
-
|
875
|
-
#
|
876
|
-
# Runs "remove_blank_values" on self.
|
877
|
-
#
|
878
|
-
def remove_blank_values!
|
879
|
-
delete_if{|k,v| v.blank?}
|
880
|
-
self
|
881
|
-
end
|
882
|
-
|
883
|
-
#
|
884
|
-
# Returns a new Hash where blank values have been removed.
|
885
|
-
# (It checks if the value is blank by calling #blank? on it)
|
886
|
-
#
|
887
|
-
def remove_blank_values
|
888
|
-
dup.remove_blank_values!
|
889
|
-
end
|
890
|
-
|
891
|
-
#
|
892
|
-
# Runs map_values on self.
|
893
|
-
#
|
894
|
-
def map_values!(&block)
|
895
|
-
keys.each do |key|
|
896
|
-
value = self[key]
|
897
|
-
self[key] = yield(value)
|
898
|
-
end
|
899
|
-
self
|
900
|
-
end
|
901
|
-
|
902
|
-
#
|
903
|
-
# Transforms the values of the hash by passing them into the supplied
|
904
|
-
# block, and then using the block's result as the new value.
|
905
|
-
#
|
906
|
-
def map_values(&block)
|
907
|
-
dup.map_values!(&block)
|
908
|
-
end
|
909
|
-
|
910
|
-
#
|
911
|
-
# Runs map_keys on self.
|
912
|
-
#
|
913
|
-
def map_keys!(&block)
|
914
|
-
keys.each do |key|
|
915
|
-
value = delete(key)
|
916
|
-
self[yield(key)] = value
|
917
|
-
end
|
918
|
-
self
|
919
|
-
end
|
920
|
-
|
921
|
-
#
|
922
|
-
# Transforms the keys of the hash by passing them into the supplied block,
|
923
|
-
# and then using the blocks result as the new key.
|
924
|
-
#
|
925
|
-
def map_keys(&block)
|
926
|
-
dup.map_keys!(&block)
|
927
|
-
end
|
928
|
-
|
929
|
-
#
|
930
|
-
# Returns a new Hash whose values default to empty arrays. (Good for collecting things!)
|
931
|
-
#
|
932
|
-
# eg:
|
933
|
-
# Hash.of_arrays[:yays] << "YAY!"
|
934
|
-
#
|
935
|
-
def self.of_arrays
|
936
|
-
new {|h,k| h[k] = [] }
|
937
|
-
end
|
938
|
-
|
939
|
-
#
|
940
|
-
# Returns a new Hash whose values default to 0. (Good for counting things!)
|
941
|
-
#
|
942
|
-
# eg:
|
943
|
-
# Hash.of_integers[:yays] += 1
|
944
|
-
#
|
945
|
-
def self.of_integers
|
946
|
-
new(0)
|
947
|
-
end
|
948
|
-
|
949
|
-
#
|
950
|
-
# Makes each element in the `path` array point to a hash containing the next element in the `path`.
|
951
|
-
# Useful for turning a bunch of strings (paths, module names, etc.) into a tree.
|
952
|
-
#
|
953
|
-
# Example:
|
954
|
-
# h = {}
|
955
|
-
# h.mkdir_p(["a", "b", "c"]) #=> {"a"=>{"b"=>{"c"=>{}}}}
|
956
|
-
# h.mkdir_p(["a", "b", "whoa"]) #=> {"a"=>{"b"=>{"c"=>{}, "whoa"=>{}}}}
|
957
|
-
#
|
958
|
-
def mkdir_p(path)
|
959
|
-
return if path.empty?
|
960
|
-
dir = path.first
|
961
|
-
self[dir] ||= {}
|
962
|
-
self[dir].mkdir_p(path[1..-1])
|
963
|
-
self
|
964
|
-
end
|
965
|
-
|
966
|
-
#
|
967
|
-
# Turn some nested hashes into a tree (returns an array of strings, padded on the left with indents.)
|
968
|
-
#
|
969
|
-
def tree(level=0, indent=" ")
|
970
|
-
result = []
|
971
|
-
dent = indent * level
|
972
|
-
each do |key, val|
|
973
|
-
result << dent+key
|
974
|
-
result += val.tree(level+1) if val.any?
|
975
|
-
end
|
976
|
-
result
|
977
|
-
end
|
978
|
-
|
979
|
-
#
|
980
|
-
# Print the result of `tree`
|
981
|
-
#
|
982
|
-
def print_tree
|
983
|
-
tree.each { |row| puts row }
|
984
|
-
nil
|
985
|
-
end
|
986
|
-
|
987
|
-
#
|
988
|
-
# Convert the hash into a GET query.
|
989
|
-
#
|
990
|
-
def to_query
|
991
|
-
params = ''
|
992
|
-
stack = []
|
993
|
-
|
994
|
-
each do |k, v|
|
995
|
-
if v.is_a?(Hash)
|
996
|
-
stack << [k,v]
|
997
|
-
else
|
998
|
-
params << "#{k}=#{v}&"
|
999
|
-
end
|
1000
|
-
end
|
1001
|
-
|
1002
|
-
stack.each do |parent, hash|
|
1003
|
-
hash.each do |k, v|
|
1004
|
-
if v.is_a?(Hash)
|
1005
|
-
stack << ["#{parent}[#{k}]", v]
|
1006
|
-
else
|
1007
|
-
params << "#{parent}[#{k}]=#{v}&"
|
1008
|
-
end
|
1009
|
-
end
|
1010
|
-
end
|
1011
|
-
|
1012
|
-
params.chop! # trailing &
|
1013
|
-
params
|
1014
|
-
end
|
1015
|
-
|
1016
|
-
end
|
1017
|
-
|
1018
|
-
unless defined?(BasicObject)
|
1019
|
-
#
|
1020
|
-
# A BasicObject class for Ruby 1.8
|
1021
|
-
#
|
1022
|
-
class BasicObject
|
1023
|
-
instance_methods.each { |m| undef_method m unless m =~ /^__/ }
|
1024
|
-
end
|
1025
|
-
end
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
class NotWrapper < BasicObject # :nodoc:
|
1030
|
-
def initialize(orig)
|
1031
|
-
@orig = orig
|
1032
|
-
end
|
1033
|
-
|
1034
|
-
def inspect
|
1035
|
-
"{NOT #{@orig.inspect}}"
|
1036
|
-
end
|
1037
|
-
|
1038
|
-
def method_missing(meth, *args, &block)
|
1039
|
-
result = @orig.send(meth, *args, &block)
|
1040
|
-
if result.is_a? ::TrueClass or result.is_a? ::FalseClass
|
1041
|
-
!result
|
1042
|
-
else
|
1043
|
-
raise "Sorry, I don't know how to invert #{result.inspect}"
|
1044
|
-
end
|
1045
|
-
end
|
1046
|
-
end
|
1047
|
-
|
1048
|
-
class Object
|
1049
|
-
|
1050
|
-
#
|
1051
|
-
# Negates a boolean, chained-method style.
|
1052
|
-
#
|
1053
|
-
# Example:
|
1054
|
-
# >> 10.even?
|
1055
|
-
# => true
|
1056
|
-
# >> 10.not.even?
|
1057
|
-
# => false
|
1058
|
-
#
|
1059
|
-
def not
|
1060
|
-
NotWrapper.new(self)
|
1061
|
-
end
|
1062
|
-
|
1063
|
-
end
|
1064
|
-
|
1065
|
-
# Metaclass
|
1066
|
-
class Object
|
1067
|
-
# The hidden singleton lurks behind everyone
|
1068
|
-
def metaclass
|
1069
|
-
class << self
|
1070
|
-
self
|
1071
|
-
end
|
1072
|
-
end
|
1073
|
-
|
1074
|
-
def meta_eval &blk
|
1075
|
-
metaclass.instance_eval &blk
|
1076
|
-
end
|
1077
|
-
|
1078
|
-
# Adds methods to a metaclass
|
1079
|
-
def meta_def name, &blk
|
1080
|
-
meta_eval { define_method name, &blk }
|
1081
|
-
end
|
1082
|
-
|
1083
|
-
# Defines an instance method within a class
|
1084
|
-
def class_def name, &blk
|
1085
|
-
class_eval { define_method name, &blk }
|
1086
|
-
end
|
1087
|
-
end
|
1088
|
-
|
1089
|
-
unless IO.respond_to? :copy_stream
|
1090
|
-
|
1091
|
-
class IO
|
1092
|
-
|
1093
|
-
def self.copy_stream(input, output)
|
1094
|
-
while chunk = input.read(8192)
|
1095
|
-
output.write(chunk)
|
1096
|
-
end
|
1097
|
-
end
|
1098
|
-
|
1099
|
-
end
|
1100
|
-
|
1101
|
-
end
|
1102
|
-
|
1103
|
-
#
|
1104
|
-
# Emit a quick debug message (only if $DEBUG is true)
|
1105
|
-
#
|
1106
|
-
def dmsg(msg)
|
1107
|
-
if $DEBUG
|
1108
|
-
case msg
|
1109
|
-
when String
|
1110
|
-
puts msg
|
1111
|
-
else
|
1112
|
-
puts msg.inspect
|
1113
|
-
end
|
1114
|
-
end
|
1115
|
-
end
|
1116
|
-
|
1117
|
-
|
1118
|
-
def del(x)
|
1119
|
-
case thing
|
1120
|
-
when String
|
1121
|
-
del(x.to_sym)
|
1122
|
-
when Class, Module
|
1123
|
-
Object.send(:remove_const, x)
|
1124
|
-
when Method
|
1125
|
-
x.owner.send(:undef_method, x.name)
|
1126
|
-
when Symbol
|
1127
|
-
if Object.const_get(x)
|
1128
|
-
Object.send(:remove_const, x)
|
1129
|
-
elsif method(x)
|
1130
|
-
undef_method x
|
1131
|
-
end
|
1132
|
-
else
|
1133
|
-
raise "Error: don't know how to 'del #{x.inspect}'"
|
1134
|
-
end
|
1135
|
-
end
|