Module: Speculation

Extended by:
NamespacedSymbols
Defined in:
lib/speculation.rb,
lib/speculation/gen.rb,
lib/speculation/pmap.rb,
lib/speculation/spec.rb,
lib/speculation/test.rb,
lib/speculation/error.rb,
lib/speculation/utils.rb,
lib/speculation/version.rb,
lib/speculation/identifier.rb,
lib/speculation/spec/f_spec.rb,
lib/speculation/spec/or_spec.rb,
lib/speculation/spec/and_spec.rb,
lib/speculation/spec/hash_spec.rb,
lib/speculation/spec/every_spec.rb,
lib/speculation/spec/merge_spec.rb,
lib/speculation/spec/regex_spec.rb,
lib/speculation/spec/tuple_spec.rb,
lib/speculation/spec/nilable_spec.rb,
lib/speculation/namespaced_symbols.rb,
lib/speculation/spec/predicate_spec.rb

Defined Under Namespace

Modules: Gen, NamespacedSymbols, Test Classes: Error

Constant Summary collapse

INVALID =
ns(:invalid)
VERSION =
"0.3.0"

Class Attribute Summary collapse

Class Method Summary collapse

Methods included from NamespacedSymbols

name, namespace, ns, symbol

Class Attribute Details

.check_assertsObject

Enables or disables spec asserts. Defaults to false.



18
19
20
# File 'lib/speculation.rb', line 18

def check_asserts
  @check_asserts
end

.coll_check_limitObject

The number of elements validated in a collection spec’ed with ‘every’.



30
31
32
# File 'lib/speculation.rb', line 30

def coll_check_limit
  @coll_check_limit
end

.coll_error_limitObject

The number of errors reported by explain in a collection spec’ed with ‘every’



34
35
36
# File 'lib/speculation.rb', line 34

def coll_error_limit
  @coll_error_limit
end

.fspec_iterationsObject

The number of times an anonymous fn specified by fspec will be (generatively) tested during conform.



27
28
29
# File 'lib/speculation.rb', line 27

def fspec_iterations
  @fspec_iterations
end

.recursion_limitObject

A soft limit on how many times a branching spec (or/alt/zero_or_more) can be recursed through during generation. After this a non-recursive branch will be chosen.



23
24
25
# File 'lib/speculation.rb', line 23

def recursion_limit
  @recursion_limit
end

Class Method Details

.alt(kv_specs) ⇒ Hash

Returns regex op that returns a two item array containing the key of the first matching pred and the corresponding value. Thus can be destructured to refer generically to the components of the return.

Examples:

S.alt(even: :even?.to_proc, small: -> (n) { n < 42 })

Parameters:

  • kv_specs (Hash)

    key+pred pairs

Returns:

  • (Hash)

    regex op that returns a two item array containing the key of the first matching pred and the corresponding value. Thus can be destructured to refer generically to the components of the return.



512
513
514
# File 'lib/speculation.rb', line 512

def self.alt(kv_specs)
  _alt(kv_specs.values, kv_specs.keys).merge(:id => SecureRandom.uuid)
end

.and(*preds) ⇒ Spec

Returns a spec that returns the conformed value. Successive conformed values propagate through rest of predicates.

Examples:

S.and(Numeric, -> (n) { n < 42 })

Parameters:

  • preds (Array)

    predicate/specs

Returns:

  • (Spec)

    a spec that returns the conformed value. Successive conformed values propagate through rest of predicates.



394
395
396
# File 'lib/speculation.rb', line 394

def self.and(*preds)
  AndSpec.new(preds)
end

.and_keys(*ks) ⇒ Object

See Also:



375
376
377
# File 'lib/speculation.rb', line 375

def self.and_keys(*ks)
  [ns(:and), *ks]
end

.assert(spec, x) ⇒ Object

