safe-me 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2010 Dario Rexin
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.rdoc ADDED
@@ -0,0 +1,91 @@
1
+ = safe-me
2
+
3
+ * http://github.com/drexin/safe-me
4
+
5
+ == Description:
6
+
7
+ Add typechecking whenever you need it, without messing up your class files.
8
+
9
+ == Install
10
+
11
+ sudo gem install safe-me
12
+
13
+ == Usage
14
+
15
+ require 'safe-me'
16
+
17
+ If you don't use safe-me with rails you have to manually call
18
+
19
+ SafeMe.init
20
+
21
+ The declaration files have to be put in a folder called 'safe' in the app's root.
22
+ The naming convention for those files is 'class_name_safe.rb'. SafeMe automatically
23
+ loads and evaluates those files on the call of SafeMe.init.
24
+
25
+ == DSL
26
+
27
+ The DSL ist very simple. Let me explain on an example:
28
+
29
+ #safe Class tells SafeMe which class it should work on
30
+ #be sure to have the class in scope
31
+ safe Foo do
32
+
33
+ #for_method takes the name of the method to be made safe
34
+ #as symbol
35
+ for_method :a_method do
36
+
37
+ #for each argument the method takes, you have to call argument
38
+ #with the type as parameter
39
+ argument Integer
40
+ end
41
+ end
42
+
43
+ For now there are 4 kinds of possible argument types.
44
+
45
+ - Classes
46
+
47
+ You can simply pass a class as type to the argument call. There are some special cases:
48
+
49
+ VarArgs: if you have a method with varargs, you have to have to pass VarArgs.ofType(Class) as the type
50
+ Array: if you want type safe arrays, you can pass Array.ofType(Class)
51
+ Hash: for hashes you can pass Hash.ofType(Class, Class) where the first class is for the key and the second for the value
52
+
53
+ - Nilable
54
+
55
+ If you also want to be able to pass nil to a method, if the argument is optional for example, you can pass nilable(Class) as type
56
+
57
+ - RespondsTo
58
+
59
+ If you only want to be sure, that an argument responds to some methods you can pass responds_to(*methods) where *methods should be the method names as symbols
60
+
61
+
62
+ - QuacksLike
63
+
64
+ If you need the same responds_to on multiple methods it can be useful to create a clean room class, that contains those methods and call quacks_like(Class) instead.
65
+ quacks_like ensures, that every public instance method of this class is also available on the object, that gets passed to the method
66
+
67
+
68
+ == License:
69
+
70
+ The MIT License
71
+
72
+ Copyright (c) 2010 Dario Rexin
73
+
74
+ Permission is hereby granted, free of charge, to any person obtaining
75
+ a copy of this software and associated documentation files (the
76
+ "Software"), to deal in the Software without restriction, including
77
+ without limitation the rights to use, copy, modify, merge, publish,
78
+ distribute, sublicense, and/or sell copies of the Software, and to
79
+ permit persons to whom the Software is furnished to do so, subject to
80
+ the following conditions:
81
+
82
+ The above copyright notice and this permission notice shall be
83
+ included in all copies or substantial portions of the Software.
84
+
85
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
87
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
88
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
89
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
90
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
91
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ #
2
+ # To change this template, choose Tools | Templates
3
+ # and open the template in the editor.
4
+
5
+
6
+ require 'rubygems'
7
+ require 'rake'
8
+ require 'rake/clean'
9
+ require 'rake/rdoctask'
10
+ require 'rspec/core/rake_task'
11
+
12
+ Rake::RDocTask.new do |rdoc|
13
+ files =['LICENSE', 'lib/**/*.rb']
14
+ rdoc.rdoc_files.add(files)
15
+ rdoc.title = "safe-me Docs"
16
+ rdoc.rdoc_dir = 'doc/rdoc' # rdoc output folder
17
+ rdoc.options << '--line-numbers'
18
+ end
19
+
20
+ desc "Run all specs"
21
+ RSpec::Core::RakeTask.new('spec') do |t|
22
+ t.rspec_opts = ["--color"]
23
+ end
@@ -0,0 +1,34 @@
1
+ require 'safe-me/var_args'
2
+
3
+ Array.class_eval do
4
+
5
+ class ArrayType
6
+ def initialize type
7
+ @type = type
8
+ end
9
+
10
+ def is_type_of? obj
11
+ obj.kind_of? @type
12
+ end
13
+
14
+ def to_s
15
+ "Array[#{@type}]"
16
+ end
17
+ end
18
+
19
+ alias_method :orig_kind_of?, :kind_of?
20
+
21
+ def kind_of? type
22
+ if type.kind_of?(ArrayType) or type.kind_of?(VarArgs)
23
+ self.each do |e|
24
+ return false if not type.is_type_of?(e)
25
+ end
26
+ else
27
+ orig_kind_of? type
28
+ end
29
+ end
30
+
31
+ def self.ofType type
32
+ ArrayType.new type
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ class Class
2
+ def type_of? obj
3
+ obj.kind_of? self
4
+ end
5
+ end
@@ -0,0 +1,34 @@
1
+ require 'extensions/module'
2
+
3
+ Hash.class_eval do
4
+ class HashType
5
+ def initialize key_type, value_type
6
+ @key_type = key_type
7
+ @value_type = value_type
8
+ end
9
+
10
+ def is_type_of? k, v
11
+ k.kind_of?(@key_type) and v.kind_of?(@value_type)
12
+ end
13
+
14
+ def to_s
15
+ "Hash[#{@key_type}, #{@value_type}]"
16
+ end
17
+ end
18
+
19
+ alias_method :orig_kind_of?, :kind_of?
20
+
21
+ def kind_of? type
22
+ if type.kind_of? HashType
23
+ self.each do |k,v|
24
+ return false if not type.is_type_of?(k, v)
25
+ end
26
+ else
27
+ orig_kind_of? type
28
+ end
29
+ end
30
+
31
+ def self.ofType key_type, value_type
32
+ HashType.new key_type, value_type
33
+ end
34
+ end
@@ -0,0 +1,19 @@
1
+ require 'safe-me/var_args'
2
+
3
+ class Module
4
+ attr_accessor :method_types
5
+ def safe_method(name, safer)
6
+ alias_method "__typeunsafe_#{name}__", name
7
+ module_eval do
8
+ method_types = {} if method_types.nil?
9
+ method_types[name] = safer
10
+ define_method name do |*args, &block|
11
+ method_types[name].check(*args)
12
+ send(:"__typeunsafe_#{name}__",*args,&block)
13
+ end
14
+ alias_method "__typesafe_#{name}__", name
15
+ end
16
+ end
17
+
18
+ private :safe_method
19
+ end
data/lib/safe-me.rb ADDED
@@ -0,0 +1,23 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+
5
+ require 'safe-me/var_args'
6
+ require 'safe-me/version'
7
+ require 'safe-me/safe_loader'
8
+ require 'extensions/module'
9
+ require 'extensions/class'
10
+ require 'extensions/hash'
11
+ require 'extensions/array'
12
+
13
+ if defined?(Rails)
14
+ require 'safe-me/rails'
15
+ end
16
+
17
+ module SafeMe
18
+ def self.init
19
+ Dir.glob("safe/*_safe.rb").each do |f|
20
+ SafeLoader.instance_eval File.read(f)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,18 @@
1
+ module SafeMe
2
+ class DuckType
3
+ def initialize type
4
+ @type = type
5
+ end
6
+
7
+ def type_of? obj
8
+ @type.public_instance_methods.each do |m|
9
+ return false unless obj.class.public_instance_methods.include? m
10
+ end
11
+ true
12
+ end
13
+
14
+ def to_s
15
+ "QuacksLike(#{@type})"
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ module SafeMe
2
+ class NilableType
3
+ def initialize type
4
+ @type = type
5
+ end
6
+
7
+ def type_of? obj
8
+ obj.nil? or obj.kind_of?(@type)
9
+ end
10
+
11
+ def to_s
12
+ "#{@type} or nil"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ module SafeMe
2
+ module Rails
3
+ class Railtie < ::Rails::Railtie
4
+ config.after_initialize do
5
+ SafeMe.init
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ module SafeMe
2
+ class RespondsTo
3
+ def initialize *methods
4
+ @methods = methods
5
+ end
6
+
7
+ def type_of? obj
8
+ @methods.each do |m|
9
+ return false unless obj.respond_to? m
10
+ end
11
+ true
12
+ end
13
+
14
+ def to_s
15
+ "RespondsTo(#{@methods.join(', ')})"
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,22 @@
1
+ require 'safe-me/type_safer'
2
+
3
+ module SafeMe
4
+ module SafeLoader
5
+ def self.safe klass, &block
6
+ m = Methods.new klass
7
+ m.instance_eval &block
8
+ end
9
+
10
+ class Methods
11
+ def initialize klass
12
+ @klass = klass
13
+ end
14
+
15
+ def for_method name, &block
16
+ @klass.class_eval do
17
+ safe_method(name, TypeSafer.new(&block))
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,41 @@
1
+ require 'safe-me/duck_type'
2
+ require 'safe-me/nilable_type'
3
+ require 'safe-me/responds_to'
4
+
5
+ module SafeMe
6
+ class TypeSafer
7
+
8
+ def initialize &block
9
+ @types = []
10
+ instance_eval &block
11
+ end
12
+
13
+ def check *args
14
+ tmp = args
15
+ if @types.last.kind_of?(VarArgs)
16
+ tmp = args.slice(0, @types.size-1) + [args.slice(@types.size-1,args.size)]
17
+ end
18
+ tmp.size.times do |i|
19
+ raise ArgumentError.new("for argument #{i+1} expected type #{@types[i]}") unless @types[i].type_of?(tmp[i])
20
+ end
21
+ end
22
+
23
+ def argument type
24
+ @types << type
25
+ end
26
+
27
+ def quacks_like type
28
+ DuckType.new type
29
+ end
30
+
31
+ def nilable type
32
+ NilableType.new type
33
+ end
34
+
35
+ def responds_to *methods
36
+ RespondsTo.new *methods
37
+ end
38
+
39
+ private :quacks_like, :argument
40
+ end
41
+ end
@@ -0,0 +1,20 @@
1
+
2
+ class VarArgs
3
+
4
+ def is_type_of? obj
5
+ obj.kind_of? @type
6
+ end
7
+
8
+ def self.ofType type
9
+ self.new(type)
10
+ end
11
+
12
+ def to_s
13
+ "VarArgs[#{@type}]"
14
+ end
15
+
16
+ private
17
+ def initialize type
18
+ @type = type
19
+ end
20
+ end
@@ -0,0 +1,6 @@
1
+ module SafeMe
2
+ unless defined?(SafeMe::VERSION)
3
+ VERSION = '0.9.0'
4
+ LIBDIR = File.expand_path(File.dirname(__FILE__) + '/../../lib')
5
+ end
6
+ end
data/safe/Foo_safe.rb ADDED
@@ -0,0 +1,20 @@
1
+ require 'spec/classes/foo'
2
+
3
+ safe Foo do
4
+ for_method :a_method do
5
+ argument quacks_like(Integer)
6
+ argument quacks_like(Integer)
7
+ end
8
+
9
+ for_method :another_method do
10
+ argument nilable(Integer)
11
+ end
12
+
13
+ for_method :give_string do
14
+ argument String
15
+ end
16
+
17
+ for_method :baz do
18
+ argument responds_to :bar
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ class Foo
2
+ def a_method a, b
3
+ "#{a} and #{b} should both be integers."
4
+ end
5
+
6
+ def another_method a
7
+ "#{a} should be an integer or nil."
8
+ end
9
+
10
+ def give_string s
11
+ "#{s} should be a string"
12
+ end
13
+
14
+ def baz a
15
+ "#{a} should respond to :bar"
16
+ end
17
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec/classes/foo'
2
+ require 'safe-me'
3
+
4
+ SafeMe.init
5
+
6
+ describe SafeMe do
7
+ describe "a class with a safe-me description file" do
8
+ describe "a method with quacks_like arguments" do
9
+ it "should only allow arguments, that has the same methods as the specified types" do
10
+ lambda{Foo.new.a_method 3, 4}.should_not raise_error(ArgumentError)
11
+ lambda{Foo.new.a_method "3", 4}.should raise_error(ArgumentError, "for argument 1 expected type QuacksLike(Integer)")
12
+ end
13
+ end
14
+
15
+ describe "a method with a nilable(Type) argument" do
16
+ it "should allow this type, or nil" do
17
+ lambda{Foo.new.another_method 3}.should_not raise_error(ArgumentError)
18
+ lambda{Foo.new.another_method nil}.should_not raise_error(ArgumentError)
19
+ lambda{Foo.new.another_method "foo"}.should raise_error(ArgumentError, "for argument 1 expected type Integer or nil")
20
+ end
21
+ end
22
+
23
+ describe "a method with an argument of a specified type" do
24
+ it "should only allow arguments of that type" do
25
+ lambda{Foo.new.give_string "a string"}.should_not raise_error(ArgumentError)
26
+ lambda{Foo.new.give_string nil}.should raise_error(ArgumentError, "for argument 1 expected type String")
27
+ lambda{Foo.new.give_string 1}.should raise_error(ArgumentError, "for argument 1 expected type String")
28
+ end
29
+ end
30
+
31
+ describe "a method with an argument that should respond to a method" do
32
+ it "should only allow arguments that respond to this method" do
33
+ b = Struct.new("Bar", :bar)
34
+ lambda{Foo.new.baz b.new}.should_not raise_error(ArgumentError)
35
+ lambda{Foo.new.baz 1}.should raise_error(ArgumentError, "for argument 1 expected type RespondsTo(bar)")
36
+ end
37
+ end
38
+ end
39
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: safe-me
3
+ version: !ruby/object:Gem::Version
4
+ hash: 59
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 9
9
+ - 0
10
+ version: 0.9.0
11
+ platform: ruby
12
+ authors:
13
+ - Dario Rexin
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-10-22 00:00:00 +02:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rspec
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
+ description: Add typechecking whenever you need it, without messing up your class files.
36
+ email: dario.rexin@r3-tech.de
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - LICENSE
43
+ - README.rdoc
44
+ files:
45
+ - LICENSE
46
+ - Rakefile
47
+ - lib/extensions/array.rb
48
+ - lib/extensions/class.rb
49
+ - lib/extensions/hash.rb
50
+ - lib/extensions/module.rb
51
+ - lib/safe-me/duck_type.rb
52
+ - lib/safe-me/nilable_type.rb
53
+ - lib/safe-me/rails.rb
54
+ - lib/safe-me/responds_to.rb
55
+ - lib/safe-me/safe_loader.rb
56
+ - lib/safe-me/type_safer.rb
57
+ - lib/safe-me/var_args.rb
58
+ - lib/safe-me/version.rb
59
+ - lib/safe-me.rb
60
+ - spec/classes/Foo.rb
61
+ - spec/safe-me/safe-me_spec.rb
62
+ - safe/Foo_safe.rb
63
+ - README.rdoc
64
+ has_rdoc: true
65
+ homepage: http://github.com/drexin/safe-me
66
+ licenses: []
67
+
68
+ post_install_message:
69
+ rdoc_options: []
70
+
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ hash: 3
79
+ segments:
80
+ - 0
81
+ version: "0"
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ hash: 3
88
+ segments:
89
+ - 0
90
+ version: "0"
91
+ requirements: []
92
+
93
+ rubyforge_project:
94
+ rubygems_version: 1.3.7
95
+ signing_key:
96
+ specification_version: 3
97
+ summary: "#{s.name}-#{s.version}"
98
+ test_files: []
99
+