matchers 0.1.0.pre.1

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.
Files changed (127) hide show
  1. checksums.yaml +7 -0
  2. data/lib/matcher/assertions.rb +19 -0
  3. data/lib/matcher/autoload.rb +5 -0
  4. data/lib/matcher/base.rb +183 -0
  5. data/lib/matcher/compatibility.rb +34 -0
  6. data/lib/matcher/debug.rb +62 -0
  7. data/lib/matcher/dsl/builder.rb +99 -0
  8. data/lib/matcher/dsl/chain.rb +84 -0
  9. data/lib/matcher/dsl/expression_dsl.rb +306 -0
  10. data/lib/matcher/dsl/matcher_dsl.rb +5 -0
  11. data/lib/matcher/dsl/optional.rb +82 -0
  12. data/lib/matcher/dsl/optional_chain.rb +24 -0
  13. data/lib/matcher/dsl/others.rb +28 -0
  14. data/lib/matcher/errors/and_error.rb +88 -0
  15. data/lib/matcher/errors/boolean_collector.rb +51 -0
  16. data/lib/matcher/errors/element_error.rb +24 -0
  17. data/lib/matcher/errors/empty_error.rb +23 -0
  18. data/lib/matcher/errors/error.rb +39 -0
  19. data/lib/matcher/errors/error_collector.rb +100 -0
  20. data/lib/matcher/errors/nested_error.rb +98 -0
  21. data/lib/matcher/errors/or_error.rb +88 -0
  22. data/lib/matcher/expression_cache.rb +57 -0
  23. data/lib/matcher/expression_labeler.rb +96 -0
  24. data/lib/matcher/expressions/array_expression.rb +45 -0
  25. data/lib/matcher/expressions/block.rb +189 -0
  26. data/lib/matcher/expressions/call.rb +307 -0
  27. data/lib/matcher/expressions/call_error.rb +45 -0
  28. data/lib/matcher/expressions/constant.rb +53 -0
  29. data/lib/matcher/expressions/expression.rb +237 -0
  30. data/lib/matcher/expressions/expression_walker.rb +77 -0
  31. data/lib/matcher/expressions/hash_expression.rb +59 -0
  32. data/lib/matcher/expressions/proc_expression.rb +96 -0
  33. data/lib/matcher/expressions/range_expression.rb +65 -0
  34. data/lib/matcher/expressions/recorder.rb +136 -0
  35. data/lib/matcher/expressions/rescue_last_error_expression.rb +49 -0
  36. data/lib/matcher/expressions/set_expression.rb +45 -0
  37. data/lib/matcher/expressions/string_expression.rb +53 -0
  38. data/lib/matcher/expressions/symbol_proc.rb +53 -0
  39. data/lib/matcher/expressions/variable.rb +87 -0
  40. data/lib/matcher/hash_stack.rb +52 -0
  41. data/lib/matcher/list.rb +102 -0
  42. data/lib/matcher/markers.rb +7 -0
  43. data/lib/matcher/matcher_cache.rb +18 -0
  44. data/lib/matcher/matchers/all_matcher.rb +60 -0
  45. data/lib/matcher/matchers/always_matcher.rb +34 -0
  46. data/lib/matcher/matchers/any_matcher.rb +70 -0
  47. data/lib/matcher/matchers/array_matcher.rb +72 -0
  48. data/lib/matcher/matchers/block_matcher.rb +61 -0
  49. data/lib/matcher/matchers/boolean_matcher.rb +37 -0
  50. data/lib/matcher/matchers/dig_matcher.rb +149 -0
  51. data/lib/matcher/matchers/each_matcher.rb +85 -0
  52. data/lib/matcher/matchers/each_pair_matcher.rb +119 -0
  53. data/lib/matcher/matchers/equal_matcher.rb +198 -0
  54. data/lib/matcher/matchers/equal_set_matcher.rb +112 -0
  55. data/lib/matcher/matchers/expression_matcher.rb +69 -0
  56. data/lib/matcher/matchers/filter_matcher.rb +115 -0
  57. data/lib/matcher/matchers/hash_matcher.rb +315 -0
  58. data/lib/matcher/matchers/imply_matcher.rb +83 -0
  59. data/lib/matcher/matchers/imply_some_matcher.rb +116 -0
  60. data/lib/matcher/matchers/index_by_matcher.rb +177 -0
  61. data/lib/matcher/matchers/inline_matcher.rb +101 -0
  62. data/lib/matcher/matchers/keys_matcher.rb +131 -0
  63. data/lib/matcher/matchers/kind_of_matcher.rb +35 -0
  64. data/lib/matcher/matchers/lazy_all_matcher.rb +69 -0
  65. data/lib/matcher/matchers/lazy_any_matcher.rb +69 -0
  66. data/lib/matcher/matchers/let_matcher.rb +73 -0
  67. data/lib/matcher/matchers/map_matcher.rb +148 -0
  68. data/lib/matcher/matchers/negated_array_matcher.rb +38 -0
  69. data/lib/matcher/matchers/negated_each_matcher.rb +36 -0
  70. data/lib/matcher/matchers/negated_each_pair_matcher.rb +38 -0
  71. data/lib/matcher/matchers/negated_imply_some_matcher.rb +46 -0
  72. data/lib/matcher/matchers/negated_matcher.rb +25 -0
  73. data/lib/matcher/matchers/negated_project_matcher.rb +31 -0
  74. data/lib/matcher/matchers/never_matcher.rb +35 -0
  75. data/lib/matcher/matchers/one_matcher.rb +68 -0
  76. data/lib/matcher/matchers/optional_matcher.rb +38 -0
  77. data/lib/matcher/matchers/parse_float_matcher.rb +86 -0
  78. data/lib/matcher/matchers/parse_integer_matcher.rb +101 -0
  79. data/lib/matcher/matchers/parse_iso8601_helper.rb +41 -0
  80. data/lib/matcher/matchers/parse_iso8601_matcher.rb +52 -0
  81. data/lib/matcher/matchers/parse_json_helper.rb +43 -0
  82. data/lib/matcher/matchers/parse_json_matcher.rb +59 -0
  83. data/lib/matcher/matchers/project_matcher.rb +72 -0
  84. data/lib/matcher/matchers/raises_matcher.rb +131 -0
  85. data/lib/matcher/matchers/range_matcher.rb +50 -0
  86. data/lib/matcher/matchers/reference_matcher.rb +213 -0
  87. data/lib/matcher/matchers/reference_matcher_collection.rb +57 -0
  88. data/lib/matcher/matchers/regexp_matcher.rb +86 -0
  89. data/lib/matcher/messages/expected_phrasing.rb +355 -0
  90. data/lib/matcher/messages/message.rb +104 -0
  91. data/lib/matcher/messages/message_builder.rb +35 -0
  92. data/lib/matcher/messages/message_rules.rb +240 -0
  93. data/lib/matcher/messages/namespaced_message_builder.rb +19 -0
  94. data/lib/matcher/messages/phrasing.rb +59 -0
  95. data/lib/matcher/messages/standard_message_builder.rb +105 -0
  96. data/lib/matcher/patterns/ast_mapping.rb +42 -0
  97. data/lib/matcher/patterns/capture_hole.rb +33 -0
  98. data/lib/matcher/patterns/constant_hole.rb +14 -0
  99. data/lib/matcher/patterns/hole.rb +30 -0
  100. data/lib/matcher/patterns/method_hole.rb +62 -0
  101. data/lib/matcher/patterns/pattern.rb +104 -0
  102. data/lib/matcher/patterns/pattern_building.rb +39 -0
  103. data/lib/matcher/patterns/pattern_capture.rb +11 -0
  104. data/lib/matcher/patterns/pattern_match.rb +29 -0
  105. data/lib/matcher/patterns/variable_hole.rb +14 -0
  106. data/lib/matcher/reporter.rb +103 -0
  107. data/lib/matcher/rules/message_factory.rb +26 -0
  108. data/lib/matcher/rules/message_rule.rb +18 -0
  109. data/lib/matcher/rules/message_rule_context.rb +26 -0
  110. data/lib/matcher/rules/rule_builder.rb +29 -0
  111. data/lib/matcher/rules/rule_set.rb +57 -0
  112. data/lib/matcher/rules/transform_builder.rb +24 -0
  113. data/lib/matcher/rules/transform_mapping.rb +5 -0
  114. data/lib/matcher/rules/transform_rule.rb +21 -0
  115. data/lib/matcher/state.rb +40 -0
  116. data/lib/matcher/testing/error_builder.rb +62 -0
  117. data/lib/matcher/testing/error_checker.rb +514 -0
  118. data/lib/matcher/testing/error_testing.rb +37 -0
  119. data/lib/matcher/testing/pattern_testing.rb +11 -0
  120. data/lib/matcher/testing/pattern_testing_scope.rb +34 -0
  121. data/lib/matcher/testing.rb +107 -0
  122. data/lib/matcher/undefined.rb +10 -0
  123. data/lib/matcher/utils/mapping_utils.rb +61 -0
  124. data/lib/matcher/utils.rb +72 -0
  125. data/lib/matcher/version.rb +5 -0
  126. data/lib/matcher.rb +346 -0
  127. metadata +174 -0
