contraction 0.1.2 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|