params-registry 0.1.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/.yardopts +2 -0
- data/LICENSE +202 -0
- data/README.md +86 -0
- data/Rakefile +8 -0
- data/lib/params/registry/error.rb +46 -0
- data/lib/params/registry/instance.rb +210 -0
- data/lib/params/registry/template.rb +400 -0
- data/lib/params/registry/types.rb +167 -0
- data/lib/params/registry/version.rb +8 -0
- data/lib/params/registry.rb +403 -0
- data/lib/params-registry.rb +1 -0
- data/params-registry.gemspec +41 -0
- data/sig/params/registry.rbs +6 -0
- metadata +77 -0
@@ -0,0 +1,400 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'types'
|
4
|
+
require_relative 'error'
|
5
|
+
|
6
|
+
# This class manages an individual parameter template.
|
7
|
+
class Params::Registry::Template
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
# this is dumb
|
12
|
+
Types = Params::Registry::Types
|
13
|
+
|
14
|
+
public
|
15
|
+
|
16
|
+
# Initialize the template object.
|
17
|
+
#
|
18
|
+
# @param registry [Params::Registry] A backreference to the
|
19
|
+
# parameter registry
|
20
|
+
# @param id [Object] The canonical, unique identifier for the
|
21
|
+
# parameter
|
22
|
+
# @param slug [#to_sym] A "friendly" symbol that will end up in
|
23
|
+
# the serialization
|
24
|
+
# @param aliases [Array<#to_sym>] Alternative nicknames for the
|
25
|
+
# parameter
|
26
|
+
# @param type [Dry::Types::Type, Symbol, Proc] An "atomic" type
|
27
|
+
# for single values
|
28
|
+
# @param composite [Dry::Types::Type, Symbol, Proc] A composite
|
29
|
+
# type into which multiple values are loaded
|
30
|
+
# @param format [String, Proc, nil] A format string or function
|
31
|
+
# @param depends [Array] Parameters that this one depends on
|
32
|
+
# @param conflicts [Array] Parameters that conflict with this one
|
33
|
+
# @param consumes [Array] Parameters that can be given in lieu of
|
34
|
+
# this one, that will be composed into this one. Parameters this
|
35
|
+
# one `consumes` implies `depends` _and_ `conflicts`.
|
36
|
+
# @param preproc [Proc, nil] A preprocessing function that is fed
|
37
|
+
# parameters from `consumes` and `depends` to generate this
|
38
|
+
# parameter
|
39
|
+
# @param min [Integer, nil] Minimum cardinality
|
40
|
+
# @param max [Integer, nil] Maximum cardinality
|
41
|
+
# @param shift [false, true] When given more than `max` values, do
|
42
|
+
# we take the ones we want from the back or from the front
|
43
|
+
# @param empty [false, true, nil] whether to treat an empty value
|
44
|
+
# as nil, the empty string, or discard it
|
45
|
+
# @param default [Object, nil] A default value
|
46
|
+
# @param universe [Proc] For {::Set} or {::Range} composite types and
|
47
|
+
# derivatives, a function that returns the universal set or range
|
48
|
+
# @param complement [Proc] For {::Set} or {::Range} composite types, a
|
49
|
+
# function that will return the complement of the set or range
|
50
|
+
# @param unwind [Proc] A function that takes the composite type
|
51
|
+
# and turns it into an {::Array} of atomic values
|
52
|
+
# @param reverse [false, true] For {::Range} composite types, a flag
|
53
|
+
# that indicates whether the values should be interpreted and/or
|
54
|
+
# serialized in reverse order. Also governs the serialization of
|
55
|
+
# {::Set} composites.
|
56
|
+
#
|
57
|
+
def initialize registry, id, slug: nil, type: Types::NormalizedString,
|
58
|
+
composite: nil, format: nil, aliases: nil, depends: nil, conflicts: nil,
|
59
|
+
consumes: nil, preproc: nil, min: 0, max: nil, shift: false,
|
60
|
+
empty: false, default: nil, universe: nil, complement: nil,
|
61
|
+
unwind: nil, reverse: false
|
62
|
+
|
63
|
+
@registry = Types::Registry[registry]
|
64
|
+
@id = Types::NonNil[id]
|
65
|
+
@slug = Types::Symbol[slug] if slug
|
66
|
+
@type = Types[type]
|
67
|
+
@composite = Types[composite] if composite
|
68
|
+
@format = (Types::Proc | Types::String)[format] if format
|
69
|
+
@aliases = Types::Array[aliases]
|
70
|
+
@depends = Types::Array[depends]
|
71
|
+
@conflicts = Types::Array[conflicts]
|
72
|
+
@consumes = Types::Array[consumes]
|
73
|
+
@preproc = Types::Proc[preproc] if preproc
|
74
|
+
@min = Types::NonNegativeInteger[min || 0]
|
75
|
+
@max = Types::PositiveInteger.optional[max]
|
76
|
+
@shift = Types::Bool[shift]
|
77
|
+
@empty = Types::Bool[empty]
|
78
|
+
@default = Types::Nominal::Any[default]
|
79
|
+
@unifunc = Types::Proc[universe] if universe
|
80
|
+
@complement = Types::Proc[complement] if complement
|
81
|
+
@unwind = Types::Proc[unwind] if unwind
|
82
|
+
@reverse = Types::Bool[reverse]
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
# @!attribute [r] registry
|
87
|
+
# @return [Params::Registry] a backreference to the registry.
|
88
|
+
#
|
89
|
+
# @!attribute [r] id
|
90
|
+
# @return [Object] the canonical identifier for the parameter.
|
91
|
+
#
|
92
|
+
# @!attribute [r] slug
|
93
|
+
# @return [Symbol, nil] the primary nickname for the parameter, if
|
94
|
+
# different from the `id`.
|
95
|
+
#
|
96
|
+
# @!attribute [r] type
|
97
|
+
# @return [Dry::Types::Type] the type for individual parameter values.
|
98
|
+
#
|
99
|
+
# @!attribute [r] composite
|
100
|
+
# @return [Dry::Types::Type, nil] the type for composite values.
|
101
|
+
#
|
102
|
+
# @!attribute [r] aliases
|
103
|
+
# @return [Array<Symbol>] any aliases for this parameter.
|
104
|
+
#
|
105
|
+
# @!attribute [r] preproc
|
106
|
+
# @return [Proc] a procedure to run over `consume`d parameters.
|
107
|
+
#
|
108
|
+
# @!attribute [r] min
|
109
|
+
# @return [Integer] minimum cardinality for the parameter's values.
|
110
|
+
#
|
111
|
+
# @!attribute [r] max
|
112
|
+
# @return [Integer, nil] maximum cardinality for the parameter's values.
|
113
|
+
#
|
114
|
+
# @!attribute [r] default
|
115
|
+
# @return [Object, nil] a default value for the parameter.
|
116
|
+
#
|
117
|
+
# @!attribute [r] unwind
|
118
|
+
# A function that will take a composite object
|
119
|
+
# and turn it into an array of strings for serialization.
|
120
|
+
# @return [Proc, nil]
|
121
|
+
|
122
|
+
attr_reader :registry, :id, :slug, :type, :composite, :aliases,
|
123
|
+
:preproc, :min, :max, :default, :unwind
|
124
|
+
|
125
|
+
# @!attribute [r] depends
|
126
|
+
# Any parameters this one depends on.
|
127
|
+
#
|
128
|
+
# @return [Array]
|
129
|
+
#
|
130
|
+
def depends
|
131
|
+
out = (@depends | (@preproc ? @consumes : [])).map do |t|
|
132
|
+
registry.templates.canonical t
|
133
|
+
end
|
134
|
+
|
135
|
+
raise Params::Registry::Error,
|
136
|
+
"Malformed dependency declaration on #{t.id}" if out.any?(&:nil?)
|
137
|
+
|
138
|
+
out
|
139
|
+
end
|
140
|
+
|
141
|
+
# @!attribute [r] conflicts
|
142
|
+
# Any parameters this one conflicts with.
|
143
|
+
#
|
144
|
+
# @return [Array]
|
145
|
+
#
|
146
|
+
def conflicts
|
147
|
+
out = (@conflicts | (@preproc ? @consumes : [])).map do |t|
|
148
|
+
registry.templates.canonical t
|
149
|
+
end
|
150
|
+
|
151
|
+
raise Params::Registry::Error,
|
152
|
+
"Malformed conflict declaration on #{t.id}" if out.any?(&:nil?)
|
153
|
+
|
154
|
+
out
|
155
|
+
end
|
156
|
+
|
157
|
+
# @!attribute [r] preproc?
|
158
|
+
# Whether there is a preprocessor function.
|
159
|
+
#
|
160
|
+
# @return [Boolean]
|
161
|
+
#
|
162
|
+
def preproc? ; !!@preproc ; end
|
163
|
+
|
164
|
+
# @!attribute [r] consumes
|
165
|
+
# Any parameters this one consumes (implies `depends` + `conflicts`).
|
166
|
+
#
|
167
|
+
# @return [Array]
|
168
|
+
#
|
169
|
+
def consumes
|
170
|
+
out = @consumes.map { |t| registry.templates.canonical t }
|
171
|
+
|
172
|
+
raise Params::Registry::Error,
|
173
|
+
"Malformed consumes declaration on #{t.id}" if out.any?(&:nil?)
|
174
|
+
|
175
|
+
out
|
176
|
+
end
|
177
|
+
|
178
|
+
# @!attribute [r] universe
|
179
|
+
# The universal composite object (e.g. set or range) from which
|
180
|
+
# valid values are drawn.
|
181
|
+
# @return [Object, nil]
|
182
|
+
def universe
|
183
|
+
refresh! unless @universe
|
184
|
+
@universe
|
185
|
+
end
|
186
|
+
|
187
|
+
# @!attribute [r] shift?
|
188
|
+
# Whether to shift values more than `max` cardinality off the front.
|
189
|
+
#
|
190
|
+
# @return [Boolean]
|
191
|
+
#
|
192
|
+
def shift? ; !!@shift; end
|
193
|
+
|
194
|
+
# @!attribute [r] empty?
|
195
|
+
# Whether to accept empty values.
|
196
|
+
#
|
197
|
+
# @return [Boolean]
|
198
|
+
#
|
199
|
+
def empty? ; !!@empty; end
|
200
|
+
|
201
|
+
# @!attribute [r] reverse?
|
202
|
+
# Whether to interpret composite values as reversed.
|
203
|
+
#
|
204
|
+
# @return [Boolean]
|
205
|
+
#
|
206
|
+
def reverse? ; !!@reverse; end
|
207
|
+
|
208
|
+
# @!attribute [r] complement?
|
209
|
+
# Whether this (composite) parameter can be complemented or inverted.
|
210
|
+
#
|
211
|
+
# @return [Boolean]
|
212
|
+
#
|
213
|
+
def complement? ; !!@complement; end
|
214
|
+
|
215
|
+
# Preprocess a parameter value against itself and/or `consume`d values.
|
216
|
+
#
|
217
|
+
# @param myself [Array] raw values for the parameter itself.
|
218
|
+
# @param others [Array] *processed* values for the consumed parameters.
|
219
|
+
#
|
220
|
+
# @return [Array] pseudo-raw, preprocessed values for the parameter.
|
221
|
+
#
|
222
|
+
def preproc myself, others
|
223
|
+
begin
|
224
|
+
# run preproc in the context of the template
|
225
|
+
out = instance_exec myself, others, &@preproc
|
226
|
+
out = [out] unless out.is_a? Array
|
227
|
+
rescue Dry::Types::CoercionError => e
|
228
|
+
# rethrow a better error
|
229
|
+
raise Params::Registry::Error.new(
|
230
|
+
"Preprocessor failed on #{template.id} with #{}",
|
231
|
+
context: self, value: e)
|
232
|
+
end
|
233
|
+
|
234
|
+
out
|
235
|
+
end
|
236
|
+
|
237
|
+
# Format an individual atomic value.
|
238
|
+
#
|
239
|
+
# @param scalar [Object] the scalar/atomic value.
|
240
|
+
#
|
241
|
+
# @return [String] serialized to a string.
|
242
|
+
#
|
243
|
+
def format scalar
|
244
|
+
return scalar.to_s unless @format
|
245
|
+
|
246
|
+
if @format.is_a? Proc
|
247
|
+
instance_exec scalar, &@format
|
248
|
+
else
|
249
|
+
@format.to_s % scalar
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# Return the complement of the composite value for the parameter.
|
254
|
+
#
|
255
|
+
# @param value [Object] the composite object to complement.
|
256
|
+
#
|
257
|
+
# @return [Object, nil] the complementary object, if a complement is defined.
|
258
|
+
#
|
259
|
+
def complement value
|
260
|
+
return unless @complement
|
261
|
+
begin
|
262
|
+
instance_exec value, &@complement
|
263
|
+
rescue e
|
264
|
+
raise Params::Registry::Error::Empirical.new(
|
265
|
+
"Complement function failed: #{e.message}",
|
266
|
+
context: self, value: value)
|
267
|
+
end if @complement
|
268
|
+
end
|
269
|
+
|
270
|
+
# Validate a list of individual parameter values and (if one is present)
|
271
|
+
# construct a `composite` value.
|
272
|
+
#
|
273
|
+
# @param values [Array] the values given for the parameter.
|
274
|
+
#
|
275
|
+
# @return [Object, Array] the processed value(s).
|
276
|
+
#
|
277
|
+
def process *values
|
278
|
+
out = []
|
279
|
+
|
280
|
+
values.each do |v|
|
281
|
+
# skip over nil/empty values unless we can be empty
|
282
|
+
if v.nil? or v.to_s.empty?
|
283
|
+
next unless empty?
|
284
|
+
v = nil
|
285
|
+
end
|
286
|
+
|
287
|
+
if v
|
288
|
+
begin
|
289
|
+
tmp = type[v] # this either crashes or it doesn't
|
290
|
+
v = tmp # in which case v is only assigned if successful
|
291
|
+
rescue Dry::Types::CoercionError => e
|
292
|
+
raise Params::Registry::Error::Syntax.new e.message,
|
293
|
+
context: self, value: v
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
out << v
|
298
|
+
end
|
299
|
+
|
300
|
+
# now we deal with cardinality
|
301
|
+
raise Params::Registry::Error::Cardinality.new(
|
302
|
+
"Need #{min} values and there are only #{out.length} values") if
|
303
|
+
out.length < min
|
304
|
+
|
305
|
+
# warn "hurr #{out.inspect}, #{max}"
|
306
|
+
|
307
|
+
if max
|
308
|
+
# return if it's supposed to be a scalar value
|
309
|
+
return out.first if max == 1
|
310
|
+
# cut the values to length from either the front or back
|
311
|
+
out.slice!((shift? ? -max : 0), max) if out.length > max
|
312
|
+
end
|
313
|
+
|
314
|
+
composite ? composite[out] : out
|
315
|
+
end
|
316
|
+
|
317
|
+
# Applies `unwind` to `value` to get an array, then `format` over
|
318
|
+
# each of the elements to get strings. If `scalar` is true, it
|
319
|
+
# will also return the flag from `unwind` indicating whether or
|
320
|
+
# not the `complement` parameter should be set.
|
321
|
+
#
|
322
|
+
# This method is called by {Params::Registry::Instance#to_s} and
|
323
|
+
# others to produce content which is amenable to serialization. As
|
324
|
+
# what happens there, the content of `rest` should be the values
|
325
|
+
# of the parameters specified in `depends`.
|
326
|
+
#
|
327
|
+
# @param value [Object, Array<Object>] The parameter value(s).
|
328
|
+
# @param rest [Array<Object>] The rest of the parameter values.
|
329
|
+
# @param with_complement_flag [false, true] Whether to return the
|
330
|
+
# `complement` flag in addition to the unwound values.
|
331
|
+
#
|
332
|
+
# @return [Array<String>, Array<(Array<String>, false)>,
|
333
|
+
# Array<(Array<String>, true)>, nil] the unwound value(s), plus
|
334
|
+
# optionally the `complement` flag, or otherwise `nil`.
|
335
|
+
#
|
336
|
+
def unprocess value, *rest, with_complement_flag: false
|
337
|
+
# take care of empty properly
|
338
|
+
if value.nil?
|
339
|
+
if empty?
|
340
|
+
return [''] if max == 1
|
341
|
+
return [] if max.nil? or max > 1
|
342
|
+
end
|
343
|
+
|
344
|
+
# i guess this is nil?
|
345
|
+
return
|
346
|
+
end
|
347
|
+
|
348
|
+
# complement flag
|
349
|
+
comp = false
|
350
|
+
begin
|
351
|
+
tmp, comp = instance_exec value, *rest, &@unwind
|
352
|
+
value = tmp
|
353
|
+
rescue Exception, e
|
354
|
+
raise Params::Registry::Error::Empirical.new(
|
355
|
+
"Cannot unprocess value #{value} for parameter #{id}: #{e.message}",
|
356
|
+
context: self, value: value)
|
357
|
+
end if @unwind
|
358
|
+
|
359
|
+
# ensure this thing is an array
|
360
|
+
value = [value] unless value.is_a? Array
|
361
|
+
|
362
|
+
# ensure the values are correctly formatted
|
363
|
+
value.map! { |v| v.nil? ? '' : self.format(v) }
|
364
|
+
|
365
|
+
# throw in the complement flag
|
366
|
+
return value, comp if with_complement_flag
|
367
|
+
|
368
|
+
value
|
369
|
+
end
|
370
|
+
|
371
|
+
# Refreshes stateful information like the universal set, if present.
|
372
|
+
#
|
373
|
+
# @return [void]
|
374
|
+
#
|
375
|
+
def refresh!
|
376
|
+
if @unifunc
|
377
|
+
# do we want to call or do we want to instance_exec?
|
378
|
+
univ = @unifunc.call
|
379
|
+
|
380
|
+
univ = @composite[univ] if @composite
|
381
|
+
|
382
|
+
@universe = univ
|
383
|
+
end
|
384
|
+
|
385
|
+
nil
|
386
|
+
end
|
387
|
+
|
388
|
+
# Return a suitable representation for debugging.
|
389
|
+
#
|
390
|
+
# @return [String] the object.
|
391
|
+
#
|
392
|
+
def inspect
|
393
|
+
c = self.class
|
394
|
+
i = id.inspect
|
395
|
+
t = '%s%s' % [type, composite ? ", #{composite}]" : '']
|
396
|
+
|
397
|
+
"#<#{c} #{i} (#{t})>"
|
398
|
+
end
|
399
|
+
|
400
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "version"
|
4
|
+
|
5
|
+
require 'dry-types' # let's try to use this and not hate it
|
6
|
+
require 'set' # for some reason Set is not in the kernel but Range is
|
7
|
+
require 'date' # includes DateTime
|
8
|
+
require 'time' # ruby has Time and DateTime to be confusing
|
9
|
+
require 'uri'
|
10
|
+
|
11
|
+
# All the type coercions used in {Params::Registry}.
|
12
|
+
module Params::Registry::Types
|
13
|
+
include Dry.Types(default: :coercible)
|
14
|
+
|
15
|
+
# Syntactic sugar for retrieving types in the library.
|
16
|
+
#
|
17
|
+
# @param const [#to_sym] The type (name).
|
18
|
+
#
|
19
|
+
# @return [Dry::Types::Type] The type instance.
|
20
|
+
#
|
21
|
+
def self.[] const
|
22
|
+
return const if const.is_a? Dry::Types::Type
|
23
|
+
begin
|
24
|
+
const_get const.to_s.to_sym
|
25
|
+
rescue NameError
|
26
|
+
raise ArgumentError, "No type named #{const}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Can be anything, as long as it isn't `nil`.
|
31
|
+
NonNil = Strict::Any.constrained not_eql: nil
|
32
|
+
|
33
|
+
# Gotta have a coercible boolean (which doesn't come stock for some reason)
|
34
|
+
Bool = Nominal::Bool.constructor do |x|
|
35
|
+
case x.to_s.strip
|
36
|
+
when /\A(1|true|on|yes)\Z/i then true
|
37
|
+
when /\A(0|false|off|no|)\Z/i then false
|
38
|
+
else
|
39
|
+
raise Dry::Types::CoercionError, "#{x} can't be coerced to true or false"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# For some reason there isn't a stock `Proc` type.
|
44
|
+
Proc = self.Instance(::Proc)
|
45
|
+
|
46
|
+
# @!group A bunch of integer types
|
47
|
+
|
48
|
+
# The problem with Kernel.Integer is that if a string representing
|
49
|
+
# a number begins with a zero it's treated as octal, so we have to
|
50
|
+
# compensate for that.
|
51
|
+
Base10Integer = Nominal::Integer.constructor do |i|
|
52
|
+
i.is_a?(::Numeric) ? i.to_i : ::Kernel.Integer(i.to_s, 10)
|
53
|
+
end
|
54
|
+
|
55
|
+
# `xsd:nonPositiveInteger`
|
56
|
+
NonPositiveInteger = Base10Integer.constrained lteq: 0
|
57
|
+
# `xsd:nonNegativeInteger`
|
58
|
+
NonNegativeInteger = Base10Integer.constrained gteq: 0
|
59
|
+
# `xsd:positiveInteger`
|
60
|
+
PositiveInteger = Base10Integer.constrained gt: 0
|
61
|
+
# `xsd:negativeInteger`
|
62
|
+
NegativeInteger = Base10Integer.constrained lt: 0
|
63
|
+
|
64
|
+
# @!group Stringy stuff, à la XSD plus some others
|
65
|
+
|
66
|
+
# This is `xsd:normalizedString`.
|
67
|
+
NormalizedString = Nominal::String.constructor do |s|
|
68
|
+
s.to_s.gsub(/[\t\r\n]/, ' ')
|
69
|
+
end
|
70
|
+
|
71
|
+
# This is `xsd:token`.
|
72
|
+
Token = NormalizedString.constructor { |s| s.tr_s(' ', ' ').strip }
|
73
|
+
|
74
|
+
# Coerce an `xsd:token` into a {::Symbol}.
|
75
|
+
Symbol = Token.constructor { |t| t.to_sym }
|
76
|
+
|
77
|
+
# Coerce an `xsd:token` into a symbol with all lower-case letters.
|
78
|
+
LCSymbol = Token.constructor { |t| t.downcase.to_sym }
|
79
|
+
|
80
|
+
# Do the same but with upper-case letters.
|
81
|
+
UCSymbol = Token.constructor { |t| t.upcase.to_sym }
|
82
|
+
|
83
|
+
# Create a symbol with all whitespace and underscores turned to hyphens.
|
84
|
+
HyphenSymbol = Token.constructor { |t| t.tr_s(' _', ?-).to_sym }
|
85
|
+
|
86
|
+
# Do the same but with all lower-case letters.
|
87
|
+
LCHyphenSymbol = HyphenSymbol.constructor { |s| s.to_s.downcase.to_sym }
|
88
|
+
|
89
|
+
# Do the same but with all upper-case letters.
|
90
|
+
UCHyphenSymbol = HyphenSymbol.constructor { |s| s.to_s.upcase.to_sym }
|
91
|
+
|
92
|
+
# Create a symbol with all whitespace and hyphens turned to underscores.
|
93
|
+
UnderscoreSymbol = Token.constructor { |t| t.tr_s(' -', ?_).to_sym }
|
94
|
+
|
95
|
+
# Do the same but with all lower-case letters.
|
96
|
+
LCUnderscoreSymbol = UnderscoreSymbol.constructor do |s|
|
97
|
+
s.to_s.downcase.to_sym
|
98
|
+
end
|
99
|
+
|
100
|
+
# Do the same but with all upper-case letters.
|
101
|
+
UCUnderscoreSymbol = UnderscoreSymbol.constructor do |s|
|
102
|
+
s.to_s.upcase.to_sym
|
103
|
+
end
|
104
|
+
|
105
|
+
# @!group Dates & Times
|
106
|
+
|
107
|
+
# Ye olde {::Date}
|
108
|
+
Date = self.Constructor(::Date) do |x|
|
109
|
+
case x
|
110
|
+
when ::Array then ::Date.new(*x.take(3))
|
111
|
+
else ::Date.parse x
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# And {::DateTime}
|
116
|
+
DateTime = self.Constructor(::DateTime) do |x|
|
117
|
+
::DateTime.parse x
|
118
|
+
end
|
119
|
+
|
120
|
+
# Aaand {::Time}
|
121
|
+
Time = self.Constructor(::Time) do |x|
|
122
|
+
case x
|
123
|
+
when ::Array then ::Time.new(*x)
|
124
|
+
when (Base10Integer[x] rescue nil) then ::Time.at(Base10Integer[x])
|
125
|
+
else ::Time.parse x
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# @!group Composite types not already defined
|
130
|
+
|
131
|
+
# {::Set}
|
132
|
+
Set = self.Constructor(::Set) { |x| ::Set[*x] }
|
133
|
+
|
134
|
+
# {::Range}
|
135
|
+
Range = self.Constructor(::Range) { |x| ::Range.new(*x.take(2)) }
|
136
|
+
|
137
|
+
# The registry itself
|
138
|
+
Registry = self.Instance(::Params::Registry)
|
139
|
+
|
140
|
+
# Templates
|
141
|
+
|
142
|
+
TemplateSpec = Hash.map(Symbol, Strict::Any)
|
143
|
+
|
144
|
+
TemplateMap = Hash|Hash.map(NonNil, TemplateSpec)
|
145
|
+
|
146
|
+
# Groups
|
147
|
+
GroupMap = Hash|Hash.map(NonNil, Array|TemplateMap)
|
148
|
+
|
149
|
+
Input = self.Constructor(::Hash) do |input|
|
150
|
+
input = input.query.to_s if input.is_a? ::URI
|
151
|
+
input = ::URI.decode_www_form input if input.is_a? ::String
|
152
|
+
|
153
|
+
case input
|
154
|
+
when ::Hash then Hash.map(Symbol, Array.of(String))[input]
|
155
|
+
when ::Array
|
156
|
+
input.reduce({}) do |out, pair|
|
157
|
+
k, *v = Strict::Array.constrained(min_size: 2)[pair]
|
158
|
+
(out[k.to_sym] ||= []).push(*v)
|
159
|
+
out
|
160
|
+
end
|
161
|
+
else
|
162
|
+
raise Dry::Types::CoercionError, "not sure what to do with #{input}"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# @!endgroup
|
167
|
+
end
|