cowsay / cmd / cli.go

@ dbd69fc1616efd5180b0502d7b5f2229ec3b97d1 | history


package cli

import (
	"flag"
	"fmt"
	"io"
	"io/fs"
	"log"
	"math/rand"
	"os"
	"path"
	"path/filepath"
	"sort"
	"strings"
	"text/template"

	"nmyk.io/cowsay"
)

var (
	list      = flag.Bool("l", false, "list available cowfiles")
	randomCow = flag.Bool("r", false, "random cowfile")
	cowfile   = flag.String("f", "default", "choose a cowfile")

	noWrap   = flag.Bool("n", false, "disable word wrap")
	padWidth = flag.Int("W", 40, "width of speech bubble")

	borg     = flag.Bool("b", false, "borg")
	greedy   = flag.Bool("g", false, "greedy")
	paranoid = flag.Bool("p", false, "paranoid")
	stoned   = flag.Bool("s", false, "stoned")
	tired    = flag.Bool("t", false, "tired")
	wired    = flag.Bool("w", false, "wired")
	youthful = flag.Bool("y", false, "youthful")
	dead     = flag.Bool("d", false, "dead")

	eyes   = flag.String("e", defaultEyes, "eyes")
	tongue = flag.String("T", defaultTongue, "tongue")
)

// Run is the entrypoint for the cowsay CLI, with a boolean switch to enable
// "cowthink mode"
func Run(think bool) {
	flag.Usage = usage
	flag.Parse()
	cowpath := parseCowpath()

	if *list {
		ls(cowpath)
		os.Exit(0)
	}

	message := slurpInput()

	cow := getCowBody()
	cow.Mood = getMood()
	cow.Width = getWidth()
	if think {
		cow.Think(message)
	} else {
		cow.Say(message)
	}
}

func usage() {
	flagSet := flag.CommandLine
	intro := `Usage: %s [-bdgpstwy] [-r] [-h] [-e eyes]
[-f cowfile] [-l] [-n] [-T tongue] [-W wrapcolumn] [message]
Options:
`
	fmt.Printf(intro, os.Args[0])
	order := "lrfnW1bgpstwyd2eT"
	for _, name := range order {
		if name == '1' {
			fmt.Println("Moods:")
			continue
		} else if name == '2' {
			fmt.Println("Customize:")
			continue
		}
		flag := flagSet.Lookup(string(name))
		fmt.Printf("-%s ", flag.Name)
		if flag.DefValue != "false" {
			fmt.Printf("  %s (default: %#v) \n", flag.Usage, flag.DefValue)
		} else {
			fmt.Printf("  %s\n", flag.Usage)
		}
	}
}

func parseCowpath() string {
	cowpathEnv := os.Getenv("COWPATH")

	type cowfilePath struct {
		path  string
		entry fs.DirEntry
	}

	if cowpathEnv != "" {
		cowsay.Cowfiles = nil
		tmpl := template.New("")
		cowpaths := filepath.SplitList(cowpathEnv)
		allEntries := make([]cowfilePath, 0)
		for _, cowpath := range cowpaths {
			entries, err := os.ReadDir(cowpath)
			if err != nil {
				continue
			}
			for _, entry := range entries {
				allEntries = append(allEntries, cowfilePath{cowpath, entry})
			}
		}
		for _, e := range allEntries {
			if !e.entry.Type().IsRegular() || filepath.Ext(e.entry.Name()) != ".cow" {
				continue
			}
			cowdata, err := os.ReadFile(path.Join(e.path, e.entry.Name()))
			if err != nil {
				panic(err.Error())
			}
			t, err := tmpl.New(e.entry.Name()).Parse(string(cowdata))
			if err != nil {
				continue
			}
			tmpl = t
		}
		cowsay.Cowfiles = tmpl
	}
	return cowpathEnv
}

func ls(cowpath string) {
	if cowpath != "" {
		fmt.Printf("Cow files in %s:\n", cowpath)
	} else {
		fmt.Println("Cow files:")
	}
	if cowsay.Cowfiles == nil {
		fmt.Println("none")
		return
	}
	cowfiles := cowsay.Cowfiles.Templates()
	names := make([]string, 0)
	for _, key := range cowfiles {
		path := strings.Split(key.Name(), "/")
		basename := path[len(path)-1]
		name := strings.Replace(basename, ".cow", "", -1)
		names = append(names, name)
	}
	sort.Strings(names)
	fmt.Println(strings.Join(names, " "))
	return
}

func slurpInput() (message string) {
	fi, _ := os.Stdin.Stat()
	if (fi.Mode() & os.ModeCharDevice) == 0 {
		m, _ := io.ReadAll(os.Stdin)
		message = strings.TrimRight(string(m), "\n")
	} else {
		args := strings.Join(flag.Args(), " ")
		if args == "" {
			flag.Usage()
			os.Exit(1)
		}
		message = args
	}
	return
}

func getCowBody() cowsay.Cow {
	var label string
	if *randomCow {
		var assets []string
		cowfiles := cowsay.Cowfiles.Templates()
		for _, key := range cowfiles {
			assets = append(assets, key.Name())
		}
		label = assets[rand.Intn(len(assets))]
	} else if *cowfile != "" {
		label = *cowfile
	}

	var cow cowsay.Cow

	if label != "" {
		var err error
		cow, err = cowsay.Load(label)
		if err != nil {
			log.Fatalf(err.Error())
		}
	}
	return cow
}

const (
	defaultEyes   = "oo"
	defaultTongue = "  "
)

func getMood() cowsay.Mood {
	var mood cowsay.Mood
	switch {
	case *borg:
		mood = cowsay.Borg
	case *greedy:
		mood = cowsay.Greedy
	case *paranoid:
		mood = cowsay.Paranoid
	case *stoned:
		mood = cowsay.Stoned
	case *tired:
		mood = cowsay.Tired
	case *wired:
		mood = cowsay.Wired
	case *youthful:
		mood = cowsay.Youthful
	case *dead:
		mood = cowsay.Dead
	default:
	}
	if *eyes != defaultEyes {
		mood.Eyes = *eyes
	}
	if *tongue != defaultTongue {
		mood.Tongue = *tongue
	}
	return mood
}

func getWidth() int {
	var width int
	if *noWrap {
		width = -1
	} else {
		width = *padWidth
	}
	return width
}