gmail_search_syntax 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/ARCHITECTURE.md +338 -0
- data/README.md +129 -0
- data/Rakefile +11 -0
- data/SCHEMA.md +223 -0
- data/examples/alias_collision_fix.rb +43 -0
- data/examples/demo.rb +28 -0
- data/examples/gmail_message_id_demo.rb +118 -0
- data/examples/postgres_vs_sqlite.rb +55 -0
- data/examples/sql_query.rb +47 -0
- data/lib/GMAIL_SEARCH_OPERATORS.md +58 -0
- data/lib/gmail_search_syntax/ast.rb +100 -0
- data/lib/gmail_search_syntax/parser.rb +224 -0
- data/lib/gmail_search_syntax/sql_visitor.rb +496 -0
- data/lib/gmail_search_syntax/tokenizer.rb +152 -0
- data/lib/gmail_search_syntax/version.rb +3 -0
- data/lib/gmail_search_syntax.rb +34 -0
- data/test/gmail_search_syntax_test.rb +691 -0
- data/test/integration_test.rb +668 -0
- data/test/postgres_visitor_test.rb +156 -0
- data/test/sql_visitor_test.rb +346 -0
- data/test/test_helper.rb +27 -0
- data/test/tokenizer_test.rb +185 -0
- metadata +115 -0
@@ -0,0 +1,691 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class GmailSearchSyntaxTest < Minitest::Test
|
4
|
+
include GmailSearchSyntax::AST
|
5
|
+
|
6
|
+
def test_version
|
7
|
+
assert_equal "0.1.0", GmailSearchSyntax::VERSION
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_simple_from_operator
|
11
|
+
ast = GmailSearchSyntax.parse!("from:amy@example.com")
|
12
|
+
assert_instance_of Operator, ast
|
13
|
+
assert_equal "from", ast.name
|
14
|
+
assert_equal "amy@example.com", ast.value
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_from_me
|
18
|
+
ast = GmailSearchSyntax.parse!("from:me")
|
19
|
+
assert_instance_of Operator, ast
|
20
|
+
assert_equal "from", ast.name
|
21
|
+
assert_equal "me", ast.value
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_to_operator
|
25
|
+
ast = GmailSearchSyntax.parse!("to:john@example.com")
|
26
|
+
assert_instance_of Operator, ast
|
27
|
+
assert_equal "to", ast.name
|
28
|
+
assert_equal "john@example.com", ast.value
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_subject_with_single_word
|
32
|
+
ast = GmailSearchSyntax.parse!("subject:dinner")
|
33
|
+
assert_instance_of Operator, ast
|
34
|
+
assert_equal "subject", ast.name
|
35
|
+
assert_equal "dinner", ast.value
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_subject_with_quoted_phrase
|
39
|
+
ast = GmailSearchSyntax.parse!('subject:"anniversary party"')
|
40
|
+
assert_instance_of Operator, ast
|
41
|
+
assert_equal "subject", ast.name
|
42
|
+
assert_equal "anniversary party", ast.value
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_after_date
|
46
|
+
ast = GmailSearchSyntax.parse!("after:2004/04/16")
|
47
|
+
assert_instance_of Operator, ast
|
48
|
+
assert_equal "after", ast.name
|
49
|
+
assert_equal "2004/04/16", ast.value
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_before_date
|
53
|
+
ast = GmailSearchSyntax.parse!("before:04/18/2004")
|
54
|
+
assert_instance_of Operator, ast
|
55
|
+
assert_equal "before", ast.name
|
56
|
+
assert_equal "04/18/2004", ast.value
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_older_than_relative
|
60
|
+
ast = GmailSearchSyntax.parse!("older_than:1y")
|
61
|
+
assert_instance_of Operator, ast
|
62
|
+
assert_equal "older_than", ast.name
|
63
|
+
assert_equal "1y", ast.value
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_newer_than_relative
|
67
|
+
ast = GmailSearchSyntax.parse!("newer_than:2d")
|
68
|
+
assert_instance_of Operator, ast
|
69
|
+
assert_equal "newer_than", ast.name
|
70
|
+
assert_equal "2d", ast.value
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_or_operator_with_from
|
74
|
+
ast = GmailSearchSyntax.parse!("from:amy OR from:david")
|
75
|
+
assert_instance_of Or, ast
|
76
|
+
|
77
|
+
assert_equal 2, ast.operands.length
|
78
|
+
assert_instance_of Operator, ast.operands[0]
|
79
|
+
assert_equal "from", ast.operands[0].name
|
80
|
+
assert_equal "amy", ast.operands[0].value
|
81
|
+
|
82
|
+
assert_instance_of Operator, ast.operands[1]
|
83
|
+
assert_equal "from", ast.operands[1].name
|
84
|
+
assert_equal "david", ast.operands[1].value
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_braces_as_or
|
88
|
+
ast = GmailSearchSyntax.parse!("{from:amy from:david}")
|
89
|
+
assert_instance_of Or, ast
|
90
|
+
|
91
|
+
assert_equal 2, ast.operands.length
|
92
|
+
assert_instance_of Operator, ast.operands[0]
|
93
|
+
assert_equal "from", ast.operands[0].name
|
94
|
+
assert_equal "amy", ast.operands[0].value
|
95
|
+
|
96
|
+
assert_instance_of Operator, ast.operands[1]
|
97
|
+
assert_equal "from", ast.operands[1].name
|
98
|
+
assert_equal "david", ast.operands[1].value
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_and_operator
|
102
|
+
ast = GmailSearchSyntax.parse!("from:amy AND to:david")
|
103
|
+
assert_instance_of And, ast
|
104
|
+
|
105
|
+
assert_equal 2, ast.operands.length
|
106
|
+
assert_instance_of Operator, ast.operands[0]
|
107
|
+
assert_equal "from", ast.operands[0].name
|
108
|
+
assert_equal "amy", ast.operands[0].value
|
109
|
+
|
110
|
+
assert_instance_of Operator, ast.operands[1]
|
111
|
+
assert_equal "to", ast.operands[1].name
|
112
|
+
assert_equal "david", ast.operands[1].value
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_implicit_and
|
116
|
+
ast = GmailSearchSyntax.parse!("from:amy to:david")
|
117
|
+
assert_instance_of And, ast
|
118
|
+
|
119
|
+
assert_equal 2, ast.operands.length
|
120
|
+
assert_instance_of Operator, ast.operands[0]
|
121
|
+
assert_equal "from", ast.operands[0].name
|
122
|
+
|
123
|
+
assert_instance_of Operator, ast.operands[1]
|
124
|
+
assert_equal "to", ast.operands[1].name
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_negation_with_minus
|
128
|
+
ast = GmailSearchSyntax.parse!("dinner -movie")
|
129
|
+
assert_instance_of And, ast
|
130
|
+
|
131
|
+
assert_equal 2, ast.operands.length
|
132
|
+
assert_instance_of Text, ast.operands[0]
|
133
|
+
assert_equal "dinner", ast.operands[0].value
|
134
|
+
|
135
|
+
assert_instance_of Not, ast.operands[1]
|
136
|
+
assert_instance_of Text, ast.operands[1].child
|
137
|
+
assert_equal "movie", ast.operands[1].child.value
|
138
|
+
end
|
139
|
+
|
140
|
+
def test_around_operator
|
141
|
+
ast = GmailSearchSyntax.parse!("holiday AROUND 10 vacation")
|
142
|
+
assert_instance_of Around, ast
|
143
|
+
|
144
|
+
assert_instance_of Text, ast.left
|
145
|
+
assert_equal "holiday", ast.left.value
|
146
|
+
assert_equal 10, ast.distance
|
147
|
+
|
148
|
+
assert_instance_of Text, ast.right
|
149
|
+
assert_equal "vacation", ast.right.value
|
150
|
+
end
|
151
|
+
|
152
|
+
def test_around_with_quoted_string
|
153
|
+
ast = GmailSearchSyntax.parse!('"secret AROUND 25 birthday"')
|
154
|
+
assert_instance_of Text, ast
|
155
|
+
assert_equal "secret AROUND 25 birthday", ast.value
|
156
|
+
end
|
157
|
+
|
158
|
+
def test_label_operator
|
159
|
+
ast = GmailSearchSyntax.parse!("label:friends")
|
160
|
+
assert_instance_of Operator, ast
|
161
|
+
assert_equal "label", ast.name
|
162
|
+
assert_equal "friends", ast.value
|
163
|
+
end
|
164
|
+
|
165
|
+
def test_category_operator
|
166
|
+
ast = GmailSearchSyntax.parse!("category:primary")
|
167
|
+
assert_instance_of Operator, ast
|
168
|
+
assert_equal "category", ast.name
|
169
|
+
assert_equal "primary", ast.value
|
170
|
+
end
|
171
|
+
|
172
|
+
def test_has_attachment
|
173
|
+
ast = GmailSearchSyntax.parse!("has:attachment")
|
174
|
+
assert_instance_of Operator, ast
|
175
|
+
assert_equal "has", ast.name
|
176
|
+
assert_equal "attachment", ast.value
|
177
|
+
end
|
178
|
+
|
179
|
+
def test_filename_operator
|
180
|
+
ast = GmailSearchSyntax.parse!("filename:pdf")
|
181
|
+
assert_instance_of Operator, ast
|
182
|
+
assert_equal "filename", ast.name
|
183
|
+
assert_equal "pdf", ast.value
|
184
|
+
end
|
185
|
+
|
186
|
+
def test_filename_with_extension
|
187
|
+
ast = GmailSearchSyntax.parse!("filename:homework.txt")
|
188
|
+
assert_instance_of Operator, ast
|
189
|
+
assert_equal "filename", ast.name
|
190
|
+
assert_equal "homework.txt", ast.value
|
191
|
+
end
|
192
|
+
|
193
|
+
def test_quoted_exact_phrase
|
194
|
+
ast = GmailSearchSyntax.parse!('"dinner and movie tonight"')
|
195
|
+
assert_instance_of Text, ast
|
196
|
+
assert_equal "dinner and movie tonight", ast.value
|
197
|
+
end
|
198
|
+
|
199
|
+
def test_parentheses_grouping
|
200
|
+
ast = GmailSearchSyntax.parse!("subject:(dinner movie)")
|
201
|
+
assert_instance_of Operator, ast
|
202
|
+
assert_equal "subject", ast.name
|
203
|
+
|
204
|
+
assert_instance_of And, ast.value
|
205
|
+
assert_equal 2, ast.value.operands.length
|
206
|
+
assert_instance_of Text, ast.value.operands[0]
|
207
|
+
assert_equal "dinner", ast.value.operands[0].value
|
208
|
+
assert_instance_of Text, ast.value.operands[1]
|
209
|
+
assert_equal "movie", ast.value.operands[1].value
|
210
|
+
end
|
211
|
+
|
212
|
+
def test_in_anywhere
|
213
|
+
ast = GmailSearchSyntax.parse!("in:anywhere movie")
|
214
|
+
assert_instance_of And, ast
|
215
|
+
|
216
|
+
assert_equal 2, ast.operands.length
|
217
|
+
assert_instance_of Operator, ast.operands[0]
|
218
|
+
assert_equal "in", ast.operands[0].name
|
219
|
+
assert_equal "anywhere", ast.operands[0].value
|
220
|
+
|
221
|
+
assert_instance_of Text, ast.operands[1]
|
222
|
+
assert_equal "movie", ast.operands[1].value
|
223
|
+
end
|
224
|
+
|
225
|
+
def test_is_starred
|
226
|
+
ast = GmailSearchSyntax.parse!("is:starred")
|
227
|
+
assert_instance_of Operator, ast
|
228
|
+
assert_equal "is", ast.name
|
229
|
+
assert_equal "starred", ast.value
|
230
|
+
end
|
231
|
+
|
232
|
+
def test_is_unread
|
233
|
+
ast = GmailSearchSyntax.parse!("is:unread")
|
234
|
+
assert_instance_of Operator, ast
|
235
|
+
assert_equal "is", ast.name
|
236
|
+
assert_equal "unread", ast.value
|
237
|
+
end
|
238
|
+
|
239
|
+
def test_size_operator
|
240
|
+
ast = GmailSearchSyntax.parse!("size:1000000")
|
241
|
+
assert_instance_of Operator, ast
|
242
|
+
assert_equal "size", ast.name
|
243
|
+
assert_equal 1000000, ast.value
|
244
|
+
end
|
245
|
+
|
246
|
+
def test_larger_operator
|
247
|
+
ast = GmailSearchSyntax.parse!("larger:10M")
|
248
|
+
assert_instance_of Operator, ast
|
249
|
+
assert_equal "larger", ast.name
|
250
|
+
assert_equal "10M", ast.value
|
251
|
+
end
|
252
|
+
|
253
|
+
def test_complex_query_with_multiple_operators
|
254
|
+
ast = GmailSearchSyntax.parse!("from:amy subject:meeting has:attachment")
|
255
|
+
assert_instance_of And, ast
|
256
|
+
|
257
|
+
assert_equal 3, ast.operands.length
|
258
|
+
assert_instance_of Operator, ast.operands[0]
|
259
|
+
assert_equal "from", ast.operands[0].name
|
260
|
+
|
261
|
+
assert_instance_of Operator, ast.operands[1]
|
262
|
+
assert_equal "subject", ast.operands[1].name
|
263
|
+
|
264
|
+
assert_instance_of Operator, ast.operands[2]
|
265
|
+
assert_equal "has", ast.operands[2].name
|
266
|
+
end
|
267
|
+
|
268
|
+
def test_complex_or_and_combination
|
269
|
+
ast = GmailSearchSyntax.parse!("from:amy OR from:bob to:me")
|
270
|
+
assert_instance_of Or, ast
|
271
|
+
|
272
|
+
assert_equal 2, ast.operands.length
|
273
|
+
assert_instance_of Operator, ast.operands[0]
|
274
|
+
|
275
|
+
assert_instance_of And, ast.operands[1]
|
276
|
+
assert_equal 2, ast.operands[1].operands.length
|
277
|
+
assert_instance_of Operator, ast.operands[1].operands[0]
|
278
|
+
assert_equal "from", ast.operands[1].operands[0].name
|
279
|
+
assert_equal "bob", ast.operands[1].operands[0].value
|
280
|
+
|
281
|
+
assert_instance_of Operator, ast.operands[1].operands[1]
|
282
|
+
assert_equal "to", ast.operands[1].operands[1].name
|
283
|
+
end
|
284
|
+
|
285
|
+
def test_negation_with_operator
|
286
|
+
ast = GmailSearchSyntax.parse!("-from:spam@example.com")
|
287
|
+
assert_instance_of Not, ast
|
288
|
+
assert_instance_of Operator, ast.child
|
289
|
+
assert_equal "from", ast.child.name
|
290
|
+
assert_equal "spam@example.com", ast.child.value
|
291
|
+
end
|
292
|
+
|
293
|
+
def test_list_operator
|
294
|
+
ast = GmailSearchSyntax.parse!("list:info@example.com")
|
295
|
+
assert_instance_of Operator, ast
|
296
|
+
assert_equal "list", ast.name
|
297
|
+
assert_equal "info@example.com", ast.value
|
298
|
+
end
|
299
|
+
|
300
|
+
def test_deliveredto_operator
|
301
|
+
ast = GmailSearchSyntax.parse!("deliveredto:username@example.com")
|
302
|
+
assert_instance_of Operator, ast
|
303
|
+
assert_equal "deliveredto", ast.name
|
304
|
+
assert_equal "username@example.com", ast.value
|
305
|
+
end
|
306
|
+
|
307
|
+
def test_rfc822msgid_operator
|
308
|
+
ast = GmailSearchSyntax.parse!("rfc822msgid:200503292@example.com")
|
309
|
+
assert_instance_of Operator, ast
|
310
|
+
assert_equal "rfc822msgid", ast.name
|
311
|
+
assert_equal "200503292@example.com", ast.value
|
312
|
+
end
|
313
|
+
|
314
|
+
def test_cc_operator
|
315
|
+
ast = GmailSearchSyntax.parse!("cc:john@example.com")
|
316
|
+
assert_instance_of Operator, ast
|
317
|
+
assert_equal "cc", ast.name
|
318
|
+
assert_equal "john@example.com", ast.value
|
319
|
+
end
|
320
|
+
|
321
|
+
def test_bcc_operator
|
322
|
+
ast = GmailSearchSyntax.parse!("bcc:david@example.com")
|
323
|
+
assert_instance_of Operator, ast
|
324
|
+
assert_equal "bcc", ast.name
|
325
|
+
assert_equal "david@example.com", ast.value
|
326
|
+
end
|
327
|
+
|
328
|
+
def test_plain_text_search
|
329
|
+
ast = GmailSearchSyntax.parse!("meeting")
|
330
|
+
assert_instance_of Text, ast
|
331
|
+
assert_equal "meeting", ast.value
|
332
|
+
end
|
333
|
+
|
334
|
+
def test_multiple_plain_text_words
|
335
|
+
ast = GmailSearchSyntax.parse!("project report")
|
336
|
+
assert_instance_of And, ast
|
337
|
+
|
338
|
+
assert_equal 2, ast.operands.length
|
339
|
+
assert_instance_of Text, ast.operands[0]
|
340
|
+
assert_equal "project", ast.operands[0].value
|
341
|
+
|
342
|
+
assert_instance_of Text, ast.operands[1]
|
343
|
+
assert_equal "report", ast.operands[1].value
|
344
|
+
end
|
345
|
+
|
346
|
+
def test_empty_query
|
347
|
+
error = assert_raises(GmailSearchSyntax::EmptyQueryError) do
|
348
|
+
GmailSearchSyntax.parse!("")
|
349
|
+
end
|
350
|
+
assert_equal "Query cannot be empty", error.message
|
351
|
+
end
|
352
|
+
|
353
|
+
def test_whitespace_only
|
354
|
+
error = assert_raises(GmailSearchSyntax::EmptyQueryError) do
|
355
|
+
GmailSearchSyntax.parse!(" ")
|
356
|
+
end
|
357
|
+
assert_equal "Query cannot be empty", error.message
|
358
|
+
end
|
359
|
+
|
360
|
+
def test_nested_parentheses_with_operators
|
361
|
+
ast = GmailSearchSyntax.parse!("from:amy (subject:meeting OR subject:call)")
|
362
|
+
assert_instance_of And, ast
|
363
|
+
|
364
|
+
assert_equal 2, ast.operands.length
|
365
|
+
assert_instance_of Operator, ast.operands[0]
|
366
|
+
assert_equal "from", ast.operands[0].name
|
367
|
+
|
368
|
+
assert_instance_of Or, ast.operands[1]
|
369
|
+
end
|
370
|
+
|
371
|
+
def test_multiple_negations
|
372
|
+
ast = GmailSearchSyntax.parse!("-from:spam -subject:junk")
|
373
|
+
assert_instance_of And, ast
|
374
|
+
|
375
|
+
assert_equal 2, ast.operands.length
|
376
|
+
assert_instance_of Not, ast.operands[0]
|
377
|
+
assert_instance_of Operator, ast.operands[0].child
|
378
|
+
assert_equal "from", ast.operands[0].child.name
|
379
|
+
|
380
|
+
assert_instance_of Not, ast.operands[1]
|
381
|
+
assert_instance_of Operator, ast.operands[1].child
|
382
|
+
assert_equal "subject", ast.operands[1].child.name
|
383
|
+
end
|
384
|
+
|
385
|
+
def test_or_with_three_terms
|
386
|
+
ast = GmailSearchSyntax.parse!("{from:a from:b from:c}")
|
387
|
+
assert_instance_of Or, ast
|
388
|
+
|
389
|
+
assert_equal 3, ast.operands.length
|
390
|
+
assert_instance_of Operator, ast.operands[0]
|
391
|
+
assert_equal "a", ast.operands[0].value
|
392
|
+
assert_instance_of Operator, ast.operands[1]
|
393
|
+
assert_equal "b", ast.operands[1].value
|
394
|
+
assert_instance_of Operator, ast.operands[2]
|
395
|
+
assert_equal "c", ast.operands[2].value
|
396
|
+
end
|
397
|
+
|
398
|
+
def test_complex_mixed_query
|
399
|
+
ast = GmailSearchSyntax.parse!("from:boss subject:urgent has:attachment -label:archive")
|
400
|
+
assert_instance_of And, ast
|
401
|
+
|
402
|
+
assert_equal 4, ast.operands.length
|
403
|
+
|
404
|
+
assert_instance_of Operator, ast.operands[0]
|
405
|
+
assert_equal "from", ast.operands[0].name
|
406
|
+
|
407
|
+
assert_instance_of Operator, ast.operands[1]
|
408
|
+
assert_equal "subject", ast.operands[1].name
|
409
|
+
|
410
|
+
assert_instance_of Operator, ast.operands[2]
|
411
|
+
assert_equal "has", ast.operands[2].name
|
412
|
+
|
413
|
+
assert_instance_of Not, ast.operands[3]
|
414
|
+
assert_instance_of Operator, ast.operands[3].child
|
415
|
+
assert_equal "label", ast.operands[3].child.name
|
416
|
+
end
|
417
|
+
|
418
|
+
def test_quoted_string_with_operators_inside
|
419
|
+
ast = GmailSearchSyntax.parse!('"from:amy to:bob"')
|
420
|
+
assert_instance_of Text, ast
|
421
|
+
assert_equal "from:amy to:bob", ast.value
|
422
|
+
end
|
423
|
+
|
424
|
+
def test_email_with_plus_sign
|
425
|
+
ast = GmailSearchSyntax.parse!("to:user+tag@example.com")
|
426
|
+
assert_instance_of Operator, ast
|
427
|
+
assert_equal "to", ast.name
|
428
|
+
assert_equal "user+tag@example.com", ast.value
|
429
|
+
end
|
430
|
+
|
431
|
+
def test_in_operator_with_location
|
432
|
+
ast = GmailSearchSyntax.parse!("in:inbox from:manager")
|
433
|
+
assert_instance_of And, ast
|
434
|
+
|
435
|
+
assert_equal 2, ast.operands.length
|
436
|
+
assert_instance_of Operator, ast.operands[0]
|
437
|
+
assert_equal "in", ast.operands[0].name
|
438
|
+
assert_equal "inbox", ast.operands[0].value
|
439
|
+
end
|
440
|
+
|
441
|
+
def test_has_drive_operator
|
442
|
+
ast = GmailSearchSyntax.parse!("has:drive")
|
443
|
+
assert_instance_of Operator, ast
|
444
|
+
assert_equal "has", ast.name
|
445
|
+
assert_equal "drive", ast.value
|
446
|
+
end
|
447
|
+
|
448
|
+
def test_category_updates
|
449
|
+
ast = GmailSearchSyntax.parse!("category:updates")
|
450
|
+
assert_instance_of Operator, ast
|
451
|
+
assert_equal "category", ast.name
|
452
|
+
assert_equal "updates", ast.value
|
453
|
+
end
|
454
|
+
|
455
|
+
def test_around_default_distance
|
456
|
+
ast = GmailSearchSyntax.parse!("meeting AROUND project")
|
457
|
+
assert_instance_of Around, ast
|
458
|
+
assert_equal 5, ast.distance
|
459
|
+
end
|
460
|
+
|
461
|
+
def test_parentheses_with_single_term
|
462
|
+
ast = GmailSearchSyntax.parse!("(meeting)")
|
463
|
+
assert_instance_of Text, ast
|
464
|
+
assert_equal "meeting", ast.value
|
465
|
+
end
|
466
|
+
|
467
|
+
def test_subject_with_parentheses_multiple_words
|
468
|
+
ast = GmailSearchSyntax.parse!("subject:(project status update)")
|
469
|
+
assert_instance_of Operator, ast
|
470
|
+
assert_equal "subject", ast.name
|
471
|
+
|
472
|
+
assert_instance_of And, ast.value
|
473
|
+
assert_equal 3, ast.value.operands.length
|
474
|
+
assert_instance_of Text, ast.value.operands[0]
|
475
|
+
assert_equal "project", ast.value.operands[0].value
|
476
|
+
assert_instance_of Text, ast.value.operands[1]
|
477
|
+
assert_equal "status", ast.value.operands[1].value
|
478
|
+
assert_instance_of Text, ast.value.operands[2]
|
479
|
+
assert_equal "update", ast.value.operands[2].value
|
480
|
+
end
|
481
|
+
|
482
|
+
def test_and_explicit_with_text
|
483
|
+
ast = GmailSearchSyntax.parse!("meeting AND project")
|
484
|
+
assert_instance_of And, ast
|
485
|
+
|
486
|
+
assert_equal 2, ast.operands.length
|
487
|
+
assert_instance_of Text, ast.operands[0]
|
488
|
+
assert_equal "meeting", ast.operands[0].value
|
489
|
+
|
490
|
+
assert_instance_of Text, ast.operands[1]
|
491
|
+
assert_equal "project", ast.operands[1].value
|
492
|
+
end
|
493
|
+
|
494
|
+
def test_smaller_operator
|
495
|
+
ast = GmailSearchSyntax.parse!("smaller:1M")
|
496
|
+
assert_instance_of Operator, ast
|
497
|
+
assert_equal "smaller", ast.name
|
498
|
+
assert_equal "1M", ast.value
|
499
|
+
end
|
500
|
+
|
501
|
+
def test_or_inside_operator_value
|
502
|
+
ast = GmailSearchSyntax.parse!("from:(mischa@ OR julik@)")
|
503
|
+
assert_instance_of Operator, ast
|
504
|
+
assert_equal "from", ast.name
|
505
|
+
|
506
|
+
assert_instance_of Or, ast.value
|
507
|
+
assert_equal 2, ast.value.operands.length
|
508
|
+
assert_instance_of Text, ast.value.operands[0]
|
509
|
+
assert_equal "mischa@", ast.value.operands[0].value
|
510
|
+
assert_instance_of Text, ast.value.operands[1]
|
511
|
+
assert_equal "julik@", ast.value.operands[1].value
|
512
|
+
end
|
513
|
+
|
514
|
+
def test_or_with_emails_inside_operator
|
515
|
+
ast = GmailSearchSyntax.parse!("from:(amy@example.com OR bob@example.com)")
|
516
|
+
assert_instance_of Operator, ast
|
517
|
+
assert_equal "from", ast.name
|
518
|
+
|
519
|
+
assert_instance_of Or, ast.value
|
520
|
+
assert_equal 2, ast.value.operands.length
|
521
|
+
assert_instance_of Text, ast.value.operands[0]
|
522
|
+
assert_equal "amy@example.com", ast.value.operands[0].value
|
523
|
+
assert_instance_of Text, ast.value.operands[1]
|
524
|
+
assert_equal "bob@example.com", ast.value.operands[1].value
|
525
|
+
end
|
526
|
+
|
527
|
+
def test_multiple_or_inside_operator
|
528
|
+
ast = GmailSearchSyntax.parse!("from:(a@ OR b@ OR c@)")
|
529
|
+
assert_instance_of Operator, ast
|
530
|
+
assert_equal "from", ast.name
|
531
|
+
|
532
|
+
assert_instance_of Or, ast.value
|
533
|
+
assert_equal 3, ast.value.operands.length
|
534
|
+
assert_equal "a@", ast.value.operands[0].value
|
535
|
+
assert_equal "b@", ast.value.operands[1].value
|
536
|
+
assert_equal "c@", ast.value.operands[2].value
|
537
|
+
end
|
538
|
+
|
539
|
+
def test_and_inside_operator_value
|
540
|
+
ast = GmailSearchSyntax.parse!("subject:(urgent AND meeting)")
|
541
|
+
assert_instance_of Operator, ast
|
542
|
+
assert_equal "subject", ast.name
|
543
|
+
|
544
|
+
assert_instance_of And, ast.value
|
545
|
+
assert_equal 2, ast.value.operands.length
|
546
|
+
assert_instance_of Text, ast.value.operands[0]
|
547
|
+
assert_equal "urgent", ast.value.operands[0].value
|
548
|
+
assert_instance_of Text, ast.value.operands[1]
|
549
|
+
assert_equal "meeting", ast.value.operands[1].value
|
550
|
+
end
|
551
|
+
|
552
|
+
def test_operator_with_or_combined_with_other_conditions
|
553
|
+
ast = GmailSearchSyntax.parse!("from:(alice@ OR bob@) subject:meeting")
|
554
|
+
assert_instance_of And, ast
|
555
|
+
|
556
|
+
assert_equal 2, ast.operands.length
|
557
|
+
assert_instance_of Operator, ast.operands[0]
|
558
|
+
assert_equal "from", ast.operands[0].name
|
559
|
+
assert_instance_of Or, ast.operands[0].value
|
560
|
+
|
561
|
+
assert_instance_of Operator, ast.operands[1]
|
562
|
+
assert_equal "subject", ast.operands[1].name
|
563
|
+
assert_equal "meeting", ast.operands[1].value
|
564
|
+
end
|
565
|
+
|
566
|
+
def test_negation_inside_operator_value
|
567
|
+
ast = GmailSearchSyntax.parse!("subject:(meeting -cancelled)")
|
568
|
+
assert_instance_of Operator, ast
|
569
|
+
assert_equal "subject", ast.name
|
570
|
+
|
571
|
+
assert_instance_of And, ast.value
|
572
|
+
assert_equal 2, ast.value.operands.length
|
573
|
+
assert_instance_of Text, ast.value.operands[0]
|
574
|
+
assert_equal "meeting", ast.value.operands[0].value
|
575
|
+
assert_instance_of Not, ast.value.operands[1]
|
576
|
+
assert_equal "cancelled", ast.value.operands[1].child.value
|
577
|
+
end
|
578
|
+
|
579
|
+
def test_complex_expression_inside_operator
|
580
|
+
ast = GmailSearchSyntax.parse!("from:(alice@ OR bob@) to:(charlie@ OR david@)")
|
581
|
+
assert_instance_of And, ast
|
582
|
+
|
583
|
+
assert_equal 2, ast.operands.length
|
584
|
+
|
585
|
+
assert_instance_of Operator, ast.operands[0]
|
586
|
+
assert_equal "from", ast.operands[0].name
|
587
|
+
assert_instance_of Or, ast.operands[0].value
|
588
|
+
assert_equal 2, ast.operands[0].value.operands.length
|
589
|
+
|
590
|
+
assert_instance_of Operator, ast.operands[1]
|
591
|
+
assert_equal "to", ast.operands[1].name
|
592
|
+
assert_instance_of Or, ast.operands[1].value
|
593
|
+
assert_equal 2, ast.operands[1].value.operands.length
|
594
|
+
end
|
595
|
+
|
596
|
+
def test_nested_parentheses_in_operator_value
|
597
|
+
ast = GmailSearchSyntax.parse!("subject:((urgent OR important) meeting)")
|
598
|
+
assert_instance_of Operator, ast
|
599
|
+
assert_equal "subject", ast.name
|
600
|
+
|
601
|
+
assert_instance_of And, ast.value
|
602
|
+
assert_equal 2, ast.value.operands.length
|
603
|
+
assert_instance_of Or, ast.value.operands[0]
|
604
|
+
assert_equal 2, ast.value.operands[0].operands.length
|
605
|
+
assert_equal "urgent", ast.value.operands[0].operands[0].value
|
606
|
+
assert_equal "important", ast.value.operands[0].operands[1].value
|
607
|
+
assert_instance_of Text, ast.value.operands[1]
|
608
|
+
assert_equal "meeting", ast.value.operands[1].value
|
609
|
+
end
|
610
|
+
|
611
|
+
def test_curly_braces_inside_operator_value
|
612
|
+
ast = GmailSearchSyntax.parse!("from:{mischa@ marc@}")
|
613
|
+
assert_instance_of Operator, ast
|
614
|
+
assert_equal "from", ast.name
|
615
|
+
|
616
|
+
assert_instance_of Or, ast.value
|
617
|
+
assert_equal 2, ast.value.operands.length
|
618
|
+
assert_instance_of Text, ast.value.operands[0]
|
619
|
+
assert_equal "mischa@", ast.value.operands[0].value
|
620
|
+
assert_instance_of Text, ast.value.operands[1]
|
621
|
+
assert_equal "marc@", ast.value.operands[1].value
|
622
|
+
end
|
623
|
+
|
624
|
+
def test_curly_braces_with_emails_inside_operator
|
625
|
+
ast = GmailSearchSyntax.parse!("from:{amy@example.com bob@example.com}")
|
626
|
+
assert_instance_of Operator, ast
|
627
|
+
assert_equal "from", ast.name
|
628
|
+
|
629
|
+
assert_instance_of Or, ast.value
|
630
|
+
assert_equal 2, ast.value.operands.length
|
631
|
+
assert_equal "amy@example.com", ast.value.operands[0].value
|
632
|
+
assert_equal "bob@example.com", ast.value.operands[1].value
|
633
|
+
end
|
634
|
+
|
635
|
+
def test_multiple_items_in_curly_braces
|
636
|
+
ast = GmailSearchSyntax.parse!("from:{a@ b@ c@ d@}")
|
637
|
+
assert_instance_of Operator, ast
|
638
|
+
assert_equal "from", ast.name
|
639
|
+
|
640
|
+
assert_instance_of Or, ast.value
|
641
|
+
assert_equal 4, ast.value.operands.length
|
642
|
+
assert_equal "a@", ast.value.operands[0].value
|
643
|
+
assert_equal "b@", ast.value.operands[1].value
|
644
|
+
assert_equal "c@", ast.value.operands[2].value
|
645
|
+
assert_equal "d@", ast.value.operands[3].value
|
646
|
+
end
|
647
|
+
|
648
|
+
def test_curly_braces_combined_with_other_conditions
|
649
|
+
ast = GmailSearchSyntax.parse!("from:{alice@ bob@} subject:meeting")
|
650
|
+
assert_instance_of And, ast
|
651
|
+
|
652
|
+
assert_equal 2, ast.operands.length
|
653
|
+
assert_instance_of Operator, ast.operands[0]
|
654
|
+
assert_equal "from", ast.operands[0].name
|
655
|
+
assert_instance_of Or, ast.operands[0].value
|
656
|
+
assert_equal 2, ast.operands[0].value.operands.length
|
657
|
+
|
658
|
+
assert_instance_of Operator, ast.operands[1]
|
659
|
+
assert_equal "subject", ast.operands[1].name
|
660
|
+
assert_equal "meeting", ast.operands[1].value
|
661
|
+
end
|
662
|
+
|
663
|
+
def test_multiple_operators_with_curly_braces
|
664
|
+
ast = GmailSearchSyntax.parse!("from:{alice@ bob@} to:{charlie@ david@}")
|
665
|
+
assert_instance_of And, ast
|
666
|
+
|
667
|
+
assert_equal 2, ast.operands.length
|
668
|
+
|
669
|
+
assert_instance_of Operator, ast.operands[0]
|
670
|
+
assert_equal "from", ast.operands[0].name
|
671
|
+
assert_instance_of Or, ast.operands[0].value
|
672
|
+
|
673
|
+
assert_instance_of Operator, ast.operands[1]
|
674
|
+
assert_equal "to", ast.operands[1].name
|
675
|
+
assert_instance_of Or, ast.operands[1].value
|
676
|
+
end
|
677
|
+
|
678
|
+
def test_mixing_parentheses_and_curly_braces
|
679
|
+
ast = GmailSearchSyntax.parse!("from:{alice@ bob@} subject:(urgent meeting)")
|
680
|
+
assert_instance_of And, ast
|
681
|
+
|
682
|
+
assert_equal 2, ast.operands.length
|
683
|
+
assert_instance_of Operator, ast.operands[0]
|
684
|
+
assert_equal "from", ast.operands[0].name
|
685
|
+
assert_instance_of Or, ast.operands[0].value
|
686
|
+
|
687
|
+
assert_instance_of Operator, ast.operands[1]
|
688
|
+
assert_equal "subject", ast.operands[1].name
|
689
|
+
assert_instance_of And, ast.operands[1].value
|
690
|
+
end
|
691
|
+
end
|