numbers and reminder markers come from the lexicon (V-530)
ruNumerals was a second copy of the number words. It stopped at fifty, had no oblique forms, and disagreed with lexicon_ru_v1.json about its own members, so "к семи" was not the hour "в семь" was. The lexicon now carries the oblique cardinals and numwords.go asks lexicon.Cardinal. "час" and "часу" stay local: they are the hour noun as often as the number one, and nobody counts "час яблок". reminderbody.go built its markers from three inline word lists. Two of them are new lexicon sets, reminder_verbs and parts_of_day, and the day offsets were already there. The alternation helper sorts by length so a longer form wins the regex. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,12 +2,20 @@ package main
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/kami/maven/internal/lexicon"
|
||||
)
|
||||
|
||||
// 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,:—-]*`)
|
||||
//
|
||||
// The verbs come from the lexicon (Vikunja #530). They are a closed set of the
|
||||
// commands she answers to, exactly like capture_verbs, and the literal that
|
||||
// stood here knew four of them.
|
||||
var reminderMarker = regexp.MustCompile(`(?i)^\s*(?:` + alternation(lexicon.ReminderVerbs()) +
|
||||
`)\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
|
||||
@@ -17,12 +25,36 @@ var reminderMarker = regexp.MustCompile(`(?i)^\s*(?:напомни(?:те)?|на
|
||||
// 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.
|
||||
//
|
||||
// The Russian word lists are gone (Vikunja #530). The day words are
|
||||
// lexicon.DayOffsetWords, which is why "вчера" and "позавчера" are stripped now
|
||||
// and were not before, and the times of day are lexicon.PartsOfDay. What is
|
||||
// still written out here is the shape of a clock reading — a preposition, digits,
|
||||
// a colon — which is structured input rather than a claim about Russian.
|
||||
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)(` + alternation(lexicon.DayOffsetWords()) + `)(\s|$)`),
|
||||
regexp.MustCompile(`(?i)(^|\s)(` + alternation(lexicon.PartsOfDay()) + `)(\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|$)`),
|
||||
}
|
||||
|
||||
// alternation folds a lexicon set into one regexp branch, longest member first
|
||||
// so "послезавтра" is not matched as "завтра" with a tail left behind. Sorted
|
||||
// rather than taken as given, because two members of equal length must still
|
||||
// produce the same pattern on every build.
|
||||
func alternation(set []string) string {
|
||||
out := make([]string, 0, len(set))
|
||||
for _, w := range set {
|
||||
out = append(out, regexp.QuoteMeta(w))
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool {
|
||||
if len(out[i]) != len(out[j]) {
|
||||
return len(out[i]) > len(out[j])
|
||||
}
|
||||
return out[i] < out[j]
|
||||
})
|
||||
return strings.Join(out, "|")
|
||||
}
|
||||
|
||||
// reminderBody is what she says at the hour.
|
||||
|
||||
Reference in New Issue
Block a user