safe-me 0.9.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 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
+