mailmate 1.8.0 → 1.8.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.
- checksums.yaml +4 -4
- data/lib/mailmate/search_syntax.rb +73 -7
- data/lib/mailmate/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2563290d07b7e90281a78c4161337f8fe94a21d4cd1f42501946a6c8107be5da
|
|
4
|
+
data.tar.gz: 04600cc5e50d4a4518ddcd27cc210d357decfeefb32e8adc1e3bb1624af17781
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d4619d78ed6224df84378ad42cff61865653e32e463b8c8b3ad1349549f32c8bf74c6635e64f377653231d3a93afc087fba18cf6da9ad2897fe97c68e096d58a
|
|
7
|
+
data.tar.gz: a1437ae600957dedc21446c151cbc6061d1def590693f83359fcd79f5d1dd36e57bcbad4ab9ecb85a7621910119a14d30b21f97eb4b90aebc826b3dc9a0f873e
|
|
@@ -69,8 +69,24 @@ module Mailmate
|
|
|
69
69
|
date sent received time
|
|
70
70
|
from to cc bcc subject body
|
|
71
71
|
in label folder mailbox category filename
|
|
72
|
+
date_last_viewed
|
|
72
73
|
].freeze
|
|
73
74
|
|
|
75
|
+
# MailMate's OWN attribute vocabulary, minus the leading `#` — a model
|
|
76
|
+
# that has seen the engine's internals or MailMate's docs reaches for
|
|
77
|
+
# `date-received:` in good faith (dogfood 2026-08-13: an agent queried
|
|
78
|
+
# `date-received:8/13/2026-8/14/2026`, got zero rows with no advisory,
|
|
79
|
+
# and reported the mailbox empty). Keys are normalized (`-` → `_`,
|
|
80
|
+
# lowercased) and then aliased to the base foreign key here, so a
|
|
81
|
+
# compound key flows through the SAME translation and flagging pipeline
|
|
82
|
+
# as its base — nothing downstream knows compounds exist.
|
|
83
|
+
# `date_last_viewed` has no faithful `d` equivalent (viewed ≠ received),
|
|
84
|
+
# so it is only flagged (via FOREIGN_KEYS above), never translated.
|
|
85
|
+
KEY_ALIASES = {
|
|
86
|
+
"date_received" => "date", "received_date" => "date", "datereceived" => "date",
|
|
87
|
+
"date_sent" => "sent", "sent_date" => "sent", "datesent" => "sent", "sentdate" => "sent",
|
|
88
|
+
}.freeze
|
|
89
|
+
|
|
74
90
|
# Spec placeholders for the zero-result hint, for foreign keys whose value
|
|
75
91
|
# translate() could NOT rewrite (e.g. `after:8am`). Keys absent here still
|
|
76
92
|
# get flagged, just without a suggested rewrite.
|
|
@@ -101,6 +117,8 @@ module Mailmate
|
|
|
101
117
|
["newer_than:2d / older_than:2w", "d 2d / d !2w"],
|
|
102
118
|
["after:2026-05 or since:2026-05", "d >=2026-05"],
|
|
103
119
|
["before:2026-08 / until:2026-08", "d <2026-08 / d <=2026-08"],
|
|
120
|
+
["date:8/13/2026-8/14/2026 (also A..B)", "d >=2026-08-13 d <=2026-08-14"],
|
|
121
|
+
["date-received: / date-sent: (MailMate heads)", "date: / sent:"],
|
|
104
122
|
].freeze
|
|
105
123
|
|
|
106
124
|
module_function
|
|
@@ -123,7 +141,7 @@ module Mailmate
|
|
|
123
141
|
# One token: an (optionally key:-prefixed) quoted string, or a bare run
|
|
124
142
|
# of non-space. Quoted regions survive as single tokens so translate()
|
|
125
143
|
# can leave a deliberate literal search (`s "date:today"`) alone.
|
|
126
|
-
TOKEN_RX = /(?:[-!]?[A-Za-z_]
|
|
144
|
+
TOKEN_RX = /(?:[-!]?[A-Za-z_][A-Za-z_-]*:)?"[^"]*"|(?:[-!]?[A-Za-z_][A-Za-z_-]*:)?'[^']*'|\S+/
|
|
127
145
|
|
|
128
146
|
# Rewrite foreign `key:value` tokens to their exact quicksearch
|
|
129
147
|
# equivalent, leaving everything else byte-for-byte intact. Returns
|
|
@@ -170,8 +188,8 @@ module Mailmate
|
|
|
170
188
|
# search for the literal text `s "date:today"` is not a mistake.
|
|
171
189
|
def foreign_tokens(query)
|
|
172
190
|
unquoted = query.to_s.gsub(/"[^"]*"|'[^']*'/, " ")
|
|
173
|
-
unquoted.scan(/(?<![\w-])!?([A-Za-z_]
|
|
174
|
-
k = key
|
|
191
|
+
unquoted.scan(/(?<![\w-])!?([A-Za-z_][A-Za-z_-]*):(\S*)/).filter_map do |key, value|
|
|
192
|
+
k = normalize_key(key)
|
|
175
193
|
next unless FOREIGN_KEYS.include?(k)
|
|
176
194
|
["#{key}:#{value}", k]
|
|
177
195
|
end.uniq { |_token, k| k }
|
|
@@ -198,13 +216,21 @@ module Mailmate
|
|
|
198
216
|
|
|
199
217
|
# ---- translation internals -------------------------------------------
|
|
200
218
|
|
|
219
|
+
# `Date-Received` / `date_received` / `datereceived` all mean the same
|
|
220
|
+
# thing to the person typing them; collapse to one spelling, then map
|
|
221
|
+
# MailMate-head aliases onto their base key.
|
|
222
|
+
def normalize_key(raw)
|
|
223
|
+
k = raw.downcase.tr("-", "_")
|
|
224
|
+
KEY_ALIASES.fetch(k, k)
|
|
225
|
+
end
|
|
226
|
+
|
|
201
227
|
# nil = not a rewritable token (not key:value, unknown key, or a value
|
|
202
228
|
# with no faithful equivalent).
|
|
203
229
|
def translate_token(token, today, european = false)
|
|
204
|
-
m = token.match(/\A(?<neg>[-!])?(?<key>[A-Za-z_]
|
|
230
|
+
m = token.match(/\A(?<neg>[-!])?(?<key>[A-Za-z_][A-Za-z_-]*):(?<value>.+)\z/m)
|
|
205
231
|
return nil unless m
|
|
206
232
|
|
|
207
|
-
key = m[:key]
|
|
233
|
+
key = normalize_key(m[:key])
|
|
208
234
|
value = unquote(m[:value])
|
|
209
235
|
return nil if value.empty?
|
|
210
236
|
|
|
@@ -255,11 +281,51 @@ module Mailmate
|
|
|
255
281
|
return translate_after(v.delete_suffix("-today"), today, european) if v.end_with?("-today")
|
|
256
282
|
return "d #{v}" if v =~ /\A\d+[dwmy]\z/
|
|
257
283
|
|
|
258
|
-
(period = normalize_period(v, european))
|
|
284
|
+
if (period = normalize_period(v, european))
|
|
285
|
+
"d #{period}"
|
|
286
|
+
else
|
|
287
|
+
translate_range(v, today, european)
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# A two-sided absolute range (`8/13/2026-8/14/2026`, `2026-08-13..2026-08-14`)
|
|
292
|
+
# → the comparison pair `d >=A d <=B`. Inclusive on both ends: "the 13th
|
|
293
|
+
# to the 14th" includes the 14th in plain reading. Only fires when BOTH
|
|
294
|
+
# sides resolve — a side we can't parse means this isn't a range we
|
|
295
|
+
# understand, and no translation beats a wrong one. A reversed range
|
|
296
|
+
# still translates: the engine rejects an impossible date combination
|
|
297
|
+
# with an explicit error, which is exactly the visibility we want.
|
|
298
|
+
def translate_range(value, today, european)
|
|
299
|
+
bounds =
|
|
300
|
+
if value.include?("..")
|
|
301
|
+
a, b = value.split("..", 2)
|
|
302
|
+
[resolve_bound(a, today, european), resolve_bound(b, today, european)]
|
|
303
|
+
else
|
|
304
|
+
# Dates carry hyphens themselves (2026-08-13), so try each hyphen
|
|
305
|
+
# as the separator and take the first split where both sides parse.
|
|
306
|
+
value.length.times.filter_map { |i|
|
|
307
|
+
next unless value[i] == "-"
|
|
308
|
+
left = resolve_bound(value[0...i], today, european)
|
|
309
|
+
right = resolve_bound(value[(i + 1)..], today, european)
|
|
310
|
+
[left, right] if left && right
|
|
311
|
+
}.first
|
|
312
|
+
end
|
|
313
|
+
return nil unless bounds&.all?
|
|
314
|
+
|
|
315
|
+
"d >=#{bounds[0]} d <=#{bounds[1]}"
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# One end of a range: a keyword day or any absolute period.
|
|
319
|
+
def resolve_bound(value, today, european)
|
|
320
|
+
v = value.to_s.strip.downcase
|
|
321
|
+
return today.strftime("%Y-%m-%d") if v == "today"
|
|
322
|
+
return (today - 1).strftime("%Y-%m-%d") if v == "yesterday"
|
|
323
|
+
|
|
324
|
+
normalize_period(v, european)
|
|
259
325
|
end
|
|
260
326
|
|
|
261
327
|
# Gmail's after: includes the named day; since: likewise → >=.
|
|
262
|
-
def translate_after(value,
|
|
328
|
+
def translate_after(value, _today, european = false)
|
|
263
329
|
v = value.downcase
|
|
264
330
|
return "d 1d" if v == "today"
|
|
265
331
|
return "d 2d" if v == "yesterday"
|
data/lib/mailmate/version.rb
CHANGED