Module: Speculation

Extended by:
NamespacedSymbols
Defined in:
lib/speculation/spec/f_spec.rb,
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/predicates.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/method_identifier.rb,
lib/speculation/spec/nilable_spec.rb,
lib/speculation/namespaced_symbols.rb,
lib/speculation/spec/predicate_spec.rb,
lib/speculation/spec/nonconforming_spec.rb

Overview

This is a Ruby translation of clojure.spec:

https://github.com/clojure/clojure/blob/master/src/clj/clojure/spec.clj

All credit belongs with Rich Hickey and contributors for their original work.

Defined Under Namespace

Modules: Gen, NamespacedSymbols, Predicates, Test Classes: Error

Constant Summary collapse

VERSION =
"0.4.1".freeze

Class Attribute Summary collapse

Class Method Summary collapse

Methods included from NamespacedSymbols

namespace, namespaced_name, ns, symbol

Class Attribute Details

.check_assertsObject

Enables or disables spec asserts. Defaults to false.


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

def check_asserts
  @check_asserts
end

.coll_check_limitObject

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


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

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'


39
40
41
# File 'lib/speculation.rb', line 39

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.


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

def fspec_iterations
  @fspec_iterations
end

.recursion_limitObject

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


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

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.


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

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.


410
411
412
# File 'lib/speculation.rb', line 410

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

.and_keys(*ks) ⇒ Object

See Also:


391
392
393
# File 'lib/speculation.rb', line 391

def self.and_keys(*ks)
  [:"Speculation/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


59
60
61
62
63
64
65
66
67
68
# File 'lib/speculation.rb', line 59

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

  ed = _explain_data(spec, [], [], [], x).merge(: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.


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

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:


476
477
478
# File 'lib/speculation.rb', line 476

def self.coll_of(pred, opts = {})
  every(pred, :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


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

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

.conformer(f, unformer = nil) ⇒ Spec

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

Parameters:

  • f (#call)

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

  • unformer (#call) (defaults to: nil)

    function that does the unform of the result of `f`

Returns:

  • (Spec)

    a spec that uses pred as a predicate/conformer.


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

def self.conformer(f, unformer = nil)
  spec_impl(f, nil, true, unformer)
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.


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

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


109
110
111
112
# File 'lib/speculation.rb', line 109

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)

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

def(key, spec)
  key = MethodIdentifier(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, nil, false)
         end

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

  key.is_a?(MethodIdentifier) ? 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 returning function, which must be a zero arg proc that returns a proc of one arg (Rantly instance) that generates a valid value.

Returns:

  • (Spec)

    spec that validates collection elements against pred

See Also:


443
444
445
446
447
# File 'lib/speculation.rb', line 443

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

  EverySpec.new(pred, opts, gen)
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) (defaults to: {})

Returns:

  • (Spec)

    spec that validates associative collections

See Also:


459
460
461
462
463
# File 'lib/speculation.rb', line 459

def self.every_kv(kpred, vpred, options = {})
  every(tuple(kpred, vpred), :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)
  • overrides (Hash)

    a generator overrides hash as per `gen`

Returns:

  • (Array)

    an array of [val, conformed_val] tuples

See Also:


646
647
648
649
650
# File 'lib/speculation.rb', line 646

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 array of triples of [args, block, ret].

Raises:

  • (ArgumentError)

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

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

  block_gen = fspec.block ? gen(fspec.block) : Utils.constantly(nil)
  gen = Gen.tuple(gen(fspec.args), block_gen)

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

.explain(spec, x) ⇒ Object

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

Parameters:

  • spec (Spec)
  • x

229
230
231
# File 'lib/speculation.rb', line 229

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 :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 :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.


187
188
189
190
191
# File 'lib/speculation.rb', line 187

def self.explain_data(spec, x)
  spec = MethodIdentifier(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)


195
196
197
198
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
# File 'lib/speculation.rb', line 195

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

  problems = Utils.sort_descending(ed.fetch(:problems)) { |prob| prob[:path] }

  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.inspect} #{PP.pp(v, String.new)}") unless k == :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


236
237
238
239
240
# File 'lib/speculation.rb', line 236

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


610
611
612
613
# File 'lib/speculation.rb', line 610

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

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

Returns that validates floats

Parameters:

  • infinite (Boolean)

    whether +/- infinity allowed (default true)

  • nan (Boolean)

    whether Flaot::NAN allowed (default true)

  • min (Boolean)

    minimum value (inclusive, default none)

  • max (Boolean)

    maximum value (inclusive, default none)

Returns:

  • (Spec)

    that validates floats


75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/speculation.rb', line 75

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

    predicate

  • ret

    predicate

  • fn

    predicate

  • block

    predicate

  • gen (Proc)

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

Returns:

  • (Spec)

See Also:


575
576
577
# File 'lib/speculation.rb', line 575

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), :gen => gen)
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)

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

