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.
- 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
|