extlib 0.9.2 → 0.9.3

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/.autotest ADDED
@@ -0,0 +1,21 @@
1
+ Autotest.add_hook :initialize do |at|
2
+ ignore = %w[ .git log plugins script tasks bin CHANGELOG FAQ MIT-LICENSE QUICKLINKS README ]
3
+
4
+ ignore.each do |exception|
5
+ at.add_exception(exception)
6
+ end
7
+
8
+ at.clear_mappings
9
+
10
+ at.add_mapping(%r{^spec/.+_spec\.rb$}) do |filename,_|
11
+ filename
12
+ end
13
+
14
+ at.add_mapping(%r{^lib/extlib/(.+)\.rb$}) do |_,match|
15
+ [ "spec/#{match[1]}_spec.rb" ]
16
+ end
17
+
18
+ at.add_mapping(%r{^spec/spec_helper\.rb$}) do
19
+ at.files_matching(%r{^spec/.+_spec\.rb$})
20
+ end
21
+ end
data/History.txt ADDED
@@ -0,0 +1 @@
1
+
data/Manifest.txt ADDED
@@ -0,0 +1,29 @@
1
+ .autotest
2
+ History.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ lib/extlib.rb
7
+ lib/extlib/assertions.rb
8
+ lib/extlib/blank.rb
9
+ lib/extlib/hook.rb
10
+ lib/extlib/inflection.rb
11
+ lib/extlib/lazy_array.rb
12
+ lib/extlib/module.rb
13
+ lib/extlib/object.rb
14
+ lib/extlib/pathname.rb
15
+ lib/extlib/pooling.rb
16
+ lib/extlib/string.rb
17
+ lib/extlib/struct.rb
18
+ lib/extlib/version.rb
19
+ spec/blank_spec.rb
20
+ spec/hook_spec.rb
21
+ spec/inflection_spec.rb
22
+ spec/lazy_array_spec.rb
23
+ spec/module_spec.rb
24
+ spec/object_spec.rb
25
+ spec/pooling_spec.rb
26
+ spec/spec_helper.rb
27
+ spec/string_spec.rb
28
+ spec/struct_spec.rb
29
+ tasks/hoe.rb
data/README.txt ADDED
@@ -0,0 +1,3 @@
1
+ = extlib
2
+
3
+ A support library for DataMapper and DataObjects.
data/Rakefile ADDED
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env ruby
2
+ require 'pathname'
3
+ require 'rubygems'
4
+ require 'rake'
5
+ require Pathname('spec/rake/spectask')
6
+ require Pathname('lib/extlib/version')
7
+
8
+ ROOT = Pathname(__FILE__).dirname.expand_path
9
+
10
+ AUTHOR = "Sam Smoot"
11
+ EMAIL = "ssmoot@gmail.com"
12
+ GEM_NAME = "extlib"
13
+ GEM_VERSION = Extlib::VERSION
14
+ GEM_DEPENDENCIES = [["english", ">=0.2.0"]]
15
+ GEM_CLEAN = "*.gem", "**/.DS_Store"
16
+ GEM_EXTRAS = { :has_rdoc => false }
17
+
18
+ PROJECT_NAME = "extlib"
19
+ PROJECT_URL = "http://extlib.rubyforge.org"
20
+ PROJECT_DESCRIPTION = PROJECT_SUMMARY = "Support Library for DataMapper and DataObjects"
21
+
22
+ require ROOT + 'tasks/hoe'
23
+
24
+ task :default => 'extlib:spec'
25
+ task :spec => 'extlib:spec'
26
+
27
+ desc 'Remove all package, docs and spec products'
28
+ task :clobber_all => %w[ clobber_package clobber_doc extlib:clobber_spec ]
29
+
30
+ namespace :extlib do
31
+ Spec::Rake::SpecTask.new(:spec) do |t|
32
+ t.spec_opts << '--format' << 'specdoc' << '--colour'
33
+ t.spec_opts << '--loadby' << 'random'
34
+ t.spec_files = Pathname.glob(ENV['FILES'] || 'spec/**/*_spec.rb')
35
+
36
+ begin
37
+ t.rcov = ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true
38
+ t.rcov_opts << '--exclude' << 'spec'
39
+ t.rcov_opts << '--text-summary'
40
+ t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
41
+ rescue Exception
42
+ # rcov not installed
43
+ end
44
+ end
45
+ end
46
+
47
+ desc "Generate documentation"
48
+ task :doc do
49
+ begin
50
+ require 'yard'
51
+ exec 'yardoc'
52
+ rescue LoadError
53
+ puts 'You will need to install the latest version of Yard to generate the
54
+ documentation for extlib.'
55
+ end
56
+ end
57
+
58
+ WINDOWS = (RUBY_PLATFORM =~ /win32|mingw|bccwin|cygwin/) rescue nil
59
+ SUDO = WINDOWS ? '' : ('sudo' unless ENV['SUDOLESS'])
60
+
61
+ desc "Install #{GEM_NAME}"
62
+ task :install => :package do
63
+ sh %{#{SUDO} gem install --local pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources}
64
+ end
65
+
66
+ if WINDOWS
67
+ namespace :dev do
68
+ desc 'Install for development (for windows)'
69
+ task :winstall => :gem do
70
+ system %{gem install --no-rdoc --no-ri -l pkg/#{GEM_NAME}-#{GEM_VERSION}.gem}
71
+ end
72
+ end
73
+ end
74
+
75
+ namespace :ci do
76
+
77
+ task :prepare do
78
+ rm_rf ROOT + "ci"
79
+ mkdir_p ROOT + "ci"
80
+ mkdir_p ROOT + "ci/doc"
81
+ mkdir_p ROOT + "ci/cyclomatic"
82
+ mkdir_p ROOT + "ci/token"
83
+ end
84
+
85
+ task :publish do
86
+ out = ENV['CC_BUILD_ARTIFACTS'] || "out"
87
+ mkdir_p out unless File.directory? out
88
+
89
+ mv "ci/unit_rspec_report.html", "#{out}/unit_rspec_report.html"
90
+ mv "ci/unit_coverage", "#{out}/unit_coverage"
91
+ mv "ci/integration_rspec_report.html", "#{out}/integration_rspec_report.html"
92
+ mv "ci/integration_coverage", "#{out}/integration_coverage"
93
+ mv "ci/doc", "#{out}/doc"
94
+ mv "ci/cyclomatic", "#{out}/cyclomatic_complexity"
95
+ mv "ci/token", "#{out}/token_complexity"
96
+ end
97
+
98
+
99
+ Spec::Rake::SpecTask.new("spec:unit" => :prepare) do |t|
100
+ t.spec_opts = ["--format", "specdoc", "--format", "html:#{ROOT}/ci/unit_rspec_report.html", "--diff"]
101
+ t.spec_files = Pathname.glob(ROOT + "spec/unit/**/*_spec.rb")
102
+ unless ENV['NO_RCOV']
103
+ t.rcov = true
104
+ t.rcov_opts << '--exclude' << "spec,gems"
105
+ t.rcov_opts << '--text-summary'
106
+ t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
107
+ t.rcov_opts << '--only-uncovered'
108
+ end
109
+ end
110
+
111
+ Spec::Rake::SpecTask.new("spec:integration" => :prepare) do |t|
112
+ t.spec_opts = ["--format", "specdoc", "--format", "html:#{ROOT}/ci/integration_rspec_report.html", "--diff"]
113
+ t.spec_files = Pathname.glob(ROOT + "spec/integration/**/*_spec.rb")
114
+ unless ENV['NO_RCOV']
115
+ t.rcov = true
116
+ t.rcov_opts << '--exclude' << "spec,gems"
117
+ t.rcov_opts << '--text-summary'
118
+ t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
119
+ t.rcov_opts << '--only-uncovered'
120
+ end
121
+ end
122
+
123
+ task :spec do
124
+ Rake::Task["ci:spec:unit"].invoke
125
+ mv ROOT + "coverage", ROOT + "ci/unit_coverage"
126
+
127
+ Rake::Task["ci:spec:integration"].invoke
128
+ mv ROOT + "coverage", ROOT + "ci/integration_coverage"
129
+ end
130
+
131
+ task :doc do
132
+ require 'yardoc'
133
+ sh 'yardoc'
134
+ end
135
+
136
+ task :saikuro => :prepare do
137
+ system "saikuro -c -i lib -y 0 -w 10 -e 15 -o ci/cyclomatic"
138
+ mv 'ci/cyclomatic/index_cyclo.html', 'ci/cyclomatic/index.html'
139
+
140
+ system "saikuro -t -i lib -y 0 -w 20 -e 30 -o ci/token"
141
+ mv 'ci/token/index_token.html', 'ci/token/index.html'
142
+ end
143
+ end
144
+
145
+ task :ci => ["ci:spec", "ci:doc", "ci:saikuro", :install, :publish]
data/lib/extlib/hook.rb CHANGED
@@ -2,18 +2,21 @@ module Extlib
2
2
  #
3
3
  # TODO: Write more documentation!
4
4
  #
5
- # Currently, before you can set a before or after hook on a method, you must
6
- # register that method as hookable; otherwise, their invocation of the hook
7
- # stack is not created.
5
+ # Overview
6
+ # ========
8
7
  #
9
- # This can be done by calling #register_class_hooks to register one or more
10
- # class methods as hookable or #register_instance_hooks to register one or
11
- # more instance methods as hookable.
8
+ # The Hook module is a very simple set of AOP helpers. Basically, it
9
+ # allows the developer to specify a method or block that should run
10
+ # before or after another method.
12
11
  #
13
- # Eventually, I'll probably have #before, #after, #before_class_method, and
14
- # #after_class_method implicitly call #register_instance_hooks and
15
- # #register_class_hooks respectivly. That way, if the exact hook insertion
16
- # location does not need to be specified, hooks can be added on the fly.
12
+ # Usage
13
+ # =====
14
+ #
15
+ # Halting The Hook Stack
16
+ #
17
+ # Inheritance
18
+ #
19
+ # Other Goodies
17
20
  #
18
21
  # Please bring up any issues regarding Hooks with carllerche on IRC
19
22
  #
@@ -23,6 +26,17 @@ module Extlib
23
26
  base.extend(ClassMethods)
24
27
  base.const_set("CLASS_HOOKS", {}) unless base.const_defined?("CLASS_HOOKS")
25
28
  base.const_set("INSTANCE_HOOKS", {}) unless base.const_defined?("INSTANCE_HOOKS")
29
+ base.class_eval do
30
+ class << self
31
+ def method_added(name)
32
+ process_method_added(name, :instance)
33
+ end
34
+
35
+ def singleton_method_added(name)
36
+ process_method_added(name, :class)
37
+ end
38
+ end
39
+ end
26
40
  end
27
41
 
28
42
  module ClassMethods
@@ -120,6 +134,8 @@ module Extlib
120
134
 
121
135
  # --- Alright kids... the rest is internal stuff ---
122
136
 
137
+ # Returns the correct HOOKS Hash depending on whether we are
138
+ # working with class methods or instance methods
123
139
  def hooks_with_scope(scope)
124
140
  case scope
125
141
  when :class then class_hooks
@@ -136,6 +152,15 @@ module Extlib
136
152
  self.const_get("INSTANCE_HOOKS")
137
153
  end
138
154
 
155
+ # Registers a method as hookable. Registering hooks involves the following
156
+ # process
157
+ #
158
+ # * Create a blank entry in the HOOK Hash for the method.
159
+ # * Define the methods that execute the before and after hook stack.
160
+ # These methods will be no-ops at first, but everytime a new hook is
161
+ # defined, the methods will be redefined to incorporate the new hook.
162
+ # * Redefine the method that is to be hookable so that the hook stacks
163
+ # are invoked approprietly.
139
164
  def register_hook(target_method, scope)
140
165
  if scope == :instance && !method_defined?(target_method)
141
166
  raise ArgumentError, "#{target_method} instance method does not exist"
@@ -147,6 +172,10 @@ module Extlib
147
172
 
148
173
  if hooks[target_method].nil?
149
174
  hooks[target_method] = {
175
+ # We need to keep track of which class in the Inheritance chain the
176
+ # method was declared hookable in. Every time a child declares a new
177
+ # hook for the method, the hook stack invocations need to be redefined
178
+ # in the original Class. See #define_hook_stack_execution_methods
150
179
  :before => [], :after => [], :in => self
151
180
  }
152
181
 
@@ -155,10 +184,14 @@ module Extlib
155
184
  end
156
185
  end
157
186
 
187
+ # Is the method registered as a hookable in the given scope.
158
188
  def registered_as_hook?(target_method, scope)
159
189
  ! hooks_with_scope(scope)[target_method].nil?
160
190
  end
161
191
 
192
+ # Generates names for the various utility methods. We need to do this because
193
+ # the various utility methods should not end in = so, while we're at it, we
194
+ # might as well get rid of all punctuation.
162
195
  def hook_method_name(target_method, prefix, suffix)
163
196
  target_method = target_method.to_s
164
197
 
@@ -166,10 +199,29 @@ module Extlib
166
199
  when '?' then "#{prefix}_#{target_method[0..-2]}_ques_#{suffix}"
167
200
  when '!' then "#{prefix}_#{target_method[0..-2]}_bang_#{suffix}"
168
201
  when '=' then "#{prefix}_#{target_method[0..-2]}_eq_#{suffix}"
169
- else "#{prefix}_#{target_method[0..-2]}_nan_#{suffix}"
202
+ # I add a _nan_ suffix here so that we don't ever encounter
203
+ # any naming conflicts.
204
+ else "#{prefix}_#{target_method[0..-1]}_nan_#{suffix}"
170
205
  end
171
206
  end
172
207
 
208
+ # This will need to be refactored
209
+ def process_method_added(method_name, scope)
210
+ hooks_with_scope(scope).each do |target_method, hooks|
211
+ if hooks[:before].any? { |hook| hook[:name] == method_name }
212
+ define_hook_stack_execution_methods(target_method, scope)
213
+ end
214
+
215
+ if hooks[:after].any? { |hook| hook[:name] == method_name }
216
+ define_hook_stack_execution_methods(target_method, scope)
217
+ end
218
+ end
219
+ end
220
+
221
+ # Defines two methods. One method executes the before hook stack. The other executes
222
+ # the after hook stack. This method will be called many times during the Class definition
223
+ # process. It should be called for each hook that is defined. It will also be called
224
+ # when a hook is redefined (to make sure that the arity hasn't changed).
173
225
  def define_hook_stack_execution_methods(target_method, scope)
174
226
  unless registered_as_hook?(target_method, scope)
175
227
  raise ArgumentError, "#{target_method} has not be registered as a hookable #{scope} method"
@@ -177,9 +229,6 @@ module Extlib
177
229
 
178
230
  hooks = hooks_with_scope(scope)
179
231
 
180
- # before_hook_stack = "execute_before_" + "#{target_method}".sub(/([?!=]?$)/, '_hook_stack\1')
181
- # after_hook_stack = "execute_after_" + "#{target_method}".sub(/([?!=]?$)/, '_hook_stack\1')
182
-
183
232
  before_hooks = hooks[target_method][:before]
184
233
  before_hooks = before_hooks.map{ |info| inline_call(info, scope) }.join("\n")
185
234
 
@@ -200,14 +249,20 @@ module Extlib
200
249
 
201
250
  source = %{class << self\n#{source}\nend} if scope == :class
202
251
 
203
- hooks[target_method][:in].class_eval(source)
252
+ hooks[target_method][:in].class_eval(source, __FILE__, __LINE__)
204
253
  end
205
254
 
255
+ # Returns ruby code that will invoke the hook. It checks the arity of the hook method
256
+ # and passes arguments accordingly.
206
257
  def inline_call(method_info, scope)
258
+ name = method_info[:name]
259
+
207
260
  if scope == :instance
208
- %(#{method_info[:name]}(*args) if self.class <= ObjectSpace._id2ref(#{method_info[:from].object_id}))
261
+ args = method_defined?(name) && instance_method(name).arity != 0 ? '*args' : ''
262
+ %(#{name}(#{args}) if self.class <= ObjectSpace._id2ref(#{method_info[:from].object_id}))
209
263
  else
210
- %(#{method_info[:name]}(*args) if self <= ObjectSpace._id2ref(#{method_info[:from].object_id}))
264
+ args = respond_to?(name) && method(name).arity != 0 ? '*args' : ''
265
+ %(#{name}(#{args}) if self <= ObjectSpace._id2ref(#{method_info[:from].object_id}))
211
266
  end
212
267
  end
213
268
 
@@ -215,8 +270,6 @@ module Extlib
215
270
  args = args_for(method_with_scope(target_method, scope))
216
271
 
217
272
  renamed_target = hook_method_name(target_method, 'hookable_', 'before_advised')
218
- # before_hook_stack = "execute_before_" + "#{target_method}".sub(/([?!=]?$)/, '_hook_stack\1')
219
- # after_hook_stack = "execute_after_" + "#{target_method}".sub(/([?!=]?$)/, '_hook_stack\1')
220
273
 
221
274
  source = <<-EOD
222
275
  def #{target_method}(#{args})
@@ -224,9 +277,9 @@ module Extlib
224
277
  catch(:halt) do
225
278
  #{hook_method_name(target_method, 'execute_before', 'hook_stack')}(#{args})
226
279
  retval = #{renamed_target}(#{args})
227
- #{hook_method_name(target_method, 'execute_after', 'hook_stack')}(#{args})
280
+ #{hook_method_name(target_method, 'execute_after', 'hook_stack')}(retval, #{args})
281
+ retval
228
282
  end
229
- retval
230
283
  end
231
284
  EOD
232
285
 
@@ -234,12 +287,12 @@ module Extlib
234
287
  send(:alias_method, renamed_target, target_method)
235
288
 
236
289
  proxy_module = Module.new
237
- proxy_module.class_eval(source)
290
+ proxy_module.class_eval(source, __FILE__, __LINE__)
238
291
  self.send(:include, proxy_module)
239
292
  else
240
293
  source = %{alias_method :#{renamed_target}, :#{target_method}\n#{source}}
241
294
  source = %{class << self\n#{source}\nend} if scope == :class
242
- class_eval(source)
295
+ class_eval(source, __FILE__, __LINE__)
243
296
  end
244
297
  end
245
298
 
@@ -286,13 +339,13 @@ module Extlib
286
339
 
287
340
  def args_for(method)
288
341
  if method.arity == 0
289
- ""
342
+ "&block"
290
343
  elsif method.arity > 0
291
- "_" << (1 .. method.arity).to_a.join(", _")
344
+ "_" << (1 .. method.arity).to_a.join(", _") << ", &block"
292
345
  elsif (method.arity + 1) < 0
293
- "_" << (1 .. (method.arity).abs - 1).to_a.join(", _") << ", *args"
346
+ "_" << (1 .. (method.arity).abs - 1).to_a.join(", _") << ", *args, &block"
294
347
  else
295
- "*args"
348
+ "*args, &block"
296
349
  end
297
350
  end
298
351
 
@@ -1,10 +1,12 @@
1
1
  class LazyArray # borrowed partially from StrokeDB
2
+ instance_methods.each { |m| undef_method m unless %w[ __id__ __send__ send dup class object_id kind_of? respond_to? assert_kind_of should should_not instance_variable_set instance_variable_get ].include?(m) }
3
+
2
4
  include Enumerable
3
5
 
4
6
  # these methods should return self or nil
5
7
  RETURN_SELF = [ :<<, :clear, :concat, :collect!, :each, :each_index,
6
- :each_with_index, :insert, :map!, :push, :replace, :reject!,
7
- :reverse!, :reverse_each, :sort!, :unshift ]
8
+ :each_with_index, :freeze, :insert, :map!, :push, :replace,
9
+ :reject!, :reverse!, :reverse_each, :sort!, :unshift ]
8
10
 
9
11
  RETURN_SELF.each do |method|
10
12
  class_eval <<-EOS, __FILE__, __LINE__
data/lib/extlib/module.rb CHANGED
@@ -1,19 +1,37 @@
1
1
  class Module
2
- def find_const(nested_name)
3
- self.__nested_constants__[nested_name]
4
- rescue NameError
5
- Object::__nested_constants__[nested_name]
2
+ def find_const(const_name)
3
+ if const_name[0..1] == '::'
4
+ Object.find_const(const_name[2..-1])
5
+ else
6
+ nested_const_lookup(const_name)
7
+ end
6
8
  end
7
9
 
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?
10
+ private
11
+
12
+ # Doesn't do any caching since constants can change with remove_const
13
+ def nested_const_lookup(const_name)
14
+ constants = [ Object ]
15
+
16
+ unless self == Object
17
+ self.name.split('::').each do |part|
18
+ constants.unshift(constants.first.const_get(part))
14
19
  end
15
- h[k] = klass
16
20
  end
21
+
22
+ parts = const_name.split('::')
23
+
24
+ # from most to least specific constant, use each as a base and try
25
+ # to find a constant with the name const_name within them
26
+ constants.each do |const|
27
+ # return the nested constant if available
28
+ return const if parts.all? do |part|
29
+ const = const.const_defined?(part) ? const.const_get(part) : nil
30
+ end
31
+ end
32
+
33
+ # if we get this far then the nested constant was not found
34
+ raise NameError, "uninitialized constant #{const_name}"
17
35
  end
18
-
19
- end # class Module
36
+
37
+ end # class Module