@@ -0,0 +1,355 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matcher
4
+ class ExpectedPhrasing < Phrasing
5
+ define(:truthy) do
6
+ verb = verb(negated: true)
7
+ truthy_or_falsy = negated ? "truthy" : "falsy"
8
+
9
+ "#{verb} a #{truthy_or_falsy} value but got #{actual.inspect}"
10
+ end
11
+
12
+ define(:same) do |object|
13
+ "#{verb} same as #{object.inspect} (id=#{object.object_id})" \
14
+ "#{" but got #{actual.inspect} (id=#{actual.object_id})" if negated}"
15
+ end
16
+
17
+ define(:equal) do |value|
18
+ if negated
19
+ "#{verb} #{value.inspect} but got #{actual.inspect}"
20
+ else
21
+ "#{verb} #{actual.inspect}"
22
+ end
23
+ end
24
+
25
+ define(:less_than) do |operand|
26
+ if negated || (actual <=> operand).nil?
27
+ "#{verb} a value < #{operand.inspect} but got #{actual.inspect}"
28
+ else
29
+ phrase_negated(:greater_than_or_equal, operand)
30
+ end
31
+ end
32
+
33
+ define(:greater_than) do |operand|
34
+ if negated || (actual <=> operand).nil?
35
+ "#{verb} a value > #{operand.inspect} but got #{actual.inspect}"
36
+ else
37
+ phrase_negated(:less_than_or_equal, operand)
38
+ end
39
+ end
40
+
41
+ define(:less_than_or_equal) do |operand|
42
+ if negated || (actual <=> operand).nil?
43
+ "#{verb} a value <= #{operand.inspect} but got #{actual.inspect}"
44
+ else
45
+ phrase_negated(:greater_than, operand)
46
+ end
47
+ end
48
+
49
+ define(:greater_than_or_equal) do |operand|
50
+ if negated || (actual <=> operand).nil?
51
+ "#{verb} a value >= #{operand.inspect} but got #{actual.inspect}"
52
+ else
53
+ phrase_negated(:less_than, operand)
54
+ end
55
+ end
56
+
57
+ define(:comparable_to) do |operand|
58
+ "#{verb} a value comparable to #{operand.inspect} " \
59
+ "but got #{actual.inspect}"
60
+ end
61
+
62
+ define(:between) do |min, max, exclude_end: false|
63
+ "#{verb} value to be between #{min.inspect} and " \
64
+ "#{max.inspect}#{' (exclusive)' if exclude_end} " \
65
+ "but got #{actual.inspect}"
66
+ end
67
+
68
+ define(:length_of) do |exp, act|
69
+ "#{verb} length of #{exp}#{" but was #{act}" if negated}"
70
+ end
71
+
72
+ define(:having_key) do |key|
73
+ "#{verb} to include key #{key.inspect} but got #{actual.inspect}"
74
+ end
75
+
76
+ define(:having_index) do |index|
77
+ "#{verb} to have index #{index.inspect} but got #{actual.inspect}"
78
+ end
79
+
80
+ define(:exist) do
81
+ "#{verb} to exist#{" but got #{actual.inspect}" unless negated}"
82
+ end
83
+
84
+ define(:in) do |collection|
85
+ "#{verb} object to be included in #{collection.inspect} " \
86
+ "but got #{actual.inspect}"
87
+ end
88
+
89
+ define(:including) do |item|
90
+ "#{verb} #{item.inspect} to be included but got #{actual.inspect}"
91
+ end
92
+
93
+ define(:duplicate) do |original_index|
94
+ "#{verb} duplicate originally at index #{original_index} " \
95
+ "but got #{actual.inspect}"
96
+ end
97
+
98
+ define(:duplicate_by) do |expression, value, original_index|
99
+ "#{verb} duplicate by #{expression.inspect}=#{value.inspect} " \
100
+ "originally at index #{original_index} but got #{actual.inspect}"
101
+ end
102
+
103
+ define(:matching) do |pattern|
104
+ "#{verb} value to match #{pattern.inspect} but got #{actual.inspect}"
105
+ end
106
+
107
+ define(:valid_format) do |format|
108
+ "#{verb} a valid #{format} string but got #{actual.inspect}"
109
+ end
110
+
111
+ define(:instance_of) do |klass|
112
+ "#{verb} an instance of #{klass} but got #{actual.inspect}"
113
+ end
114
+
115
+ define(:kind_of) do |klass|
116
+ "#{verb} a kind of #{klass} but got #{actual.inspect}"
117
+ end
118
+
119
+ define(:responding_to) do |method|
120
+ "#{verb} an object responding to `#{method}' but got #{actual.inspect}"
121
+ end
122
+
123
+ define(:predicate) do |predicate|
124
+ predicate = predicate.to_s.delete_suffix("?")
125
+
126
+ "#{verb} value to be #{predicate} but got #{actual.inspect}"
127
+ end
128
+
129
+ define(:described_by) do |description|
130
+ "#{verb} #{description} but got #{actual.inspect}"
131
+ end
132
+
133
+ namespace(:expression) do
134
+ define(:truthy) do |expression, value, given|
135
+ verb = verb(negated: true)
136
+ truthy_or_falsy = negated ? "truthy" : "falsy"
137
+
138
+ "#{verb} #{expression} to be #{truthy_or_falsy} " \
139
+ "but got #{value.inspect}#{where_text(given, expression)}"
140
+ end
141
+
142
+ define(:same) do |left_expression, right_expression, left, right, given|
143
+ "#{verb} #{left_expression} to be same as #{right_expression} " \
144
+ "but got #{left.inspect} (id=#{left.object_id})" \
145
+ "#{" and #{right.inspect} (id=#{right.object_id})" if negated}" \
146
+ "#{where_text(given, left_expression, right_expression)}"
147
+ end
148
+
149
+ negated_comparisons =
150
+ { :== => :!=, :!= => :==, :< => :>=, :> => :<=, :<= => :>, :>= => :< }
151
+
152
+ define(:comparison) do |expression, left, right, given|
153
+ can_negate = !negated &&
154
+ (method = negated_comparisons[expression.method]) &&
155
+ (%i[== !=].include?(method) || left <=> right)
156
+
157
+ verb = if can_negate
158
+ expression = Call.new(
159
+ expression.receiver,
160
+ method,
161
+ expression.args,
162
+ expression.kwargs,
163
+ )
164
+
165
+ verb(negated: true)
166
+ else
167
+ self.verb
168
+ end
169
+
170
+ "#{verb} #{expression} but got " \
171
+ "#{left.inspect} #{expression.method} #{right.inspect}" \
172
+ "#{where_text(given, expression.receiver, expression.args[0])}"
173
+ end
174
+
175
+ define(:comparable_to) do |expression, value, operand, given|
176
+ "#{verb} #{expression} to be comparable to #{operand.inspect} " \
177
+ "but got #{value.inspect}#{where_text(given, expression)}"
178
+ end
179
+
180
+ define(:between) do |expression, value, min, max, given|
181
+ "#{verb} #{expression} to be between " \
182
+ "#{min.inspect} and #{max.inspect} " \
183
+ "but got #{value.inspect}#{where_text(given, expression)}"
184
+ end
185
+
186
+ define(:length_of) do |expression, _value, exp, act, given|
187
+ "#{verb} #{expression} to have length of #{exp}" \
188
+ "#{" but was #{act}" if negated}#{where_text(given, expression)}"
189
+ end
190
+
191
+ define(:having_key) do |expression, value, key, given|
192
+ "#{verb} #{expression} to include key #{key.inspect} " \
193
+ "but got #{value.inspect}#{where_text(given, expression)}"
194
+ end
195
+
196
+ define(:in) do |expression, value, collection, given|
197
+ "#{verb} #{expression} to be included in #{collection.inspect} " \
198
+ "but got #{value.inspect}#{where_text(given, expression)}"
199
+ end
200
+
201
+ define(:including) do |expression, value, item, given|
202
+ "#{verb} #{expression} to include #{item.inspect} " \
203
+ "but got #{value.inspect}#{where_text(given, expression)}"
204
+ end
205
+
206
+ define(:matching) do |expression, value, pattern, given|
207
+ "#{verb} #{expression} to match #{pattern.inspect} " \
208
+ "but got #{value.inspect}#{where_text(given, expression)}"
209
+ end
210
+
211
+ match_at_words = {
212
+ :== => "at",
213
+ :!= => "not at",
214
+ :< => "before",
215
+ :> => "after",
216
+ :<= => "at or before",
217
+ :>= => "at or after",
218
+ }
219
+
220
+ define(:match_at) do |expression, value, pattern, position, comparison,
221
+ operand, given|
222
+
223
+ comparison_word = match_at_words[comparison]
224
+
225
+ message = String.new
226
+ message << "#{verb} #{expression} to match " \
227
+ "#{pattern.inspect} #{comparison_word} #{operand} "
228
+ message << "but was at #{position} " if
229
+ operand != position || comparison != (negated ? :!= : :==)
230
+ message << "for #{value.inspect}#{where_text(given, expression)}"
231
+
232
+ message
233
+ end
234
+
235
+ define(:instance_of) do |expression, value, klass, given|
236
+ "#{verb} #{expression} to be an instance of #{klass} " \
237
+ "but got #{value.inspect}#{where_text(given, expression)}"
238
+ end
239
+
240
+ define(:kind_of) do |expression, value, klass, given|
241
+ "#{verb} #{expression} to be a kind of #{klass} " \
242
+ "but got #{value.inspect}#{where_text(given, expression)}"
243
+ end
244
+
245
+ define(:responding_to) do |expression, value, method, given|
246
+ "#{verb} #{expression} to respond to `#{method}' " \
247
+ "but got #{value.inspect}#{where_text(given, expression)}"
248
+ end
249
+
250
+ define(:predicate) do |expression, value, predicate, given|
251
+ predicate = predicate.to_s.delete_suffix("?")
252
+
253
+ "#{verb} #{expression} to be #{predicate} " \
254
+ "but got #{value.inspect}#{where_text(given, expression)}"
255
+ end
256
+
257
+ define(:raising) do |expression, error, given|
258
+ error_class = error.is_a?(Class) ? error : error.class
259
+ where = where_text(given, expression)
260
+
261
+ text = "#{verb} #{expression} to raise #{error_class}#{where}"
262
+ text += ": #{error.message}" if error.is_a?(Exception)
263
+
264
+ text
265
+ end
266
+ end
267
+
268
+ namespace(:negated) do
269
+ define(:valid) do |matcher|
270
+ "#{verb} #{matcher} to be valid but got #{actual.inspect}"
271
+ end
272
+ end
273
+
274
+ namespace(:block) do
275
+ define(:satisfied) do |block_location|
276
+ "#{verb} to satisfy condition #{block_location} " \
277
+ "but got #{actual.inspect}"
278
+ end
279
+ end
280
+
281
+ namespace(:imply_some) do
282
+ def x_conditions(count)
283
+ case count
284
+ when :any
285
+ "any condition"
286
+ when 1
287
+ "one condition"
288
+ else
289
+ "#{count} conditions"
290
+ end
291
+ end
292
+ private :x_conditions
293
+
294
+ define(:no_condition_satisfied) do |conditions, count|
295
+ "#{negated_verb} to satisfy #{x_conditions(count)} " \
296
+ "but got #{actual.inspect} and met none of these: #{join(conditions)}"
297
+ end
298
+
299
+ define(:x_conditions_satisfied) do |conditions, count|
300
+ "#{negated_verb} to satisfy #{x_conditions(count)} " \
301
+ "but got #{actual.inspect} and met these: #{join(conditions)}"
302
+ end
303
+ end
304
+
305
+ namespace(:reference) do
306
+ define(:cyclic) do
307
+ "#{verb} a cyclic structure" \
308
+ "#{' but actual has already been visited' unless negated}"
309
+ end
310
+
311
+ define(:failed_from_cache) do
312
+ "actual has already failed before"
313
+ end
314
+ end
315
+
316
+ namespace(:set) do
317
+ define(:equal) do |set|
318
+ "#{verb} object to be an equal set to #{set.inspect} " \
319
+ "but got #{actual.inspect}"
320
+ end
321
+ end
322
+
323
+ private
324
+
325
+ def verb(negated: self.negated)
326
+ # A message says what actual is but expected says what it is not. That's
327
+ # why the verb is counter-intuitively negated.
328
+
329
+ negated ? "expected" : "did not expect"
330
+ end
331
+
332
+ def negated_verb
333
+ verb(negated: !negated)
334
+ end
335
+
336
+ def join(objects)
337
+ objects.join(", ")
338
+ end
339
+
340
+ def where_text(values, *expressions)
341
+ return "" if values.empty?
342
+
343
+ except = expressions.filter_map { _1.symbol if _1.is_a?(Variable) }
344
+ symbols = expressions.flat_map(&:variables).uniq - except
345
+
346
+ return "" if symbols.empty?
347
+
348
+ list = symbols
349
+ .map { "#{_1} = #{values.fetch(_1).inspect}" }
350
+ .join(", ")
351
+
352
+ ", where #{list}"
353
+ end
354
+ end
355
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matcher
4
+ class Message
5
+ attr_reader :key, :negated, :actual, :args, :kwargs
6
+
7
+ def initialize(key, negated, actual, *args, **kwargs)
8
+ @key = key
9
+ @negated = negated
10
+ @actual = actual
11
+ @args = args
12
+ @kwargs = kwargs
13
+ end
14
+
15
+ def negate!
16
+ @negated = !@negated
17
+ end
18
+
19
+ def ==(other)
20
+ return true if equal?(other)
21
+
22
+ other.instance_of?(Message) &&
23
+ @key.eql?(other.key) &&
24
+ @negated == other.negated &&
25
+ @actual.eql?(other.actual) &&
26
+ @args.eql?(other.args) &&
27
+ @kwargs.eql?(other.kwargs)
28
+ end
29
+ alias eql? ==
30
+
31
+ def hash
32
+ @hash ||= [self.class, @key, @negated, @actual, @args, @kwargs].hash
33
+ end
34
+
35
+ def namespace
36
+ @key.is_a?(Array) ? @key[0] : nil
37
+ end
38
+
39
+ def to_standard
40
+ return self if namespace != :expression
41
+
42
+ key = @key[1]
43
+
44
+ case key
45
+ when :truthy
46
+ _expr, value = @args
47
+ Message.new(key, @negated, value)
48
+ when :same
49
+ _l_expr, _r_expr, left, right = @args
50
+ Message.new(:same, @negated, left, right)
51
+ when :comparable_to, :having_key, :in, :including, :matching,
52
+ :instance_of, :kind_of, :responding_to, :predicate
53
+
54
+ _expr, value, operand = @args
55
+ Message.new(key, @negated, value, operand)
56
+ when :between, :length_of
57
+ _expr, value, operand1, operand2 = @args
58
+ Message.new(key, @negated, value, operand1, operand2)
59
+ when :comparison
60
+ expr, left, right = @args
61
+
62
+ case expr.method
63
+ when :==
64
+ Message.new(:equal, @negated, left, right)
65
+ when :!=
66
+ Message.new(:equal, !@negated, left, right)
67
+ when :<
68
+ Message.new(:less_than, @negated, left, right)
69
+ when :>
70
+ Message.new(:greater_than, @negated, left, right)
71
+ when :<=
72
+ Message.new(:less_than_or_equal, @negated, left, right)
73
+ when :>=
74
+ Message.new(:greater_than_or_equal, @negated, left, right)
75
+ else
76
+ raise "unexpected method: #{expr.method.inspect}"
77
+ end
78
+ else
79
+ self
80
+ end
81
+ end
82
+
83
+ def to_s
84
+ if @key.is_a?(Array)
85
+ namespace, key = @key
86
+ else
87
+ key = @key
88
+ end
89
+
90
+ args_and_kwargs = @args.map(&:inspect) + @kwargs.map do |k, v|
91
+ k.is_a?(Symbol) ? "#{k}: #{v.inspect}" : "#{k.inspect} => #{v.inspect}"
92
+ end
93
+
94
+ string = "report(#{@actual.inspect})"
95
+ string += ".namespace(#{namespace.inspect})" if namespace
96
+ string += ".not" if @negated
97
+ string += ".#{key}"
98
+ string += "(#{args_and_kwargs.join(', ')})" unless args_and_kwargs.empty?
99
+
100
+ string
101
+ end
102
+ alias inspect to_s
103
+ end
104
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matcher
4
+ class MessageBuilder
5
+ def initialize(negated, actual)
6
+ @negated = negated
7
+ @actual = actual
8
+ end
9
+
10
+ def not
11
+ @negated = !@negated
12
+ self
13
+ end
14
+
15
+ def not_if(condition)
16
+ condition ? self.not : self
17
+ end
18
+
19
+ protected
20
+
21
+ def message(key, *, **)
22
+ Message.new(key, @negated, @actual, *, **)
23
+ end
24
+
25
+ private
26
+
27
+ def method_missing(method, *, **)
28
+ message(method, *, **)
29
+ end
30
+
31
+ def respond_to_missing?(_name, _include_private = false)
32
+ true
33
+ end
34
+ end
35
+ end