contraction 0.1.1 → 0.1.2
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/lib/contract.rb +39 -0
- data/lib/contraction.rb +90 -80
- metadata +4 -3
data/lib/contract.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
module Contraction
|
2
|
+
private
|
3
|
+
|
4
|
+
class Contract
|
5
|
+
attr_accessor :type, :name, :message, :contract
|
6
|
+
def initialize(args={})
|
7
|
+
@type = args[:type]
|
8
|
+
@name = args[:name]
|
9
|
+
@message = args[:message]
|
10
|
+
@contract = args[:contract] || ''
|
11
|
+
|
12
|
+
create_checkers!
|
13
|
+
end
|
14
|
+
|
15
|
+
def check!(value, named_args)
|
16
|
+
@type_checker.call(value, named_args) if @type_checker
|
17
|
+
if @contract_checker
|
18
|
+
raise ArgumentError.new(contract_message(value)) unless @contract_checker.call(value, named_args)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def create_checkers!
|
25
|
+
if type
|
26
|
+
@type_checker = lambda { |value, named_args| raise ArgumentError.new(type_message(value)) unless value.is_a?(type) }
|
27
|
+
end
|
28
|
+
@contract_checker = eval("lambda { |result, named_args| #{contract} }") unless contract == ''
|
29
|
+
end
|
30
|
+
|
31
|
+
def type_message(value)
|
32
|
+
"#{name} (#{value.inspect}) must be a #{type}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def contract_message(value)
|
36
|
+
"#{name} (#{message}) must fullfill #{contract.inspect}, but is #{value.inspect}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/contraction.rb
CHANGED
@@ -1,92 +1,102 @@
|
|
1
1
|
require 'string'
|
2
|
+
require 'contract'
|
2
3
|
module Contraction
|
3
|
-
def self.
|
4
|
+
def self.patch_instance_method(mod, method_name)
|
4
5
|
instance = mod.allocate
|
5
|
-
|
6
|
+
args, returns = parse_comments(instance.method(method_name).source_location)
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
next if line == ''
|
19
|
-
break unless line.start_with?('#')
|
20
|
-
break if line.start_with?('##')
|
21
|
-
|
22
|
-
# if m = /^#\s*@return\s+(?<type>\[[^\]]+\])?\s*(?<message>[^{]+)?(\{(?<contract>[^}]+)\})?/.match(line)
|
23
|
-
if m = /^#\s*@return\s+(\[[^\]]+\])?\s*([^{]+)?(\{([^}]+)\})?/.match(line)
|
24
|
-
type = m[1].to_s.gsub(/(\[|\])/, '')
|
25
|
-
type = type == '' ? Object : type.constantize
|
26
|
-
contract = m[4].to_s.strip.gsub('return', "result")
|
27
|
-
contract = contract == '' ? 'true' : contract
|
28
|
-
returns = [type, m[2], contract]
|
29
|
-
# elsif m = /^#\s*@param\s+(?<type>\[[^\]]+\])?\s*(?<name>[^\s]+)\s+(?<message>[^{]+)?(\{(?<contract>[^}]+)\})?/.match(line)
|
30
|
-
elsif m = /^#\s*@param\s+(\[[^\]]+\])?\s*([^\s]+)\s+([^{]+)?(\{([^}]+)\})?/.match(line)
|
31
|
-
type = m[1].to_s.gsub(/(\[|\])/, '')
|
32
|
-
type = type == '' ? Object : type.constantize
|
33
|
-
contract = m[5].to_s.strip.gsub(m[2], "named_args[#{m[2].inspect}]")
|
34
|
-
contract = contract == '' ? 'true' : contract
|
35
|
-
args << [type, m[2], m[3], contract]
|
36
|
-
end
|
8
|
+
arg_names = args.map(&:name)
|
9
|
+
arg_names.each do |name|
|
10
|
+
returns.contract = returns.contract.gsub(name, "named_args[#{name.inspect}]")
|
11
|
+
end
|
12
|
+
|
13
|
+
old_method = mod.instance_method(method_name)
|
14
|
+
|
15
|
+
mod.send(:define_method, method_name) do |*method_args|
|
16
|
+
named_args = args.each_with_index.inject({}) do |h, (arg, index)|
|
17
|
+
h[arg.name] = method_args[index]
|
18
|
+
h
|
37
19
|
end
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
20
|
+
|
21
|
+
args.each { |arg| arg.check!(named_args[arg.name], named_args) }
|
22
|
+
result = old_method.bind(self).call(*method_args)
|
23
|
+
returns.check!(result, named_args)
|
24
|
+
|
25
|
+
result
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.patch_class_method(mod, method_name)
|
30
|
+
args, returns = parse_comments(mod.method(method_name).source_location)
|
31
|
+
arg_names = args.map(&:name)
|
32
|
+
arg_names.each do |name|
|
33
|
+
returns.contract = returns.contract.gsub(name, "named_args[#{name.inspect}]")
|
34
|
+
end
|
35
|
+
|
36
|
+
old_method = mod.method(method_name)
|
37
|
+
|
38
|
+
arg_checks = []
|
39
|
+
result_check = nil
|
40
|
+
mod.define_singleton_method(method_name) do |*method_args|
|
41
|
+
named_args = args.each_with_index.inject({}) do |h, (arg, index)|
|
42
|
+
h[arg.name] = method_args[index]
|
43
|
+
h
|
42
44
|
end
|
43
45
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
raise ArgumentError.new("Return value of #{method_name} (#{message}) must fullfill #{contract.inspect}, but is #{result.inspect}")
|
82
|
-
end
|
83
|
-
}
|
84
|
-
end
|
85
|
-
result_check.call(named_args)
|
86
|
-
|
87
|
-
result
|
46
|
+
args.each { |arg| arg.check!(named_args[arg.name], named_args) }
|
47
|
+
result = old_method.call(*method_args)
|
48
|
+
returns.check!(result, named_args)
|
49
|
+
|
50
|
+
result
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.file_content(filename)
|
55
|
+
@file_contents ||= {}
|
56
|
+
@file_contents[filename] ||= File.read(filename)
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.parse_comments(location)
|
60
|
+
file, line = location
|
61
|
+
filename = File.expand_path(file)
|
62
|
+
|
63
|
+
args = []
|
64
|
+
returns = Contraction::Contract.new()
|
65
|
+
file_content(filename).split("\n")[0..line-2].reverse.each do |line|
|
66
|
+
line = line.strip
|
67
|
+
next if line == ''
|
68
|
+
break unless line.start_with?('#')
|
69
|
+
break if line.start_with?('##')
|
70
|
+
|
71
|
+
if m = /^#\s*@return\s+(\[[^\]]+\])?\s*([^{]+)?(\{([^}]+)\})?/.match(line)
|
72
|
+
type = m[1].to_s.gsub(/(\[|\])/, '')
|
73
|
+
type = type == '' ? Object : type.constantize
|
74
|
+
contract = m[4].to_s.strip.gsub('return', "result")
|
75
|
+
contract = contract == '' ? 'true' : contract
|
76
|
+
returns = Contraction::Contract.new(type: type, name: 'returns', message: m[2], contract: contract)
|
77
|
+
elsif m = /^#\s*@param\s+(\[[^\]]+\])?\s*([^\s]+)\s+([^{]+)?(\{([^}]+)\})?/.match(line)
|
78
|
+
type = m[1].to_s.gsub(/(\[|\])/, '')
|
79
|
+
type = type == '' ? Object : type.constantize
|
80
|
+
contract = m[5].to_s.strip.gsub(m[2], "named_args[#{m[2].inspect}]")
|
81
|
+
contract = contract == '' ? 'true' : contract
|
82
|
+
args << Contraction::Contract.new(type: type, name: m[2], message: m[3], contract: contract)
|
88
83
|
end
|
89
84
|
end
|
85
|
+
args.reverse!
|
86
|
+
return [args, returns]
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.included(mod)
|
90
|
+
instance_methods = (mod.instance_methods - Object.instance_methods - Contraction.instance_methods)
|
91
|
+
|
92
|
+
instance_methods.each do |method_name|
|
93
|
+
patch_instance_method(mod, method_name)
|
94
|
+
end
|
95
|
+
|
96
|
+
class_methods = (mod.methods - Object.methods - Contraction.methods)
|
97
|
+
class_methods.each do |method_name|
|
98
|
+
patch_class_method(mod, method_name)
|
99
|
+
end
|
90
100
|
end
|
91
101
|
end
|
92
102
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: contraction
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-04-
|
12
|
+
date: 2013-04-19 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: Using RDoc documentation as your contract definition, you get solid code,
|
15
15
|
and good docs. Win-win!
|
@@ -21,6 +21,7 @@ extra_rdoc_files:
|
|
21
21
|
files:
|
22
22
|
- README.md
|
23
23
|
- contraction.rb
|
24
|
+
- lib/contract.rb
|
24
25
|
- lib/contraction.rb
|
25
26
|
- lib/string.rb
|
26
27
|
homepage: https://github.com/thomasluce/contraction
|
@@ -37,7 +38,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
37
38
|
version: '0'
|
38
39
|
segments:
|
39
40
|
- 0
|
40
|
-
hash:
|
41
|
+
hash: 2360464299481397474
|
41
42
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
42
43
|
none: false
|
43
44
|
requirements:
|