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.1".freeze

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.



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

def check_asserts
  @check_asserts
end

.coll_check_limitObject

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



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

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’



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

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.



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

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.



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

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.



535
536
537
# File 'lib/speculation.rb', line 535

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.



417
418
419
# File 'lib/speculation.rb', line 417

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

.and_keys(*ks) ⇒ Object

See Also:



398
399
400
# File 'lib/speculation.rb', line 398

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



78
79
80
81
82
83
84
85
86
87
# File 'lib/speculation.rb', line 78

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.



544
545
546
547
548
549
# File 'lib/speculation.rb', line 544

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:



485
486
487
# File 'lib/speculation.rb', line 485

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



154
155
156
157
# File 'lib/speculation.rb', line 154

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.



563
564
565
# File 'lib/speculation.rb', line 563

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.



556
557
558
# File 'lib/speculation.rb', line 556

def self.constrained(re, *preds)
  { OP => 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



128
129
130
131
# File 'lib/speculation.rb', line 128

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)


293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/speculation.rb', line 293

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:



450
451
452
453
454
455
456
# File 'lib/speculation.rb', line 450

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:



468
469
470
471
472
# File 'lib/speculation.rb', line 468

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:



648
649
650
651
652
# File 'lib/speculation.rb', line 648

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)


661
662
663
664
665
666
# File 'lib/speculation.rb', line 661

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


231
232
233
# File 'lib/speculation.rb', line 231

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.



191
192
193
194
195
# File 'lib/speculation.rb', line 191

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)



199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/speculation.rb', line 199

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



238
239
240
241
242
# File 'lib/speculation.rb', line 238

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



620
621
622
623
# File 'lib/speculation.rb', line 620

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



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/speculation.rb', line 94

def self.float_in(min: nil, max: nil, infinite: true, nan: true)
  preds = [Float]

  preds.push(->(x) { !x.nan? })      unless nan
  preds.push(->(x) { !x.infinite? }) unless infinite
  preds.push(->(x) { x <= max })     if max
  preds.push(->(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:



583
584
585
586
587
# File 'lib/speculation.rb', line 583

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)


274
275
276
277
# File 'lib/speculation.rb', line 274

def self.gen(spec, overrides = nil)
  spec = Identifier(spec)
  gensub(spec, overrides, [], 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



321
322
323
# File 'lib/speculation.rb', line 321

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:



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

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



114
115
116
117
# File 'lib/speculation.rb', line 114

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



147
148
149
# File 'lib/speculation.rb', line 147

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



386
387
388
389
390
# File 'lib/speculation.rb', line 386

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.



424
425
426
# File 'lib/speculation.rb', line 424

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



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

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



518
519
520
# File 'lib/speculation.rb', line 518

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.



408
409
410
# File 'lib/speculation.rb', line 408

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

.or_keys(*ks) ⇒ Object

See Also:



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

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



141
142
143
# File 'lib/speculation.rb', line 141

def self.regex?(x)
  Utils.hash?(x) && x[OP] && x
end

.registryHash

Returns the registry hash.

Returns:

  • (Hash)

    the registry hash

See Also:



315
316
317
# File 'lib/speculation.rb', line 315

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)


348
349
350
351
352
353
354
# File 'lib/speculation.rb', line 348

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



135
136
137
# File 'lib/speculation.rb', line 135

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



121
122
123
124
# File 'lib/speculation.rb', line 121

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.



593
594
595
# File 'lib/speculation.rb', line 593

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.



628
629
630
631
632
633
# File 'lib/speculation.rb', line 628

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)


163
164
165
166
167
168
169
# File 'lib/speculation.rb', line 163

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



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

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



525
526
527
# File 'lib/speculation.rb', line 525

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