entitlements 0.1.8 → 0.2.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.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/bin/deploy-entitlements +10 -1
- data/lib/contracts-ruby2/CHANGELOG.markdown +115 -0
- data/lib/contracts-ruby2/Gemfile +17 -0
- data/lib/contracts-ruby2/LICENSE +23 -0
- data/lib/contracts-ruby2/README.md +108 -0
- data/lib/contracts-ruby2/Rakefile +8 -0
- data/lib/contracts-ruby2/TODO.markdown +6 -0
- data/lib/contracts-ruby2/TUTORIAL.md +773 -0
- data/lib/contracts-ruby2/benchmarks/bench.rb +67 -0
- data/lib/contracts-ruby2/benchmarks/hash.rb +69 -0
- data/lib/contracts-ruby2/benchmarks/invariants.rb +91 -0
- data/lib/contracts-ruby2/benchmarks/io.rb +62 -0
- data/lib/contracts-ruby2/benchmarks/wrap_test.rb +57 -0
- data/lib/contracts-ruby2/contracts.gemspec +17 -0
- data/lib/contracts-ruby2/cucumber.yml +1 -0
- data/lib/contracts-ruby2/dependabot.yml +20 -0
- data/lib/contracts-ruby2/features/README.md +17 -0
- data/lib/contracts-ruby2/features/basics/functype.feature +71 -0
- data/lib/contracts-ruby2/features/basics/pretty-print.feature +241 -0
- data/lib/contracts-ruby2/features/basics/simple_example.feature +210 -0
- data/lib/contracts-ruby2/features/builtin_contracts/README.md +22 -0
- data/lib/contracts-ruby2/features/builtin_contracts/and.feature +103 -0
- data/lib/contracts-ruby2/features/builtin_contracts/any.feature +44 -0
- data/lib/contracts-ruby2/features/builtin_contracts/args.feature +80 -0
- data/lib/contracts-ruby2/features/builtin_contracts/array_of.feature +1 -0
- data/lib/contracts-ruby2/features/builtin_contracts/bool.feature +64 -0
- data/lib/contracts-ruby2/features/builtin_contracts/enum.feature +1 -0
- data/lib/contracts-ruby2/features/builtin_contracts/eq.feature +1 -0
- data/lib/contracts-ruby2/features/builtin_contracts/exactly.feature +1 -0
- data/lib/contracts-ruby2/features/builtin_contracts/func.feature +1 -0
- data/lib/contracts-ruby2/features/builtin_contracts/hash_of.feature +1 -0
- data/lib/contracts-ruby2/features/builtin_contracts/int.feature +93 -0
- data/lib/contracts-ruby2/features/builtin_contracts/keyword_args.feature +1 -0
- data/lib/contracts-ruby2/features/builtin_contracts/maybe.feature +1 -0
- data/lib/contracts-ruby2/features/builtin_contracts/nat.feature +115 -0
- data/lib/contracts-ruby2/features/builtin_contracts/nat_pos.feature +119 -0
- data/lib/contracts-ruby2/features/builtin_contracts/neg.feature +115 -0
- data/lib/contracts-ruby2/features/builtin_contracts/none.feature +145 -0
- data/lib/contracts-ruby2/features/builtin_contracts/not.feature +1 -0
- data/lib/contracts-ruby2/features/builtin_contracts/num.feature +64 -0
- data/lib/contracts-ruby2/features/builtin_contracts/or.feature +83 -0
- data/lib/contracts-ruby2/features/builtin_contracts/pos.feature +116 -0
- data/lib/contracts-ruby2/features/builtin_contracts/range_of.feature +1 -0
- data/lib/contracts-ruby2/features/builtin_contracts/respond_to.feature +78 -0
- data/lib/contracts-ruby2/features/builtin_contracts/send.feature +147 -0
- data/lib/contracts-ruby2/features/builtin_contracts/set_of.feature +1 -0
- data/lib/contracts-ruby2/features/builtin_contracts/xor.feature +99 -0
- data/lib/contracts-ruby2/features/support/env.rb +6 -0
- data/lib/contracts-ruby2/lib/contracts/attrs.rb +24 -0
- data/lib/contracts-ruby2/lib/contracts/builtin_contracts.rb +542 -0
- data/lib/contracts-ruby2/lib/contracts/call_with.rb +108 -0
- data/lib/contracts-ruby2/lib/contracts/core.rb +52 -0
- data/lib/contracts-ruby2/lib/contracts/decorators.rb +47 -0
- data/lib/contracts-ruby2/lib/contracts/engine/base.rb +136 -0
- data/lib/contracts-ruby2/lib/contracts/engine/eigenclass.rb +50 -0
- data/lib/contracts-ruby2/lib/contracts/engine/target.rb +70 -0
- data/lib/contracts-ruby2/lib/contracts/engine.rb +26 -0
- data/lib/contracts-ruby2/lib/contracts/errors.rb +71 -0
- data/lib/contracts-ruby2/lib/contracts/formatters.rb +136 -0
- data/lib/contracts-ruby2/lib/contracts/invariants.rb +68 -0
- data/lib/contracts-ruby2/lib/contracts/method_handler.rb +187 -0
- data/lib/contracts-ruby2/lib/contracts/method_reference.rb +100 -0
- data/lib/contracts-ruby2/lib/contracts/support.rb +61 -0
- data/lib/contracts-ruby2/lib/contracts/validators.rb +139 -0
- data/lib/contracts-ruby2/lib/contracts/version.rb +3 -0
- data/lib/contracts-ruby2/lib/contracts.rb +281 -0
- data/lib/contracts-ruby2/script/docs-release +3 -0
- data/lib/contracts-ruby2/script/docs-staging +3 -0
- data/lib/contracts-ruby2/script/rubocop.rb +5 -0
- data/lib/contracts-ruby2/spec/attrs_spec.rb +119 -0
- data/lib/contracts-ruby2/spec/builtin_contracts_spec.rb +461 -0
- data/lib/contracts-ruby2/spec/contracts_spec.rb +770 -0
- data/lib/contracts-ruby2/spec/fixtures/fixtures.rb +730 -0
- data/lib/contracts-ruby2/spec/invariants_spec.rb +17 -0
- data/lib/contracts-ruby2/spec/methods_spec.rb +54 -0
- data/lib/contracts-ruby2/spec/module_spec.rb +18 -0
- data/lib/contracts-ruby2/spec/override_validators_spec.rb +162 -0
- data/lib/contracts-ruby2/spec/ruby_version_specific/contracts_spec_1.9.rb +24 -0
- data/lib/contracts-ruby2/spec/ruby_version_specific/contracts_spec_2.0.rb +55 -0
- data/lib/contracts-ruby2/spec/ruby_version_specific/contracts_spec_2.1.rb +63 -0
- data/lib/contracts-ruby2/spec/spec_helper.rb +102 -0
- data/lib/contracts-ruby2/spec/support.rb +10 -0
- data/lib/contracts-ruby2/spec/support_spec.rb +21 -0
- data/lib/contracts-ruby2/spec/validators_spec.rb +47 -0
- data/lib/contracts-ruby3/CHANGELOG.markdown +117 -0
- data/lib/contracts-ruby3/Gemfile +21 -0
- data/lib/contracts-ruby3/LICENSE +23 -0
- data/lib/contracts-ruby3/README.md +114 -0
- data/lib/contracts-ruby3/Rakefile +10 -0
- data/lib/contracts-ruby3/TODO.markdown +6 -0
- data/lib/contracts-ruby3/TUTORIAL.md +773 -0
- data/lib/contracts-ruby3/benchmarks/bench.rb +67 -0
- data/lib/contracts-ruby3/benchmarks/hash.rb +69 -0
- data/lib/contracts-ruby3/benchmarks/invariants.rb +91 -0
- data/lib/contracts-ruby3/benchmarks/io.rb +62 -0
- data/lib/contracts-ruby3/benchmarks/wrap_test.rb +57 -0
- data/lib/contracts-ruby3/contracts.gemspec +20 -0
- data/lib/contracts-ruby3/cucumber.yml +1 -0
- data/lib/contracts-ruby3/dependabot.yml +20 -0
- data/lib/contracts-ruby3/features/README.md +17 -0
- data/lib/contracts-ruby3/features/basics/functype.feature +71 -0
- data/lib/contracts-ruby3/features/basics/pretty-print.feature +241 -0
- data/lib/contracts-ruby3/features/basics/simple_example.feature +210 -0
- data/lib/contracts-ruby3/features/builtin_contracts/README.md +22 -0
- data/lib/contracts-ruby3/features/builtin_contracts/and.feature +103 -0
- data/lib/contracts-ruby3/features/builtin_contracts/any.feature +44 -0
- data/lib/contracts-ruby3/features/builtin_contracts/args.feature +80 -0
- data/lib/contracts-ruby3/features/builtin_contracts/array_of.feature +1 -0
- data/lib/contracts-ruby3/features/builtin_contracts/bool.feature +64 -0
- data/lib/contracts-ruby3/features/builtin_contracts/enum.feature +1 -0
- data/lib/contracts-ruby3/features/builtin_contracts/eq.feature +1 -0
- data/lib/contracts-ruby3/features/builtin_contracts/exactly.feature +1 -0
- data/lib/contracts-ruby3/features/builtin_contracts/func.feature +1 -0
- data/lib/contracts-ruby3/features/builtin_contracts/hash_of.feature +1 -0
- data/lib/contracts-ruby3/features/builtin_contracts/int.feature +93 -0
- data/lib/contracts-ruby3/features/builtin_contracts/keyword_args.feature +1 -0
- data/lib/contracts-ruby3/features/builtin_contracts/maybe.feature +1 -0
- data/lib/contracts-ruby3/features/builtin_contracts/nat.feature +115 -0
- data/lib/contracts-ruby3/features/builtin_contracts/nat_pos.feature +119 -0
- data/lib/contracts-ruby3/features/builtin_contracts/neg.feature +115 -0
- data/lib/contracts-ruby3/features/builtin_contracts/none.feature +145 -0
- data/lib/contracts-ruby3/features/builtin_contracts/not.feature +1 -0
- data/lib/contracts-ruby3/features/builtin_contracts/num.feature +64 -0
- data/lib/contracts-ruby3/features/builtin_contracts/or.feature +83 -0
- data/lib/contracts-ruby3/features/builtin_contracts/pos.feature +116 -0
- data/lib/contracts-ruby3/features/builtin_contracts/range_of.feature +1 -0
- data/lib/contracts-ruby3/features/builtin_contracts/respond_to.feature +78 -0
- data/lib/contracts-ruby3/features/builtin_contracts/send.feature +147 -0
- data/lib/contracts-ruby3/features/builtin_contracts/set_of.feature +1 -0
- data/lib/contracts-ruby3/features/builtin_contracts/xor.feature +99 -0
- data/lib/contracts-ruby3/features/support/env.rb +8 -0
- data/lib/contracts-ruby3/lib/contracts/attrs.rb +26 -0
- data/lib/contracts-ruby3/lib/contracts/builtin_contracts.rb +575 -0
- data/lib/contracts-ruby3/lib/contracts/call_with.rb +119 -0
- data/lib/contracts-ruby3/lib/contracts/core.rb +54 -0
- data/lib/contracts-ruby3/lib/contracts/decorators.rb +50 -0
- data/lib/contracts-ruby3/lib/contracts/engine/base.rb +137 -0
- data/lib/contracts-ruby3/lib/contracts/engine/eigenclass.rb +51 -0
- data/lib/contracts-ruby3/lib/contracts/engine/target.rb +72 -0
- data/lib/contracts-ruby3/lib/contracts/engine.rb +28 -0
- data/lib/contracts-ruby3/lib/contracts/errors.rb +74 -0
- data/lib/contracts-ruby3/lib/contracts/formatters.rb +140 -0
- data/lib/contracts-ruby3/lib/contracts/invariants.rb +72 -0
- data/lib/contracts-ruby3/lib/contracts/method_handler.rb +197 -0
- data/lib/contracts-ruby3/lib/contracts/method_reference.rb +102 -0
- data/lib/contracts-ruby3/lib/contracts/support.rb +63 -0
- data/lib/contracts-ruby3/lib/contracts/validators.rb +143 -0
- data/lib/contracts-ruby3/lib/contracts/version.rb +5 -0
- data/lib/contracts-ruby3/lib/contracts.rb +290 -0
- data/lib/contracts-ruby3/script/docs-release +3 -0
- data/lib/contracts-ruby3/script/docs-staging +3 -0
- data/lib/contracts-ruby3/script/rubocop.rb +5 -0
- data/lib/contracts-ruby3/spec/attrs_spec.rb +119 -0
- data/lib/contracts-ruby3/spec/builtin_contracts_spec.rb +457 -0
- data/lib/contracts-ruby3/spec/contracts_spec.rb +773 -0
- data/lib/contracts-ruby3/spec/fixtures/fixtures.rb +725 -0
- data/lib/contracts-ruby3/spec/invariants_spec.rb +17 -0
- data/lib/contracts-ruby3/spec/methods_spec.rb +54 -0
- data/lib/contracts-ruby3/spec/module_spec.rb +18 -0
- data/lib/contracts-ruby3/spec/override_validators_spec.rb +162 -0
- data/lib/contracts-ruby3/spec/ruby_version_specific/contracts_spec_1.9.rb +24 -0
- data/lib/contracts-ruby3/spec/ruby_version_specific/contracts_spec_2.0.rb +55 -0
- data/lib/contracts-ruby3/spec/ruby_version_specific/contracts_spec_2.1.rb +63 -0
- data/lib/contracts-ruby3/spec/spec_helper.rb +102 -0
- data/lib/contracts-ruby3/spec/support.rb +10 -0
- data/lib/contracts-ruby3/spec/support_spec.rb +21 -0
- data/lib/contracts-ruby3/spec/validators_spec.rb +47 -0
- data/lib/entitlements/data/groups/calculated/yaml.rb +7 -1
- data/lib/entitlements/data/people/yaml.rb +9 -1
- data/lib/entitlements/extras/ldap_group/rules/ldap_group.rb +5 -1
- data/lib/entitlements/extras/orgchart/person_methods.rb +7 -1
- data/lib/entitlements.rb +13 -2
- data/lib/ruby_version_check.rb +17 -0
- metadata +209 -14
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module Contracts
|
|
2
|
+
module MethodDecorators
|
|
3
|
+
def self.extended(klass)
|
|
4
|
+
Engine.apply(klass)
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def inherited(subclass)
|
|
8
|
+
Engine.fetch_from(subclass).set_eigenclass_owner
|
|
9
|
+
super
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def method_added(name)
|
|
13
|
+
MethodHandler.new(name, false, self).handle
|
|
14
|
+
super
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def singleton_method_added(name)
|
|
18
|
+
MethodHandler.new(name, true, self).handle
|
|
19
|
+
super
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class Decorator
|
|
24
|
+
# an attr_accessor for a class variable:
|
|
25
|
+
class << self; attr_accessor :decorators; end
|
|
26
|
+
|
|
27
|
+
def self.inherited(klass)
|
|
28
|
+
name = klass.name.gsub(/^./) { |m| m.downcase }
|
|
29
|
+
|
|
30
|
+
return if name =~ /^[^A-Za-z_]/ || name =~ /[^0-9A-Za-z_]/
|
|
31
|
+
|
|
32
|
+
# the file and line parameters set the text for error messages
|
|
33
|
+
# make a new method that is the name of your decorator.
|
|
34
|
+
# that method accepts random args and a block.
|
|
35
|
+
# inside, `decorate` is called with those params.
|
|
36
|
+
MethodDecorators.module_eval <<-ruby_eval, __FILE__, __LINE__ + 1
|
|
37
|
+
def #{klass}(*args, &blk)
|
|
38
|
+
::Contracts::Engine.fetch_from(self).decorate(#{klass}, *args, &blk)
|
|
39
|
+
end
|
|
40
|
+
ruby_eval
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def initialize(klass, method)
|
|
44
|
+
@method = method
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
module Contracts
|
|
2
|
+
module Engine
|
|
3
|
+
# Contracts engine
|
|
4
|
+
class Base
|
|
5
|
+
# Enable contracts engine for klass
|
|
6
|
+
#
|
|
7
|
+
# @param [Class] klass - target class
|
|
8
|
+
def self.apply(klass)
|
|
9
|
+
Engine::Target.new(klass).apply
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Returns true if klass has contracts engine
|
|
13
|
+
#
|
|
14
|
+
# @param [Class] klass - target class
|
|
15
|
+
# @return [Bool]
|
|
16
|
+
def self.applied?(klass)
|
|
17
|
+
Engine::Target.new(klass).applied?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Fetches contracts engine out of klass
|
|
21
|
+
#
|
|
22
|
+
# @param [Class] klass - target class
|
|
23
|
+
# @return [Engine::Base or Engine::Eigenclass]
|
|
24
|
+
def self.fetch_from(klass)
|
|
25
|
+
Engine::Target.new(klass).engine
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Creates new instance of contracts engine
|
|
29
|
+
#
|
|
30
|
+
# @param [Class] klass - class that owns this engine
|
|
31
|
+
def initialize(klass)
|
|
32
|
+
@klass = klass
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Adds provided decorator to the engine
|
|
36
|
+
# It validates that decorator can be added to this engine at the
|
|
37
|
+
# moment
|
|
38
|
+
#
|
|
39
|
+
# @param [Decorator:Class] decorator_class
|
|
40
|
+
# @param args - arguments for decorator
|
|
41
|
+
def decorate(decorator_class, *args)
|
|
42
|
+
validate!
|
|
43
|
+
decorators << [decorator_class, args]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Sets eigenclass' owner to klass
|
|
47
|
+
def set_eigenclass_owner
|
|
48
|
+
eigenclass_engine.owner_class = klass
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Fetches all accumulated decorators (both this engine and
|
|
52
|
+
# corresponding eigenclass' engine)
|
|
53
|
+
# It clears all accumulated decorators
|
|
54
|
+
#
|
|
55
|
+
# @return [ArrayOf[Decorator]]
|
|
56
|
+
def all_decorators
|
|
57
|
+
pop_decorators + eigenclass_engine.all_decorators
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Fetches decorators of specified type for method with name
|
|
61
|
+
#
|
|
62
|
+
# @param [Or[:class_methods, :instance_methods]] type - method type
|
|
63
|
+
# @param [Symbol] name - method name
|
|
64
|
+
# @return [ArrayOf[Decorator]]
|
|
65
|
+
def decorated_methods_for(type, name)
|
|
66
|
+
Array(decorated_methods[type][name])
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Returns true if there are any decorated methods
|
|
70
|
+
#
|
|
71
|
+
# @return [Bool]
|
|
72
|
+
def decorated_methods?
|
|
73
|
+
!decorated_methods[:class_methods].empty? ||
|
|
74
|
+
!decorated_methods[:instance_methods].empty?
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Adds method decorator
|
|
78
|
+
#
|
|
79
|
+
# @param [Or[:class_methods, :instance_methods]] type - method type
|
|
80
|
+
# @param [Symbol] name - method name
|
|
81
|
+
# @param [Decorator] decorator - method decorator
|
|
82
|
+
def add_method_decorator(type, name, decorator)
|
|
83
|
+
decorated_methods[type][name] ||= []
|
|
84
|
+
decorated_methods[type][name] << decorator
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Returns nearest ancestor's engine that has decorated methods
|
|
88
|
+
#
|
|
89
|
+
# @return [Engine::Base or Engine::Eigenclass]
|
|
90
|
+
def nearest_decorated_ancestor
|
|
91
|
+
current = klass
|
|
92
|
+
current_engine = self
|
|
93
|
+
ancestors = current.ancestors[1..-1]
|
|
94
|
+
|
|
95
|
+
while current && current_engine && !current_engine.decorated_methods?
|
|
96
|
+
current = ancestors.shift
|
|
97
|
+
current_engine = Engine.fetch_from(current)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
current_engine
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
attr_reader :klass
|
|
106
|
+
|
|
107
|
+
def decorated_methods
|
|
108
|
+
@_decorated_methods ||= { :class_methods => {}, :instance_methods => {} }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# No-op because it is safe to add decorators to normal classes
|
|
112
|
+
def validate!
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def pop_decorators
|
|
116
|
+
decorators.tap { clear_decorators }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def eigenclass
|
|
120
|
+
Support.eigenclass_of(klass)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def eigenclass_engine
|
|
124
|
+
Eigenclass.lift(eigenclass, klass)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def decorators
|
|
128
|
+
@_decorators ||= []
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def clear_decorators
|
|
132
|
+
@_decorators = []
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module Contracts
|
|
2
|
+
module Engine
|
|
3
|
+
# Special case of contracts engine for eigenclasses
|
|
4
|
+
# We don't care about eigenclass of eigenclass at this point
|
|
5
|
+
class Eigenclass < Base
|
|
6
|
+
# Class that owns this eigenclass
|
|
7
|
+
attr_accessor :owner_class
|
|
8
|
+
|
|
9
|
+
# Automatically enables eigenclass engine if it is not
|
|
10
|
+
# Returns its engine
|
|
11
|
+
# NOTE: Required by jruby in 1.9 mode. Otherwise inherited
|
|
12
|
+
# eigenclasses don't have their engines
|
|
13
|
+
#
|
|
14
|
+
# @param [Class] eigenclass - class in question
|
|
15
|
+
# @param [Class] owner - owner of eigenclass
|
|
16
|
+
# @return [Engine::Eigenclass]
|
|
17
|
+
def self.lift(eigenclass, owner)
|
|
18
|
+
return Engine.fetch_from(eigenclass) if Engine.applied?(eigenclass)
|
|
19
|
+
|
|
20
|
+
Target.new(eigenclass).apply(Eigenclass)
|
|
21
|
+
eigenclass.extend(MethodDecorators)
|
|
22
|
+
# FIXME; this should detect what user uses `include Contracts` or
|
|
23
|
+
# `include Contracts;;Core`
|
|
24
|
+
eigenclass.send(:include, Contracts)
|
|
25
|
+
Engine.fetch_from(owner).set_eigenclass_owner
|
|
26
|
+
Engine.fetch_from(eigenclass)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# No-op for eigenclasses
|
|
30
|
+
def set_eigenclass_owner
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Fetches just eigenclasses decorators
|
|
34
|
+
def all_decorators
|
|
35
|
+
pop_decorators
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
# Fails when contracts are not included in owner class
|
|
41
|
+
def validate!
|
|
42
|
+
fail ContractsNotIncluded unless owner?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def owner?
|
|
46
|
+
!!owner_class
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
module Contracts
|
|
2
|
+
module Engine
|
|
3
|
+
# Represents class in question
|
|
4
|
+
class Target
|
|
5
|
+
# Creates new instance of Target
|
|
6
|
+
#
|
|
7
|
+
# @param [Class] target - class in question
|
|
8
|
+
def initialize(target)
|
|
9
|
+
@target = target
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Enable contracts engine for target
|
|
13
|
+
# - it is no-op if contracts engine is already enabled
|
|
14
|
+
# - it automatically enables contracts engine for its eigenclass
|
|
15
|
+
# - it sets owner class to target for its eigenclass
|
|
16
|
+
#
|
|
17
|
+
# @param [Engine::Base:Class] engine_class - type of engine to
|
|
18
|
+
# enable (Base or Eigenclass)
|
|
19
|
+
def apply(engine_class = Base)
|
|
20
|
+
return if applied?
|
|
21
|
+
|
|
22
|
+
apply_to_eigenclass
|
|
23
|
+
|
|
24
|
+
eigenclass.class_eval do
|
|
25
|
+
define_method(:__contracts_engine) do
|
|
26
|
+
@__contracts_engine ||= engine_class.new(self)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
engine.set_eigenclass_owner
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Returns true if target has contracts engine already
|
|
34
|
+
#
|
|
35
|
+
# @return [Bool]
|
|
36
|
+
def applied?
|
|
37
|
+
target.respond_to?(:__contracts_engine)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Returns contracts engine of target
|
|
41
|
+
#
|
|
42
|
+
# @return [Engine::Base or Engine::Eigenclass]
|
|
43
|
+
def engine
|
|
44
|
+
applied? && target.__contracts_engine
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
attr_reader :target
|
|
50
|
+
|
|
51
|
+
def apply_to_eigenclass
|
|
52
|
+
return unless meaningless_eigenclass?
|
|
53
|
+
|
|
54
|
+
self.class.new(eigenclass).apply(Eigenclass)
|
|
55
|
+
eigenclass.extend(MethodDecorators)
|
|
56
|
+
# FIXME; this should detect what user uses `include Contracts` or
|
|
57
|
+
# `include Contracts;;Core`
|
|
58
|
+
eigenclass.send(:include, Contracts)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def eigenclass
|
|
62
|
+
Support.eigenclass_of(target)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def meaningless_eigenclass?
|
|
66
|
+
!Support.eigenclass?(target)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require "contracts/engine/base"
|
|
2
|
+
require "contracts/engine/target"
|
|
3
|
+
require "contracts/engine/eigenclass"
|
|
4
|
+
|
|
5
|
+
require "forwardable"
|
|
6
|
+
|
|
7
|
+
module Contracts
|
|
8
|
+
# Engine facade, normally you shouldn't refer internals of Engine
|
|
9
|
+
# module directly.
|
|
10
|
+
module Engine
|
|
11
|
+
class << self
|
|
12
|
+
extend Forwardable
|
|
13
|
+
|
|
14
|
+
# .apply(klass) - enables contracts engine on klass
|
|
15
|
+
# .applied?(klass) - returns true if klass has contracts engine
|
|
16
|
+
# .fetch_from(klass) - returns contracts engine for klass
|
|
17
|
+
def_delegators :base_engine, :apply, :applied?, :fetch_from
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def base_engine
|
|
22
|
+
Base
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# @private
|
|
2
|
+
# Base class for Contract errors
|
|
3
|
+
#
|
|
4
|
+
# If default failure callback is used it stores failure data
|
|
5
|
+
class ContractBaseError < ArgumentError
|
|
6
|
+
attr_reader :data
|
|
7
|
+
|
|
8
|
+
def initialize(message, data)
|
|
9
|
+
super(message)
|
|
10
|
+
@data = data
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Used to convert to simple ContractError from other contract errors
|
|
14
|
+
def to_contract_error
|
|
15
|
+
self
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Default contract error
|
|
20
|
+
#
|
|
21
|
+
# If default failure callback is used, users normally see only these contract errors
|
|
22
|
+
class ContractError < ContractBaseError
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class ParamContractError < ContractError
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class ReturnContractError < ContractError
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @private
|
|
32
|
+
# Special contract error used internally to detect pattern failure during pattern matching
|
|
33
|
+
class PatternMatchingError < ContractBaseError
|
|
34
|
+
# Used to convert to ContractError from PatternMatchingError
|
|
35
|
+
def to_contract_error
|
|
36
|
+
ContractError.new(to_s, data)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Base invariant violation error
|
|
41
|
+
class InvariantError < StandardError
|
|
42
|
+
def to_contract_error
|
|
43
|
+
self
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
module Contracts
|
|
48
|
+
# Error issued when user haven't included Contracts in original class but used Contract definition in singleton class
|
|
49
|
+
#
|
|
50
|
+
# Provides useful description for user of the gem and an example of correct usage.
|
|
51
|
+
class ContractsNotIncluded < TypeError
|
|
52
|
+
DEFAULT_MESSAGE = %{In order to use contracts in singleton class, please include Contracts module in original class
|
|
53
|
+
Example:
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
class Example
|
|
57
|
+
include Contracts # this line is required
|
|
58
|
+
class << self
|
|
59
|
+
# you can use `Contract` definition here now
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
```}
|
|
63
|
+
|
|
64
|
+
attr_reader :message
|
|
65
|
+
alias_method :to_s, :message
|
|
66
|
+
|
|
67
|
+
def initialize(message = DEFAULT_MESSAGE)
|
|
68
|
+
@message = message
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
require "pp"
|
|
2
|
+
|
|
3
|
+
module Contracts
|
|
4
|
+
# A namespace for classes related to formatting.
|
|
5
|
+
module Formatters
|
|
6
|
+
# Used to format contracts for the `Expected:` field of error output.
|
|
7
|
+
class Expected
|
|
8
|
+
# @param full [Boolean] if false only unique `to_s` values will be output,
|
|
9
|
+
# non unique values become empty string.
|
|
10
|
+
def initialize(contract, full = true)
|
|
11
|
+
@contract, @full = contract, full
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Formats any type of Contract.
|
|
15
|
+
def contract(contract = @contract)
|
|
16
|
+
if contract.is_a?(Hash)
|
|
17
|
+
hash_contract(contract)
|
|
18
|
+
elsif contract.is_a?(Array)
|
|
19
|
+
array_contract(contract)
|
|
20
|
+
else
|
|
21
|
+
InspectWrapper.create(contract, @full)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Formats Hash contracts.
|
|
26
|
+
def hash_contract(hash)
|
|
27
|
+
@full = true # Complex values output completely, overriding @full
|
|
28
|
+
hash.inject({}) do |repr, (k, v)|
|
|
29
|
+
repr.merge(k => InspectWrapper.create(contract(v), @full))
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Formats Array contracts.
|
|
34
|
+
def array_contract(array)
|
|
35
|
+
@full = true
|
|
36
|
+
array.map { |v| InspectWrapper.create(contract(v), @full) }
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# A wrapper class to produce correct inspect behaviour for different
|
|
41
|
+
# contract values - constants, Class contracts, instance contracts etc.
|
|
42
|
+
module InspectWrapper
|
|
43
|
+
# InspectWrapper is a factory, will never be an instance
|
|
44
|
+
# @return [ClassInspectWrapper, ObjectInspectWrapper]
|
|
45
|
+
def self.create(value, full = true)
|
|
46
|
+
if value.class == Class
|
|
47
|
+
ClassInspectWrapper
|
|
48
|
+
else
|
|
49
|
+
ObjectInspectWrapper
|
|
50
|
+
end.new(value, full)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @param full [Boolean] if false only unique `to_s` values will be output,
|
|
54
|
+
# non unique values become empty string.
|
|
55
|
+
def initialize(value, full)
|
|
56
|
+
@value, @full = value, full
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Inspect different types of contract values.
|
|
60
|
+
# Contracts module prefix will be removed from classes.
|
|
61
|
+
# Custom to_s messages will be wrapped in round brackets to differentiate
|
|
62
|
+
# from standard Strings.
|
|
63
|
+
# Primitive values e.g. 42, true, nil will be left alone.
|
|
64
|
+
def inspect
|
|
65
|
+
return "" unless full?
|
|
66
|
+
return @value.inspect if empty_val?
|
|
67
|
+
return @value.to_s if plain?
|
|
68
|
+
return delim(@value.to_s) if useful_to_s?
|
|
69
|
+
useful_inspect
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def delim(value)
|
|
73
|
+
@full ? "(#{value})" : "#{value}"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Eliminates eronious quotes in output that plain inspect includes.
|
|
77
|
+
def to_s
|
|
78
|
+
inspect
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def empty_val?
|
|
84
|
+
@value.nil? || @value == ""
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def full?
|
|
88
|
+
@full ||
|
|
89
|
+
@value.is_a?(Hash) || @value.is_a?(Array) ||
|
|
90
|
+
(!plain? && useful_to_s?)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def plain?
|
|
94
|
+
# Not a type of contract that can have a custom to_s defined
|
|
95
|
+
!@value.is_a?(Builtin::CallableClass) && @value.class != Class
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def useful_to_s?
|
|
99
|
+
# Useless to_s value or no custom to_s behavious defined
|
|
100
|
+
!empty_to_s? && custom_to_s?
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def empty_to_s?
|
|
104
|
+
@value.to_s.empty?
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def strip_prefix(val)
|
|
108
|
+
val.gsub(/^Contracts::Builtin::/, "")
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
class ClassInspectWrapper
|
|
113
|
+
include InspectWrapper
|
|
114
|
+
|
|
115
|
+
def custom_to_s?
|
|
116
|
+
@value.to_s != @value.name
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def useful_inspect
|
|
120
|
+
strip_prefix(empty_to_s? ? @value.name : @value.inspect)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
class ObjectInspectWrapper
|
|
125
|
+
include InspectWrapper
|
|
126
|
+
|
|
127
|
+
def custom_to_s?
|
|
128
|
+
!@value.to_s.match(/#\<\w+:.+\>/)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def useful_inspect
|
|
132
|
+
strip_prefix(empty_to_s? ? @value.class.name : @value.inspect)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module Contracts
|
|
2
|
+
module Invariants
|
|
3
|
+
def self.included(base)
|
|
4
|
+
common base
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def self.extended(base)
|
|
8
|
+
common base
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.common(base)
|
|
12
|
+
return if base.respond_to?(:Invariant)
|
|
13
|
+
|
|
14
|
+
base.extend(InvariantExtension)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def verify_invariants!(method)
|
|
18
|
+
return unless self.class.respond_to?(:invariants)
|
|
19
|
+
|
|
20
|
+
self.class.invariants.each do |invariant|
|
|
21
|
+
invariant.check_on(self, method)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
module InvariantExtension
|
|
26
|
+
def invariant(name, &condition)
|
|
27
|
+
return if ENV["NO_CONTRACTS"]
|
|
28
|
+
|
|
29
|
+
invariants << Invariant.new(self, name, &condition)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def invariants
|
|
33
|
+
@invariants ||= []
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class Invariant
|
|
38
|
+
def initialize(klass, name, &condition)
|
|
39
|
+
@klass, @name, @condition = klass, name, condition
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def expected
|
|
43
|
+
"#{@name} condition to be true"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def check_on(target, method)
|
|
47
|
+
return if target.instance_eval(&@condition)
|
|
48
|
+
|
|
49
|
+
self.class.failure_callback(:expected => expected,
|
|
50
|
+
:actual => false,
|
|
51
|
+
:target => target,
|
|
52
|
+
:method => method)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.failure_callback(data)
|
|
56
|
+
fail InvariantError, failure_msg(data)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.failure_msg(data)
|
|
60
|
+
%{Invariant violation:
|
|
61
|
+
Expected: #{data[:expected]}
|
|
62
|
+
Actual: #{data[:actual]}
|
|
63
|
+
Value guarded in: #{data[:target].class}::#{Support.method_name(data[:method])}
|
|
64
|
+
At: #{Support.method_position(data[:method])}}
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|