monkey 0.0.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,17 +1,63 @@
1
- = monkey
1
+ = Monkey
2
2
 
3
- Description goes here.
3
+ Monkey is a smart, scoped monkeypatching library. You can define monkeypatches
4
+ via an intuitive DSL, and apply them only to scoped blocks, assured that your
5
+ changes won't leak out to objects outside your scope.
4
6
 
5
- == Note on Patches/Pull Requests
7
+ == Usage
8
+
9
+ === Monkeypatching objects
10
+
11
+ # define the "metaclass" monkeypatch on Object
12
+ Monkey.see(Object) do
13
+ def metaclass
14
+ class << self; self; end
15
+ end
16
+ end
17
+
18
+ # patch Object to include the "metaclass" monkeypatch for the duration of
19
+ # the block
20
+ Monkey.patch(Object, :metaclass) do
21
+ "foo".metaclass # => #<Class:#<String:0x1016d7570>>
22
+ end
23
+
24
+ # the monkeypatch doesn't make it out of the scope
25
+ "foo".metaclass # => NoMethodError
26
+
27
+ # patch Object to include the "metaclass" monkeypatch
28
+ Monkey.patch(Object, :metaclass)
29
+
30
+ # the monkeypatch persists
31
+ "foo".metaclass # => #<Class:#<String:0x1016d7570>>
32
+
33
+ === Monkeypatching classes
6
34
 
