contraction 0.1.2 → 0.2.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.
- checksums.yaml +7 -0
- data/README.md +41 -6
- data/lib/contract.rb +60 -23
- data/lib/contraction.rb +30 -83
- data/lib/parser.rb +57 -0
- data/lib/parser/lines.rb +64 -0
- data/lib/parser/type.rb +138 -0
- data/lib/string.rb +1 -0
- metadata +10 -12
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a6acaeb85e36db1453f073fa71abf2c75fbf4079
|
4
|
+
data.tar.gz: dcfcd8b39e26bd1c4544f000ba13a3442911ccf6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 40dde6d63f5c70f67064246e74c57ce004475a4a8f7c26660e0e5e5659d9a83ffd342b945606f99f667c6e2221b801d8b3c24041700aed9a3b3aa27166892961
|
7
|
+
data.tar.gz: a7be353ccd6f064d4830d58e3cbe71ce4f33c2f80e94dbcf5fc603fbfb68525d88275b56f77302aa93f5d53a6564f17ebcbcffb4e696405aee3db562eab3adae
|
data/README.md
CHANGED
@@ -40,15 +40,50 @@ names of the params for their values, and `return` for the return value. If you
|
|
40
40
|
provide a type for either the param or the return, type-checking will be
|
41
41
|
enforced as well. A full-qualified type is recommended (`Foo::Bar` instead of `Bar`.)
|
42
42
|
|
43
|
-
|
44
|
-
|
43
|
+
Ruby and the splendiferous always-open class
|
44
|
+
============================================
|
45
|
+
|
46
|
+
Because a class in Ruby can never really be "fully loaded", there are strange
|
47
|
+
cases where Contraction may not build contracts for all the methods that you
|
48
|
+
want it to. For example, if some third-party code modifies your class to add
|
49
|
+
methods at run-time, after Contraction has already been loaded. In this case,
|
50
|
+
if you would like contracts to be enabled for those classes, you can update the
|
51
|
+
annotated methods by calling:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
class MySuperCoolClass
|
55
|
+
...
|
56
|
+
|
57
|
+
include Contraction
|
58
|
+
end
|
59
|
+
|
60
|
+
...
|
61
|
+
|
62
|
+
MySuperCoolClass.update_contracts
|
63
|
+
```
|
64
|
+
|
65
|
+
Please bear in mind that you will re-incur the overhead of parsing the RDoc
|
66
|
+
docs for each method.
|
67
|
+
|
68
|
+
Warning about speed/overhead
|
69
|
+
============================
|
45
70
|
|
46
71
|
This will slow things down. All-in-all you're looking at about an 8x increase
|
47
72
|
in overhead vs just calling a function. It is not recommended that you use this
|
48
73
|
for every method in a deeply-nested code-path. This overhead is more-or-less
|
49
74
|
in-line with other Ruby design-by-contract libraries, however, and with the
|
50
|
-
added benefit of free documentation.
|
75
|
+
added benefit of free documentation. It is recommended that you have some
|
76
|
+
concept of environment (staging, dev, production, etc.), and only include
|
77
|
+
Contraction in development-like environments:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
require 'rubygems'
|
81
|
+
require 'contraction'
|
82
|
+
|
83
|
+
class MySuperCoolClass
|
84
|
+
...
|
85
|
+
|
86
|
+
include Contraction if Rails.env.development?
|
87
|
+
end
|
88
|
+
```
|
51
89
|
|
52
|
-
Also, this is not super-heavily tested. I've been using it myself and thought I
|
53
|
-
would release it to the world, but I don't do a lot of RDoc-fu, so YMMV.
|
54
|
-
Pull-requests welcome.
|
data/lib/contract.rb
CHANGED
@@ -1,39 +1,76 @@
|
|
1
1
|
module Contraction
|
2
|
-
private
|
3
|
-
|
4
2
|
class Contract
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
@
|
11
|
-
|
12
|
-
|
3
|
+
attr_reader :rules, :mod, :method_name, :params
|
4
|
+
|
5
|
+
# @params [Array<TypedLine>] rules The individual lines that define the
|
6
|
+
# contract.
|
7
|
+
def initialize(rules, mod, method_name)
|
8
|
+
@rules = rules
|
9
|
+
@mod = mod
|
10
|
+
@method_name = method_name
|
11
|
+
|
12
|
+
update_rule_values
|
13
|
+
get_method_definition
|
13
14
|
end
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
# Test weather the arguments to a method are of the correct type, and meet
|
17
|
+
# the correct contractual obligations.
|
18
|
+
def valid_args?(*method_args)
|
19
|
+
return true if @rules.nil?
|
20
|
+
named_args = params.each_with_index.inject({}) do |h, (param, index)|
|
21
|
+
h[param.to_s] = method_args[index]
|
22
|
+
h
|
23
|
+
end
|
24
|
+
|
25
|
+
b = binding
|
26
|
+
param_rules.all? do |rule|
|
27
|
+
raise ArgumentError.new("#{rule.name} (#{named_args[rule.name].inspect}) must be a #{rule.type}") unless rule.valid?(named_args[rule.name])
|
28
|
+
rule.evaluate_in_context(b, method_name, named_args[rule.name])
|
19
29
|
end
|
20
30
|
end
|
21
31
|
|
22
|
-
|
32
|
+
# Tests weather or not the return value is of the correct type and meets
|
33
|
+
# the correct contractual obligations.
|
34
|
+
def valid_return?(*method_args, result)
|
35
|
+
named_args = params.each_with_index.inject({}) do |h, (param, index)|
|
36
|
+
h[param] = method_args[index]
|
37
|
+
h
|
38
|
+
end
|
23
39
|
|
24
|
-
|
25
|
-
|
26
|
-
|
40
|
+
return true unless return_rule
|
41
|
+
unless return_rule.valid?(result)
|
42
|
+
raise ArgumentError.new("Return value of #{method_name} must be a #{return_rule.type}")
|
43
|
+
end
|
44
|
+
if return_rule.contract
|
45
|
+
b = binding
|
46
|
+
return_rule.evaluate_in_context(b, method_name, result)
|
27
47
|
end
|
28
|
-
@contract_checker = eval("lambda { |result, named_args| #{contract} }") unless contract == ''
|
29
48
|
end
|
30
49
|
|
31
|
-
|
32
|
-
|
50
|
+
private
|
51
|
+
|
52
|
+
def return_rule
|
53
|
+
@return_rule ||= @rules.select { |r| r.is_a?(Contraction::Parser::ReturnLine) }.first
|
54
|
+
end
|
55
|
+
|
56
|
+
def param_rules
|
57
|
+
@param_rules ||= @rules.select { |r| r.is_a?(Contraction::Parser::ParamLine) }
|
58
|
+
end
|
59
|
+
|
60
|
+
def get_method_definition
|
61
|
+
@params = mod.instance_method(method_name).parameters.map(&:last)
|
33
62
|
end
|
34
63
|
|
35
|
-
def
|
36
|
-
|
64
|
+
def update_rule_values
|
65
|
+
names = rules.map(&:name).compact.uniq
|
66
|
+
|
67
|
+
rules.each do |rule|
|
68
|
+
names.each do |name|
|
69
|
+
rule.contract.gsub!(name, "named_args['#{name}']")
|
70
|
+
end
|
71
|
+
rule.contract.gsub!('return', 'result')
|
72
|
+
end
|
37
73
|
end
|
38
74
|
end
|
39
75
|
end
|
76
|
+
|
data/lib/contraction.rb
CHANGED
@@ -1,101 +1,48 @@
|
|
1
1
|
require 'string'
|
2
|
-
require '
|
2
|
+
require 'parser'
|
3
|
+
|
3
4
|
module Contraction
|
4
|
-
|
5
|
+
# Call this method to update contracts for any methods that may have been
|
6
|
+
# added after the class/module file was loaded by some third-party code. It's
|
7
|
+
# unlikely that you will need this method, but I thought I would include it
|
8
|
+
# just in case.
|
9
|
+
# @param [Class] mod The module or class to update contracts for.
|
10
|
+
def self.update_contracts(mod)
|
5
11
|
instance = mod.allocate
|
6
|
-
|
7
|
-
|
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
|
19
|
-
end
|
12
|
+
instance_methods = (mod.instance_methods - Object.instance_methods - Contraction.instance_methods)
|
20
13
|
|
21
|
-
|
22
|
-
|
23
|
-
returns.check!(result, named_args)
|
14
|
+
instance_methods.each do |method_name|
|
15
|
+
file_contents, line_no = read_file_for_method(instance, method_name)
|
24
16
|
|
25
|
-
|
17
|
+
contract = Contraction::Parser.parse(file_contents[0..line_no-2].reverse, mod, method_name)
|
18
|
+
define_wrapped_method(mod, method_name, contract)
|
26
19
|
end
|
27
20
|
end
|
28
21
|
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
44
|
-
end
|
45
|
-
|
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
|
22
|
+
# Called by ruby when Contraction is included in a class.
|
23
|
+
def self.included(mod)
|
24
|
+
update_contracts(mod)
|
52
25
|
end
|
53
26
|
|
54
|
-
|
55
|
-
@file_contents ||= {}
|
56
|
-
@file_contents[filename] ||= File.read(filename)
|
57
|
-
end
|
27
|
+
private
|
58
28
|
|
59
|
-
def self.
|
60
|
-
file, line =
|
29
|
+
def self.read_file_for_method(instance, method_name)
|
30
|
+
file, line = instance.method(method_name).source_location
|
61
31
|
filename = File.expand_path(file)
|
62
|
-
|
63
|
-
|
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)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
args.reverse!
|
86
|
-
return [args, returns]
|
32
|
+
file_contents = File.read(filename).split("\n")
|
33
|
+
return [file_contents, line]
|
87
34
|
end
|
88
35
|
|
89
|
-
def self.
|
90
|
-
|
91
|
-
|
92
|
-
instance_methods.each do |method_name|
|
93
|
-
patch_instance_method(mod, method_name)
|
94
|
-
end
|
36
|
+
def self.define_wrapped_method(mod, method_name, contract)
|
37
|
+
old_method = mod.instance_method(method_name)
|
95
38
|
|
96
|
-
|
97
|
-
|
98
|
-
|
39
|
+
arg_checks = []
|
40
|
+
result_check = nil
|
41
|
+
mod.send(:define_method, method_name) do |*method_args|
|
42
|
+
contract.valid_args?(*method_args)
|
43
|
+
result = old_method.bind(self).call(*method_args)
|
44
|
+
contract.valid_return?(*method_args, result)
|
45
|
+
result
|
99
46
|
end
|
100
47
|
end
|
101
48
|
end
|
data/lib/parser.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'string'
|
2
|
+
require 'parser/type'
|
3
|
+
require 'parser/lines'
|
4
|
+
require 'contract'
|
5
|
+
|
6
|
+
module Contraction
|
7
|
+
module Parser
|
8
|
+
RETURN_LINE_REGEX = /^#\s*@return\s+(?<type>\[[^\]]+\])?\s*(?<message>[^{]+)?(?<contract>\{([^}]+)\})?/
|
9
|
+
PARAM_LINE_REGEX = /^#\s*@param\s+(?<type>\[[^\]]+\])?\s*(?<name>[^\s]+)\s+(?<message>[^{]+)?(?<contract>\{([^}]+)\})?/
|
10
|
+
|
11
|
+
# Parses text passed to it for a given method for RDoc @param and @return
|
12
|
+
# lines to build contracts.
|
13
|
+
# @param [Array,String] text The text to be parsed.
|
14
|
+
# @param [Class,Module] mod The class or module that the method is defined
|
15
|
+
# in.
|
16
|
+
# @param [Symbol,String] method_name The name of the method that the
|
17
|
+
# contracts/docs apply to
|
18
|
+
# @return [Contract] A Contract object that can be used to evaluate
|
19
|
+
# correctness at run-time.
|
20
|
+
def self.parse(text, mod, method_name)
|
21
|
+
lines = text.is_a?(String) ? text.split(/$/) : text
|
22
|
+
results = []
|
23
|
+
lines.each do |line|
|
24
|
+
line.strip!
|
25
|
+
break unless line.start_with? '#'
|
26
|
+
break if line.start_with? '##'
|
27
|
+
results << parse_line(line.strip)
|
28
|
+
end
|
29
|
+
results.compact!
|
30
|
+
|
31
|
+
Contract.new(results, mod, method_name)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Parse a single line of text for @param and @return statements.
|
35
|
+
# @param [String] line The line of text to parse
|
36
|
+
# @return [TypedLine] An object that represents the parsed line including
|
37
|
+
# type information and contract.
|
38
|
+
def self.parse_line(line)
|
39
|
+
if m = line.match(PARAM_LINE_REGEX)
|
40
|
+
args = {
|
41
|
+
type: m['type'].to_s.gsub(/(\[|\])/, ''),
|
42
|
+
name: m['name'],
|
43
|
+
message: m['message'],
|
44
|
+
contract: (m['contract'] || 'true').gsub(/(^\{)|(\}$)/, '')
|
45
|
+
}
|
46
|
+
return ParamLine.new(args)
|
47
|
+
elsif m = line.match(RETURN_LINE_REGEX)
|
48
|
+
args = {
|
49
|
+
type: m['type'].to_s.gsub(/(\[|\])/, ''),
|
50
|
+
message: m['message'],
|
51
|
+
contract: (m['contract'] || 'true').gsub(/(^\{)|(\}$)/, '')
|
52
|
+
}
|
53
|
+
return ReturnLine.new(args)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/parser/lines.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# FIXME: There is an aweful lot of knowledge about which kind of TypedLine is
|
2
|
+
# being used scattered around the system. I need to encapsulate that better;
|
3
|
+
# the abstractions are leaking!
|
4
|
+
module Contraction
|
5
|
+
module Parser
|
6
|
+
class TypedLine
|
7
|
+
attr_reader :type, :contract, :message, :types
|
8
|
+
attr_writer :contract
|
9
|
+
|
10
|
+
def initialize(args={})
|
11
|
+
@type = args[:type]
|
12
|
+
@contract = args[:contract]
|
13
|
+
@message = args[:message]
|
14
|
+
parse_type
|
15
|
+
end
|
16
|
+
|
17
|
+
def parse_type
|
18
|
+
parts = type.split(/(\>|\}|\)),/)
|
19
|
+
@types = []
|
20
|
+
parts.each do |part|
|
21
|
+
@types << Type.new(part)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def valid?(*value)
|
26
|
+
@types.each_with_index.all? do |t, i|
|
27
|
+
t.check(value[i])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def evaluate_in_context(context, method_name, value)
|
32
|
+
return if !contract || contract.to_s.strip == ''
|
33
|
+
raise contract_message(value, method_name) unless eval(contract, context)
|
34
|
+
end
|
35
|
+
|
36
|
+
def contract_message(value=nil, method_name=nil)
|
37
|
+
raise 'Not Implemented'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class ParamLine < TypedLine
|
42
|
+
attr_reader :name
|
43
|
+
|
44
|
+
def initialize(args={})
|
45
|
+
super(args)
|
46
|
+
@name = args[:name]
|
47
|
+
end
|
48
|
+
|
49
|
+
def contract_message(value, method_name=nil)
|
50
|
+
"#{name} (#{message}) must fullfill #{contract.inspect}, but is #{value.inspect}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class ReturnLine < TypedLine
|
55
|
+
def name
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def contract_message(value, method_name=nil)
|
60
|
+
"Return value of #{method_name} (#{message}) must fullfill #{contract.inspect}, but is #{value}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/parser/type.rb
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
module Contraction
|
2
|
+
module Parser
|
3
|
+
class Type
|
4
|
+
attr_reader :legal_types, :method_requirements, :length, :key_types, :value_types
|
5
|
+
|
6
|
+
def initialize(part)
|
7
|
+
@legal_types = []
|
8
|
+
@method_requirements = []
|
9
|
+
@length = -1
|
10
|
+
@key_types = []
|
11
|
+
@value_types = []
|
12
|
+
|
13
|
+
parse(part)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Checks weather or not thing is a given type.
|
17
|
+
# @param [String] thing A string containing a type definition. For example:
|
18
|
+
# Array<String>
|
19
|
+
def check(thing)
|
20
|
+
check_types(thing) &&
|
21
|
+
check_duck_typing(thing) &&
|
22
|
+
check_length(thing) &&
|
23
|
+
check_hash(thing)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def parse(line)
|
29
|
+
parse_typed_container(line) ||
|
30
|
+
parse_duck_type(line) ||
|
31
|
+
parse_fixed_list(line) ||
|
32
|
+
parse_hash(line) ||
|
33
|
+
parse_short_hash_or_reference(line) ||
|
34
|
+
parse_regular(line)
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse_typed_container(line)
|
38
|
+
return unless line.include? '<'
|
39
|
+
# It's some kind of container that can only hold certain things
|
40
|
+
list = line.match(/\<(?<list>[^\>]+)\>/)['list']
|
41
|
+
list.split(',').each do |type|
|
42
|
+
@legal_types << Type.new(type.strip)
|
43
|
+
end
|
44
|
+
true
|
45
|
+
end
|
46
|
+
|
47
|
+
def parse_duck_type(line)
|
48
|
+
return unless line =~ /^#/
|
49
|
+
# It's a duck-typed object of some kind
|
50
|
+
methods = line.split(",").map { |p| p.strip.gsub(/^#/,'').to_sym }
|
51
|
+
@method_requirements += methods
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
def parse_fixed_list(line)
|
56
|
+
return unless line.include?('(')
|
57
|
+
# It's a fixed-length list
|
58
|
+
list = line.match(/\((?<list>[^\>]+)\)/)['list']
|
59
|
+
parts = list.split(',')
|
60
|
+
@length = parts.length
|
61
|
+
parts.each do |type|
|
62
|
+
@legal_types << Type.new(type.strip)
|
63
|
+
end
|
64
|
+
true
|
65
|
+
end
|
66
|
+
|
67
|
+
def parse_hash(line)
|
68
|
+
return unless line.include? 'Hash{'
|
69
|
+
# It's a hash with specific key-value pair types
|
70
|
+
parts = line.match(/\{(?<key_types>.+)\s*=\>\s*(?<value_types>[^\}]+)\}/)
|
71
|
+
@key_types = parts['key_types'].split(',').map { |t| t.include?('#') ? t.strip.gsub(/^#/, '').to_sym : t.strip.constantize }
|
72
|
+
@value_types = parts['value_types'].split(',').map { |t| t.include?('#') ? t.strip.gsub(/^#/, '').to_sym : t.strip.constantize }
|
73
|
+
end
|
74
|
+
|
75
|
+
def parse_short_hash_or_reference(line)
|
76
|
+
return unless line.include? '{'
|
77
|
+
if parts = line.match(/\{(?<key_types>.+)\s*=\>\s*(?<value_types>[^\}]+)\}/)
|
78
|
+
@key_types = parts['key_types'].split(',').map { |t| t.include?('#') ? t.strip.gsub(/^#/, '').to_sym : t.strip.constantize }
|
79
|
+
@value_types = parts['value_types'].split(',').map { |t| t.include?('#') ? t.strip.gsub(/^#/, '').to_sym : t.strip.constantize }
|
80
|
+
else
|
81
|
+
# It's a reference to another documented type defined someplace in
|
82
|
+
# the codebase. We can ignore the reference, and treat it like a
|
83
|
+
# normal type.
|
84
|
+
@legal_types << line.gsub(/\{|\}/, '').constantize
|
85
|
+
end
|
86
|
+
true
|
87
|
+
end
|
88
|
+
|
89
|
+
def parse_regular(line)
|
90
|
+
# It's a regular-ass type.
|
91
|
+
@legal_types << line.constantize
|
92
|
+
end
|
93
|
+
|
94
|
+
def check_hash(thing)
|
95
|
+
return true if @key_types.empty? or @value_types.empty?
|
96
|
+
return false unless thing.is_a?(Hash)
|
97
|
+
thing.keys.all? do |k|
|
98
|
+
@key_types.any? { |kt| kt.is_a?(Symbol) ? k.respond_to?(kt) : k.is_a?(kt) }
|
99
|
+
end &&
|
100
|
+
thing.values.all? do |v|
|
101
|
+
@value_types.any? { |vt| vt.is_a?(Symbol) ? v.respond_to?(vt) : v.is_a?(vt) }
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def check_length(thing)
|
106
|
+
return true if @length == -1
|
107
|
+
thing.length == @length
|
108
|
+
end
|
109
|
+
|
110
|
+
def check_duck_typing(thing)
|
111
|
+
return true if @method_requirements.empty?
|
112
|
+
@method_requirements.all? do |m|
|
113
|
+
thing.respond_to? m
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def check_types(thing)
|
118
|
+
return true if @legal_types.empty?
|
119
|
+
if thing.is_a? Enumerable
|
120
|
+
types = @legal_types.map { |t| t.respond_to?(:legal_types) ? t.legal_types : t }.flatten
|
121
|
+
return thing.all? { |th| types.include?(th.class) }
|
122
|
+
else
|
123
|
+
@legal_types.any? do |t|
|
124
|
+
if t.is_a?(Contraction::Parser::Type)
|
125
|
+
# Given the fact that we check enumerables above, we should never be here.
|
126
|
+
next false
|
127
|
+
end
|
128
|
+
if thing.is_a?(Enumerable)
|
129
|
+
thing.all? { |th| th.is_a?(t) }
|
130
|
+
else
|
131
|
+
thing.is_a?(t)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
data/lib/string.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: contraction
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.2.2
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Thomas Luce
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2014-07-18 00:00:00.000000000 Z
|
13
12
|
dependencies: []
|
14
13
|
description: Using RDoc documentation as your contract definition, you get solid code,
|
15
14
|
and good docs. Win-win!
|
@@ -23,32 +22,31 @@ files:
|
|
23
22
|
- contraction.rb
|
24
23
|
- lib/contract.rb
|
25
24
|
- lib/contraction.rb
|
25
|
+
- lib/parser.rb
|
26
|
+
- lib/parser/lines.rb
|
27
|
+
- lib/parser/type.rb
|
26
28
|
- lib/string.rb
|
27
29
|
homepage: https://github.com/thomasluce/contraction
|
28
30
|
licenses: []
|
31
|
+
metadata: {}
|
29
32
|
post_install_message:
|
30
33
|
rdoc_options: []
|
31
34
|
require_paths:
|
32
35
|
- lib
|
33
36
|
required_ruby_version: !ruby/object:Gem::Requirement
|
34
|
-
none: false
|
35
37
|
requirements:
|
36
|
-
- -
|
38
|
+
- - '>='
|
37
39
|
- !ruby/object:Gem::Version
|
38
40
|
version: '0'
|
39
|
-
segments:
|
40
|
-
- 0
|
41
|
-
hash: 2360464299481397474
|
42
41
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
43
|
-
none: false
|
44
42
|
requirements:
|
45
|
-
- -
|
43
|
+
- - '>='
|
46
44
|
- !ruby/object:Gem::Version
|
47
45
|
version: '0'
|
48
46
|
requirements: []
|
49
47
|
rubyforge_project:
|
50
|
-
rubygems_version:
|
48
|
+
rubygems_version: 2.2.2
|
51
49
|
signing_key:
|
52
|
-
specification_version:
|
50
|
+
specification_version: 4
|
53
51
|
summary: A simple desgin-by-contract library
|
54
52
|
test_files: []
|