params-registry 0.1.12 → 0.2.1
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 +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
|