ree 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +13 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +41 -0
- data/LICENSE.txt +21 -0
- data/README.md +474 -0
- data/Rakefile +8 -0
- data/bin/console +8 -0
- data/bin/setup +8 -0
- data/exe/ree +264 -0
- data/lib/ree/args.rb +34 -0
- data/lib/ree/bean_dsl.rb +24 -0
- data/lib/ree/cli/generate_package.rb +18 -0
- data/lib/ree/cli/generate_package_schema.rb +54 -0
- data/lib/ree/cli/generate_packages_schema.rb +17 -0
- data/lib/ree/cli/generate_template.rb +20 -0
- data/lib/ree/cli/init.rb +22 -0
- data/lib/ree/cli/spec_runner.rb +184 -0
- data/lib/ree/cli.rb +12 -0
- data/lib/ree/container.rb +67 -0
- data/lib/ree/contracts/arg_contracts/any.rb +15 -0
- data/lib/ree/contracts/arg_contracts/array_of.rb +52 -0
- data/lib/ree/contracts/arg_contracts/block.rb +15 -0
- data/lib/ree/contracts/arg_contracts/bool.rb +21 -0
- data/lib/ree/contracts/arg_contracts/eq.rb +28 -0
- data/lib/ree/contracts/arg_contracts/exactly.rb +28 -0
- data/lib/ree/contracts/arg_contracts/hash_of.rb +59 -0
- data/lib/ree/contracts/arg_contracts/ksplat.rb +141 -0
- data/lib/ree/contracts/arg_contracts/kwargs.rb +22 -0
- data/lib/ree/contracts/arg_contracts/nilor.rb +16 -0
- data/lib/ree/contracts/arg_contracts/none.rb +15 -0
- data/lib/ree/contracts/arg_contracts/optblock.rb +11 -0
- data/lib/ree/contracts/arg_contracts/or.rb +30 -0
- data/lib/ree/contracts/arg_contracts/range_of.rb +51 -0
- data/lib/ree/contracts/arg_contracts/set_of.rb +54 -0
- data/lib/ree/contracts/arg_contracts/splat.rb +297 -0
- data/lib/ree/contracts/arg_contracts/splat_of.rb +64 -0
- data/lib/ree/contracts/arg_contracts/squarable.rb +11 -0
- data/lib/ree/contracts/arg_contracts/subclass_of.rb +28 -0
- data/lib/ree/contracts/arg_contracts.rb +29 -0
- data/lib/ree/contracts/called_args_validator.rb +291 -0
- data/lib/ree/contracts/contract_definition.rb +142 -0
- data/lib/ree/contracts/contractable.rb +34 -0
- data/lib/ree/contracts/core.rb +17 -0
- data/lib/ree/contracts/engine.rb +71 -0
- data/lib/ree/contracts/engine_proxy.rb +13 -0
- data/lib/ree/contracts/errors/bad_contract_error.rb +4 -0
- data/lib/ree/contracts/errors/contract_error.rb +4 -0
- data/lib/ree/contracts/errors/error.rb +4 -0
- data/lib/ree/contracts/errors/return_contract_error.rb +4 -0
- data/lib/ree/contracts/method_decorator.rb +158 -0
- data/lib/ree/contracts/truncatable.rb +9 -0
- data/lib/ree/contracts/utils.rb +9 -0
- data/lib/ree/contracts/validators/array_validator.rb +51 -0
- data/lib/ree/contracts/validators/base_validator.rb +27 -0
- data/lib/ree/contracts/validators/class_validator.rb +17 -0
- data/lib/ree/contracts/validators/default_validator.rb +20 -0
- data/lib/ree/contracts/validators/hash_validator.rb +100 -0
- data/lib/ree/contracts/validators/proc_validator.rb +17 -0
- data/lib/ree/contracts/validators/range_validator.rb +17 -0
- data/lib/ree/contracts/validators/regexp_validator.rb +17 -0
- data/lib/ree/contracts/validators/valid_validator.rb +28 -0
- data/lib/ree/contracts/validators.rb +42 -0
- data/lib/ree/contracts.rb +45 -0
- data/lib/ree/core/link_validator.rb +42 -0
- data/lib/ree/core/object.rb +132 -0
- data/lib/ree/core/object_error.rb +9 -0
- data/lib/ree/core/object_link.rb +21 -0
- data/lib/ree/core/object_schema.rb +47 -0
- data/lib/ree/core/object_schema_builder.rb +110 -0
- data/lib/ree/core/package.rb +177 -0
- data/lib/ree/core/package_dep.rb +9 -0
- data/lib/ree/core/package_env_var.rb +12 -0
- data/lib/ree/core/package_loader.rb +95 -0
- data/lib/ree/core/package_schema.rb +27 -0
- data/lib/ree/core/package_schema_builder.rb +53 -0
- data/lib/ree/core/package_schema_loader.rb +170 -0
- data/lib/ree/core/packages_detector.rb +43 -0
- data/lib/ree/core/packages_schema.rb +19 -0
- data/lib/ree/core/packages_schema_builder.rb +50 -0
- data/lib/ree/core/packages_schema_loader.rb +95 -0
- data/lib/ree/core/packages_schema_locator.rb +27 -0
- data/lib/ree/core/packages_store.rb +32 -0
- data/lib/ree/core/path_helper.rb +104 -0
- data/lib/ree/dsl/build_package_dsl.rb +155 -0
- data/lib/ree/dsl/domain_error.rb +4 -0
- data/lib/ree/dsl/error_builder.rb +39 -0
- data/lib/ree/dsl/error_dsl.rb +27 -0
- data/lib/ree/dsl/import_dsl.rb +106 -0
- data/lib/ree/dsl/link_import_builder.rb +66 -0
- data/lib/ree/dsl/object_dsl.rb +319 -0
- data/lib/ree/dsl/object_hooks.rb +6 -0
- data/lib/ree/dsl/package_require.rb +44 -0
- data/lib/ree/error.rb +11 -0
- data/lib/ree/facades/packages_facade.rb +197 -0
- data/lib/ree/fn_dsl.rb +24 -0
- data/lib/ree/gen/init.rb +64 -0
- data/lib/ree/gen/package.rb +56 -0
- data/lib/ree/gen.rb +8 -0
- data/lib/ree/handlers/template_handler.rb +118 -0
- data/lib/ree/link_dsl.rb +175 -0
- data/lib/ree/object_compiler.rb +149 -0
- data/lib/ree/package_dsl.rb +34 -0
- data/lib/ree/rspec_link_dsl.rb +19 -0
- data/lib/ree/spec_runner/command_generator.rb +49 -0
- data/lib/ree/spec_runner/command_params.rb +9 -0
- data/lib/ree/spec_runner/runner.rb +200 -0
- data/lib/ree/spec_runner/spec_filename_matcher.rb +27 -0
- data/lib/ree/spec_runner/view.rb +30 -0
- data/lib/ree/spec_runner.rb +11 -0
- data/lib/ree/templates/init/.gitignore +1 -0
- data/lib/ree/templates/init/.irbrc +13 -0
- data/lib/ree/templates/init/.rspec +3 -0
- data/lib/ree/templates/init/.ruby-version +1 -0
- data/lib/ree/templates/init/Gemfile +7 -0
- data/lib/ree/templates/init/Packages.schema.json +1 -0
- data/lib/ree/templates/init/bin/console +5 -0
- data/lib/ree/templates/init/readme.md +2 -0
- data/lib/ree/templates/init/ree.setup.rb +21 -0
- data/lib/ree/templates/init/spec.init.rb +7 -0
- data/lib/ree/templates/package/.gitignore +0 -0
- data/lib/ree/templates/package/.rspec +2 -0
- data/lib/ree/templates/package/<%=package_subdir_name%>/<%=package_name%>/.gitkeep +0 -0
- data/lib/ree/templates/package/<%=package_subdir_name%>/<%=package_name%>.rb +15 -0
- data/lib/ree/templates/package/Package.schema.json +0 -0
- data/lib/ree/templates/package/bin/console +5 -0
- data/lib/ree/templates/package/spec/package_schema_spec.rb +14 -0
- data/lib/ree/templates/package/spec/spec_helper.rb +3 -0
- data/lib/ree/templates/template_detector.rb +35 -0
- data/lib/ree/templates/template_renderer.rb +55 -0
- data/lib/ree/utils/render_utils.rb +20 -0
- data/lib/ree/utils/string_utils.rb +29 -0
- data/lib/ree/version.rb +5 -0
- data/lib/ree.rb +279 -0
- data/sig/ree.rbs +4 -0
- 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,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
|