// Package cabrillo writes a Cabrillo v3 contest log from OpsLog QSOs — the // format most contest sponsors require for log submission (a header of // CATEGORY-* / CONTEST / CALLSIGN tags followed by one "QSO:" line each). package cabrillo import ( "fmt" "io" "strings" "hamlog/internal/qso" ) // Header holds the Cabrillo header fields. Only Callsign and Contest really // matter; the rest carry sensible defaults the operator can edit in the file // before submitting (categories vary per contest). type Header struct { Callsign string Contest string Operators string CatOperator string // SINGLE-OP | MULTI-OP | CHECKLOG CatBand string // ALL | 20M | … CatMode string // CW | SSB | MIXED | … CatPower string // HIGH | LOW | QRP GridLocator string Name string Email string Club string } func def(v, d string) string { if strings.TrimSpace(v) == "" { return d } return v } // Generate writes the Cabrillo document for qsos and returns the number of QSO // lines written (rows without a usable frequency/callsign are skipped). func Generate(w io.Writer, h Header, qsos []qso.QSO) (int, error) { myCall := strings.ToUpper(strings.TrimSpace(h.Callsign)) fmt.Fprintln(w, "START-OF-LOG: 3.0") fmt.Fprintln(w, "CREATED-BY: OpsLog") fmt.Fprintf(w, "CONTEST: %s\n", strings.ToUpper(def(h.Contest, "UNKNOWN"))) fmt.Fprintf(w, "CALLSIGN: %s\n", myCall) fmt.Fprintf(w, "CATEGORY-OPERATOR: %s\n", strings.ToUpper(def(h.CatOperator, "SINGLE-OP"))) fmt.Fprintf(w, "CATEGORY-BAND: %s\n", strings.ToUpper(def(h.CatBand, "ALL"))) fmt.Fprintf(w, "CATEGORY-MODE: %s\n", strings.ToUpper(def(h.CatMode, "MIXED"))) fmt.Fprintf(w, "CATEGORY-POWER: %s\n", strings.ToUpper(def(h.CatPower, "HIGH"))) fmt.Fprintln(w, "CLAIMED-SCORE: 0") fmt.Fprintf(w, "OPERATORS: %s\n", strings.ToUpper(def(h.Operators, myCall))) if h.GridLocator != "" { fmt.Fprintf(w, "GRID-LOCATOR: %s\n", strings.ToUpper(h.GridLocator)) } if h.Name != "" { fmt.Fprintf(w, "NAME: %s\n", h.Name) } if h.Club != "" { fmt.Fprintf(w, "CLUB: %s\n", h.Club) } if h.Email != "" { fmt.Fprintf(w, "EMAIL: %s\n", h.Email) } n := 0 for _, q := range qsos { call := strings.ToUpper(strings.TrimSpace(q.Callsign)) if call == "" { continue } khz := freqKHz(q) mode := cabrilloMode(q.Mode) myRST := def(q.RSTSent, defaultRST(mode)) hisRST := def(q.RSTRcvd, defaultRST(mode)) myExch := exchange(q.STX, q.STXString) hisExch := exchange(q.SRX, q.SRXString) // QSO: