params-registry 0.1.12 → 0.2.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ecda0bf7bd68a9dd7fd9b04cf9d5e3a2695aea9ac1d855f52dabff25f3992af3
4
- data.tar.gz: 0acfd9a1a99e2be5f61f85c0b8fad2bdde2e2db9b04060d3ead7d810a508827d
3
+ metadata.gz: b55a15071c752544d76d3256a835eb927507ff8685a90cc94cc00a9bf6fed373
4
+ data.tar.gz: c4e5cc3a55c98e39405fa5d61e88ccecdc279e5149d38a04469a6ab5db664e33
5
5
  SHA512:
6
- metadata.gz: 7f095e9e830b31e7c546cecde3dde1dc9112c4b65d82fa623c403fb5c0672cfeb1ebf0596582139c966b8cd5b23a9dbb23955c2d02100e56fd7e582b68006a36
7
- data.tar.gz: fc4a9ce7dea830a031553473d7088951b1f10a16772b561ac10259dea87669ac4ce20caff03490a27a4709ab6bf1ad0ca88a82a8e92d17b13655d55e0ab9fd24
6
+ metadata.gz: 356602be7b8437695e5439bafcb74c62d85a1e5205a436c2ab90e8ec19d1cdc0692fdd0b25dd16ac18f86861c96d7a501b4114a357469b301e17f6396becd834
7
+ data.tar.gz: b7f9c511c11070da981d7d85d4354c4f46c7d72a780aee8b2e4eace3ce34c5d52eef6866dce51add8bd32947924614c853f677cf602d7e34d7ec7f6f17df2d19
data/README.md CHANGED
@@ -18,9 +18,9 @@ _symbols_, which you have to _manage_, and this is a _problem_.
18
18
  table: named parameters that are exposed to the wild through
19
19
  mechanisms like URLs and APIs.
20
20
 
21
- ## So, query parameters, isn't that like, _super_ anal?
21
+ ## So, query parameters, isn't that like, _super_ uptight?
22
22
 