Can be enabled or disabled at runtime:

  • enabled/disabled by setting ‘check_asserts`.

  • enabled by setting environment variable SPECULATION_CHECK_ASSERTS to the string “true”

Defaults to false if not set.

Parameters:

  • spec (Spec)
  • x

    value to validate

Returns:

  • x if x is valid? according to spec

Raises:

  • (Error)

    with explain_data plus :Speculation/failure of :assertion_failed



56
57
58
59
60
61
62
63
64
65
# File 'lib/speculation.rb', line 56

def self.assert(spec, x)
  return x unless check_asserts
  return x if valid?(spec, x)

  ed = _explain_data(spec, [], [], [], x).merge(ns(:failure) => :assertion_failed)
  out = StringIO.new
  explain_out(ed, out)

  raise Speculation::Error.new("Spec assertion failed\n#{out.string}", ed)
end

.cat(named_specs) ⇒ Hash

Returns regex op that matches (all) values in sequence, returning a map containing the keys of each pred and the corresponding value.

Examples:

S.cat(e: :even?.to_proc, o: :odd?.to_proc)

Parameters:

  • named_specs (Hash)

    key+pred hash

Returns:

  • (Hash)

    regex op that matches (all) values in sequence, returning a map containing the keys of each pred and the corresponding value.



521
522
523
524
525
526
# File 'lib/speculation.rb', line 521

def self.cat(named_specs)
  keys = named_specs.keys
  predicates = named_specs.values

  pcat(:keys => keys, :predicates => predicates, :return_value => {})
end

.coll_of(pred, opts = {}) ⇒ Spec

Returns a spec for a collection of items satisfying pred. Unlike ‘every’, coll_of will exhaustively conform every value.

Same options as ‘every’. conform will produce a collection corresponding to :into if supplied, else will match the input collection, avoiding rebuilding when possible.

Parameters:

  • pred
  • opts (Hash) (defaults to: {})

Returns:

  • (Spec)

See Also:



462
463
464
# File 'lib/speculation.rb', line 462

def self.coll_of(pred, opts = {})
  every(pred, ns(:conform_all) => true, **opts)
end

.conform(spec, value) ⇒ Symbol, Object

Returns :Speculation/invalid if value does not match spec, else the (possibly destructured) value.

Parameters:

  • spec (Spec)
  • value

    value to conform

Returns:

  • (Symbol, Object)

    :Speculation/invalid if value does not match spec, else the (possibly destructured) value



131
132
133
134
# File 'lib/speculation.rb', line 131

def self.conform(spec, value)
  spec = Identifier(spec)
  specize(spec).conform(value)
end

.conformer {|value| ... } ⇒ Spec

Returns a spec that uses block as a predicate/conformer.

Yields:

  • (value)

    predicate function with the semantics of conform i.e. it should return either a (possibly converted) value or :“Speculation/invalid”

Returns:

  • (Spec)

    a spec that uses block as a predicate/conformer.



540
541
542
# File 'lib/speculation.rb', line 540

def self.conformer(&pred)
  spec_impl(pred, true)
end

.constrained(re, *preds) ⇒ Hash

Returns regex-op that consumes input as per re but subjects the resulting value to the conjunction of the predicates, and any conforming they might perform.

Parameters:

  • re (Hash)

    regex op

  • preds (Array)

    predicates

Returns:

  • (Hash)

    regex-op that consumes input as per re but subjects the resulting value to the conjunction of the predicates, and any conforming they might perform.



533
534
535
# File 'lib/speculation.rb', line 533

def self.constrained(re, *preds)
  { ns(:op) => ns(:amp), :p1 => re, :predicates => preds }
end

.date_in(date_range) ⇒ Object

Returns Spec that validates dates in the given range.

Parameters:

  • date_range (Range<Date>)

Returns:

  • Spec that validates dates in the given range



105
106
107
108
# File 'lib/speculation.rb', line 105

def self.date_in(date_range)
  spec(self.and(Date, ->(x) { date_range.cover?(x) }),
       :gen => ->(_) { rand(date_range) })
end

.def(key, spec) ⇒ Symbol, Method

Given a namespace-qualified symbol key, and a spec, spec name, predicate or regex-op makes an entry in the registry mapping key to the spec

Parameters:

  • key (Symbol)

    namespace-qualified symbol

  • spec (Spec, Symbol, Proc, Hash)

    a spec, spec name, predicate or regex-op

Returns:

  • (Symbol, Method)


270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/speculation.rb', line 270

def(key, spec)
  key = Identifier(key)

  unless Utils.ident?(key) && (!key.is_a?(Symbol) || NamespacedSymbols.namespace(key))
    raise ArgumentError, "key must be a namespaced Symbol, e.g. #{ns(:my_spec)}, or a Method"
  end

  spec = if spec?(spec) || regex?(spec) || registry[spec]
           spec
         else
           spec_impl(spec, false)
         end

  @registry_ref.swap do |reg|
    reg.merge(key => with_name(spec, key)).freeze
  end

  key.is_a?(Identifier) ? key.get_method : key
end

.every(pred, opts = {}) ⇒ Spec

Note:

that ‘every’ does not do exhaustive checking, rather it samples ‘coll_check_limit` elements. Nor (as a result) does it do any conforming of elements. ’explain’ will report at most coll_error_limit problems. Thus ‘every’ should be suitable for potentially large collections.