def self.gen(spec, overrides = nil)
  spec = MethodIdentifier(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


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

def self.get_spec(key)
  registry[MethodIdentifier(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::Predicates.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:


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

def self.hash_of(kpred, vpred, options = {})
  every_kv(kpred, vpred, :kind        => Predicates.method(:hash?),
                         :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


95
96
97
98
# File 'lib/speculation.rb', line 95

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


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

def self.invalid?(value)
  value.equal?(:"Speculation/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>)
  • opt (Array<Symbol>)
  • req_un (Array<Symbol>)
  • opt_un (Array<Symbol>)
  • gen (Proc)

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


381
382
383
# File 'lib/speculation.rb', line 381

def self.keys(req: [], opt: [], req_un: [], opt_un: [], gen: nil)
  HashSpec.new(req, opt, req_un, opt_un, gen)
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.


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

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


627
628
629
# File 'lib/speculation.rb', line 627

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

.nonconforming(spec) ⇒ Spec

Returns a spec that has the same properies as the given spec, except `conform` will return the original (not the conformed) value. Note, will specize regex ops.

Parameters:

  • spec

Returns:

  • (Spec)

    a spec that has the same properies as the given spec, except `conform` will return the original (not the conformed) value. Note, will specize regex ops.


635
636
637
# File 'lib/speculation.rb', line 635

def self.nonconforming(spec)
  NonconformingSpec.new(spec)
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


509
510
511
# File 'lib/speculation.rb', line 509

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.


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

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

.or_keys(*ks) ⇒ Object

See Also:


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

def self.or_keys(*ks)
  [:"Speculation/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


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

def self.regex?(x)
  x.is_a?(Hash) && x[:op] && x
end

.registryHash

Returns the registry hash

Returns:

  • (Hash)

    the registry hash

See Also:


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

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 no-arg proc that returns a generator (proc that receives a 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)

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

Returns:

  • (Spec)

347
348
349
# File 'lib/speculation.rb', line 347

def self.spec(pred, gen: nil)
  spec_impl(pred, gen, false) if pred
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


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

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


102
103
104
105
# File 'lib/speculation.rb', line 102

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.


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

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

.unform(spec, value) ⇒ Object

Returns value with conform destructuring undone

Parameters:

  • spec (Spec)

Returns:

  • value with conform destructuring undone


144
145
146
# File 'lib/speculation.rb', line 144

def self.unform(spec, value)
  specize(spec).unform(value)
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.


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

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

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

.with_gen(spec, &gen) ⇒ Spec

Takes a spec and a no-arg generator returning block and returns a version of the spec that uses

that generator

Parameters:

  • spec (Spec)

Yield Returns:

  • Rantly generator

Returns:

  • (Spec)

153
154
155
156
157
158
159
160
161
162
163
# File 'lib/speculation.rb', line 153

def self.with_gen(spec, &gen)
  if gen && !gen.arity.zero?
    raise ArgumentError, "gen must be a no-arg block that returns a generator"
  end

  if regex?(spec)
    spec.merge(:gfn => gen)
  else
    specize(spec).with_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


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

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


516
517
518
# File 'lib/speculation.rb', line 516

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