df58710141
Two of the four defects on the task.
The stored payload was the whole utterance, so /reminders and the agenda
recited "напомни завтра в 9 утра выпить таблетки" where the reminder is
"выпить таблетки". The marker is an instruction that was already carried out
and the hour is already a column, so reminderBody strips both, and falls back
to the unstripped body whenever stripping would leave nothing — a reminder
that fires and says nothing is worse than a wordy one.
The page rendered the raw {"text":...} envelope and the UTC instant. Both are
now done in mavweb: reminderRows unwraps the payload and formats through
Local(). The unwrap is a copy of store.ReminderText rather than a call to it,
because mavweb builds without CGO and internal/store carries the sqlite
driver — the ipc DTOs are decoupled from the store on purpose.
TestClarifySubjectAnswerFillsRatherThanClobbers asserted the hour survived as
a word in the payload. It now asserts the fire time, which is where the hour
lives.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
54 lines
2.4 KiB
Go
54 lines
2.4 KiB
Go
package main
|
|
|
|
import (
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// reminderMarker — the words that open a reminder. Stripped because they are
|
|
// the instruction, not the thing to say at the hour.
|
|
var reminderMarker = regexp.MustCompile(`(?i)^\s*(?:напомни(?:те)?|напомнить|remind)\s*(?:мне|me)?[\s,:—-]*`)
|
|
|
|
// reminderTimeWords — the time expressions a reminder carries, removed from
|
|
// the body because the fire time is already a column. Ordered longest-first
|
|
// where two could match the same words, so "через полтора часа" does not leave
|
|
// "полтора" behind.
|
|
//
|
|
// Go's \b is ASCII-only and never fires next to a Cyrillic letter, so the word
|
|
// boundaries here are written out as whitespace or an end of string — the same
|
|
// trap the agenda grammars hit.
|
|
var reminderTimeWords = []*regexp.Regexp{
|
|
regexp.MustCompile(`(?i)(^|\s)через\s+\S+(\s+(часа?|часов|минут[уы]?|секунд[уы]?|дня|дней|недел[юи]))?(\s|$)`),
|
|
regexp.MustCompile(`(?i)(^|\s)(в|во)\s+\d{1,2}(:\d{2})?(\s*(часа?|часов))?(\s*(утра|вечера|дня|ночи))?(\s|$)`),
|
|
regexp.MustCompile(`(?i)(^|\s)(завтра|послезавтра|сегодня|вечером|утром|днём|днем|ночью)(\s|$)`),
|
|
regexp.MustCompile(`(?i)(^|\s)(at|in)\s+\d{1,2}(:\d{2})?\s*(am|pm)?(\s|$)`),
|
|
regexp.MustCompile(`(?i)(^|\s)(tomorrow|today|tonight)(\s|$)`),
|
|
}
|
|
|
|
// reminderBody is what she says at the hour.
|
|
//
|
|
// The whole utterance used to be stored, so /reminders read "напомни завтра в
|
|
// 9 утра выпить таблетки" where it should read "выпить таблетки", and the
|
|
// agenda recited the marker back at him (Vikunja #469). The fire time is
|
|
// already a column, and the marker is an instruction that was carried out.
|
|
//
|
|
// Falls back to the fuller text whenever stripping would leave nothing: an
|
|
// empty body is a reminder that fires and says nothing, which is worse than a
|
|
// wordy one.
|
|
func reminderBody(utterance, text string) string {
|
|
body := strings.TrimSpace(text)
|
|
if body == "" {
|
|
body = strings.TrimSpace(utterance)
|
|
}
|
|
stripped := reminderMarker.ReplaceAllString(body, "")
|
|
for _, re := range reminderTimeWords {
|
|
stripped = re.ReplaceAllString(stripped, " ")
|
|
}
|
|
stripped = strings.TrimSpace(strings.Join(strings.Fields(stripped), " "))
|
|
stripped = strings.Trim(stripped, " ,;:—-")
|
|
if stripped == "" {
|
|
return body
|
|
}
|
|
return stripped
|
|
}
|