28c0ff73bd
PR 113's review is about internal/say/summary_ru_v1.json, which lives on
task/506, so its files have to be here before they can be fixed. Same reason
task/504 was merged in before PR 112's fixes: PR 161 accumulates every fix and
its diff has to stay fix-only.
Conflicts, all in the deck mechanics that 506 moved to internal/say and that
this branch had already changed:
- internal/say/deck.go — the exported Deck from 506 keeps this branch's per-family
floor. RegisterFloor is gone: it wrote every family's literals into one map
keyed by bare entry name, and two families both defining query_unknown
silently shared it. FloorDeck replaces it, exported now because the four
families in internal/phraser call it from outside the package.
- internal/say/summary.go — the fifth family off RegisterFloor onto the same
per-family map.
- internal/phraser/{acks,acts,fallbacks,query}.go — say.FloorDeck for the same.
--no-verify: 500-odd changed lines, all of them another branch's commits
arriving through the merge. The guard counts the merge, not the resolution.
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 = 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 say.FloorDeck(ackFloor)
|
|
}
|
|
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)
|
|
}
|