1// Package cowsay is an on-chain Gno port of the classic `cowsay` program:2// it builds an ASCII cow with a speech bubble containing a message — as a3// reusable pure package.4//5// Original: cowsay by Tony Monroe (1999), a Perl program later ported to Go6// many times. This package reimplements the core rendering — bubble framing,7// word-wrapping and the cow art — as pure, deterministic string logic.8//9// A live demo of this package (an interactive cow that says whatever you put in10// the path) is at [r/moul/x/daily/cowsaydemo](/r/moul/x/daily/cowsaydemo/v0).11package cowsay1213import (14 "strings"15)1617// defaultMessage is shown when no message is supplied.18const defaultMessage = "Moo!"1920// wrapWidth is the maximum bubble text width (classic cowsay uses 40).21const wrapWidth = 402223// Say returns the full cowsay art (speech bubble + cow) for msg.24// An empty msg falls back to the default message.25func Say(msg string) string {26 msg = strings.TrimSpace(msg)27 if msg == "" {28 msg = defaultMessage29 }30 lines := wrap(msg, wrapWidth)31 return balloon(lines) + cow32}3334// balloon builds the speech bubble around the given (already wrapped) lines.35func balloon(lines []string) string {36 width := maxLen(lines)3738 var b strings.Builder39 // top border: " " + "_"*(width+2)40 b.WriteString(" ")41 b.WriteString(strings.Repeat("_", width+2))42 b.WriteString("\n")4344 n := len(lines)45 for i, ln := range lines {46 left, right := borderChars(i, n)47 b.WriteString(left)48 b.WriteString(" ")49 b.WriteString(ln)50 b.WriteString(strings.Repeat(" ", width-len(ln)))51 b.WriteString(" ")52 b.WriteString(right)53 b.WriteString("\n")54 }5556 // bottom border: " " + "-"*(width+2)57 b.WriteString(" ")58 b.WriteString(strings.Repeat("-", width+2))59 b.WriteString("\n")60 return b.String()61}6263// borderChars picks the left/right bubble characters for line i of n.64// Single line uses < >. Multi-line uses / \ for the first row,65// \ / for the last row, and | | for the middle rows.66func borderChars(i, n int) (string, string) {67 if n == 1 {68 return "<", ">"69 }70 switch i {71 case 0:72 return "/", "\\"73 case n - 1:74 return "\\", "/"75 default:76 return "|", "|"77 }78}7980// wrap splits text into lines no longer than width, breaking on spaces.81// Words longer than width are hard-split.82func wrap(text string, width int) []string {83 words := strings.Fields(text)84 if len(words) == 0 {85 return []string{""}86 }8788 var lines []string89 cur := ""90 for _, w := range words {91 // hard-split a single word that is too long92 for len(w) > width {93 if cur != "" {94 lines = append(lines, cur)95 cur = ""96 }97 lines = append(lines, w[:width])98 w = w[width:]99 }100 if cur == "" {101 cur = w102 } else if len(cur)+1+len(w) <= width {103 cur = cur + " " + w104 } else {105 lines = append(lines, cur)106 cur = w107 }108 }109 if cur != "" {110 lines = append(lines, cur)111 }112 return lines113}114115// maxLen returns the length of the longest line.116func maxLen(lines []string) int {117 m := 0118 for _, ln := range lines {119 if len(ln) > m {120 m = len(ln)121 }122 }123 return m124}125126// cow is the classic happy cow, aligned under the bubble.127const cow = ` \ ^__^128 \ (oo)\_______129 (__)\ )\/\130 ||----w |131 || ||132`133Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.