23
- So, I vacillated for _years_ before making [the _first_ version of
23
+ I vacillated for _years_ before making [the _first_ version of
24
24
  this module](https://metacpan.org/dist/Params-Registry) back in 2013.
25
25
  _Query_ parameters? I mean, who cares? Well, it turns out that if you
26
26
  want certain outcomes, this is the kind of software you need. _What_
@@ -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 = context
11
- @value = 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 the value doesn't
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
- # A correctable error
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, complement: false, force: false
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
- # run the preprocessor
24
- if template.preproc? and template.consumes.all? { |k| @content.key? k }
25
- others = @content.values_at(*template.consumes)
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
- # XXX maybe we should
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
- return del if values.empty? and not force
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(*values)
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
- # Make a new instance.
91
+ # Initialize a parameter instance. Any `params` will be passed to #process.
60
92
  #
61
- # @param registry [Params::Registry] the registry
62
- # @param struct [Hash{Symbol => Array<String>}] something that
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, struct, defaults: false, force: false
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
- # canonicalize the keys of the struct
80
- struct = Types::Input[struct].reduce({}) do |hash, pair|
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(*struct.fetch(@registry.complement.id, []))
151
+ @registry.complement.process(params.fetch(@registry.complement.id, []))
100
152
 
101
153
  # warn registry.templates.ranked.inspect
102
154
 
103
- # warn complements.class
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 = struct.fetch t.id, []
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, force: force, complement: c
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,39 @@ class Params::Registry::Instance
159
215
  #
160
216
  def []= param, value
161
217
  unless template = registry.templates[param]
162
- value = value.respond_to?(:to_a) ? value.to_a : value
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
- # XXX do something less dumb about this
167
- c = (@content[registry.complement.id] || Set[]).include? template.id
168
-
169
- # this modifies @content and may raise
170
- del = process_one template, value, force: true, complement: c
223
+ # XXX THIS MIGHT FUCK SHIT UP
224
+ if value.nil?
225
+ @content.delete template.id
226
+ else
227
+ @content[template.id] = template.process value
228
+ end
229
+ end
171
230
 
172
- del.each { |d| @content.delete d }
231
+ # Bulk-assign instance content.
232
+ #
233
+ # @param struct [Hash]
234
+ #
235
+ def content= struct
236
+ # just use the member assign we already have
237
+ struct.each { |k, v| self[k] = v }
238
+ end
173
239
 
174
- # return
175
- @content[template.id]
240
+ # Return a URI with the query set to the string value of this instance.
241
+ #
242
+ # @param uri [URI, #query=] the URI you want to assign
243
+ # @param defaults [false, true] whether to include defaults
244
+ #
245
+ # @return [URI, #query=] the URI with the new query string
246
+ #
247
+ def make_uri uri, defaults: false, extra: false
248
+ uri = uri.dup
249
+ uri.query = to_s defaults: defaults, extra: extra
250
+ uri
176
251
  end
177
252
 
178
253
  # Taxidermy this object as an ordinary hash.
@@ -199,42 +274,75 @@ class Params::Registry::Instance
199
274
  out
200
275
  end
201
276
 
202
- def inspect
203
- "<#{self.class} content: #{@content.inspect}, extra: #{@extra.inspect}>"
277
+ # Create a shallow copy of the parameter instance.
278
+ #
279
+ # @return [Params::Registry::Instance] the copy
280
+ #
281
+ def dup
282
+ out = self.class.new @registry[@group]
283
+ out.content = @content.dup
284
+ out
204
285
  end
205
286
 
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
287
  # Serialize the instance back to a {::URI} query string.
220
288
  #
289
+ # @params defaults [false, true, Object, Array] whether to serialize
290
+ # default values, or specific keys/slugs to include
291
+ # @params extra [false, true] whether to include parameters that
292
+ # were passed in that aren't in the registry
293
+ #
221
294
  # @return [String] the instance serialized as a URI query string.
222
295
  #
223
- def to_s
296
+ def to_s defaults: false, extra: false
224
297
  ts = registry.templates
298
+
299
+ # warn ts.inspect
300
+
301
+ # this should give us a list of keys that have defaults that we
302
+ # want to show up
303
+ defaults = case defaults
304
+ when nil, false then []
305
+ when true then ts.select { |k| ts[k].default? }.values.map(&:id)
306
+ when -> d { d.respond_to? :to_a } then defaults.to_a
307
+ else [defaults]
308
+ end.map { |d| ts[d]&.id }.compact
309
+
310
+ # the template keys should have the original order so we want to intersee
225
311
  sequence = ts.keys & @content.keys
226
312
  complements = Set[]
227
- sequence.map do |k|
313
+ out = sequence.map do |k|
228
314
  template = ts[k]
229
- deps = @content.values_at(*(template.depends - template.consumes))
230
- v, c = template.unprocess @content[k], *deps, with_complement_flag: true
315
+
316
+ # we want to skip the parameter if it's the same as
317
+ next if template.default? and @content[k] == template.default and
318
+ not defaults.include? k
319
+
320
+ # get the dependencies, convert to an array of strings, harvest
321
+ # complement flag (if present)
322
+ deps = @content.slice(*(template.depends - template.consumes))
323
+ v, c = template.unprocess @content[k], deps, try_complement: true
231
324
  complements << k if c
232
325
 
233
326
  # warn @content[k], v.inspect
234
327
 
235
328
  next if v.empty?
236
329
 
237
- v.map { |v| "#{template.slug || k}=#{v}" }.join ?&
238
- end.compact.join ?&
330
+ v.map do |v|
331
+ "#{template.slug || encode_value(k)}=#{encode_value v}"
332
+ end.join ?&
333
+ end.compact
334
+
335
+ # XXX TODO complement and extras i just don't feel like looking up how
336
+ # that's supposed to work rn but they would go here
337
+
338
+ out.join ?&
339
+ end
340
+
341
+ # Return a string representation of the object suitable for debugging.
342
+ #
343
+ # @return [String] said string representation
344
+ #
345
+ def inspect
346
+ "<#{self.class} content: #{@content.inspect}, extra: #{@extra.inspect}>"
239
347
  end
240
348
  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 the composite type
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 = Types::Registry[registry]
71
- @id = Types::NonNil[id]
72
- @slug = Types::Symbol[slug] if slug
73
- @type = Types[type]
74
- @composite = Types[composite] if composite
75
- @format = (Types::Proc | Types::String)[format] if format
76
- @aliases = Types::Array[aliases]
77
- @depends = Types::Array[depends]
78
- @conflicts = Types::Array[conflicts]
79
- @consumes = Types::Array[consumes]
80
- @preproc = Types::Proc[preproc] if preproc
81
- @min = Types::NonNegativeInteger[min || 0]
82
- @max = Types::PositiveInteger.optional[max]
83
- @shift = Types::Bool[shift]
84
- @empty = Types::Bool[empty]
85
- @default = Types::Nominal::Any[default]
86
- @unifunc = Types::Proc[universe] if universe
87
- @complement = Types::Proc[complement] if complement
88
- @unwind = Types::Proc[unwind] if unwind
89
- @reverse = Types::Bool[reverse]
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, :unwind
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 Params::Registry::Error,
145
- "Malformed dependency declaration on #{t.id}" if out.any?(&:nil?)
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,26 @@ class Params::Registry::Template
157
276
  registry.templates.canonical t
158
277
  end
159
278
 
160
- raise Params::Registry::Error,
161
- "Malformed conflict declaration on #{t.id}" if out.any?(&:nil?)
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
+
292
+ # @!attribute [r] default?
293
+ # Whether this parameter has a default value.
294
+ #
295
+ # @return [Boolean]
296
+ #
297
+ def default? ; !@default.nil?; end
298
+
166
299
  # @!attribute [r] preproc?
167
300
  # Whether there is a preprocessor function.
168
301
  #
@@ -178,8 +311,8 @@ class Params::Registry::Template
178
311
  def consumes
179
312
  out = @consumes.map { |t| registry.templates.canonical t }
180
313
 
181
- raise Params::Registry::Error,
182
- "Malformed consumes declaration on #{t.id}" if out.any?(&:nil?)
314
+ raise E::Internal,
315
+ "Malformed consumes declaration on #{id}" if out.any?(&:nil?)
183
316
 
184
317
  out
185
318
  end
@@ -219,7 +352,14 @@ class Params::Registry::Template
219
352
  #
220
353
  # @return [Boolean]
221
354
  #
222
- def complement? ; !!@complement; end
355
+ def complement? ; !!@comfunc; end
356
+
357
+ # @!attribute [r] contextualize? Whether there is a contextualizing
358
+ # function present in the unprocessing stack.
359
+ #
360
+ # @return [Boolean]
361
+ #
362
+ def contextualize? ; !!@confunc; end
223
363
 
224
364
  # @!attribute [r] blank?
225
365
  # Returns true if the template has no configuration data to speak of.
@@ -230,7 +370,8 @@ class Params::Registry::Template
230
370
  @format.nil? && @aliases.empty? && @depends.empty? &&
231
371
  @conflicts.empty? && @consumes.empty? && @preproc.nil? &&
232
372
  @min == 0 && @max.nil? && !@shift && !@empty && @default.nil? &&
233
- @unifunc.nil? && @complement.nil? && @unwind.nil? && !@reverse
373
+ @unifunc.nil? && @comfunc.nil? && @unwfunc.nil? && @confunc.nil? &&
374
+ !@reverse
234
375
  end
235
376
 
236
377
  # Preprocess a parameter value against itself and/or `consume`d values.
@@ -247,9 +388,9 @@ class Params::Registry::Template
247
388
  out = [out] unless out.is_a? Array
248
389
  rescue Dry::Types::CoercionError => e
249
390
  # rethrow a better error
250
- raise Params::Registry::Error.new(
251
- "Preprocessor failed on #{template.id} with #{}",
252
- context: self, value: e)
391
+ raise E::Internal.new(
392
+ "Preprocessor failed on #{id} on value #{myself} with #{e.message}",
393
+ context: self, original: e)
253
394
  end
254
395
 
255
396
  out
@@ -267,38 +408,57 @@ class Params::Registry::Template
267
408
  if @format.is_a? Proc
268
409
  instance_exec scalar, &@format
269
410
  else
270
- @format.to_s % scalar
411
+ (@format || '%s').to_s % scalar
271
412
  end
272
413
  end
273
414
 
274
415
  # Return the complement of the composite value for the parameter.
275
416
  #
276
417
  # @param value [Object] the composite object to complement.
418
+ # @param unwind [false, true] whether or not to also apply `unwind`
277
419
  #
278
420
  # @return [Object, nil] the complementary object, if a complement is defined.
279
421
  #
280
- def complement value
281
- return unless @complement
422
+ def complement value, unwind: false
423
+ return unless @comfunc
424
+
282
425
  begin
283
- instance_exec value, &@complement
426
+ out = instance_exec value, &@comfunc
284
427
  rescue e
285
- raise Params::Registry::Error::Empirical.new(
286
- "Complement function failed: #{e.message}",
287
- context: self, value: value)
288
- end if @complement
428
+ raise E::Internal.new(
429
+ "Complement function on #{id} failed: #{e.message}",
430
+ context: self, value: value, original: e)
431
+ end
432
+
433
+ unwind ? self.unwind(out) : out
289
434
  end
290
435
 
291
436
  # Validate a list of individual parameter values and (if one is present)
292
437
  # construct a `composite` value.
293
438
  #
294
- # @param values [Array] the values given for the parameter.
439
+ # @param value [Object] the values given for the parameter.
295
440
  #
296
441
  # @return [Object, Array] the processed value(s).
297
442
  #
298
- def process *values
443
+ def process value
444
+ # XXX what we _really_ want is `Types::Set.of` and
445
+ # `Types::Range.of` but who the hell knows how to actually make
446
+ # that happen, so what we're gonna do instead is test if the
447
+ # template is composite, then test the input against the composite
448
+ # type, then run `unwind` on it and test the individual members
449
+
450
+ # warn [(slug || id), value].inspect
451
+
452
+ # coerce and then unwind
453
+ value = unwind composite[value] if composite?
454
+
455
+ # otherwise coerce into an array
456
+ value = [value] unless value.is_a? Array
457
+
458
+ # copy to out
299
459
  out = []
300
460
 
301
- values.each do |v|
461
+ value.each do |v|
302
462
  # skip over nil/empty values unless we can be empty
303
463
  if v.nil? or v.to_s.empty?
304
464
  next unless empty?
@@ -310,8 +470,7 @@ class Params::Registry::Template
310
470
  tmp = type[v] # this either crashes or it doesn't
311
471
  v = tmp # in which case v is only assigned if successful
312
472
  rescue Dry::Types::CoercionError => e
313
- raise Params::Registry::Error::Syntax.new e.message,
314
- context: self, value: v
473
+ raise E::Syntax.new e.message, context: self, value: v, original: e
315
474
  end
316
475
  end
317
476
 
@@ -319,8 +478,8 @@ class Params::Registry::Template
319
478
  end
320
479
 
321
480
  # now we deal with cardinality
322
- raise Params::Registry::Error::Cardinality.new(
323
- "Need #{min} values and there are only #{out.length} values") if
481
+ raise E::Cardinality,
482
+ "Need #{min} values and there are only #{out.length} values" if
324
483
  out.length < min
325
484
 
326
485
  # warn "hurr #{out.inspect}, #{max}"
@@ -335,6 +494,9 @@ class Params::Registry::Template
335
494
  composite ? composite[out] : out
336
495
  end
337
496
 
497
+ # This method takes a value which is assumed to be valid and
498
+ # transforms it into an array of strings suitable for serialization.
499
+ #
338
500
  # Applies `unwind` to `value` to get an array, then `format` over
339
501
  # each of the elements to get strings. If `scalar` is true, it
340
502
  # will also return the flag from `unwind` indicating whether or
@@ -346,47 +508,46 @@ class Params::Registry::Template
346
508
  # of the parameters specified in `depends`.
347
509
  #
348
510
  # @param value [Object, Array<Object>] The parameter value(s).
349
- # @param rest [Array<Object>] The rest of the parameter values.
350
- # @param with_complement_flag [false, true] Whether to return the
351
- # `complement` flag in addition to the unwound values.
511
+ # @param dependencies [Hash] The rest of the parameter values.
512
+ # @param try_complement [false, true] Whether to attempt to
513
+ # complement a composite vlaue and return the `complement` flag in
514
+ # addition to the unwound values.
352
515
  #
353
516
  # @return [Array<String>, Array<(Array<String>, false)>,
354
517
  # Array<(Array<String>, true)>, nil] the unwound value(s), plus
355
518
  # optionally the `complement` flag, or otherwise `nil`.
356
519
  #
357
- def unprocess value, *rest, with_complement_flag: false
358
- # take care of empty properly
359
- if value.nil?
360
- if empty?
361
- return [''] if max == 1
362
- return [] if max.nil? or max > 1
363
- end
520
+ def unprocess value, dependencies = {}, try_complement: false
521
+ # we begin assuming the value has not been complemented
522
+ comp = false
364
523
 
365
- # i guess this is nil?
366
- return
524
+ if composite?
525
+ # coerce just to be sure
526
+ value = composite[value]
527
+ # now unwind
528
+ value = unwind value
529
+
530
+ if try_complement and complement?
531
+ tmp = complement value, unwind: true
532
+ if tmp.length < value.length
533
+ value = tmp
534
+ comp = true
535
+ end
536
+ end
537
+ else
538
+ value = [value] unless value.is_a? Array
367
539
  end
368
540
 
369
- # complement flag
370
- comp = false
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
541
+ # now we contextualize
542
+ value = contextualize value if contextualize?
379
543
 
380
- # ensure this thing is an array
381
- value = [value] unless value.is_a? Array
382
-
383
- # ensure the values are correctly formatted
544
+ # now we maybe prune out blanks
545
+ value.compact! unless empty?
546
+ # any nil at this point is on purpose
384
547
  value.map! { |v| v.nil? ? '' : self.format(v) }
385
548
 
386
- # throw in the complement flag
387
- return value, comp if with_complement_flag
388
-
389
- value
549
+ # now we return the pair if we're trying to complement it
550
+ try_complement ? [value, comp] : value
390
551
  end
391
552
 
392
553
  # Refreshes stateful information like the universal set, if present.
@@ -398,7 +559,7 @@ class Params::Registry::Template
398
559
  # do we want to call or do we want to instance_exec?
399
560
  univ = @unifunc.call
400
561
 
401
- univ = @composite[univ] if @composite
562
+ univ = composite[univ] if composite?
402
563
 
403
564
  @universe = univ
404
565
  end
@@ -40,6 +40,8 @@ module Params::Registry::Types
40
40
  end
41
41
  end
42
42
 
43
+ Boolean = Bool
44
+
43
45
  # For some reason there isn't a stock `Proc` type.
44
46
  Proc = self.Instance(::Proc)
45
47
 
@@ -135,15 +137,66 @@ module Params::Registry::Types
135
137
 
136
138
  # @!group Composite types not already defined
137
139
 
140
+ # XXX okay so once again dry-types has to be weird as hell. What we
141
+ # _want_ are `Set.of` and `Range.of` just like the built-in
142
+ # `Array.of`. What we have to _do_ to achieve this is god-knows-what.
143
+ #
144
+ class Container < ::Dry::Types::Nominal
145
+
146
+ class Constructor < ::Dry::Types::Array::Constructor
147
+ def constructor_type = Container::Constructor
148
+ end
149
+
150
+ class Member < ::Dry::Types::Array::Member
151
+ def constructor_type = Container::Constructor
152
+ end
153
+
154
+ def member_type = Container::Member
155
+
156
+ def constructor_type = Container::Constructor
157
+
158
+ def of(type)
159
+ member = case type
160
+ when ::String then ::Dry::Types[type]
161
+ else type
162
+ end
163
+
164
+ member_type.new(primitive, **options, member: member)
165
+ end
166
+
167
+ end
168
+
169
+ # class List < Array
170
+ # end
171
+
172
+ # XXX UGGGH CAN'T RETURN FROM THESE BECAUSE OF COURSE NOT UGGHGGHHG
173
+
138
174
  List = self.Constructor(::Array) do |x|
139
- x.respond_to?(:to_a) ? x.to_a : [x]
175
+ if x.is_a? ::Array
176
+ x
177
+ else
178
+ x.respond_to?(:to_a) ? x.to_a : [x]
179
+ end
140
180
  end
141
181
 
142
182
  # {::Set}
143
- Set = self.Constructor(::Set) { |x| ::Set[*x] }
183
+ Set = self.Constructor(::Set) do |x|
184
+ if x.is_a? ::Set
185
+ x
186
+ else
187
+ ::Set[*x]
188
+ end
189
+ end
144
190
 
145
191
  # {::Range}
146
- Range = self.Constructor(::Range) { |x| ::Range.new(*x.take(2)) }
192
+ Range = self.Constructor(::Range) do |x|
193
+ # warn x.inspect
194
+ if x.is_a? ::Range
195
+ x
196
+ else
197
+ ::Range.new(*x.take(2).sort)
198
+ end
199
+ end
147
200
 
148
201
  # The registry itself
149
202
  Registry = self.Instance(::Params::Registry)
@@ -157,13 +210,19 @@ module Params::Registry::Types
157
210
  # Groups
158
211
  GroupMap = Hash|Hash.map(NonNil, Array|TemplateMap)
159
212
 
213
+ Values = self.Constructor(::Object) do |a|
214
+ # still kind of torn on how to deal with this
215
+ a.is_a?(::Array) ? a : [a]
216
+ # a.respond_to?(:to_a) ? a : [a]
217
+ end
218
+
160
219
  Input = self.Constructor(::Hash) do |input|
161
220
  input = input.query.to_s if input.is_a? ::URI
162
221
  input = '' if input.nil?
163
222
  input = ::URI.decode_www_form input if input.is_a? ::String
164
223
 
165
224
  case input
166
- when ::Hash then Hash.map(Symbolish, Array.of(String))[input]
225
+ when ::Hash then Hash.map(Symbolish, Values)[input]
167
226
  when ::Array
168
227
  input.reduce({}) do |out, pair|
169
228
  k, *v = Strict::Array.constrained(min_size: 2)[pair]
@@ -3,6 +3,6 @@
3
3
  module Params
4
4
  class Registry
5
5
  # The module version
6
- VERSION = '0.1.12'
6
+ VERSION = '0.2.2'
7
7
  end
8
8
  end
@@ -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
@@ -239,11 +244,16 @@ class Params::Registry
239
244
  # @param params
240
245
  # [String, URI, Hash{#to_sym => Array}, Array<Array<(#to_sym, Object)>>]
241
246
  # the parameter set, in a dizzying variety of inputs.
247
+ # @param defaults [true, false] whether to include default values
248
+ # in the instance
249
+ # @param force [false, true] whether to force something i don't
250
+ # remember what though lol
242
251
  #
243
252
  # @return [Params::Registry::Instance] the instance.
244
253
  #
245
- def process params
246
- registry.instance_class.new self, Types::Input[params]
254
+ def process params, defaults: true, force: false
255
+ registry.instance_class.new self,
256
+ params: params, defaults: defaults, force: force
247
257
  end
248
258
 
249
259
  end
@@ -269,6 +279,7 @@ class Params::Registry
269
279
  # for the closures
270
280
  ts = templates
271
281
 
282
+
272
283
  # we always want these closures so we steamroll over whatever the
273
284
  # user might have put in these slots
274
285
  spec.merge!({
@@ -282,7 +293,7 @@ class Params::Registry
282
293
  unwind: -> set {
283
294
  # XXX do we want to sort this lexically or do we want it in
284
295
  # the same order as the keys?
285
- [set.to_a.map { |t| t = ts[t]; (t.slug || t.id).to_s }.sort, false]
296
+ set.to_a.map { |t| t = ts[t]; (t.slug || t.id).to_s }.sort
286
297
  }
287
298
  })
288
299
 
@@ -397,11 +408,15 @@ class Params::Registry
397
408
  # @param params
398
409
  # [String, URI, Hash{#to_sym => Array}, Array<Array<(#to_sym, Object)>>]
399
410
  # the parameter set, in a dizzying variety of inputs.
411
+ # @param defaults [true, false] whether to include default values
412
+ # in the instance
413
+ # @param force [false, true] whether to force something i don't
414
+ # remember what though lol
400
415
  #
401
416
  # @return [Params::Registry::Instance] the instance.
402
417
  #
403
- def process params
404
- instance_class.new self, Types::Input[params]
418
+ def process params, defaults: true, force: false
419
+ instance_class.new self, params: params, defaults: defaults, force: force
405
420
  end
406
421
 
407
422
  # Refresh any stateful elements of the templates.
@@ -409,7 +424,7 @@ class Params::Registry
409
424
  # @return [self]
410
425
  #
411
426
  def refresh!
412
- templates.each { |t| t.refresh! }
427
+ templates.templates.each { |t| t.refresh! }
413
428
 
414
429
  self
415
430
  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.12
4
+ version: 0.2.2
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-01-14 00:00:00.000000000 Z
11
+ date: 2025-03-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-types