robsharp-extlib 0.9.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/.autotest +21 -0
  2. data/.document +5 -0
  3. data/.gitignore +22 -0
  4. data/LICENSE +47 -0
  5. data/README.rdoc +17 -0
  6. data/Rakefile +28 -0
  7. data/VERSION +1 -0
  8. data/extlib.gemspec +147 -0
  9. data/lib/extlib.rb +50 -0
  10. data/lib/extlib/array.rb +38 -0
  11. data/lib/extlib/assertions.rb +8 -0
  12. data/lib/extlib/blank.rb +89 -0
  13. data/lib/extlib/boolean.rb +11 -0
  14. data/lib/extlib/byte_array.rb +6 -0
  15. data/lib/extlib/class.rb +179 -0
  16. data/lib/extlib/datetime.rb +29 -0
  17. data/lib/extlib/dictionary.rb +433 -0
  18. data/lib/extlib/hash.rb +450 -0
  19. data/lib/extlib/hook.rb +407 -0
  20. data/lib/extlib/inflection.rb +442 -0
  21. data/lib/extlib/lazy_array.rb +453 -0
  22. data/lib/extlib/lazy_module.rb +18 -0
  23. data/lib/extlib/logger.rb +198 -0
  24. data/lib/extlib/mash.rb +157 -0
  25. data/lib/extlib/module.rb +51 -0
  26. data/lib/extlib/nil.rb +5 -0
  27. data/lib/extlib/numeric.rb +5 -0
  28. data/lib/extlib/object.rb +178 -0
  29. data/lib/extlib/object_space.rb +13 -0
  30. data/lib/extlib/pathname.rb +20 -0
  31. data/lib/extlib/pooling.rb +235 -0
  32. data/lib/extlib/rubygems.rb +38 -0
  33. data/lib/extlib/simple_set.rb +66 -0
  34. data/lib/extlib/string.rb +176 -0
  35. data/lib/extlib/struct.rb +17 -0
  36. data/lib/extlib/symbol.rb +21 -0
  37. data/lib/extlib/time.rb +44 -0
  38. data/lib/extlib/try_dup.rb +44 -0
  39. data/lib/extlib/virtual_file.rb +10 -0
  40. data/spec/array_spec.rb +40 -0
  41. data/spec/blank_spec.rb +86 -0
  42. data/spec/byte_array_spec.rb +8 -0
  43. data/spec/class_spec.rb +158 -0
  44. data/spec/datetime_spec.rb +22 -0
  45. data/spec/hash_spec.rb +536 -0
  46. data/spec/hook_spec.rb +1235 -0
  47. data/spec/inflection/plural_spec.rb +565 -0
  48. data/spec/inflection/singular_spec.rb +498 -0
  49. data/spec/inflection_extras_spec.rb +111 -0
  50. data/spec/lazy_array_spec.rb +1961 -0
  51. data/spec/lazy_module_spec.rb +38 -0
  52. data/spec/mash_spec.rb +312 -0
  53. data/spec/module_spec.rb +71 -0
  54. data/spec/object_space_spec.rb +10 -0
  55. data/spec/object_spec.rb +114 -0
  56. data/spec/pooling_spec.rb +511 -0
  57. data/spec/rcov.opts +6 -0
  58. data/spec/simple_set_spec.rb +58 -0
  59. data/spec/spec.opts +4 -0
  60. data/spec/spec_helper.rb +7 -0
  61. data/spec/string_spec.rb +222 -0
  62. data/spec/struct_spec.rb +13 -0
  63. data/spec/symbol_spec.rb +9 -0
  64. data/spec/time_spec.rb +31 -0
  65. data/spec/try_call_spec.rb +74 -0
  66. data/spec/try_dup_spec.rb +46 -0
  67. data/spec/virtual_file_spec.rb +22 -0
  68. data/tasks/ci.rake +1 -0
  69. data/tasks/metrics.rake +36 -0
  70. data/tasks/spec.rake +25 -0
  71. data/tasks/yard.rake +9 -0
  72. data/tasks/yardstick.rake +19 -0
  73. metadata +198 -0
