params-registry 0.1.12 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/params/registry/error.rb +14 -6
- data/lib/params/registry/instance.rb +121 -48
- data/lib/params/registry/template.rb +241 -87
- data/lib/params/registry/types.rb +61 -4
- data/lib/params/registry/version.rb +1 -1
- data/lib/params/registry.rb +14 -7
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ac097d76612371d0ccf0cd787b7aa3fa39b1b0392dd65ee09fb9188488785c67
|
4
|
+
data.tar.gz: 78d0cfe84914872dbc96852600af044b13dff6883cdf475810a411ce8c1d4b31
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 54afd98474e0c2570a3aa40b6149395dae855a9c99e4b7da0791739760aaa96f0bf1c11694290c5a0f975e396deb218174f2926d7af5ac4ea78028b0ad5da162
|
7
|
+
data.tar.gz: 2c245cbd712bf4bad769ef3b9f5e11f4926597d8ca9d557540cf57b9e441868c6d5130f37c974235660fb1ecb12bf82ac0c58fbb564e29fd5f464c8b38e35d20
|
@@ -6,25 +6,33 @@ require_relative 'types'
|
|
6
6
|
# We assume all errors are argument errors
|
7
7
|
class Params::Registry::Error < ArgumentError
|
8
8
|
|
9
|
-
def initialize message, context: nil, value: nil
|
10
|
-
@context
|
11
|
-
@value
|
9
|
+
def initialize message, context: nil, value: nil, original: nil
|
10
|
+
@context = context
|
11
|
+
@value = value
|
12
|
+
@original = original
|
12
13
|
super message
|
13
14
|
end
|
14
15
|
|
16
|
+
class Internal < self
|
17
|
+
end
|
18
|
+
|
15
19
|
# Errors for when nothing can be done with the lexical value of the input
|
16
20
|
class Syntax < self
|
17
21
|
end
|
18
22
|
|
19
|
-
# Errors for when the syntax checks out but
|
20
|
-
# conform empirically
|
23
|
+
# Errors for when the syntax checks out but one or more values don't
|
24
|
+
# conform empirically.
|
21
25
|
class Empirical < self
|
22
26
|
end
|
23
27
|
|
28
|
+
# An error for when there are specifically not enough elements (too
|
29
|
+
# many can always be pruned).
|
24
30
|
class Cardinality < Empirical
|
25
31
|
end
|
26
32
|
|
27
|
-
#
|
33
|
+
# This is less an error and more a way to signal to the caller that
|
34
|
+
# the parameter set will serialize differently from how it was
|
35
|
+
# initialized.
|
28
36
|
class Correction < Empirical
|
29
37
|
def initialize message, context: nil, value: nil, nearest: nil
|
30
38
|
@nearest = nearest
|
@@ -14,26 +14,50 @@ class Params::Registry::Instance
|
|
14
14
|
# i wish it was smart and would just resolve relative class names
|
15
15
|
Types = Params::Registry::Types
|
16
16
|
|
17
|
+
# This method is to process a single parameter _within the context of the
|
18
|
+
# entire parameter set_.
|
19
|
+
#
|
17
20
|
# This is the epitome of an internal method. It has weird
|
18
21
|
# parameters, modifies state, and returns a value that is useless
|
19
22
|
# for anything but subsequent internal processing.
|
20
|
-
def process_one template, values,
|
23
|
+
def process_one template, values,
|
24
|
+
defaults: true, complement: false, force: false
|
25
|
+
|
26
|
+
# unconditionally coerce to array unless it already is one
|
27
|
+
values = values.nil? ? [] : values.is_a?(Array) ? values : [values]
|
28
|
+
|
29
|
+
# warn [:process_one, template.slug || template.id, values].inspect
|
30
|
+
|
31
|
+
# set up the set of elements to be deleted
|
21
32
|
del = Set[]
|
22
33
|
|
23
|
-
|
24
|
-
|
25
|
-
|
34
|
+
if template.composite? and template.composite.try(values.first).success?
|
35
|
+
# do not run the preprocessor
|
36
|
+
values = values.first
|
37
|
+
elsif template.preproc? and template.consumes.all? { |k| @content.key? k }
|
38
|
+
# we prefer the slugs to make it easier on people
|
39
|
+
others = @content.slice(*template.consumes).transform_keys do |k|
|
40
|
+
t = registry.templates[k]
|
41
|
+
t.slug || k
|
42
|
+
end
|
26
43
|
|
27
|
-
#
|
44
|
+
# run the preproc function
|
28
45
|
values = template.preproc values, others
|
46
|
+
|
47
|
+
# add these to the instance parameters to be deleted
|
29
48
|
del += template.consumes
|
30
49
|
end
|
31
50
|
|
32
51
|
# if this actually goes here then there's a bug in the perl one
|
33
|
-
|
52
|
+
if values.is_a? Array and values.empty? and not force
|
53
|
+
# warn "lol wtf #{template.default}"
|
54
|
+
@content[template.id] = template.default.dup if
|
55
|
+
defaults and not template.default.nil?
|
56
|
+
return del
|
57
|
+
end
|
34
58
|
|
35
59
|
# now we use the template to process it (note this raises)
|
36
|
-
tmp = template.process
|
60
|
+
tmp = template.process values
|
37
61
|
@content[template.id] = tmp if !tmp.nil? or template.empty?
|
38
62
|
|
39
63
|
# now we test for conflicts
|
@@ -52,17 +76,26 @@ class Params::Registry::Instance
|
|
52
76
|
del # the keys slated for deletion
|
53
77
|
end
|
54
78
|
|
79
|
+
def encode_value value
|
80
|
+
# URI::encode_www_form_component sucks
|
81
|
+
value = value.to_s.b
|
82
|
+
# the only thing we really need to encode is [&=%#] and non-ascii
|
83
|
+
# because in a query string all other uri chars are legal
|
84
|
+
value.gsub(/[\x0-\x20\x7f-\xff&=%#]/n) { |s| '%%%02X' % s.ord }
|
85
|
+
end
|
86
|
+
|
55
87
|
public
|
56
88
|
|
57
|
-
attr_reader :registry
|
89
|
+
attr_reader :registry, :extra
|
58
90
|
|
59
|
-
#
|
91
|
+
# Initialize a parameter instance. Any `params` will be passed to #process.
|
60
92
|
#
|
61
|
-
# @param registry [Params::Registry] the registry
|
62
|
-
# @param
|
63
|
-
# resembles the output of `URI.decode_www_form`.
|
93
|
+
# @param registry [Params::Registry, Params::Registry::Group] the registry
|
94
|
+
# @param params [String, Hash{Symbol => Array}] something that
|
95
|
+
# resembles either the input or the output of `URI.decode_www_form`.
|
64
96
|
#
|
65
|
-
def initialize registry,
|
97
|
+
def initialize registry, params: nil, defaults: false, force: false
|
98
|
+
# deal with registry/group stuff
|
66
99
|
if registry.is_a? Params::Registry::Group
|
67
100
|
@group = registry.id
|
68
101
|
@registry = registry = registry.registry
|
@@ -71,13 +104,32 @@ class Params::Registry::Instance
|
|
71
104
|
@registry = registry
|
72
105
|
end
|
73
106
|
|
107
|
+
# set up members
|
74
108
|
@content = {}
|
75
109
|
@extra = {}
|
76
110
|
|
111
|
+
process params, defaults: defaults, force: force if params
|
112
|
+
end
|
113
|
+
|
114
|
+
# Process a set of parameters of varying degrees of parsed-ness, up
|
115
|
+
# to and including a raw query string.
|
116
|
+
#
|
117
|
+
# @param params [String, Hash{Symbol => Array}] something that
|
118
|
+
# resembles either the input or the output of `URI.decode_www_form`.
|
119
|
+
# @param defaults [true, false] whether to include defaults in the result
|
120
|
+
# @param force [false, true] force strict cardinality checking
|
121
|
+
#
|
122
|
+
# @return [self] for daisy-chaining
|
123
|
+
#
|
124
|
+
def process params, defaults: true, force: false
|
125
|
+
|
77
126
|
# warn "wtf lol #{@registry[@group].inspect}"
|
78
127
|
|
79
|
-
#
|
80
|
-
|
128
|
+
# warn [:before, params].inspect
|
129
|
+
|
130
|
+
# make sure we get a struct-like object with canonical keys but
|
131
|
+
# don't touch the values yet
|
132
|
+
params = Types::Input[params].reduce({}) do |hash, pair|
|
81
133
|
key, value = pair
|
82
134
|
# warn "kv: #{key.inspect} => #{value.inspect}"
|
83
135
|
if t = @registry[@group][key]
|
@@ -92,15 +144,15 @@ class Params::Registry::Instance
|
|
92
144
|
end
|
93
145
|
|
94
146
|
errors = {} # collect errors so we only raise once at the end
|
95
|
-
del = Set[] # mark for deletion
|
147
|
+
del = Set[] # mark these keys for deletion
|
96
148
|
|
97
149
|
# grab the complements now
|
98
150
|
complements = @content[@registry.complement.id] =
|
99
|
-
@registry.complement.process(
|
151
|
+
@registry.complement.process(params.fetch(@registry.complement.id, []))
|
100
152
|
|
101
153
|
# warn registry.templates.ranked.inspect
|
102
154
|
|
103
|
-
# warn
|
155
|
+
# warn [:process, params].inspect
|
104
156
|
|
105
157
|
# now we get the ranked templates and pass them through
|
106
158
|
@registry[@group].ranked.each do |templates|
|
@@ -110,12 +162,13 @@ class Params::Registry::Instance
|
|
110
162
|
# warn t.id
|
111
163
|
|
112
164
|
# obtain the raw values or an empty array instead
|
113
|
-
raw =
|
165
|
+
raw = params.fetch t.id, []
|
114
166
|
|
115
167
|
c = complements.include? t.id
|
116
168
|
|
117
169
|
begin
|
118
|
-
del += process_one t, raw,
|
170
|
+
del += process_one t, raw,
|
171
|
+
defaults: defaults, force: force, complement: c
|
119
172
|
rescue Params::Registry::Error => e
|
120
173
|
errors[t.id] = e
|
121
174
|
end
|
@@ -130,6 +183,9 @@ class Params::Registry::Instance
|
|
130
183
|
|
131
184
|
# delete the unwanted parameters
|
132
185
|
del.each { |d| @content.delete d }
|
186
|
+
|
187
|
+
# this is a daisy chainer
|
188
|
+
self
|
133
189
|
end
|
134
190
|
|
135
191
|
# Retrieve the processed value for a parameter.
|
@@ -159,20 +215,34 @@ class Params::Registry::Instance
|
|
159
215
|
#
|
160
216
|
def []= param, value
|
161
217
|
unless template = registry.templates[param]
|
162
|
-
|
218
|
+
# XXX THIS IS POTENTIALLY DUMB
|
219
|
+
value = value.respond_to?(:to_a) ? value : [value]
|
163
220
|
return @extras[param] = value
|
164
221
|
end
|
165
222
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
# this modifies @content and may raise
|
170
|
-
del = process_one template, value, force: true, complement: c
|
223
|
+
@content[template.id] = template.process value
|
224
|
+
end
|
171
225
|
|
172
|
-
|
226
|
+
# Bulk-assign instance content.
|
227
|
+
#
|
228
|
+
# @param struct [Hash]
|
229
|
+
#
|
230
|
+
def content= struct
|
231
|
+
# just use the member assign we already have
|
232
|
+
struct.each { |k, v| self[k] = v }
|
233
|
+
end
|
173
234
|
|
174
|
-
|
175
|
-
|
235
|
+
# Return a URI with the query set to the string value of this instance.
|
236
|
+
#
|
237
|
+
# @param uri [URI, #query=] the URI you want to assign
|
238
|
+
# @param defaults [false, true] whether to include defaults
|
239
|
+
#
|
240
|
+
# @return [URI, #query=] the URI with the new query string
|
241
|
+
#
|
242
|
+
def make_uri uri, defaults: false
|
243
|
+
uri = uri.dup
|
244
|
+
uri.query = to_s defaults: defaults
|
245
|
+
uri
|
176
246
|
end
|
177
247
|
|
178
248
|
# Taxidermy this object as an ordinary hash.
|
@@ -199,42 +269,45 @@ class Params::Registry::Instance
|
|
199
269
|
out
|
200
270
|
end
|
201
271
|
|
202
|
-
|
203
|
-
|
272
|
+
# Create a shallow copy of the parameter instance.
|
273
|
+
#
|
274
|
+
# @return [Params::Registry::Instance] the copy
|
275
|
+
#
|
276
|
+
def dup
|
277
|
+
out = self.class.new @registry[@group]
|
278
|
+
out.content = @content.dup
|
279
|
+
out
|
204
280
|
end
|
205
281
|
|
206
|
-
# # Retrieve an {Params::Registry::Instance} that isolates the
|
207
|
-
# # intersection of one or more groups
|
208
|
-
# #
|
209
|
-
# # @param group [Object] the group identifier.
|
210
|
-
# # @param extra [false, true] whether to include any "extra" unparsed
|
211
|
-
# # parameters.
|
212
|
-
# #
|
213
|
-
# # @return [Params::Registry::Instance] an instance containing just
|
214
|
-
# # the group(s) identified.
|
215
|
-
# #
|
216
|
-
# def group *group, extra: false
|
217
|
-
# end
|
218
|
-
|
219
282
|
# Serialize the instance back to a {::URI} query string.
|
220
283
|
#
|
221
284
|
# @return [String] the instance serialized as a URI query string.
|
222
285
|
#
|
223
|
-
def to_s
|
286
|
+
def to_s defaults: false, extra: false
|
224
287
|
ts = registry.templates
|
225
288
|
sequence = ts.keys & @content.keys
|
226
289
|
complements = Set[]
|
227
290
|
sequence.map do |k|
|
228
291
|
template = ts[k]
|
229
|
-
deps = @content.
|
230
|
-
v, c = template.unprocess @content[k],
|
292
|
+
deps = @content.slice(*(template.depends - template.consumes))
|
293
|
+
v, c = template.unprocess @content[k], deps, try_complement: true
|
231
294
|
complements << k if c
|
232
295
|
|
233
296
|
# warn @content[k], v.inspect
|
234
297
|
|
235
298
|
next if v.empty?
|
236
299
|
|
237
|
-
v.map
|
300
|
+
v.map do |v|
|
301
|
+
"#{template.slug || encode_value(k)}=#{encode_value v}"
|
302
|
+
end.join ?&
|
238
303
|
end.compact.join ?&
|
239
304
|
end
|
305
|
+
|
306
|
+
# Return a string representation of the object suitable for debugging.
|
307
|
+
#
|
308
|
+
# @return [String] said string representation
|
309
|
+
#
|
310
|
+
def inspect
|
311
|
+
"<#{self.class} content: #{@content.inspect}, extra: #{@extra.inspect}>"
|
312
|
+
end
|
240
313
|
end
|
@@ -3,13 +3,101 @@
|
|
3
3
|
require_relative 'types'
|
4
4
|
require_relative 'error'
|
5
5
|
|
6
|
-
# This class manages an individual parameter template.
|
6
|
+
# This class manages an individual parameter template. It encapsulates
|
7
|
+
# all the information and operations needed to validate and coerce
|
8
|
+
# individual parameter values, as well as for serializing them back
|
9
|
+
# into a string, and for doing so with bit-for-bit consistency.
|
10
|
+
#
|
11
|
+
# A parameter template can have a human-readable {::Symbol} as a
|
12
|
+
# `slug`, which is distinct from its canonical identifier (`id`),
|
13
|
+
# which can be any object, although that must be unique for the entire
|
14
|
+
# registry (while a slug only needs to be unique within its enclosing
|
15
|
+
# group). It can also have any number of `aliases`.
|
16
|
+
#
|
17
|
+
# A template can manage a simple type like a string or number, or a
|
18
|
+
# composite type like an array, tuple (implemented as a fixed-length
|
19
|
+
# array), set, or range containing appropriate simple types. The
|
20
|
+
# current (provisional) way to specify the types for the template are
|
21
|
+
# the `type` and `composite` initialization parameters, where if the
|
22
|
+
# latter is present, the former will be treated as its member type.
|
23
|
+
#
|
24
|
+
# > If certain forays into open-source diplomacy go well, these can be
|
25
|
+
# > consolidated into a single type declaration.
|
26
|
+
#
|
27
|
+
# A parameter may depend on (`depends`) or conflict (`conflicts`) with
|
28
|
+
# other parameters, or even consume (`consumes`) them as input. The
|
29
|
+
# cardinality of a parameter is controlled by `min` and `max`, which
|
30
|
+
# default to zero and unbounded, respectively. To require a parameter,
|
31
|
+
# set `min` to an integer greater than zero, and to enforce a single
|
32
|
+
# scalar value, set `max` to 1. (Setting `min` greater than `max` will
|
33
|
+
# raise an error.) To control whether a value of `nil` or the empty
|
34
|
+
# string is dropped, kept (as the empty string) or kept as `nil`, set
|
35
|
+
# the `empty` parameter.
|
36
|
+
#
|
37
|
+
# When `max` is greater than 1, the template automatically coerces any
|
38
|
+
# simple value into an array containing that value. (And when `max` is
|
39
|
+
# equal to 1, an array will be replaced with a single value.) Passing
|
40
|
+
# an array into #process with fewer than `min` values (or a single
|
41
|
+
# value when `min` is greater than 1) will produce an error. Whether
|
42
|
+
# the first N values (up to `max`) or the _last_ N values are taken
|
43
|
+
# from the input, is controlled by the `shift` parameter.
|
44
|
+
#
|
45
|
+
# Composite values begin life as arrays of simple values. During
|
46
|
+
# processing, the individual values are coerced from what are assumed
|
47
|
+
# to be strings, and then the arrays themselves are coerced into the
|
48
|
+
# composite types. Serialization is the same thing in reverse, using a
|
49
|
+
# function passed into `unwind` (which otherwise defaults to `to_a`)
|
50
|
+
# to turn the composite type back into an array, before the individual
|
51
|
+
# values being turned into strings by way of the value passed into
|
52
|
+
# `format`, which can either be a standard format string or a
|
53
|
+
# {::Proc}. The `unwind` function is also expected to sort the
|
54
|
+
# array. There is also a `reverse` flag for when it makes sense to
|
55
|
+
#
|
56
|
+
# The transformation process, from array of strings to composite
|
57
|
+
# object and back again, has a few more points of intervention. There
|
58
|
+
# is an optional `preproc` function, which is run when the
|
59
|
+
# {Params::Registry::Instance} is processed, after the
|
60
|
+
# individual values are coerced and before the composite coercion is
|
61
|
+
# applied, and a `contextualize` function, which is run after `unwind`
|
62
|
+
# but before `format`. Both of these functions make it possible to use
|
63
|
+
# information from the parameter's dependencies to manipulate its
|
64
|
+
# values based on its context within a live
|
65
|
+
# {Params::Registry::Instance}.
|
66
|
+
#
|
67
|
+
# Certain composite types, such as sets and ranges, have a coherent
|
68
|
+
# concept of a `universe`, which is implemented here as a function
|
69
|
+
# that generates a compatible object. This is useful for when the
|
70
|
+
# serialized representation of a parameter can be large. For instance,
|
71
|
+
# if a set's universe has 100 elements and we want to represent the
|
72
|
+
# subset with all the elements except for element 42, rather than
|
73
|
+
# serializing a 99-element query string, we complement the set and
|
74
|
+
# make a note to that effect (to be picked up by the
|
75
|
+
# {Params::Registry::Instance} serialization process and put in its
|
76
|
+
# `complement` parameter). The function passed into `complement` will
|
77
|
+
# be run as an instance method, which has access to `universe`. Other
|
78
|
+
# remarks about these functions:
|
79
|
+
#
|
80
|
+
# * The `preproc` and `contextualize` functions are expected to take
|
81
|
+
# the form `-> value, hash { expr }` and must return an array. The
|
82
|
+
# `hash` here is expected to contain at least the subset of
|
83
|
+
# parameters marked as dependencies (as is done in
|
84
|
+
# {Params::Registry::Instance}), keyed by `slug` or, failing that,
|
85
|
+
# `id`.
|
86
|
+
# * The `unwind` and `complement` functions both take the composite
|
87
|
+
# value as an argument (`-> value { expr }`). `unwind` is expected
|
88
|
+
# to return an array of elements, and `complement` should return a
|
89
|
+
# composite object of the same type.
|
90
|
+
# * The `universe` function takes no arguments and is expected to
|
91
|
+
# return a composite object of the same type.
|
92
|
+
#
|
7
93
|
class Params::Registry::Template
|
8
94
|
|
9
95
|
private
|
10
96
|
|
11
97
|
# this is dumb
|
12
98
|
Types = Params::Registry::Types
|
99
|
+
# when in rome
|
100
|
+
E = Params::Registry::Error
|
13
101
|
|
14
102
|
# Post-initialization hook for subclasses, because the constructor
|
15
103
|
# is so hairy.
|
@@ -54,8 +142,11 @@ class Params::Registry::Template
|
|
54
142
|
# derivatives, a function that returns the universal set or range
|
55
143
|
# @param complement [Proc] For {::Set} or {::Range} composite types, a
|
56
144
|
# function that will return the complement of the set or range
|
57
|
-
# @param unwind [Proc] A function that takes
|
145
|
+
# @param unwind [Proc] A function that takes a composite type
|
58
146
|
# and turns it into an {::Array} of atomic values
|
147
|
+
# @param contextualize [Proc] A function that takes an unwound
|
148
|
+
# composite value and modifies it based on the other parameters it
|
149
|
+
# depends on
|
59
150
|
# @param reverse [false, true] For {::Range} composite types, a flag
|
60
151
|
# that indicates whether the values should be interpreted and/or
|
61
152
|
# serialized in reverse order. Also governs the serialization of
|
@@ -65,28 +156,32 @@ class Params::Registry::Template
|
|
65
156
|
composite: nil, format: nil, aliases: nil, depends: nil, conflicts: nil,
|
66
157
|
consumes: nil, preproc: nil, min: 0, max: nil, shift: false,
|
67
158
|
empty: false, default: nil, universe: nil, complement: nil,
|
68
|
-
unwind: nil, reverse: false
|
69
|
-
|
70
|
-
@registry
|
71
|
-
@id
|
72
|
-
@slug
|
73
|
-
@type
|
74
|
-
@composite
|
75
|
-
@format
|
76
|
-
@aliases
|
77
|
-
@depends
|
78
|
-
@conflicts
|
79
|
-
@consumes
|
80
|
-
@preproc
|
81
|
-
@min
|
82
|
-
@max
|
83
|
-
@shift
|
84
|
-
@empty
|
85
|
-
@default
|
86
|
-
@unifunc
|
87
|
-
@
|
88
|
-
@
|
89
|
-
@
|
159
|
+
unwind: nil, contextualize: nil, reverse: false
|
160
|
+
|
161
|
+
@registry = Types::Registry[registry]
|
162
|
+
@id = Types::NonNil[id]
|
163
|
+
@slug = Types::Symbol[slug] if slug
|
164
|
+
@type = Types[type]
|
165
|
+
@composite = Types[composite] if composite
|
166
|
+
@format = (Types::Proc | Types::String)[format] if format
|
167
|
+
@aliases = Types::Array[aliases]
|
168
|
+
@depends = Types::Array[depends]
|
169
|
+
@conflicts = Types::Array[conflicts]
|
170
|
+
@consumes = Types::Array[consumes]
|
171
|
+
@preproc = Types::Proc[preproc] if preproc
|
172
|
+
@min = Types::NonNegativeInteger[min || 0]
|
173
|
+
@max = Types::PositiveInteger.optional[max]
|
174
|
+
@shift = Types::Bool[shift]
|
175
|
+
@empty = Types::Bool[empty]
|
176
|
+
@default = Types::Nominal::Any[default]
|
177
|
+
@unifunc = Types::Proc[universe] if universe
|
178
|
+
@comfunc = Types::Proc[complement] if complement
|
179
|
+
@unwfunc = Types::Proc[unwind] if unwind
|
180
|
+
@confunc = Types::Proc[contextualize] if contextualize
|
181
|
+
@reverse = Types::Bool[reverse]
|
182
|
+
|
183
|
+
raise ArgumentError, "min (#{@min}) cannot be greater than max (#{@max})" if
|
184
|
+
@min and @max and @min > @max
|
90
185
|
|
91
186
|
# post-initialization hook
|
92
187
|
post_init
|
@@ -123,13 +218,37 @@ class Params::Registry::Template
|
|
123
218
|
# @!attribute [r] default
|
124
219
|
# @return [Object, nil] a default value for the parameter.
|
125
220
|
#
|
126
|
-
# @!attribute [r] unwind
|
127
|
-
# A function that will take a composite object
|
128
|
-
# and turn it into an array of strings for serialization.
|
129
|
-
# @return [Proc, nil]
|
130
|
-
|
131
221
|
attr_reader :registry, :id, :slug, :type, :composite, :aliases,
|
132
|
-
:preproc, :min, :max, :default
|
222
|
+
:preproc, :min, :max, :default
|
223
|
+
|
224
|
+
# Unwind a composite value into an array of simple values. This
|
225
|
+
# wraps the matching function passed into the constructor, or
|
226
|
+
# `#to_a` in lieu of one.
|
227
|
+
#
|
228
|
+
# @param value [Object] a composite value
|
229
|
+
#
|
230
|
+
# @raise
|
231
|
+
#
|
232
|
+
# @return [Array] the unwound composite
|
233
|
+
#
|
234
|
+
def unwind value
|
235
|
+
return unless composite?
|
236
|
+
|
237
|
+
func = @unwfunc || -> v { v.to_a }
|
238
|
+
|
239
|
+
begin
|
240
|
+
out = instance_exec value, &func
|
241
|
+
rescue Exception, e
|
242
|
+
raise E::Internal.new "Unwind on #{id} failed: #{e.message}",
|
243
|
+
value: value, original: e
|
244
|
+
end
|
245
|
+
|
246
|
+
raise E::Internal,
|
247
|
+
"Unwind on #{id} returned #{out.class}, not an Array" unless
|
248
|
+
out.is_a? Array
|
249
|
+
|
250
|
+
out
|
251
|
+
end
|
133
252
|
|
134
253
|
# @!attribute [r] depends
|
135
254
|
# Any parameters this one depends on.
|
@@ -141,8 +260,8 @@ class Params::Registry::Template
|
|
141
260
|
registry.templates.canonical t
|
142
261
|
end
|
143
262
|
|
144
|
-
raise
|
145
|
-
|
263
|
+
raise E::Internal, "Malformed dependency declaration on #{id}" if
|
264
|
+
out.any?(&:nil?)
|
146
265
|
|
147
266
|
out
|
148
267
|
end
|
@@ -157,12 +276,19 @@ class Params::Registry::Template
|
|
157
276
|
registry.templates.canonical t
|
158
277
|
end
|
159
278
|
|
160
|
-
raise
|
161
|
-
|
279
|
+
raise E::Internal, "Malformed conflict declaration on #{id}" if
|
280
|
+
out.any?(&:nil?)
|
162
281
|
|
163
282
|
out
|
164
283
|
end
|
165
284
|
|
285
|
+
# @!attribute [r] composite?
|
286
|
+
# Whether this parameter is composite.
|
287
|
+
#
|
288
|
+
# @return [Boolean]
|
289
|
+
#
|
290
|
+
def composite? ; !!@composite; end
|
291
|
+
|
166
292
|
# @!attribute [r] preproc?
|
167
293
|
# Whether there is a preprocessor function.
|
168
294
|
#
|
@@ -178,8 +304,8 @@ class Params::Registry::Template
|
|
178
304
|
def consumes
|
179
305
|
out = @consumes.map { |t| registry.templates.canonical t }
|
180
306
|
|
181
|
-
raise
|
182
|
-
"Malformed consumes declaration on #{
|
307
|
+
raise E::Internal,
|
308
|
+
"Malformed consumes declaration on #{id}" if out.any?(&:nil?)
|
183
309
|
|
184
310
|
out
|
185
311
|
end
|
@@ -219,7 +345,14 @@ class Params::Registry::Template
|
|
219
345
|
#
|
220
346
|
# @return [Boolean]
|
221
347
|
#
|
222
|
-
def complement? ; !!@
|
348
|
+
def complement? ; !!@comfunc; end
|
349
|
+
|
350
|
+
# @!attribute [r] contextualize? Whether there is a contextualizing
|
351
|
+
# function present in the unprocessing stack.
|
352
|
+
#
|
353
|
+
# @return [Boolean]
|
354
|
+
#
|
355
|
+
def contextualize? ; !!@confunc; end
|
223
356
|
|
224
357
|
# @!attribute [r] blank?
|
225
358
|
# Returns true if the template has no configuration data to speak of.
|
@@ -230,7 +363,8 @@ class Params::Registry::Template
|
|
230
363
|
@format.nil? && @aliases.empty? && @depends.empty? &&
|
231
364
|
@conflicts.empty? && @consumes.empty? && @preproc.nil? &&
|
232
365
|
@min == 0 && @max.nil? && !@shift && !@empty && @default.nil? &&
|
233
|
-
@unifunc.nil? && @
|
366
|
+
@unifunc.nil? && @comfunc.nil? && @unwfunc.nil? && @confunc.nil? &&
|
367
|
+
!@reverse
|
234
368
|
end
|
235
369
|
|
236
370
|
# Preprocess a parameter value against itself and/or `consume`d values.
|
@@ -247,9 +381,9 @@ class Params::Registry::Template
|
|
247
381
|
out = [out] unless out.is_a? Array
|
248
382
|
rescue Dry::Types::CoercionError => e
|
249
383
|
# rethrow a better error
|
250
|
-
raise
|
251
|
-
"Preprocessor failed on #{
|
252
|
-
context: self,
|
384
|
+
raise E::Internal.new(
|
385
|
+
"Preprocessor failed on #{id} on value #{myself} with #{e.message}",
|
386
|
+
context: self, original: e)
|
253
387
|
end
|
254
388
|
|
255
389
|
out
|
@@ -267,38 +401,57 @@ class Params::Registry::Template
|
|
267
401
|
if @format.is_a? Proc
|
268
402
|
instance_exec scalar, &@format
|
269
403
|
else
|
270
|
-
@format.to_s % scalar
|
404
|
+
(@format || '%s').to_s % scalar
|
271
405
|
end
|
272
406
|
end
|
273
407
|
|
274
408
|
# Return the complement of the composite value for the parameter.
|
275
409
|
#
|
276
410
|
# @param value [Object] the composite object to complement.
|
411
|
+
# @param unwind [false, true] whether or not to also apply `unwind`
|
277
412
|
#
|
278
413
|
# @return [Object, nil] the complementary object, if a complement is defined.
|
279
414
|
#
|
280
|
-
def complement value
|
281
|
-
return unless @
|
415
|
+
def complement value, unwind: false
|
416
|
+
return unless @comfunc
|
417
|
+
|
282
418
|
begin
|
283
|
-
instance_exec value, &@
|
419
|
+
out = instance_exec value, &@comfunc
|
284
420
|
rescue e
|
285
|
-
raise
|
286
|
-
"Complement function failed: #{e.message}",
|
287
|
-
context: self, value: value)
|
288
|
-
end
|
421
|
+
raise E::Internal.new(
|
422
|
+
"Complement function on #{id} failed: #{e.message}",
|
423
|
+
context: self, value: value, original: e)
|
424
|
+
end
|
425
|
+
|
426
|
+
unwind ? self.unwind(out) : out
|
289
427
|
end
|
290
428
|
|
291
429
|
# Validate a list of individual parameter values and (if one is present)
|
292
430
|
# construct a `composite` value.
|
293
431
|
#
|
294
|
-
# @param
|
432
|
+
# @param value [Object] the values given for the parameter.
|
295
433
|
#
|
296
434
|
# @return [Object, Array] the processed value(s).
|
297
435
|
#
|
298
|
-
def process
|
436
|
+
def process value
|
437
|
+
# XXX what we _really_ want is `Types::Set.of` and
|
438
|
+
# `Types::Range.of` but who the hell knows how to actually make
|
439
|
+
# that happen, so what we're gonna do instead is test if the
|
440
|
+
# template is composite, then test the input against the composite
|
441
|
+
# type, then run `unwind` on it and test the individual members
|
442
|
+
|
443
|
+
# warn [(slug || id), value].inspect
|
444
|
+
|
445
|
+
# coerce and then unwind
|
446
|
+
value = unwind composite[value] if composite?
|
447
|
+
|
448
|
+
# otherwise coerce into an array
|
449
|
+
value = [value] unless value.is_a? Array
|
450
|
+
|
451
|
+
# copy to out
|
299
452
|
out = []
|
300
453
|
|
301
|
-
|
454
|
+
value.each do |v|
|
302
455
|
# skip over nil/empty values unless we can be empty
|
303
456
|
if v.nil? or v.to_s.empty?
|
304
457
|
next unless empty?
|
@@ -310,8 +463,7 @@ class Params::Registry::Template
|
|
310
463
|
tmp = type[v] # this either crashes or it doesn't
|
311
464
|
v = tmp # in which case v is only assigned if successful
|
312
465
|
rescue Dry::Types::CoercionError => e
|
313
|
-
raise
|
314
|
-
context: self, value: v
|
466
|
+
raise E::Syntax.new e.message, context: self, value: v, original: e
|
315
467
|
end
|
316
468
|
end
|
317
469
|
|
@@ -319,8 +471,8 @@ class Params::Registry::Template
|
|
319
471
|
end
|
320
472
|
|
321
473
|
# now we deal with cardinality
|
322
|
-
raise
|
323
|
-
"Need #{min} values and there are only #{out.length} values"
|
474
|
+
raise E::Cardinality,
|
475
|
+
"Need #{min} values and there are only #{out.length} values" if
|
324
476
|
out.length < min
|
325
477
|
|
326
478
|
# warn "hurr #{out.inspect}, #{max}"
|
@@ -335,6 +487,9 @@ class Params::Registry::Template
|
|
335
487
|
composite ? composite[out] : out
|
336
488
|
end
|
337
489
|
|
490
|
+
# This method takes a value which is assumed to be valid and
|
491
|
+
# transforms it into an array of strings suitable for serialization.
|
492
|
+
#
|
338
493
|
# Applies `unwind` to `value` to get an array, then `format` over
|
339
494
|
# each of the elements to get strings. If `scalar` is true, it
|
340
495
|
# will also return the flag from `unwind` indicating whether or
|
@@ -346,47 +501,46 @@ class Params::Registry::Template
|
|
346
501
|
# of the parameters specified in `depends`.
|
347
502
|
#
|
348
503
|
# @param value [Object, Array<Object>] The parameter value(s).
|
349
|
-
# @param
|
350
|
-
# @param
|
351
|
-
#
|
504
|
+
# @param dependencies [Hash] The rest of the parameter values.
|
505
|
+
# @param try_complement [false, true] Whether to attempt to
|
506
|
+
# complement a composite vlaue and return the `complement` flag in
|
507
|
+
# addition to the unwound values.
|
352
508
|
#
|
353
509
|
# @return [Array<String>, Array<(Array<String>, false)>,
|
354
510
|
# Array<(Array<String>, true)>, nil] the unwound value(s), plus
|
355
511
|
# optionally the `complement` flag, or otherwise `nil`.
|
356
512
|
#
|
357
|
-
def unprocess value,
|
358
|
-
#
|
359
|
-
|
360
|
-
if empty?
|
361
|
-
return [''] if max == 1
|
362
|
-
return [] if max.nil? or max > 1
|
363
|
-
end
|
513
|
+
def unprocess value, dependencies = {}, try_complement: false
|
514
|
+
# we begin assuming the value has not been complemented
|
515
|
+
comp = false
|
364
516
|
|
365
|
-
|
366
|
-
|
517
|
+
if composite?
|
518
|
+
# coerce just to be sure
|
519
|
+
value = composite[value]
|
520
|
+
# now unwind
|
521
|
+
value = unwind value
|
522
|
+
|
523
|
+
if try_complement and complement?
|
524
|
+
tmp = complement value, unwind: true
|
525
|
+
if tmp.length < value.length
|
526
|
+
value = tmp
|
527
|
+
comp = true
|
528
|
+
end
|
529
|
+
end
|
530
|
+
else
|
531
|
+
value = [value] unless value.is_a? Array
|
367
532
|
end
|
368
533
|
|
369
|
-
#
|
370
|
-
|
371
|
-
begin
|
372
|
-
tmp, comp = instance_exec value, *rest, &@unwind
|
373
|
-
value = tmp
|
374
|
-
rescue Exception, e
|
375
|
-
raise Params::Registry::Error::Empirical.new(
|
376
|
-
"Cannot unprocess value #{value} for parameter #{id}: #{e.message}",
|
377
|
-
context: self, value: value)
|
378
|
-
end if @unwind
|
379
|
-
|
380
|
-
# ensure this thing is an array
|
381
|
-
value = [value] unless value.is_a? Array
|
534
|
+
# now we contextualize
|
535
|
+
value = contextualize value if contextualize?
|
382
536
|
|
383
|
-
#
|
537
|
+
# now we maybe prune out blanks
|
538
|
+
value.compact! unless empty?
|
539
|
+
# any nil at this point is on purpose
|
384
540
|
value.map! { |v| v.nil? ? '' : self.format(v) }
|
385
541
|
|
386
|
-
#
|
387
|
-
|
388
|
-
|
389
|
-
value
|
542
|
+
# now we return the pair if we're trying to complement it
|
543
|
+
try_complement ? [value, comp] : value
|
390
544
|
end
|
391
545
|
|
392
546
|
# Refreshes stateful information like the universal set, if present.
|
@@ -398,7 +552,7 @@ class Params::Registry::Template
|
|
398
552
|
# do we want to call or do we want to instance_exec?
|
399
553
|
univ = @unifunc.call
|
400
554
|
|
401
|
-
univ =
|
555
|
+
univ = composite[univ] if composite?
|
402
556
|
|
403
557
|
@universe = univ
|
404
558
|
end
|
@@ -135,15 +135,66 @@ module Params::Registry::Types
|
|
135
135
|
|
136
136
|
# @!group Composite types not already defined
|
137
137
|
|
138
|
+
# XXX okay so once again dry-types has to be weird as hell. What we
|
139
|
+
# _want_ are `Set.of` and `Range.of` just like the built-in
|
140
|
+
# `Array.of`. What we have to _do_ to achieve this is god-knows-what.
|
141
|
+
#
|
142
|
+
class Container < ::Dry::Types::Nominal
|
143
|
+
|
144
|
+
class Constructor < ::Dry::Types::Array::Constructor
|
145
|
+
def constructor_type = Container::Constructor
|
146
|
+
end
|
147
|
+
|
148
|
+
class Member < ::Dry::Types::Array::Member
|
149
|
+
def constructor_type = Container::Constructor
|
150
|
+
end
|
151
|
+
|
152
|
+
def member_type = Container::Member
|
153
|
+
|
154
|
+
def constructor_type = Container::Constructor
|
155
|
+
|
156
|
+
def of(type)
|
157
|
+
member = case type
|
158
|
+
when ::String then ::Dry::Types[type]
|
159
|
+
else type
|
160
|
+
end
|
161
|
+
|
162
|
+
member_type.new(primitive, **options, member: member)
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
# class List < Array
|
168
|
+
# end
|
169
|
+
|
170
|
+
# XXX UGGGH CAN'T RETURN FROM THESE BECAUSE OF COURSE NOT UGGHGGHHG
|
171
|
+
|
138
172
|
List = self.Constructor(::Array) do |x|
|
139
|
-
x.
|
173
|
+
if x.is_a? ::Array
|
174
|
+
x
|
175
|
+
else
|
176
|
+
x.respond_to?(:to_a) ? x.to_a : [x]
|
177
|
+
end
|
140
178
|
end
|
141
179
|
|
142
180
|
# {::Set}
|
143
|
-
Set = self.Constructor(::Set)
|
181
|
+
Set = self.Constructor(::Set) do |x|
|
182
|
+
if x.is_a? ::Set
|
183
|
+
x
|
184
|
+
else
|
185
|
+
::Set[*x]
|
186
|
+
end
|
187
|
+
end
|
144
188
|
|
145
189
|
# {::Range}
|
146
|
-
Range = self.Constructor(::Range)
|
190
|
+
Range = self.Constructor(::Range) do |x|
|
191
|
+
# warn x.inspect
|
192
|
+
if x.is_a? ::Range
|
193
|
+
x
|
194
|
+
else
|
195
|
+
::Range.new(*x.take(2).sort)
|
196
|
+
end
|
197
|
+
end
|
147
198
|
|
148
199
|
# The registry itself
|
149
200
|
Registry = self.Instance(::Params::Registry)
|
@@ -157,13 +208,19 @@ module Params::Registry::Types
|
|
157
208
|
# Groups
|
158
209
|
GroupMap = Hash|Hash.map(NonNil, Array|TemplateMap)
|
159
210
|
|
211
|
+
Values = self.Constructor(::Object) do |a|
|
212
|
+
# still kind of torn on how to deal with this
|
213
|
+
a.is_a?(::Array) ? a : [a]
|
214
|
+
# a.respond_to?(:to_a) ? a : [a]
|
215
|
+
end
|
216
|
+
|
160
217
|
Input = self.Constructor(::Hash) do |input|
|
161
218
|
input = input.query.to_s if input.is_a? ::URI
|
162
219
|
input = '' if input.nil?
|
163
220
|
input = ::URI.decode_www_form input if input.is_a? ::String
|
164
221
|
|
165
222
|
case input
|
166
|
-
when ::Hash then Hash.map(Symbolish,
|
223
|
+
when ::Hash then Hash.map(Symbolish, Values)[input]
|
167
224
|
when ::Array
|
168
225
|
input.reduce({}) do |out, pair|
|
169
226
|
k, *v = Strict::Array.constrained(min_size: 2)[pair]
|
data/lib/params/registry.rb
CHANGED
@@ -6,6 +6,7 @@ require_relative 'registry/template'
|
|
6
6
|
require_relative 'registry/instance'
|
7
7
|
|
8
8
|
require 'uri'
|
9
|
+
require 'forwardable'
|
9
10
|
|
10
11
|
# {Params::Registry} is intended to contain an organization-wide
|
11
12
|
# registry of reusable named parameters. The initial purpose of such a
|
@@ -19,6 +20,10 @@ class Params::Registry
|
|
19
20
|
|
20
21
|
# A group is an identifiable sequence of parameters.
|
21
22
|
class Group
|
23
|
+
extend Forwardable
|
24
|
+
|
25
|
+
def_delegators :@templates, :select
|
26
|
+
|
22
27
|
# Create a new group.
|
23
28
|
#
|
24
29
|
# @param registry [Params::Registry] the registry
|
@@ -137,7 +142,7 @@ class Params::Registry
|
|
137
142
|
|
138
143
|
# Assign a new sequence of templates to the group.
|
139
144
|
#
|
140
|
-
# @param templates [Array, Hash]
|
145
|
+
# @param templates [Array, Hash] the set of templates
|
141
146
|
#
|
142
147
|
# @return [Array, Hash] whatever was passed
|
143
148
|
# in because Ruby ignores the output
|
@@ -242,8 +247,9 @@ class Params::Registry
|
|
242
247
|
#
|
243
248
|
# @return [Params::Registry::Instance] the instance.
|
244
249
|
#
|
245
|
-
def process params
|
246
|
-
registry.instance_class.new self,
|
250
|
+
def process params, defaults: false, force: false
|
251
|
+
registry.instance_class.new self,
|
252
|
+
params: params, defaults: defaults, force: force
|
247
253
|
end
|
248
254
|
|
249
255
|
end
|
@@ -269,6 +275,7 @@ class Params::Registry
|
|
269
275
|
# for the closures
|
270
276
|
ts = templates
|
271
277
|
|
278
|
+
|
272
279
|
# we always want these closures so we steamroll over whatever the
|
273
280
|
# user might have put in these slots
|
274
281
|
spec.merge!({
|
@@ -282,7 +289,7 @@ class Params::Registry
|
|
282
289
|
unwind: -> set {
|
283
290
|
# XXX do we want to sort this lexically or do we want it in
|
284
291
|
# the same order as the keys?
|
285
|
-
|
292
|
+
set.to_a.map { |t| t = ts[t]; (t.slug || t.id).to_s }.sort
|
286
293
|
}
|
287
294
|
})
|
288
295
|
|
@@ -400,8 +407,8 @@ class Params::Registry
|
|
400
407
|
#
|
401
408
|
# @return [Params::Registry::Instance] the instance.
|
402
409
|
#
|
403
|
-
def process params
|
404
|
-
instance_class.new self,
|
410
|
+
def process params, defaults: false, force: false
|
411
|
+
instance_class.new self, params: params, defaults: defaults, force: force
|
405
412
|
end
|
406
413
|
|
407
414
|
# Refresh any stateful elements of the templates.
|
@@ -409,7 +416,7 @@ class Params::Registry
|
|
409
416
|
# @return [self]
|
410
417
|
#
|
411
418
|
def refresh!
|
412
|
-
templates.each { |t| t.refresh! }
|
419
|
+
templates.templates.each { |t| t.refresh! }
|
413
420
|
|
414
421
|
self
|
415
422
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: params-registry
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dorian Taylor
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-02-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dry-types
|