1package cli 2 3import ( 4 "bufio" 5 cryptorand "crypto/rand" 6 "errors" 7 "fmt" 8 "io" 9 "math" 10 "math/big" 11 "math/rand" 12 "os" 13 "strconv" 14 "strings" 15 "time" 16 17 cowsay "github.com/Code-Hex/Neo-cowsay/v2" 18 "github.com/Code-Hex/Neo-cowsay/v2/internal/decoration" 19 "github.com/Code-Hex/Neo-cowsay/v2/internal/super" 20 "github.com/Code-Hex/go-wordwrap" 21 "github.com/jessevdk/go-flags" 22 "github.com/ktr0731/go-fuzzyfinder" 23 "github.com/mattn/go-colorable" 24) 25 26func init() { 27 // safely set the seed globally so we generate random ids. Tries to use a 28 // crypto seed before falling back to time. 29 var seed int64 30 cryptoseed, err := cryptorand.Int(cryptorand.Reader, big.NewInt(math.MaxInt64)) 31 if err != nil { 32 // This should not happen, but worst-case fallback to time-based seed. 33 seed = time.Now().UnixNano() 34 } else { 35 seed = cryptoseed.Int64() 36 } 37 rand.Seed(seed) 38} 39 40// options struct for parse command line arguments 41type options struct { 42 Help bool `short:"h"` 43 Eyes string `short:"e"` 44 Tongue string `short:"T"` 45 Width int `short:"W"` 46 Borg bool `short:"b"` 47 Dead bool `short:"d"` 48 Greedy bool `short:"g"` 49 Paranoia bool `short:"p"` 50 Stoned bool `short:"s"` 51 Tired bool `short:"t"` 52 Wired bool `short:"w"` 53 Youthful bool `short:"y"` 54 List bool `short:"l"` 55 NewLine bool `short:"n"` 56 File string `short:"f"` 57 Bold bool `long:"bold"` 58 Super bool `long:"super"` 59 Random bool `long:"random"` 60 Rainbow bool `long:"rainbow"` 61 Aurora bool `long:"aurora"` 62} 63 64// CLI prepare for running command-line. 65type CLI struct { 66 Version string 67 Thinking bool 68 stderr io.Writer 69 stdout io.Writer 70 stdin io.Reader 71} 72 73func (c *CLI) program() string { 74 if c.Thinking { 75 return "cowthink" 76 } 77 return "cowsay" 78} 79 80// Run runs command-line. 81func (c *CLI) Run(argv []string) int { 82 if c.stderr == nil { 83 c.stderr = os.Stderr 84 } 85 if c.stdout == nil { 86 c.stdout = colorable.NewColorableStdout() 87 } 88 if c.stdin == nil { 89 c.stdin = os.Stdin 90 } 91 if err := c.mow(argv); err != nil { 92 fmt.Fprintf(c.stderr, "%s: %s\n", c.program(), err.Error()) 93 return 1 94 } 95 return 0 96} 97 98// mow will parsing for cowsay command line arguments and invoke cowsay. 99func (c *CLI) mow(argv []string) error { 100 var opts options 101 args, err := c.parseOptions(&opts, argv) 102 if err != nil { 103 return err 104 } 105 106 if opts.List { 107 cowPaths, err := cowsay.Cows() 108 if err != nil { 109 return err 110 } 111 for _, cowPath := range cowPaths { 112 if cowPath.LocationType == cowsay.InBinary { 113 fmt.Fprintf(c.stdout, "Cow files in binary:\n") 114 } else { 115 fmt.Fprintf(c.stdout, "Cow files in %s:\n", cowPath.Name) 116 } 117 fmt.Fprintln(c.stdout, wordwrap.WrapString(strings.Join(cowPath.CowFiles, " "), 80)) 118 fmt.Fprintln(c.stdout) 119 } 120 return nil 121 } 122 123 if err := c.mowmow(&opts, args); err != nil { 124 return err 125 } 126 127 return nil 128} 129 130func (c *CLI) parseOptions(opts *options, argv []string) ([]string, error) { 131 p := flags.NewParser(opts, flags.None) 132 args, err := p.ParseArgs(argv) 133 if err != nil { 134 return nil, err 135 } 136 137 if opts.Help { 138 c.stdout.Write(c.usage()) 139 os.Exit(0) 140 } 141 142 return args, nil 143} 144 145func (c *CLI) usage() []byte { 146 year := strconv.Itoa(time.Now().Year()) 147 return []byte(c.program() + ` version ` + c.Version + `, (c) ` + year + ` codehex 148Usage: ` + c.program() + ` [-bdgpstwy] [-h] [-e eyes] [-f cowfile] [--random] 149 [-l] [-n] [-T tongue] [-W wrapcolumn] 150 [--bold] [--rainbow] [--aurora] [--super] [message] 151 152Original Author: (c) 1999 Tony Monroe 153`) 154} 155 156func (c *CLI) generateOptions(opts *options) []cowsay.Option { 157 o := make([]cowsay.Option, 0, 8) 158 if opts.File == "-" { 159 cows := cowList() 160 idx, _ := fuzzyfinder.Find(cows, func(i int) string { 161 return cows[i] 162 }) 163 opts.File = cows[idx] 164 } 165 o = append(o, cowsay.Type(opts.File)) 166 if c.Thinking { 167 o = append(o, 168 cowsay.Thinking(), 169 cowsay.Thoughts('o'), 170 ) 171 } 172 if opts.Random { 173 o = append(o, cowsay.Random()) 174 } 175 if opts.Eyes != "" { 176 o = append(o, cowsay.Eyes(opts.Eyes)) 177 } 178 if opts.Tongue != "" { 179 o = append(o, cowsay.Tongue(opts.Tongue)) 180 } 181 if opts.Width > 0 { 182 o = append(o, cowsay.BallonWidth(uint(opts.Width))) 183 } 184 if opts.NewLine { 185 o = append(o, cowsay.DisableWordWrap()) 186 } 187 return selectFace(opts, o) 188} 189 190func cowList() []string { 191 cows, err := cowsay.Cows() 192 if err != nil { 193 return cowsay.CowsInBinary() 194 } 195 list := make([]string, 0) 196 for _, cow := range cows { 197 list = append(list, cow.CowFiles...) 198 } 199 return list 200} 201 202func (c *CLI) phrase(opts *options, args []string) string { 203 if len(args) > 0 { 204 return strings.Join(args, " ") 205 } 206 lines := make([]string, 0, 40) 207 scanner := bufio.NewScanner(c.stdin) 208 for scanner.Scan() { 209 lines = append(lines, scanner.Text()) 210 } 211 return strings.Join(lines, "\n") 212} 213 214func (c *CLI) mowmow(opts *options, args []string) error { 215 phrase := c.phrase(opts, args) 216 o := c.generateOptions(opts) 217 if opts.Super { 218 return super.RunSuperCow(phrase, opts.Bold, o...) 219 } 220 221 say, err := cowsay.Say(phrase, o...) 222 if err != nil { 223 var notfound *cowsay.NotFound 224 if errors.As(err, ¬found) { 225 return fmt.Errorf("could not find %s cowfile", notfound.Cowfile) 226 } 227 return err 228 } 229 230 options := make([]decoration.Option, 0) 231 232 if opts.Bold { 233 options = append(options, decoration.WithBold()) 234 } 235 if opts.Rainbow { 236 options = append(options, decoration.WithRainbow()) 237 } 238 if opts.Aurora { 239 options = append(options, decoration.WithAurora(rand.Intn(256))) 240 } 241 242 w := decoration.NewWriter(c.stdout, options...) 243 fmt.Fprintln(w, say) 244 245 return nil 246} 247 248func selectFace(opts *options, o []cowsay.Option) []cowsay.Option { 249 switch { 250 case opts.Borg: 251 o = append(o, 252 cowsay.Eyes("=="), 253 cowsay.Tongue(" "), 254 ) 255 case opts.Dead: 256 o = append(o, 257 cowsay.Eyes("xx"), 258 cowsay.Tongue("U "), 259 ) 260 case opts.Greedy: 261 o = append(o, 262 cowsay.Eyes("$$"), 263 cowsay.Tongue(" "), 264 ) 265 case opts.Paranoia: 266 o = append(o, 267 cowsay.Eyes("@@"), 268 cowsay.Tongue(" "), 269 ) 270 case opts.Stoned: 271 o = append(o, 272 cowsay.Eyes("**"), 273 cowsay.Tongue("U "), 274 ) 275 case opts.Tired: 276 o = append(o, 277 cowsay.Eyes("--"), 278 cowsay.Tongue(" "), 279 ) 280 case opts.Wired: 281 o = append(o, 282 cowsay.Eyes("OO"), 283 cowsay.Tongue(" "), 284 ) 285 case opts.Youthful: 286 o = append(o, 287 cowsay.Eyes(".."), 288 cowsay.Tongue(" "), 289 ) 290 } 291 return o 292} 293