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