package text import ( "fmt" "math" "time" ) // Pluralize returns "1 issue" or "2 issues" depending on count. // It applies a simple "s" suffix rule. func Pluralize(count int, singular string) string { if count == 1 { return fmt.Sprintf("%d %s", count, singular) } return fmt.Sprintf("%d %ss", count, singular) } // FuzzyAgo returns a human-friendly relative time string like "just now", // "2 minutes ago", "3 hours ago", etc. func FuzzyAgo(t time.Time) string { d := time.Since(t) if d < time.Minute { return "just now" } minutes := int(math.Floor(d.Minutes())) if minutes < 60 { return fmt.Sprintf("%s ago", Pluralize(minutes, "minute")) } hours := int(math.Floor(d.Hours())) if hours < 24 { return fmt.Sprintf("%s ago", Pluralize(hours, "hour")) } days := hours / 24 if days < 30 { return fmt.Sprintf("%s ago", Pluralize(days, "day")) } months := days / 30 if months < 12 { return fmt.Sprintf("%s ago", Pluralize(months, "month")) } years := months / 12 return fmt.Sprintf("%s ago", Pluralize(years, "year")) } // Truncate shortens text to maxWidth, replacing the end with "..." if it exceeds // the limit. If maxWidth is less than or equal to 3, the result is just "...". func Truncate(text string, maxWidth int) string { if len(text) <= maxWidth { return text } if maxWidth <= 3 { return "..."[:maxWidth] } return text[:maxWidth-3] + "..." } // FormatDate returns a human-friendly relative time for TTY output, or an // RFC3339 timestamp for piped output. func FormatDate(t time.Time, isTTY bool) string { if isTTY { return FuzzyAgo(t) } return t.Format(time.RFC3339) }