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 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,3 @@
1
+ module Watchdog
2
+ VERSION = '0.1.0'
3
+ 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
+