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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

cowsay.gno

3.17 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/nym-moultest123/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`