|
| 1 | +// Package cmd is a package that contains the root command (entrypoint) for the GitHub Skyline CLI tool. |
| 2 | +package cmd |
| 3 | + |
| 4 | +import ( |
| 5 | + "context" |
| 6 | + "fmt" |
| 7 | + "os" |
| 8 | + "time" |
| 9 | + |
| 10 | + "github.com/cli/go-gh/v2/pkg/auth" |
| 11 | + "github.com/cli/go-gh/v2/pkg/browser" |
| 12 | + "github.com/github/gh-skyline/cmd/skyline" |
| 13 | + "github.com/github/gh-skyline/internal/errors" |
| 14 | + "github.com/github/gh-skyline/internal/github" |
| 15 | + "github.com/github/gh-skyline/internal/logger" |
| 16 | + "github.com/github/gh-skyline/internal/utils" |
| 17 | + "github.com/spf13/cobra" |
| 18 | +) |
| 19 | + |
| 20 | +// Command line variables and root command configuration |
| 21 | +var ( |
| 22 | + yearRange string |
| 23 | + user string |
| 24 | + full bool |
| 25 | + debug bool |
| 26 | + web bool |
| 27 | + artOnly bool |
| 28 | + output string // new output path flag |
| 29 | +) |
| 30 | + |
| 31 | +// rootCmd is the root command for the GitHub Skyline CLI tool. |
| 32 | +var rootCmd = &cobra.Command{ |
| 33 | + Use: "skyline", |
| 34 | + Short: "Generate a 3D model of a user's GitHub contribution history", |
| 35 | + Long: `GitHub Skyline creates 3D printable STL files from GitHub contribution data. |
| 36 | +It can generate models for specific years or year ranges for the authenticated user or an optional specified user. |
| 37 | +
|
| 38 | +While the STL file is being generated, an ASCII preview will be displayed in the terminal. |
| 39 | +
|
| 40 | +ASCII Preview Legend: |
| 41 | + ' ' Empty/Sky - No contributions |
| 42 | + '.' Future dates - What contributions could you make? |
| 43 | + '░' Low level - Light contribution activity |
| 44 | + '▒' Medium level - Moderate contribution activity |
| 45 | + '▓' High level - Heavy contribution activity |
| 46 | + '╻┃╽' Top level - Last block with contributions in the week (Low, Medium, High) |
| 47 | +
|
| 48 | +Layout: |
| 49 | +Each column represents one week. Days within each week are reordered vertically |
| 50 | +to create a "building" effect, with empty spaces (no contributions) at the top.`, |
| 51 | + RunE: handleSkylineCommand, |
| 52 | +} |
| 53 | + |
| 54 | +// init initializes command line flags for the skyline CLI tool. |
| 55 | +func init() { |
| 56 | + initFlags() |
| 57 | +} |
| 58 | + |
| 59 | +// Execute initializes and executes the root command for the GitHub Skyline CLI. |
| 60 | +func Execute(_ context.Context) error { |
| 61 | + if err := rootCmd.Execute(); err != nil { |
| 62 | + return err |
| 63 | + } |
| 64 | + return nil |
| 65 | +} |
| 66 | + |
| 67 | +// initFlags sets up command line flags for the skyline CLI tool. |
| 68 | +func initFlags() { |
| 69 | + flags := rootCmd.Flags() |
| 70 | + flags.StringVarP(&yearRange, "year", "y", fmt.Sprintf("%d", time.Now().Year()), "Year or year range (e.g., 2024 or 2014-2024)") |
| 71 | + flags.StringVarP(&user, "user", "u", "", "GitHub username (optional, defaults to authenticated user)") |
| 72 | + flags.BoolVarP(&full, "full", "f", false, "Generate contribution graph from join year to current year") |
| 73 | + flags.BoolVarP(&debug, "debug", "d", false, "Enable debug logging") |
| 74 | + flags.BoolVarP(&web, "web", "w", false, "Open GitHub profile (authenticated or specified user).") |
| 75 | + flags.BoolVarP(&artOnly, "art-only", "a", false, "Generate only ASCII preview") |
| 76 | + flags.StringVarP(&output, "output", "o", "", "Output file path (optional)") |
| 77 | +} |
| 78 | + |
| 79 | +// executeRootCmd is the main execution function for the root command. |
| 80 | +func handleSkylineCommand(_ *cobra.Command, _ []string) error { |
| 81 | + log := logger.GetLogger() |
| 82 | + if debug { |
| 83 | + log.SetLevel(logger.DEBUG) |
| 84 | + if err := log.Debug("Debug logging enabled"); err != nil { |
| 85 | + return err |
| 86 | + } |
| 87 | + } |
| 88 | + |
| 89 | + client, err := github.InitializeGitHubClient() |
| 90 | + if err != nil { |
| 91 | + return errors.New(errors.NetworkError, "failed to initialize GitHub client", err) |
| 92 | + } |
| 93 | + |
| 94 | + if web { |
| 95 | + b := browser.New("", os.Stdout, os.Stderr) |
| 96 | + if err := openGitHubProfile(user, client, b); err != nil { |
| 97 | + fmt.Fprintf(os.Stderr, "Error: %v\n", err) |
| 98 | + os.Exit(1) |
| 99 | + } |
| 100 | + return nil |
| 101 | + } |
| 102 | + |
| 103 | + startYear, endYear, err := utils.ParseYearRange(yearRange) |
| 104 | + if err != nil { |
| 105 | + return fmt.Errorf("invalid year range: %v", err) |
| 106 | + } |
| 107 | + |
| 108 | + return skyline.GenerateSkyline(startYear, endYear, user, full, output, artOnly) |
| 109 | +} |
| 110 | + |
| 111 | +// Browser interface matches browser.Browser functionality. |
| 112 | +type Browser interface { |
| 113 | + Browse(url string) error |
| 114 | +} |
| 115 | + |
| 116 | +// openGitHubProfile opens the GitHub profile page for the specified user or authenticated user. |
| 117 | +func openGitHubProfile(targetUser string, client skyline.GitHubClientInterface, b Browser) error { |
| 118 | + if targetUser == "" { |
| 119 | + username, err := client.GetAuthenticatedUser() |
| 120 | + if err != nil { |
| 121 | + return errors.New(errors.NetworkError, "failed to get authenticated user", err) |
| 122 | + } |
| 123 | + targetUser = username |
| 124 | + } |
| 125 | + |
| 126 | + hostname, _ := auth.DefaultHost() |
| 127 | + profileURL := fmt.Sprintf("https://%s/%s", hostname, targetUser) |
| 128 | + return b.Browse(profileURL) |
| 129 | +} |
0 commit comments