Returns spec that validates collection elements against pred.

Parameters:

  • pred

    predicate to validate collections with

  • opts (Hash) (defaults to: {})

    Takes several kwargs options that further constrain the collection:

Options Hash (opts):

  • :kind (Object) — default: nil

    a pred/spec that the collection type must satisfy, e.g. ‘Array` Note that if :kind is specified and :into is not, this pred must generate in order for every to generate.

  • :count (Integer) — default: nil

    specifies coll has exactly this count

  • :min_count (Integer) — default: nil

    coll has count >= min_count

  • :max_count (Integer) — default: nil

    coll has count <= max_count

  • :distinct (Boolean) — default: nil

    all the elements are distinct

  • :gen_max (Integer) — default: 20

    the maximum coll size to generate

  • :into (Array, Hash, Set) — default: Array

    one of [], {}, Set[], the default collection to generate into (default: empty coll as generated by :kind pred if supplied, else [])

  • :gen (Proc)

    generator proc, which must be a proc of one arg (Rantly instance) that generates a valid value.

Returns:

  • (Spec)

    spec that validates collection elements against pred

See Also:



427
428
429
430
431
432
433
# File 'lib/speculation.rb', line 427

def self.every(pred, opts = {})
  gen = opts.delete(:gen)

  EverySpec.new(pred, opts).tap do |spec|
    spec.gen = gen
  end
end

.every_kv(kpred, vpred, options) ⇒ Spec

Like ‘every’ but takes separate key and val preds and works on associative collections.

Same options as ‘every’, :into defaults to {}

Parameters:

  • kpred

    key pred

  • vpred

    val pred

  • options (Hash)

Returns:

  • (Spec)

    spec that validates associative collections

See Also:



445
446
447
448
449
# File 'lib/speculation.rb', line 445

def self.every_kv(kpred, vpred, options)
  every(tuple(kpred, vpred), ns(:kfn) => ->(_i, v) { v.first },
                             :into    => {},
                             **options)
end

.exercise(spec, n: 10, overrides: {}) ⇒ Array

Generates a number (default 10) of values compatible with spec and maps conform over them, returning a sequence of [val conformed-val] tuples.

Parameters:

  • spec
  • n (Integer) (defaults to: 10)
  • overrides (Hash) (defaults to: {})

    a generator overrides hash as per ‘gen`

Returns:

  • (Array)

    an array of [val, conformed_val] tuples

See Also:



624
625
626
627
628
# File 'lib/speculation.rb', line 624

def self.exercise(spec, n: 10, overrides: {})
  Gen.sample(gen(spec, overrides), n).map { |value|
    [value, conform(spec, value)]
  }
end

.exercise_fn(method, n: 10, fspec: nil) ⇒ Array

Exercises the method by applying it to n (default 10) generated samples of its args spec. When fspec is supplied its arg spec is used, and method can be a proc.

Parameters:

  • method (Method)
  • n (Integer) (defaults to: 10)
  • fspec (Spec) (defaults to: nil)

Returns:

  • (Array)

    an arrray of tuples of [args, ret].

Raises:

  • (ArgumentError)


637
638
639
640
641
642
# File 'lib/speculation.rb', line 637

def self.exercise_fn(method, n: 10, fspec: nil)
  fspec ||= get_spec(method)
  raise ArgumentError, "No fspec found for #{method}" unless fspec

  Gen.sample(gen(fspec.args), n).map { |args| [args, method.call(*args)] }
end

.explain(spec, x) ⇒ Object

Given a spec and a value that fails to conform, prints an explaination to STDOUT

Parameters:

  • spec (Spec)
  • x


208
209
210
# File 'lib/speculation.rb', line 208

def self.explain(spec, x)
  explain_out(explain_data(spec, x))
end

.explain_data(spec, x) ⇒ nil, Hash

Given a spec and a value x which ought to conform, returns nil if x conforms, else a hash with at least the key :“Speculation/problems” whose value is a collection of problem-hashes, where problem-hash has at least :path :pred and :val keys describing the predicate and the value that failed at that path.

