dry-validation 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.rspec +3 -0
- data/.rubocop.yml +16 -0
- data/.rubocop_todo.yml +7 -0
- data/.travis.yml +29 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +11 -0
- data/LICENSE +20 -0
- data/README.md +297 -0
- data/Rakefile +12 -0
- data/config/errors.yml +35 -0
- data/dry-validation.gemspec +25 -0
- data/examples/basic.rb +21 -0
- data/examples/nested.rb +30 -0
- data/examples/rule_ast.rb +33 -0
- data/lib/dry-validation.rb +1 -0
- data/lib/dry/validation.rb +12 -0
- data/lib/dry/validation/error.rb +43 -0
- data/lib/dry/validation/error_compiler.rb +116 -0
- data/lib/dry/validation/messages.rb +71 -0
- data/lib/dry/validation/predicate.rb +39 -0
- data/lib/dry/validation/predicate_set.rb +22 -0
- data/lib/dry/validation/predicates.rb +88 -0
- data/lib/dry/validation/result.rb +64 -0
- data/lib/dry/validation/rule.rb +125 -0
- data/lib/dry/validation/rule_compiler.rb +57 -0
- data/lib/dry/validation/schema.rb +74 -0
- data/lib/dry/validation/schema/definition.rb +15 -0
- data/lib/dry/validation/schema/key.rb +39 -0
- data/lib/dry/validation/schema/rule.rb +28 -0
- data/lib/dry/validation/schema/value.rb +31 -0
- data/lib/dry/validation/version.rb +5 -0
- data/rakelib/rubocop.rake +18 -0
- data/spec/fixtures/errors.yml +4 -0
- data/spec/integration/custom_error_messages_spec.rb +35 -0
- data/spec/integration/custom_predicates_spec.rb +57 -0
- data/spec/integration/validation_spec.rb +118 -0
- data/spec/shared/predicates.rb +31 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/unit/error_compiler_spec.rb +165 -0
- data/spec/unit/predicate_spec.rb +37 -0
- data/spec/unit/predicates/empty_spec.rb +38 -0
- data/spec/unit/predicates/eql_spec.rb +21 -0
- data/spec/unit/predicates/exclusion_spec.rb +35 -0
- data/spec/unit/predicates/filled_spec.rb +38 -0
- data/spec/unit/predicates/format_spec.rb +21 -0
- data/spec/unit/predicates/gt_spec.rb +40 -0
- data/spec/unit/predicates/gteq_spec.rb +40 -0
- data/spec/unit/predicates/inclusion_spec.rb +35 -0
- data/spec/unit/predicates/int_spec.rb +34 -0
- data/spec/unit/predicates/key_spec.rb +29 -0
- data/spec/unit/predicates/lt_spec.rb +40 -0
- data/spec/unit/predicates/lteq_spec.rb +40 -0
- data/spec/unit/predicates/max_size_spec.rb +49 -0
- data/spec/unit/predicates/min_size_spec.rb +49 -0
- data/spec/unit/predicates/nil_spec.rb +28 -0
- data/spec/unit/predicates/size_spec.rb +49 -0
- data/spec/unit/predicates/str_spec.rb +32 -0
- data/spec/unit/rule/each_spec.rb +20 -0
- data/spec/unit/rule/key_spec.rb +27 -0
- data/spec/unit/rule/set_spec.rb +32 -0
- data/spec/unit/rule/value_spec.rb +42 -0
- data/spec/unit/rule_compiler_spec.rb +86 -0
- metadata +230 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require File.expand_path('../lib/dry/validation/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |spec|
|
5
|
+
spec.name = 'dry-validation'
|
6
|
+
spec.version = Dry::Validation::VERSION
|
7
|
+
spec.authors = ['Andy Holland', 'Piotr Solnica']
|
8
|
+
spec.email = ['andyholland1991@aol.com', 'piotr.solnica@gmail.com']
|
9
|
+
spec.summary = 'A simple validation library'
|
10
|
+
spec.homepage = 'https://github.com/dryrb/dry-validation'
|
11
|
+
spec.license = 'MIT'
|
12
|
+
|
13
|
+
spec.files = `git ls-files -z`.split("\x0")
|
14
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
15
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
16
|
+
spec.require_paths = ['lib']
|
17
|
+
|
18
|
+
spec.add_runtime_dependency 'dry-configurable', '~> 0.1'
|
19
|
+
spec.add_runtime_dependency 'dry-container', '~> 0.2', '>= 0.2.6'
|
20
|
+
spec.add_runtime_dependency 'dry-equalizer', '~> 0.2'
|
21
|
+
|
22
|
+
spec.add_development_dependency 'bundler'
|
23
|
+
spec.add_development_dependency 'rake'
|
24
|
+
spec.add_development_dependency 'rspec'
|
25
|
+
end
|
data/examples/basic.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'dry-validation'
|
2
|
+
|
3
|
+
class Schema < Dry::Validation::Schema
|
4
|
+
key(:email) { |email| email.filled? }
|
5
|
+
|
6
|
+
key(:age) do |age|
|
7
|
+
age.int? & age.gt?(18)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
schema = Schema.new
|
12
|
+
|
13
|
+
errors = schema.messages(email: 'jane@doe.org', age: 19)
|
14
|
+
|
15
|
+
puts errors.inspect
|
16
|
+
# []
|
17
|
+
|
18
|
+
errors = schema.messages(email: nil, age: 19)
|
19
|
+
|
20
|
+
puts errors.inspect
|
21
|
+
# [[:email, ["email must be filled"]]]
|
data/examples/nested.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'dry-validation'
|
2
|
+
|
3
|
+
class Schema < Dry::Validation::Schema
|
4
|
+
key(:address) do |address|
|
5
|
+
address.key(:city) do |city|
|
6
|
+
city.min_size?(3)
|
7
|
+
end
|
8
|
+
|
9
|
+
address.key(:street) do |street|
|
10
|
+
street.filled?
|
11
|
+
end
|
12
|
+
|
13
|
+
address.key(:country) do |country|
|
14
|
+
country.key(:name, &:filled?)
|
15
|
+
country.key(:code, &:filled?)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
schema = Schema.new
|
21
|
+
|
22
|
+
errors = schema.messages({})
|
23
|
+
|
24
|
+
puts errors.inspect
|
25
|
+
#<Dry::Validation::Error::Set:0x007fc4f89c4360 @errors=[#<Dry::Validation::Error:0x007fc4f89c4108 @result=#<Dry::Validation::Result::Value success?=false input=nil rule=#<Dry::Validation::Rule::Key name=:address predicate=#<Dry::Validation::Predicate id=:key?>>>>]>
|
26
|
+
|
27
|
+
errors = schema.messages(address: { city: 'NYC' })
|
28
|
+
|
29
|
+
puts errors.inspect
|
30
|
+
#<Dry::Validation::Error::Set:0x007fd151189b18 @errors=[#<Dry::Validation::Error:0x007fd151188e20 @result=#<Dry::Validation::Result::Set success?=false input={:city=>"NYC"} rule=#<Dry::Validation::Rule::Set name=:address predicate=[#<Dry::Validation::Rule::Conjunction left=#<Dry::Validation::Rule::Key name=:city predicate=#<Dry::Validation::Predicate id=:key?>> right=#<Dry::Validation::Rule::Value name=:city predicate=#<Dry::Validation::Predicate id=:min_size?>>>, #<Dry::Validation::Rule::Conjunction left=#<Dry::Validation::Rule::Key name=:street predicate=#<Dry::Validation::Predicate id=:key?>> right=#<Dry::Validation::Rule::Value name=:street predicate=#<Dry::Validation::Predicate id=:filled?>>>, #<Dry::Validation::Rule::Conjunction left=#<Dry::Validation::Rule::Key name=:country predicate=#<Dry::Validation::Predicate id=:key?>> right=#<Dry::Validation::Rule::Set name=:country predicate=[#<Dry::Validation::Rule::Conjunction left=#<Dry::Validation::Rule::Key name=:name predicate=#<Dry::Validation::Predicate id=:key?>> right=#<Dry::Validation::Rule::Value name=:name predicate=#<Dry::Validation::Predicate id=:filled?>>>, #<Dry::Validation::Rule::Conjunction left=#<Dry::Validation::Rule::Key name=:code predicate=#<Dry::Validation::Predicate id=:key?>> right=#<Dry::Validation::Rule::Value name=:code predicate=#<Dry::Validation::Predicate id=:filled?>>>]>>]>>>]>
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'dry-validation'
|
2
|
+
|
3
|
+
ast = [
|
4
|
+
[
|
5
|
+
:and,
|
6
|
+
[
|
7
|
+
[:key, [:age, [:predicate, [:key?, []]]]],
|
8
|
+
[
|
9
|
+
:and,
|
10
|
+
[
|
11
|
+
[:val, [:age, [:predicate, [:filled?, []]]]],
|
12
|
+
[:val, [:age, [:predicate, [:gt?, [18]]]]]
|
13
|
+
]
|
14
|
+
]
|
15
|
+
]
|
16
|
+
]
|
17
|
+
]
|
18
|
+
|
19
|
+
compiler = Dry::Validation::RuleCompiler.new(Dry::Validation::Predicates)
|
20
|
+
|
21
|
+
rules = compiler.call(ast)
|
22
|
+
|
23
|
+
puts rules.inspect
|
24
|
+
# [
|
25
|
+
# #<Dry::Validation::Rule::Conjunction
|
26
|
+
# left=#<Dry::Validation::Rule::Key name=:age predicate=#<Dry::Validation::Predicate id=:key?>>
|
27
|
+
# right=#<Dry::Validation::Rule::Conjunction
|
28
|
+
# left=#<Dry::Validation::Rule::Value name=:age predicate=#<Dry::Validation::Predicate id=:filled?>>
|
29
|
+
# right=#<Dry::Validation::Rule::Value name=:age predicate=#<Dry::Validation::Predicate id=:gt?>>>>
|
30
|
+
# ]
|
31
|
+
|
32
|
+
puts rules.map(&:to_ary).inspect
|
33
|
+
# [[:and, [:key, [:age, [:predicate, [:key?, [:age]]]]], [[:and, [:val, [:age, [:predicate, [:filled?, []]]]], [[:val, [:age, [:predicate, [:gt?, [18]]]]]]]]]]
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'dry/validation'
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Dry
|
2
|
+
module Validation
|
3
|
+
class Error
|
4
|
+
class Set
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
attr_reader :errors
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@errors = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def each(&block)
|
14
|
+
errors.each(&block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def empty?
|
18
|
+
errors.empty?
|
19
|
+
end
|
20
|
+
|
21
|
+
def <<(error)
|
22
|
+
errors << error
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_ary
|
26
|
+
errors.map { |error| error.to_ary }
|
27
|
+
end
|
28
|
+
alias_method :to_a, :to_ary
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_reader :result
|
32
|
+
|
33
|
+
def initialize(result)
|
34
|
+
@result = result
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_ary
|
38
|
+
[:error, result.to_ary]
|
39
|
+
end
|
40
|
+
alias_method :to_a, :to_ary
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module Dry
|
2
|
+
module Validation
|
3
|
+
class ErrorCompiler
|
4
|
+
attr_reader :messages
|
5
|
+
|
6
|
+
def initialize(messages)
|
7
|
+
@messages = messages
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(ast)
|
11
|
+
ast.map { |node| visit(node) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def visit(node, *args)
|
15
|
+
__send__(:"visit_#{node[0]}", node[1], *args)
|
16
|
+
end
|
17
|
+
|
18
|
+
def visit_error(error)
|
19
|
+
visit(error)
|
20
|
+
end
|
21
|
+
|
22
|
+
def visit_input(input, *args)
|
23
|
+
name, value, rules = input
|
24
|
+
[name, rules.map { |rule| visit(rule, name, value) }]
|
25
|
+
end
|
26
|
+
|
27
|
+
def visit_key(rule, name, value)
|
28
|
+
_, predicate = rule
|
29
|
+
visit(predicate, value, name)
|
30
|
+
end
|
31
|
+
|
32
|
+
def visit_val(rule, name, value)
|
33
|
+
name, predicate = rule
|
34
|
+
visit(predicate, value, name)
|
35
|
+
end
|
36
|
+
|
37
|
+
def visit_predicate(predicate, value, name)
|
38
|
+
messages.lookup(predicate[0], name, predicate[1][0]) % visit(predicate, value).merge(name: name)
|
39
|
+
end
|
40
|
+
|
41
|
+
def visit_key?(*args, value)
|
42
|
+
{ name: args[0][0] }
|
43
|
+
end
|
44
|
+
|
45
|
+
def visit_empty?(*args, value)
|
46
|
+
{ value: value }
|
47
|
+
end
|
48
|
+
|
49
|
+
def visit_exclusion?(*args, value)
|
50
|
+
{ list: args[0][0].join(', ') }
|
51
|
+
end
|
52
|
+
|
53
|
+
def visit_inclusion?(*args, value)
|
54
|
+
{ list: args[0][0].join(', ') }
|
55
|
+
end
|
56
|
+
|
57
|
+
def visit_gt?(*args, value)
|
58
|
+
{ num: args[0][0], value: value }
|
59
|
+
end
|
60
|
+
|
61
|
+
def visit_gteq?(*args, value)
|
62
|
+
{ num: args[0][0], value: value }
|
63
|
+
end
|
64
|
+
|
65
|
+
def visit_lt?(*args, value)
|
66
|
+
{ num: args[0][0], value: value }
|
67
|
+
end
|
68
|
+
|
69
|
+
def visit_lteq?(*args, value)
|
70
|
+
{ num: args[0][0], value: value }
|
71
|
+
end
|
72
|
+
|
73
|
+
def visit_int?(*args, value)
|
74
|
+
{ num: args[0][0], value: value }
|
75
|
+
end
|
76
|
+
|
77
|
+
def visit_max_size?(*args, value)
|
78
|
+
{ num: args[0][0], value: value }
|
79
|
+
end
|
80
|
+
|
81
|
+
def visit_min_size?(*args, value)
|
82
|
+
{ num: args[0][0], value: value }
|
83
|
+
end
|
84
|
+
|
85
|
+
def visit_eql?(*args, value)
|
86
|
+
{ eql_value: args[0][0], value: value }
|
87
|
+
end
|
88
|
+
|
89
|
+
def visit_size?(*args, value)
|
90
|
+
num = args[0][0]
|
91
|
+
|
92
|
+
if num.is_a?(Range)
|
93
|
+
{ left: num.first, right: num.last, value: value }
|
94
|
+
else
|
95
|
+
{ num: args[0][0], value: value }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def visit_str?(*args, value)
|
100
|
+
{ value: value }
|
101
|
+
end
|
102
|
+
|
103
|
+
def visit_format?(*args, value)
|
104
|
+
{}
|
105
|
+
end
|
106
|
+
|
107
|
+
def visit_nil?(*args, value)
|
108
|
+
{}
|
109
|
+
end
|
110
|
+
|
111
|
+
def visit_filled?(*args)
|
112
|
+
{}
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
module Dry
|
5
|
+
module Validation
|
6
|
+
class Messages
|
7
|
+
DEFAULT_PATH = Pathname(__dir__).join('../../../config/errors.yml').freeze
|
8
|
+
|
9
|
+
attr_reader :data
|
10
|
+
|
11
|
+
def self.default
|
12
|
+
load(DEFAULT_PATH)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.load(path)
|
16
|
+
new(load_yaml(path))
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.load_yaml(path)
|
20
|
+
symbolize_keys(YAML.load_file(path))
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.symbolize_keys(hash)
|
24
|
+
hash.each_with_object({}) do |(k, v), r|
|
25
|
+
r[k.to_sym] = v.is_a?(Hash) ? symbolize_keys(v) : v
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Namespaced
|
30
|
+
attr_reader :namespace, :fallback
|
31
|
+
|
32
|
+
def initialize(namespace, fallback)
|
33
|
+
@namespace = namespace
|
34
|
+
@fallback = fallback
|
35
|
+
end
|
36
|
+
|
37
|
+
def lookup(*args)
|
38
|
+
namespace.lookup(*args) { fallback.lookup(*args) }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize(data)
|
43
|
+
@data = data
|
44
|
+
end
|
45
|
+
|
46
|
+
def merge(overrides)
|
47
|
+
if overrides.is_a?(Hash)
|
48
|
+
self.class.new(data.merge(overrides))
|
49
|
+
else
|
50
|
+
self.class.new(data.merge(Messages.load_yaml(overrides)))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def namespaced(namespace)
|
55
|
+
Namespaced.new(Messages.new(data[namespace]), self)
|
56
|
+
end
|
57
|
+
|
58
|
+
def lookup(identifier, key, arg, &block)
|
59
|
+
message = data.fetch(:attributes, {}).fetch(key, {}).fetch(identifier) do
|
60
|
+
data.fetch(identifier, &block)
|
61
|
+
end
|
62
|
+
|
63
|
+
if message.is_a?(Hash)
|
64
|
+
message.fetch(arg.class.name.downcase.to_sym, message.fetch(:default))
|
65
|
+
else
|
66
|
+
message
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Dry
|
2
|
+
module Validation
|
3
|
+
def self.Predicate(block)
|
4
|
+
case block
|
5
|
+
when Method then Predicate.new(block.name, &block)
|
6
|
+
else raise ArgumentError, 'predicate needs an :id'
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class Predicate
|
11
|
+
include Dry::Equalizer(:id)
|
12
|
+
|
13
|
+
attr_reader :id, :args, :fn
|
14
|
+
|
15
|
+
def initialize(id, *args, &block)
|
16
|
+
@id = id
|
17
|
+
@fn = block
|
18
|
+
@args = args
|
19
|
+
end
|
20
|
+
|
21
|
+
def call(*args)
|
22
|
+
fn.(*args)
|
23
|
+
end
|
24
|
+
|
25
|
+
def negation
|
26
|
+
self.class.new(:"not_#{id}") { |input| !fn.(input) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def curry(*args)
|
30
|
+
self.class.new(id, *args, &fn.curry.(*args))
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_ary
|
34
|
+
[:predicate, [id, args]]
|
35
|
+
end
|
36
|
+
alias_method :to_a, :to_ary
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'dry/validation/predicate'
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Validation
|
5
|
+
module PredicateSet
|
6
|
+
module Methods
|
7
|
+
def predicate(name, &block)
|
8
|
+
register(name) { Predicate.new(name, &block) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def import(predicate_set)
|
12
|
+
merge(predicate_set)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.extended(other)
|
17
|
+
super
|
18
|
+
other.extend(Methods, Dry::Container::Mixin)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'dry/validation/predicate_set'
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Validation
|
5
|
+
module Predicates
|
6
|
+
extend PredicateSet
|
7
|
+
|
8
|
+
def self.included(other)
|
9
|
+
super
|
10
|
+
other.extend(PredicateSet)
|
11
|
+
other.import(self)
|
12
|
+
end
|
13
|
+
|
14
|
+
predicate(:nil?) do |input|
|
15
|
+
input.nil?
|
16
|
+
end
|
17
|
+
|
18
|
+
predicate(:key?) do |name, input|
|
19
|
+
input.key?(name)
|
20
|
+
end
|
21
|
+
|
22
|
+
predicate(:empty?) do |input|
|
23
|
+
case input
|
24
|
+
when String, Array, Hash then input.empty?
|
25
|
+
when nil then true
|
26
|
+
else
|
27
|
+
false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
predicate(:filled?) do |input|
|
32
|
+
!self[:empty?].(input)
|
33
|
+
end
|
34
|
+
|
35
|
+
predicate(:int?) do |input|
|
36
|
+
input.is_a?(Fixnum)
|
37
|
+
end
|
38
|
+
|
39
|
+
predicate(:str?) do |input|
|
40
|
+
input.is_a?(String)
|
41
|
+
end
|
42
|
+
|
43
|
+
predicate(:lt?) do |num, input|
|
44
|
+
input < num
|
45
|
+
end
|
46
|
+
|
47
|
+
predicate(:gt?) do |num, input|
|
48
|
+
input > num
|
49
|
+
end
|
50
|
+
|
51
|
+
predicate(:lteq?) do |num, input|
|
52
|
+
!self[:gt?].(num, input)
|
53
|
+
end
|
54
|
+
|
55
|
+
predicate(:gteq?) do |num, input|
|
56
|
+
!self[:lt?].(num, input)
|
57
|
+
end
|
58
|
+
|
59
|
+
predicate(:size?) do |num, input|
|
60
|
+
input.size == num
|
61
|
+
end
|
62
|
+
|
63
|
+
predicate(:min_size?) do |num, input|
|
64
|
+
input.size >= num
|
65
|
+
end
|
66
|
+
|
67
|
+
predicate(:max_size?) do |num, input|
|
68
|
+
input.size <= num
|
69
|
+
end
|
70
|
+
|
71
|
+
predicate(:inclusion?) do |list, input|
|
72
|
+
list.include?(input)
|
73
|
+
end
|
74
|
+
|
75
|
+
predicate(:exclusion?) do |list, input|
|
76
|
+
!self[:inclusion?].(list, input)
|
77
|
+
end
|
78
|
+
|
79
|
+
predicate(:eql?) do |left, right|
|
80
|
+
left.eql?(right)
|
81
|
+
end
|
82
|
+
|
83
|
+
predicate(:format?) do |regex, input|
|
84
|
+
!regex.match(input).nil?
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|