c35979d9f9
The summaries family is spoken by internal/memory, internal/morning and internal/tasks. internal/phraser already imports internal/memory, so the deck cannot stay in phraser without a cycle. internal/say is a leaf: embed, json, math/rand, strings, sync. The four phraser families keep their files and their floors and now call say.Load, *say.Deck, Text, Matches, Variants, RequirePlaceholder and RegisterFloor. No copy changed and no behaviour changed.
165 lines
6.1 KiB
Go
165 lines
6.1 KiB
Go
package phraser
|
|
|
|
// The capture acknowledgements — what she says after storing something he said,
|
|
// and what she says when storing it failed.
|
|
//
|
|
// They were string literals in eight files under cmd/mavend plus the stub
|
|
// replier in internal/voice. He hears them many times a day, which is exactly
|
|
// why they need variants and exactly why rewording one must not be a rebuild.
|
|
// Same shape as fallbacks_ru_v1.json, on the shared deck (deck.go).
|
|
//
|
|
// His data stays Go-side. The file holds "отметила: {key} = {value}"; the key
|
|
// and the value are interpolated here, so nothing he said lives in the copy.
|
|
|
|
import (
|
|
_ "embed"
|
|
"log"
|
|
"math/rand"
|
|
"sync"
|
|
|
|
"github.com/kami/maven/internal/say"
|
|
)
|
|
|
|
//go:embed ack_ru_v1.json
|
|
var ackJSON []byte
|
|
|
|
// AckSchemaVersion — this family's own version. A file that changes on a
|
|
// different day than the fallbacks cannot share their number (Vikunja #397).
|
|
const AckSchemaVersion = 1
|
|
|
|
// The entry keys. Namespaced by family, because the floor map behind a nil deck
|
|
// is process-wide.
|
|
const (
|
|
AckFact = "ack_fact"
|
|
AckFactKey = "ack_fact_key"
|
|
AckFactValue = "ack_fact_kv"
|
|
AckNote = "ack_note"
|
|
AckReminder = "ack_reminder"
|
|
AckAct = "ack_act"
|
|
AckTask = "ack_task"
|
|
AckTaskUrgent = "ack_task_urgent"
|
|
AckTaskDuplicate = "ack_task_duplicate"
|
|
AckNudge = "ack_nudge"
|
|
AckSnooze = "ack_snooze"
|
|
AckGeneric = "ack_generic"
|
|
AckQuietOn = "quiet_on"
|
|
AckQuietOff = "quiet_off"
|
|
|
|
FailFact = "fail_fact"
|
|
FailFactUnparsed = "fail_fact_unparsed"
|
|
FailNote = "fail_note"
|
|
FailReminder = "fail_reminder"
|
|
FailReminderTime = "fail_reminder_time"
|
|
FailTask = "fail_task"
|
|
FailAck = "fail_ack"
|
|
FailSnooze = "fail_snooze"
|
|
FailQuiet = "fail_quiet"
|
|
)
|
|
|
|
// ackKeys — every key the code requires the file to define.
|
|
var ackKeys = []string{
|
|
AckFact, AckFactKey, AckFactValue, AckNote, AckReminder, AckAct,
|
|
AckTask, AckTaskUrgent, AckTaskDuplicate, AckNudge, AckSnooze, AckGeneric,
|
|
AckQuietOn, AckQuietOff,
|
|
FailFact, FailFactUnparsed, FailNote, FailReminder, FailReminderTime,
|
|
FailTask, FailAck, FailSnooze, FailQuiet,
|
|
}
|
|
|
|
// ackFloor — the literal each key falls back to when the file is unusable.
|
|
// These are the exact strings that lived in Go before this file existed.
|
|
var ackFloor = say.RegisterFloor(map[string]string{
|
|
AckFact: "записала факт.",
|
|
AckFactKey: "отметила: {key}",
|
|
AckFactValue: "отметила: {key} = {value}",
|
|
AckNote: "сохранила заметку.",
|
|
AckReminder: "напомню.",
|
|
AckAct: "ок, записала действие: {fn}",
|
|
AckTask: "записала: {text}",
|
|
AckTaskUrgent: "поняла, беру в работу: {text}",
|
|
AckTaskDuplicate: "это уже в списке.",
|
|
AckNudge: "отлично, отметила.",
|
|
AckSnooze: "хорошо, вернусь к этому позже.",
|
|
AckGeneric: "приняла.",
|
|
AckQuietOn: "тихий режим включён. буду реже напоминать.",
|
|
AckQuietOff: "тихий режим выключен.",
|
|
|
|
FailFact: "не получилось сохранить факт.",
|
|
FailFactUnparsed: "не разобрала, что записать — попробуй иначе.",
|
|
FailNote: "не получилось сохранить заметку.",
|
|
FailReminder: "не получилось поставить напоминание.",
|
|
FailReminderTime: "не получилось разобрать время напоминания.",
|
|
FailTask: "не получилось записать задачу.",
|
|
FailAck: "не получилось отметить.",
|
|
FailSnooze: "не получилось отложить.",
|
|
FailQuiet: "не получилось переключить тихий режим.",
|
|
})
|
|
|
|
// Acks picks a hand-written Russian acknowledgement. Safe for concurrent use.
|
|
type Acks struct{ d *say.Deck }
|
|
|
|
// LoadAcks reads the embedded file. Pass a source to make the picking
|
|
// reproducible in tests; nil seeds from the clock.
|
|
func LoadAcks(src rand.Source) (*Acks, error) {
|
|
d, err := say.Load(ackJSON, AckSchemaVersion, ackKeys, ackFloor, src)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// The three entries that exist to read his own words back. A variant
|
|
// without the placeholder would confirm the capture and drop what was
|
|
// captured, which reads as a successful save of nothing.
|
|
for _, req := range []struct{ key, ph string }{
|
|
{AckFactKey, "{key}"}, {AckFactValue, "{key}"}, {AckFactValue, "{value}"},
|
|
{AckAct, "{fn}"}, {AckTask, "{text}"}, {AckTaskUrgent, "{text}"},
|
|
} {
|
|
if err := d.RequirePlaceholder(req.key, req.ph); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return &Acks{d: d}, nil
|
|
}
|
|
|
|
// deck reads through a nil *Acks, which is the unloadable-file case.
|
|
func (a *Acks) deck() *say.Deck {
|
|
if a == nil {
|
|
return nil
|
|
}
|
|
return a.d
|
|
}
|
|
|
|
// Say returns one line for key, with his data filled into the frame. Pass nil
|
|
// when the entry takes none.
|
|
func (a *Acks) Say(key string, vars map[string]string) string {
|
|
return a.deck().Text(key, vars)
|
|
}
|
|
|
|
// Variants returns every line the file can produce, for the persona scorer.
|
|
func (a *Acks) Variants() []string { return a.deck().Variants() }
|
|
|
|
var (
|
|
ackOnce sync.Once
|
|
acks *Acks
|
|
)
|
|
|
|
// DefaultAcks returns the shared instance, loading it on first use. A broken
|
|
// file logs once and leaves a nil *Acks, which still answers from ackFloor.
|
|
func DefaultAcks() *Acks {
|
|
ackOnce.Do(func() {
|
|
a, err := LoadAcks(nil)
|
|
if err != nil {
|
|
log.Printf("phraser: acknowledgements unavailable, using the built-in lines: %v", err)
|
|
return
|
|
}
|
|
acks = a
|
|
})
|
|
return acks
|
|
}
|
|
|
|
// Ack — one acknowledgement line, the way every caller says it.
|
|
func Ack(key string, vars map[string]string) string { return DefaultAcks().Say(key, vars) }
|
|
|
|
// IsAck reports whether text is a line key could have produced. For the daemon
|
|
// tests, which can no longer compare against one literal.
|
|
func IsAck(key string, vars map[string]string, text string) bool {
|
|
return DefaultAcks().deck().Matches(key, vars, text)
|
|
}
|