textmachine/backend/internal/pipeline/holes.go

58 lines
2.6 KiB
Go

package pipeline
// holes.go: what a HOLE in the export is — the one predicate the plaintext render (cmd/tmctl render.go),
// the book writer (bookbuild.go) and any later consumer read the per-unit states through.
//
// BookExport's counters carry three book-level states (pending, ghost, drift). Two more are PER UNIT and
// live only in the record: a unit that reached the final stage and shipped NO text (withheld — a
// substantive flag or an upstream skip, D2: contaminated output never ships), and a unit that shipped
// text with a member's worth of it MISSING (incomplete — the c-lite editor dropped a member). A file that
// consults the counters alone comes out «silently whole» on a book with either, which is the worst thing a
// reader's copy can do; hence one definition, here, rather than an arithmetic each surface repeats.
// HoleKind names why a unit has no text, or not all of it.
type HoleKind string
const (
// HoleNone: the unit shipped its whole text.
HoleNone HoleKind = ""
// HolePending: a manifest unit with no final row — not translated yet (BookExport.PendingUnits).
HolePending HoleKind = "pending"
// HoleWithheld: a final row with no text — a substantive flag / upstream skip withheld it.
HoleWithheld HoleKind = "withheld"
// HoleIncomplete: text present, a member chunk of it missing (ChunkExport.DroppedMembers > 0).
HoleIncomplete HoleKind = "incomplete"
// HoleStale: the unit's final row was made for a source that is no longer the book's (its content
// hash differs from what the run would render now). The WRITER's kind (bookbuild.go staleUnits) —
// UnitHole never returns it, because the export record does not carry the fact.
HoleStale HoleKind = "stale"
)
// UnitHole classifies one export record. The order of the tests is the plaintext render's: pending
// first, then «no text», then «text with a dropped member» — an incomplete unit DID ship text.
func UnitHole(ce ChunkExport) HoleKind {
switch {
case ce.Disposition == exportPending:
return HolePending
case ce.FinalText == "":
return HoleWithheld
case ce.DroppedMembers > 0:
return HoleIncomplete
}
return HoleNone
}
// HoleCounts returns the per-unit hole counts of an export: withheld and incomplete (pending is the
// counter BookExport.PendingUnits). `incomplete` is a SUBSET of the exported units, not a fourth part
// of the total: those units did ship text, with a piece of it missing.
func HoleCounts(exp *BookExport) (withheld, incomplete int) {
for _, ce := range exp.Chunks {
switch UnitHole(ce) {
case HoleWithheld:
withheld++
case HoleIncomplete:
incomplete++
}
}
return withheld, incomplete
}