Parameters:

  • spec (Spec)
  • x

    value which ought to conform

Returns:

  • (nil, Hash)

    nil if x conforms, else a hash with at least the key :Speculation/problems whose value is a collection of problem-hashes, where problem-hash has at least :path :pred and :val keys describing the predicate and the value that failed at that path.



168
169
170
171
172
# File 'lib/speculation.rb', line 168

def self.explain_data(spec, x)
  spec = Identifier(spec)
  name = spec_name(spec)
  _explain_data(spec, [], Array(name), [], x)
end

.explain_out(ed, out = STDOUT) ⇒ Object

Parameters:

  • ed (Hash)

    explain data (per ‘explain_data’)

  • out (IO) (defaults to: STDOUT)

    destination to write explain human readable message to (default STDOUT)



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/speculation.rb', line 176

def self.explain_out(ed, out = STDOUT)
  return out.puts("Success!") unless ed

  ed.fetch(ns(:problems)).each do |prob|
    path, pred, val, reason, via, inn = prob.values_at(:path, :pred, :val, :reason, :via, :in)

    out.print("In: ", inn.to_a.inspect, " ") unless inn.empty?
    out.print("val: ", val.inspect, " fails")
    out.print(" spec: ", via.last.inspect) unless via.empty?
    out.print(" at: ", path.to_a.inspect) unless path.empty?
    out.print(" predicate: ", pred.inspect)
    out.print(", ", reason.inspect) if reason

    prob.each do |k, v|
      unless [:path, :pred, :val, :reason, :via, :in].include?(k)
        out.print("\n\t ", k.inspect, PP.pp(v, String.new))
      end
    end

    out.puts
  end

  ed.each do |k, v|
    out.puts("#{k} #{PP.pp(v, String.new)}") unless k == ns(:problems)
  end

  nil
end

.explain_str(spec, x) ⇒ String

Returns a human readable explaination.

Parameters:

  • spec (Spec)
  • x

    a value that fails to conform

Returns:

  • (String)

    a human readable explaination



215
216
217
218
219
# File 'lib/speculation.rb', line 215

def self.explain_str(spec, x)
  out = StringIO.new
  explain_out(explain_data(spec, x), out)
  out.string
end

.fdef(method, spec) ⇒ Method

Note:

Note that :fn specs require the presence of :args and :ret specs to conform values, and so :fn specs will be ignored if :args or :ret are missing.

Once registered, specs are checked by instrument and tested by the runner Speculation::Test.check

Examples:

to register method specs for the Hash[] method:

S.fdef(Hash.method(:[]),
  args: S.alt(
    hash: Hash,
    array_of_pairs: S.coll_of(S.tuple(ns(S, :any), ns(S, :any)), kind: Array),
    kvs: S.constrained(S.one_or_more(ns(S, :any)), -> (kvs) { kvs.count.even? })
  ),
  ret: Hash
)

Parameters:

  • method (Method)
  • spec (Hash)

Options Hash (spec):

  • :args (Hash)

    regex spec for the method arguments as a list

  • :block (Object)

    an fspec for the method’s block

  • :ret (Object)

    a spec for the method’s return value

  • :fn (Object)

    a spec of the relationship between args and ret - the value passed is { args: conformed_args, block: given_block, ret: conformed_ret } and is expected to contain predicates that relate those values

Returns:

  • (Method)

    the method spec’ed



596
597
598
599
# File 'lib/speculation.rb', line 596

def self.fdef(method, spec)
  self.def(Identifier(method), fspec(spec))
  method
end

.float_in(min: nil, max: nil, infinite: true, nan: true) ⇒ Spec

Returns that validates floats.

Parameters:

  • infinite (Boolean) (defaults to: true)

    whether +/- infinity allowed (default true)

  • nan (Boolean) (defaults to: true)

    whether Flaot::NAN allowed (default true)

  • min (Boolean) (defaults to: nil)

    minimum value (inclusive, default none)

  • max (Boolean) (defaults to: nil)

    maximum value (inclusive, default none)

Returns:

  • (Spec)

    that validates floats



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/speculation.rb', line 72

