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.
Files changed (176) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/bin/deploy-entitlements +10 -1
  4. data/lib/contracts-ruby2/CHANGELOG.markdown +115 -0
  5. data/lib/contracts-ruby2/Gemfile +17 -0
  6. data/lib/contracts-ruby2/LICENSE +23 -0
  7. data/lib/contracts-ruby2/README.md +108 -0
  8. data/lib/contracts-ruby2/Rakefile +8 -0
  9. data/lib/contracts-ruby2/TODO.markdown +6 -0
  10. data/lib/contracts-ruby2/TUTORIAL.md +773 -0
  11. data/lib/contracts-ruby2/benchmarks/bench.rb +67 -0
  12. data/lib/contracts-ruby2/benchmarks/hash.rb +69 -0
  13. data/lib/contracts-ruby2/benchmarks/invariants.rb +91 -0
  14. data/lib/contracts-ruby2/benchmarks/io.rb +62 -0
  15. data/lib/contracts-ruby2/benchmarks/wrap_test.rb +57 -0
  16. data/lib/contracts-ruby2/contracts.gemspec +17 -0
  17. data/lib/contracts-ruby2/cucumber.yml +1 -0
  18. data/lib/contracts-ruby2/dependabot.yml +20 -0
  19. data/lib/contracts-ruby2/features/README.md +17 -0
  20. data/lib/contracts-ruby2/features/basics/functype.feature +71 -0
  21. data/lib/contracts-ruby2/features/basics/pretty-print.feature +241 -0
  22. data/lib/contracts-ruby2/features/basics/simple_example.feature +210 -0
  23. data/lib/contracts-ruby2/features/builtin_contracts/README.md +22 -0
  24. data/lib/contracts-ruby2/features/builtin_contracts/and.feature +103 -0
  25. data/lib/contracts-ruby2/features/builtin_contracts/any.feature +44 -0
  26. data/lib/contracts-ruby2/features/builtin_contracts/args.feature +80 -0
  27. data/lib/contracts-ruby2/features/builtin_contracts/array_of.feature +1 -0
  28. data/lib/contracts-ruby2/features/builtin_contracts/bool.feature +64 -0
  29. data/lib/contracts-ruby2/features/builtin_contracts/enum.feature +1 -0
  30. data/lib/contracts-ruby2/features/builtin_contracts/eq.feature +1 -0
  31. data/lib/contracts-ruby2/features/builtin_contracts/exactly.feature +1 -0
  32. data/lib/contracts-ruby2/features/builtin_contracts/func.feature +1 -0
  33. data/lib/contracts-ruby2/features/builtin_contracts/hash_of.feature +1 -0
  34. data/lib/contracts-ruby2/features/builtin_contracts/int.feature +93 -0
  35. data/lib/contracts-ruby2/features/builtin_contracts/keyword_args.feature +1 -0
  36. data/lib/contracts-ruby2/features/builtin_contracts/maybe.feature +1 -0
  37. data/lib/contracts-ruby2/features/builtin_contracts/nat.feature +115 -0
  38. data/lib/contracts-ruby2/features/builtin_contracts/nat_pos.feature +119 -0
  39. data/lib/contracts-ruby2/features/builtin_contracts/neg.feature +115 -0
  40. data/lib/contracts-ruby2/features/builtin_contracts/none.feature +145 -0
  41. data/lib/contracts-ruby2/features/builtin_contracts/not.feature +1 -0
  42. data/lib/contracts-ruby2/features/builtin_contracts/num.feature +64 -0
  43. data/lib/contracts-ruby2/features/builtin_contracts/or.feature +83 -0
  44. data/lib/contracts-ruby2/features/builtin_contracts/pos.feature +116 -0
  45. data/lib/contracts-ruby2/features/builtin_contracts/range_of.feature +1 -0
  46. data/lib/contracts-ruby2/features/builtin_contracts/respond_to.feature +78 -0
  47. data/lib/contracts-ruby2/features/builtin_contracts/send.feature +147 -0
  48. data/lib/contracts-ruby2/features/builtin_contracts/set_of.feature +1 -0
  49. data/lib/contracts-ruby2/features/builtin_contracts/xor.feature +99 -0
  50. data/lib/contracts-ruby2/features/support/env.rb +6 -0
  51. data/lib/contracts-ruby2/lib/contracts/attrs.rb +24 -0
  52. data/lib/contracts-ruby2/lib/contracts/builtin_contracts.rb +542 -0
  53. data/lib/contracts-ruby2/lib/contracts/call_with.rb +108 -0
  54. data/lib/contracts-ruby2/lib/contracts/core.rb +52 -0
  55. data/lib/contracts-ruby2/lib/contracts/decorators.rb +47 -0
  56. data/lib/contracts-ruby2/lib/contracts/engine/base.rb +136 -0
  57. data/lib/contracts-ruby2/lib/contracts/engine/eigenclass.rb +50 -0
  58. data/lib/contracts-ruby2/lib/contracts/engine/target.rb +70 -0
  59. data/lib/contracts-ruby2/lib/contracts/engine.rb +26 -0
  60. data/lib/contracts-ruby2/lib/contracts/errors.rb +71 -0
  61. data/lib/contracts-ruby2/lib/contracts/formatters.rb +136 -0
  62. data/lib/contracts-ruby2/lib/contracts/invariants.rb +68 -0
  63. data/lib/contracts-ruby2/lib/contracts/method_handler.rb +187 -0
  64. data/lib/contracts-ruby2/lib/contracts/method_reference.rb +100 -0
  65. data/lib/contracts-ruby2/lib/contracts/support.rb +61 -0
  66. data/lib/contracts-ruby2/lib/contracts/validators.rb +139 -0
  67. data/lib/contracts-ruby2/lib/contracts/version.rb +3 -0
  68. data/lib/contracts-ruby2/lib/contracts.rb +281 -0
  69. data/lib/contracts-ruby2/script/docs-release +3 -0
  70. data/lib/contracts-ruby2/script/docs-staging +3 -0
  71. data/lib/contracts-ruby2/script/rubocop.rb +5 -0
  72. data/lib/contracts-ruby2/spec/attrs_spec.rb +119 -0
  73. data/lib/contracts-ruby2/spec/builtin_contracts_spec.rb +461 -0
  74. data/lib/contracts-ruby2/spec/contracts_spec.rb +770 -0
  75. data/lib/contracts-ruby2/spec/fixtures/fixtures.rb +730 -0
  76. data/lib/contracts-ruby2/spec/invariants_spec.rb +17 -0
  77. data/lib/contracts-ruby2/spec/methods_spec.rb +54 -0
  78. data/lib/contracts-ruby2/spec/module_spec.rb +18 -0
  79. data/lib/contracts-ruby2/spec/override_validators_spec.rb +162 -0
  80. data/lib/contracts-ruby2/spec/ruby_version_specific/contracts_spec_1.9.rb +24 -0
  81. data/lib/contracts-ruby2/spec/ruby_version_specific/contracts_spec_2.0.rb +55 -0
  82. data/lib/contracts-ruby2/spec/ruby_version_specific/contracts_spec_2.1.rb +63 -0
  83. data/lib/contracts-ruby2/spec/spec_helper.rb +102 -0
  84. data/lib/contracts-ruby2/spec/support.rb +10 -0
  85. data/lib/contracts-ruby2/spec/support_spec.rb +21 -0
  86. data/lib/contracts-ruby2/spec/validators_spec.rb +47 -0
  87. data/lib/contracts-ruby3/CHANGELOG.markdown +117 -0
  88. data/lib/contracts-ruby3/Gemfile +21 -0
  89. data/lib/contracts-ruby3/LICENSE +23 -0
  90. data/lib/contracts-ruby3/README.md +114 -0
  91. data/lib/contracts-ruby3/Rakefile +10 -0
  92. data/lib/contracts-ruby3/TODO.markdown +6 -0
  93. data/lib/contracts-ruby3/TUTORIAL.md +773 -0
  94. data/lib/contracts-ruby3/benchmarks/bench.rb +67 -0
  95. data/lib/contracts-ruby3/benchmarks/hash.rb +69 -0
  96. data/lib/contracts-ruby3/benchmarks/invariants.rb +91 -0
  97. data/lib/contracts-ruby3/benchmarks/io.rb +62 -0
  98. data/lib/contracts-ruby3/benchmarks/wrap_test.rb +57 -0
  99. data/lib/contracts-ruby3/contracts.gemspec +20 -0
  100. data/lib/contracts-ruby3/cucumber.yml +1 -0
  101. data/lib/contracts-ruby3/dependabot.yml +20 -0
  102. data/lib/contracts-ruby3/features/README.md +17 -0
  103. data/lib/contracts-ruby3/features/basics/functype.feature +71 -0
  104. data/lib/contracts-ruby3/features/basics/pretty-print.feature +241 -0
  105. data/lib/contracts-ruby3/features/basics/simple_example.feature +210 -0
  106. data/lib/contracts-ruby3/features/builtin_contracts/README.md +22 -0
  107. data/lib/contracts-ruby3/features/builtin_contracts/and.feature +103 -0
  108. data/lib/contracts-ruby3/features/builtin_contracts/any.feature +44 -0
  109. data/lib/contracts-ruby3/features/builtin_contracts/args.feature +80 -0
  110. data/lib/contracts-ruby3/features/builtin_contracts/array_of.feature +1 -0
  111. data/lib/contracts-ruby3/features/builtin_contracts/bool.feature +64 -0
  112. data/lib/contracts-ruby3/features/builtin_contracts/enum.feature +1 -0
  113. data/lib/contracts-ruby3/features/builtin_contracts/eq.feature +1 -0
  114. data/lib/contracts-ruby3/features/builtin_contracts/exactly.feature +1 -0
  115. data/lib/contracts-ruby3/features/builtin_contracts/func.feature +1 -0
  116. data/lib/contracts-ruby3/features/builtin_contracts/hash_of.feature +1 -0
  117. data/lib/contracts-ruby3/features/builtin_contracts/int.feature +93 -0
  118. data/lib/contracts-ruby3/features/builtin_contracts/keyword_args.feature +1 -0
  119. data/lib/contracts-ruby3/features/builtin_contracts/maybe.feature +1 -0
  120. data/lib/contracts-ruby3/features/builtin_contracts/nat.feature +115 -0
  121. data/lib/contracts-ruby3/features/builtin_contracts/nat_pos.feature +119 -0
  122. data/lib/contracts-ruby3/features/builtin_contracts/neg.feature +115 -0
  123. data/lib/contracts-ruby3/features/builtin_contracts/none.feature +145 -0
  124. data/lib/contracts-ruby3/features/builtin_contracts/not.feature +1 -0
  125. data/lib/contracts-ruby3/features/builtin_contracts/num.feature +64 -0
  126. data/lib/contracts-ruby3/features/builtin_contracts/or.feature +83 -0
  127. data/lib/contracts-ruby3/features/builtin_contracts/pos.feature +116 -0
  128. data/lib/contracts-ruby3/features/builtin_contracts/range_of.feature +1 -0
  129. data/lib/contracts-ruby3/features/builtin_contracts/respond_to.feature +78 -0
  130. data/lib/contracts-ruby3/features/builtin_contracts/send.feature +147 -0
  131. data/lib/contracts-ruby3/features/builtin_contracts/set_of.feature +1 -0
  132. data/lib/contracts-ruby3/features/builtin_contracts/xor.feature +99 -0
  133. data/lib/contracts-ruby3/features/support/env.rb +8 -0
  134. data/lib/contracts-ruby3/lib/contracts/attrs.rb +26 -0
  135. data/lib/contracts-ruby3/lib/contracts/builtin_contracts.rb +575 -0
  136. data/lib/contracts-ruby3/lib/contracts/call_with.rb +119 -0
  137. data/lib/contracts-ruby3/lib/contracts/core.rb +54 -0
  138. data/lib/contracts-ruby3/lib/contracts/decorators.rb +50 -0
  139. data/lib/contracts-ruby3/lib/contracts/engine/base.rb +137 -0
  140. data/lib/contracts-ruby3/lib/contracts/engine/eigenclass.rb +51 -0
  141. data/lib/contracts-ruby3/lib/contracts/engine/target.rb +72 -0
  142. data/lib/contracts-ruby3/lib/contracts/engine.rb +28 -0
  143. data/lib/contracts-ruby3/lib/contracts/errors.rb +74 -0
  144. data/lib/contracts-ruby3/lib/contracts/formatters.rb +140 -0
  145. data/lib/contracts-ruby3/lib/contracts/invariants.rb +72 -0
  146. data/lib/contracts-ruby3/lib/contracts/method_handler.rb +197 -0
  147. data/lib/contracts-ruby3/lib/contracts/method_reference.rb +102 -0
  148. data/lib/contracts-ruby3/lib/contracts/support.rb +63 -0
  149. data/lib/contracts-ruby3/lib/contracts/validators.rb +143 -0
  150. data/lib/contracts-ruby3/lib/contracts/version.rb +5 -0
  151. data/lib/contracts-ruby3/lib/contracts.rb +290 -0
  152. data/lib/contracts-ruby3/script/docs-release +3 -0
  153. data/lib/contracts-ruby3/script/docs-staging +3 -0
  154. data/lib/contracts-ruby3/script/rubocop.rb +5 -0
  155. data/lib/contracts-ruby3/spec/attrs_spec.rb +119 -0
  156. data/lib/contracts-ruby3/spec/builtin_contracts_spec.rb +457 -0
  157. data/lib/contracts-ruby3/spec/contracts_spec.rb +773 -0
  158. data/lib/contracts-ruby3/spec/fixtures/fixtures.rb +725 -0
  159. data/lib/contracts-ruby3/spec/invariants_spec.rb +17 -0
  160. data/lib/contracts-ruby3/spec/methods_spec.rb +54 -0
  161. data/lib/contracts-ruby3/spec/module_spec.rb +18 -0
  162. data/lib/contracts-ruby3/spec/override_validators_spec.rb +162 -0
  163. data/lib/contracts-ruby3/spec/ruby_version_specific/contracts_spec_1.9.rb +24 -0
  164. data/lib/contracts-ruby3/spec/ruby_version_specific/contracts_spec_2.0.rb +55 -0
  165. data/lib/contracts-ruby3/spec/ruby_version_specific/contracts_spec_2.1.rb +63 -0
  166. data/lib/contracts-ruby3/spec/spec_helper.rb +102 -0
  167. data/lib/contracts-ruby3/spec/support.rb +10 -0
  168. data/lib/contracts-ruby3/spec/support_spec.rb +21 -0
  169. data/lib/contracts-ruby3/spec/validators_spec.rb +47 -0
  170. data/lib/entitlements/data/groups/calculated/yaml.rb +7 -1
  171. data/lib/entitlements/data/people/yaml.rb +9 -1
  172. data/lib/entitlements/extras/ldap_group/rules/ldap_group.rb +5 -1
  173. data/lib/entitlements/extras/orgchart/person_methods.rb +7 -1
  174. data/lib/entitlements.rb +13 -2
  175. data/lib/ruby_version_check.rb +17 -0
  176. 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