ruby_contracts 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/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ruby_contracts.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Nicolas ZERMATI
2
+
3
+ MIT License
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,47 @@
1
+ # RubyContracts
2
+
3
+ RubyContracts is a small DSL to add pre & post condition to methods. It try to bring some design by contract in the Ruby world.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'ruby_contracts'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install ruby_contracts
18
+
19
+ ## Usage
20
+
21
+ class Example
22
+ include Contracts::DSL
23
+
24
+ def initialize(val)
25
+ @value = val
26
+ end
27
+
28
+ type :in => [Integer, Proc], :out => Numeric
29
+ pre "a must be > the example's value" do |a| a < @value end
30
+ post "result must be between a and val" do |result, a| result >= a && result < @value end
31
+
32
+ def method(a)
33
+ a * 3 + yield - 4
34
+ end
35
+ end
36
+
37
+ Run this sample with `ENABLE_ASSERTION=1 bundle exec ruby example.rb`.
38
+
39
+ Without the environment variable `ENABLE_ASSERTION` you have zero overhead and zero verification.
40
+
41
+ ## Contributing
42
+
43
+ 1. Fork it
44
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
45
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
46
+ 4. Push to the branch (`git push origin my-new-feature`)
47
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,3 @@
1
+ module RubyContracts
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,111 @@
1
+ require "ruby_contracts/version"
2
+
3
+ module Contracts
4
+ class Error < Exception ; end
5
+
6
+ module DSL
7
+ def self.included(base)
8
+ base.extend Contracts::DSL::ClassMethods
9
+ end
10
+
11
+ module ClassMethods
12
+ def self.extended(base)
13
+ base.instance_eval "@__contracts = {:before => [], :after => []}"
14
+ end
15
+
16
+ def __contract_failure!(name, message, result, *args)
17
+ args.pop if args.last.kind_of?(Proc)
18
+ raise Contracts::Error.new("#{self}##{name}(#{args.join ', '}) => #{result || "?"} ; #{message}.")
19
+ end
20
+
21
+ def type(options)
22
+ @__contracts[:before] << [:type, options[:in]] if ENV['ENABLE_ASSERTION'] && options.has_key?(:in)
23
+ @__contracts[:after] << [:type, options[:out]] if ENV['ENABLE_ASSERTION'] && options.has_key?(:out)
24
+ end
25
+
26
+ def pre(message=nil, &block)
27
+ @__contracts[:before] << [:params, message, block] if ENV['ENABLE_ASSERTION']
28
+ end
29
+
30
+ def post(message=nil, &block)
31
+ @__contracts[:after] << [:result, message, block] if ENV['ENABLE_ASSERTION']
32
+ end
33
+
34
+ def method_added(name)
35
+ if ENV['ENABLE_ASSERTION'] && (!@__contracts[:before].empty? || !@__contracts[:after].empty?)
36
+ __contracts_copy = @__contracts
37
+ @__contracts = {:before => [], :after => []}
38
+
39
+ original_method_name = "#{name}__with_contracts"
40
+ define_method(original_method_name, instance_method(name))
41
+
42
+
43
+ count = 0
44
+ before_contracts = __contracts_copy[:before].reduce("") do |code, contract|
45
+ type, *args = contract
46
+ case type
47
+ when :type
48
+ classes = args[0]
49
+ code << "if __args.size < #{classes.size} then\n"
50
+ code << " self.class.__contract_failure!('#{name}', \"need at least #{classes.size} arguments (%i given)\" % [__args.size], nil, *args)\n"
51
+ code << "else\n"
52
+ conditions = []
53
+ classes.each_with_index{ |klass, i| conditions << "__args[#{i}].kind_of?(#{klass})" }
54
+ code << " if !(#{conditions.join(' && ')}) then\n"
55
+ code << " self.class.__contract_failure!('#{name}', 'input type error', nil, *__args)\n"
56
+ code << " end\n"
57
+ code << "end\n"
58
+ code
59
+
60
+ when :params
61
+ # Define a method that verify the assertion
62
+ contract_method_name = "__verify_contract_#{name}_in_#{count = count + 1}"
63
+ define_method(contract_method_name) { |*params| self.instance_exec(*params, &args[1]) }
64
+
65
+ code << "if !#{contract_method_name}(*__args) then\n"
66
+ code << " self.class.__contract_failure!('#{name}', \"invalid precondition: #{args[0]}\", nil, *__args)\n"
67
+ code << "end"
68
+ code
69
+ else
70
+ code
71
+ end
72
+ end
73
+
74
+ after_contracts = __contracts_copy[:after].reduce("") do |code, contract|
75
+ type, *args = contract
76
+ case type
77
+ when :type
78
+ code << "if !result.kind_of?(#{args[0]}) then\n"
79
+ code << "self.class.__contract_failure!(name, \"result must be a kind of '#{args[0]}' not '%s'\" % [result.class.to_s], result, *__args)\n"
80
+ code << "end\n"
81
+ code
82
+ when :result
83
+ # Define a method that verify the assertion
84
+ contract_method_name = "__verify_contract_#{name}_out_#{count = count + 1}"
85
+ define_method(contract_method_name) { |*params| self.instance_exec(*params, &args[1]) }
86
+
87
+ code << "if !#{contract_method_name}(result, *__args) then\n"
88
+ code << " self.class.__contract_failure!('#{name}', \"invalid postcondition: #{args[0]}\", result, *__args)\n"
89
+ code << "end"
90
+ code
91
+ else
92
+ code
93
+ end
94
+ end
95
+
96
+ method = <<-EOM
97
+ def #{name}(*args, &block)
98
+ __args = block.nil? ? args : args + [block]
99
+ #{before_contracts}
100
+ result = #{original_method_name}(*args, &block)
101
+ #{after_contracts}
102
+ return result
103
+ end
104
+ EOM
105
+
106
+ class_eval method
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ $:.push File.expand_path("../lib", __FILE__)
4
+
5
+ require 'ruby_contracts/version'
6
+
7
+ Gem::Specification.new do |gem|
8
+ gem.authors = ["Nicolas ZERMATI"]
9
+ gem.email = ["nicoolas25@gmail.com"]
10
+ gem.description = %q{Micro DSL to add pre & post condition to methods. It try to bring some design by contract in the Ruby world.}
11
+ gem.summary = %q{Micro DSL to add pre & post condition to methods. It try to bring some design by contract in the Ruby world.}
12
+ gem.homepage = ""
13
+
14
+ gem.files = `git ls-files`.split($\)
15
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.name = "ruby_contracts"
18
+ gem.require_paths = ["lib"]
19
+ gem.version = RubyContracts::VERSION
20
+ end
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby_contracts
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Nicolas ZERMATI
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-03 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Micro DSL to add pre & post condition to methods. It try to bring some
15
+ design by contract in the Ruby world.
16
+ email:
17
+ - nicoolas25@gmail.com
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - .gitignore
23
+ - Gemfile
24
+ - LICENSE
25
+ - README.md
26
+ - Rakefile
27
+ - lib/ruby_contracts.rb
28
+ - lib/ruby_contracts/version.rb
29
+ - ruby_contracts.gemspec
30
+ homepage: ''
31
+ licenses: []
32
+ post_install_message:
33
+ rdoc_options: []
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ! '>='
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ required_rubygems_version: !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ requirements: []
49
+ rubyforge_project:
50
+ rubygems_version: 1.8.23
51
+ signing_key:
52
+ specification_version: 3
53
+ summary: Micro DSL to add pre & post condition to methods. It try to bring some design
54
+ by contract in the Ruby world.
55
+ test_files: []
56
+ has_rdoc: