morpher 0.2.6
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of morpher might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/.circle.yml +6 -0
- data/.gitignore +5 -0
- data/.rspec +4 -0
- data/.rubocop.yml +8 -0
- data/Changelog.md +60 -0
- data/Gemfile +3 -0
- data/LICENSE +20 -0
- data/README.md +56 -0
- data/Rakefile +95 -0
- data/circle.yml +7 -0
- data/config/devtools.yml +2 -0
- data/config/flay.yml +3 -0
- data/config/flog.yml +2 -0
- data/config/heckle.yml +3 -0
- data/config/mutant.yml +8 -0
- data/config/reek.yml +109 -0
- data/config/rubocop.yml +138 -0
- data/config/yardstick.yml +2 -0
- data/examples/README.md +13 -0
- data/examples/a.rb +25 -0
- data/examples/b.rb +35 -0
- data/lib/morpher.rb +111 -0
- data/lib/morpher/compiler.rb +17 -0
- data/lib/morpher/compiler/emitter.rb +82 -0
- data/lib/morpher/compiler/error.rb +84 -0
- data/lib/morpher/compiler/evaluator.rb +63 -0
- data/lib/morpher/compiler/evaluator/emitter.rb +224 -0
- data/lib/morpher/compiler/preprocessor.rb +29 -0
- data/lib/morpher/compiler/preprocessor/emitter.rb +54 -0
- data/lib/morpher/compiler/preprocessor/emitter/anima.rb +69 -0
- data/lib/morpher/compiler/preprocessor/emitter/boolean.rb +31 -0
- data/lib/morpher/compiler/preprocessor/emitter/key.rb +87 -0
- data/lib/morpher/compiler/preprocessor/emitter/noop.rb +45 -0
- data/lib/morpher/compiler/preprocessor/emitter/param.rb +50 -0
- data/lib/morpher/evaluation.rb +118 -0
- data/lib/morpher/evaluator.rb +40 -0
- data/lib/morpher/evaluator/binary.rb +46 -0
- data/lib/morpher/evaluator/nary.rb +97 -0
- data/lib/morpher/evaluator/nullary.rb +92 -0
- data/lib/morpher/evaluator/nullary/parameterized.rb +48 -0
- data/lib/morpher/evaluator/predicate.rb +22 -0
- data/lib/morpher/evaluator/predicate/boolean.rb +76 -0
- data/lib/morpher/evaluator/predicate/contradiction.rb +36 -0
- data/lib/morpher/evaluator/predicate/eql.rb +50 -0
- data/lib/morpher/evaluator/predicate/negation.rb +52 -0
- data/lib/morpher/evaluator/predicate/primitive.rb +49 -0
- data/lib/morpher/evaluator/predicate/tautology.rb +36 -0
- data/lib/morpher/evaluator/transformer.rb +75 -0
- data/lib/morpher/evaluator/transformer/attribute.rb +25 -0
- data/lib/morpher/evaluator/transformer/block.rb +81 -0
- data/lib/morpher/evaluator/transformer/coerce.rb +166 -0
- data/lib/morpher/evaluator/transformer/custom.rb +34 -0
- data/lib/morpher/evaluator/transformer/domain.rb +86 -0
- data/lib/morpher/evaluator/transformer/domain/attribute_accessors.rb +60 -0
- data/lib/morpher/evaluator/transformer/domain/attribute_hash.rb +52 -0
- data/lib/morpher/evaluator/transformer/domain/instance_variables.rb +60 -0
- data/lib/morpher/evaluator/transformer/domain/param.rb +54 -0
- data/lib/morpher/evaluator/transformer/guard.rb +62 -0
- data/lib/morpher/evaluator/transformer/hash_transform.rb +149 -0
- data/lib/morpher/evaluator/transformer/input.rb +37 -0
- data/lib/morpher/evaluator/transformer/key.rb +86 -0
- data/lib/morpher/evaluator/transformer/map.rb +100 -0
- data/lib/morpher/evaluator/transformer/merge.rb +25 -0
- data/lib/morpher/evaluator/transformer/static.rb +27 -0
- data/lib/morpher/evaluator/unary.rb +79 -0
- data/lib/morpher/node_helpers.rb +19 -0
- data/lib/morpher/printer.rb +233 -0
- data/lib/morpher/printer/mixin.rb +58 -0
- data/lib/morpher/registry.rb +51 -0
- data/lib/morpher/type_lookup.rb +51 -0
- data/morpher.gemspec +29 -0
- data/spec/integration_spec.rb +184 -0
- data/spec/rcov.opts +7 -0
- data/spec/shared/evaluator_behavior.rb +155 -0
- data/spec/spec_helper.rb +36 -0
- data/spec/support/ice_nine_config.rb +8 -0
- data/spec/support/let_mock_helper.rb +8 -0
- data/spec/support/strip_helper.rb +12 -0
- data/spec/unit/morpher/compiler/preprocessor_spec.rb +46 -0
- data/spec/unit/morpher/evaluator/nullary/parameterized_spec.rb +25 -0
- data/spec/unit/morpher/evaluator/predicate/boolean/and_spec.rb +11 -0
- data/spec/unit/morpher/evaluator/predicate/boolean/or_spec.rb +26 -0
- data/spec/unit/morpher/evaluator/predicate/boolean/xor_spec.rb +26 -0
- data/spec/unit/morpher/evaluator/predicate/contrandiction_spec.rb +7 -0
- data/spec/unit/morpher/evaluator/predicate/eql_spec.rb +11 -0
- data/spec/unit/morpher/evaluator/predicate/negation_spec.rb +10 -0
- data/spec/unit/morpher/evaluator/predicate/primitive_spec.rb +17 -0
- data/spec/unit/morpher/evaluator/predicate/tautology_spec.rb +7 -0
- data/spec/unit/morpher/evaluator/transformer/attribute_spec.rb +9 -0
- data/spec/unit/morpher/evaluator/transformer/block_spec.rb +92 -0
- data/spec/unit/morpher/evaluator/transformer/coerce/parse_int_spec.rb +23 -0
- data/spec/unit/morpher/evaluator/transformer/custom_spec.rb +13 -0
- data/spec/unit/morpher/evaluator/transformer/domain/attribute_accessors_spec.rb +48 -0
- data/spec/unit/morpher/evaluator/transformer/domain/attribute_hash_spec.rb +40 -0
- data/spec/unit/morpher/evaluator/transformer/domain/instance_variables_spec.rb +47 -0
- data/spec/unit/morpher/evaluator/transformer/guard_spec.rb +12 -0
- data/spec/unit/morpher/evaluator/transformer/hash_transform_spec.rb +47 -0
- data/spec/unit/morpher/evaluator/transformer/input_spec.rb +11 -0
- data/spec/unit/morpher/evaluator/transformer/map_spec.rb +25 -0
- data/spec/unit/morpher/evaluator/transformer/static_spec.rb +10 -0
- data/spec/unit/morpher/evaluator_spec.rb +15 -0
- data/spec/unit/morpher/printer_spec.rb +21 -0
- data/spec/unit/morpher/registry_spec.rb +11 -0
- data/spec/unit/morpher_spec.rb +53 -0
- metadata +302 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
module Morpher
|
2
|
+
class Printer
|
3
|
+
|
4
|
+
# Printer behavior mixin
|
5
|
+
module Mixin
|
6
|
+
|
7
|
+
# Class level methods to be mixed in
|
8
|
+
module ClassMethods
|
9
|
+
|
10
|
+
# Register printer block for class
|
11
|
+
#
|
12
|
+
# @return [self]
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
#
|
16
|
+
def printer(&block)
|
17
|
+
REGISTRY[self] = block
|
18
|
+
end
|
19
|
+
|
20
|
+
end # ClassMethods
|
21
|
+
|
22
|
+
# Instance level methods to be mixed in
|
23
|
+
module InstanceMethods
|
24
|
+
|
25
|
+
# Return description
|
26
|
+
#
|
27
|
+
# @return [String]
|
28
|
+
#
|
29
|
+
# @api private
|
30
|
+
#
|
31
|
+
def description
|
32
|
+
io = StringIO.new
|
33
|
+
Printer.run(self, io)
|
34
|
+
io.rewind
|
35
|
+
io.read
|
36
|
+
end
|
37
|
+
|
38
|
+
end # InstanceMethods
|
39
|
+
|
40
|
+
# Callback whem module gets included
|
41
|
+
#
|
42
|
+
# @param [Class, Module] descendant
|
43
|
+
#
|
44
|
+
# @return [undefined]
|
45
|
+
#
|
46
|
+
# @api private
|
47
|
+
#
|
48
|
+
def self.included(descendant)
|
49
|
+
descendant.class_eval do
|
50
|
+
extend ClassMethods
|
51
|
+
include InstanceMethods
|
52
|
+
end
|
53
|
+
end
|
54
|
+
private_class_method :included
|
55
|
+
|
56
|
+
end # Mixin
|
57
|
+
end # Printer
|
58
|
+
end # Morpher
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Morpher
|
2
|
+
# Mixin for to provide a node registry
|
3
|
+
module Registry
|
4
|
+
|
5
|
+
# Hook called when module is included
|
6
|
+
#
|
7
|
+
# @param [Module, Class] descendant
|
8
|
+
#
|
9
|
+
# @return [undefined]
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
#
|
13
|
+
def self.included(descendant)
|
14
|
+
descendant.const_set(:REGISTRY, {})
|
15
|
+
descendant.class_eval do
|
16
|
+
extend ClassMethods
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Return node type
|
21
|
+
#
|
22
|
+
# @return [Symbol]
|
23
|
+
#
|
24
|
+
# @api private
|
25
|
+
#
|
26
|
+
def type
|
27
|
+
self.class::TYPE
|
28
|
+
end
|
29
|
+
|
30
|
+
# Methods to get mixed in at singleton level
|
31
|
+
module ClassMethods
|
32
|
+
|
33
|
+
# Register evaluator under name
|
34
|
+
#
|
35
|
+
# TODO: Disallow duplicate registration under same name
|
36
|
+
#
|
37
|
+
# @param [Symbol] name
|
38
|
+
#
|
39
|
+
# @return [undefined]
|
40
|
+
#
|
41
|
+
# @api private
|
42
|
+
#
|
43
|
+
def register(name)
|
44
|
+
const_set(:TYPE, name)
|
45
|
+
self::REGISTRY[name] = self
|
46
|
+
end
|
47
|
+
|
48
|
+
end # ClassMethods
|
49
|
+
|
50
|
+
end # Registry
|
51
|
+
end # Morpher
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Morpher
|
2
|
+
|
3
|
+
# Type lookup via registry and superclass chaining
|
4
|
+
#
|
5
|
+
# TODO: Cache results.
|
6
|
+
#
|
7
|
+
class TypeLookup
|
8
|
+
include Adamantium::Flat, Concord.new(:registry)
|
9
|
+
|
10
|
+
# Error raised on compiling unknown nodes
|
11
|
+
class TypeNotFoundError < RuntimeError
|
12
|
+
include Concord.new(:type)
|
13
|
+
|
14
|
+
# Return exception error message
|
15
|
+
#
|
16
|
+
# @return [String]
|
17
|
+
#
|
18
|
+
# @api private
|
19
|
+
#
|
20
|
+
def message
|
21
|
+
"Node type: #{type.inspect} is unknown"
|
22
|
+
end
|
23
|
+
|
24
|
+
end # TypeNotFoundError
|
25
|
+
|
26
|
+
# Perform type lookup
|
27
|
+
#
|
28
|
+
# @param [Object] object
|
29
|
+
#
|
30
|
+
# @return [Object]
|
31
|
+
# if found
|
32
|
+
#
|
33
|
+
# @raise [TypeNotFoundError]
|
34
|
+
# otherwise
|
35
|
+
#
|
36
|
+
# @api private
|
37
|
+
#
|
38
|
+
def call(object)
|
39
|
+
current = target = object.class
|
40
|
+
while current != Object
|
41
|
+
if registry.key?(current)
|
42
|
+
return registry.fetch(current)
|
43
|
+
end
|
44
|
+
current = current.superclass
|
45
|
+
end
|
46
|
+
|
47
|
+
fail TypeNotFoundError, target
|
48
|
+
end
|
49
|
+
|
50
|
+
end # TypeLookup
|
51
|
+
end # Morpher
|
data/morpher.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
Gem::Specification.new do |gem|
|
2
|
+
gem.name = 'morpher'
|
3
|
+
gem.version = '0.2.6'
|
4
|
+
gem.authors = ['Markus Schirp']
|
5
|
+
gem.email = ['mbj@schirp-dso.com']
|
6
|
+
gem.description = 'Composeable predicates on POROs'
|
7
|
+
gem.summary = gem.description
|
8
|
+
gem.homepage = 'https://github.com/mbj/morpher'
|
9
|
+
gem.license = 'MIT'
|
10
|
+
|
11
|
+
gem.require_paths = %w[lib]
|
12
|
+
gem.files = `git ls-files`.split("\n")
|
13
|
+
gem.test_files = `git ls-files -- spec/{unit,integration}`.split("\n")
|
14
|
+
gem.extra_rdoc_files = %w[LICENSE]
|
15
|
+
gem.license = 'MIT'
|
16
|
+
|
17
|
+
gem.required_ruby_version = ['>= 2.1']
|
18
|
+
|
19
|
+
gem.add_runtime_dependency('abstract_type', '~> 0.0.7')
|
20
|
+
gem.add_runtime_dependency('adamantium', '~> 0.2.0')
|
21
|
+
gem.add_runtime_dependency('anima', '~> 0.3.0')
|
22
|
+
gem.add_runtime_dependency('ast', '~> 2.2')
|
23
|
+
gem.add_runtime_dependency('concord', '~> 0.1.5')
|
24
|
+
gem.add_runtime_dependency('equalizer', '~> 0.0.9')
|
25
|
+
gem.add_runtime_dependency('ice_nine', '~> 0.11.0')
|
26
|
+
gem.add_runtime_dependency('procto', '~> 0.0.2')
|
27
|
+
|
28
|
+
gem.add_development_dependency('devtools', '~> 0.1.3')
|
29
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
describe Morpher do
|
2
|
+
|
3
|
+
class Foo
|
4
|
+
include Anima.new(:attribute_a, :attribute_b)
|
5
|
+
end
|
6
|
+
|
7
|
+
class Bar
|
8
|
+
include Anima.new(:baz)
|
9
|
+
end
|
10
|
+
|
11
|
+
class Baz
|
12
|
+
include Anima.new(:value)
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:tree_a) do
|
16
|
+
Foo.new(
|
17
|
+
attribute_a: Baz.new(value: :value_a),
|
18
|
+
attribute_b: :value_b
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
let(:transformer_ast) do
|
23
|
+
s(
|
24
|
+
:block,
|
25
|
+
s(:guard, s(:primitive, Hash)),
|
26
|
+
s(
|
27
|
+
:hash_transform,
|
28
|
+
s(
|
29
|
+
:key_symbolize,
|
30
|
+
:attribute_a,
|
31
|
+
s(:guard, s(:primitive, String))
|
32
|
+
),
|
33
|
+
s(
|
34
|
+
:key_symbolize,
|
35
|
+
:attribute_b,
|
36
|
+
s(:guard, s(:primitive, Fixnum))
|
37
|
+
)
|
38
|
+
),
|
39
|
+
s(:load_attribute_hash, s(:param, Foo))
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
let(:predicate_ast) do
|
44
|
+
s(
|
45
|
+
:block,
|
46
|
+
s(:key_fetch, :attribute_a),
|
47
|
+
s(:eql, s(:static, 'foo'), s(:input))
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
specify 'allows to execute a transformation' do
|
52
|
+
evaluator = Morpher.compile(transformer_ast)
|
53
|
+
|
54
|
+
valid = {
|
55
|
+
'attribute_a' => 'a string',
|
56
|
+
'attribute_b' => 8015
|
57
|
+
}
|
58
|
+
|
59
|
+
expect(evaluator.call(valid)).to eql(
|
60
|
+
Foo.new(attribute_a: 'a string', attribute_b: 8015)
|
61
|
+
)
|
62
|
+
|
63
|
+
evaluation = evaluator.evaluation(valid)
|
64
|
+
|
65
|
+
expect(evaluation.output).to eql(
|
66
|
+
Foo.new(attribute_a: 'a string', attribute_b: 8015)
|
67
|
+
)
|
68
|
+
|
69
|
+
invalid = {
|
70
|
+
'attribute_a' => 0,
|
71
|
+
'attribute_b' => 8015
|
72
|
+
}
|
73
|
+
|
74
|
+
expect { evaluator.call(invalid) }.to raise_error(Morpher::Evaluator::Transformer::TransformError)
|
75
|
+
|
76
|
+
evaluation = evaluator.evaluation(invalid)
|
77
|
+
expect(evaluation.success?).to be(false)
|
78
|
+
end
|
79
|
+
|
80
|
+
specify 'allows to inverse a transformations' do
|
81
|
+
evaluator = Morpher.compile(transformer_ast)
|
82
|
+
|
83
|
+
expect(evaluator.inverse.inverse).to eql(evaluator)
|
84
|
+
|
85
|
+
input = Foo.new(attribute_a: 'a string', attribute_b: 8015)
|
86
|
+
|
87
|
+
valid = {
|
88
|
+
'attribute_a' => 'a string',
|
89
|
+
'attribute_b' => 8015
|
90
|
+
}
|
91
|
+
|
92
|
+
expect(evaluator.inverse.call(input)).to eql(valid)
|
93
|
+
end
|
94
|
+
|
95
|
+
specify 'allows to merge inputs' do
|
96
|
+
evaluator = Morpher.compile(s(:merge, foo: :bar))
|
97
|
+
|
98
|
+
expect(evaluator.call(foo: :bar)).to eql(foo: :bar)
|
99
|
+
expect(evaluator.call(bar: :baz)).to eql(foo: :bar, bar: :baz)
|
100
|
+
end
|
101
|
+
|
102
|
+
specify 'allows to coerce inputs from string to int and back' do
|
103
|
+
evaluator = Morpher.compile(s(:parse_int, 10))
|
104
|
+
|
105
|
+
expect(evaluator.call('42')).to be(42)
|
106
|
+
expect(evaluator.inverse.call(42)).to eql('42')
|
107
|
+
|
108
|
+
evaluator = Morpher.compile(s(:int_to_string, 10))
|
109
|
+
|
110
|
+
expect(evaluator.call(42)).to eql('42')
|
111
|
+
expect(evaluator.inverse.call('42')).to be(42)
|
112
|
+
end
|
113
|
+
|
114
|
+
specify 'allows to coerce inputs from ISO8601 string to DateTime and back' do
|
115
|
+
evaluator = Morpher.compile(s(:parse_iso8601_date_time, 0))
|
116
|
+
|
117
|
+
iso8601_string = '2014-08-04T00:00:00+00:00'
|
118
|
+
date_time = DateTime.new(2014, 8, 4)
|
119
|
+
|
120
|
+
expect(evaluator.call(iso8601_string)).to eq(date_time)
|
121
|
+
expect(evaluator.inverse.call(date_time)).to eq(iso8601_string)
|
122
|
+
|
123
|
+
evaluator = Morpher.compile(s(:date_time_to_iso8601_string, 0))
|
124
|
+
|
125
|
+
expect(evaluator.call(date_time)).to eq(iso8601_string)
|
126
|
+
expect(evaluator.inverse.call(iso8601_string)).to eq(date_time)
|
127
|
+
end
|
128
|
+
|
129
|
+
specify 'allows custom transformations' do
|
130
|
+
evaluator = Morpher.compile(s(:custom, [->(v) { "changed_#{v}" }]))
|
131
|
+
|
132
|
+
expect(evaluator.call('test')).to eql('changed_test')
|
133
|
+
end
|
134
|
+
|
135
|
+
specify 'allows predicates to be run from sexp' do
|
136
|
+
|
137
|
+
valid = { attribute_a: 'foo' }
|
138
|
+
invalid = { attribute_a: 'bar' }
|
139
|
+
|
140
|
+
evaluator = Morpher.compile(predicate_ast)
|
141
|
+
|
142
|
+
expect(evaluator.call(valid)).to be(true)
|
143
|
+
expect(evaluator.call(invalid)).to be(false)
|
144
|
+
|
145
|
+
evaluation = evaluator.evaluation(valid)
|
146
|
+
|
147
|
+
expect(evaluation.output).to be(true)
|
148
|
+
expect(evaluation.input).to be(valid)
|
149
|
+
expect(evaluation.description).to eql(strip(<<-TEXT))
|
150
|
+
Morpher::Evaluation::Nary
|
151
|
+
input: {:attribute_a=>"foo"}
|
152
|
+
output: true
|
153
|
+
success?: true
|
154
|
+
evaluator: Morpher::Evaluator::Transformer::Block
|
155
|
+
evaluations:
|
156
|
+
Morpher::Evaluation::Nullary
|
157
|
+
input: {:attribute_a=>"foo"}
|
158
|
+
output: "foo"
|
159
|
+
success?: true
|
160
|
+
evaluator:
|
161
|
+
Morpher::Evaluator::Transformer::Key::Fetch
|
162
|
+
param: :attribute_a
|
163
|
+
Morpher::Evaluation::Binary
|
164
|
+
input: "foo"
|
165
|
+
output: true
|
166
|
+
success?: true
|
167
|
+
left_evaluation:
|
168
|
+
Morpher::Evaluation::Nullary
|
169
|
+
input: "foo"
|
170
|
+
output: "foo"
|
171
|
+
success?: true
|
172
|
+
evaluator:
|
173
|
+
Morpher::Evaluator::Transformer::Static
|
174
|
+
param: "foo"
|
175
|
+
right_evaluation:
|
176
|
+
Morpher::Evaluation::Nullary
|
177
|
+
input: "foo"
|
178
|
+
output: "foo"
|
179
|
+
success?: true
|
180
|
+
evaluator:
|
181
|
+
Morpher::Evaluator::Transformer::Input
|
182
|
+
TEXT
|
183
|
+
end
|
184
|
+
end
|
data/spec/rcov.opts
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
shared_examples_for 'inverse evaluator' do
|
2
|
+
context '#inverse' do
|
3
|
+
subject { object.inverse }
|
4
|
+
|
5
|
+
it 'returns the expected inverse evaluator' do
|
6
|
+
should eql(expected_inverse)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
shared_examples_for 'evaluator' do
|
12
|
+
it 'round trips transitive evaluators via #inverse' do
|
13
|
+
if object.kind_of?(Morpher::Evaluator::Transformer) && object.transitive?
|
14
|
+
object.inverse.inverse.should eql(object)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'round trips evaluators via #node' do
|
19
|
+
Morpher.compile(object.node).should eql(object)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
shared_examples_for 'predicate evaluator' do
|
24
|
+
include_examples 'evaluator'
|
25
|
+
|
26
|
+
let(:negative_example?) { true }
|
27
|
+
let(:expected_output) { true }
|
28
|
+
let(:valid_input) { positive_input }
|
29
|
+
let(:expected_positive_output) { true }
|
30
|
+
let(:expected_negative_output) { false }
|
31
|
+
|
32
|
+
context 'with positive input' do
|
33
|
+
it 'evaluates to positive output' do
|
34
|
+
expect(object.call(positive_input)).to be(expected_positive_output)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'evaluates to inverted positive output' do
|
38
|
+
expect(object.inverse.call(positive_input)).to be(!expected_positive_output)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'evaluates to the same output under #evaluation' do
|
42
|
+
evaluation = object.evaluation(positive_input)
|
43
|
+
expect(evaluation.success?).to be(true)
|
44
|
+
expect(evaluation.input).to be(positive_input)
|
45
|
+
expect(evaluation.output).to be(expected_positive_output)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'with negative input' do
|
50
|
+
it 'evaluates to false' do
|
51
|
+
expect(object.call(negative_input)).to be(false)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'evaluates to true on inverse' do
|
55
|
+
expect(object.inverse.call(negative_input)).to be(true)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'evaluates to the same output under #evaluation' do
|
59
|
+
evaluation = object.evaluation(negative_input)
|
60
|
+
expect(evaluation.input).to be(negative_input)
|
61
|
+
expect(evaluation.success?).to be(true)
|
62
|
+
expect(evaluation.output).to be(false)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
shared_examples_for 'transitive evaluator' do
|
68
|
+
include_examples 'evaluator'
|
69
|
+
|
70
|
+
let(:invalid_input_example?) { true }
|
71
|
+
|
72
|
+
it 'signals transitivity via #transitive?' do
|
73
|
+
expect(object.transitive?).to be(true)
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'round trips valid inputs via #evaluation' do
|
77
|
+
evaluation = object.evaluation(valid_input)
|
78
|
+
expect(evaluation.success?).to be(true)
|
79
|
+
evaluation = object.inverse.evaluation(evaluation.output)
|
80
|
+
expect(evaluation.output).to eql(valid_input)
|
81
|
+
expect(evaluation.success?).to be(true)
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'round trips valid inputs via #call' do
|
85
|
+
forward = object.call(valid_input)
|
86
|
+
expect(object.inverse.call(forward)).to eql(valid_input)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
shared_examples_for 'intransitive evaluator' do
|
91
|
+
include_examples 'evaluator'
|
92
|
+
|
93
|
+
let(:invalid_input_example?) { true }
|
94
|
+
|
95
|
+
it 'signals intransitivity via #transitive?' do
|
96
|
+
expect(object.transitive?).to be(false)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
shared_examples_for 'transforming evaluator on valid input' do
|
101
|
+
include_examples 'evaluator'
|
102
|
+
|
103
|
+
it 'transforms to expected output via #call' do
|
104
|
+
result = object.call(valid_input)
|
105
|
+
expect(result).to eql(expected_output)
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'transforms to expected output via #evaluation' do
|
109
|
+
evaluation = object.evaluation(valid_input)
|
110
|
+
expect(evaluation.success?).to eql(true)
|
111
|
+
expect(evaluation.output).to eql(expected_output)
|
112
|
+
end
|
113
|
+
|
114
|
+
specify '#evaluation' do
|
115
|
+
evaluation = object.evaluation(valid_input)
|
116
|
+
expect(evaluation.success?).to be(true)
|
117
|
+
expect(evaluation.evaluator).to eql(object)
|
118
|
+
expect(evaluation.input).to eql(valid_input)
|
119
|
+
expect(evaluation.output).to eql(expected_output)
|
120
|
+
end
|
121
|
+
|
122
|
+
specify '#call' do
|
123
|
+
expect(object.call(valid_input)).to eql(expected_output)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
shared_examples_for 'transforming evaluator on invalid input' do
|
128
|
+
it 'raises error for #call' do
|
129
|
+
expect { object.call(invalid_input) }.to raise_error(
|
130
|
+
Morpher::Evaluator::Transformer::TransformError
|
131
|
+
)
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'returns error evaluator for #evaluation' do
|
135
|
+
evaluation = object.evaluation(invalid_input)
|
136
|
+
expect(evaluation.success?).to eql(false)
|
137
|
+
expect(evaluation.output).to be(Morpher::Undefined)
|
138
|
+
end
|
139
|
+
|
140
|
+
let(:expected_exception) do
|
141
|
+
Morpher::Evaluator::Transformer::TransformError.new(object, invalid_input)
|
142
|
+
end
|
143
|
+
|
144
|
+
specify '#call' do
|
145
|
+
expect { object.call(invalid_input) }.to raise_error(expected_exception)
|
146
|
+
end
|
147
|
+
|
148
|
+
specify '#evaluation' do
|
149
|
+
evaluation = object.evaluation(invalid_input)
|
150
|
+
expect(evaluation.success?).to be(false)
|
151
|
+
expect(evaluation.evaluator).to eql(object)
|
152
|
+
expect(evaluation.input).to eql(invalid_input)
|
153
|
+
expect(evaluation.output).to eql(Morpher::Undefined)
|
154
|
+
end
|
155
|
+
end
|