71 lines
3.4 KiB
Go
71 lines
3.4 KiB
Go
package seed
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// decode.go: the STRICT readers of the two files this schema describes.
|
|
//
|
|
// They live with the schema rather than with a loader because the key set IS the format: a key the
|
|
// struct does not have is a typo, and yaml.v3 DROPS one silently, so `gendr:` for `gender:` reads as
|
|
// "not set" and the run looks normal (backlog row 212). The same class the strict `book.yaml` decoder
|
|
// was bought against in pack-16 (config.LoadBook, dec.KnownFields(true)) — and these files are the ones
|
|
// the owner edits BY HAND most often. Two readers share each of these documents (the memory bank loads
|
|
// them, the decisions verb rewrites them), so the strictness has to sit where the format does or the
|
|
// two would answer differently about the same bytes.
|
|
|
|
// DecodeFile parses a seed YAML document. Strict: an unknown key is a loud error naming the line.
|
|
func DecodeFile(raw []byte) (File, error) { return decodeStrict[File](raw) }
|
|
|
|
// RejectFile is the owner's mined-term reject list: the src surfaces reviewed and DECLINED, which the
|
|
// bank-mining emission drops — a declined term never re-enters a signature map or the auto-bank. It is a
|
|
// PROPOSAL filter only — a reject never enters bank content — which is why it is deliberately not folded
|
|
// into the snapshot. (It does not govern the STOP: that is the presented memory's question, D39.144.)
|
|
type RejectFile struct {
|
|
Rejects []Reject `yaml:"rejects"`
|
|
}
|
|
|
|
// Reject is one declined mined term. Note is the owner's optional reason, ignored by the miner but kept
|
|
// so a reject list stays self-documenting (six months on, "why was this declined").
|
|
type Reject struct {
|
|
Src string `yaml:"src"`
|
|
Note string `yaml:"note,omitempty"`
|
|
}
|
|
|
|
// DecodeRejects parses a mined-rejects YAML document, strict for the same reason DecodeFile is: a
|
|
// misspelled `rejcts:` used to read as an empty list, silently un-declining every term the owner
|
|
// declined — they all re-enter the map and the auto-bank as if the review never happened.
|
|
func DecodeRejects(raw []byte) (RejectFile, error) { return decodeStrict[RejectFile](raw) }
|
|
|
|
// decodeStrict decodes one YAML document with unknown-key rejection.
|
|
//
|
|
// A stream carrying NO DOCUMENT decodes to the zero value rather than to an error: yaml.Decoder reports
|
|
// that as io.EOF, while yaml.Unmarshal — which every caller used before this — accepted it as "no
|
|
// records". An empty decision file is a legitimate state of both formats, so turning it into a load
|
|
// failure would break a book over a file that says nothing.
|
|
//
|
|
// The EOF is what is tested, not the bytes. The first version pre-checked for whitespace-only content
|
|
// instead, which is a DIFFERENT set: a file holding only COMMENTS has non-blank bytes and still carries
|
|
// no document, so it fell through to the decoder and came back `seed: EOF` — a file that loaded fine
|
|
// before this change, in the format whose own reject entries exist to be self-documenting, and on the
|
|
// path that governs every book's glossary_seed. Asking the decoder is both simpler and the actual
|
|
// question.
|
|
func decodeStrict[T any](raw []byte) (T, error) {
|
|
var out T
|
|
dec := yaml.NewDecoder(bytes.NewReader(raw))
|
|
dec.KnownFields(true)
|
|
if err := dec.Decode(&out); err != nil {
|
|
if errors.Is(err, io.EOF) {
|
|
var zero T
|
|
return zero, nil // no document in the stream: empty, or comments only
|
|
}
|
|
var zero T
|
|
return zero, fmt.Errorf("seed: %w", err)
|
|
}
|
|
return out, nil
|
|
}
|