package main // The mail signal's own words (novox/hq ADR 0461 §3, §4; issue 1429): a pure function, no store, no bus. // Sending it — messenger_check, the safe form, the cap, marking mails told — is 1522. import ( "fmt" "sort" "time" ) // SignalMail is one mail the mail signal may name (ADR 0461 §3): just enough of its entry, never a body. // Date is the mail's own date, the "oldest first" tiebreak among mails with no due date (1429/AC1); it is // not itself said. type SignalMail struct { SenderName string // "" answers "a sender without a name" (1429/AC2), never the address Summary string Date time.Time DueDate time.Time // zero means none } // SignalWords is what one mail signal says (ADR 0461 §1): a headline, and at most two lines. type SignalWords struct { Headline string Lines []string } // orderSignalMails is 1429/AC1, AC4: nearest due date first (one without a due date after all those with // one), then oldest first among mails sharing that place — "mails carried from earlier (untold)" are simply // part of the same list handed in, ordered exactly as any other mail that matters is. func orderSignalMails(mails []SignalMail) []SignalMail { sorted := append([]SignalMail(nil), mails...) sort.SliceStable(sorted, func(i, j int) bool { a, b := sorted[i], sorted[j] aNone, bNone := a.DueDate.IsZero(), b.DueDate.IsZero() if aNone != bNone { return bNone // a has a due date, b does not: a first } if !aNone { return a.DueDate.Before(b.DueDate) } return a.Date.Before(b.Date) // oldest first, among mails with no due date }) return sorted } // headline is "1 mail matters" or "N mails matter" (1429/AC1, AC3: the same headline whether the words are // said in full or the safe form). func headline(n int) string { if n == 1 { return "1 mail matters" } return fmt.Sprintf("%d mails matter", n) } // mailLine is one mail's own line (1429/AC1): " — — due ", the due part left // out entirely when there is none; a sender with no display name is "a sender without a name" (AC2), never // its address. func mailLine(m SignalMail) string { sender := m.SenderName if sender == "" { sender = "a sender without a name" } line := sender + " — " + m.Summary if !m.DueDate.IsZero() { line += " — due " + m.DueDate.Format("2006-01-02") } return line } // BuildSignal is the mail signal's words in full (1429/AC1, AC2, AC4): the headline, the nearest-due mail's // own line, and — with more than one — the next one's, ending ", and N-2 more on the mail page" once there // are more than two in all. func BuildSignal(mails []SignalMail) SignalWords { ordered := orderSignalMails(mails) n := len(ordered) w := SignalWords{Headline: headline(n)} if n == 0 { return w } w.Lines = append(w.Lines, mailLine(ordered[0])) if n >= 2 { second := mailLine(ordered[1]) if n > 2 { second += fmt.Sprintf(", and %d more on the mail page", n-2) } w.Lines = append(w.Lines, second) } return w } // SafeSignal is 1429/AC3: the safe form a refused set of words falls back to — the same headline, one line // naming nothing of any mail. func SafeSignal(n int) SignalWords { return SignalWords{Headline: headline(n), Lines: []string{"open the mail page"}} }