Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

cowsay.gno

3.15 Kb · 132 lines
  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 a
  3// reusable pure package.
  4//
  5// Original: cowsay by Tony Monroe (1999), a Perl program later ported to Go
  6// 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 in
 10// the path) is at [r/moul/x/daily/cowsaydemo](/r/moul/x/daily/cowsaydemo/v0).
 11package cowsay
 12
 13import (
 14	"strings"
 15)
 16
 17// defaultMessage is shown when no message is supplied.
 18const defaultMessage = "Moo!"
 19
 20// wrapWidth is the maximum bubble text width (classic cowsay uses 40).
 21const wrapWidth = 40
 22
 23// 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 = defaultMessage
 29	}
 30	lines := wrap(msg, wrapWidth)
 31	return balloon(lines) + cow
 32}
 33
 34// balloon builds the speech bubble around the given (already wrapped) lines.
 35func balloon(lines []string) string {
 36	width := maxLen(lines)
 37
 38	var b strings.Builder
 39	// top border: " " + "_"*(width+2)
 40	b.WriteString(" ")
 41	b.WriteString(strings.Repeat("_", width+2))
 42	b.WriteString("\n")
 43
 44	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	}
 55
 56	// bottom border: " " + "-"*(width+2)
 57	b.WriteString(" ")
 58	b.WriteString(strings.Repeat("-", width+2))
 59	b.WriteString("\n")
 60	return b.String()
 61}
 62
 63// 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}
 79
 80// 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	}
 87
 88	var lines []string
 89	cur := ""
 90	for _, w := range words {
 91		// hard-split a single word that is too long
 92		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 = w
102		} else if len(cur)+1+len(w) <= width {
103			cur = cur + " " + w
104		} else {
105			lines = append(lines, cur)
106			cur = w
107		}
108	}
109	if cur != "" {
110		lines = append(lines, cur)
111	}
112	return lines
113}
114
115// maxLen returns the length of the longest line.
116func maxLen(lines []string) int {
117	m := 0
118	for _, ln := range lines {
119		if len(ln) > m {
120			m = len(ln)
121		}
122	}
123	return m
124}
125
126// cow is the classic happy cow, aligned under the bubble.
127const cow = `        \   ^__^
128         \  (oo)\_______
129            (__)\       )\/\
130                ||----w |
131                ||     ||
132`