def self.float_in(min: nil, max: nil, infinite: true, nan: true)
  preds = [Float]
  preds << ->(x) { !x.nan? } unless nan
  preds << ->(x) { !x.infinite? } unless infinite
  preds << ->(x) { x <= max } if max
  preds << ->(x) { x >= min } if min

  min ||= Float::MIN
  max ||= Float::MAX

  gens = [[20, ->(_) { rand(min.to_f..max.to_f) }]]
  gens << [1, ->(r) { r.choose(Float::INFINITY, -Float::INFINITY) }] if infinite
  gens << [1, ->(_) { Float::NAN }] if nan

  spec(self.and(*preds), :gen => ->(rantly) { rantly.freq(*gens) })
end

.fspec(args: nil, ret: nil, fn: nil, block: nil, gen: nil) ⇒ Spec

Takes :args :ret and (optional) :block and :fn kwargs whose values are preds and returns a spec whose conform/explain take a method/proc and validates it using generative testing. The conformed value is always the method itself.

fspecs can generate procs that validate the arguments and fabricate a return value compliant with the :ret spec, ignoring the :fn spec if present.

Parameters:

  • args (defaults to: nil)

    predicate

  • ret (defaults to: nil)

    predicate

  • fn (defaults to: nil)

    predicate

  • block (defaults to: nil)

    predicate

  • gen (Proc) (defaults to: nil)

    generator proc, which must be a proc of one arg (Rantly instance) that generates a valid value.

Returns:

  • (Spec)

See Also:



559
560
561
562
563
# File 'lib/speculation.rb', line 559

def self.fspec(args: nil, ret: nil, fn: nil, block: nil, gen: nil)
  FSpec.new(:args => spec(args), :ret => spec(ret), :fn => spec(fn), :block => spec(block)).tap do |spec|
    spec.gen = gen
  end
end

.gen(spec, overrides = nil) ⇒ Proc

Given a spec, returns the generator for it, or raises if none can be constructed.

Optionally an overrides hash can be provided which should map spec names or paths (array of symbols) to no-arg generator Procs. These will be used instead of the generators at those names/paths. Note that parent generator (in the spec or overrides map) will supersede those of any subtrees. A generator for a regex op must always return a sequential collection (i.e. a generator for Speculation.zero_or_more should return either an empty array or an array with one item in it)

Parameters:

  • spec (Spec)
  • overrides (Hash) (defaults to: nil)

Returns:

  • (Proc)


251
252
253
254
# File 'lib/speculation.rb', line 251

def self.gen(spec, overrides = nil)
  spec = Identifier(spec)
  gensub(spec, overrides, [], ns(:recursion_limit) => recursion_limit)
end

.get_spec(key) ⇒ Spec?

Returns spec registered for key, or nil.

Parameters:

  • key (Symbol, Method)

Returns:

  • (Spec, nil)

    spec registered for key, or nil



298
299
300
# File 'lib/speculation.rb', line 298

def self.get_spec(key)
  registry[Identifier(key)]
end

.hash_of(kpred, vpred, options = {}) ⇒ Spec

Returns a spec for a hash whose keys satisfy kpred and vals satisfy vpred. Unlike ‘every_kv’, hash_of will exhaustively conform every value.

Same options as ‘every’, :kind defaults to ‘Speculation::Utils.hash?`, with the addition of:

:conform_keys - conform keys as well as values (default false)

Parameters:

  • kpred

    key pred

  • vpred

    val pred

  • options (Hash) (defaults to: {})

Returns:

  • (Spec)

See Also:



479
480
481
482
483
# File 'lib/speculation.rb', line 479

def self.hash_of(kpred, vpred, options = {})
  every_kv(kpred, vpred, :kind            => Utils.method(:hash?),
                         ns(:conform_all) => true,
                         **options)
end

.int_in(range) ⇒ Object

Returns Spec that validates ints in the given range.

Parameters:

  • range (Range<Integer>)

Returns:

  • Spec that validates ints in the given range



91
92
93
94
# File 'lib/speculation.rb', line 91

def self.int_in(range)
  spec(self.and(Integer, ->(x) { range.include?(x) }),
       :gen => ->(_) { rand(range) })
end

.invalid?(value) ⇒ Boolean

Returns true if value is the result of an unsuccessful conform.

Parameters:

  • value

    return value of a ‘conform` call

Returns:

  • (Boolean)

    true if value is the result of an unsuccessful conform



124
125
126
# File 'lib/speculation.rb', line 124

def self.invalid?(value)
  value.equal?(INVALID)
end

.keys(req: [], opt: [], req_un: [], opt_un: [], gen: nil) ⇒ Object

