From 1aab2084f19f1b6844bf05368d7fbca6f3ce5799 Mon Sep 17 00:00:00 2001 From: vilmibm Date: Mon, 27 Apr 2020 10:14:58 -0500 Subject: [PATCH] cute command to show a thank you to contributors this commit is the result of April's hack day. it animates a thank you for a project's contributors. it only really works on linux/mac; animation woes led me to just make it static and plain on windows. --- command/credits.go | 248 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 command/credits.go diff --git a/command/credits.go b/command/credits.go new file mode 100644 index 000000000..a35ef0945 --- /dev/null +++ b/command/credits.go @@ -0,0 +1,248 @@ +package command + +import ( + "bytes" + "fmt" + "math" + "math/rand" + "os" + "os/exec" + "runtime" + "strings" + "time" + + "github.com/spf13/cobra" + "golang.org/x/crypto/ssh/terminal" + + "github.com/cli/cli/utils" +) + +var isWindows bool + +var thankYou = ` + _ _ + | | | | +_|_ | | __, _ _ | | __ + | |/ \ / | / |/ | |/_) | | / \_| | + |_/| |_/\_/|_/ | |_/| \_/ \_/|/\__/ \_/|_/ + /| + \| + _ + o | | | + __ __ _ _ _|_ ,_ | | _|_ __ ,_ , | +/ / \_/ |/ | | / | | |/ \_| | | / \_/ | / \_| +\___/\__/ | |_/|_/ |_/|_/\_/ \_/|_/|_/\__/ |_/ \/ o + + +` + +func init() { + rand.Seed(time.Now().UnixNano()) + isWindows = runtime.GOOS == "windows" + + RootCmd.AddCommand(creditsCmd) + + creditsCmd.Flags().BoolP("static", "s", false, "Print a static version of the credits") +} + +var creditsCmd = &cobra.Command{ + Use: "credits [repository]", + Short: "View project's credits", + Long: `View animated credits for this or another project. + +Examples: + + gh credits # see a credits animation for this project + gh credits owner/repo # see a credits animation for owner/repo + gh credts -s # display a static thank you instead of animating + gh credits | cat # just print the contributors, one per line +`, + Args: cobra.MaximumNArgs(1), + RunE: credits, +} + +func credits(cmd *cobra.Command, args []string) error { + ctx := contextForCommand(cmd) + client, err := apiClientForContext(ctx) + if err != nil { + return err + } + + owner := "cli" + repo := "cli" + if len(args) > 0 { + parts := strings.SplitN(args[0], "/", 2) + owner = parts[0] + repo = parts[1] + } + + type Contributor struct { + Login string + } + + type Result []Contributor + + result := Result{} + body := bytes.NewBufferString("") + path := fmt.Sprintf("repos/%s/%s/contributors", owner, repo) + + err = client.REST("GET", path, body, &result) + if err != nil { + return err + } + + out := cmd.OutOrStdout() + isTTY := false + outFile, isFile := out.(*os.File) + if isFile { + isTTY = utils.IsTerminal(outFile) + if isTTY { + // FIXME: duplicates colorableOut + out = utils.NewColorable(outFile) + } + } + + static, err := cmd.Flags().GetBool("static") + if err != nil { + return err + } + + static = static || isWindows + + if isTTY && static { + fmt.Fprintln(out, "THANK YOU CONTRIBUTORS!!! <3") + fmt.Println() + } + + logins := []string{} + for x, c := range result { + if isTTY && !static { + logins = append(logins, getColor(x)(c.Login)) + } else { + fmt.Fprintf(out, "%s\n", c.Login) + } + } + + if !isTTY || static { + return nil + } + + lines := []string{} + + thankLines := strings.Split(thankYou, "\n") + for x, tl := range thankLines { + lines = append(lines, getColor(x)(tl)) + } + lines = append(lines, "") + lines = append(lines, logins...) + lines = append(lines, "( <3 press ctrl-c to quit <3 )") + + termWidth, termHeight, err := terminal.GetSize(int(outFile.Fd())) + if err != nil { + return err + } + + margin := termWidth / 3 + + starLinesLeft := []string{} + for x := 0; x < len(lines); x++ { + starLinesLeft = append(starLinesLeft, starLine(margin)) + } + + starLinesRight := []string{} + for x := 0; x < len(lines); x++ { + lineWidth := termWidth - (margin + len(lines[x])) + starLinesRight = append(starLinesRight, starLine(lineWidth)) + } + + loop := true + startx := termHeight - 1 + li := 0 + + for loop { + clear() + for x := 0; x < termHeight; x++ { + if x == startx || startx < 0 { + starty := 0 + if startx < 0 { + starty = int(math.Abs(float64(startx))) + } + for y := starty; y < li+1; y++ { + if y >= len(lines) { + continue + } + starLineLeft := starLinesLeft[y] + starLinesLeft[y] = twinkle(starLineLeft) + starLineRight := starLinesRight[y] + starLinesRight[y] = twinkle(starLineRight) + fmt.Fprintf(out, "%s %s %s\n", starLineLeft, lines[y], starLineRight) + } + li += 1 + x += li + } else { + fmt.Fprintf(out, "\n") + } + } + if li < len(lines) { + startx -= 1 + } + time.Sleep(300 * time.Millisecond) + } + + return nil +} + +func starLine(width int) string { + line := "" + starChance := 0.1 + for y := 0; y < width; y++ { + chance := rand.Float64() + if chance <= starChance { + charRoll := rand.Float64() + switch { + case charRoll < 0.3: + line += "." + case charRoll > 0.3 && charRoll < 0.6: + line += "+" + default: + line += "*" + } + } else { + line += " " + } + } + + return line +} + +func twinkle(starLine string) string { + starLine = strings.ReplaceAll(starLine, ".", "P") + starLine = strings.ReplaceAll(starLine, "+", "A") + starLine = strings.ReplaceAll(starLine, "*", ".") + starLine = strings.ReplaceAll(starLine, "P", "+") + starLine = strings.ReplaceAll(starLine, "A", "*") + return starLine +} + +func getColor(x int) func(string) string { + rainbow := []func(string) string{ + utils.Magenta, + utils.Red, + utils.Yellow, + utils.Green, + utils.Cyan, + utils.Blue, + } + + ix := x % len(rainbow) + + return rainbow[ix] +} + +func clear() { + // on windows we'd do cmd := exec.Command("cmd", "/c", "cls"); unfortunately the draw speed is so + // slow that the animation is very jerky, flashy, and painful to look at. + cmd := exec.Command("clear") + cmd.Stdout = os.Stdout + _ = cmd.Run() +}