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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ecda0bf7bd68a9dd7fd9b04cf9d5e3a2695aea9ac1d855f52dabff25f3992af3
4
- data.tar.gz: 0acfd9a1a99e2be5f61f85c0b8fad2bdde2e2db9b04060d3ead7d810a508827d
3
+ metadata.gz: ac097d76612371d0ccf0cd787b7aa3fa39b1b0392dd65ee09fb9188488785c67
4
+ data.tar.gz: 78d0cfe84914872dbc96852600af044b13dff6883cdf475810a411ce8c1d4b31
5
5
  SHA512:
6
- metadata.gz: 7f095e9e830b31e7c546cecde3dde1dc9112c4b65d82fa623c403fb5c0672cfeb1ebf0596582139c966b8cd5b23a9dbb23955c2d02100e56fd7e582b68006a36
7
- data.tar.gz: fc4a9ce7dea830a031553473d7088951b1f10a16772b561ac10259dea87669ac4ce20caff03490a27a4709ab6bf1ad0ca88a82a8e92d17b13655d55e0ab9fd24
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 = 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,34 @@ 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
+ @content[template.id] = template.process value
224
+ end
171
225
 
172
- del.each { |d| @content.delete d }
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
- # return
175
- @content[template.id]
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
- def inspect
203
- "<#{self.class} content: #{@content.inspect}, extra: #{@extra.inspect}>"
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.values_at(*(template.depends - template.consumes))
230
- v, c = template.unprocess @content[k], *deps, with_complement_flag: true
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 { |v| "#{template.slug || k}=#{v}" }.join ?&
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 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,19 @@ 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
+
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 Params::Registry::Error,
182
- "Malformed consumes declaration on #{t.id}" if out.any?(&:nil?)
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? ; !!@complement; end
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? && @complement.nil? && @unwind.nil? && !@reverse
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 Params::Registry::Error.new(
251
- "Preprocessor failed on #{template.id} with #{}",
252
- context: self, value: e)
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 @complement
415
+ def complement value, unwind: false
416
+ return unless @comfunc
417
+
282
418
  begin
283
- instance_exec value, &@complement
419
+ out = instance_exec value, &@comfunc
284
420
  rescue e
285
- raise Params::Registry::Error::Empirical.new(
286
- "Complement function failed: #{e.message}",
287
- context: self, value: value)
288
- end if @complement
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 values [Array] the values given for the parameter.
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 *values
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
- values.each do |v|
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 Params::Registry::Error::Syntax.new e.message,
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 Params::Registry::Error::Cardinality.new(
323
- "Need #{min} values and there are only #{out.length} values") if
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 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.
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, *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
513
+ def unprocess value, dependencies = {}, try_complement: false
514
+ # we begin assuming the value has not been complemented
515
+ comp = false
364
516
 
365
- # i guess this is nil?
366
- return
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
- # 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
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
- # ensure the values are correctly formatted
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
- # throw in the complement flag
387
- return value, comp if with_complement_flag
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 = @composite[univ] if @composite
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.respond_to?(:to_a) ? x.to_a : [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) { |x| ::Set[*x] }
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) { |x| ::Range.new(*x.take(2)) }
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, Array.of(String))[input]
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]
@@ -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.1'
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
@@ -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, Types::Input[params]
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
- [set.to_a.map { |t| t = ts[t]; (t.slug || t.id).to_s }.sort, false]
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, Types::Input[params]
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.12
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-01-14 00:00:00.000000000 Z
11
+ date: 2025-02-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-types