Creates and returns a hash validating spec. :req and :opt are both arrays of namespaced-qualified keywords (e.g. “:MyApp/foo”). The validator will ensure the :req keys are present. The :opt keys serve as documentation and may be used by the generator.

The :req key array supports ‘and_keys’ and ‘or_keys’ for key groups:

S.keys(req: [ns(:x), ns(:y), S.or_keys(ns(:secret), S.and_keys(ns(:user), ns(:pwd)))],
       opt: [ns(:z)])

There are also _un versions of :req and :opt. These allow you to connect unqualified keys to specs. In each case, fully qualfied keywords are passed, which name the specs, but unqualified keys (with the same name component) are expected and checked at conform-time, and generated during gen:

S.keys(req_un: [:"MyApp/x", :"MyApp/y"])

The above says keys :x and :y are required, and will be validated and generated by specs (if they exist) named :“MyApp/x” :“MyApp/y” respectively.

In addition, the values of all namespace-qualified keys will be validated (and possibly destructured) by any registered specs. Note: there is no support for inline value specification, by design.

Parameters:

  • req (Array<Symbol>) (defaults to: [])
  • opt (Array<Symbol>) (defaults to: [])
  • req_un (Array<Symbol>) (defaults to: [])
  • opt_un (Array<Symbol>) (defaults to: [])
  • gen (Proc) (defaults to: nil)

    generator function, which must be a proc of one arg (Rantly instance) that generates a valid value



363
364
365
366
367
# File 'lib/speculation.rb', line 363

def self.keys(req: [], opt: [], req_un: [], opt_un: [], gen: nil)
  HashSpec.new(req, opt, req_un, opt_un).tap do |spec|
    spec.gen = gen
  end
end

.merge(*preds) ⇒ Spec

Note:

Unlike ‘and’, merge can generate maps satisfying the union of the predicates.

Returns a spec that returns a conformed hash satisfying all of the specs.

Parameters:

  • preds (Array)

    hash-validating specs (e.g. ‘keys’ specs)

Returns:

  • (Spec)

    a spec that returns a conformed hash satisfying all of the specs.



401
402
403
# File 'lib/speculation.rb', line 401

def self.merge(*preds)
  MergeSpec.new(preds)
end

.nilable(pred) ⇒ Spec

Returns a spec that accepts nil and values satisfying pred.

Parameters:

  • pred

Returns:

  • (Spec)

    a spec that accepts nil and values satisfying pred



613
614
615
# File 'lib/speculation.rb', line 613

def self.nilable(pred)
  NilableSpec.new(pred)
end

.one_or_more(pred) ⇒ Hash

an array of matches

Parameters:

  • pred

Returns:

  • (Hash)

    regex op that matches one or more values matching pred. Produces



495
496
497
# File 'lib/speculation.rb', line 495

def self.one_or_more(pred)
  pcat(:predicates => [pred, rep(pred, pred, [], true)], :return_value => [])
end

.or(key_preds) ⇒ Spec

Returns a destructuring spec that returns a two element array containing the key of the first matching pred and the corresponding value. Thus the ‘key’ and ‘val’ functions can be used to refer generically to the components of the tagged return.

Examples:

S.or(even: -> (n) { n.even? }, small: -> (n) { n < 42 })

Parameters:

  • key_preds (Hash)

    Takes key+pred hash

Returns:

  • (Spec)

    a destructuring spec that returns a two element array containing the key of the first matching pred and the corresponding value. Thus the ‘key’ and ‘val’ functions can be used to refer generically to the components of the tagged return.



385
386
387
# File 'lib/speculation.rb', line 385

def self.or(key_preds)
  OrSpec.new(key_preds)
end

.or_keys(*ks) ⇒ Object

See Also:



370
371
372
# File 'lib/speculation.rb', line 370

def self.or_keys(*ks)
  [ns(:or), *ks]
end

.regex?(x) ⇒ Hash, false

Returns x if x is a (Speculation) regex op, else logical false.

Parameters:

  • x (Hash, Object)

Returns:

  • (Hash, false)

    x if x is a (Speculation) regex op, else logical false



118
119
120
# File 'lib/speculation.rb', line 118

def self.regex?(x)
  Utils.hash?(x) && x[ns(:op)] && x
end

.registryHash

Returns the registry hash.

Returns:

  • (Hash)

    the registry hash

See Also:



292
293
294
# File 'lib/speculation.rb', line 292

def self.registry
  @registry_ref.value
