ree 1.0.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 (140) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +13 -0
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +5 -0
  6. data/CODE_OF_CONDUCT.md +84 -0
  7. data/Gemfile +9 -0
  8. data/Gemfile.lock +41 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +474 -0
  11. data/Rakefile +8 -0
  12. data/bin/console +8 -0
  13. data/bin/setup +8 -0
  14. data/exe/ree +264 -0
  15. data/lib/ree/args.rb +34 -0
  16. data/lib/ree/bean_dsl.rb +24 -0
  17. data/lib/ree/cli/generate_package.rb +18 -0
  18. data/lib/ree/cli/generate_package_schema.rb +54 -0
  19. data/lib/ree/cli/generate_packages_schema.rb +17 -0
  20. data/lib/ree/cli/generate_template.rb +20 -0
  21. data/lib/ree/cli/init.rb +22 -0
  22. data/lib/ree/cli/spec_runner.rb +184 -0
  23. data/lib/ree/cli.rb +12 -0
  24. data/lib/ree/container.rb +67 -0
  25. data/lib/ree/contracts/arg_contracts/any.rb +15 -0
  26. data/lib/ree/contracts/arg_contracts/array_of.rb +52 -0
  27. data/lib/ree/contracts/arg_contracts/block.rb +15 -0
  28. data/lib/ree/contracts/arg_contracts/bool.rb +21 -0
  29. data/lib/ree/contracts/arg_contracts/eq.rb +28 -0
  30. data/lib/ree/contracts/arg_contracts/exactly.rb +28 -0
  31. data/lib/ree/contracts/arg_contracts/hash_of.rb +59 -0
  32. data/lib/ree/contracts/arg_contracts/ksplat.rb +141 -0
  33. data/lib/ree/contracts/arg_contracts/kwargs.rb +22 -0
  34. data/lib/ree/contracts/arg_contracts/nilor.rb +16 -0
  35. data/lib/ree/contracts/arg_contracts/none.rb +15 -0
  36. data/lib/ree/contracts/arg_contracts/optblock.rb +11 -0
  37. data/lib/ree/contracts/arg_contracts/or.rb +30 -0
  38. data/lib/ree/contracts/arg_contracts/range_of.rb +51 -0
  39. data/lib/ree/contracts/arg_contracts/set_of.rb +54 -0
  40. data/lib/ree/contracts/arg_contracts/splat.rb +297 -0
  41. data/lib/ree/contracts/arg_contracts/splat_of.rb +64 -0
  42. data/lib/ree/contracts/arg_contracts/squarable.rb +11 -0
  43. data/lib/ree/contracts/arg_contracts/subclass_of.rb +28 -0
  44. data/lib/ree/contracts/arg_contracts.rb +29 -0
  45. data/lib/ree/contracts/called_args_validator.rb +291 -0
  46. data/lib/ree/contracts/contract_definition.rb +142 -0
  47. data/lib/ree/contracts/contractable.rb +34 -0
  48. data/lib/ree/contracts/core.rb +17 -0
  49. data/lib/ree/contracts/engine.rb +71 -0
  50. data/lib/ree/contracts/engine_proxy.rb +13 -0
  51. data/lib/ree/contracts/errors/bad_contract_error.rb +4 -0
  52. data/lib/ree/contracts/errors/contract_error.rb +4 -0
  53. data/lib/ree/contracts/errors/error.rb +4 -0
  54. data/lib/ree/contracts/errors/return_contract_error.rb +4 -0
  55. data/lib/ree/contracts/method_decorator.rb +158 -0
  56. data/lib/ree/contracts/truncatable.rb +9 -0
  57. data/lib/ree/contracts/utils.rb +9 -0
  58. data/lib/ree/contracts/validators/array_validator.rb +51 -0
  59. data/lib/ree/contracts/validators/base_validator.rb +27 -0
  60. data/lib/ree/contracts/validators/class_validator.rb +17 -0
  61. data/lib/ree/contracts/validators/default_validator.rb +20 -0
  62. data/lib/ree/contracts/validators/hash_validator.rb +100 -0
  63. data/lib/ree/contracts/validators/proc_validator.rb +17 -0
  64. data/lib/ree/contracts/validators/range_validator.rb +17 -0
  65. data/lib/ree/contracts/validators/regexp_validator.rb +17 -0
  66. data/lib/ree/contracts/validators/valid_validator.rb +28 -0
  67. data/lib/ree/contracts/validators.rb +42 -0
  68. data/lib/ree/contracts.rb +45 -0
  69. data/lib/ree/core/link_validator.rb +42 -0
  70. data/lib/ree/core/object.rb +132 -0
  71. data/lib/ree/core/object_error.rb +9 -0
  72. data/lib/ree/core/object_link.rb +21 -0
  73. data/lib/ree/core/object_schema.rb +47 -0
  74. data/lib/ree/core/object_schema_builder.rb +110 -0
  75. data/lib/ree/core/package.rb +177 -0
  76. data/lib/ree/core/package_dep.rb +9 -0
  77. data/lib/ree/core/package_env_var.rb +12 -0
  78. data/lib/ree/core/package_loader.rb +95 -0
  79. data/lib/ree/core/package_schema.rb +27 -0
  80. data/lib/ree/core/package_schema_builder.rb +53 -0
  81. data/lib/ree/core/package_schema_loader.rb +170 -0
  82. data/lib/ree/core/packages_detector.rb +43 -0
  83. data/lib/ree/core/packages_schema.rb +19 -0
  84. data/lib/ree/core/packages_schema_builder.rb +50 -0
  85. data/lib/ree/core/packages_schema_loader.rb +95 -0
  86. data/lib/ree/core/packages_schema_locator.rb +27 -0
  87. data/lib/ree/core/packages_store.rb +32 -0
  88. data/lib/ree/core/path_helper.rb +104 -0
  89. data/lib/ree/dsl/build_package_dsl.rb +155 -0
  90. data/lib/ree/dsl/domain_error.rb +4 -0
  91. data/lib/ree/dsl/error_builder.rb +39 -0
  92. data/lib/ree/dsl/error_dsl.rb +27 -0
  93. data/lib/ree/dsl/import_dsl.rb +106 -0
  94. data/lib/ree/dsl/link_import_builder.rb +66 -0
  95. data/lib/ree/dsl/object_dsl.rb +319 -0
  96. data/lib/ree/dsl/object_hooks.rb +6 -0
  97. data/lib/ree/dsl/package_require.rb +44 -0
  98. data/lib/ree/error.rb +11 -0
  99. data/lib/ree/facades/packages_facade.rb +197 -0
  100. data/lib/ree/fn_dsl.rb +24 -0
  101. data/lib/ree/gen/init.rb +64 -0
  102. data/lib/ree/gen/package.rb +56 -0
  103. data/lib/ree/gen.rb +8 -0
  104. data/lib/ree/handlers/template_handler.rb +118 -0
  105. data/lib/ree/link_dsl.rb +175 -0
  106. data/lib/ree/object_compiler.rb +149 -0
  107. data/lib/ree/package_dsl.rb +34 -0
  108. data/lib/ree/rspec_link_dsl.rb +19 -0
  109. data/lib/ree/spec_runner/command_generator.rb +49 -0
  110. data/lib/ree/spec_runner/command_params.rb +9 -0
  111. data/lib/ree/spec_runner/runner.rb +200 -0
  112. data/lib/ree/spec_runner/spec_filename_matcher.rb +27 -0
  113. data/lib/ree/spec_runner/view.rb +30 -0
  114. data/lib/ree/spec_runner.rb +11 -0
  115. data/lib/ree/templates/init/.gitignore +1 -0
  116. data/lib/ree/templates/init/.irbrc +13 -0
  117. data/lib/ree/templates/init/.rspec +3 -0
  118. data/lib/ree/templates/init/.ruby-version +1 -0
  119. data/lib/ree/templates/init/Gemfile +7 -0
  120. data/lib/ree/templates/init/Packages.schema.json +1 -0
  121. data/lib/ree/templates/init/bin/console +5 -0
  122. data/lib/ree/templates/init/readme.md +2 -0
  123. data/lib/ree/templates/init/ree.setup.rb +21 -0
  124. data/lib/ree/templates/init/spec.init.rb +7 -0
  125. data/lib/ree/templates/package/.gitignore +0 -0
  126. data/lib/ree/templates/package/.rspec +2 -0
  127. data/lib/ree/templates/package/<%=package_subdir_name%>/<%=package_name%>/.gitkeep +0 -0
  128. data/lib/ree/templates/package/<%=package_subdir_name%>/<%=package_name%>.rb +15 -0
  129. data/lib/ree/templates/package/Package.schema.json +0 -0
  130. data/lib/ree/templates/package/bin/console +5 -0
  131. data/lib/ree/templates/package/spec/package_schema_spec.rb +14 -0
  132. data/lib/ree/templates/package/spec/spec_helper.rb +3 -0
  133. data/lib/ree/templates/template_detector.rb +35 -0
  134. data/lib/ree/templates/template_renderer.rb +55 -0
  135. data/lib/ree/utils/render_utils.rb +20 -0
  136. data/lib/ree/utils/string_utils.rb +29 -0
  137. data/lib/ree/version.rb +5 -0
  138. data/lib/ree.rb +279 -0
  139. data/sig/ree.rbs +4 -0
  140. metadata +199 -0
