1package cowsay 2 3import ( 4 "fmt" 5 "strings" 6 7 wordwrap "github.com/Code-Hex/go-wordwrap" 8 runewidth "github.com/mattn/go-runewidth" 9) 10 11type border struct { 12 first [2]rune 13 middle [2]rune 14 last [2]rune 15 only [2]rune 16} 17 18func (cow *Cow) borderType() border { 19 if cow.thinking { 20 return border{ 21 first: [2]rune{'(', ')'}, 22 middle: [2]rune{'(', ')'}, 23 last: [2]rune{'(', ')'}, 24 only: [2]rune{'(', ')'}, 25 } 26 } 27 28 return border{ 29 first: [2]rune{'/', '\\'}, 30 middle: [2]rune{'|', '|'}, 31 last: [2]rune{'\\', '/'}, 32 only: [2]rune{'<', '>'}, 33 } 34} 35 36type line struct { 37 text string 38 runeWidth int 39} 40 41type lines []*line 42 43func (cow *Cow) maxLineWidth(lines []*line) int { 44 maxWidth := 0 45 for _, line := range lines { 46 if line.runeWidth > maxWidth { 47 maxWidth = line.runeWidth 48 } 49 if !cow.disableWordWrap && maxWidth > cow.ballonWidth { 50 return cow.ballonWidth 51 } 52 } 53 return maxWidth 54} 55 56func (cow *Cow) getLines(phrase string) []*line { 57 text := cow.canonicalizePhrase(phrase) 58 lineTexts := strings.Split(text, "\n") 59 lines := make([]*line, 0, len(lineTexts)) 60 for _, lineText := range lineTexts { 61 lines = append(lines, &line{ 62 text: lineText, 63 runeWidth: runewidth.StringWidth(lineText), 64 }) 65 } 66 return lines 67} 68 69func (cow *Cow) canonicalizePhrase(phrase string) string { 70 // Replace tab to 8 spaces 71 phrase = strings.Replace(phrase, "\t", " ", -1) 72 73 if cow.disableWordWrap { 74 return phrase 75 } 76 width := cow.ballonWidth 77 return wordwrap.WrapString(phrase, uint(width)) 78} 79 80// Balloon to get the balloon and the string entered in the balloon. 81func (cow *Cow) Balloon(phrase string) string { 82 defer cow.buf.Reset() 83 84 lines := cow.getLines(phrase) 85 maxWidth := cow.maxLineWidth(lines) 86 87 cow.writeBallon(lines, maxWidth) 88 89 return cow.buf.String() 90} 91 92func (cow *Cow) writeBallon(lines []*line, maxWidth int) { 93 top := make([]byte, 0, maxWidth+2) 94 bottom := make([]byte, 0, maxWidth+2) 95 96 top = append(top, ' ') 97 bottom = append(bottom, ' ') 98 99 for i := 0; i < maxWidth+2; i++ { 100 top = append(top, '_') 101 bottom = append(bottom, '-') 102 } 103 104 borderType := cow.borderType() 105 106 cow.buf.Write(top) 107 cow.buf.Write([]byte{' ', '\n'}) 108 defer func() { 109 cow.buf.Write(bottom) 110 cow.buf.Write([]byte{' ', '\n'}) 111 }() 112 113 l := len(lines) 114 if l == 1 { 115 border := borderType.only 116 cow.buf.WriteRune(border[0]) 117 cow.buf.WriteRune(' ') 118 cow.buf.WriteString(lines[0].text) 119 cow.buf.WriteRune(' ') 120 cow.buf.WriteRune(border[1]) 121 cow.buf.WriteRune('\n') 122 return 123 } 124 125 var border [2]rune 126 for i := 0; i < l; i++ { 127 switch i { 128 case 0: 129 border = borderType.first 130 case l - 1: 131 border = borderType.last 132 default: 133 border = borderType.middle 134 } 135 cow.buf.WriteRune(border[0]) 136 cow.buf.WriteRune(' ') 137 cow.padding(lines[i], maxWidth) 138 cow.buf.WriteRune(' ') 139 cow.buf.WriteRune(border[1]) 140 cow.buf.WriteRune('\n') 141 } 142} 143 144func (cow *Cow) flush(text, top, bottom fmt.Stringer) string { 145 return fmt.Sprintf( 146 "%s\n%s%s\n", 147 top.String(), 148 text.String(), 149 bottom.String(), 150 ) 151} 152 153func (cow *Cow) padding(line *line, maxWidth int) { 154 if maxWidth <= line.runeWidth { 155 cow.buf.WriteString(line.text) 156 return 157 } 158 159 cow.buf.WriteString(line.text) 160 l := maxWidth - line.runeWidth 161 for i := 0; i < l; i++ { 162 cow.buf.WriteRune(' ') 163 } 164} 165