@@ -0,0 +1,13 @@
1
+ module ObjectSpace
2
+
3
+ class << self
4
+
5
+ # @return [Array<Class>] All the classes in the object space.
6
+ def classes
7
+ klasses = []
8
+ ObjectSpace.each_object(Class) {|o| klasses << o}
9
+ klasses
10
+ end
11
+ end
12
+
13
+ end
@@ -0,0 +1,20 @@
1
+ class Pathname
2
+ # Append path segments and expand to absolute path
3
+ #
4
+ # file = Pathname(Dir.pwd) / "subdir1" / :subdir2 / "filename.ext"
5
+ #
6
+ # @param [Pathname, String, #to_s] path path segment to concatenate with receiver
7
+ #
8
+ # @return [Pathname]
9
+ # receiver with _path_ appended and expanded to an absolute path
10
+ #
11
+ # @api public
12
+ def /(path)
13
+ (self + path).expand_path
14
+ end
15
+
16
+ # alias to_s to to_str when to_str not defined
17
+ unless public_instance_methods(false).any? { |m| m.to_sym == :to_str }
18
+ alias to_str to_s
19
+ end
20
+ end # class Pathname
@@ -0,0 +1,235 @@
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 acquired 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
+ defined?(@scavenger) && !@scavenger.nil? && @scavenger.alive?
30
+ end
31
+
32
+ def self.scavenger
33
+ unless scavenger?
34
+ @scavenger = Thread.new do
35
+ running = true
36
+ while running do
37
+ # Sleep before we actually start doing anything.
38
+ # Otherwise we might clean up something we just made
39
+ sleep(scavenger_interval)
40
+
41
+ lock.synchronize do
42
+ pools.each do |pool|
43
+ # This is a useful check, but non-essential, and right now it breaks lots of stuff.
44
+ # if pool.expired?
45
+ pool.lock.synchronize do
46
+ if pool.expired?
47
+ pool.dispose
48
+ end
49
+ end
50
+ # end
51
+ end
52
+
53
+ # The pool is empty, we stop the scavenger
54
+ # It wil be restarted if new resources are added again
55
+ if pools.empty?
56
+ running = false
57
+ end
58
+ end
59
+ end # loop
60
+ end
61
+ end
62
+
63
+ @scavenger.priority = -10
64
+ @scavenger
65
+ end
66
+
67
+ def self.pools
68
+ @pools ||= Set.new
69
+ end
70
+
71
+ def self.append_pool(pool)
72
+ lock.synchronize do
73
+ pools << pool
74
+ end
75
+ Extlib::Pooling.scavenger
76
+ end
77
+
78
+ def self.lock
79
+ @lock ||= Mutex.new
80
+ end
81
+
82
+ class InvalidResourceError < StandardError
83
+ end
84
+
85
+ def self.included(target)
86
+ target.class_eval do
87
+ class << self
88
+ alias __new new
89
+ end
90
+
91
+ @__pools = {}
92
+ @__pool_lock = Mutex.new
93
+ @__pool_wait = ConditionVariable.new
94
+
95
+ def self.__pool_lock
96
+ @__pool_lock
97
+ end
98
+
99
+ def self.__pool_wait
100
+ @__pool_wait
101
+ end
102
+
103
+ def self.new(*args)
104
+ (@__pools[args] ||= __pool_lock.synchronize { Pool.new(self.pool_size, self, args) }).new
105
+ end
106
+
107
+ def self.__pools
108
+ @__pools
109
+ end
110
+
111
+ def self.pool_size
112
+ 8
113
+ end
114
+ end
115
+ end
116
+
117
+ def release
118
+ @__pool.release(self) unless @__pool.nil?
119
+ end
120
+
121
+ def detach
122
+ @__pool.delete(self) unless @__pool.nil?
123
+ end
124
+
125
+ class Pool
126
+ attr_reader :available
127
+ attr_reader :used
128
+
129
+ def initialize(max_size, resource, args)
130
+ raise ArgumentError.new("+max_size+ should be a Fixnum but was #{max_size.inspect}") unless Fixnum === max_size
131
+ raise ArgumentError.new("+resource+ should be a Class but was #{resource.inspect}") unless Class === resource
132
+
133
+ @max_size = max_size
134
+ @resource = resource
135
+ @args = args
136
+
137
+ @available = []
138
+ @used = {}
139
+ Extlib::Pooling.append_pool(self)
140
+ end
141
+
142
+ def lock
143
+ @resource.__pool_lock
144
+ end
145
+
146
+ def wait
147
+ @resource.__pool_wait
148
+ end
149
+
150
+ def scavenge_interval
151
+ @resource.scavenge_interval
152
+ end
153
+
154
+ def new
155
+ instance = nil
156
+ begin
157
+ lock.synchronize do
158
+ if @available.size > 0
159
+ instance = @available.pop
160
+ @used[instance.object_id] = instance
161
+ elsif @used.size < @max_size
162
+ instance = @resource.__new(*@args)
163
+ raise InvalidResourceError.new("#{@resource} constructor created a nil object") if instance.nil?
164
+ raise InvalidResourceError.new("#{instance} is already part of the pool") if @used.include? instance
165
+ instance.instance_variable_set(:@__pool, self)
166
+ instance.instance_variable_set(:@__allocated_in_pool, Time.now)
167
+ @used[instance.object_id] = instance
168
+ else
169
+ # Wait for another thread to release an instance.
170
+ # If we exhaust the pool and don't release the active instance,
171
+ # we'll wait here forever, so it's *very* important to always
172
+ # release your services and *never* exhaust the pool within
173
+ # a single thread.
174
+ wait.wait(lock)
175
+ end
176
+ end
177
+ end until instance
178
+ instance
179
+ end
180
+
181
+ def release(instance)
182
+ lock.synchronize do
183
+ instance.instance_variable_set(:@__allocated_in_pool, Time.now)
184
+ @used.delete(instance.object_id)
185
+ @available.push(instance)
186
+ wait.signal
187
+ end
188
+ nil
189
+ end
190
+
191
+ def delete(instance)
192
+ lock.synchronize do
193
+ instance.instance_variable_set(:@__pool, nil)
194
+ @used.delete(instance.object_id)
195
+ wait.signal
196
+ end
197
+ nil
198
+ end
199
+
200
+ def size
201
+ @used.size + @available.size
202
+ end
203
+ alias length size
204
+
205
+ def inspect
206
+ "#<Extlib::Pooling::Pool<#{@resource.name}> available=#{@available.size} used=#{@used.size} size=#{@max_size}>"
207
+ end
208
+
209
+ def flush!
210
+ @available.pop.dispose until @available.empty?
211
+ end
212
+
213
+ def dispose
214
+ flush!
215
+ @resource.__pools.delete(@args)
216
+ !Extlib::Pooling.pools.delete?(self).nil?
217
+ end
218
+
219
+ def expired?
220
+ @available.each do |instance|
221
+ if Extlib.exiting || instance.instance_variable_get(:@__allocated_in_pool) + Extlib::Pooling.scavenger_interval <= (Time.now + 0.02)
222
+ instance.dispose
223
+ @available.delete(instance)
224
+ end
225
+ end
226
+ size == 0
227
+ end
228
+
229
+ end
230
+
231
+ def self.scavenger_interval
232
+ 60
233
+ end
234
+ end # module Pooling
235
+ end # module Extlib
@@ -0,0 +1,38 @@
1
+ # this is a temporary workaround until rubygems Does the Right thing here
2
+ require 'rubygems'
3
+ module Gem
4
+ class SourceIndex
5
+
6
+ # This is resolved in 1.1
7
+ if Version.new(RubyGemsVersion) < Version.new("1.1")
8
+
9
+ # Overwrite this so that a gem of the same name and version won't push one
10
+ # from the gems directory out entirely.
11
+ #
12
+ # @param gem_spec<Gem::Specification> The specification of the gem to add.
13
+ def add_spec(gem_spec)
14
+ unless gem_spec.instance_variable_get("@loaded_from") &&
15
+ @gems[gem_spec.full_name].is_a?(Gem::Specification) &&
16
+ @gems[gem_spec.full_name].installation_path ==
17
+ File.join(defined?(Merb) && Merb.respond_to?(:root) ? Merb.root : Dir.pwd,"gems")
18
+
19
+ @gems[gem_spec.full_name] = gem_spec
20
+ end
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+
27
+ class Specification
28
+
29
+ # Overwrite this so that gems in the gems directory get preferred over gems
30
+ # from any other location. If there are two gems of different versions in
31
+ # the gems directory, the later one will load as usual.
32
+ #
33
+ # @return [Array<Array>] The object used for sorting gem specs.
34
+ def sort_obj
35
+ [@name, installation_path == File.join(defined?(Merb) && Merb.respond_to?(:root) ? Merb.root : Dir.pwd,"gems") ? 1 : -1, @version.to_ints, @new_platform == Gem::Platform::RUBY ? -1 : 1]
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,66 @@
1
+ module Extlib
2
+ # Simple set implementation
3
+ # on top of Hash with merging support.
4
+ #
5
+ # In particular this is used to store
6
+ # a set of callable actions of controller.
7
+ class SimpleSet < Hash
8
+
9
+ ##
10
+ # Create a new SimpleSet containing the unique members of _arr_
11
+ #
12
+ # @param [Array] arr Initial set values.
13
+ #
14
+ # @return [Array] The array the Set was initialized with
15
+ #
16
+ # @api public
17
+ def initialize(arr = [])
18
+ Array(arr).each {|x| self[x] = true}
19
+ end
20
+
21
+ ##
22
+ # Add a value to the set, and return it
23
+ #
24
+ # @param [Object] value Value to add to set.
25
+ #
26
+ # @return [SimpleSet] Receiver
27
+ #
28
+ # @api public
29
+ def <<(value)
30
+ self[value] = true
31
+ self
32
+ end
33
+
34
+ ##
35
+ # Merge _arr_ with receiver, producing the union of receiver & _arr_
36
+ #
37
+ # s = Extlib::SimpleSet.new([:a, :b, :c])
38
+ # s.merge([:c, :d, :e, f]) #=> #<SimpleSet: {:e, :c, :f, :a, :d, :b}>
39
+ #
40
+ # @param [Array] arr Values to merge with set.
41
+ #
42
+ # @return [SimpleSet] The set after the Array was merged in.
43
+ #
44
+ # @api public
45
+ def merge(arr)
46
+ super(arr.inject({}) {|s,x| s[x] = true; s })
47
+ end
48
+
49
+ ##
50
+ # Get a human readable version of the set.
51
+ #
52
+ # s = SimpleSet.new([:a, :b, :c])
53
+ # s.inspect #=> "#<SimpleSet: {:c, :a, :b}>"
54
+ #
55
+ # @return [String] A human readable version of the set.
56
+ #
57
+ # @api public
58
+ def inspect
59
+ "#<SimpleSet: {#{keys.map {|x| x.inspect}.join(", ")}}>"
60
+ end
61
+
62
+ # def to_a
63
+ alias_method :to_a, :keys
64
+
65
+ end # SimpleSet
66
+ end # Merb
@@ -0,0 +1,176 @@
1
+ require "pathname"
2
+
3
+ class String
4
+ ##
5
+ # Escape all regexp special characters.
6
+ #
7
+ # "*?{}.".escape_regexp #=> "\\*\\?\\{\\}\\."
8
+ #
9
+ # @return [String] Receiver with all regexp special characters escaped.
10
+ #
11
+ # @api public
12
+ def escape_regexp
13
+ Regexp.escape self
14
+ end
15
+
16
+ ##
17
+ # Unescape all regexp special characters.
18
+ #
19
+ # "\\*\\?\\{\\}\\.".unescape_regexp #=> "*?{}."
20
+ #
21
+ # @return [String] Receiver with all regexp special characters unescaped.
22
+ #
23
+ # @api public
24
+ def unescape_regexp
25
+ self.gsub(/\\([\.\?\|\(\)\[\]\{\}\^\$\*\+\-])/, '\1')
26
+ end
27
+
28
+ ##
29
+ # Convert to snake case.
30
+ #
31
+ # "FooBar".snake_case #=> "foo_bar"
32
+ # "HeadlineCNNNews".snake_case #=> "headline_cnn_news"
33
+ # "CNN".snake_case #=> "cnn"
34
+ #
35
+ # @return [String] Receiver converted to snake case.
36
+ #
37
+ # @api public
38
+ def snake_case
39
+ return downcase if match(/\A[A-Z]+\z/)
40
+ gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
41
+ gsub(/([a-z])([A-Z])/, '\1_\2').
42
+ downcase
43
+ end
44
+
45
+ ##
46
+ # Convert to camel case.
47
+ #
48
+ # "foo_bar".camel_case #=> "FooBar"
49
+ #
50
+ # @return [String] Receiver converted to camel case.
51
+ #
52
+ # @api public
53
+ def camel_case
54
+ return self if self !~ /_/ && self =~ /[A-Z]+.*/
55
+ split('_').map{|e| e.capitalize}.join
56
+ end
57
+
58
+ ##
59
+ # Convert a path string to a constant name.
60
+ #
61
+ # "merb/core_ext/string".to_const_string #=> "Merb::CoreExt::String"
62
+ #
63
+ # @return [String] Receiver converted to a constant name.
64
+ #
65
+ # @api public
66
+ def to_const_string
67
+ gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
68
+ end
69
+
70
+ ##
71
+ # Convert a constant name to a path, assuming a conventional structure.
72
+ #
73
+ # "FooBar::Baz".to_const_path # => "foo_bar/baz"
74
+ #
75
+ # @return [String] Path to the file containing the constant named by receiver
76
+ # (constantized string), assuming a conventional structure.
77
+ #
78
+ # @api public
79
+ def to_const_path
80
+ snake_case.gsub(/::/, "/")
81
+ end
82
+
83
+ ##
84
+ # Join with _o_ as a file path.
85
+ #
86
+ # "merb"/"core_ext" #=> "merb/core_ext"
87
+ #
88
+ # @param [String] o Path component to join with receiver.
89
+ #
90
+ # @return [String] Receiver joined with o as a file path.
91
+ #
92
+ # @api public
93
+ def /(o)
94
+ File.join(self, o.to_s)
95
+ end
96
+
97
+ ##
98
+ # Calculate a relative path *from* _other_.
99
+ #
100
+ # "/opt/local/lib".relative_path_from("/opt/local/lib/ruby/site_ruby") # => "../.."
101
+ #
102
+ # @param [String] other Base path to calculate *from*.
103
+ #
104
+ # @return [String] Relative path from _other_ to receiver.
105
+ #
106
+ # @api public
107
+ def relative_path_from(other)
108
+ Pathname.new(self).relative_path_from(Pathname.new(other)).to_s
109
+ end
110
+
111
+ # Overwrite this method to provide your own translations.
112
+ def self.translate(value)
113
+ translations[value] || value
114
+ end
115
+
116
+ def self.translations
117
+ @translations ||= {}
118
+ end
119
+
120
+ ##
121
+ # Replace sequences of whitespace (including newlines) with either
122
+ # a single space or remove them entirely (according to param _spaced_)
123
+ #
124
+ # <<QUERY.compress_lines
125
+ # SELECT name
126
+ # FROM users
127
+ # QUERY => "SELECT name FROM users"
128
+ #
129
+ # @param [TrueClass, FalseClass] spaced (default=true)
130
+ # Determines whether returned string has whitespace collapsed or removed
131
+ #
132
+ # @return [String] Receiver with whitespace (including newlines) replaced
133
+ #
134
+ # @api public
135
+ def compress_lines(spaced = true)
136
+ split($/).map { |line| line.strip }.join(spaced ? ' ' : '')
137
+ end
138
+
139
+ ##
140
+ # Remove whitespace margin.
141
+ #
142
+ # @param [Object] indicator ???
143
+ #
144
+ # @return [String] receiver with whitespace margin removed
145
+ #
146
+ # @api public
147
+ def margin(indicator = nil)
148
+ lines = self.dup.split($/)
149
+
150
+ min_margin = 0
151
+ lines.each do |line|
152
+ if line =~ /^(\s+)/ && (min_margin == 0 || $1.size < min_margin)
153
+ min_margin = $1.size
154
+ end
155
+ end
156
+ lines.map { |line| line.sub(/^\s{#{min_margin}}/, '') }.join($/)
157
+ end
158
+
159
+ ##
160
+ # Formats String for easy translation. Replaces an arbitrary number of
161
+ # values using numeric identifier replacement.
162
+ #
163
+ # "%s %s %s" % %w(one two three) #=> "one two three"
164
+ # "%3$s %2$s %1$s" % %w(one two three) #=> "three two one"
165
+ #
166
+ # @param [#to_s] values
167
+ # A list of values to translate and interpolate into receiver
168
+ #
169
+ # @return [String]
170
+ # Receiver translated with values translated and interpolated positionally
171
+ #
172
+ # @api public
173
+ def t(*values)
174
+ self.class::translate(self) % values.collect! { |value| value.frozen? ? value : self.class::translate(value.to_s) }
175
+ end
176
+ end # class String