@@ -0,0 +1,291 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ree::Contracts
4
+ class CalledArgsValidator
5
+ class ArgContract < Struct.new(:type, :contract, :arg); end
6
+ class Arg < Struct.new(:name, :type, :validator, :contract); end
7
+
8
+ attr_reader :contracts, :block_contract, :params, :printed_name
9
+
10
+ def initialize(contract, params, printed_name)
11
+ @contracts = contract.arg_contracts
12
+ @block_contract = contract.block_contract
13
+ @params = params
14
+ @printed_name = printed_name
15
+ @args = {}
16
+ build_args
17
+ end
18
+
19
+ def get_args
20
+ @args
21
+ end
22
+
23
+ def get_arg(arg_name)
24
+ @args[arg_name]
25
+ end
26
+
27
+ def call(args, kwargs, blk)
28
+ if block_contract == ArgContracts::Block && !blk
29
+ msg = "missing required block"
30
+ raise ContractError, "Wrong number of arguments for #{printed_name}\n\t - #{msg}"
31
+ end
32
+
33
+ errors = []
34
+
35
+ validate_args(args, errors)
36
+ validate_kwargs(kwargs, errors)
37
+
38
+ return if errors.empty?
39
+
40
+ msg = error_message(errors)
41
+ puts(colorize(msg))
42
+
43
+ raise ContractError, msg
44
+ end
45
+
46
+ private
47
+
48
+ def colorize(str)
49
+ "\e[31m#{str}\e[0m"
50
+ end
51
+
52
+ def build_args
53
+ ordinary_args = params.select { |type, _| type == :req || type == :opt }
54
+ all_keyword_args = params.select { |type, _| type == :keyreq || type == :key }
55
+ opt_keyword_args = params.select { |type, _| type == :key }
56
+ ksplat_arg = params.detect { |type, _| type == :keyrest }
57
+ splat_arg = params.detect { |type, _| type == :rest }
58
+
59
+ kwarg_contract = contracts.detect { _1.is_a?(ArgContracts::Kwargs) }
60
+ splat_contract = contracts.detect { _1.is_a?(ArgContracts::Splat) }
61
+ ksplat_contract = contracts.detect { _1.is_a?(ArgContracts::Ksplat) }
62
+ default_msg = "Contract definition mismatches method definition for #{printed_name}"
63
+
64
+ @ksplat_contract = ksplat_contract
65
+ @kwarg_contract = kwarg_contract
66
+
67
+ if !opt_keyword_args.empty?
68
+ if kwarg_contract.nil?
69
+ msgs = [default_msg]
70
+ msgs << "\t - methods with optional keyword arguments should use Kwargs[...] to describe all keyword args"
71
+ raise BadContractError, msgs.join("\n")
72
+ else
73
+ missing_keys = all_keyword_args.map { _2 } - kwarg_contract.keys
74
+
75
+ if !missing_keys.empty?
76
+ msgs = [default_msg]
77
+
78
+ missing_keys.each do |key|
79
+ msgs << "\t - missing Kwargs contract for keyword argument `#{key}`"
80
+ end
81
+ raise BadContractError, msgs.join("\n")
82
+ end
83
+ end
84
+ elsif kwarg_contract && all_keyword_args.empty?
85
+ msgs = [default_msg]
86
+ msgs << "\t - Kwargs contract should be used to describe keyword arguments only"
87
+ raise BadContractError, msgs.join("\n")
88
+ end
89
+
90
+ arg_contracts = []
91
+
92
+ contracts.each do |contract|
93
+ next if contract.is_a?(ArgContracts::None)
94
+
95
+ if contract.is_a?(ArgContracts::Kwargs)
96
+ contract.each do |key, cont|
97
+ arg_contracts << ArgContract.new(:key, contract, key)
98
+ end
99
+ elsif contract.is_a?(ArgContracts::SplatOf)
100
+ arg_contracts << ArgContract.new(:splat, contract, nil)
101
+ elsif contract.is_a?(ArgContracts::Splat)
102
+ arg_contracts << ArgContract.new(:splat, contract, nil)
103
+ elsif contract.is_a?(ArgContracts::Ksplat)
104
+ arg_contracts << ArgContract.new(:ksplat, contract, nil)
105
+ else
106
+ arg_contracts << ArgContract.new(:ord, contract, nil)
107
+ end
108
+ end
109
+
110
+ if block_contract
111
+ arg_contracts << ArgContract.new(:block, block_contract, nil)
112
+ end
113
+
114
+ if arg_contracts.size != params.size
115
+ msgs = [default_msg]
116
+ msgs << "\t - contract count is not equal to argument count"
117
+ msgs << "\t - contract count: #{arg_contracts.size}"
118
+ msgs << "\t - argument count: #{params.size}"
119
+ raise BadContractError, msgs.join("\n")
120
+ end
121
+
122
+ arg_contracts.each_with_index do |arg_contract, index|
123
+ type, name = params[index]
124
+
125
+ validator, contract = if (type == :req || type == :opt)
126
+ if arg_contract.type == :ord
127
+ [Validators.fetch_for(arg_contract.contract), arg_contract.contract]
128
+ end
129
+ elsif type == :key || type == :keyreq
130
+ if arg_contract.type == :key
131
+ [Validators.fetch_for(arg_contract.contract[name]), arg_contract.contract[name]]
132
+ elsif arg_contract.type == :ord
133
+ [Validators.fetch_for(arg_contract.contract), arg_contract.contract]
134
+ end
135
+ elsif type == :rest
136
+ if arg_contract.type == :splat
137
+ [Validators.fetch_for(arg_contract.contract), arg_contract.contract]
138
+ end
139
+ elsif type == :keyrest
140
+ if arg_contract.type == :ksplat
141
+ [Validators.fetch_for(arg_contract.contract), arg_contract.contract]
142
+ end
143
+ elsif type == :block
144
+ if arg_contract.type == :block
145
+ [nil, arg_contract.contract]
146
+ end
147
+ else
148
+ raise NotImplementedError, "type `#{type}` is not supported. Message gem owner to add it :)"
149
+ end
150
+
151
+ if validator.nil? && contract.nil?
152
+ msgs = [default_msg]
153
+ msgs << "\t - invalid contract for `#{name}` argument => #{arg_contract.contract.inspect}"
154
+ raise BadContractError, msgs.join("\n")
155
+ end
156
+
157
+ @args[name] = Arg.new(name, type, validator, contract)
158
+ end
159
+ end
160
+
161
+ def validate_kwargs(kwargs, errors)
162
+ extra_keys = kwargs.keys - key_params.map { _1.last }
163
+
164
+ if !extra_keys.empty? && !ksplat_arg
165
+ msgs = []
166
+
167
+ extra_keys.each do |key|
168
+ msgs << "\t - unknown keyword arg `#{key}`"
169
+ end
170
+
171
+ raise ContractError, "Wrong number of arguments for #{printed_name}\n#{msgs.join("\n")}"
172
+ end
173
+
174
+ key_params.each do |type, name|
175
+ if type == :keyreq && !kwargs.has_key?(name)
176
+ msg = "missing keyword arg `#{name}`"
177
+ raise ContractError, "Wrong number of arguments for #{printed_name}\n\t - #{msg}"
178
+ end
179
+
180
+ next if type == :key && !kwargs.has_key?(name)
181
+
182
+ arg = @args.fetch(name)
183
+ arg_value = kwargs.fetch(name)
184
+
185
+ next if arg.validator.call(arg_value)
186
+
187
+ errors << [name, arg.contract, arg_value]
188
+ end
189
+
190
+ if ksplat_arg && !extra_keys.empty?
191
+ key = ksplat_arg.last
192
+ arg = @args.fetch(key)
193
+ value = {}
194
+
195
+ extra_keys.each do |key|
196
+ value.store(key, kwargs[key])
197
+ end
198
+
199
+ if !arg.validator.call(value)
200
+ errors << [key, arg.contract, value]
201
+ end
202
+ end
203
+ end
204
+
205
+ def validate_args(args, errors)
206
+ args = args.dup
207
+ args_number = args.size
208
+ assigned_args = {}
209
+
210
+ # validate req args before opt/splat
211
+ arg_params.each do |(type, name)|
212
+ break if type != :req
213
+
214
+ if args.size == 0
215
+ msg = "missing value for `#{name}`"
216
+ raise ContractError, "Wrong number of arguments for #{printed_name}\n\t - #{msg}"
217
+ end
218
+
219
+ assigned_args[name] = true
220
+ arg_value = args.shift
221
+ arg = @args[name]
222
+ errors << [name, arg.contract, arg_value] unless arg.validator.call(arg_value)
223
+ end
224
+
225
+ # validate req args after opt/splat
226
+ arg_params.reverse_each.with_index(1) do |(type, name), idx|
227
+ break if type != :req
228
+ break if assigned_args[name]
229
+
230
+ if args.size == 0
231
+ msg = "missing value for `#{name}`"
232
+ raise ContractError, "Wrong number of arguments for #{printed_name}\n\t - #{msg}"
233
+ end
234
+
235
+ arg_value = args.pop
236
+ arg = @args[name]
237
+ errors << [name, arg.contract, arg_value] unless arg.validator.call(arg_value)
238
+ end unless args.empty?
239
+
240
+ # validate opt args
241
+ arg_params.each_with_index do |(type, name), idx|
242
+ break if args.empty?
243
+ next if type != :opt
244
+
245
+ arg_value = args.shift
246
+ arg = @args[name]
247
+ errors << [name, arg.contract, arg_value] unless arg.validator.call(arg_value)
248
+ end
249
+
250
+ if !splat_arg && args.size > 0
251
+ msg = "given #{args_number}, expected #{arg_params.size}"
252
+ raise ContractError, "Wrong number of arguments for #{printed_name}\n\t - #{msg}"
253
+ end
254
+
255
+ if splat_arg
256
+ name = splat_arg.last
257
+ arg = @args[name]
258
+ errors << [name, arg.contract, args] unless arg.validator.call(args)
259
+ end
260
+ end
261
+
262
+ def splat_arg
263
+ @splat_arg ||= params.detect { |type, _| type == :rest }
264
+ end
265
+
266
+ def ksplat_arg
267
+ @ksplat_arg ||= params.detect { |type, _| type == :keyrest }
268
+ end
269
+
270
+ def arg_params
271
+ @arg_params ||= params.select { |type, _| type == :req || type == :opt }
272
+ end
273
+
274
+ def key_params
275
+ @key_params ||= params.select { |type, _| type == :keyreq || type == :key }
276
+ end
277
+
278
+ def error_message(errors)
279
+ msgs = ["Contract violation for #{printed_name}"]
280
+
281
+ errors.each do |name, cont, val|
282
+ validator = Validators.fetch_for(cont)
283
+ msg = validator.message(val, name)
284
+ sps = msg.lines.one? ? ' ' : ''
285
+ msgs << "\t - #{name}:#{sps}#{msg}"
286
+ end
287
+
288
+ msgs.join("\n")
289
+ end
290
+ end
291
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ree::Contracts
4
+ class ContractDefinition
5
+ attr_reader :arg_contracts, :return_contract, :block_contract
6
+
7
+ def initialize(contract)
8
+ @arg_contracts, @return_contract = split_contract(contract)
9
+ @block_contract = arg_contracts.pop if ArgContracts.opt_or_block?(arg_contracts.last)
10
+
11
+ validate_block_contract
12
+ validate_return_contract
13
+ validate_kwargs_contract
14
+ validate_splat_contract
15
+ validate_ksplat_contract
16
+ validate_dependent_contracts
17
+ end
18
+
19
+ private
20
+
21
+ def validate_dependent_contracts
22
+ splat_index = arg_contracts.index { |cont| cont.is_a?(ArgContracts::Splat) }
23
+ splat_of_index = arg_contracts.index { |cont| cont.is_a?(ArgContracts::SplatOf) }
24
+ kwargs_index = arg_contracts.index { |cont| cont.is_a?(ArgContracts::Kwargs) }
25
+ ksplat_index = arg_contracts.index { |cont| cont.is_a?(ArgContracts::Ksplat) }
26
+
27
+ if splat_index && kwargs_index && splat_index > kwargs_index
28
+ raise BadContractError, "Splat contract should go before Kwargs"
29
+ end
30
+
31
+ if splat_of_index && kwargs_index && splat_of_index > kwargs_index
32
+ raise BadContractError, "SplatOf contract should go before Kwargs"
33
+ end
34
+
35
+ if splat_index && ksplat_index && splat_index > ksplat_index
36
+ raise BadContractError, "Splat contract should go before Ksplat"
37
+ end
38
+
39
+ if ksplat_index && kwargs_index && ksplat_index < kwargs_index
40
+ raise BadContractError, "Ksplat contract should go after Kwargs"
41
+ end
42
+ end
43
+
44
+ def validate_splat_contract
45
+ splat_cont_number = arg_contracts.count { |cont| cont.is_a?(ArgContracts::Splat) }
46
+
47
+ if splat_cont_number > 1
48
+ raise BadContractError, "Multiple Splat contracts are not allowed"
49
+ end
50
+
51
+ splat_of_cont_number = arg_contracts.count { |cont| cont.is_a?(ArgContracts::SplatOf) }
52
+
53
+ if splat_of_cont_number > 1
54
+ raise BadContractError, "Multiple SplatOf contracts are not allowed"
55
+ end
56
+ end
57
+
58
+ def validate_ksplat_contract
59
+ ksplat_cont_number = arg_contracts.count { |cont| cont.is_a?(ArgContracts::Ksplat) }
60
+
61
+ if ksplat_cont_number > 1
62
+ raise BadContractError, "Multiple Ksplat contracts are not allowed"
63
+ end
64
+
65
+ kwargs = arg_contracts.detect { |cont| cont.is_a?(ArgContracts::Kwargs) }
66
+ ksplat = arg_contracts.detect { |cont| cont.is_a?(ArgContracts::Ksplat) }
67
+
68
+ if kwargs && ksplat
69
+ keys = kwargs.contracts.keys & ksplat.validators.keys
70
+
71
+ if keys.size > 0
72
+ raise BadContractError, "Ksplat & Kwargs contracts has same keys #{keys.inspect}"
73
+ end
74
+ end
75
+ end
76
+
77
+ def validate_kwargs_contract
78
+ kwargs_cont_number = arg_contracts.count { |cont| cont.is_a?(ArgContracts::Kwargs) }
79
+
80
+ if kwargs_cont_number > 1
81
+ raise BadContractError, "Only one Kwargs contract could be provided"
82
+ end
83
+
84
+ kwargs_cont_index = arg_contracts.index { |cont| cont.is_a?(ArgContracts::Kwargs) }
85
+ ksplat_index = arg_contracts.index { |cont| cont.is_a?(ArgContracts::Ksplat) }
86
+ splat_index = arg_contracts.index { |cont| cont.is_a?(ArgContracts::Splat) }
87
+ splat_of_index = arg_contracts.index { |cont| cont.is_a?(ArgContracts::SplatOf) }
88
+
89
+ if kwargs_cont_index && !ksplat_index && !splat_index && !splat_of_index
90
+ return if kwargs_cont_index == arg_contracts.size - 1
91
+ raise BadContractError, 'Kwargs contract should appear in the end'
92
+ end
93
+ end
94
+
95
+ FORBIDDEN_RETURN_CONTRACTS = [
96
+ ArgContracts::None, ArgContracts::Kwargs,
97
+ ArgContracts::Block, ArgContracts::Optblock,
98
+ ArgContracts::Splat, ArgContracts::Ksplat
99
+ ]
100
+
101
+ def validate_return_contract
102
+ return if !FORBIDDEN_RETURN_CONTRACTS.include?(return_contract) && !FORBIDDEN_RETURN_CONTRACTS.include?(return_contract.class)
103
+ raise BadContractError, "Contract for return value does not support None, Kwargs, Block, Optblock, Splat, Ksplat"
104
+ end
105
+
106
+ def validate_block_contract
107
+ block_cont_idx = arg_contracts.index { |cont| ArgContracts.opt_or_block?(cont) }
108
+ return if !block_cont_idx
109
+ raise BadContractError, 'Block (Optblock) contract should appear in the end'
110
+ end
111
+
112
+ def split_contract(contract)
113
+ last_contract = contract.last
114
+
115
+ if last_contract.is_a?(Hash) && last_contract.one?
116
+ last_arg_contract, return_contract = last_contract.first
117
+
118
+ arg_contracts = contract[0..-2] + [last_arg_contract]
119
+
120
+ if arg_contracts.any? { |cont| cont == ArgContracts::None }
121
+ if arg_contracts.size > 1
122
+ raise BadContractError, 'Combination of None contract with other contracts is not allowed'
123
+ end
124
+
125
+ arg_contracts = []
126
+ end
127
+
128
+ return [arg_contracts, return_contract]
129
+ end
130
+
131
+ return [[], contract.first] if contract.one?
132
+
133
+ raise BadContractError, <<~STR
134
+ It looks like your contract doesn't have a return value.
135
+ A contract should be written as
136
+ `contract arg1, arg2 => return_value`
137
+ or
138
+ `contract return_value`.
139
+ STR
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'method_decorator'
4
+ require_relative 'engine'
5
+ require_relative 'engine_proxy'
6
+
7
+ module Ree::Contracts
8
+ module Contractable
9
+ if !Ree::Contracts.no_contracts?
10
+ def method_added(name)
11
+ MethodDecorator.new(name, false, self).call
12
+ super
13
+ end
14
+
15
+ def singleton_method_added(name)
16
+ MethodDecorator.new(name, true, self).call
17
+ super
18
+ end
19
+ end
20
+
21
+ def doc(str)
22
+ return if Ree::Contracts.no_contracts?
23
+
24
+ engine = Engine.fetch_for(self)
25
+ engine.add_doc(str.strip)
26
+ end
27
+
28
+ def contract(*args, &block)
29
+ engine = Engine.fetch_for(self)
30
+ engine.add_contract(*args, &block)
31
+ EngineProxy.new(engine)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ree::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(Contractable)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ree::Contracts
4
+ class Engine
5
+ class << self
6
+ def fetch_for(target)
7
+ engines[target.object_id] ||= new(target)
8
+ end
9
+
10
+ private
11
+
12
+ def engines
13
+ @engines ||= {}
14
+ end
15
+ end
16
+
17
+ include Ree::Args
18
+
19
+ attr_reader :target
20
+
21
+ def initialize(target)
22
+ check_arg_any(target, :target, [Class, Module])
23
+ @target = target
24
+ end
25
+
26
+ def add_contract(*args)
27
+ if @contract
28
+ raise Ree::Error.new('Another active contract definition found', :invalid_dsl_usage)
29
+ end
30
+
31
+ @contract = ContractDefinition.new(args)
32
+ end
33
+
34
+ def add_doc(str)
35
+ check_arg(str, :str, String)
36
+
37
+ if @doc
38
+ raise Ree::Error.new('Another active contract definition found', :invalid_dsl_usage)
39
+ end
40
+
41
+ @doc = str
42
+ end
43
+
44
+ def add_errors(*errors)
45
+ if @errors
46
+ raise Ree::Error.new('Another active contract definition found', :invalid_dsl_usage)
47
+ end
48
+
49
+ errors.each do |e|
50
+ check_arg(e, :errors, Class)
51
+ end
52
+
53
+ @errors = errors
54
+ end
55
+
56
+ def fetch_doc
57
+ doc, @doc = @doc, nil
58
+ doc
59
+ end
60
+
61
+ def fetch_contract
62
+ contract, @contract = @contract, nil
63
+ contract
64
+ end
65
+
66
+ def fetch_errors
67
+ errors, @errors = @errors, nil
68
+ errors
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ree::Contracts
4
+ class EngineProxy
5
+ def initialize(engine)
6
+ @engine = engine
7
+ end
8
+
9
+ def throws(*errors)
10
+ @engine.add_errors(*errors)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,4 @@
1
+ module Ree::Contracts
2
+ class BadContractError < Error
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Ree::Contracts
2
+ class ContractError < Error
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Ree::Contracts
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Ree::Contracts
2
+ class ReturnContractError < ContractError
3
+ end
4
+ end