7
- * Fork the project.
8
- * Make your feature addition or bug fix.
9
- * Add tests for it. This is important so I don't break it in a
10
- future version unintentionally.
11
- * Commit, do not mess with rakefile, version, or history.
12
- (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
13
- * Send me a pull request. Bonus points for topic branches.
35
+ # define the "foo" and "bar" monkeypatches on the class-level of Object
36
+ Monkey.see(Object.metaclass) do
37
+ def foo
38
+ Kernel.rand
39
+ end
40
+
41
+ def bar
42
+ "hello!"
43
+ end
44
+ end
45
+
46
+ Monkey.patch(Object.metaclass, :foo, :bar) do
47
+ Object.foo # => 0.571499912853919
48
+ Object.bar # => "hello!"
49
+ end
14
50
 
15
- == Copyright
51
+ === Monkeypatching instances
16
52
 
17
- Copyright (c) 2010 Stephen Touset. See LICENSE for details.
53
+ # define the "metaclass" monkeypatch on Object
54
+ Monkey.see(Object) do
55
+ def metaclass
56
+ class << self; self; end
57
+ end
58
+ end
59
+
60
+ a = "foo"
61
+
62
+ Monkey.patch(a, :metaclass) { a.metaclass }
63
+
data/Rakefile CHANGED
@@ -1,52 +1,46 @@
1
1
  require 'rubygems'
2
- require 'rake'
3
2
 
4
- begin
5
- require 'jeweler'
6
- Jeweler::Tasks.new do |gem|
7
- gem.name = "monkey"
8
- gem.summary = %Q{Scopeable monkeypatching support for Ruby}
9
- gem.description = %Q{This is just a dummy package to reserve the name until the project is ready to be released}
10
- gem.email = "stephen@touset.org"
11
- gem.homepage = "http://github.com/stouset/monkey"
12
- gem.authors = ["Stephen Touset"]
13
- # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
14
- end
15
- Jeweler::GemcutterTasks.new
16
- rescue LoadError
17
- puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
18
- end
3
+ require 'rake/gempackagetask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/version_task'
6
+ require 'spec/rake/spectask'
19
7
 
20
- require 'rake/testtask'
21
- Rake::TestTask.new(:test) do |test|
22
- test.libs << 'lib' << 'test'
23
- test.pattern = 'test/**/test_*.rb'
24
- test.verbose = true
8
+ spec = Gem::Specification.new do |s|
9
+ s.name = 'monkey'
10
+ s.version = Version.current
11
+ s.summary = 'A smart, scoped monkeypatching library'
12
+
13
+ s.author = 'Stephen Touset'
14
+ s.email = 'stephen@touset.org'
15
+
16
+ s.files = Dir['[A-Z]*', 'lib/**/*.rb', 'spec/**/*']
17
+
18
+ s.extra_rdoc_files = Dir['*.rdoc']
19
+ s.rdoc_options = %w{ --main README.rdoc }
20
+
21
+ s.add_development_dependency 'version'
22
+ s.add_development_dependency 'rspec'
25
23
  end
26
24
 
27
- begin
28
- require 'rcov/rcovtask'
29
- Rcov::RcovTask.new do |test|
30
- test.libs << 'test'
31
- test.pattern = 'test/**/test_*.rb'
32
- test.verbose = true
33
- end
34
- rescue LoadError
35
- task :rcov do
36
- abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
37
- end
25
+ Rake::GemPackageTask.new(spec) do |gem|
26
+ gem.need_tar = true
38
27
  end
39
28
 
40
- task :test => :check_dependencies
41
-
42
- task :default => :test
29
+ Rake::RDocTask.new do |doc|
30
+ doc.title = "monkey #{Version.current}"
31
+ doc.rdoc_dir = 'doc'
32
+ doc.main = 'README.rdoc'
33
+ doc.rdoc_files.include('*.rdoc')
34
+ doc.rdoc_files.include('lib/**/*.rb')
35
+ end
43
36
 
44
- require 'rake/rdoctask'
45
- Rake::RDocTask.new do |rdoc|
46
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
37
+ Spec::Rake::SpecTask.new(:spec) do |task|
38
+ task.spec_files = FileList['spec/**/*_spec.rb']
39
+ end
47
40
 
48
- rdoc.rdoc_dir = 'rdoc'
49
- rdoc.title = "monkey #{version}"
50
- rdoc.rdoc_files.include('README*')
51
- rdoc.rdoc_files.include('lib/**/*.rb')
41
+ Rake::VersionTask.new do |v|
42
+ v.with_git_tag = true
43
+ v.with_gemspec = spec
52
44
  end
45
+
46
+ task :default => :spec
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.0
1
+ 0.6.0
@@ -0,0 +1,82 @@
1
+ require 'rubygems'
2
+ require 'inline'
3
+
4
+ class Object
5
+ inline(:C) do |builder|
6
+ builder.prefix %{
7
+ static VALUE
8
+ rb_obj_dummy()
9
+ {
10
+ return Qnil;
11
+ }
12
+ }
13
+ builder.add_to_init %{
14
+ rb_define_private_method(rb_cModule, "unextended", rb_obj_dummy, 1);
15
+ }
16
+ builder.c %{
17
+ VALUE unextend(VALUE mod)
18
+ {
19
+ VALUE p, prev;
20
+ Check_Type(mod, T_MODULE);
21
+ if (mod == rb_mKernel)
22
+ rb_raise(rb_eArgError, "unextending Kernel is prohibited");
23
+
24
+
25
+ p = rb_singleton_class(self);
26
+
27
+ while (p) {
28
+ if (p == mod || RCLASS(p)->m_tbl == RCLASS(mod)->m_tbl) {
29
+ RCLASS(prev)->super = RCLASS(p)->super;
30
+ rb_clear_cache();
31
+ rb_funcall(mod, rb_intern("unextended"), 1, self);
32
+ return self;
33
+ }
34
+ prev = p;
35
+ p = RCLASS(p)->super;
36
+ }
37
+ return self;
38
+ }
39
+ }
40
+ end
41
+
42
+ end
43
+
44
+ class Class
45
+
46
+ inline(:C) do |builder|
47
+ builder.prefix %{
48
+ static VALUE
49
+ rb_obj_dummy()
50
+ {
51
+ return Qnil;
52
+ }
53
+ }
54
+ builder.add_to_init %{
55
+ rb_define_private_method(rb_cModule, "unincluded", rb_obj_dummy, 1);
56
+ }
57
+ builder.c %{
58
+ VALUE uninclude(VALUE mod)
59
+ {
60
+ VALUE p, prev;
61
+ Check_Type(mod, T_MODULE);
62
+ if (mod == rb_mKernel)
63
+ rb_raise(rb_eArgError, "unincluding Kernel is prohibited");
64
+
65
+ p = self;
66
+
67
+ while (p) {
68
+ if (p == mod || RCLASS(p)->m_tbl == RCLASS(mod)->m_tbl) {
69
+ RCLASS(prev)->super = RCLASS(p)->super;
70
+ rb_clear_cache();
71
+ rb_funcall(mod, rb_intern("unincluded"), 1, self);
72
+ return self;
73
+ }
74
+ prev = p;
75
+ p = RCLASS(p)->super;
76
+ }
77
+ return self;
78
+ }
79
+ }
80
+ end
81
+
82
+ end
@@ -0,0 +1,5 @@
1
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == "rbx"
2
+ require File.join(File.dirname(__FILE__),'rbxmodexcl')
3
+ else
4
+ require File.join(File.dirname(__FILE__),'mrimodexcl')
5
+ end
@@ -0,0 +1,48 @@
1
+
2
+ class Object
3
+
4
+ def unextend(mod)
5
+ raise ArgumentError, "Module is expected" unless mod.is_a?(Module)
6
+ raise ArgumentError, "unextending Kernel is prohibited" if mod == Kernel
7
+ prev = p = metaclass
8
+ while p
9
+ if (p == mod || (p.respond_to?(:module) && p.module == mod))
10
+ prev.set_superclass(p.direct_superclass)
11
+ # remove cache
12
+ self.methods.each do |name|
13
+ name = self.metaclass.send(:normalize_name,name)
14
+ Rubinius::VM.reset_method_cache(name)
15
+ end
16
+ #
17
+ mod.send(:unextended, self) if mod.respond_to?(:unextended)
18
+ return self
19
+ end
20
+ prev = p
21
+ p = p.direct_superclass
22
+ end
23
+
24
+ end
25
+
26
+ def uninclude(mod)
27
+ raise ArgumentError, "Module is expected" unless mod.is_a?(Module)
28
+ raise ArgumentError, "unincluding Kernel is prohibited" if mod == Kernel
29
+ prev = p = self
30
+ while p
31
+ if (p == mod || (p.respond_to?(:module) && p.module == mod))
32
+ prev.superclass=(p.direct_superclass)
33
+ # remove cache
34
+ self.methods.each do |name|
35
+ name = self.metaclass.send(:normalize_name,name)
36
+ Rubinius::VM.reset_method_cache(name)
37
+ end
38
+ #
39
+ mod.send(:unincluded, self) if mod.respond_to?(:unincluded)
40
+ return self
41
+ end
42
+ prev = p
43
+ p = p.direct_superclass
44
+ end
45
+
46
+ end
47
+
48
+ end
data/lib/monkey.rb CHANGED
@@ -0,0 +1,135 @@
1
+ require 'pathname'
2
+ require 'monkey/ext/modexcl/rbmodexcl'
3
+
4
+ module Monkey
5
+ class MonkeyError < StandardError; end
6
+ class MethodPatchedError < MonkeyError; end
7
+ class MethodMissingError < MonkeyError; end
8
+
9
+ #
10
+ # Allows you to specify a set of monkey patches that can be applied to a
11
+ # class or module. Pass a block to the method and define methods inside the
12
+ # block as normal, and the methods will be available only inside a
13
+ # Monkey#patch block.
14
+ #
15
+ # Raises a Monkey::MethodPatchedError if the +method+ already has a
16
+ # monkeypatch registered on +klass+.
17
+ #
18
+ # Monkey.see(Object) do
19
+ # def metaclass
20
+ # (class << self; self; end)
21
+ # end
22
+ # end
23
+ #
24
+ def self.see(klass, &block)
25
+ mod = Module.new(&block)
26
+ methods = mod.instance_methods(false)
27
+
28
+ methods.each do |method|
29
+ register_patch(klass, method.to_sym, mod)
30
+ end
31
+ end
32
+
33
+ #
34
+ # Patches +object+ to contain the monkeypatched +methods+. If a block is
35
+ # passed, the monkeypatches are only active for the duration of the block,
36
+ # and the return value is the return value of the block.
37
+ #
38
+ # Raises a Monkey::MethodMissingError if a named monkeypatch is not defined.
39
+ #
40
+ # Monkey.patch(Object, :metaclass) do
41
+ # "foo".metaclass # => #<Class:#<String:0x10153caf8>>
42
+ # end
43
+ #
44
+ # "foo".metaclass # => NoMethodError
45
+ #
46
+ def self.patch(object, *methods, &scope)
47
+ # if the object is a class or metaclass, monkeypatch the class, otherwise
48
+ # monkeypatch the metaclass of the object
49
+ if object.kind_of?(Class)
50
+ _patch(object, object, *methods, &scope)
51
+ else
52
+ metaclass = (class << object; self; end)
53
+ klass = object.class
54
+
55
+ _patch(metaclass, klass, *methods, &scope)
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ #
62
+ # Actually applies monkeypatches to +object+. Looks up the +methods+ to be
63
+ # patched in the scope of of +klass+.
64
+ #
65
+ def self._patch(object, klass, *methods, &scope)
66
+ patches = methods.map do |method|
67
+ patch = klass.ancestors.map {|a| patch_for(a, method) }.compact.first
68
+ error_missing(klass, method) if patch.nil?
69
+ patch
70
+ end
71
+
72
+ scope ? scope_patches(object, *patches, &scope) \
73
+ : apply_patches(object, *patches)
74
+ end
75
+
76
+ #
77
+ # Applies +patches+ to +object+, yields, then removes the patches when the
78
+ # block returns.
79
+ #
80
+ def self.scope_patches(object, *patches)
81
+ apply_patches(object, *patches)
82
+ yield
83
+ ensure
84
+ remove_patches(object, *patches)
85
+ end
86
+
87
+ #
88
+ # Applies the Modules +patches+ to +object+.
89
+ #
90
+ def self.apply_patches(object, *patches)
91
+ patches.each {|patch| object.send(:include, patch) }
92
+ end
93
+
94
+ #
95
+ # Removes the Modules +patches+ from +object+.
96
+ #
97
+ def self.remove_patches(object, *patches)
98
+ patches.each {|patch| object.send(:uninclude, patch) }
99
+ end
100
+
101
+ #
102
+ # Registers a monkeypatch +patch+, which should be a module that defines the
103
+ # named +method+. The patch is scoped to +klass+ and is named +method+.
104
+ #
105
+ def self.register_patch(klass, method, patch)
106
+ error_patched(klass, method) if patch_for(klass, method)
107
+
108
+ self.patches[klass][method] = patch
109
+ end
110
+
111
+ #
112
+ # Looks up the monkeypatch for +method+ in +klass+.
113
+ #
114
+ def self.patch_for(klass, method)
115
+ self.patches[klass][method]
116
+ end
117
+
118
+ #
119
+ # A nested hash of existing monkeypatch definitions, first by class, then
120
+ # method name.
121
+ #
122
+ def self.patches
123
+ @patches ||= Hash.new {|h,k| h[k] = Hash.new }
124
+ end
125
+
126
+ def self.error_patched(klass, method)
127
+ raise MethodPatchedError,
128
+ "#{klass}##{method} already has a monkeypatch"
129
+ end
130
+
131
+ def self.error_missing(klass, method)
132
+ raise MethodMissingError,
133
+ "No monkeypatch #{klass}##{method} has been defined"
134
+ end
135
+ end
metadata CHANGED
@@ -1,7 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: monkey
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ hash: 7
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 6
9
+ - 0
10
+ version: 0.6.0
5
11
  platform: ruby
6
12
  authors:
7
13
  - Stephen Touset
@@ -9,57 +15,87 @@ autorequire:
9
15
  bindir: bin
10
16
  cert_chain: []
11
17
 
12
- date: 2010-02-26 00:00:00 -05:00
18
+ date: 2010-07-12 00:00:00 -04:00
13
19
  default_executable:
14
- dependencies: []
15
-
16
- description: This is just a dummy package to reserve the name until the project is ready to be released
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: version
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :development
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: rspec
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id002
49
+ description:
17
50
  email: stephen@touset.org
18
51
  executables: []
19
52
 
20
53
  extensions: []
21
54
 
22
55
  extra_rdoc_files:
23
- - LICENSE
24
56
  - README.rdoc
25
57
  files:
26
- - .document
27
- - .gitignore
28
- - LICENSE
29
- - README.rdoc
30
58
  - Rakefile
59
+ - README.rdoc
31
60
  - VERSION
61
+ - lib/monkey/ext/modexcl/mrimodexcl.rb
62
+ - lib/monkey/ext/modexcl/rbmodexcl.rb
63
+ - lib/monkey/ext/modexcl/rbxmodexcl.rb
32
64
  - lib/monkey.rb
33
- - test/helper.rb
34
- - test/test_monkey.rb
35
65
  has_rdoc: true
36
- homepage: http://github.com/stouset/monkey
66
+ homepage:
37
67
  licenses: []
38
68
 
39
69
  post_install_message:
40
70
  rdoc_options:
41
- - --charset=UTF-8
71
+ - --main
72
+ - README.rdoc
42
73
  require_paths:
43
74
  - lib
44
75
  required_ruby_version: !ruby/object:Gem::Requirement
76
+ none: false
45
77
  requirements:
46
78
  - - ">="
47
79
  - !ruby/object:Gem::Version
80
+ hash: 3
81
+ segments:
82
+ - 0
48
83
  version: "0"
49
- version:
50
84
  required_rubygems_version: !ruby/object:Gem::Requirement
85
+ none: false
51
86
  requirements:
52
87
  - - ">="
53
88
  - !ruby/object:Gem::Version
89
+ hash: 3
90
+ segments:
91
+ - 0
54
92
  version: "0"
55
- version:
56
93
  requirements: []
57
94
 
58
95
  rubyforge_project:
59
- rubygems_version: 1.3.5
96
+ rubygems_version: 1.3.7
60
97
  signing_key:
61
98
  specification_version: 3
62
- summary: Scopeable monkeypatching support for Ruby
63
- test_files:
64
- - test/helper.rb
65
- - test/test_monkey.rb
99
+ summary: A smart, scoped monkeypatching library
100
+ test_files: []
101
+
data/.document DELETED
@@ -1,5 +0,0 @@
1
- README.rdoc
2
- lib/**/*.rb
3
- bin/*
4
- features/**/*.feature
5
- LICENSE
data/.gitignore DELETED
@@ -1,21 +0,0 @@
1
- ## MAC OS
2
- .DS_Store
3
-
4
- ## TEXTMATE
5
- *.tmproj
6
- tmtags
7
-
8
- ## EMACS
9
- *~
10
- \#*
11
- .\#*
12
-
13
- ## VIM
14
- *.swp
15
-
16
- ## PROJECT::GENERAL
17
- coverage
18
- rdoc
19
- pkg
20
-
21
- ## PROJECT::SPECIFIC
data/LICENSE DELETED
@@ -1,20 +0,0 @@
1
- Copyright (c) 2009 Stephen Touset
2
-
3
- Permission is hereby granted, free of charge, to any person obtaining
4
- a copy of this software and associated documentation files (the
5
- "Software"), to deal in the Software without restriction, including
6
- without limitation the rights to use, copy, modify, merge, publish,
7
- distribute, sublicense, and/or sell copies of the Software, and to
8
- permit persons to whom the Software is furnished to do so, subject to
9
- the following conditions:
10
-
11
- The above copyright notice and this permission notice shall be
12
- included in all copies or substantial portions of the Software.
13
-
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/test/helper.rb DELETED
@@ -1,10 +0,0 @@
1
- require 'rubygems'
2
- require 'test/unit'
3
- require 'shoulda'
4
-
5
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
- $LOAD_PATH.unshift(File.dirname(__FILE__))
7
- require 'monkey'
8
-
9
- class Test::Unit::TestCase
10
- end
data/test/test_monkey.rb DELETED
@@ -1,7 +0,0 @@
1
- require 'helper'
2
-
3
- class TestMonkey < Test::Unit::TestCase
4
- should "probably rename this file and start testing for real" do
5
- flunk "hey buddy, you should probably rename this file and start testing for real"
6
- end
7
- end