github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/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/ungtb10d/cli/v2/api" 17 "github.com/ungtb10d/cli/v2/internal/ghrepo" 18 "github.com/ungtb10d/cli/v2/pkg/cmdutil" 19 "github.com/ungtb10d/cli/v2/pkg/iostreams" 20 "github.com/ungtb10d/cli/v2/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: "ungtb10d/cli", 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 `