Class: Params::Registry::Template
- Inherits:
-
Object
- Object
- Params::Registry::Template
- Defined in:
- lib/params/registry/template.rb
Overview
This class manages an individual parameter template. It encapsulates all the information and operations needed to validate and coerce individual parameter values, as well as for serializing them back into a string, and for doing so with bit-for-bit consistency.
A parameter template can have a human-readable Symbol as a
slug
, which is distinct from its canonical identifier (id
),
which can be any object, although that must be unique for the entire
registry (while a slug only needs to be unique within its enclosing
group). It can also have any number of aliases
.
A template can manage a simple type like a string or number, or a
composite type like an array, tuple (implemented as a fixed-length
array), set, or range containing appropriate simple types. The
current (provisional) way to specify the types for the template are
the type
and composite
initialization parameters, where if the
latter is present, the former will be treated as its member type.
If certain forays into open-source diplomacy go well, these can be consolidated into a single type declaration.
A parameter may depend on (depends
) or conflict (conflicts
) with
other parameters, or even consume (consumes
) them as input. The
cardinality of a parameter is controlled by min
and max
, which
default to zero and unbounded, respectively. To require a parameter,
set min
to an integer greater than zero, and to enforce a single
scalar value, set max
to 1. (Setting min
greater than max
will
raise an error.) To control whether a value of nil
or the empty
string is dropped, kept (as the empty string) or kept as nil
, set
the empty
parameter.
When max
is greater than 1, the template automatically coerces any
simple value into an array containing that value. (And when max
is
equal to 1, an array will be replaced with a single value.) Passing
an array into #process with fewer than min
values (or a single
value when min
is greater than 1) will produce an error. Whether
the first N values (up to max
) or the last N values are taken
from the input, is controlled by the shift
parameter.
Composite values begin life as arrays of simple values. During
processing, the individual values are coerced from what are assumed
to be strings, and then the arrays themselves are coerced into the
composite types. Serialization is the same thing in reverse, using a
function passed into unwind
(which otherwise defaults to to_a
)
to turn the composite type back into an array, before the individual
values being turned into strings by way of the value passed into
format
, which can either be a standard format string or a
Proc. The unwind
function is also expected to sort the
array. There is also a reverse
flag for when it makes sense to
The transformation process, from array of strings to composite
object and back again, has a few more points of intervention. There
is an optional preproc
function, which is run when the
Instance is processed, after the
individual values are coerced and before the composite coercion is
applied, and a contextualize
function, which is run after unwind
but before format
. Both of these functions make it possible to use
information from the parameter's dependencies to manipulate its
values based on its context within a live
Instance.
Certain composite types, such as sets and ranges, have a coherent
concept of a universe
, which is implemented here as a function
that generates a compatible object. This is useful for when the
serialized representation of a parameter can be large. For instance,
if a set's universe has 100 elements and we want to represent the
subset with all the elements except for element 42, rather than
serializing a 99-element query string, we complement the set and
make a note to that effect (to be picked up by the
Instance serialization process and put in its
complement
parameter). The function passed into complement
will
be run as an instance method, which has access to universe
. Other
remarks about these functions:
- The
preproc
andcontextualize
functions are expected to take the form-> value, hash { expr }
and must return an array. Thehash
here is expected to contain at least the subset of parameters marked as dependencies (as is done in Instance), keyed byslug
or, failing that,id
. - The
unwind
andcomplement
functions both take the composite value as an argument (-> value { expr }
).unwind
is expected to return an array of elements, andcomplement
should return a composite object of the same type. - The
universe
function takes no arguments and is expected to return a composite object of the same type.
Instance Attribute Summary collapse
-
#aliases ⇒ Array<Symbol>
readonly
Any aliases for this parameter.
-
#blank? ⇒ Boolean
readonly
Returns true if the template has no configuration data to speak of.
-
#complement? ⇒ Boolean
readonly
Whether this (composite) parameter can be complemented or inverted.
-
#composite ⇒ Dry::Types::Type?
readonly
The type for composite values.
-
#composite? ⇒ Boolean
readonly
Whether this parameter is composite.
-
#conflicts ⇒ Array
readonly
Any parameters this one conflicts with.
-
#consumes ⇒ Array
readonly
Any parameters this one consumes (implies
depends
+conflicts
). -
#contextualize? Whether there is a contextualizing(Whetherthereisacontextualizing) ⇒ Boolean
readonly
function present in the unprocessing stack.
-
#default ⇒ Object
readonly
Returns the value of attribute default.
-
#default? ⇒ Boolean
readonly
Whether this parameter has a default value.
-
#depends ⇒ Array
readonly
Any parameters this one depends on.
-
#empty? ⇒ Boolean
readonly
Whether to accept empty values.
-
#id ⇒ Object
readonly
The canonical identifier for the parameter.
-
#max ⇒ Integer?
readonly
Maximum cardinality for the parameter's values.
-
#min ⇒ Integer
readonly
Minimum cardinality for the parameter's values.
-
#preproc(myself, others) ⇒ Array
readonly
Preprocess a parameter value against itself and/or
consume
d values. -
#preproc? ⇒ Boolean
readonly
Whether there is a preprocessor function.
-
#registry ⇒ Params::Registry
readonly
A backreference to the registry.
-
#reverse? ⇒ Boolean
readonly
Whether to interpret composite values as reversed.
-
#shift? ⇒ Boolean
readonly
Whether to shift values more than
max
cardinality off the front. -
#slug ⇒ Symbol?
readonly
The primary nickname for the parameter, if different from the
id
. -
#type ⇒ Dry::Types::Type
readonly
The type for individual parameter values.
-
#universe ⇒ Object?
readonly
The universal composite object (e.g. set or range) from which valid values are drawn.
Instance Method Summary collapse
-
#complement(value, unwind: false) ⇒ Object?
Return the complement of the composite value for the parameter.
-
#contextualize? ⇒ Boolean
function present in the unprocessing stack.
-
#format(scalar) ⇒ String
Format an individual atomic value.
-
#initialize(registry, id, slug: nil, type: Types::NormalizedString, composite: nil, format: nil, aliases: nil, depends: nil, conflicts: nil, consumes: nil, preproc: nil, min: 0, max: nil, shift: false, empty: false, default: nil, universe: nil, complement: nil, unwind: nil, contextualize: nil, reverse: false) ⇒ Template
constructor
Initialize the template object.
-
#inspect ⇒ String
Return a suitable representation for debugging.
-
#process(value) ⇒ Object, Array
Validate a list of individual parameter values and (if one is present) construct a
composite
value. -
#refresh! ⇒ void
Refreshes stateful information like the universal set, if present.
-
#unprocess(value, dependencies = {}, try_complement: false) ⇒ Array<String>, ...
This method takes a value which is assumed to be valid and transforms it into an array of strings suitable for serialization.
-
#unwind(value) ⇒ Array
Unwind a composite value into an array of simple values.
Constructor Details
#initialize(registry, id, slug: nil, type: Types::NormalizedString, composite: nil, format: nil, aliases: nil, depends: nil, conflicts: nil, consumes: nil, preproc: nil, min: 0, max: nil, shift: false, empty: false, default: nil, universe: nil, complement: nil, unwind: nil, contextualize: nil, reverse: false) ⇒ Template
Initialize the template object.
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/params/registry/template.rb', line 155 def initialize registry, id, slug: nil, type: Types::NormalizedString, composite: nil, format: nil, aliases: nil, depends: nil, conflicts: nil, consumes: nil, preproc: nil, min: 0, max: nil, shift: false, empty: false, default: nil, universe: nil, complement: nil, unwind: nil, contextualize: nil, reverse: false @registry = Types::Registry[registry] @id = Types::NonNil[id] @slug = Types::Symbol[slug] if slug @type = Types[type] @composite = Types[composite] if composite @format = (Types::Proc | Types::String)[format] if format @aliases = Types::Array[aliases] @depends = Types::Array[depends] @conflicts = Types::Array[conflicts] @consumes = Types::Array[consumes] @preproc = Types::Proc[preproc] if preproc @min = Types::NonNegativeInteger[min || 0] @max = Types::PositiveInteger.optional[max] @shift = Types::Bool[shift] @empty = Types::Bool[empty] @default = Types::Nominal::Any[default] @unifunc = Types::Proc[universe] if universe @comfunc = Types::Proc[complement] if complement @unwfunc = Types::Proc[unwind] if unwind @confunc = Types::Proc[contextualize] if contextualize @reverse = Types::Bool[reverse] raise ArgumentError, "min (#{@min}) cannot be greater than max (#{@max})" if @min and @max and @min > @max # post-initialization hook post_init end |
Instance Attribute Details
#aliases ⇒ Array<Symbol> (readonly)
Returns any aliases for this parameter.
221 222 |
# File 'lib/params/registry/template.rb', line 221 attr_reader :registry, :id, :slug, :type, :composite, :aliases, :preproc, :min, :max, :default |
#blank? ⇒ Boolean (readonly)
Returns true if the template has no configuration data to speak of.
367 368 369 370 371 372 373 374 375 |
# File 'lib/params/registry/template.rb', line 367 def blank? # XXX PHEWWW @slug.nil? && @type == Types::NormalizedString && @composite.nil? && @format.nil? && @aliases.empty? && @depends.empty? && @conflicts.empty? && @consumes.empty? && @preproc.nil? && @min == 0 && @max.nil? && !@shift && !@empty && @default.nil? && @unifunc.nil? && @comfunc.nil? && @unwfunc.nil? && @confunc.nil? && !@reverse end |
#complement? ⇒ Boolean (readonly)
Whether this (composite) parameter can be complemented or inverted.
355 |
# File 'lib/params/registry/template.rb', line 355 def complement? ; !!@comfunc; end |
#composite ⇒ Dry::Types::Type? (readonly)
Returns the type for composite values.
221 222 |
# File 'lib/params/registry/template.rb', line 221 attr_reader :registry, :id, :slug, :type, :composite, :aliases, :preproc, :min, :max, :default |
#composite? ⇒ Boolean (readonly)
Whether this parameter is composite.
290 |
# File 'lib/params/registry/template.rb', line 290 def composite? ; !!@composite; end |
#conflicts ⇒ Array (readonly)
Any parameters this one conflicts with.
274 275 276 277 278 279 280 281 282 283 |
# File 'lib/params/registry/template.rb', line 274 def conflicts out = (@conflicts | (@preproc ? @consumes : [])).map do |t| registry.templates.canonical t end raise E::Internal, "Malformed conflict declaration on #{id}" if out.any?(&:nil?) out end |
#consumes ⇒ Array (readonly)
Any parameters this one consumes (implies depends
+ conflicts
).
311 312 313 314 315 316 317 318 |
# File 'lib/params/registry/template.rb', line 311 def consumes out = @consumes.map { |t| registry.templates.canonical t } raise E::Internal, "Malformed consumes declaration on #{id}" if out.any?(&:nil?) out end |
#contextualize? Whether there is a contextualizing(Whetherthereisacontextualizing) ⇒ Boolean (readonly)
function present in the unprocessing stack.
362 |
# File 'lib/params/registry/template.rb', line 362 def contextualize? ; !!@confunc; end |
#default ⇒ Object (readonly)
Returns the value of attribute default.
221 222 |
# File 'lib/params/registry/template.rb', line 221 attr_reader :registry, :id, :slug, :type, :composite, :aliases, :preproc, :min, :max, :default |
#default? ⇒ Boolean (readonly)
Whether this parameter has a default value.
297 |
# File 'lib/params/registry/template.rb', line 297 def default? ; !@default.nil?; end |
#depends ⇒ Array (readonly)
Any parameters this one depends on.
258 259 260 261 262 263 264 265 266 267 |
# File 'lib/params/registry/template.rb', line 258 def depends out = (@depends | (@preproc ? @consumes : [])).map do |t| registry.templates.canonical t end raise E::Internal, "Malformed dependency declaration on #{id}" if out.any?(&:nil?) out end |
#empty? ⇒ Boolean (readonly)
Whether to accept empty values.
341 |
# File 'lib/params/registry/template.rb', line 341 def empty? ; !!@empty; end |
#id ⇒ Object (readonly)
Returns the canonical identifier for the parameter.
221 222 |
# File 'lib/params/registry/template.rb', line 221 attr_reader :registry, :id, :slug, :type, :composite, :aliases, :preproc, :min, :max, :default |
#max ⇒ Integer? (readonly)
Returns maximum cardinality for the parameter's values.
221 222 |
# File 'lib/params/registry/template.rb', line 221 attr_reader :registry, :id, :slug, :type, :composite, :aliases, :preproc, :min, :max, :default |
#min ⇒ Integer (readonly)
Returns minimum cardinality for the parameter's values.
221 222 |
# File 'lib/params/registry/template.rb', line 221 attr_reader :registry, :id, :slug, :type, :composite, :aliases, :preproc, :min, :max, :default |
#preproc(myself, others) ⇒ Array (readonly)
Preprocess a parameter value against itself and/or consume
d values.
221 222 |
# File 'lib/params/registry/template.rb', line 221 attr_reader :registry, :id, :slug, :type, :composite, :aliases, :preproc, :min, :max, :default |
#preproc? ⇒ Boolean (readonly)
Whether there is a preprocessor function.
304 |
# File 'lib/params/registry/template.rb', line 304 def preproc? ; !!@preproc ; end |
#registry ⇒ Params::Registry (readonly)
Returns a backreference to the registry.
221 222 223 |
# File 'lib/params/registry/template.rb', line 221 def registry @registry end |
#reverse? ⇒ Boolean (readonly)
Whether to interpret composite values as reversed.
348 |
# File 'lib/params/registry/template.rb', line 348 def reverse? ; !!@reverse; end |
#shift? ⇒ Boolean (readonly)
Whether to shift values more than max
cardinality off the front.
334 |
# File 'lib/params/registry/template.rb', line 334 def shift? ; !!@shift; end |
#slug ⇒ Symbol? (readonly)
Returns the primary nickname for the parameter, if
different from the id
.
221 222 |
# File 'lib/params/registry/template.rb', line 221 attr_reader :registry, :id, :slug, :type, :composite, :aliases, :preproc, :min, :max, :default |
#type ⇒ Dry::Types::Type (readonly)
Returns the type for individual parameter values.
221 222 |
# File 'lib/params/registry/template.rb', line 221 attr_reader :registry, :id, :slug, :type, :composite, :aliases, :preproc, :min, :max, :default |
#universe ⇒ Object? (readonly)
The universal composite object (e.g. set or range) from which valid values are drawn.
324 325 326 327 |
# File 'lib/params/registry/template.rb', line 324 def universe refresh! if @unifunc and not @universe @universe end |
Instance Method Details
#complement(value, unwind: false) ⇒ Object?
Return the complement of the composite value for the parameter.
422 423 424 425 426 427 428 429 430 431 432 433 434 |
# File 'lib/params/registry/template.rb', line 422 def complement value, unwind: false return unless @comfunc begin out = instance_exec value, &@comfunc rescue e raise E::Internal.new( "Complement function on #{id} failed: #{e.message}", context: self, value: value, original: e) end unwind ? self.unwind(out) : out end |
#contextualize? ⇒ Boolean
function present in the unprocessing stack.
362 |
# File 'lib/params/registry/template.rb', line 362 def contextualize? ; !!@confunc; end |
#format(scalar) ⇒ String
Format an individual atomic value.
405 406 407 408 409 410 411 412 413 |
# File 'lib/params/registry/template.rb', line 405 def format scalar return scalar.to_s unless @format if @format.is_a? Proc instance_exec scalar, &@format else (@format || '%s').to_s % scalar end end |
#inspect ⇒ String
Return a suitable representation for debugging.
574 575 576 577 578 579 580 |
# File 'lib/params/registry/template.rb', line 574 def inspect c = self.class i = id.inspect t = '%s%s' % [type, composite ? ", #{composite}]" : ''] "#<#{c} #{i} (#{t})>" end |
#process(value) ⇒ Object, Array
Validate a list of individual parameter values and (if one is present)
construct a composite
value.
443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 |
# File 'lib/params/registry/template.rb', line 443 def process value # XXX what we _really_ want is `Types::Set.of` and # `Types::Range.of` but who the hell knows how to actually make # that happen, so what we're gonna do instead is test if the # template is composite, then test the input against the composite # type, then run `unwind` on it and test the individual members # warn [(slug || id), value].inspect # coerce and then unwind value = unwind composite[value] if composite? # otherwise coerce into an array value = [value] unless value.is_a? Array # copy to out out = [] value.each do |v| # skip over nil/empty values unless we can be empty if v.nil? or v.to_s.empty? next unless empty? v = nil end if v begin tmp = type[v] # this either crashes or it doesn't v = tmp # in which case v is only assigned if successful rescue Dry::Types::CoercionError => e raise E::Syntax.new e., context: self, value: v, original: e end end out << v end # now we deal with cardinality raise E::Cardinality, "Need #{min} values and there are only #{out.length} values" if out.length < min # warn "hurr #{out.inspect}, #{max}" if max # return if it's supposed to be a scalar value return out.first if max == 1 # cut the values to length from either the front or back out.slice!((shift? ? -max : 0), max) if out.length > max end composite ? composite[out] : out end |
#refresh! ⇒ void
This method returns an undefined value.
Refreshes stateful information like the universal set, if present.
557 558 559 560 561 562 563 564 565 566 567 568 |
# File 'lib/params/registry/template.rb', line 557 def refresh! if @unifunc # do we want to call or do we want to instance_exec? univ = @unifunc.call univ = composite[univ] if composite? @universe = univ end self end |
#unprocess(value, dependencies = {}, try_complement: false) ⇒ Array<String>, ...
This method takes a value which is assumed to be valid and transforms it into an array of strings suitable for serialization.
Applies unwind
to value
to get an array, then format
over
each of the elements to get strings. If scalar
is true, it
will also return the flag from unwind
indicating whether or
not the complement
parameter should be set.
This method is called by Instance#to_s and
others to produce content which is amenable to serialization. As
what happens there, the content of rest
should be the values
of the parameters specified in depends
.
520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 |
# File 'lib/params/registry/template.rb', line 520 def unprocess value, dependencies = {}, try_complement: false # we begin assuming the value has not been complemented comp = false if composite? # coerce just to be sure value = composite[value] # now unwind value = unwind value if try_complement and complement? tmp = complement value, unwind: true if tmp.length < value.length value = tmp comp = true end end else value = [value] unless value.is_a? Array end # now we contextualize value = contextualize value if contextualize? # now we maybe prune out blanks value.compact! unless empty? # any nil at this point is on purpose value.map! { |v| v.nil? ? '' : self.format(v) } # now we return the pair if we're trying to complement it try_complement ? [value, comp] : value end |
#unwind(value) ⇒ Array
Unwind a composite value into an array of simple values. This
wraps the matching function passed into the constructor, or
#to_a
in lieu of one.
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 |
# File 'lib/params/registry/template.rb', line 234 def unwind value return unless composite? func = @unwfunc || -> v { v.to_a } begin out = instance_exec value, &func rescue Exception, e raise E::Internal.new "Unwind on #{id} failed: #{e.message}", value: value, original: e end raise E::Internal, "Unwind on #{id} returned #{out.class}, not an Array" unless out.is_a? Array out end |