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,254 @@
|
|
1
|
+
class Numeric
|
2
|
+
|
3
|
+
def integer?; true; end
|
4
|
+
|
5
|
+
def truthy?; self > 0; end
|
6
|
+
|
7
|
+
def commatize
|
8
|
+
to_s.gsub(/(\d)(?=\d{3}+(?:\.|$))(\d{3}\..*)?/,'\1,\2')
|
9
|
+
end
|
10
|
+
|
11
|
+
#
|
12
|
+
# Time methods
|
13
|
+
#
|
14
|
+
{
|
15
|
+
|
16
|
+
'second' => 1,
|
17
|
+
'minute' => 60,
|
18
|
+
'hour' => 60 * 60,
|
19
|
+
'day' => 60 * 60 * 24,
|
20
|
+
'week' => 60 * 60 * 24 * 7,
|
21
|
+
'month' => 60 * 60 * 24 * 30,
|
22
|
+
'year' => 60 * 60 * 24 * 365,
|
23
|
+
|
24
|
+
}.each do |unit, scale|
|
25
|
+
define_method(unit) { self * scale }
|
26
|
+
define_method(unit+'s') { self * scale }
|
27
|
+
end
|
28
|
+
|
29
|
+
def ago
|
30
|
+
Time.now - self
|
31
|
+
end
|
32
|
+
|
33
|
+
def from_now
|
34
|
+
Time.now + self
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# If `n.times` is like `each`, `n.things` is like `map`. Return
|
39
|
+
#
|
40
|
+
def things(&block)
|
41
|
+
if block_given?
|
42
|
+
Array.new(self, &block)
|
43
|
+
else
|
44
|
+
Array.new(self)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
[:cos,
|
49
|
+
:sin,
|
50
|
+
:tan,
|
51
|
+
:acos,
|
52
|
+
:asin,
|
53
|
+
:atan,
|
54
|
+
:cosh,
|
55
|
+
:sinh,
|
56
|
+
:tanh,
|
57
|
+
:acosh,
|
58
|
+
:asinh,
|
59
|
+
:atanh,
|
60
|
+
:exp,
|
61
|
+
:log2,
|
62
|
+
:log10,
|
63
|
+
:sqrt,
|
64
|
+
:cbrt,
|
65
|
+
:frexp,
|
66
|
+
:erf,
|
67
|
+
:erfc,
|
68
|
+
:gamma,
|
69
|
+
:lgamma
|
70
|
+
].each do |meth|
|
71
|
+
|
72
|
+
class_eval %{
|
73
|
+
def #{meth}
|
74
|
+
Math.#{meth}(self)
|
75
|
+
end
|
76
|
+
}
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
def log(n)
|
81
|
+
Math.log(self, n)
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
class Integer
|
88
|
+
|
89
|
+
#
|
90
|
+
# 'true' if the integer is 0
|
91
|
+
#
|
92
|
+
def blank?; self == 0; end
|
93
|
+
|
94
|
+
#
|
95
|
+
# Convert the number into a hexadecimal string representation.
|
96
|
+
# (Identical to to_s(16), except that numbers < 16 will have a 0 in front of them.)
|
97
|
+
#
|
98
|
+
def to_hex
|
99
|
+
"%0.2x" % self
|
100
|
+
end
|
101
|
+
|
102
|
+
#
|
103
|
+
# Convert the number to an array of bits (least significant digit first, or little-endian).
|
104
|
+
#
|
105
|
+
def to_bits
|
106
|
+
# TODO: Why does thos go into an infinite loop in 1.8.7?
|
107
|
+
("%b" % self).chars.to_a.reverse.map(&:to_i)
|
108
|
+
end
|
109
|
+
alias_method :bits, :to_bits
|
110
|
+
|
111
|
+
#
|
112
|
+
# Cached constants for base62 encoding
|
113
|
+
#
|
114
|
+
BASE62_DIGITS = ['0'..'9', 'A'..'Z', 'a'..'z'].map(&:to_a).flatten
|
115
|
+
BASE62_BASE = BASE62_DIGITS.size
|
116
|
+
|
117
|
+
#
|
118
|
+
# Convert a number to a string representation (in "base62" encoding).
|
119
|
+
#
|
120
|
+
# Base62 encoding represents the number using the characters: 0..9, A..Z, a..z
|
121
|
+
#
|
122
|
+
# It's the same scheme that url shorteners and YouTube uses for their
|
123
|
+
# ID strings. (eg: http://www.youtube.com/watch?v=dQw4w9WgXcQ)
|
124
|
+
#
|
125
|
+
def to_base62
|
126
|
+
result = []
|
127
|
+
remainder = self
|
128
|
+
max_power = ( Math.log(self) / Math.log(BASE62_BASE) ).floor
|
129
|
+
|
130
|
+
max_power.downto(0) do |power|
|
131
|
+
divisor = BASE62_BASE**power
|
132
|
+
#p [:div, divisor, :rem, remainder]
|
133
|
+
digit, remainder = remainder.divmod(divisor)
|
134
|
+
result << digit
|
135
|
+
end
|
136
|
+
|
137
|
+
result << remainder if remainder > 0
|
138
|
+
|
139
|
+
result.map{|digit| BASE62_DIGITS[digit]}.join ''
|
140
|
+
end
|
141
|
+
|
142
|
+
#
|
143
|
+
# Returns the all the prime factors of a number.
|
144
|
+
#
|
145
|
+
def factors
|
146
|
+
Prime # autoload the prime module
|
147
|
+
prime_division.map { |n,count| [n]*count }.flatten
|
148
|
+
end
|
149
|
+
|
150
|
+
#
|
151
|
+
# Factorial (iterated style)
|
152
|
+
#
|
153
|
+
def fact
|
154
|
+
total = 1
|
155
|
+
self.downto(2) { |x| total *= x }
|
156
|
+
total
|
157
|
+
end
|
158
|
+
alias_method :factorial, :fact
|
159
|
+
|
160
|
+
#
|
161
|
+
# Fibonacci (recursive style)
|
162
|
+
#
|
163
|
+
def fib
|
164
|
+
self < 2 ? self : (self-1).fib + (self-2).fib
|
165
|
+
end
|
166
|
+
alias_method :fibonacci, :fib
|
167
|
+
|
168
|
+
#
|
169
|
+
# Convert seconds to hours:minutes:seconds (hours is dropped if it's zero)
|
170
|
+
#
|
171
|
+
def to_hms
|
172
|
+
seconds = self
|
173
|
+
|
174
|
+
days, seconds = seconds.divmod(86400)
|
175
|
+
hours, seconds = seconds.divmod(3600)
|
176
|
+
minutes, seconds = seconds.divmod(60)
|
177
|
+
|
178
|
+
result = "%0.2d:%0.2d" % [minutes,seconds]
|
179
|
+
result = ("%0.2d:" % hours) + result if hours > 0 or days > 0
|
180
|
+
result = ("%0.2d:" % days) + result if days > 0
|
181
|
+
|
182
|
+
result
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
|
187
|
+
#
|
188
|
+
# Monkeypatch [] into Bignum and Fixnum using class_eval.
|
189
|
+
#
|
190
|
+
# (This is necessary because [] is defined directly on the classes, and a mixin
|
191
|
+
# module will still be overridden by Big/Fixnum's native [] method.)
|
192
|
+
#
|
193
|
+
[Bignum, Fixnum].each do |klass|
|
194
|
+
|
195
|
+
klass.class_eval do
|
196
|
+
|
197
|
+
alias_method :bit, :"[]"
|
198
|
+
|
199
|
+
#
|
200
|
+
# Extends [] so that Integers can be sliced as if they were arrays.
|
201
|
+
#
|
202
|
+
def [](arg)
|
203
|
+
case arg
|
204
|
+
when Integer
|
205
|
+
self.bit(arg)
|
206
|
+
when Range
|
207
|
+
self.bits[arg]
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
|
215
|
+
class Time
|
216
|
+
|
217
|
+
#
|
218
|
+
# Relative time, in words. (eg: "1 second ago", "2 weeks from now", etc.)
|
219
|
+
#
|
220
|
+
def in_words
|
221
|
+
delta = (Time.now-self).to_i
|
222
|
+
a = delta.abs
|
223
|
+
|
224
|
+
amount = case a
|
225
|
+
when 0
|
226
|
+
'just now'
|
227
|
+
when 1
|
228
|
+
'1 second'
|
229
|
+
when 2..59
|
230
|
+
"second".amount(a)
|
231
|
+
when 1.minute...1.hour
|
232
|
+
"minute".amount(a/1.minute)
|
233
|
+
when 1.hour...1.day
|
234
|
+
"hour".amount(a/1.hour)
|
235
|
+
when 1.day...7.days
|
236
|
+
"day".amount(a/1.day)
|
237
|
+
when 1.week...1.month
|
238
|
+
"week".amount(a/1.week)
|
239
|
+
when 1.month...12.months
|
240
|
+
"month".amount(a/1.month)
|
241
|
+
else
|
242
|
+
"year".amount(a/1.year)
|
243
|
+
end
|
244
|
+
|
245
|
+
if delta < 0
|
246
|
+
amount += " from now"
|
247
|
+
elsif delta > 0
|
248
|
+
amount += " ago"
|
249
|
+
end
|
250
|
+
|
251
|
+
amount
|
252
|
+
end
|
253
|
+
|
254
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
class Object
|
2
|
+
|
3
|
+
#
|
4
|
+
# Return a hash of local variables in the caller's scope: {:variable_name=>value}
|
5
|
+
#
|
6
|
+
def locals
|
7
|
+
require 'binding_of_caller'
|
8
|
+
caller = binding.of_caller(1)
|
9
|
+
vars = caller.eval("local_variables").reject{|e| e[/^_/]}
|
10
|
+
vals = caller.eval("[ #{vars.join(",")} ]")
|
11
|
+
Hash[ vars.zip(vals) ]
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
#
|
16
|
+
# Gives you a copy of the object with its attributes changed to whatever was
|
17
|
+
# passed in the options hash.
|
18
|
+
#
|
19
|
+
# Example:
|
20
|
+
# >> cookie = Cookie.new(:size=>10, :chips=>200)
|
21
|
+
# => #<Cookie:0xffffffe @size=10, @chips=200>
|
22
|
+
# >> cookie.with(:chips=>50)
|
23
|
+
# => #<Cookie:0xfffffff @size=10, @chips=50>
|
24
|
+
#
|
25
|
+
# (All this method does is dup the object, then call "key=(value)" for each
|
26
|
+
# key/value in the options hash.)
|
27
|
+
#
|
28
|
+
def with(options={})
|
29
|
+
obj = dup
|
30
|
+
options.each { |key, value| obj.send "#{key}=", value }
|
31
|
+
obj
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# Return a copy of the class with modules mixed into it.
|
36
|
+
#
|
37
|
+
def self.using(*args)
|
38
|
+
if block_given?
|
39
|
+
yield using(*args)
|
40
|
+
else
|
41
|
+
copy = self.dup
|
42
|
+
args.each { |arg| copy.send(:include, arg) }
|
43
|
+
copy
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Turns block-accepting iterator methods (eg: each) into methods that return an
|
49
|
+
# Enumerator when they're called called without a block.
|
50
|
+
#
|
51
|
+
# It can transform many methods at once (eg: `enumerable :each, :all_people`).
|
52
|
+
#
|
53
|
+
# Example:
|
54
|
+
#
|
55
|
+
# def lots_of_stuff
|
56
|
+
# @stuff.each { |thing| yield thing }
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# enumerable :lots_of_stuff
|
60
|
+
#
|
61
|
+
# Now you can use it like an Enumerator: object.lots_of_stuff.with_index.sort.zip(99..1000)
|
62
|
+
#
|
63
|
+
def self.enumerable *meths
|
64
|
+
meths.each do |meth|
|
65
|
+
alias_method "#{meth}_without_enumerator", meth
|
66
|
+
class_eval %{
|
67
|
+
def #{meth}(*args, &block)
|
68
|
+
return Enum.new(self, #{meth.inspect}, *args, &block) unless block_given?
|
69
|
+
#{meth}_without_enumerator(*args, &block)
|
70
|
+
end
|
71
|
+
}
|
72
|
+
end
|
73
|
+
end
|
74
|
+
alias_class_method :enumerator, :enumerable
|
75
|
+
|
76
|
+
#
|
77
|
+
# Instead of:
|
78
|
+
# if cookie_jar.include? cookie
|
79
|
+
# Now you can do:
|
80
|
+
# if cookie.in? cookie_jar
|
81
|
+
#
|
82
|
+
def in?(enum)
|
83
|
+
enum.include? self
|
84
|
+
end
|
85
|
+
|
86
|
+
#
|
87
|
+
# Instead of:
|
88
|
+
# @person ? @person.name : nil
|
89
|
+
# Now you can do:
|
90
|
+
# @person.try(:name)
|
91
|
+
#
|
92
|
+
def try(method, *args, &block)
|
93
|
+
send(method, *args, &block) if respond_to? method
|
94
|
+
end
|
95
|
+
|
96
|
+
#
|
97
|
+
# Serialize this object to a binary String, using Marshal.dump.
|
98
|
+
#
|
99
|
+
def marshal
|
100
|
+
Marshal.dump self
|
101
|
+
end
|
102
|
+
alias_method :to_marshal, :marshal
|
103
|
+
|
104
|
+
#
|
105
|
+
# Serialize this object to YAML.
|
106
|
+
#
|
107
|
+
def to_yaml
|
108
|
+
YAML::dump(self)
|
109
|
+
end
|
110
|
+
|
111
|
+
#
|
112
|
+
# Serialize this object to JSON (defaults to "pretty" indented JSON).
|
113
|
+
#
|
114
|
+
def to_json(pretty=true)
|
115
|
+
pretty ? JSON::pretty_generate(self) : JSON.dump(self)
|
116
|
+
end
|
117
|
+
|
118
|
+
#
|
119
|
+
# Proper grammar.
|
120
|
+
#
|
121
|
+
alias_method :is_an?, :is_a?
|
122
|
+
alias_method :responds_to?, :respond_to?
|
123
|
+
|
124
|
+
#
|
125
|
+
# Emit a quick debug message (only if $DEBUG is true)
|
126
|
+
#
|
127
|
+
def dmsg(msg)
|
128
|
+
if $DEBUG
|
129
|
+
case msg
|
130
|
+
when String
|
131
|
+
puts msg
|
132
|
+
else
|
133
|
+
puts msg.inspect
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
#
|
139
|
+
# Time a block.
|
140
|
+
#
|
141
|
+
def time(message=nil)
|
142
|
+
start = Time.now
|
143
|
+
result = yield
|
144
|
+
elapsed = Time.now - start
|
145
|
+
|
146
|
+
print "[#{message}] " if message
|
147
|
+
puts "elapsed time: %0.5fs" % elapsed
|
148
|
+
result
|
149
|
+
end
|
150
|
+
|
151
|
+
#
|
152
|
+
# Quick and easy benchmark.
|
153
|
+
#
|
154
|
+
# Examples:
|
155
|
+
# bench { something }
|
156
|
+
# bench(90000000) { something }
|
157
|
+
# bench :fast => proc { fast_method }, :slow => proc { slow_method }
|
158
|
+
#
|
159
|
+
# In Ruby 1.9:
|
160
|
+
# bench fast: ->{ fast_method }, slow: ->{ slow_method }
|
161
|
+
#
|
162
|
+
def bench(*args, &block)
|
163
|
+
|
164
|
+
# Shitty perl-esque hack to let the method take all the different kind of arguments.
|
165
|
+
opts = Hash === args.last ? args.pop : {}
|
166
|
+
n = args.first || 100
|
167
|
+
|
168
|
+
if opts.any?
|
169
|
+
|
170
|
+
raise "Error: Supply either a do/end block, or procs as options. Not both." if block_given?
|
171
|
+
raise "Error: Options must be procs." unless opts.all? { |k, v| v.is_a?(Proc) }
|
172
|
+
|
173
|
+
benchblock = proc do |bm|
|
174
|
+
opts.each do |name, blk|
|
175
|
+
bm.report(name.to_s) { n.times &blk }
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
elsif block_given?
|
180
|
+
|
181
|
+
benchblock = proc do |bm|
|
182
|
+
bm.report { n.times &block }
|
183
|
+
end
|
184
|
+
|
185
|
+
else
|
186
|
+
raise "Error: Must supply some code to benchmark."
|
187
|
+
end
|
188
|
+
|
189
|
+
puts "* Benchmarking #{n} iterations..."
|
190
|
+
Benchmark.bmbm(&benchblock)
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
|
195
|
+
|
@@ -0,0 +1,338 @@
|
|
1
|
+
require 'epitools/core_ext/numbers'
|
2
|
+
|
3
|
+
class String
|
4
|
+
|
5
|
+
#
|
6
|
+
# Could this string be cast to an integer?
|
7
|
+
#
|
8
|
+
def integer?
|
9
|
+
strip.match(/^\d+$/) ? true : false
|
10
|
+
end
|
11
|
+
|
12
|
+
#
|
13
|
+
# 'true' if the string's length is 0 (after whitespace has been stripped from the ends)
|
14
|
+
#
|
15
|
+
def blank?
|
16
|
+
strip.size == 0
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
# Does this string contain something that means roughly "true"?
|
21
|
+
#
|
22
|
+
def truthy?
|
23
|
+
case strip.downcase
|
24
|
+
when "1", "true", "yes", "on", "enabled", "affirmative"
|
25
|
+
true
|
26
|
+
else
|
27
|
+
false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# Convert \r\n to \n
|
33
|
+
#
|
34
|
+
def to_unix
|
35
|
+
gsub("\r\n", "\n")
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# Remove redundant whitespaces (not including newlines).
|
40
|
+
#
|
41
|
+
def tighten
|
42
|
+
gsub(/[\t ]+/,' ').strip
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# Remove redundant whitespace AND newlines.
|
47
|
+
#
|
48
|
+
def dewhitespace
|
49
|
+
gsub(/\s+/,' ').strip
|
50
|
+
end
|
51
|
+
|
52
|
+
#
|
53
|
+
# Remove ANSI color codes.
|
54
|
+
#
|
55
|
+
def strip_color
|
56
|
+
gsub(/\e\[.*?(\d)+m/, '')
|
57
|
+
end
|
58
|
+
alias_method :strip_ansi, :strip_color
|
59
|
+
|
60
|
+
#
|
61
|
+
# Like #lines, but skips empty lines and removes \n's.
|
62
|
+
#
|
63
|
+
def nice_lines
|
64
|
+
# note: $/ is the platform's newline separator
|
65
|
+
split($/).select{|l| not l.blank? }
|
66
|
+
end
|
67
|
+
|
68
|
+
alias_method :nicelines, :nice_lines
|
69
|
+
alias_method :clean_lines, :nice_lines
|
70
|
+
|
71
|
+
#
|
72
|
+
# The Infamous Caesar-Cipher. Unbreakable to this day.
|
73
|
+
#
|
74
|
+
def rot13
|
75
|
+
tr('n-za-mN-ZA-M', 'a-zA-Z')
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
# Convert non-URI characters into %XXes.
|
80
|
+
#
|
81
|
+
def urlencode
|
82
|
+
URI.escape(self)
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# Convert an URI's %XXes into regular characters.
|
87
|
+
#
|
88
|
+
def urldecode
|
89
|
+
URI.unescape(self)
|
90
|
+
end
|
91
|
+
|
92
|
+
#
|
93
|
+
# Convert a query string to a hash of params
|
94
|
+
#
|
95
|
+
def to_params
|
96
|
+
CGI.parse(self).map_values do |v|
|
97
|
+
# CGI.parse wraps every value in an array. Unwrap them!
|
98
|
+
if v.is_a?(Array) and v.size == 1
|
99
|
+
v.first
|
100
|
+
else
|
101
|
+
v
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
#
|
108
|
+
# Cached constants for base62 decoding.
|
109
|
+
#
|
110
|
+
BASE62_DIGITS = Hash[ Integer::BASE62_DIGITS.zip((0...Integer::BASE62_DIGITS.size).to_a) ]
|
111
|
+
BASE62_BASE = Integer::BASE62_BASE
|
112
|
+
|
113
|
+
#
|
114
|
+
# Convert a string (encoded in base16 "hex" -- for example, an MD5 or SHA1 hash)
|
115
|
+
# into "base62" format. (See Integer#to_base62 for more info.)
|
116
|
+
#
|
117
|
+
def to_base62
|
118
|
+
to_i(16).to_base62
|
119
|
+
end
|
120
|
+
|
121
|
+
#
|
122
|
+
# Convert a string encoded in base62 into an integer.
|
123
|
+
# (See Integer#to_base62 for more info.)
|
124
|
+
#
|
125
|
+
def from_base62
|
126
|
+
accumulator = 0
|
127
|
+
digits = chars.map { |c| BASE62_DIGITS[c] }.reverse
|
128
|
+
digits.each_with_index do |digit, power|
|
129
|
+
accumulator += (BASE62_BASE**power) * digit if digit > 0
|
130
|
+
end
|
131
|
+
accumulator
|
132
|
+
end
|
133
|
+
|
134
|
+
#
|
135
|
+
# Decode a mime64/base64 encoded string
|
136
|
+
#
|
137
|
+
def from_base64
|
138
|
+
Base64.decode64 self
|
139
|
+
end
|
140
|
+
alias_method :decode64, :from_base64
|
141
|
+
|
142
|
+
#
|
143
|
+
# Encode into a mime64/base64 string
|
144
|
+
#
|
145
|
+
def to_base64
|
146
|
+
Base64.encode64 self
|
147
|
+
end
|
148
|
+
alias_method :base64, :to_base64
|
149
|
+
alias_method :encode64, :to_base64
|
150
|
+
|
151
|
+
#
|
152
|
+
# MD5 the string
|
153
|
+
#
|
154
|
+
def md5
|
155
|
+
Digest::MD5.hexdigest self
|
156
|
+
end
|
157
|
+
|
158
|
+
#
|
159
|
+
# SHA1 the string
|
160
|
+
#
|
161
|
+
def sha1
|
162
|
+
Digest::SHA1.hexdigest self
|
163
|
+
end
|
164
|
+
|
165
|
+
#
|
166
|
+
# gzip the string
|
167
|
+
#
|
168
|
+
def gzip(level=nil)
|
169
|
+
zipped = StringIO.new
|
170
|
+
Zlib::GzipWriter.wrap(zipped, level) { |io| io.write(self) }
|
171
|
+
zipped.string
|
172
|
+
end
|
173
|
+
|
174
|
+
#
|
175
|
+
# gunzip the string
|
176
|
+
#
|
177
|
+
def gunzip
|
178
|
+
data = StringIO.new(self)
|
179
|
+
Zlib::GzipReader.new(data).read
|
180
|
+
end
|
181
|
+
|
182
|
+
#
|
183
|
+
# deflate the string
|
184
|
+
#
|
185
|
+
def deflate(level=nil)
|
186
|
+
Zlib::Deflate.deflate(self, level)
|
187
|
+
end
|
188
|
+
|
189
|
+
#
|
190
|
+
# inflate the string
|
191
|
+
#
|
192
|
+
def inflate
|
193
|
+
Zlib::Inflate.inflate(self)
|
194
|
+
end
|
195
|
+
|
196
|
+
# `true` if this string starts with the substring
|
197
|
+
#
|
198
|
+
def startswith(substring)
|
199
|
+
self[0...substring.size] == substring
|
200
|
+
end
|
201
|
+
|
202
|
+
#
|
203
|
+
# `true` if this string ends with the substring
|
204
|
+
#
|
205
|
+
def endswith(substring)
|
206
|
+
self[-substring.size..-1] == substring
|
207
|
+
end
|
208
|
+
|
209
|
+
#
|
210
|
+
# Parse this string as JSON
|
211
|
+
#
|
212
|
+
def from_json
|
213
|
+
JSON.parse(self)
|
214
|
+
end
|
215
|
+
|
216
|
+
#
|
217
|
+
# Parse this string as YAML
|
218
|
+
#
|
219
|
+
def from_yaml
|
220
|
+
YAML.parse(self).to_ruby
|
221
|
+
end
|
222
|
+
|
223
|
+
#
|
224
|
+
# Unmarshal the string (transform it into Ruby datatypes).
|
225
|
+
#
|
226
|
+
def unmarshal
|
227
|
+
Marshal.restore self
|
228
|
+
end
|
229
|
+
alias_method :from_marshal, :unmarshal
|
230
|
+
|
231
|
+
#
|
232
|
+
# Convert the string to a Path object.
|
233
|
+
#
|
234
|
+
def as_path
|
235
|
+
Path[self]
|
236
|
+
end
|
237
|
+
alias_method :to_p, :as_path
|
238
|
+
|
239
|
+
#
|
240
|
+
# Convert this string into a string describing this many of the string.
|
241
|
+
# (Note: Doesn't know anything about proper grammar.)
|
242
|
+
#
|
243
|
+
# Example:
|
244
|
+
# "cookie".amount(0) #=> "0 cookies"
|
245
|
+
# "shirt".amount(17) #=> "17 shirts"
|
246
|
+
# "dollar".amount(-10) #=> "-10 dollars"
|
247
|
+
# "love".amount(1) #=> "1 love"
|
248
|
+
#
|
249
|
+
def amount(n)
|
250
|
+
case n
|
251
|
+
when 0
|
252
|
+
"0 #{self}s"
|
253
|
+
when 1, -1
|
254
|
+
"#{n} #{self}"
|
255
|
+
else
|
256
|
+
"#{n} #{self}s"
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
|
261
|
+
unless public_method_defined? :to_proc
|
262
|
+
|
263
|
+
#
|
264
|
+
# String#to_proc
|
265
|
+
#
|
266
|
+
# See http://weblog.raganwald.com/2007/10/stringtoproc.html
|
267
|
+
#
|
268
|
+
# Ported from the String Lambdas in Oliver Steele's Functional Javascript
|
269
|
+
# http://osteele.com/sources/javascript/functional/
|
270
|
+
#
|
271
|
+
# This work is licensed under the MIT License:
|
272
|
+
#
|
273
|
+
# (c) 2007 Reginald Braithwaite
|
274
|
+
# Portions Copyright (c) 2006 Oliver Steele
|
275
|
+
#
|
276
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
277
|
+
# a copy of this software and associated documentation files (the
|
278
|
+
# "Software"), to deal in the Software without restriction, including
|
279
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
280
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
281
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
282
|
+
# the following conditions:
|
283
|
+
#
|
284
|
+
# The above copyright notice and this permission notice shall be
|
285
|
+
# included in all copies or substantial portions of the Software.
|
286
|
+
#
|
287
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
288
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
289
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
290
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
291
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
292
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
293
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
294
|
+
#
|
295
|
+
def to_proc &block
|
296
|
+
params = []
|
297
|
+
expr = self
|
298
|
+
sections = expr.split(/\s*->\s*/m)
|
299
|
+
if sections.length > 1 then
|
300
|
+
eval_block(sections.reverse!.inject { |e, p| "(Proc.new { |#{p.split(/\s/).join(', ')}| #{e} })" }, block)
|
301
|
+
elsif expr.match(/\b_\b/)
|
302
|
+
eval_block("Proc.new { |_| #{expr} }", block)
|
303
|
+
else
|
304
|
+
leftSection = expr.match(/^\s*(?:[+*\/%&|\^\.=<>\[]|!=)/m)
|
305
|
+
rightSection = expr.match(/[+\-*\/%&|\^\.=<>!]\s*$/m)
|
306
|
+
if leftSection || rightSection then
|
307
|
+
if (leftSection) then
|
308
|
+
params.push('$left')
|
309
|
+
expr = '$left' + expr
|
310
|
+
end
|
311
|
+
if (rightSection) then
|
312
|
+
params.push('$right')
|
313
|
+
expr = expr + '$right'
|
314
|
+
end
|
315
|
+
else
|
316
|
+
self.gsub(
|
317
|
+
/(?:\b[A-Z]|\.[a-zA-Z_$])[a-zA-Z_$\d]*|[a-zA-Z_$][a-zA-Z_$\d]*:|self|arguments|'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"/, ''
|
318
|
+
).scan(
|
319
|
+
/([a-z_$][a-z_$\d]*)/i
|
320
|
+
) do |v|
|
321
|
+
params.push(v) unless params.include?(v)
|
322
|
+
end
|
323
|
+
end
|
324
|
+
eval_block("Proc.new { |#{params.join(', ')}| #{expr} }", block)
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
private
|
329
|
+
|
330
|
+
def eval_block(code, block)
|
331
|
+
eval code, block && block.binding
|
332
|
+
end
|
333
|
+
|
334
|
+
end # unless public_method_defined? :to_proc
|
335
|
+
|
336
|
+
end
|
337
|
+
|
338
|
+
|