params-registry 0.1.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/.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
|