package formatter import ( "fmt" "io" "strings" "unicode/utf8" "royalroadupdates/internal/royalroad" ) // PrintTable writes a plain-text aligned table of stories to w. func PrintTable(w io.Writer, stories []royalroad.Story) { if len(stories) == 0 { return } nameHeader := "Story Name" updateHeader := "Last Update" // 1. Measure natural widths in runes maxNameLen := utf8.RuneCountInString(nameHeader) maxUpdateLen := utf8.RuneCountInString(updateHeader) for _, s := range stories { if l := utf8.RuneCountInString(s.Name); l > maxNameLen { maxNameLen = l } if l := utf8.RuneCountInString(Relative(s.DateModified)); l > maxUpdateLen { maxUpdateLen = l } } // 2. Determine final name column width based on terminal availability nameWidth := maxNameLen const minNameWidth = 30 const gutter = 2 termWidth, err := getTerminalWidth() if err == nil { // We have a terminal. check if it's wide enough to support a cap above the min. if termWidth >= minNameWidth+gutter+maxUpdateLen { availableForName := termWidth - gutter - maxUpdateLen if nameWidth > availableForName { nameWidth = availableForName } } } // 3. Render Table fmt.Fprintf(w, "%-*s %s\n", nameWidth, nameHeader, updateHeader) fmt.Fprintf(w, "%s %s\n", strings.Repeat("-", nameWidth), strings.Repeat("-", maxUpdateLen)) for _, s := range stories { displayName := s.Name runeName := []rune(displayName) if len(runeName) > nameWidth { // Truncate to (nameWidth - 3) and add ellipsis displayName = string(runeName[:nameWidth-3]) + "..." } fmt.Fprintf(w, "%-*s %s\n", nameWidth, displayName, Relative(s.DateModified)) } }