watchdog 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE.txt +22 -0
- data/README.md +71 -0
- data/Rakefile +39 -0
- data/lib/watchdog.rb +38 -0
- data/lib/watchdog/error.rb +16 -0
- data/lib/watchdog/german_shepard.rb +28 -0
- data/lib/watchdog/version.rb +3 -0
- data/spec/watchdog_spec.rb +94 -0
- data/watchdog.gemspec +17 -0
- metadata +65 -0
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT LICENSE
|
2
|
+
|
3
|
+
Copyright (c) 2011 Wegowise Inc.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
Description
|
2
|
+
===========
|
3
|
+
|
4
|
+
Watchdog ensures your extensions and monkey patches don't redefine existing methods as well as get redefined by others.
|
5
|
+
|
6
|
+
Install
|
7
|
+
=======
|
8
|
+
|
9
|
+
$ gem install watchdog
|
10
|
+
|
11
|
+
Usage
|
12
|
+
=====
|
13
|
+
|
14
|
+
Let's say we want to add an instance method to String with extension module ToDate:
|
15
|
+
|
16
|
+
module ToDate
|
17
|
+
def to_date
|
18
|
+
Date.parse(self)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
String.send :include, ToDate
|
23
|
+
|
24
|
+
What happens if String#to_date already exists? What happens if another gem redefines that method
|
25
|
+
later? Breakage.
|
26
|
+
|
27
|
+
Watchdog watches over these concerns with a simple extend:
|
28
|
+
|
29
|
+
module ToDate
|
30
|
+
extend Watchdog
|
31
|
+
|
32
|
+
def to_date
|
33
|
+
Date.parse(self)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
String.send :include, ToDate
|
38
|
+
|
39
|
+
Now if String#to_date already exists, watchdog raises a runtime error. Same goes if someone tries to
|
40
|
+
redefine the method later:
|
41
|
+
|
42
|
+
>> class String; def to_date; p 'HAHAHA'; end; end
|
43
|
+
Watchdog::ExtensionMethodExistsError: Date not allowed to redefine extension method from ToDate#to_date
|
44
|
+
./lib/watchdog.rb:13:in `guard'
|
45
|
+
./lib/watchdog/german_shepard.rb:23:in `method_added'
|
46
|
+
(ripl):3
|
47
|
+
|
48
|
+
Watchdog also guards over extension modules that define class methods:
|
49
|
+
|
50
|
+
|
51
|
+
module DaysTillXmas
|
52
|
+
extend Watchdog
|
53
|
+
|
54
|
+
def days_till_xmas
|
55
|
+
Date.new(Date.today.year, 12, 25) - Date.today
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
Date.extend DaysTillXmas
|
60
|
+
# Date.days_till_xmas -> 253 NOOOO...
|
61
|
+
|
62
|
+
Credits
|
63
|
+
=======
|
64
|
+
|
65
|
+
Thanks to Wegowise for open-source time to make this possible!
|
66
|
+
|
67
|
+
|
68
|
+
License
|
69
|
+
=======
|
70
|
+
|
71
|
+
See LICENSE file
|
data/Rakefile
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
def gemspec_name
|
5
|
+
@gemspec_name ||= Dir['*.gemspec'][0]
|
6
|
+
end
|
7
|
+
|
8
|
+
def gemspec
|
9
|
+
@gemspec ||= eval(File.read(gemspec_name), binding, gemspec_name)
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "Build the gem"
|
13
|
+
task :gem=>:gemspec do
|
14
|
+
sh "gem build #{gemspec_name}"
|
15
|
+
FileUtils.mkdir_p 'pkg'
|
16
|
+
FileUtils.mv "#{gemspec.name}-#{gemspec.version}.gem", 'pkg'
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "Install the gem locally"
|
20
|
+
task :install => :gem do
|
21
|
+
sh %{gem install pkg/#{gemspec.name}-#{gemspec.version}}
|
22
|
+
end
|
23
|
+
|
24
|
+
desc "Generate the gemspec"
|
25
|
+
task :generate do
|
26
|
+
puts gemspec.to_ruby
|
27
|
+
end
|
28
|
+
|
29
|
+
desc "Validate the gemspec"
|
30
|
+
task :gemspec do
|
31
|
+
gemspec.validate
|
32
|
+
end
|
33
|
+
|
34
|
+
desc 'Run tests'
|
35
|
+
task :test do |t|
|
36
|
+
sh 'rspec spec'
|
37
|
+
end
|
38
|
+
|
39
|
+
task :default => :test
|
data/lib/watchdog.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'watchdog/error'
|
2
|
+
require 'watchdog/german_shepard'
|
3
|
+
require 'watchdog/version'
|
4
|
+
|
5
|
+
module Watchdog
|
6
|
+
# Maps objects or modules to their extension modules
|
7
|
+
class <<self; attr_accessor :extensions; end
|
8
|
+
self.extensions = {}
|
9
|
+
|
10
|
+
# Guards extension methods from being overwritten
|
11
|
+
def self.guard(obj, meth)
|
12
|
+
if extensions[obj].instance_methods.map(&:to_sym).include?(meth)
|
13
|
+
raise ExtensionMethodExistsError.new(meth, obj, extensions[obj])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.setup_guard(extension, extended)
|
18
|
+
extensions[extended] = extension
|
19
|
+
extended.extend GermanShepard
|
20
|
+
end
|
21
|
+
|
22
|
+
def append_features(mod)
|
23
|
+
Watchdog.setup_guard(self, mod)
|
24
|
+
existing = mod.private_instance_methods + mod.instance_methods
|
25
|
+
(existing & self.instance_methods).each do |m|
|
26
|
+
raise MethodExistsError.new(m, self, mod)
|
27
|
+
end
|
28
|
+
super
|
29
|
+
end
|
30
|
+
|
31
|
+
def extend_object(obj)
|
32
|
+
Watchdog.setup_guard(self, obj)
|
33
|
+
self.instance_methods.each do |m|
|
34
|
+
raise MethodExistsError.new(m, self, obj) if obj.respond_to?(m, true)
|
35
|
+
end
|
36
|
+
super
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Watchdog
|
2
|
+
class Error < StandardError
|
3
|
+
def initialize(meth, from, to)
|
4
|
+
mtype = to.is_a?(Module) ? '#' : '.'
|
5
|
+
super self.class::MESSAGE % [from, "#{to}#{mtype}#{meth}"]
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class MethodExistsError < Error
|
10
|
+
MESSAGE = "%s not allowed to redefine existing method %s"
|
11
|
+
end
|
12
|
+
|
13
|
+
class ExtensionMethodExistsError < Error
|
14
|
+
MESSAGE = "%s not allowed to redefine extension method from %s"
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Watchdog
|
2
|
+
module GermanShepard
|
3
|
+
def self.create_guard(meth, obj)
|
4
|
+
meta = class <<obj; self end
|
5
|
+
original = meta.instance_method(meth)
|
6
|
+
meta.send(:define_method, meth) do |m|
|
7
|
+
Watchdog.guard(self, m)
|
8
|
+
original.bind(obj).call(m)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.extend_object(obj)
|
13
|
+
if obj.respond_to? :method_added
|
14
|
+
create_guard :method_added, obj
|
15
|
+
elsif obj.respond_to? :singleton_method_added
|
16
|
+
create_guard :singleton_method_added, obj
|
17
|
+
end
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
[:singleton_method_added, :method_added].each do |m|
|
22
|
+
define_method(m) do |meth|
|
23
|
+
Watchdog.guard(self, meth)
|
24
|
+
super(meth)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'watchdog'
|
2
|
+
|
3
|
+
describe Watchdog do
|
4
|
+
def create_methods(obj, *meths)
|
5
|
+
obj_class = obj.is_a?(Module) ? obj : class << obj; self; end
|
6
|
+
meths.each {|e| obj_class.send(:define_method, e) {} }
|
7
|
+
obj
|
8
|
+
end
|
9
|
+
|
10
|
+
context "when extended" do
|
11
|
+
let(:extensions) { create_methods Module.new.extend(Watchdog), :blah }
|
12
|
+
|
13
|
+
context "new extension method" do
|
14
|
+
it "doesn't raise error if no existing methods conflict" do
|
15
|
+
existing = Object.new
|
16
|
+
lambda { existing.extend extensions }.should_not raise_error
|
17
|
+
end
|
18
|
+
|
19
|
+
it "raises error if existing public methods conflict" do
|
20
|
+
existing = create_methods Object.new, :blah
|
21
|
+
lambda { existing.extend extensions }.should raise_error(Watchdog::MethodExistsError, /\.blah/)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "raises error if existing private methods conflict" do
|
25
|
+
existing = create_methods Object.new, :blah
|
26
|
+
class <<existing; self.send :private, :blah; end
|
27
|
+
lambda { existing.extend extensions }.should raise_error(Watchdog::MethodExistsError)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "new method" do
|
32
|
+
it "doesn't raise error if it doesn't redefine extension methods" do
|
33
|
+
existing = Object.new.extend extensions
|
34
|
+
lambda { def existing.bling; end }.should_not raise_error
|
35
|
+
end
|
36
|
+
|
37
|
+
it "raises error if it redefines extension methods" do
|
38
|
+
existing = Object.new.extend extensions
|
39
|
+
lambda { def existing.blah; end }.should raise_error(Watchdog::ExtensionMethodExistsError)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "raises error if it redefines extension methods for object with singleton_method_added" do
|
43
|
+
existing = Object.new
|
44
|
+
def existing.singleton_method_added(meth); end
|
45
|
+
existing.extend extensions
|
46
|
+
lambda { def existing.blah; end }.should raise_error(Watchdog::ExtensionMethodExistsError)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "when included" do
|
52
|
+
let(:extensions) { create_methods Module.new.extend(Watchdog), :blah }
|
53
|
+
|
54
|
+
context "new extension method" do
|
55
|
+
it "doesn't raise error if no existing methods conflict" do
|
56
|
+
existing = Module.new
|
57
|
+
lambda { existing.send :include, extensions }.should_not raise_error
|
58
|
+
end
|
59
|
+
|
60
|
+
it "raises error if existing public methods conflict" do
|
61
|
+
existing = create_methods Module.new, :blah
|
62
|
+
lambda { existing.send :include, extensions }.should raise_error(Watchdog::MethodExistsError, /#blah/)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "raises error if existing private methods conflict" do
|
66
|
+
existing = create_methods Module.new, :blah
|
67
|
+
existing.send :private, :blah
|
68
|
+
lambda { existing.send :include, extensions }.should raise_error(Watchdog::MethodExistsError)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "new method" do
|
73
|
+
it "doesn't raise error if it doesn't redefine extension methods" do
|
74
|
+
existing = Module.new.send :include, extensions
|
75
|
+
lambda { existing.send(:define_method, :bling) { } }.should_not raise_error
|
76
|
+
end
|
77
|
+
|
78
|
+
it "raises error if it redefines extension methods" do
|
79
|
+
existing = Module.new.send :include, extensions
|
80
|
+
lambda {
|
81
|
+
existing.send(:define_method, :blah) { }
|
82
|
+
}.should raise_error(Watchdog::ExtensionMethodExistsError)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "raises error if it redefines extension methods for module with method_added" do
|
86
|
+
existing = Module.new { def self.method_added(meth); end }
|
87
|
+
existing.send :include, extensions
|
88
|
+
lambda {
|
89
|
+
existing.send(:define_method, :blah) { }
|
90
|
+
}.should raise_error(Watchdog::ExtensionMethodExistsError)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/watchdog.gemspec
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require 'rubygems' unless defined? Gem
|
3
|
+
require File.dirname(__FILE__) + "/lib/watchdog/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "watchdog"
|
7
|
+
s.version = Watchdog::VERSION
|
8
|
+
s.authors = ["Gabriel Horner"]
|
9
|
+
s.email = "ghorner@wegowise.com"
|
10
|
+
s.homepage = "http://github.com/wegowise/watchdog"
|
11
|
+
s.summary = "Watches over your extensions and monkey patches"
|
12
|
+
s.description = "Watchdog ensures your extensions and monkey patches don't redefine existing methods as well as get redefined by others."
|
13
|
+
s.required_rubygems_version = ">= 1.3.6"
|
14
|
+
s.files = Dir.glob(%w[{lib,spec}/**/*.rb [A-Z]*.{txt,rdoc,md} *.gemspec]) + %w{Rakefile}
|
15
|
+
s.extra_rdoc_files = ["README.md", "LICENSE.txt"]
|
16
|
+
s.license = 'MIT'
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: watchdog
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Gabriel Horner
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-04-16 00:00:00 -04:00
|
14
|
+
default_executable:
|
15
|
+
dependencies: []
|
16
|
+
|
17
|
+
description: Watchdog ensures your extensions and monkey patches don't redefine existing methods as well as get redefined by others.
|
18
|
+
email: ghorner@wegowise.com
|
19
|
+
executables: []
|
20
|
+
|
21
|
+
extensions: []
|
22
|
+
|
23
|
+
extra_rdoc_files:
|
24
|
+
- README.md
|
25
|
+
- LICENSE.txt
|
26
|
+
files:
|
27
|
+
- lib/watchdog/error.rb
|
28
|
+
- lib/watchdog/german_shepard.rb
|
29
|
+
- lib/watchdog/version.rb
|
30
|
+
- lib/watchdog.rb
|
31
|
+
- spec/watchdog_spec.rb
|
32
|
+
- LICENSE.txt
|
33
|
+
- README.md
|
34
|
+
- watchdog.gemspec
|
35
|
+
- Rakefile
|
36
|
+
has_rdoc: true
|
37
|
+
homepage: http://github.com/wegowise/watchdog
|
38
|
+
licenses:
|
39
|
+
- MIT
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options: []
|
42
|
+
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
none: false
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: "0"
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 1.3.6
|
57
|
+
requirements: []
|
58
|
+
|
59
|
+
rubyforge_project:
|
60
|
+
rubygems_version: 1.6.1
|
61
|
+
signing_key:
|
62
|
+
specification_version: 3
|
63
|
+
summary: Watches over your extensions and monkey patches
|
64
|
+
test_files: []
|
65
|
+
|