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,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
|
+
|