matchers 0.1.0.pre.test
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/lib/matcher/assertions.rb +21 -0
- data/lib/matcher/base.rb +189 -0
- data/lib/matcher/builder.rb +74 -0
- data/lib/matcher/chain.rb +60 -0
- data/lib/matcher/debug.rb +48 -0
- data/lib/matcher/errors/and_error.rb +86 -0
- data/lib/matcher/errors/boolean_collector.rb +51 -0
- data/lib/matcher/errors/element_error.rb +24 -0
- data/lib/matcher/errors/empty_error.rb +23 -0
- data/lib/matcher/errors/error.rb +39 -0
- data/lib/matcher/errors/error_collector.rb +99 -0
- data/lib/matcher/errors/nested_error.rb +96 -0
- data/lib/matcher/errors/or_error.rb +86 -0
- data/lib/matcher/expression_cache.rb +57 -0
- data/lib/matcher/expression_labeler.rb +91 -0
- data/lib/matcher/expressions/array_expression.rb +45 -0
- data/lib/matcher/expressions/block.rb +153 -0
- data/lib/matcher/expressions/call.rb +338 -0
- data/lib/matcher/expressions/call_error.rb +45 -0
- data/lib/matcher/expressions/constant.rb +53 -0
- data/lib/matcher/expressions/expression.rb +147 -0
- data/lib/matcher/expressions/expression_building.rb +258 -0
- data/lib/matcher/expressions/expression_walker.rb +73 -0
- data/lib/matcher/expressions/hash_expression.rb +59 -0
- data/lib/matcher/expressions/proc_expression.rb +92 -0
- data/lib/matcher/expressions/range_expression.rb +58 -0
- data/lib/matcher/expressions/recorder.rb +86 -0
- data/lib/matcher/expressions/rescue_last_error_expression.rb +44 -0
- data/lib/matcher/expressions/set_expression.rb +45 -0
- data/lib/matcher/expressions/string_expression.rb +53 -0
- data/lib/matcher/expressions/symbol_proc.rb +53 -0
- data/lib/matcher/expressions/variable.rb +85 -0
- data/lib/matcher/hash_stack.rb +53 -0
- data/lib/matcher/list.rb +102 -0
- data/lib/matcher/markers/optional.rb +80 -0
- data/lib/matcher/markers/others.rb +28 -0
- data/lib/matcher/matcher_cache.rb +18 -0
- data/lib/matcher/matchers/all_matcher.rb +60 -0
- data/lib/matcher/matchers/always_matcher.rb +28 -0
- data/lib/matcher/matchers/any_matcher.rb +70 -0
- data/lib/matcher/matchers/array_matcher.rb +35 -0
- data/lib/matcher/matchers/block_matcher.rb +59 -0
- data/lib/matcher/matchers/boolean_matcher.rb +35 -0
- data/lib/matcher/matchers/dig_matcher.rb +146 -0
- data/lib/matcher/matchers/each_matcher.rb +52 -0
- data/lib/matcher/matchers/each_pair_matcher.rb +119 -0
- data/lib/matcher/matchers/equal_matcher.rb +197 -0
- data/lib/matcher/matchers/equal_set_matcher.rb +99 -0
- data/lib/matcher/matchers/expression_matcher.rb +73 -0
- data/lib/matcher/matchers/filter_matcher.rb +111 -0
- data/lib/matcher/matchers/hash_matcher.rb +223 -0
- data/lib/matcher/matchers/imply_matcher.rb +81 -0
- data/lib/matcher/matchers/imply_some_matcher.rb +112 -0
- data/lib/matcher/matchers/index_by_matcher.rb +175 -0
- data/lib/matcher/matchers/inline_matcher.rb +99 -0
- data/lib/matcher/matchers/keys_matcher.rb +121 -0
- data/lib/matcher/matchers/kind_of_matcher.rb +35 -0
- data/lib/matcher/matchers/lazy_all_matcher.rb +68 -0
- data/lib/matcher/matchers/lazy_any_matcher.rb +68 -0
- data/lib/matcher/matchers/let_matcher.rb +73 -0
- data/lib/matcher/matchers/map_matcher.rb +129 -0
- data/lib/matcher/matchers/matcher_building.rb +5 -0
- data/lib/matcher/matchers/negated_array_matcher.rb +38 -0
- data/lib/matcher/matchers/negated_each_matcher.rb +36 -0
- data/lib/matcher/matchers/negated_each_pair_matcher.rb +38 -0
- data/lib/matcher/matchers/negated_imply_some_matcher.rb +46 -0
- data/lib/matcher/matchers/negated_matcher.rb +23 -0
- data/lib/matcher/matchers/negated_project_matcher.rb +31 -0
- data/lib/matcher/matchers/never_matcher.rb +29 -0
- data/lib/matcher/matchers/one_matcher.rb +70 -0
- data/lib/matcher/matchers/optional_matcher.rb +38 -0
- data/lib/matcher/matchers/parse_float_matcher.rb +86 -0
- data/lib/matcher/matchers/parse_integer_matcher.rb +98 -0
- data/lib/matcher/matchers/parse_iso8601_matcher.rb +92 -0
- data/lib/matcher/matchers/parse_json_matcher.rb +95 -0
- data/lib/matcher/matchers/project_matcher.rb +68 -0
- data/lib/matcher/matchers/raises_matcher.rb +124 -0
- data/lib/matcher/matchers/range_matcher.rb +47 -0
- data/lib/matcher/matchers/reference_matcher.rb +111 -0
- data/lib/matcher/matchers/reference_matcher_collection.rb +57 -0
- data/lib/matcher/matchers/regexp_matcher.rb +84 -0
- data/lib/matcher/messages/expected_phrasing.rb +342 -0
- data/lib/matcher/messages/message.rb +102 -0
- data/lib/matcher/messages/message_builder.rb +35 -0
- data/lib/matcher/messages/message_rules.rb +223 -0
- data/lib/matcher/messages/namespaced_message_builder.rb +19 -0
- data/lib/matcher/messages/phrasing.rb +57 -0
- data/lib/matcher/messages/standard_message_builder.rb +105 -0
- data/lib/matcher/once_before.rb +18 -0
- data/lib/matcher/optional_chain.rb +24 -0
- data/lib/matcher/patterns/ast_mapping.rb +42 -0
- data/lib/matcher/patterns/capture_hole.rb +33 -0
- data/lib/matcher/patterns/constant_hole.rb +14 -0
- data/lib/matcher/patterns/hole.rb +30 -0
- data/lib/matcher/patterns/method_hole.rb +58 -0
- data/lib/matcher/patterns/pattern.rb +92 -0
- data/lib/matcher/patterns/pattern_building.rb +39 -0
- data/lib/matcher/patterns/pattern_capture.rb +11 -0
- data/lib/matcher/patterns/pattern_match.rb +29 -0
- data/lib/matcher/patterns/variable_hole.rb +14 -0
- data/lib/matcher/reporter.rb +98 -0
- data/lib/matcher/rules/message_factory.rb +25 -0
- data/lib/matcher/rules/message_rule.rb +18 -0
- data/lib/matcher/rules/message_rule_context.rb +24 -0
- data/lib/matcher/rules/rule_builder.rb +29 -0
- data/lib/matcher/rules/rule_set.rb +57 -0
- data/lib/matcher/rules/transform_builder.rb +24 -0
- data/lib/matcher/rules/transform_mapping.rb +5 -0
- data/lib/matcher/rules/transform_rule.rb +21 -0
- data/lib/matcher/state.rb +40 -0
- data/lib/matcher/testing/error_builder.rb +62 -0
- data/lib/matcher/testing/error_checker.rb +496 -0
- data/lib/matcher/testing/error_testing.rb +37 -0
- data/lib/matcher/testing/pattern_testing.rb +11 -0
- data/lib/matcher/testing/pattern_testing_scope.rb +34 -0
- data/lib/matcher/testing.rb +102 -0
- data/lib/matcher/undefined.rb +10 -0
- data/lib/matcher/utils/mapping_utils.rb +61 -0
- data/lib/matcher/utils.rb +72 -0
- data/lib/matcher/version.rb +5 -0
- data/lib/matcher.rb +337 -0
- metadata +167 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: dd26d76a69e586fdc17822b522ad0a1761f2ad87526ecbf818018f7505b52620
|
|
4
|
+
data.tar.gz: 4c621e0c1f678c9846b9dfdffddc089db092b797eddc9db8b919ad496a2d8598
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: fe00a5777c09a4d01a904c0337131474c3c77cee05b4b0a31a5c3ff5a8a915c74164d78102880226062cbe2ad6c00d8b5b3430f9521bdd44f38aa23225091509
|
|
7
|
+
data.tar.gz: 5492feb0ec66aab747ef9d32f9d5c3c5f66eb9c357533c979e1ed57909358337cebdd7a0ef3aec6cb5f54fd13ee1fdd30ba2547111dd5412d0cf052f72de2ff4
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
module Assertions
|
|
5
|
+
def assert_structure(actual, &)
|
|
6
|
+
errors = Matcher.build(&).match(actual)
|
|
7
|
+
|
|
8
|
+
# rubocop:disable Minitest/AssertWithExpectedArgument
|
|
9
|
+
assert(false, <<~TEXT.chomp) unless errors.valid?
|
|
10
|
+
For object:
|
|
11
|
+
|
|
12
|
+
#{actual.inspect}
|
|
13
|
+
|
|
14
|
+
The following conditions were not satisfied:
|
|
15
|
+
|
|
16
|
+
#{Reporter.report(errors)}
|
|
17
|
+
TEXT
|
|
18
|
+
# rubocop:enable Minitest/AssertWithExpectedArgument
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
data/lib/matcher/base.rb
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
class Base
|
|
5
|
+
include NoExpression
|
|
6
|
+
include NoKey
|
|
7
|
+
|
|
8
|
+
##
|
|
9
|
+
# Negates this matcher
|
|
10
|
+
def ~
|
|
11
|
+
matcher_cache = MatcherCache.current
|
|
12
|
+
|
|
13
|
+
return negate unless matcher_cache
|
|
14
|
+
|
|
15
|
+
cache = (matcher_cache.negated_matchers ||= {}.compare_by_identity)
|
|
16
|
+
negated = cache[self]
|
|
17
|
+
|
|
18
|
+
unless negated
|
|
19
|
+
negated = negate
|
|
20
|
+
cache[self] = negated
|
|
21
|
+
cache[negated] = self
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
negated
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def negate
|
|
28
|
+
NegatedMatcher.new(self)
|
|
29
|
+
end
|
|
30
|
+
protected :negate
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
# Combines with matcher to AnyMatcher
|
|
34
|
+
# @param matcher
|
|
35
|
+
# @return [AnyMatcher]
|
|
36
|
+
# @see MatcherBuilding#any
|
|
37
|
+
def +(matcher)
|
|
38
|
+
matcher = Matcher.cache(matcher)
|
|
39
|
+
|
|
40
|
+
matchers = if matcher.is_a?(AnyMatcher)
|
|
41
|
+
[self].concat(matcher.matchers)
|
|
42
|
+
else
|
|
43
|
+
[self, matcher]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
AnyMatcher.new(matchers)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
##
|
|
50
|
+
# Combines with matcher to AllMatcher
|
|
51
|
+
# @param matcher
|
|
52
|
+
# @return [AllMatcher]
|
|
53
|
+
# @see MatcherBuilding#all
|
|
54
|
+
def *(matcher)
|
|
55
|
+
matcher = Matcher.cache(matcher)
|
|
56
|
+
|
|
57
|
+
matchers = if matcher.is_a?(AllMatcher)
|
|
58
|
+
[self].concat(matcher.matchers)
|
|
59
|
+
else
|
|
60
|
+
[self, matcher]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
AllMatcher.new(matchers)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
##
|
|
67
|
+
# Combines with matcher to LazyAnyMatcher
|
|
68
|
+
# @param matcher
|
|
69
|
+
# @return [LazyAnyMatcher]
|
|
70
|
+
# @see MatcherBuilding#lazy_any
|
|
71
|
+
def |(matcher)
|
|
72
|
+
matcher = Matcher.cache(matcher)
|
|
73
|
+
|
|
74
|
+
matchers = if matcher.is_a?(LazyAnyMatcher)
|
|
75
|
+
[self].concat(matcher.matchers)
|
|
76
|
+
else
|
|
77
|
+
[self, matcher]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
LazyAnyMatcher.new(matchers)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
##
|
|
84
|
+
# Combines with matcher to LazyAllMatcher
|
|
85
|
+
# @param matcher
|
|
86
|
+
# @return [LazyAllMatcher]
|
|
87
|
+
# @see MatcherBuilding#lazy_all
|
|
88
|
+
def &(matcher)
|
|
89
|
+
matcher = Matcher.cache(matcher)
|
|
90
|
+
|
|
91
|
+
matchers = if matcher.is_a?(LazyAllMatcher)
|
|
92
|
+
[self].concat(matcher.matchers)
|
|
93
|
+
else
|
|
94
|
+
[self, matcher]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
LazyAllMatcher.new(matchers)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
##
|
|
101
|
+
# Implies another matcher
|
|
102
|
+
# @param matcher
|
|
103
|
+
# @return [ImplyMatcher]
|
|
104
|
+
# @see MatcherBuilding#imply
|
|
105
|
+
def >>(matcher)
|
|
106
|
+
ImplyMatcher.new(self, Matcher.cache(matcher))
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
StackData = Struct.new(:actual, :vals, :errors)
|
|
110
|
+
|
|
111
|
+
##
|
|
112
|
+
# Returns +true+ if actual matches, +false+ otherwise
|
|
113
|
+
# @example
|
|
114
|
+
# Matcher.build { Integer }.match?(42) # => true
|
|
115
|
+
# @param actual the value to match against
|
|
116
|
+
# @param ** values
|
|
117
|
+
# @return [Boolean]
|
|
118
|
+
def match?(actual, **)
|
|
119
|
+
match_helper(true, actual:, **).valid?
|
|
120
|
+
end
|
|
121
|
+
alias === match?
|
|
122
|
+
|
|
123
|
+
##
|
|
124
|
+
# Returns an error tree describing all mismatches
|
|
125
|
+
# @example
|
|
126
|
+
# errors = Matcher.build { Integer }.match("foo")
|
|
127
|
+
# puts Matcher::Reporter.report(errors)
|
|
128
|
+
# # > root: expected a kind of Integer but got "foo"
|
|
129
|
+
# @param actual the value to match against
|
|
130
|
+
# @param ** values
|
|
131
|
+
# @return [Error]
|
|
132
|
+
def match(actual, **)
|
|
133
|
+
match_helper(false, actual:, **)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def match_helper(boolean, **)
|
|
137
|
+
hash_stack = HashStack.new
|
|
138
|
+
|
|
139
|
+
invoke = lambda do |matcher, act = UNDEFINED, **kwargs|
|
|
140
|
+
state = State.new(hash_stack, boolean:)
|
|
141
|
+
kwargs[:actual] = act unless Matcher.undefined?(act)
|
|
142
|
+
|
|
143
|
+
hash_stack.push(kwargs)
|
|
144
|
+
|
|
145
|
+
catch(:mismatch) do
|
|
146
|
+
matcher.validate(state, &invoke)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
hash_stack.pop(kwargs)
|
|
150
|
+
|
|
151
|
+
state.result
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
Matcher.with_session do
|
|
155
|
+
invoke.call(self, **)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
private :match_helper
|
|
159
|
+
|
|
160
|
+
def validate(state)
|
|
161
|
+
raise NotImplementedError
|
|
162
|
+
end
|
|
163
|
+
protected :validate
|
|
164
|
+
|
|
165
|
+
def inspect
|
|
166
|
+
to_s
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
protected
|
|
170
|
+
|
|
171
|
+
##
|
|
172
|
+
# Stores information for this matcher instance during match time.
|
|
173
|
+
def session(key = object_id)
|
|
174
|
+
Matcher.session[key] ||= {}
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
##
|
|
178
|
+
# Stores information for this matcher class during match time.
|
|
179
|
+
def self.session
|
|
180
|
+
Matcher.session[self] ||= {}
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
##
|
|
184
|
+
# Stores information for this matcher's class during match time.
|
|
185
|
+
def class_session
|
|
186
|
+
self.class.session
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
class Builder
|
|
5
|
+
include ExpressionBuilding
|
|
6
|
+
include MatcherBuilding
|
|
7
|
+
|
|
8
|
+
def initialize(outside, build_session: Matcher.build_session)
|
|
9
|
+
ExpressionBuilding.init(self, build_session)
|
|
10
|
+
|
|
11
|
+
@outside = outside
|
|
12
|
+
@matcher_cache = MatcherCache.current(build_session)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
##
|
|
16
|
+
# Turns a matchable value into a matcher
|
|
17
|
+
# @example
|
|
18
|
+
# of(String).class # => Matcher::KindOfMatcher
|
|
19
|
+
# of(String) & !_.empty? # matches non-empty strings
|
|
20
|
+
# @param value
|
|
21
|
+
# @return [Base]
|
|
22
|
+
def matcher_of(value)
|
|
23
|
+
Matcher.of(
|
|
24
|
+
value,
|
|
25
|
+
matcher_cache: @matcher_cache,
|
|
26
|
+
expression_cache: @expression_cache,
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
alias of matcher_of
|
|
30
|
+
|
|
31
|
+
##
|
|
32
|
+
# Accesses the outside context within a build block
|
|
33
|
+
# @example
|
|
34
|
+
# # access instance variables or methods from the enclosing scope
|
|
35
|
+
# Matcher.build do
|
|
36
|
+
# [outside { @ivar }, outside.my_method]
|
|
37
|
+
# end
|
|
38
|
+
# @return [Object] the outside context
|
|
39
|
+
def outside(&)
|
|
40
|
+
if block_given?
|
|
41
|
+
@outside.instance_eval(&)
|
|
42
|
+
else
|
|
43
|
+
@outside
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
##
|
|
48
|
+
# Negates a matcher
|
|
49
|
+
# @example
|
|
50
|
+
# # matches anything except 1
|
|
51
|
+
# neg(1)
|
|
52
|
+
# # alternatively:
|
|
53
|
+
# ~equal(1)
|
|
54
|
+
# @param matcher
|
|
55
|
+
# @return [Base]
|
|
56
|
+
# @see Base#~
|
|
57
|
+
def neg(matcher)
|
|
58
|
+
~matcher_of(matcher)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
##
|
|
62
|
+
# Matches given matcher but not +nil+
|
|
63
|
+
# @example
|
|
64
|
+
# present(my_value)
|
|
65
|
+
# @param matcher
|
|
66
|
+
# @return [AllMatcher]
|
|
67
|
+
def present(matcher)
|
|
68
|
+
AllMatcher.new([
|
|
69
|
+
~equal(nil),
|
|
70
|
+
matcher_of(matcher),
|
|
71
|
+
])
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
class Chain
|
|
5
|
+
include NoMatcher
|
|
6
|
+
include NoExpression
|
|
7
|
+
include NoKey
|
|
8
|
+
|
|
9
|
+
def initialize(negated: false, &block)
|
|
10
|
+
@block = block
|
|
11
|
+
@negated = negated
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def ~
|
|
15
|
+
Chain.new(negated: !@negated, &@block)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
##
|
|
19
|
+
# Chains this with a matcher or another chain
|
|
20
|
+
#
|
|
21
|
+
# Many helpers return a Chain that accepts a child matcher via +^+.
|
|
22
|
+
# Chains can also be composed: +each ^ map(_.to_i) ^ (_ > 0)+.
|
|
23
|
+
# @example
|
|
24
|
+
# each ^ Integer
|
|
25
|
+
# map(_.to_i) ^ [1, 2]
|
|
26
|
+
# filter(_.odd?) ^ [1, 3, 5]
|
|
27
|
+
# @param operand matcher or chain
|
|
28
|
+
# @return [Base]
|
|
29
|
+
def ^(operand)
|
|
30
|
+
if !Recorder.recorder?(operand) && operand.is_a?(Chain)
|
|
31
|
+
Chain.new { @block.call(operand ^ _1) }
|
|
32
|
+
else
|
|
33
|
+
matcher = Matcher.cache(operand)
|
|
34
|
+
result = @block.call(matcher)
|
|
35
|
+
result = ~result if @negated
|
|
36
|
+
result
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def optional(fallback = AlwaysMatcher.instance)
|
|
41
|
+
OptionalChain.new(self, fallback)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
module MatcherBuilding
|
|
46
|
+
##
|
|
47
|
+
# Reduces multiple chains to one
|
|
48
|
+
# @example
|
|
49
|
+
# chain(let(limit: 10), map(_.compact), filter(_.even?), _ < vars[:limit])
|
|
50
|
+
# # instead of
|
|
51
|
+
# let(limit: 10) ^ map(_.compact) ^ filter(_.even?) ^ (_ < vars[:limit])
|
|
52
|
+
# # which is equivalent to
|
|
53
|
+
# let({ limit: 10 }, map(_.compact, filter(_.even?, _ < vars[:limit])))
|
|
54
|
+
# @param *chains
|
|
55
|
+
# @return [Chain]
|
|
56
|
+
def chain(*chains)
|
|
57
|
+
chains.reduce(:^)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
module Debug
|
|
5
|
+
def self.enable
|
|
6
|
+
init(force: true)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
DEBUGGERS = %w[/bin/irb: ruby-debug-ide].freeze
|
|
10
|
+
|
|
11
|
+
def self.init(force: false)
|
|
12
|
+
return if @initialized
|
|
13
|
+
|
|
14
|
+
main_caller = caller[-1]
|
|
15
|
+
|
|
16
|
+
return if !force && DEBUGGERS.none? { main_caller.include?(_1) }
|
|
17
|
+
|
|
18
|
+
Recorder.prepend(ExpressionRecorderDebug)
|
|
19
|
+
@initialized = true
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.debugging?(last_caller)
|
|
23
|
+
%w[puts p].any? { last_caller.end_with?(":in `#{_1}'") } ||
|
|
24
|
+
last_caller.include?('ruby-debug-ide')
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
module ExpressionRecorderDebug
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def method_missing(method, *args, **kwargs, &block)
|
|
32
|
+
if Debug.debugging?(caller[0])
|
|
33
|
+
return @expression.to_s if %i[to_s inspect].include?(method)
|
|
34
|
+
|
|
35
|
+
return Object.instance_method(method)
|
|
36
|
+
.bind_call(self, *args, **kwargs, &block)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
super
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def respond_to_missing?(method, _include_private = false)
|
|
43
|
+
return Object.instance_methods.include?(method) if Debug.debugging?(caller[0])
|
|
44
|
+
|
|
45
|
+
super
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
class AndError < Error
|
|
5
|
+
attr_reader :children
|
|
6
|
+
|
|
7
|
+
def self.from(children)
|
|
8
|
+
length = children.length
|
|
9
|
+
|
|
10
|
+
return EmptyError.instance if length == 0
|
|
11
|
+
|
|
12
|
+
children.reduce do |left, right|
|
|
13
|
+
if left.is_a?(AndError)
|
|
14
|
+
left << right
|
|
15
|
+
else
|
|
16
|
+
left & right
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize(children)
|
|
22
|
+
raise 'children fewer than 2' if children.length < 2
|
|
23
|
+
|
|
24
|
+
super()
|
|
25
|
+
|
|
26
|
+
@children = children
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def ==(other)
|
|
30
|
+
return true if equal?(other)
|
|
31
|
+
|
|
32
|
+
other.instance_of?(AndError) &&
|
|
33
|
+
@children == other.children
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def &(other)
|
|
37
|
+
return self if other.is_a?(EmptyError)
|
|
38
|
+
|
|
39
|
+
clone << other
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def add(other)
|
|
43
|
+
case other
|
|
44
|
+
when AndError
|
|
45
|
+
right = other.children.dup
|
|
46
|
+
|
|
47
|
+
@children.each_with_index do |l, i|
|
|
48
|
+
next unless l.is_a?(NestedError)
|
|
49
|
+
|
|
50
|
+
index = right.find_index { _1.is_a?(NestedError) && _1.key == l.key }
|
|
51
|
+
|
|
52
|
+
@children[i] = l & right.delete_at(index) if index
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
@children.concat(right)
|
|
56
|
+
when NestedError
|
|
57
|
+
index = @children.find_index { _1.is_a?(NestedError) && _1.key == other.key }
|
|
58
|
+
|
|
59
|
+
if index
|
|
60
|
+
@children[index] &= other
|
|
61
|
+
else
|
|
62
|
+
@children << other
|
|
63
|
+
end
|
|
64
|
+
else
|
|
65
|
+
@children << other
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
self
|
|
69
|
+
end
|
|
70
|
+
alias << add
|
|
71
|
+
|
|
72
|
+
def clone
|
|
73
|
+
klone = super
|
|
74
|
+
klone.instance_exec do
|
|
75
|
+
@children = @children.dup
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
klone
|
|
79
|
+
end
|
|
80
|
+
alias dup clone
|
|
81
|
+
|
|
82
|
+
def to_s
|
|
83
|
+
@children.map(&:to_s).join(' & ')
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
class BooleanCollector
|
|
5
|
+
def initialize
|
|
6
|
+
@result = true
|
|
7
|
+
@mode = :and
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def or!
|
|
11
|
+
@mode = :or
|
|
12
|
+
self
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def or?
|
|
16
|
+
@mode == :or
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def and?
|
|
20
|
+
@mode == :and
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def empty?
|
|
24
|
+
@result
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
INVALID_ERROR = ElementError.new('invalid')
|
|
28
|
+
private_constant :INVALID_ERROR
|
|
29
|
+
|
|
30
|
+
def error
|
|
31
|
+
@result ? EmptyError.instance : INVALID_ERROR
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def <<(error)
|
|
35
|
+
if !error.is_a?(Error) || !error.valid?
|
|
36
|
+
@result = false
|
|
37
|
+
throw :mismatch if and?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
self.error
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def [](_key)
|
|
44
|
+
self
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def clear
|
|
48
|
+
@result = true
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
class ElementError < Error
|
|
5
|
+
attr_reader :message
|
|
6
|
+
|
|
7
|
+
def initialize(message)
|
|
8
|
+
super()
|
|
9
|
+
|
|
10
|
+
@message = message
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def ==(other)
|
|
14
|
+
return true if equal?(other)
|
|
15
|
+
|
|
16
|
+
other.instance_of?(ElementError) &&
|
|
17
|
+
@message == other.message
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def to_s
|
|
21
|
+
@message.inspect
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
class EmptyError < Error
|
|
5
|
+
include Singleton
|
|
6
|
+
|
|
7
|
+
def &(other)
|
|
8
|
+
other
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def |(other)
|
|
12
|
+
other
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def valid?
|
|
16
|
+
true
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def to_s
|
|
20
|
+
'<>'
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
class Error
|
|
5
|
+
def &(other)
|
|
6
|
+
case other
|
|
7
|
+
when EmptyError
|
|
8
|
+
self
|
|
9
|
+
when AndError
|
|
10
|
+
AndError.new([self].concat(other.children))
|
|
11
|
+
else
|
|
12
|
+
AndError.new([self, other])
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def |(other)
|
|
17
|
+
case other
|
|
18
|
+
when EmptyError
|
|
19
|
+
self
|
|
20
|
+
when OrError
|
|
21
|
+
OrError.new([self].concat(other.children))
|
|
22
|
+
else
|
|
23
|
+
OrError.new([self, other])
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def valid?
|
|
28
|
+
false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def inspect
|
|
32
|
+
to_s
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def report
|
|
36
|
+
Reporter.report(self)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|