params-registry 0.1.12 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml 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