extlib 0.9.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of extlib might be problematic. Click here for more details.
- data/README +0 -0
- data/lib/extlib.rb +19 -0
- data/lib/extlib/assertions.rb +8 -0
- data/lib/extlib/blank.rb +42 -0
- data/lib/extlib/hook.rb +313 -0
- data/lib/extlib/inflection.rb +141 -0
- data/lib/extlib/lazy_array.rb +104 -0
- data/lib/extlib/module.rb +19 -0
- data/lib/extlib/object.rb +7 -0
- data/lib/extlib/pathname.rb +5 -0
- data/lib/extlib/pooling.rb +227 -0
- data/lib/extlib/string.rb +45 -0
- data/lib/extlib/struct.rb +8 -0
- data/spec/blank_spec.rb +85 -0
- data/spec/hook_spec.rb +897 -0
- data/spec/inflection_spec.rb +50 -0
- data/spec/lazy_array_spec.rb +882 -0
- data/spec/module_spec.rb +36 -0
- data/spec/object_spec.rb +4 -0
- data/spec/pooling_spec.rb +490 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/string_spec.rb +4 -0
- data/spec/struct_spec.rb +12 -0
- metadata +92 -0
@@ -0,0 +1,104 @@
|
|
1
|
+
class LazyArray # borrowed partially from StrokeDB
|
2
|
+
include Enumerable
|
3
|
+
|
4
|
+
# these methods should return self or nil
|
5
|
+
RETURN_SELF = [ :<<, :clear, :concat, :collect!, :each, :each_index,
|
6
|
+
:each_with_index, :insert, :map!, :push, :replace, :reject!,
|
7
|
+
:reverse!, :reverse_each, :sort!, :unshift ]
|
8
|
+
|
9
|
+
RETURN_SELF.each do |method|
|
10
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
11
|
+
def #{method}(*args, &block)
|
12
|
+
lazy_load
|
13
|
+
results = @array.#{method}(*args, &block)
|
14
|
+
results.kind_of?(Array) ? self : results
|
15
|
+
end
|
16
|
+
EOS
|
17
|
+
end
|
18
|
+
|
19
|
+
(Array.public_instance_methods(false).map { |m| m.to_sym } - RETURN_SELF - [ :taguri= ]).each do |method|
|
20
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
21
|
+
def #{method}(*args, &block)
|
22
|
+
lazy_load
|
23
|
+
@array.#{method}(*args, &block)
|
24
|
+
end
|
25
|
+
EOS
|
26
|
+
end
|
27
|
+
|
28
|
+
def replace(other)
|
29
|
+
mark_loaded
|
30
|
+
@array.replace(other.entries)
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def clear
|
35
|
+
mark_loaded
|
36
|
+
@array.clear
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
def eql?(other)
|
41
|
+
lazy_load
|
42
|
+
@array.eql?(other.entries)
|
43
|
+
end
|
44
|
+
|
45
|
+
alias == eql?
|
46
|
+
|
47
|
+
def load_with(&block)
|
48
|
+
@load_with_proc = block
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
def loaded?
|
53
|
+
@loaded == true
|
54
|
+
end
|
55
|
+
|
56
|
+
def unload
|
57
|
+
clear
|
58
|
+
@loaded = false
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
def respond_to?(method, include_private = false)
|
63
|
+
super || @array.respond_to?(method, include_private)
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_proc
|
67
|
+
@load_with_proc
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def initialize(*args, &block)
|
73
|
+
@loaded = false
|
74
|
+
@load_with_proc = proc { |v| v }
|
75
|
+
@array = Array.new(*args, &block)
|
76
|
+
end
|
77
|
+
|
78
|
+
def initialize_copy(original)
|
79
|
+
@array = original.entries
|
80
|
+
load_with(&original)
|
81
|
+
mark_loaded if @array.any?
|
82
|
+
end
|
83
|
+
|
84
|
+
def lazy_load
|
85
|
+
return if loaded?
|
86
|
+
mark_loaded
|
87
|
+
@load_with_proc[self]
|
88
|
+
end
|
89
|
+
|
90
|
+
def mark_loaded
|
91
|
+
@loaded = true
|
92
|
+
end
|
93
|
+
|
94
|
+
# delegate any not-explicitly-handled methods to @array, if possible.
|
95
|
+
# this is handy for handling methods mixed-into Array like group_by
|
96
|
+
def method_missing(method, *args, &block)
|
97
|
+
if @array.respond_to?(method)
|
98
|
+
lazy_load
|
99
|
+
@array.send(method, *args, &block)
|
100
|
+
else
|
101
|
+
super
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class Module
|
2
|
+
def find_const(nested_name)
|
3
|
+
self.__nested_constants__[nested_name]
|
4
|
+
rescue NameError
|
5
|
+
Object::__nested_constants__[nested_name]
|
6
|
+
end
|
7
|
+
|
8
|
+
protected
|
9
|
+
def __nested_constants__
|
10
|
+
@__nested_constants__ ||= Hash.new do |h,k|
|
11
|
+
klass = self
|
12
|
+
k.split('::').each do |c|
|
13
|
+
klass = klass.const_get(c) unless c.empty?
|
14
|
+
end
|
15
|
+
h[k] = klass
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end # class Module
|
@@ -0,0 +1,227 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
module Extlib
|
5
|
+
# ==== Notes
|
6
|
+
# Provides pooling support to class it got included in.
|
7
|
+
#
|
8
|
+
# Pooling of objects is a faster way of aquiring instances
|
9
|
+
# of objects compared to regular allocation and initialization
|
10
|
+
# because instances are keeped in memory reused.
|
11
|
+
#
|
12
|
+
# Classes that include Pooling module have re-defined new
|
13
|
+
# method that returns instances aquired from pool.
|
14
|
+
#
|
15
|
+
# Term resource is used for any type of poolable objects
|
16
|
+
# and should NOT be thought as DataMapper Resource or
|
17
|
+
# ActiveResource resource and such.
|
18
|
+
#
|
19
|
+
# In Data Objects connections are pooled so that it is
|
20
|
+
# unnecessary to allocate and initialize connection object
|
21
|
+
# each time connection is needed, like per request in a
|
22
|
+
# web application.
|
23
|
+
#
|
24
|
+
# Pool obviously has to be thread safe because state of
|
25
|
+
# object is reset when it is released.
|
26
|
+
module Pooling
|
27
|
+
|
28
|
+
def self.scavenger
|
29
|
+
@scavenger || begin
|
30
|
+
@scavenger = Thread.new do
|
31
|
+
loop do
|
32
|
+
lock.synchronize do
|
33
|
+
pools.each do |pool|
|
34
|
+
if pool.expired?
|
35
|
+
pool.lock.synchronize do
|
36
|
+
if pool.size == 0
|
37
|
+
pool.dispose
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
sleep(scavenger_interval)
|
44
|
+
end # loop
|
45
|
+
end
|
46
|
+
|
47
|
+
@scavenger.priority = -10
|
48
|
+
@scavenger
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.pools
|
53
|
+
@pools ||= Set.new
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.append_pool(pool)
|
57
|
+
lock.synchronize do
|
58
|
+
pools << pool
|
59
|
+
end
|
60
|
+
Extlib::Pooling::scavenger
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.lock
|
64
|
+
@lock ||= Mutex.new
|
65
|
+
end
|
66
|
+
|
67
|
+
class CrossPoolError < StandardError
|
68
|
+
end
|
69
|
+
|
70
|
+
class OrphanedObjectError < StandardError
|
71
|
+
end
|
72
|
+
|
73
|
+
class ThreadStopError < StandardError
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.included(target)
|
77
|
+
target.class_eval do
|
78
|
+
class << self
|
79
|
+
alias __new new
|
80
|
+
end
|
81
|
+
|
82
|
+
@__pools = Hash.new { |h,k| __pool_lock.synchronize { h[k] = Pool.new(target.pool_size, target, k) } }
|
83
|
+
@__pool_lock = Mutex.new
|
84
|
+
|
85
|
+
def self.__pool_lock
|
86
|
+
@__pool_lock
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.new(*args)
|
90
|
+
@__pools[args].new
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.__pools
|
94
|
+
@__pools
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.pool_size
|
98
|
+
1
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.scavenge_interval
|
102
|
+
10
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def release
|
108
|
+
@__pool.release(self)
|
109
|
+
end
|
110
|
+
|
111
|
+
class Pool
|
112
|
+
def initialize(max_size, resource, args)
|
113
|
+
raise ArgumentError.new("+max_size+ should be a Fixnum but was #{max_size.inspect}") unless Fixnum === max_size
|
114
|
+
raise ArgumentError.new("+resource+ should be a Class but was #{resource.inspect}") unless Class === resource
|
115
|
+
|
116
|
+
@max_size = max_size
|
117
|
+
@resource = resource
|
118
|
+
@args = args
|
119
|
+
|
120
|
+
@available = []
|
121
|
+
@reserved = Set.new
|
122
|
+
|
123
|
+
Extlib::Pooling::append_pool(self)
|
124
|
+
end
|
125
|
+
|
126
|
+
def lock
|
127
|
+
@resource.__pool_lock
|
128
|
+
end
|
129
|
+
|
130
|
+
def scavenge_interval
|
131
|
+
@resource.scavenge_interval
|
132
|
+
end
|
133
|
+
|
134
|
+
def new
|
135
|
+
instance = nil
|
136
|
+
|
137
|
+
lock.synchronize do
|
138
|
+
instance = aquire
|
139
|
+
end
|
140
|
+
|
141
|
+
if instance.nil?
|
142
|
+
# Account for the current thread, and the pool scavenger.
|
143
|
+
if ThreadGroup::Default.list.size == 2 && @reserved.size >= @max_size
|
144
|
+
raise ThreadStopError.new(size)
|
145
|
+
else
|
146
|
+
sleep(0.01)
|
147
|
+
new
|
148
|
+
end
|
149
|
+
else
|
150
|
+
instance
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def release(instance)
|
155
|
+
lock.synchronize do
|
156
|
+
raise OrphanedObjectError.new(instance) unless @reserved.delete?(instance)
|
157
|
+
instance.instance_variable_set(:@__pool, nil)
|
158
|
+
@available.push(instance)
|
159
|
+
end
|
160
|
+
nil
|
161
|
+
end
|
162
|
+
|
163
|
+
def size
|
164
|
+
@available.size + @reserved.size
|
165
|
+
end
|
166
|
+
alias length size
|
167
|
+
|
168
|
+
def inspect
|
169
|
+
"#<Extlib::Pooling::Pool<#{@resource.name}> available=#{@available.size} reserved=#{@reserved.size}>"
|
170
|
+
end
|
171
|
+
|
172
|
+
def flush!
|
173
|
+
lock.synchronize do
|
174
|
+
@available.each do |instance|
|
175
|
+
instance.dispose
|
176
|
+
end
|
177
|
+
@available.clear
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def dispose
|
182
|
+
@resource.__pools.delete(@args)
|
183
|
+
!Extlib::Pooling::pools.delete?(self).nil?
|
184
|
+
end
|
185
|
+
|
186
|
+
def expired?
|
187
|
+
lock.synchronize do
|
188
|
+
@available.each do |instance|
|
189
|
+
if instance.instance_variable_get(:@__allocated_in_pool) + scavenge_interval < Time.now
|
190
|
+
instance.dispose
|
191
|
+
@available.delete(instance)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
size == 0
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
private
|
200
|
+
|
201
|
+
def aquire
|
202
|
+
instance = if !@available.empty?
|
203
|
+
@available.pop
|
204
|
+
elsif size < @max_size
|
205
|
+
@resource.__new(*@args)
|
206
|
+
else
|
207
|
+
nil
|
208
|
+
end
|
209
|
+
|
210
|
+
if instance.nil?
|
211
|
+
instance
|
212
|
+
else
|
213
|
+
raise CrossPoolError.new(instance) if instance.instance_variable_get(:@__pool)
|
214
|
+
@reserved << instance
|
215
|
+
instance.instance_variable_set(:@__pool, self)
|
216
|
+
instance.instance_variable_set(:@__allocated_in_pool, Time.now)
|
217
|
+
instance
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
private
|
223
|
+
def self.scavenger_interval
|
224
|
+
60
|
225
|
+
end
|
226
|
+
end # module Pooling
|
227
|
+
end # module Extlib
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class String
|
2
|
+
# Overwrite this method to provide your own translations.
|
3
|
+
def self.translate(value)
|
4
|
+
translations[value] || value
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.translations
|
8
|
+
@translations ||= {}
|
9
|
+
end
|
10
|
+
|
11
|
+
# Matches any whitespace (including newline) and replaces with a single space
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# <<QUERY.compress_lines
|
15
|
+
# SELECT name
|
16
|
+
# FROM users
|
17
|
+
# QUERY
|
18
|
+
# => "SELECT name FROM users"
|
19
|
+
def compress_lines(spaced = true)
|
20
|
+
split($/).map { |line| line.strip }.join(spaced ? ' ' : '')
|
21
|
+
end
|
22
|
+
|
23
|
+
# Useful for heredocs - removes whitespace margin.
|
24
|
+
def margin(indicator = nil)
|
25
|
+
lines = self.dup.split($/)
|
26
|
+
|
27
|
+
min_margin = 0
|
28
|
+
lines.each do |line|
|
29
|
+
if line =~ /^(\s+)/ && (min_margin == 0 || $1.size < min_margin)
|
30
|
+
min_margin = $1.size
|
31
|
+
end
|
32
|
+
end
|
33
|
+
lines.map { |line| line.sub(/^\s{#{min_margin}}/, '') }.join($/)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Formats String for easy translation. Replaces an arbitrary number of
|
37
|
+
# values using numeric identifier replacement.
|
38
|
+
#
|
39
|
+
# @example
|
40
|
+
# "%s %s %s" % %w(one two three) #=> "one two three"
|
41
|
+
# "%3$s %2$s %1$s" % %w(one two three) #=> "three two one"
|
42
|
+
def t(*values)
|
43
|
+
self.class::translate(self) % values
|
44
|
+
end
|
45
|
+
end # class String
|
data/spec/blank_spec.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
|
3
|
+
describe Object do
|
4
|
+
it 'should provide blank?' do
|
5
|
+
Object.new.should respond_to(:blank?)
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'should be blank if it is nil' do
|
9
|
+
object = Object.new
|
10
|
+
class << object
|
11
|
+
def nil?; true end
|
12
|
+
end
|
13
|
+
object.should be_blank
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should be blank if it is empty' do
|
17
|
+
{}.should be_blank
|
18
|
+
[].should be_blank
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should not be blank if not nil or empty' do
|
22
|
+
Object.new.should_not be_blank
|
23
|
+
[nil].should_not be_blank
|
24
|
+
{ nil => 0 }.should_not be_blank
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe Numeric do
|
29
|
+
it 'should provide blank?' do
|
30
|
+
1.should respond_to(:blank?)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should never be blank' do
|
34
|
+
1.should_not be_blank
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe NilClass do
|
39
|
+
it 'should provide blank?' do
|
40
|
+
nil.should respond_to(:blank?)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should always be blank' do
|
44
|
+
nil.should be_blank
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe TrueClass do
|
49
|
+
it 'should provide blank?' do
|
50
|
+
true.should respond_to(:blank?)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should never be blank' do
|
54
|
+
true.should_not be_blank
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe FalseClass do
|
59
|
+
it 'should provide blank?' do
|
60
|
+
false.should respond_to(:blank?)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should always be blank' do
|
64
|
+
false.should be_blank
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe String do
|
69
|
+
it 'should provide blank?' do
|
70
|
+
'string'.should respond_to(:blank?)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should be blank if empty' do
|
74
|
+
''.should be_blank
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should be blank if it only contains whitespace' do
|
78
|
+
' '.should be_blank
|
79
|
+
" \r \n \t ".should be_blank
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'should not be blank if it contains non-whitespace' do
|
83
|
+
' a '.should_not be_blank
|
84
|
+
end
|
85
|
+
end
|