github.com/secman-team/gh-api@v1.8.2/pkg/cmd/repo/credits/credits.go (about)

     1  package credits
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"math"
     7  	"math/rand"
     8  	"net/http"
     9  	"os"
    10  	"os/exec"
    11  	"runtime"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/MakeNowJust/heredoc"
    16  	"github.com/secman-team/gh-api/api"
    17  	"github.com/secman-team/gh-api/core/ghrepo"
    18  	"github.com/secman-team/gh-api/pkg/cmdutil"
    19  	"github.com/secman-team/gh-api/pkg/iostreams"
    20  	"github.com/secman-team/gh-api/utils"
    21  	"github.com/spf13/cobra"
    22  )
    23  
    24  type CreditsOptions struct {
    25  	HttpClient func() (*http.Client, error)
    26  	BaseRepo   func() (ghrepo.Interface, error)
    27  	IO         *iostreams.IOStreams
    28  
    29  	Repository string
    30  	Static     bool
    31  }
    32  
    33  func NewCmdCredits(f *cmdutil.Factory, runF func(*CreditsOptions) error) *cobra.Command {
    34  	opts := &CreditsOptions{
    35  		HttpClient: f.HttpClient,
    36  		IO:         f.IOStreams,
    37  		BaseRepo:   f.BaseRepo,
    38  		Repository: "secman-team/gh-api",
    39  	}
    40  
    41  	cmd := &cobra.Command{
    42  		Use:   "credits",
    43  		Short: "View credits for this tool",
    44  		Long:  `View animated credits for gh, the tool you are currently using :)`,
    45  		Example: heredoc.Doc(`
    46  			# see a credits animation for this project
    47  			$ gh credits
    48  
    49  			# display a non-animated thank you
    50  			$ gh credits -s
    51  
    52  			# just print the contributors, one per line
    53  			$ gh credits | cat
    54  		`),
    55  		Args: cobra.ExactArgs(0),
    56  		RunE: func(cmd *cobra.Command, args []string) error {
    57  			if runF != nil {
    58  				return runF(opts)
    59  			}
    60  
    61  			return creditsRun(opts)
    62  		},
    63  		Hidden: true,
    64  	}
    65  
    66  	cmd.Flags().BoolVarP(&opts.Static, "static", "s", false, "Print a static version of the credits")
    67  
    68  	return cmd
    69  }
    70  
    71  func NewCmdRepoCredits(f *cmdutil.Factory, runF func(*CreditsOptions) error) *cobra.Command {
    72  	opts := &CreditsOptions{
    73  		HttpClient: f.HttpClient,
    74  		BaseRepo:   f.BaseRepo,
    75  		IO:         f.IOStreams,
    76  	}
    77  
    78  	cmd := &cobra.Command{
    79  		Use:   "credits [<repository>]",
    80  		Short: "View credits for a repository",
    81  		Example: heredoc.Doc(`
    82        # view credits for the current repository
    83        $ gh repo credits
    84  
    85        # view credits for a specific repository
    86        $ gh repo credits cool/repo
    87  
    88        # print a non-animated thank you
    89        $ gh repo credits -s
    90  
    91        # pipe to just print the contributors, one per line
    92        $ gh repo credits | cat
    93      `),
    94  		Args: cobra.MaximumNArgs(1),
    95  		RunE: func(cmd *cobra.Command, args []string) error {
    96  			if len(args) > 0 {
    97  				opts.Repository = args[0]
    98  			}
    99  
   100  			if runF != nil {
   101  				return runF(opts)
   102  			}
   103  
   104  			return creditsRun(opts)
   105  		},
   106  		Hidden: true,
   107  	}
   108  
   109  	cmd.Flags().BoolVarP(&opts.Static, "static", "s", false, "Print a static version of the credits")
   110  
   111  	return cmd
   112  }
   113  
   114  func creditsRun(opts *CreditsOptions) error {
   115  	isWindows := runtime.GOOS == "windows"
   116  	httpClient, err := opts.HttpClient()
   117  	if err != nil {
   118  		return err
   119  	}
   120  
   121  	client := api.NewClientFromHTTP(httpClient)
   122  
   123  	var baseRepo ghrepo.Interface
   124  	if opts.Repository == "" {
   125  		baseRepo, err = opts.BaseRepo()
   126  		if err != nil {
   127  			return err
   128  		}
   129  	} else {
   130  		baseRepo, err = ghrepo.FromFullName(opts.Repository)
   131  		if err != nil {
   132  			return err
   133  		}
   134  	}
   135  
   136  	type Contributor struct {
   137  		Login string
   138  		Type  string
   139  	}
   140  
   141  	type Result []Contributor
   142  
   143  	result := Result{}
   144  	body := bytes.NewBufferString("")
   145  	path := fmt.Sprintf("repos/%s/%s/contributors", baseRepo.RepoOwner(), baseRepo.RepoName())
   146  
   147  	err = client.REST(baseRepo.RepoHost(), "GET", path, body, &result)
   148  	if err != nil {
   149  		return err
   150  	}
   151  
   152  	isTTY := opts.IO.IsStdoutTTY()
   153  
   154  	static := opts.Static || isWindows
   155  
   156  	out := opts.IO.Out
   157  	cs := opts.IO.ColorScheme()
   158  
   159  	if isTTY && static {
   160  		fmt.Fprintln(out, "THANK YOU CONTRIBUTORS!!! <3")
   161  		fmt.Fprintln(out, "")
   162  	}
   163  
   164  	logins := []string{}
   165  	for x, c := range result {
   166  		if c.Type != "User" {
   167  			continue
   168  		}
   169  
   170  		if isTTY && !static {
   171  			logins = append(logins, cs.ColorFromString(getColor(x))(c.Login))
   172  		} else {
   173  			fmt.Fprintf(out, "%s\n", c.Login)
   174  		}
   175  	}
   176  
   177  	if !isTTY || static {
   178  		return nil
   179  	}
   180  
   181  	rand.Seed(time.Now().UnixNano())
   182  
   183  	lines := []string{}
   184  
   185  	thankLines := strings.Split(thankYou, "\n")
   186  	for x, tl := range thankLines {
   187  		lines = append(lines, cs.ColorFromString(getColor(x))(tl))
   188  	}
   189  	lines = append(lines, "")
   190  	lines = append(lines, logins...)
   191  	lines = append(lines, "( <3 press ctrl-c to quit <3 )")
   192  
   193  	termWidth, termHeight, err := utils.TerminalSize(out)
   194  	if err != nil {
   195  		return err
   196  	}
   197  
   198  	margin := termWidth / 3
   199  
   200  	starLinesLeft := []string{}
   201  	for x := 0; x < len(lines); x++ {
   202  		starLinesLeft = append(starLinesLeft, starLine(margin))
   203  	}
   204  
   205  	starLinesRight := []string{}
   206  	for x := 0; x < len(lines); x++ {
   207  		lineWidth := termWidth - (margin + len(lines[x]))
   208  		starLinesRight = append(starLinesRight, starLine(lineWidth))
   209  	}
   210  
   211  	loop := true
   212  	startx := termHeight - 1
   213  	li := 0
   214  
   215  	for loop {
   216  		clear()
   217  		for x := 0; x < termHeight; x++ {
   218  			if x == startx || startx < 0 {
   219  				starty := 0
   220  				if startx < 0 {
   221  					starty = int(math.Abs(float64(startx)))
   222  				}
   223  				for y := starty; y < li+1; y++ {
   224  					if y >= len(lines) {
   225  						continue
   226  					}
   227  					starLineLeft := starLinesLeft[y]
   228  					starLinesLeft[y] = twinkle(starLineLeft)
   229  					starLineRight := starLinesRight[y]
   230  					starLinesRight[y] = twinkle(starLineRight)
   231  					fmt.Fprintf(out, "%s %s %s\n", starLineLeft, lines[y], starLineRight)
   232  				}
   233  				li += 1
   234  				x += li
   235  			} else {
   236  				fmt.Fprintf(out, "\n")
   237  			}
   238  		}
   239  		if li < len(lines) {
   240  			startx -= 1
   241  		}
   242  		time.Sleep(300 * time.Millisecond)
   243  	}
   244  
   245  	return nil
   246  }
   247  
   248  func starLine(width int) string {
   249  	line := ""
   250  	starChance := 0.1
   251  	for y := 0; y < width; y++ {
   252  		chance := rand.Float64()
   253  		if chance <= starChance {
   254  			charRoll := rand.Float64()
   255  			switch {
   256  			case charRoll < 0.3:
   257  				line += "."
   258  			case charRoll > 0.3 && charRoll < 0.6:
   259  				line += "+"
   260  			default:
   261  				line += "*"
   262  			}
   263  		} else {
   264  			line += " "
   265  		}
   266  	}
   267  
   268  	return line
   269  }
   270  
   271  func twinkle(starLine string) string {
   272  	starLine = strings.ReplaceAll(starLine, ".", "P")
   273  	starLine = strings.ReplaceAll(starLine, "+", "A")
   274  	starLine = strings.ReplaceAll(starLine, "*", ".")
   275  	starLine = strings.ReplaceAll(starLine, "P", "+")
   276  	starLine = strings.ReplaceAll(starLine, "A", "*")
   277  	return starLine
   278  }
   279  
   280  func getColor(x int) string {
   281  	rainbow := []string{
   282  		"magenta",
   283  		"red",
   284  		"yellow",
   285  		"green",
   286  		"cyan",
   287  		"blue",
   288  	}
   289  
   290  	ix := x % len(rainbow)
   291  
   292  	return rainbow[ix]
   293  }
   294  
   295  func clear() {
   296  	// on windows we'd do cmd := exec.Command("cmd", "/c", "cls"); unfortunately the draw speed is so
   297  	// slow that the animation is very jerky, flashy, and painful to look at.
   298  	cmd := exec.Command("clear")
   299  	cmd.Stdout = os.Stdout
   300  	_ = cmd.Run()
   301  }
   302  
   303  var thankYou = `
   304       _                    _
   305      | |                  | |
   306  _|_ | |     __,   _  _   | |           __
   307   |  |/ \   /  |  / |/ |  |/_)   |   | /  \_|   |
   308   |_/|   |_/\_/|_/  |  |_/| \_/   \_/|/\__/  \_/|_/
   309                                     /|
   310                                     \|
   311                                _
   312                             o | |                           |
   313   __   __   _  _  _|_  ,_     | |        _|_  __   ,_    ,  |
   314  /    /  \_/ |/ |  |  /  |  | |/ \_|   |  |  /  \_/  |  / \_|
   315  \___/\__/   |  |_/|_/   |_/|_/\_/  \_/|_/|_/\__/    |_/ \/ o
   316  
   317  
   318  `