package credits import ( "bytes" "fmt" "math" "math/rand" "net/http" "os" "os/exec" "runtime" "strings" "time" "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/api" "github.com/cli/cli/v2/internal/ghrepo" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/iostreams" "github.com/cli/cli/v2/utils" "github.com/spf13/cobra" ) type CreditsOptions struct { HttpClient func() (*http.Client, error) BaseRepo func() (ghrepo.Interface, error) IO *iostreams.IOStreams Repository string Static bool } func NewCmdCredits(f *cmdutil.Factory, runF func(*CreditsOptions) error) *cobra.Command { opts := &CreditsOptions{ HttpClient: f.HttpClient, IO: f.IOStreams, BaseRepo: f.BaseRepo, Repository: "cli/cli", } cmd := &cobra.Command{ Use: "credits", Short: "View credits for this tool", Long: `View animated credits for gh, the tool you are currently using :)`, Example: heredoc.Doc(` # See a credits animation for this project $ gh credits # Display a non-animated thank you $ gh credits -s # Just print the contributors, one per line $ gh credits | cat `), Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { if runF != nil { return runF(opts) } return creditsRun(opts) }, Hidden: true, } cmd.Flags().BoolVarP(&opts.Static, "static", "s", false, "Print a static version of the credits") return cmd } func NewCmdRepoCredits(f *cmdutil.Factory, runF func(*CreditsOptions) error) *cobra.Command { opts := &CreditsOptions{ HttpClient: f.HttpClient, BaseRepo: f.BaseRepo, IO: f.IOStreams, } cmd := &cobra.Command{ Use: "credits []", Short: "View credits for a repository", Example: heredoc.Doc(` # View credits for the current repository $ gh repo credits # View credits for a specific repository $ gh repo credits cool/repo # Print a non-animated thank you $ gh repo credits -s # Pipe to just print the contributors, one per line $ gh repo credits | cat `), Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { if len(args) > 0 { opts.Repository = args[0] } if runF != nil { return runF(opts) } return creditsRun(opts) }, Hidden: true, } cmd.Flags().BoolVarP(&opts.Static, "static", "s", false, "Print a static version of the credits") return cmd } func creditsRun(opts *CreditsOptions) error { isWindows := runtime.GOOS == "windows" httpClient, err := opts.HttpClient() if err != nil { return err } client := api.NewClientFromHTTP(httpClient) var baseRepo ghrepo.Interface if opts.Repository == "" { baseRepo, err = opts.BaseRepo() if err != nil { return err } } else { baseRepo, err = ghrepo.FromFullName(opts.Repository) if err != nil { return err } } type Contributor struct { Login string Type string } type Result []Contributor result := Result{} body := bytes.NewBufferString("") path := fmt.Sprintf("repos/%s/%s/contributors", baseRepo.RepoOwner(), baseRepo.RepoName()) err = client.REST(baseRepo.RepoHost(), "GET", path, body, &result) if err != nil { return err } isTTY := opts.IO.IsStdoutTTY() static := opts.Static || isWindows out := opts.IO.Out cs := opts.IO.ColorScheme() if isTTY && static { fmt.Fprintln(out, "THANK YOU CONTRIBUTORS!!! <3") fmt.Fprintln(out, "") } logins := []string{} for x, c := range result { if c.Type != "User" { continue } if isTTY && !static { logins = append(logins, cs.ColorFromString(getColor(x))(c.Login)) } else { fmt.Fprintf(out, "%s\n", c.Login) } } if !isTTY || static { return nil } r := rand.New(rand.NewSource(time.Now().UnixNano())) lines := []string{} thankLines := strings.Split(thankYou, "\n") for x, tl := range thankLines { lines = append(lines, cs.ColorFromString(getColor(x))(tl)) } lines = append(lines, "") lines = append(lines, logins...) lines = append(lines, "( <3 press ctrl-c to quit <3 )") termWidth, termHeight, err := utils.TerminalSize(out) if err != nil { return err } margin := termWidth / 3 starLinesLeft := []string{} for x := 0; x < len(lines); x++ { starLinesLeft = append(starLinesLeft, starLine(r, margin)) } starLinesRight := []string{} for x := 0; x < len(lines); x++ { lineWidth := termWidth - (margin + len(lines[x])) starLinesRight = append(starLinesRight, starLine(r, 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(r *rand.Rand, width int) string { line := "" starChance := 0.1 for y := 0; y < width; y++ { chance := r.Float64() if chance <= starChance { charRoll := r.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) string { rainbow := []string{ "magenta", "red", "yellow", "green", "cyan", "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() } var thankYou = ` _ _ | | | | _|_ | | __, _ _ | | __ | |/ \ / | / |/ | |/_) | | / \_| | |_/| |_/\_/|_/ | |_/| \_/ \_/|/\__/ \_/|_/ /| \| _ o | | | __ __ _ _ _|_ ,_ | | _|_ __ ,_ , | / / \_/ |/ | | / | | |/ \_| | | / \_/ | / \_| \___/\__/ | |_/|_/ |_/|_/\_/ \_/|_/|_/\__/ |_/ \/ o `