end

.spec(pred, gen: nil) ⇒ Spec

NOTE: it is not generally necessary to wrap predicates in spec when using ‘S.def` etc., only to attach a unique generator.

Optionally takes :gen generator function, which must be a proc of one arg (Rantly instance) that generates a valid value.

Parameters:

  • pred (Proc, Method, Set, Class, Regexp, Hash)

    Takes a single predicate. A predicate can be one of:

    • Proc, e.g. ‘-> (x) { x.even? }`, will be called with the given value

    • Method, e.g. ‘Foo.method(:bar?)`, will be called with the given value

    • Set, e.g. ‘Set[1, 2]`, will be tested whether it includes the given value

    • Class/Module, e.g. ‘String`, will be tested for case equality (is_a?) with the given value

    • Regexp, e.g. ‘/foo/`, will be tested using `===` with given value

    Can also be passed the result of one of the regex ops - cat, alt, zero_or_more, one_or_more, zero_or_one, in which case it will return a regex-conforming spec, useful when nesting an independent regex.

  • gen (Proc) (defaults to: nil)

    generator function, which must be a proc of one arg (Rantly instance) that generates a valid value.

Returns:

  • (Spec)


325
326
327
328
329
330
331
# File 'lib/speculation.rb', line 325

def self.spec(pred, gen: nil)
  if pred
    spec_impl(pred, false).tap do |spec|
      spec.gen = gen if gen
    end
  end
end

.spec?(x) ⇒ Spec, false

Returns x if x is a spec, else false.

Parameters:

  • x (Spec, Object)

Returns:

  • (Spec, false)

    x if x is a spec, else false



112
113
114
# File 'lib/speculation.rb', line 112

def self.spec?(x)
  x if x.is_a?(Spec)
end

.time_in(time_range) ⇒ Object

Returns Spec that validates times in the given range.

Parameters:

  • time_range (Range<Time>)

Returns:

  • Spec that validates times in the given range



98
99
100
101
# File 'lib/speculation.rb', line 98

def self.time_in(time_range)
  spec(self.and(Time, ->(x) { time_range.cover?(x) }),
       :gen => ->(_) { rand(time_range) })
end

.tuple(*preds) ⇒ Spec

Returns a spec for a tuple, an array where each element conforms to the corresponding pred. Each element will be referred to in paths using its ordinal.

Parameters:

  • preds (Array)

    one or more preds

Returns:

  • (Spec)

    a spec for a tuple, an array where each element conforms to the corresponding pred. Each element will be referred to in paths using its ordinal.



569
570
571
# File 'lib/speculation.rb', line 569

def self.tuple(*preds)
  TupleSpec.new(preds)
end

.valid?(spec, x) ⇒ Boolean

Returns true when x is valid for spec.

Parameters:

  • spec
  • x

Returns:

  • (Boolean)

    true when x is valid for spec.



604
605
606
607
608
609
# File 'lib/speculation.rb', line 604

def self.valid?(spec, x)
  spec = Identifier(spec)
  spec = specize(spec)

  !invalid?(spec.conform(x))
end

.with_gen(spec, gen) ⇒ Spec

Takes a spec and a one-arg generator function and returns a version of the spec that uses that generator

Parameters:

  • spec (Spec)
  • gen (Proc)

    generator proc that receives a Rantly instance

Returns:

  • (Spec)


140
141
142
143
144
145
146
# File 'lib/speculation.rb', line 140

def self.with_gen(spec, gen)
  if regex?(spec)
    spec.merge(ns(:gfn) => gen)
  else
    specize(spec).tap { |s| s.gen = gen }
  end
end

.zero_or_more(pred) ⇒ Hash

Returns regex op that matches zero or more values matching pred. Produces an array of matches iff there is at least one match.

Parameters:

  • pred

Returns:

  • (Hash)

    regex op that matches zero or more values matching pred. Produces an array of matches iff there is at least one match



488
489
490
# File 'lib/speculation.rb', line 488

def self.zero_or_more(pred)
  rep(pred, pred, [], false)
end

.zero_or_one(pred) ⇒ Hash

single value (not a collection) if matched.

Parameters:

  • pred

Returns:

  • (Hash)

    regex op that matches zero or one value matching pred. Produces a



502
503
504
# File 'lib/speculation.rb', line 502

def self.zero_or_one(pred)
  _alt([pred, accept(ns(:nil))], nil)
end