From 05d421ecffd34479ccddc3c416bdfa427e52b737 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Sun, 1 Dec 2024 14:29:32 +0000 Subject: [PATCH 01/94] Add feature request issue template --- .github/ISSUE_TEMPLATE/1-feature-request.yml | 53 ++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/1-feature-request.yml diff --git a/.github/ISSUE_TEMPLATE/1-feature-request.yml b/.github/ISSUE_TEMPLATE/1-feature-request.yml new file mode 100644 index 0000000..0d7149f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1-feature-request.yml @@ -0,0 +1,53 @@ +name: Feature Request +description: Propose a new feature or enhancement +title: "[Feature]: " +labels: ["enhancement", "triage"] +assignees: + - chrisreddington +body: + - type: markdown + attributes: + value: | + Thank you for taking the time to submit a feature request! Please check for existing requests before submitting. If you find one, feel free to add more information or give a 👍 to the original issue. + + - type: textarea + id: description + attributes: + label: Description + description: Describe the feature or enhancement you're proposing + placeholder: Explain the current situation and why this change would be beneficial + validations: + required: true + + - type: textarea + id: requirements + attributes: + label: Requirements + description: List the specific requirements or changes needed + placeholder: | + - Requirement 1 + - Requirement 2 + - Requirement 3 + validations: + required: true + + - type: textarea + id: definition-of-done + attributes: + label: Definition of Done + description: What criteria need to be met for this feature to be considered complete? + value: | + - [ ] Criterion 1 + - [ ] Criterion 2 + - [ ] Criterion 3 + - [ ] Relevant documentation is updated + - [ ] Unit tests are updated/written and passing + validations: + required: true + + - type: textarea + id: additional-notes + attributes: + label: Additional Notes + description: Any other context, implementation suggestions, or considerations + placeholder: Add any other relevant information here From 8df5caf3fb952c9b0b681b8c2c90b471bb7bb45d Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Sun, 1 Dec 2024 14:29:34 +0000 Subject: [PATCH 02/94] Add bug report issue template --- .github/ISSUE_TEMPLATE/2-bug-report.yml | 41 +++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/2-bug-report.yml diff --git a/.github/ISSUE_TEMPLATE/2-bug-report.yml b/.github/ISSUE_TEMPLATE/2-bug-report.yml new file mode 100644 index 0000000..0cbf55e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2-bug-report.yml @@ -0,0 +1,41 @@ +name: Bug Report +description: File a bug report. +title: "[Bug]: " +labels: ["bug", "triage"] +assignees: + - chrisreddington +body: + - type: markdown + attributes: | + Thank you for taking the time to report this issue! Please check for existing issues before submitting. If you find one, feel free to add more information or give a 👍 to the original issue. + - type: input + id: gh-cli-version + attributes: + label: GitHub CLI Version + description: What version of the GitHub CLI are you using? + placeholder: 2.0.0 + - type: input + id: gh-skyline-version + attributes: + label: gh-skyline Version + description: What version of gh-skyline are you using? + placeholder: 0.0.3 + - type: dropdown + id: os + attributes: + label: What Operating System are you seeing the problem on? + multiple: true + options: + - Linux + - macOS + - Windows + - Other + - type: textarea + id: what-happened + attributes: + label: What happened? + description: Also tell us, what did you expect to happen? + placeholder: Tell us what you see! + value: "A bug happened!" + validations: + required: true From 52a0fd178c18b32975c029d65bc26c352bd713af Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Sun, 1 Dec 2024 14:31:43 +0000 Subject: [PATCH 03/94] Fix improper yaml schema --- .github/ISSUE_TEMPLATE/2-bug-report.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/2-bug-report.yml b/.github/ISSUE_TEMPLATE/2-bug-report.yml index 0cbf55e..5af8c67 100644 --- a/.github/ISSUE_TEMPLATE/2-bug-report.yml +++ b/.github/ISSUE_TEMPLATE/2-bug-report.yml @@ -6,7 +6,8 @@ assignees: - chrisreddington body: - type: markdown - attributes: | + attributes: + value: | Thank you for taking the time to report this issue! Please check for existing issues before submitting. If you find one, feel free to add more information or give a 👍 to the original issue. - type: input id: gh-cli-version From 78d06475a5cac42d7422c933afda7212290f367d Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Sun, 1 Dec 2024 14:32:20 +0000 Subject: [PATCH 04/94] Fix indentation --- .github/ISSUE_TEMPLATE/2-bug-report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/2-bug-report.yml b/.github/ISSUE_TEMPLATE/2-bug-report.yml index 5af8c67..2f36255 100644 --- a/.github/ISSUE_TEMPLATE/2-bug-report.yml +++ b/.github/ISSUE_TEMPLATE/2-bug-report.yml @@ -8,7 +8,7 @@ body: - type: markdown attributes: value: | - Thank you for taking the time to report this issue! Please check for existing issues before submitting. If you find one, feel free to add more information or give a 👍 to the original issue. + Thank you for taking the time to report this issue! Please check for existing issues before submitting. If you find one, feel free to add more information or give a 👍 to the original issue. - type: input id: gh-cli-version attributes: From 5159bca3e5667524cc09ffb7b0a6278e692bbf4b Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Sun, 1 Dec 2024 14:33:11 +0000 Subject: [PATCH 05/94] Update issue templates to include links for checking existing requests and bugs --- .github/ISSUE_TEMPLATE/1-feature-request.yml | 2 +- .github/ISSUE_TEMPLATE/2-bug-report.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/1-feature-request.yml b/.github/ISSUE_TEMPLATE/1-feature-request.yml index 0d7149f..ae07e41 100644 --- a/.github/ISSUE_TEMPLATE/1-feature-request.yml +++ b/.github/ISSUE_TEMPLATE/1-feature-request.yml @@ -8,7 +8,7 @@ body: - type: markdown attributes: value: | - Thank you for taking the time to submit a feature request! Please check for existing requests before submitting. If you find one, feel free to add more information or give a 👍 to the original issue. + Thank you for taking the time to submit a feature request! Please [check for existing requests](https://github.com/github/gh-skyline/issues) before submitting. If you find one, feel free to add more information or give a 👍 to the original issue. - type: textarea id: description diff --git a/.github/ISSUE_TEMPLATE/2-bug-report.yml b/.github/ISSUE_TEMPLATE/2-bug-report.yml index 2f36255..ceb3a0c 100644 --- a/.github/ISSUE_TEMPLATE/2-bug-report.yml +++ b/.github/ISSUE_TEMPLATE/2-bug-report.yml @@ -8,7 +8,7 @@ body: - type: markdown attributes: value: | - Thank you for taking the time to report this issue! Please check for existing issues before submitting. If you find one, feel free to add more information or give a 👍 to the original issue. + Thank you for taking the time to report this issue! Please [check for existing bugs](https://github.com/github/gh-skyline/issues) before submitting. If you find one, feel free to add more information or give a 👍 to the original issue. - type: input id: gh-cli-version attributes: From 78ed0b8bb4ef3950f61ea7d06e1aa93f45ac733d Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Fri, 6 Dec 2024 13:19:17 +0000 Subject: [PATCH 06/94] Update images --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7952ccc..a22c185 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,13 @@ A GitHub CLI extension that generates 3D-printable STL files of your GitHub cont - Generate a Binary STL file from GitHub contribution data for 3D printing - Customizable year selection (single year and multi-year) + + ![Example GitHub Skyline](https://github.com/user-attachments/assets/f3f5d3d8-cd15-40c4-a2cb-2bbd74bdbc4e) + - Automatic authentication via GitHub CLI or specify a user - ASCII art loading preview of contribution data unique to each user and year -![Examples of gh-skyline being used in the terminal](https://github.com/user-attachments/assets/a69de393-64d1-4eba-9190-f6e2f81c5145) + ![Example of gh-skyline and ASCII art in the terminal](https://github.com/user-attachments/assets/8ddda088-ac5a-4020-8ae0-ef0d4825f6a1) ## Usage From 0eaf612dbb3988cc2abbf93fed7060293eb9f688 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Sat, 7 Dec 2024 05:32:39 +0000 Subject: [PATCH 07/94] Update dependencies, Refactor main command structure, add --web and --output --- go.mod | 5 +++ go.sum | 12 +++++ main.go | 136 ++++++++++++++++++++++++++++++++++++++++---------------- 3 files changed, 114 insertions(+), 39 deletions(-) diff --git a/go.mod b/go.mod index 3a61c55..191a873 100644 --- a/go.mod +++ b/go.mod @@ -11,16 +11,21 @@ require ( require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/cli/browser v1.3.0 // indirect github.com/cli/safeexec v1.0.1 // indirect github.com/cli/shurcooL-graphql v0.0.4 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/henvic/httpretty v0.1.4 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/muesli/termenv v0.15.2 // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/spf13/cobra v1.8.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect github.com/thlib/go-timezone-local v0.0.3 // indirect golang.org/x/image v0.22.0 // indirect golang.org/x/sys v0.27.0 // indirect diff --git a/go.sum b/go.sum index 4f9c6fb..978888d 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,14 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo= +github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk= github.com/cli/go-gh/v2 v2.11.1 h1:amAyfqMWQTBdue8iTmDUegGZK7c8kk6WCxD9l/wLtGI= github.com/cli/go-gh/v2 v2.11.1/go.mod h1:MeRoKzXff3ygHu7zP+NVTT+imcHW6p3tpuxHAzRM2xE= github.com/cli/safeexec v1.0.1 h1:e/C79PbXF4yYTN/wauC4tviMxEV13BwljGj0N9j+N00= github.com/cli/safeexec v1.0.1/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q= github.com/cli/shurcooL-graphql v0.0.4 h1:6MogPnQJLjKkaXPyGqPRXOI2qCsQdqNfUY1QSJu2GuY= github.com/cli/shurcooL-graphql v0.0.4/go.mod h1:3waN4u02FiZivIV+p1y4d0Jo1jc6BViMA73C+sZo2fk= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -13,10 +16,14 @@ github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/henvic/httpretty v0.1.4 h1:Jo7uwIRWVFxkqOnErcoYfH90o3ddQyVrSANeS4cxYmU= github.com/henvic/httpretty v0.1.4/go.mod h1:Dn60sQTZfbt2dYsdUSNsCljyF4AfdqnuJFDLJA1I4AM= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -34,6 +41,11 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/thlib/go-timezone-local v0.0.3 h1:ie5XtZWG5lQ4+1MtC5KZ/FeWlOKzW2nPoUnXYUbV/1s= diff --git a/main.go b/main.go index 5af66a3..7d0be61 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,6 @@ -// Package main provides the entry point for the GitHub Skyline Generator. -// It generates a 3D model of GitHub contributions in STL format. package main import ( - "flag" "fmt" "os" "strconv" @@ -11,21 +8,89 @@ import ( "time" "github.com/cli/go-gh/v2/pkg/api" + "github.com/cli/go-gh/v2/pkg/browser" "github.com/github/gh-skyline/ascii" "github.com/github/gh-skyline/errors" "github.com/github/gh-skyline/github" "github.com/github/gh-skyline/logger" "github.com/github/gh-skyline/stl" "github.com/github/gh-skyline/types" + "github.com/spf13/cobra" ) +// Constants for GitHub launch year and default output file format const ( - // githubLaunchYear represents the year GitHub was launched and contributions began githubLaunchYear = 2008 - // outputFileFormat defines the format for the generated STL file outputFileFormat = "%s-%s-github-skyline.stl" ) +// Command line variables and root command configuration +var ( + yearRange string + user string + full bool + debug bool + web bool + output string // new output path flag + + rootCmd = &cobra.Command{ + Use: "skyline", + Short: "Generate a 3D model of a user's GitHub contribution history", + Long: `GitHub Skyline creates 3D printable STL files from GitHub contribution data. +It can generate models for specific years or year ranges for the authenticated user or an optional specified user. + +ASCII Preview Legend: + ' ' Empty/Sky - No contributions + '.' Future dates - What contributions could you make? + '░' Low level - Light contribution activity + '▒' Medium level - Moderate contribution activity + '▓' High level - Heavy contribution activity + '╻┃╽' Top level - Last block with contributions in the week (Low, Medium, High) + +Layout: +Each column represents one week. Days within each week are reordered vertically +to create a "building" effect, with empty spaces (no contributions) at the top.`, + RunE: func(cmd *cobra.Command, args []string) error { + log := logger.GetLogger() + if debug { + log.SetLevel(logger.DEBUG) + if err := log.Debug("Debug logging enabled"); err != nil { + return err + } + } + + if web { + return openGitHubProfile(user) + } + + startYear, endYear, err := parseYearRange(yearRange) + if err != nil { + return fmt.Errorf("invalid year range: %v", err) + } + + return generateSkyline(startYear, endYear, user, full) + }, + } +) + +// init sets up command line flags for the skyline CLI tool +func init() { + rootCmd.Flags().StringVarP(&yearRange, "year", "y", fmt.Sprintf("%d", time.Now().Year()), "Year or year range (e.g., 2024 or 2014-2024)") + rootCmd.Flags().StringVarP(&user, "user", "u", "", "GitHub username (optional, defaults to authenticated user)") + rootCmd.Flags().BoolVarP(&full, "full", "f", false, "Generate contribution graph from join year to current year") + rootCmd.Flags().BoolVarP(&debug, "debug", "d", false, "Enable debug logging") + rootCmd.Flags().BoolVarP(&web, "web", "w", false, "Open GitHub profile (authenticated or specified user).") + rootCmd.Flags().StringVarP(&output, "output", "o", "", "Output file path (optional)") +} + +// main initializes and executes the root command for the GitHub Skyline CLI +func main() { + if err := rootCmd.Execute(); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } +} + // formatYearRange returns a formatted string representation of the year range func formatYearRange(startYear, endYear int) string { if startYear == endYear { @@ -36,6 +101,13 @@ func formatYearRange(startYear, endYear int) string { // generateOutputFilename creates a consistent filename for the STL output func generateOutputFilename(user string, startYear, endYear int) string { + if output != "" { + // Ensure the filename ends with .stl + if !strings.HasSuffix(strings.ToLower(output), ".stl") { + return output + ".stl" + } + return output + } yearStr := formatYearRange(startYear, endYear) return fmt.Sprintf(outputFileFormat, user, yearStr) } @@ -143,40 +215,6 @@ func fetchContributionData(client *github.Client, username string, year int) ([] return contributionGrid, nil } -// main is the entry point for the GitHub Skyline Generator. -func main() { - yearRange := flag.String("year", fmt.Sprintf("%d", time.Now().Year()), "Year or year range (e.g., 2024 or 2014-2024)") - user := flag.String("user", "", "GitHub username (optional, defaults to authenticated user)") - full := flag.Bool("full", false, "Generate contribution graph from join year to current year") - debug := flag.Bool("debug", false, "Enable debug logging") - flag.Parse() - - log := logger.GetLogger() - if *debug { - log.SetLevel(logger.DEBUG) - if err := log.Debug("Debug logging enabled"); err != nil { - fmt.Fprintf(os.Stderr, "Failed to enable debug logging: %v\n", err) - os.Exit(1) - } - } - - // Parse year range - startYear, endYear, err := parseYearRange(*yearRange) - if err != nil { - if logErr := log.Error("Invalid year range: %v", err); logErr != nil { - fmt.Fprintf(os.Stderr, "Failed to log error: %v\n", logErr) - } - os.Exit(1) - } - - if err := generateSkyline(startYear, endYear, *user, *full); err != nil { - if logErr := log.Error("Failed to generate skyline: %v", err); logErr != nil { - fmt.Fprintf(os.Stderr, "Failed to log error: %v\n", logErr) - } - os.Exit(1) - } -} - // Parse year range string (e.g., "2024" or "2014-2024") func parseYearRange(yearRange string) (startYear, endYear int, err error) { if strings.Contains(yearRange, "-") { @@ -212,3 +250,23 @@ func validateYearRange(startYear, endYear int) error { } return nil } + +// openGitHubProfile opens the GitHub profile page for the specified user or authenticated user +func openGitHubProfile(targetUser string) error { + if targetUser == "" { + client, err := initializeGitHubClient() + if err != nil { + return errors.New(errors.NetworkError, "failed to initialize GitHub client", err) + } + + username, err := client.GetAuthenticatedUser() + if err != nil { + return errors.New(errors.NetworkError, "failed to get authenticated user", err) + } + targetUser = username + } + + profileURL := fmt.Sprintf("https://github.com/%s", targetUser) + b := browser.New("", os.Stdout, os.Stderr) + return b.Browse(profileURL) +} From d0086ca92fd9f6e57f4d38cbbdfd64056939851e Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Sat, 7 Dec 2024 05:39:29 +0000 Subject: [PATCH 08/94] Refactor logger info level formatting to remove prefix and timestamp --- logger/logger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logger/logger.go b/logger/logger.go index 2922083..3e5575e 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -45,7 +45,7 @@ func GetLogger() *Logger { once.Do(func() { instance = &Logger{ debug: log.New(os.Stdout, "DEBUG: ", log.Ldate|log.Ltime|log.Lshortfile), - info: log.New(os.Stdout, "INFO: ", log.Ldate|log.Ltime), + info: log.New(os.Stdout, "", 0), warning: log.New(os.Stdout, "WARNING: ", log.Ldate|log.Ltime), error: log.New(os.Stderr, "ERROR: ", log.Ldate|log.Ltime), level: INFO, From 4f09ee4ee373f2973b24c6eff67a6ed27c2e0f3b Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Sat, 7 Dec 2024 05:45:37 +0000 Subject: [PATCH 09/94] Update README.md --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a22c185..535401b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +![Example GitHub Skyline](https://github.com/user-attachments/assets/ed0fe34e-6825-4eb2-91d7-a0834966dc3a) + # GitHub Skyline (gh-skyline) A GitHub CLI extension that generates 3D-printable STL files of your GitHub contribution graph. @@ -6,9 +8,6 @@ A GitHub CLI extension that generates 3D-printable STL files of your GitHub cont - Generate a Binary STL file from GitHub contribution data for 3D printing - Customizable year selection (single year and multi-year) - - ![Example GitHub Skyline](https://github.com/user-attachments/assets/f3f5d3d8-cd15-40c4-a2cb-2bbd74bdbc4e) - - Automatic authentication via GitHub CLI or specify a user - ASCII art loading preview of contribution data unique to each user and year From 6f235d50278fdb0084387d377ed25ac90a70e68c Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Sat, 7 Dec 2024 05:53:00 +0000 Subject: [PATCH 10/94] Move images into a table below the feature list --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 535401b..31577b5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -![Example GitHub Skyline](https://github.com/user-attachments/assets/ed0fe34e-6825-4eb2-91d7-a0834966dc3a) - # GitHub Skyline (gh-skyline) A GitHub CLI extension that generates 3D-printable STL files of your GitHub contribution graph. @@ -11,7 +9,10 @@ A GitHub CLI extension that generates 3D-printable STL files of your GitHub cont - Automatic authentication via GitHub CLI or specify a user - ASCII art loading preview of contribution data unique to each user and year - ![Example of gh-skyline and ASCII art in the terminal](https://github.com/user-attachments/assets/8ddda088-ac5a-4020-8ae0-ef0d4825f6a1) + +| 3D Print | ASCII Art | +|------------------------|-------------------| +| ![Example GitHub Skyline](https://github.com/user-attachments/assets/ed0fe34e-6825-4eb2-91d7-a0834966dc3a) | ![Example of gh-skyline and ASCII art in the terminal](https://github.com/user-attachments/assets/8ddda088-ac5a-4020-8ae0-ef0d4825f6a1) | ## Usage From 8386833fac1d6e3b6f51c2ef01781f8b57c59561 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Sat, 7 Dec 2024 05:53:15 +0000 Subject: [PATCH 11/94] Remove unnecessary blank line in README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 31577b5..8b95b5c 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,6 @@ A GitHub CLI extension that generates 3D-printable STL files of your GitHub cont - Automatic authentication via GitHub CLI or specify a user - ASCII art loading preview of contribution data unique to each user and year - | 3D Print | ASCII Art | |------------------------|-------------------| | ![Example GitHub Skyline](https://github.com/user-attachments/assets/ed0fe34e-6825-4eb2-91d7-a0834966dc3a) | ![Example of gh-skyline and ASCII art in the terminal](https://github.com/user-attachments/assets/8ddda088-ac5a-4020-8ae0-ef0d4825f6a1) | From 0b92f6b8898cecbee458b277e2c74102164d356d Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Sat, 7 Dec 2024 05:58:58 +0000 Subject: [PATCH 12/94] Update table formatting in README.md from prettier fix --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8b95b5c..3740977 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ A GitHub CLI extension that generates 3D-printable STL files of your GitHub cont - Automatic authentication via GitHub CLI or specify a user - ASCII art loading preview of contribution data unique to each user and year -| 3D Print | ASCII Art | -|------------------------|-------------------| +| 3D Print | ASCII Art | +| ---------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | ![Example GitHub Skyline](https://github.com/user-attachments/assets/ed0fe34e-6825-4eb2-91d7-a0834966dc3a) | ![Example of gh-skyline and ASCII art in the terminal](https://github.com/user-attachments/assets/8ddda088-ac5a-4020-8ae0-ef0d4825f6a1) | ## Usage From 80b3fa1a0e80a747c0c619296539fabb0adc7d01 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Sat, 7 Dec 2024 06:10:09 +0000 Subject: [PATCH 13/94] Additional command flags and usage examples; Add ASCII legend --- README.md | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 86 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 3740977..bb8ff71 100644 --- a/README.md +++ b/README.md @@ -31,14 +31,20 @@ gh extension install github/gh-skyline You can run the `gh skyline` command with the following flags: -- `--user`: Specify the GitHub username. If not provided, the authenticated user is used. +- `-d`, `--debug`: Enable debug logging for more detailed output. + - Example: `gh skyline --debug` +- `-h`, `--help`: Show help for the command. + - Example: `gh skyline --help` +- `-f`, `--full`: Generate the contribution graph from the user's join year to the current year. + - Example: `gh skyline --full` +- `-o`, `--output`: Specify the output file name. If not provided, the default is `{username}-{year}-github-skyline.stl`. + - Example: `gh skyline --output my-skyline.stl` +- `-u`, `--user`: Specify the GitHub username. If not provided, the authenticated user is used. - Example: `gh skyline --user mona` -- `--year`: Specify the year or range of years for the skyline. Must be between 2008 and the current year. +- `-y`, `--year`: Specify the year or range of years for the skyline. Must be between 2008 and the current year. - Examples: `gh skyline --year 2020`, `gh skyline --year 2014-2024` -- `--full`: Generate the contribution graph from the user's join year to the current year. - - Example: `gh skyline --full` -- `--debug`: Enable debug logging for more detailed output. - - Example: `gh skyline --debug` +- `-w`, `--web`: Open the GitHub profile for the authenticated or specified user. + - Example: `gh skyline --web`, `gh skyline --user mona --web` ### Examples @@ -78,7 +84,80 @@ Enable debug logging: gh skyline --debug ``` -This will create a `{username}-{year}-github-skyline.stl` file in your current directory. +By default, the CLI will create a `{username}-{year}-github-skyline.stl` file in your current directory. You can specify a different file name using the `--output` flag. + +```bash +gh skyline --output my-skyline.stl +``` + +Open the GitHub profile for the authenticated user: + +```bash +gh skyline --web +``` + +Open the GitHub profile for a specific user: + +```bash +gh skyline --user mona --web +``` + +## ASCII Art + +The extension generates ASCII art in terminal while loading, a unique and fun way to vizualise your contribution data while you wait! Each column represents one week. Days within each week are reordered vertically to create a "building" effect, with empty spaces (no contributions) at the top. + +- `' '` Empty/Sky: No contributions +- `'.'` Future dates: What contributions could you make? +- `'░'` Low level: Light contribution activity +- `'▒'` Medium level: Moderate contribution activity +- `'▓'` High level: Heavy contribution activity +- `'╻┃╽'` Top level: Last block with contributions in the week (Low, Medium, High) + +## Project Structure + +```text +├── ascii/ +│ ├── block.go: ASCII block character definitions for contribution levels +│ ├── block_test.go: Block character unit tests +│ ├── generator.go: Contribution visualization ASCII art generation +│ ├── generator_test.go: ASCII generation tests +│ ├── text.go: ASCII text formatting utilities +│ └── text_test.go: Text formatting unit tests +├── errors/ +│ ├── errors.go: Custom error types and domain-specific error handling +│ └── errors_test.go: Error handling unit tests +├── github/ +│ ├── client.go: GitHub API client for fetching contribution data +│ └── client_test.go: API client unit tests +├── logger/ +│ ├── logger.go: Thread-safe logging with severity levels +│ └── logger_test.go: Logger unit tests +├── stl/ +│ ├── generator.go: STL 3D model generation from contribution data +│ ├── generator_test.go: Model generation unit tests +│ ├── stl.go: STL binary file format implementation +│ ├── stl_test.go: STL file generation tests +│ └── geometry/ +│ ├── geometry.go: 3D geometry calculations and transformations +│ ├── geometry_test.go: Geometry unit tests +│ ├── shapes.go: Basic 3D primitive shape definitions +│ ├── text.go: 3D text geometry generation +│ └── text_test.go: Text geometry unit tests +├── types/ +│ ├── types.go: Shared data structures and interfaces +│ └── types_test.go: Data structure unit tests +└── main.go: CLI application entry point +``` + +## Contributing + +To contribute to the project, please read the instructions and contributing guidelines in [CONTRIBUTING.md](CONTRIBUTING.md). + +## License + +This project is licensed under the terms of the MIT open source license. Please refer to [MIT](./LICENSE) for the full terms. + +Mona Sans is licensed under the [SIL Open ## Project Structure From 808b1b94f49dd129c1914d0efa869158229c6ae5 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Sat, 7 Dec 2024 06:15:21 +0000 Subject: [PATCH 14/94] Remove duplicate content after refactoring error --- README.md | 50 ++------------------------------------------------ 1 file changed, 2 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index bb8ff71..6416604 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ You can run the `gh skyline` command with the following flags: - Example: `gh skyline --help` - `-f`, `--full`: Generate the contribution graph from the user's join year to the current year. - Example: `gh skyline --full` -- `-o`, `--output`: Specify the output file name. If not provided, the default is `{username}-{year}-github-skyline.stl`. +- `-o`, `--output`: Specify the output filename. If not provided, the default is `{username}-{year}-github-skyline.stl`. - Example: `gh skyline --output my-skyline.stl` - `-u`, `--user`: Specify the GitHub username. If not provided, the authenticated user is used. - Example: `gh skyline --user mona` @@ -84,7 +84,7 @@ Enable debug logging: gh skyline --debug ``` -By default, the CLI will create a `{username}-{year}-github-skyline.stl` file in your current directory. You can specify a different file name using the `--output` flag. +By default, the CLI will create a `{username}-{year}-github-skyline.stl` file in your current directory. You can specify a different filename using the `--output` flag. ```bash gh skyline --output my-skyline.stl @@ -157,52 +157,6 @@ To contribute to the project, please read the instructions and contributing guid This project is licensed under the terms of the MIT open source license. Please refer to [MIT](./LICENSE) for the full terms. -Mona Sans is licensed under the [SIL Open - -## Project Structure - -```text -├── ascii/ -│ ├── block.go: ASCII block character definitions for contribution levels -│ ├── block_test.go: Block character unit tests -│ ├── generator.go: Contribution visualization ASCII art generation -│ ├── generator_test.go: ASCII generation tests -│ ├── text.go: ASCII text formatting utilities -│ └── text_test.go: Text formatting unit tests -├── errors/ -│ ├── errors.go: Custom error types and domain-specific error handling -│ └── errors_test.go: Error handling unit tests -├── github/ -│ ├── client.go: GitHub API client for fetching contribution data -│ └── client_test.go: API client unit tests -├── logger/ -│ ├── logger.go: Thread-safe logging with severity levels -│ └── logger_test.go: Logger unit tests -├── stl/ -│ ├── generator.go: STL 3D model generation from contribution data -│ ├── generator_test.go: Model generation unit tests -│ ├── stl.go: STL binary file format implementation -│ ├── stl_test.go: STL file generation tests -│ └── geometry/ -│ ├── geometry.go: 3D geometry calculations and transformations -│ ├── geometry_test.go: Geometry unit tests -│ ├── shapes.go: Basic 3D primitive shape definitions -│ ├── text.go: 3D text geometry generation -│ └── text_test.go: Text geometry unit tests -├── types/ -│ ├── types.go: Shared data structures and interfaces -│ └── types_test.go: Data structure unit tests -└── main.go: CLI application entry point -``` - -## Contributing - -To contribute to the project, please read the instructions and contributing guidelines in [CONTRIBUTING.md](CONTRIBUTING.md). - -## License - -This project is licensed under the terms of the MIT open source license. Please refer to [MIT](./LICENSE) for the full terms. - Mona Sans is licensed under the [SIL Open Font License v1.1](https://scripts.sil.org/OFL). Find more details at [github/mona-sans](https://github.com/github/mona-sans). [golang/freetype](https://github.com/golang/freetype) is used as a dependency. Portions of this software are copyright © 2024 The FreeType Project ([www.freetype.org](https://www.freetype.org)). All rights reserved. From 4b103b1f121ff79adda47bfbbb1e8e2ed743a17d Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Sat, 7 Dec 2024 06:15:34 +0000 Subject: [PATCH 15/94] Linting fixes --- main.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 7d0be61..3301edc 100644 --- a/main.go +++ b/main.go @@ -1,3 +1,5 @@ +// Package main provides the entry point for the GitHub Skyline Generator. +// It generates a 3D model of GitHub contributions in STL format. package main import ( @@ -50,7 +52,7 @@ ASCII Preview Legend: Layout: Each column represents one week. Days within each week are reordered vertically to create a "building" effect, with empty spaces (no contributions) at the top.`, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { log := logger.GetLogger() if debug { log.SetLevel(logger.DEBUG) From f42622e0cc6670a8ce3ef5b077d1d0a1b36ce7a2 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Sat, 7 Dec 2024 06:20:11 +0000 Subject: [PATCH 16/94] Remove unused argument in command execution function --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 3301edc..572c39b 100644 --- a/main.go +++ b/main.go @@ -52,7 +52,7 @@ ASCII Preview Legend: Layout: Each column represents one week. Days within each week are reordered vertically to create a "building" effect, with empty spaces (no contributions) at the top.`, - RunE: func(_ *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, _ []string) error { log := logger.GetLogger() if debug { log.SetLevel(logger.DEBUG) From 61a5bb272fe6433bcc8b9334e351b1c0b46f217b Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Sun, 8 Dec 2024 21:22:38 +0000 Subject: [PATCH 17/94] Implement GitHub client interface and browser integration; add mock implementations for testing --- main.go | 35 +++++++++++++++----- main_test.go | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 115 insertions(+), 10 deletions(-) diff --git a/main.go b/main.go index 572c39b..938ec91 100644 --- a/main.go +++ b/main.go @@ -20,6 +20,18 @@ import ( "github.com/spf13/cobra" ) +// Browser interface matches browser.Browser functionality +type Browser interface { + Browse(url string) error +} + +// GitHub ClientInterface defines the methods for interacting with GitHub API +type GitHubClientInterface interface { + GetAuthenticatedUser() (string, error) + GetUserJoinYear(username string) (int, error) + FetchContributions(username string, year int) (*types.ContributionsResponse, error) +} + // Constants for GitHub launch year and default output file format const ( githubLaunchYear = 2008 @@ -61,8 +73,18 @@ to create a "building" effect, with empty spaces (no contributions) at the top.` } } + client, err := initializeGitHubClient() + if err != nil { + return errors.New(errors.NetworkError, "failed to initialize GitHub client", err) + } + if web { - return openGitHubProfile(user) + b := browser.New("", os.Stdout, os.Stderr) + if err := openGitHubProfile(user, client, b); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + return nil } startYear, endYear, err := parseYearRange(yearRange) @@ -242,6 +264,9 @@ func parseYearRange(yearRange string) (startYear, endYear int, err error) { return startYear, endYear, validateYearRange(startYear, endYear) } +// validateYearRange checks if the years are within the range +// of GitHub's launch year to the current year and if +// the start year is not greater than the end year. func validateYearRange(startYear, endYear int) error { currentYear := time.Now().Year() if startYear < githubLaunchYear || endYear > currentYear { @@ -254,13 +279,8 @@ func validateYearRange(startYear, endYear int) error { } // openGitHubProfile opens the GitHub profile page for the specified user or authenticated user -func openGitHubProfile(targetUser string) error { +func openGitHubProfile(targetUser string, client GitHubClientInterface, b Browser) error { if targetUser == "" { - client, err := initializeGitHubClient() - if err != nil { - return errors.New(errors.NetworkError, "failed to initialize GitHub client", err) - } - username, err := client.GetAuthenticatedUser() if err != nil { return errors.New(errors.NetworkError, "failed to get authenticated user", err) @@ -269,6 +289,5 @@ func openGitHubProfile(targetUser string) error { } profileURL := fmt.Sprintf("https://github.com/%s", targetUser) - b := browser.New("", os.Stdout, os.Stderr) return b.Browse(profileURL) } diff --git a/main_test.go b/main_test.go index 41816e1..ec9d22d 100644 --- a/main_test.go +++ b/main_test.go @@ -15,8 +15,9 @@ import ( // MockGitHubClient implements the github.APIClient interface type MockGitHubClient struct { - username string - joinYear int + username string + joinYear int + shouldError bool // Add error flag } // Get implements the APIClient interface @@ -79,14 +80,28 @@ func contributionResponse(username string) []byte { return []byte(response) } +// GetAuthenticatedUser returns the authenticated user's username or an error +// if the mock client is set to error or the username is not set. func (m *MockGitHubClient) GetAuthenticatedUser() (string, error) { + // Return error if shouldError is true + if m.shouldError { + return "", fmt.Errorf("mock client error") + } + // Validate username is not empty + if m.username == "" { + return "", fmt.Errorf("mock username not set") + } return m.username, nil } +// GetUserJoinYear implements the GitHubClientInterface. +// It returns the year the user joined GitHub. func (m *MockGitHubClient) GetUserJoinYear(_ string) (int, error) { return m.joinYear, nil } +// FetchContributions mocks fetching GitHub contributions for a user +// in a given year, returning minimal valid data. func (m *MockGitHubClient) FetchContributions(username string, year int) (*types.ContributionsResponse, error) { // Return minimal valid response resp := &types.ContributionsResponse{} @@ -108,6 +123,22 @@ func (m *MockGitHubClient) FetchContributions(username string, year int) (*types return resp, nil } +// MockBrowser implements the Browser interface +type MockBrowser struct { + LastURL string + ShouldError bool +} + +// Browse implements the Browser interface +// Changed from pointer receiver to value receiver +func (m *MockBrowser) Browse(url string) error { + m.LastURL = url + if m.ShouldError { + return fmt.Errorf("mock browser error") + } + return nil +} + func TestFormatYearRange(t *testing.T) { tests := []struct { name string @@ -337,3 +368,58 @@ func TestGenerateSkyline(t *testing.T) { }) } } + +// TestOpenGitHubProfile tests the openGitHubProfile function +func TestOpenGitHubProfile(t *testing.T) { + tests := []struct { + name string + targetUser string + mockClient *MockGitHubClient + wantURL string + wantErr bool + }{ + { + name: "specific user", + targetUser: "testuser", + mockClient: &MockGitHubClient{}, + wantURL: "https://github.com/testuser", + wantErr: false, + }, + { + name: "authenticated user", + targetUser: "", + mockClient: &MockGitHubClient{ + username: "authuser", + shouldError: false, + }, + wantURL: "https://github.com/authuser", + wantErr: false, + }, + { + name: "client error", + targetUser: "", + mockClient: &MockGitHubClient{ + username: "", + shouldError: true, + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create MockBrowser and call openGitHubProfile + mockBrowser := &MockBrowser{ShouldError: tt.wantErr} + err := openGitHubProfile(tt.targetUser, tt.mockClient, mockBrowser) + + if (err != nil) != tt.wantErr { + t.Errorf("openGitHubProfile() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if !tt.wantErr && mockBrowser.LastURL != tt.wantURL { + t.Errorf("openGitHubProfile() URL = %v, want %v", mockBrowser.LastURL, tt.wantURL) + } + }) + } +} From a7beb72c719787d71d0b2f49c8136ca54c001f64 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Sun, 8 Dec 2024 21:26:14 +0000 Subject: [PATCH 18/94] Update comment and documentation to include ASCII preview details --- main.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 938ec91..e815810 100644 --- a/main.go +++ b/main.go @@ -25,7 +25,7 @@ type Browser interface { Browse(url string) error } -// GitHub ClientInterface defines the methods for interacting with GitHub API +// GitHubClientInterface defines the methods for interacting with GitHub API type GitHubClientInterface interface { GetAuthenticatedUser() (string, error) GetUserJoinYear(username string) (int, error) @@ -53,6 +53,8 @@ var ( Long: `GitHub Skyline creates 3D printable STL files from GitHub contribution data. It can generate models for specific years or year ranges for the authenticated user or an optional specified user. +While the STL file is being generated, an ASCII preview will be displayed in the terminal. + ASCII Preview Legend: ' ' Empty/Sky - No contributions '.' Future dates - What contributions could you make? From 10b8deb57d5e909753519cbf4cddf2f94ca2e415 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 8 Dec 2024 21:42:00 +0000 Subject: [PATCH 19/94] Bump golang.org/x/sys from 0.27.0 to 0.28.0 Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.27.0 to 0.28.0. - [Commits](https://github.com/golang/sys/compare/v0.27.0...v0.28.0) --- updated-dependencies: - dependency-name: golang.org/x/sys dependency-type: indirect update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 191a873..68bd067 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ toolchain go1.23.3 require ( github.com/cli/go-gh/v2 v2.11.1 github.com/fogleman/gg v1.3.0 + github.com/spf13/cobra v1.8.1 ) require ( @@ -24,11 +25,10 @@ require ( github.com/mattn/go-runewidth v0.0.16 // indirect github.com/muesli/termenv v0.15.2 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/thlib/go-timezone-local v0.0.3 // indirect golang.org/x/image v0.22.0 // indirect - golang.org/x/sys v0.27.0 // indirect + golang.org/x/sys v0.28.0 // indirect golang.org/x/term v0.26.0 // indirect golang.org/x/text v0.20.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 978888d..2b18620 100644 --- a/go.sum +++ b/go.sum @@ -54,8 +54,8 @@ golang.org/x/image v0.22.0 h1:UtK5yLUzilVrkjMAZAZ34DXGpASN8i8pj8g+O+yd10g= golang.org/x/image v0.22.0/go.mod h1:9hPFhljd4zZ1GNSIZJ49sqbp45GKK9t6w+iXvGqZUz4= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= From 022fbf80d28af6f8e2bbbe39130711744b863f87 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Sun, 8 Dec 2024 21:53:11 +0000 Subject: [PATCH 20/94] Add dependency groups for Go and GitHub Actions in Dependabot configuration --- .github/dependabot.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index bdd8483..2d76a1b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -12,7 +12,15 @@ updates: allow: - dependency-type: "direct" - dependency-type: "indirect" + groups: + go-dependencies: + patterns: + - "*" - package-ecosystem: "github-actions" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "weekly" + groups: + github-actions: + patterns: + - "*" From fa7dc757ed11b1f3180e126f3cfbb23613d3a34a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 8 Dec 2024 21:57:02 +0000 Subject: [PATCH 21/94] Bump golang.org/x/term from 0.26.0 to 0.27.0 Bumps [golang.org/x/term](https://github.com/golang/term) from 0.26.0 to 0.27.0. - [Commits](https://github.com/golang/term/compare/v0.26.0...v0.27.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-type: indirect update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 68bd067..dafa3a8 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/thlib/go-timezone-local v0.0.3 // indirect golang.org/x/image v0.22.0 // indirect golang.org/x/sys v0.28.0 // indirect - golang.org/x/term v0.26.0 // indirect + golang.org/x/term v0.27.0 // indirect golang.org/x/text v0.20.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 2b18620..0ed4b7c 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,8 @@ golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= -golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= From 262e06a8bc0f2b5a3d5f486747b3241011cae611 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 8 Dec 2024 22:00:48 +0000 Subject: [PATCH 22/94] Bump golang.org/x/text from 0.20.0 to 0.21.0 Bumps [golang.org/x/text](https://github.com/golang/text) from 0.20.0 to 0.21.0. - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.20.0...v0.21.0) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-type: indirect update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index dafa3a8..09462e1 100644 --- a/go.mod +++ b/go.mod @@ -30,6 +30,6 @@ require ( golang.org/x/image v0.22.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/term v0.27.0 // indirect - golang.org/x/text v0.20.0 // indirect + golang.org/x/text v0.21.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 0ed4b7c..97b7f6c 100644 --- a/go.sum +++ b/go.sum @@ -58,8 +58,8 @@ golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 5156de027589b175ac69dc222ef25674fd83b126 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 8 Dec 2024 22:00:53 +0000 Subject: [PATCH 23/94] Bump golang.org/x/image from 0.22.0 to 0.23.0 Bumps [golang.org/x/image](https://github.com/golang/image) from 0.22.0 to 0.23.0. - [Commits](https://github.com/golang/image/compare/v0.22.0...v0.23.0) --- updated-dependencies: - dependency-name: golang.org/x/image dependency-type: indirect update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index dafa3a8..50dfda7 100644 --- a/go.mod +++ b/go.mod @@ -27,9 +27,9 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/thlib/go-timezone-local v0.0.3 // indirect - golang.org/x/image v0.22.0 // indirect + golang.org/x/image v0.23.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/term v0.27.0 // indirect - golang.org/x/text v0.20.0 // indirect + golang.org/x/text v0.21.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 0ed4b7c..35d856f 100644 --- a/go.sum +++ b/go.sum @@ -50,16 +50,16 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/thlib/go-timezone-local v0.0.3 h1:ie5XtZWG5lQ4+1MtC5KZ/FeWlOKzW2nPoUnXYUbV/1s= github.com/thlib/go-timezone-local v0.0.3/go.mod h1:/Tnicc6m/lsJE0irFMA0LfIwTBo4QP7A8IfyIv4zZKI= -golang.org/x/image v0.22.0 h1:UtK5yLUzilVrkjMAZAZ34DXGpASN8i8pj8g+O+yd10g= -golang.org/x/image v0.22.0/go.mod h1:9hPFhljd4zZ1GNSIZJ49sqbp45GKK9t6w+iXvGqZUz4= +golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= +golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 4d45474a01c741a13410a88d790df3be1ae7e980 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Mon, 9 Dec 2024 08:07:16 +0000 Subject: [PATCH 24/94] Update GolangCI configuration to enable additional linters and adjust settings --- .github/linters/.golangci.yml | 17 ++++++++++++++++- .golangci.yml | 1 + 2 files changed, 17 insertions(+), 1 deletion(-) create mode 120000 .golangci.yml diff --git a/.github/linters/.golangci.yml b/.github/linters/.golangci.yml index 00aaeb7..b503820 100644 --- a/.github/linters/.golangci.yml +++ b/.github/linters/.golangci.yml @@ -8,11 +8,26 @@ run: # Allow multiple packages allow-separate-packages: true + tests: true + + +linters-settings: + gosec: + excludes: + - G304 # File path provided as taint input + # Configure specific linters linters: enable: + - bodyclose + - copyloopvar + - durationcheck + - gocritic - gofmt + - gosec - govet + - ineffassign + - nilerr - revive - staticcheck @@ -25,4 +40,4 @@ issues: # If needed, explicitly specify which directories to analyze exclude-dirs: - - vendor + - vendor \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml new file mode 120000 index 0000000..7292dd3 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1 @@ +.github/linters/.golangci.yml \ No newline at end of file From 2f78a93c0094ae8612ff04dd798e3c0bad857b58 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Mon, 9 Dec 2024 08:12:07 +0000 Subject: [PATCH 25/94] Configure development environment for Go: update devcontainer settings and add VSCode configuration --- .devcontainer/devcontainer.json | 4 ++-- .vscode/settings.json | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index f49fea5..e9aa8cb 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -29,13 +29,13 @@ "features": { "ghcr.io/devcontainers/features/github-cli:1": {} - } + }, // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "go version", + "postCreateCommand": "go install -v golang.org/x/tools/cmd/goimports@latest", // Configure tool-specific properties. // "customizations": {}, diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..dff05c1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,21 @@ +{ + // Use golangci-lint for linting + "go.lintTool": "golangci-lint", + + // Configure linter flags + "go.lintFlags": [ + "--fast" + ], + + "go.formatTool": "goimports", + "go.useLanguageServer": true, + "go.testOnSave": true, + + // Editor settings optimized for Go development + "[go]": { + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports":"always" + } + } +} \ No newline at end of file From cb41a55ee3f99ab837efe01c633eed70243050c2 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Mon, 9 Dec 2024 08:12:43 +0000 Subject: [PATCH 26/94] Resolve G115 by validating triangle count range in WriteSTLBinary function --- stl/stl.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stl/stl.go b/stl/stl.go index df70aa7..ca40e17 100644 --- a/stl/stl.go +++ b/stl/stl.go @@ -141,8 +141,8 @@ func WriteSTLBinary(filename string, triangles []types.Triangle) error { } triangleCount := len(triangles) - if triangleCount < 0 { - return errors.New(errors.ValidationError, "invalid number of triangles for STL format", nil) + if triangleCount < 0 || triangleCount > math.MaxUint32 { + return errors.New(errors.ValidationError, "triangle count exceeds valid range for STL format", nil) } if err := writeTriangleCount(writer, uint32(triangleCount)); err != nil { return err From c8e444a03a558e9d853758addae366587a410892 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Mon, 9 Dec 2024 08:18:12 +0000 Subject: [PATCH 27/94] Fix formatting in devcontainer.json postCreateCommand --- .devcontainer/devcontainer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index e9aa8cb..6c71182 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -35,7 +35,7 @@ // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "go install -v golang.org/x/tools/cmd/goimports@latest", + "postCreateCommand": "go install -v golang.org/x/tools/cmd/goimports@latest" // Configure tool-specific properties. // "customizations": {}, From eef58024ab43676ac27b837178b05e0ed1c76e56 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Mon, 9 Dec 2024 08:21:57 +0000 Subject: [PATCH 28/94] Prettier fixes --- .github/linters/.golangci.yml | 3 +-- .vscode/settings.json | 34 ++++++++++++++++------------------ 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/.github/linters/.golangci.yml b/.github/linters/.golangci.yml index b503820..ca09278 100644 --- a/.github/linters/.golangci.yml +++ b/.github/linters/.golangci.yml @@ -10,7 +10,6 @@ run: tests: true - linters-settings: gosec: excludes: @@ -40,4 +39,4 @@ issues: # If needed, explicitly specify which directories to analyze exclude-dirs: - - vendor \ No newline at end of file + - vendor diff --git a/.vscode/settings.json b/.vscode/settings.json index dff05c1..ee878d0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,21 +1,19 @@ { - // Use golangci-lint for linting - "go.lintTool": "golangci-lint", + // Use golangci-lint for linting + "go.lintTool": "golangci-lint", - // Configure linter flags - "go.lintFlags": [ - "--fast" - ], - - "go.formatTool": "goimports", - "go.useLanguageServer": true, - "go.testOnSave": true, - - // Editor settings optimized for Go development - "[go]": { - "editor.formatOnSave": true, - "editor.codeActionsOnSave": { - "source.organizeImports":"always" - } + // Configure linter flags + "go.lintFlags": ["--fast"], + + "go.formatTool": "goimports", + "go.useLanguageServer": true, + "go.testOnSave": true, + + // Editor settings optimized for Go development + "[go]": { + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": "always" } -} \ No newline at end of file + } +} From 03098ce1e1c64c1ac15a5964012124e7a48b98d8 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Mon, 9 Dec 2024 08:25:56 +0000 Subject: [PATCH 29/94] Refactor GitHub Actions workflow to post coverage summary to the workflow instead of a PR comment --- .github/workflows/build.yml | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ff406a8..7dfe71f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,6 @@ on: permissions: contents: read - pull-requests: write jobs: build: @@ -40,17 +39,9 @@ jobs: coverage.html coverage.txt - - name: Post Coverage Comment - if: github.event_name == 'pull_request' - uses: actions/github-script@v7 - with: - script: | - const fs = require('fs'); - const coverage = fs.readFileSync('coverage.txt', 'utf8'); - const comment = `### Code Coverage Report\n\`\`\`\n${coverage}\`\`\``; - github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body: comment - }); + - name: Post Coverage Summary + run: | + echo "### Code Coverage Report" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + cat coverage.txt >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY From ba0f7f8e80b35cef57da822ef717cedf36af459a Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Mon, 9 Dec 2024 08:34:03 +0000 Subject: [PATCH 30/94] Refactor coverage summary posting based on github_actions linter result --- .github/workflows/build.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7dfe71f..7ee3dbc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,7 +41,9 @@ jobs: - name: Post Coverage Summary run: | - echo "### Code Coverage Report" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - cat coverage.txt >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY + { + echo "### Code Coverage Report" + echo '```' + cat coverage.txt + echo '```' + } >> $GITHUB_STEP_SUMMARY From 924a70f85238a1024f504a991cb571c793ef867b Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Mon, 9 Dec 2024 08:37:40 +0000 Subject: [PATCH 31/94] Fix quoting for GITHUB_STEP_SUMMARY in build workflow --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7ee3dbc..7a50f5d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -46,4 +46,4 @@ jobs: echo '```' cat coverage.txt echo '```' - } >> $GITHUB_STEP_SUMMARY + } >> "$GITHUB_STEP_SUMMARY" From cc3c90f589843351fc73c9efec4e3a8c1b9726c2 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Mon, 9 Dec 2024 08:43:37 +0000 Subject: [PATCH 32/94] Add .golangci.yml to .prettierignore due to symlink limitation --- .prettierignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .prettierignore diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..79e6dcb --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +.golangci.yml \ No newline at end of file From 148400bd29739b067f09f7bfcd8922bd8e5a9260 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Mon, 9 Dec 2024 08:49:33 +0000 Subject: [PATCH 33/94] Update .prettierignore to ignore all instances of .golangci.yml --- .prettierignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.prettierignore b/.prettierignore index 79e6dcb..3307735 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1 @@ -.golangci.yml \ No newline at end of file +**/.golangci.yml \ No newline at end of file From 0c29cf9b8d03cb8f7b048f4a908bb9d996b70207 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Mon, 9 Dec 2024 08:54:23 +0000 Subject: [PATCH 34/94] Disable YAML validation in linter workflow --- .github/workflows/linter.yml | 1 + .prettierignore | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 .prettierignore diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index cf75908..8166258 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -40,3 +40,4 @@ jobs: VALIDATE_JSON_PRETTIER: false LINTER_RULES_PATH: .github/linters GOLANGCI_LINT_CONFIG: .golangci.yml + VALIDATE_YAML_PRETTIER: false diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 3307735..0000000 --- a/.prettierignore +++ /dev/null @@ -1 +0,0 @@ -**/.golangci.yml \ No newline at end of file From 63ffc662cf1e8f57be701ec371fc9d401582a654 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Mon, 9 Dec 2024 10:06:41 +0000 Subject: [PATCH 35/94] Update CODEOWNERS --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 820093c..de31019 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -2,4 +2,4 @@ # This repository is maintained by: -* @chrisreddington @leereilly @martinwoodward +* @chrisreddington @leereilly From f3d251d4a56817f1637a1307e280010168ceae73 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Mon, 9 Dec 2024 12:22:00 +0000 Subject: [PATCH 36/94] Validate triangle count for STL format and handle negative values --- stl/stl.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/stl/stl.go b/stl/stl.go index ca40e17..cda281a 100644 --- a/stl/stl.go +++ b/stl/stl.go @@ -140,11 +140,16 @@ func WriteSTLBinary(filename string, triangles []types.Triangle) error { return err } - triangleCount := len(triangles) - if triangleCount < 0 || triangleCount > math.MaxUint32 { + triangleCountInt := len(triangles) + if triangleCountInt < 0 { + return errors.New(errors.ValidationError, "triangle count cannot be negative", nil) + } + if triangleCountInt > int(math.MaxUint32) { return errors.New(errors.ValidationError, "triangle count exceeds valid range for STL format", nil) } - if err := writeTriangleCount(writer, uint32(triangleCount)); err != nil { + + triangleCount := uint32(triangleCountInt) + if err := writeTriangleCount(writer, triangleCount); err != nil { return err } From d15e2ccdfb4f19cc9197173ef0a40317a8222b76 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Mon, 9 Dec 2024 12:40:13 +0000 Subject: [PATCH 37/94] Add maxTriangleCount constant and validate triangle count for STL files --- stl/stl.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/stl/stl.go b/stl/stl.go index cda281a..8d6c03f 100644 --- a/stl/stl.go +++ b/stl/stl.go @@ -38,6 +38,9 @@ const ( // - Attribute count: 2 bytes // Total: 50 bytes triangleSize = (12 * 4) + 2 + + // maxTriangleCount defines the maximum number of triangles allowed in an STL file. + maxTriangleCount = uint64(math.MaxUint32) ) // bufferWriter encapsulates common buffer writing operations @@ -140,16 +143,14 @@ func WriteSTLBinary(filename string, triangles []types.Triangle) error { return err } - triangleCountInt := len(triangles) - if triangleCountInt < 0 { - return errors.New(errors.ValidationError, "triangle count cannot be negative", nil) - } - if triangleCountInt > int(math.MaxUint32) { + triangleCount := uint64(len(triangles)) + if triangleCount > maxTriangleCount { return errors.New(errors.ValidationError, "triangle count exceeds valid range for STL format", nil) } - triangleCount := uint32(triangleCountInt) - if err := writeTriangleCount(writer, triangleCount); err != nil { + // Now safely convert to uint32 since we know it's in range + triangleCountUint32 := uint32(triangleCount) + if err := writeTriangleCount(writer, triangleCountUint32); err != nil { return err } From 9dd38d5062aacf18244c3e03ac5ba266eb8250bc Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Mon, 9 Dec 2024 15:46:46 +0000 Subject: [PATCH 38/94] Add Contributor Covenant Code of Conduct --- CODE_OF_CONDUCT.md | 74 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..a1f82f0 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at . All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ \ No newline at end of file From a19d74a339e8177ebf34e92940031601e247e93d Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Mon, 9 Dec 2024 15:54:27 +0000 Subject: [PATCH 39/94] Linting fixes --- CODE_OF_CONDUCT.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index a1f82f0..bb6d2a4 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -14,21 +14,21 @@ orientation. Examples of behavior that contributes to creating a positive environment include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or -advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a +- Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities @@ -47,7 +47,7 @@ threatening, offensive, or harmful. This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail +representing a project or community include using an official project email address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. @@ -71,4 +71,4 @@ This Code of Conduct is adapted from the [Contributor Covenant][homepage], versi available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ \ No newline at end of file +[version]: http://contributor-covenant.org/version/1/4/ From 746c17b66e0104d3c9527db74c33b2ba5005f69c Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Mon, 9 Dec 2024 16:06:02 +0000 Subject: [PATCH 40/94] Fix broken links --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 85f38d8..0e5f65f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,8 @@ # Contributing -[fork]: https://github.com/github/REPO/fork -[pr]: https://github.com/github/REPO/compare -[style]: https://github.com/github/REPO/blob/main/.golangci.yaml +[fork]: https://github.com/github/gh-skyline/fork +[pr]: https://github.com/github/gh-skyline/compare +[style]: https://github.com/github/gh-skyline/blob/main/.github/linters/.golangci.yml Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. From 968aef5c35948c40ad08ad624feba3540e2874e6 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Mon, 9 Dec 2024 16:38:18 +0000 Subject: [PATCH 41/94] Add a section on visualizing STL files --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 6416604..518bc6d 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,10 @@ The extension generates ASCII art in terminal while loading, a unique and fun wa - `'▓'` High level: Heavy contribution activity - `'╻┃╽'` Top level: Last block with contributions in the week (Low, Medium, High) +## Visualizing your Skyline + +Once you have generated your STL file, you can visualize it using 3D modeling or 3D printing software. But did you know that you can upload your STL file to a GitHub repository and view your Skyline there? For example, take a look at [@chrisreddington's GitHub Skyline from 2011 - 2024](https://github.com/chrisreddington/chrisreddington/blob/master/chrisreddington-11-24-github-skyline.stl). + ## Project Structure ```text From bf9f13979674427232a36b74c0c19461872bcb61 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 19:01:05 +0000 Subject: [PATCH 42/94] Bump github.com/thlib/go-timezone-local in the go-dependencies group Bumps the go-dependencies group with 1 update: [github.com/thlib/go-timezone-local](https://github.com/thlib/go-timezone-local). Updates `github.com/thlib/go-timezone-local` from 0.0.3 to 0.0.4 - [Release notes](https://github.com/thlib/go-timezone-local/releases) - [Commits](https://github.com/thlib/go-timezone-local/compare/v0.0.3...v0.0.4) --- updated-dependencies: - dependency-name: github.com/thlib/go-timezone-local dependency-type: indirect update-type: version-update:semver-patch dependency-group: go-dependencies ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 50dfda7..8a2aa58 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/muesli/termenv v0.15.2 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/thlib/go-timezone-local v0.0.3 // indirect + github.com/thlib/go-timezone-local v0.0.4 // indirect golang.org/x/image v0.23.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/term v0.27.0 // indirect diff --git a/go.sum b/go.sum index 35d856f..63ad29a 100644 --- a/go.sum +++ b/go.sum @@ -48,8 +48,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/thlib/go-timezone-local v0.0.3 h1:ie5XtZWG5lQ4+1MtC5KZ/FeWlOKzW2nPoUnXYUbV/1s= -github.com/thlib/go-timezone-local v0.0.3/go.mod h1:/Tnicc6m/lsJE0irFMA0LfIwTBo4QP7A8IfyIv4zZKI= +github.com/thlib/go-timezone-local v0.0.4 h1:9oqkZLirWUtrFmhF/7WxR8Y0TBAGaThe95w6K3pLKBk= +github.com/thlib/go-timezone-local v0.0.4/go.mod h1:/Tnicc6m/lsJE0irFMA0LfIwTBo4QP7A8IfyIv4zZKI= golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From 44fee7dc8cf4822c01ad2f4880b5461d39196cc5 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Wed, 11 Dec 2024 14:12:26 +0000 Subject: [PATCH 43/94] Refactor GitHub client to consitently use GraphQL API. Add mock data utilities to consolidate duplication in tests --- github/client.go | 97 +++++++-------- github/client_test.go | 228 +++++++++++++++++------------------- main.go | 8 +- main_test.go | 172 +++++---------------------- testutil/fixtures/github.go | 43 +++++++ testutil/mocks/github.go | 79 +++++++++++++ types/types.go | 30 ++--- types/types_test.go | 40 +++---- 8 files changed, 335 insertions(+), 362 deletions(-) create mode 100644 testutil/fixtures/github.go create mode 100644 testutil/mocks/github.go diff --git a/github/client.go b/github/client.go index ed11802..54e641b 100644 --- a/github/client.go +++ b/github/client.go @@ -3,10 +3,7 @@ package github import ( - "bytes" - "encoding/json" "fmt" - "io" "time" "github.com/github/gh-skyline/errors" @@ -15,8 +12,7 @@ import ( // APIClient interface defines the methods we need from the client type APIClient interface { - Get(path string, response interface{}) error - Post(path string, body io.Reader, response interface{}) error + Do(query string, variables map[string]interface{}, response interface{}) error } // Client holds the API client @@ -31,17 +27,31 @@ func NewClient(apiClient APIClient) *Client { // GetAuthenticatedUser fetches the authenticated user's login name from GitHub. func (c *Client) GetAuthenticatedUser() (string, error) { - response := struct{ Login string }{} - err := c.api.Get("user", &response) + // GraphQL query to fetch the authenticated user's login. + query := ` + query { + viewer { + login + } + }` + + var response struct { + Viewer struct { + Login string `json:"login"` + } `json:"viewer"` + } + + // Execute the GraphQL query. + err := c.api.Do(query, nil, &response) if err != nil { return "", errors.New(errors.NetworkError, "failed to fetch authenticated user", err) } - if response.Login == "" { + if response.Viewer.Login == "" { return "", errors.New(errors.ValidationError, "received empty username from GitHub API", nil) } - return response.Login, nil + return response.Viewer.Login, nil } // FetchContributions retrieves the contribution data for a given username and year from GitHub. @@ -54,9 +64,10 @@ func (c *Client) FetchContributions(username string, year int) (*types.Contribut return nil, errors.New(errors.ValidationError, "year cannot be before GitHub's launch (2008)", nil) } - startDate := fmt.Sprintf("%d-01-01", year) - endDate := fmt.Sprintf("%d-12-31", year) + startDate := fmt.Sprintf("%d-01-01T00:00:00Z", year) + endDate := fmt.Sprintf("%d-12-31T23:59:59Z", year) + // GraphQL query to fetch the user's contributions within the specified date range. query := ` query ContributionGraph($username: String!, $from: DateTime!, $to: DateTime!) { user(login: $username) { @@ -77,34 +88,23 @@ func (c *Client) FetchContributions(username string, year int) (*types.Contribut variables := map[string]interface{}{ "username": username, - "from": startDate + "T00:00:00Z", - "to": endDate + "T23:59:59Z", + "from": startDate, + "to": endDate, } - payload := struct { - Query string `json:"query"` - Variables map[string]interface{} `json:"variables"` - }{ - Query: query, - Variables: variables, - } + var response types.ContributionsResponse - body, err := json.Marshal(payload) + // Execute the GraphQL query. + err := c.api.Do(query, variables, &response) if err != nil { - return nil, err - } - - var resp types.ContributionsResponse - if err := c.api.Post("graphql", bytes.NewBuffer(body), &resp); err != nil { - return nil, errors.New(errors.GraphQLError, "failed to fetch contributions", err) + return nil, errors.New(errors.NetworkError, "failed to fetch contributions", err) } - // Validate response - if resp.Data.User.Login == "" { - return nil, errors.New(errors.GraphQLError, "user not found", nil) + if response.User.Login == "" { + return nil, errors.New(errors.ValidationError, "received empty username from GitHub API", nil) } - return &resp, nil + return &response, nil } // GetUserJoinYear fetches the year a user joined GitHub using the GitHub API. @@ -113,6 +113,7 @@ func (c *Client) GetUserJoinYear(username string) (int, error) { return 0, errors.New(errors.ValidationError, "username cannot be empty", nil) } + // GraphQL query to fetch the user's account creation date. query := ` query UserJoinDate($username: String!) { user(login: $username) { @@ -124,35 +125,23 @@ func (c *Client) GetUserJoinYear(username string) (int, error) { "username": username, } - payload := struct { - Query string `json:"query"` - Variables map[string]interface{} `json:"variables"` - }{ - Query: query, - Variables: variables, + var response struct { + User struct { + CreatedAt time.Time `json:"createdAt"` + } `json:"user"` } - body, err := json.Marshal(payload) + // Execute the GraphQL query. + err := c.api.Do(query, variables, &response) if err != nil { - return 0, err - } - - var resp struct { - Data struct { - User struct { - CreatedAt string `json:"createdAt"` - } `json:"user"` - } `json:"data"` - } - if err := c.api.Post("graphql", bytes.NewBuffer(body), &resp); err != nil { - return 0, errors.New(errors.GraphQLError, "failed to fetch user join date", err) + return 0, errors.New(errors.NetworkError, "failed to fetch user's join date", err) } // Parse the join date - joinDate, err := time.Parse(time.RFC3339, resp.Data.User.CreatedAt) - if err != nil { - return 0, errors.New(errors.ValidationError, "failed to parse join date", err) + joinYear := response.User.CreatedAt.Year() + if joinYear == 0 { + return 0, errors.New(errors.ValidationError, "invalid join date received from GitHub API", nil) } - return joinDate.Year(), nil + return joinYear, nil } diff --git a/github/client_test.go b/github/client_test.go index 5587783..1d822ad 100644 --- a/github/client_test.go +++ b/github/client_test.go @@ -1,98 +1,50 @@ package github import ( - "encoding/json" - "io" "testing" + "time" "github.com/github/gh-skyline/errors" + "github.com/github/gh-skyline/testutil/mocks" + "github.com/github/gh-skyline/types" ) -type MockAPIClient struct { - GetFunc func(path string, response interface{}) error - PostFunc func(path string, body io.Reader, response interface{}) error -} - -func (m *MockAPIClient) Get(path string, response interface{}) error { - return m.GetFunc(path, response) -} - -func (m *MockAPIClient) Post(path string, body io.Reader, response interface{}) error { - return m.PostFunc(path, body, response) -} - -// mockAPIClient implements APIClient for testing -type mockAPIClient struct { - getResponse string - postResponse string - shouldError bool -} - -func (m *mockAPIClient) Get(_ string, response interface{}) error { - if m.shouldError { - return errors.New(errors.NetworkError, "mock error", nil) - } - return json.Unmarshal([]byte(m.getResponse), response) -} - -func (m *mockAPIClient) Post(_ string, _ io.Reader, response interface{}) error { - if m.shouldError { - return errors.New(errors.NetworkError, "mock error", nil) - } - return json.Unmarshal([]byte(m.postResponse), response) -} - -func TestNewClient(t *testing.T) { - mock := &mockAPIClient{} - client := NewClient(mock) - if client == nil { - t.Fatal("NewClient returned nil") - } - if client.api != mock { - t.Error("NewClient did not set api client correctly") - } -} - func TestGetAuthenticatedUser(t *testing.T) { tests := []struct { name string - response string - shouldError bool + mockResponse string + mockError error expectedUser string expectedError bool }{ { name: "successful response", - response: `{"login": "testuser"}`, + mockResponse: "testuser", expectedUser: "testuser", expectedError: false, }, { name: "empty username", - response: `{"login": ""}`, + mockResponse: "", expectedError: true, }, { name: "network error", - shouldError: true, + mockError: errors.New(errors.NetworkError, "network error", nil), expectedError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - mock := &mockAPIClient{ - getResponse: tt.response, - shouldError: tt.shouldError, - } - client := NewClient(mock) + client := NewClient(&mocks.MockGitHubClient{ + Username: tt.mockResponse, + Err: tt.mockError, + }) user, err := client.GetAuthenticatedUser() - if tt.expectedError && err == nil { - t.Error("expected error but got none") - } - if !tt.expectedError && err != nil { - t.Errorf("unexpected error: %v", err) + if (err != nil) != tt.expectedError { + t.Errorf("expected error: %v, got: %v", tt.expectedError, err) } if user != tt.expectedUser { t.Errorf("expected user %q, got %q", tt.expectedUser, user) @@ -101,123 +53,153 @@ func TestGetAuthenticatedUser(t *testing.T) { } } -func TestFetchContributions(t *testing.T) { +func TestGetUserJoinYear(t *testing.T) { tests := []struct { name string username string - year int - response string - shouldError bool + mockResponse time.Time + mockError error + expectedYear int expectedError bool }{ { - name: "successful response", - username: "testuser", - year: 2023, - response: `{"data":{"user":{"login":"testuser","contributionsCollection":{"contributionCalendar":{"totalContributions":100,"weeks":[]}}}}}`, + name: "successful response", + username: "testuser", + mockResponse: time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC), + expectedYear: 2015, + expectedError: false, }, { name: "empty username", username: "", - year: 2023, - expectedError: true, - }, - { - name: "invalid year", - username: "testuser", - year: 2007, expectedError: true, }, { name: "network error", username: "testuser", - year: 2023, - shouldError: true, - expectedError: true, - }, - { - name: "user not found", - username: "testuser", - year: 2023, - response: `{"data":{"user":{"login":""}}}`, + mockError: errors.New(errors.NetworkError, "network error", nil), expectedError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - mock := &mockAPIClient{ - postResponse: tt.response, - shouldError: tt.shouldError, - } - client := NewClient(mock) - - resp, err := client.FetchContributions(tt.username, tt.year) - if tt.expectedError && err == nil { - t.Error("expected error but got none") + client := NewClient(&mocks.MockGitHubClient{ + JoinYear: tt.expectedYear, + Err: tt.mockError, + }) + + year, err := client.GetUserJoinYear(tt.username) + if (err != nil) != tt.expectedError { + t.Errorf("expected error: %v, got: %v", tt.expectedError, err) } - if !tt.expectedError && err != nil { - t.Errorf("unexpected error: %v", err) - } - if !tt.expectedError && resp == nil { - t.Error("expected response but got nil") + if !tt.expectedError && year != tt.expectedYear { + t.Errorf("expected year %d, got %d", tt.expectedYear, year) } }) } } -func TestGetUserJoinYear(t *testing.T) { +func TestFetchContributions(t *testing.T) { + mockContributions := &types.ContributionsResponse{ + User: struct { + Login string `json:"login"` + ContributionsCollection struct { + ContributionCalendar struct { + TotalContributions int `json:"totalContributions"` + Weeks []struct { + ContributionDays []types.ContributionDay `json:"contributionDays"` + } `json:"weeks"` + } `json:"contributionCalendar"` + } `json:"contributionsCollection"` + }{ + Login: "chrisreddington", + ContributionsCollection: struct { + ContributionCalendar struct { + TotalContributions int `json:"totalContributions"` + Weeks []struct { + ContributionDays []types.ContributionDay `json:"contributionDays"` + } `json:"weeks"` + } `json:"contributionCalendar"` + }{ + ContributionCalendar: struct { + TotalContributions int `json:"totalContributions"` + Weeks []struct { + ContributionDays []types.ContributionDay `json:"contributionDays"` + } `json:"weeks"` + }{ + TotalContributions: 100, + Weeks: []struct { + ContributionDays []types.ContributionDay `json:"contributionDays"` + }{ + { + ContributionDays: []types.ContributionDay{ + { + ContributionCount: 5, + Date: "2023-01-01", + }, + }, + }, + }, + }, + }, + }, + } + tests := []struct { name string username string - response string - shouldError bool - expectedYear int + year int + mockResponse *types.ContributionsResponse + mockError error expectedError bool }{ { name: "successful response", username: "testuser", - response: `{"data":{"user":{"createdAt":"2015-01-01T00:00:00Z"}}}`, - expectedYear: 2015, + year: 2023, + mockResponse: mockContributions, expectedError: false, }, { name: "empty username", username: "", + year: 2023, expectedError: true, }, { - name: "network error", + name: "invalid year", username: "testuser", - shouldError: true, + year: 2007, expectedError: true, }, { - name: "invalid date format", + name: "network error", username: "testuser", - response: `{"data":{"user":{"createdAt":"invalid-date"}}}`, + year: 2023, + mockError: errors.New(errors.NetworkError, "network error", nil), expectedError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - mock := &mockAPIClient{ - postResponse: tt.response, - shouldError: tt.shouldError, - } - client := NewClient(mock) + client := NewClient(&mocks.MockGitHubClient{ + Username: tt.username, + MockData: tt.mockResponse, + Err: tt.mockError, + }) - joinYear, err := client.GetUserJoinYear(tt.username) - if tt.expectedError && err == nil { - t.Error("expected error but got none") - } - if !tt.expectedError && err != nil { - t.Errorf("unexpected error: %v", err) + resp, err := client.FetchContributions(tt.username, tt.year) + if (err != nil) != tt.expectedError { + t.Errorf("expected error: %v, got: %v", tt.expectedError, err) } - if joinYear != tt.expectedYear { - t.Errorf("expected year %d, got %d", tt.expectedYear, joinYear) + if !tt.expectedError { + if resp == nil { + t.Error("expected response but got nil") + } else if resp.User.Login != "testuser" { + t.Errorf("expected user testuser, got %s", resp.User.Login) + } } }) } diff --git a/main.go b/main.go index e815810..253bfdf 100644 --- a/main.go +++ b/main.go @@ -217,22 +217,22 @@ var initializeGitHubClient = defaultGitHubClient // defaultGitHubClient is the default implementation of client initialization func defaultGitHubClient() (*github.Client, error) { - apiClient, err := api.DefaultRESTClient() + apiClient, err := api.DefaultGraphQLClient() if err != nil { - return nil, fmt.Errorf("failed to create REST client: %w", err) + return nil, fmt.Errorf("failed to create GraphQL client: %w", err) } return github.NewClient(apiClient), nil } // fetchContributionData retrieves and formats the contribution data for the specified year. func fetchContributionData(client *github.Client, username string, year int) ([][]types.ContributionDay, error) { - resp, err := client.FetchContributions(username, year) + response, err := client.FetchContributions(username, year) if err != nil { return nil, fmt.Errorf("failed to fetch contributions: %w", err) } // Convert weeks data to 2D array for STL generation - weeks := resp.Data.User.ContributionsCollection.ContributionCalendar.Weeks + weeks := response.User.ContributionsCollection.ContributionCalendar.Weeks contributionGrid := make([][]types.ContributionDay, len(weeks)) for i, week := range weeks { contributionGrid[i] = week.ContributionDays diff --git a/main_test.go b/main_test.go index ec9d22d..be9ae03 100644 --- a/main_test.go +++ b/main_test.go @@ -1,142 +1,25 @@ package main import ( - "io" "testing" - "time" - "encoding/json" "fmt" - "strings" "github.com/github/gh-skyline/github" - "github.com/github/gh-skyline/types" + "github.com/github/gh-skyline/testutil/fixtures" + "github.com/github/gh-skyline/testutil/mocks" ) -// MockGitHubClient implements the github.APIClient interface -type MockGitHubClient struct { - username string - joinYear int - shouldError bool // Add error flag -} - -// Get implements the APIClient interface -func (m *MockGitHubClient) Get(_ string, _ interface{}) error { - return nil -} - -// Post implements the APIClient interface -func (m *MockGitHubClient) Post(path string, body io.Reader, response interface{}) error { - if path == "graphql" { - // Read the request body to determine which GraphQL query is being made - bodyBytes, _ := io.ReadAll(body) - bodyStr := string(bodyBytes) - - if strings.Contains(bodyStr, "UserJoinDate") { - // Handle user join date query - resp := response.(*struct { - Data struct { - User struct { - CreatedAt string `json:"createdAt"` - } `json:"user"` - } `json:"data"` - }) - resp.Data.User.CreatedAt = time.Date(m.joinYear, 1, 1, 0, 0, 0, 0, time.UTC).Format(time.RFC3339) - return nil - } - - if strings.Contains(bodyStr, "ContributionGraph") { - // Handle contribution graph query (existing logic) - return json.Unmarshal(contributionResponse(m.username), response) - } - } - return nil -} - -// Helper function to generate mock contribution response -func contributionResponse(username string) []byte { - response := fmt.Sprintf(`{ - "data": { - "user": { - "login": "%s", - "contributionsCollection": { - "contributionCalendar": { - "totalContributions": 1, - "weeks": [ - { - "contributionDays": [ - { - "contributionCount": 1, - "date": "2024-01-01" - } - ] - } - ] - } - } - } - } - }`, username) - return []byte(response) -} - -// GetAuthenticatedUser returns the authenticated user's username or an error -// if the mock client is set to error or the username is not set. -func (m *MockGitHubClient) GetAuthenticatedUser() (string, error) { - // Return error if shouldError is true - if m.shouldError { - return "", fmt.Errorf("mock client error") - } - // Validate username is not empty - if m.username == "" { - return "", fmt.Errorf("mock username not set") - } - return m.username, nil -} - -// GetUserJoinYear implements the GitHubClientInterface. -// It returns the year the user joined GitHub. -func (m *MockGitHubClient) GetUserJoinYear(_ string) (int, error) { - return m.joinYear, nil -} - -// FetchContributions mocks fetching GitHub contributions for a user -// in a given year, returning minimal valid data. -func (m *MockGitHubClient) FetchContributions(username string, year int) (*types.ContributionsResponse, error) { - // Return minimal valid response - resp := &types.ContributionsResponse{} - resp.Data.User.Login = username - // Add a single week with a single day for minimal valid data - week := struct { - ContributionDays []types.ContributionDay `json:"contributionDays"` - }{ - ContributionDays: []types.ContributionDay{ - { - ContributionCount: 1, - Date: time.Date(year, 1, 1, 0, 0, 0, 0, time.UTC).Format("2006-01-02"), - }, - }, - } - resp.Data.User.ContributionsCollection.ContributionCalendar.Weeks = []struct { - ContributionDays []types.ContributionDay `json:"contributionDays"` - }{week} - return resp, nil -} - // MockBrowser implements the Browser interface type MockBrowser struct { - LastURL string - ShouldError bool + LastURL string + Err error } // Browse implements the Browser interface -// Changed from pointer receiver to value receiver func (m *MockBrowser) Browse(url string) error { m.LastURL = url - if m.ShouldError { - return fmt.Errorf("mock browser error") - } - return nil + return m.Err } func TestFormatYearRange(t *testing.T) { @@ -313,7 +196,7 @@ func TestGenerateSkyline(t *testing.T) { endYear int targetUser string full bool - mockClient *MockGitHubClient + mockClient *mocks.MockGitHubClient wantErr bool }{ { @@ -322,9 +205,10 @@ func TestGenerateSkyline(t *testing.T) { endYear: 2024, targetUser: "testuser", full: false, - mockClient: &MockGitHubClient{ - username: "testuser", - joinYear: 2020, + mockClient: &mocks.MockGitHubClient{ + Username: "testuser", + JoinYear: 2020, + MockData: fixtures.GenerateContributionsResponse("testuser", 2024), }, wantErr: false, }, @@ -334,21 +218,23 @@ func TestGenerateSkyline(t *testing.T) { endYear: 2024, targetUser: "testuser", full: false, - mockClient: &MockGitHubClient{ - username: "testuser", - joinYear: 2020, + mockClient: &mocks.MockGitHubClient{ + Username: "testuser", + JoinYear: 2020, + MockData: fixtures.GenerateContributionsResponse("testuser", 2024), }, wantErr: false, }, { name: "full range", - startYear: 2020, + startYear: 2008, endYear: 2024, targetUser: "testuser", full: true, - mockClient: &MockGitHubClient{ - username: "testuser", - joinYear: 2020, + mockClient: &mocks.MockGitHubClient{ + Username: "testuser", + JoinYear: 2008, + MockData: fixtures.GenerateContributionsResponse("testuser", 2024), }, wantErr: false, }, @@ -374,23 +260,22 @@ func TestOpenGitHubProfile(t *testing.T) { tests := []struct { name string targetUser string - mockClient *MockGitHubClient + mockClient *mocks.MockGitHubClient wantURL string wantErr bool }{ { name: "specific user", targetUser: "testuser", - mockClient: &MockGitHubClient{}, + mockClient: &mocks.MockGitHubClient{}, wantURL: "https://github.com/testuser", wantErr: false, }, { name: "authenticated user", targetUser: "", - mockClient: &MockGitHubClient{ - username: "authuser", - shouldError: false, + mockClient: &mocks.MockGitHubClient{ + Username: "authuser", }, wantURL: "https://github.com/authuser", wantErr: false, @@ -398,9 +283,8 @@ func TestOpenGitHubProfile(t *testing.T) { { name: "client error", targetUser: "", - mockClient: &MockGitHubClient{ - username: "", - shouldError: true, + mockClient: &mocks.MockGitHubClient{ + Err: fmt.Errorf("mock error"), }, wantErr: true, }, @@ -408,8 +292,10 @@ func TestOpenGitHubProfile(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - // Create MockBrowser and call openGitHubProfile - mockBrowser := &MockBrowser{ShouldError: tt.wantErr} + mockBrowser := &MockBrowser{} + if tt.wantErr { + mockBrowser.Err = fmt.Errorf("mock error") + } err := openGitHubProfile(tt.targetUser, tt.mockClient, mockBrowser) if (err != nil) != tt.wantErr { diff --git a/testutil/fixtures/github.go b/testutil/fixtures/github.go new file mode 100644 index 0000000..491baba --- /dev/null +++ b/testutil/fixtures/github.go @@ -0,0 +1,43 @@ +// Package fixtures provides test utilities and mock data generators +// for testing the gh-skyline application. +package fixtures + +import ( + "time" + + "github.com/github/gh-skyline/types" +) + +// GenerateContributionsResponse creates a mock contributions response +func GenerateContributionsResponse(username string, year int) *types.ContributionsResponse { + response := &types.ContributionsResponse{} + response.User.Login = username + response.User.ContributionsCollection.ContributionCalendar.TotalContributions = 100 + + // Create sample weeks with contribution days + weeks := make([]struct { + ContributionDays []types.ContributionDay `json:"contributionDays"` + }, 52) + + for i := range weeks { + days := make([]types.ContributionDay, 7) + for j := range days { + days[j] = types.ContributionDay{ + ContributionCount: (i + j) % 10, + Date: time.Date(year, 1, 1+i*7+j, 0, 0, 0, 0, time.UTC).Format("2006-01-02"), + } + } + weeks[i].ContributionDays = days + } + + response.User.ContributionsCollection.ContributionCalendar.Weeks = weeks + return response +} + +// CreateMockContributionDay creates a mock contribution day +func CreateMockContributionDay(date time.Time, count int) types.ContributionDay { + return types.ContributionDay{ + ContributionCount: count, + Date: date.Format("2006-01-02"), + } +} diff --git a/testutil/mocks/github.go b/testutil/mocks/github.go new file mode 100644 index 0000000..b03409e --- /dev/null +++ b/testutil/mocks/github.go @@ -0,0 +1,79 @@ +// Package mocks provides mock implementations of interfaces used in testing +package mocks + +import ( + "fmt" + "time" + + "github.com/github/gh-skyline/testutil/fixtures" + "github.com/github/gh-skyline/types" +) + +// MockGitHubClient implements both GitHubClientInterface and APIClient interfaces +type MockGitHubClient struct { + Username string + JoinYear int + MockData *types.ContributionsResponse + Response interface{} // Generic response field for testing + Err error // Error to return if needed +} + +// GetAuthenticatedUser implements GitHubClientInterface +func (m *MockGitHubClient) GetAuthenticatedUser() (string, error) { + if m.Err != nil { + return "", m.Err + } + if m.Username == "" { + return "", fmt.Errorf("mock username not set") + } + return m.Username, nil +} + +// GetUserJoinYear implements GitHubClientInterface +func (m *MockGitHubClient) GetUserJoinYear(_ string) (int, error) { + if m.Err != nil { + return 0, m.Err + } + if m.JoinYear == 0 { + return 0, fmt.Errorf("mock join year not set") + } + return m.JoinYear, nil +} + +// FetchContributions implements GitHubClientInterface +func (m *MockGitHubClient) FetchContributions(username string, year int) (*types.ContributionsResponse, error) { + if m.Err != nil { + return nil, m.Err + } + // Always return generated mock data with valid contributions + return fixtures.GenerateContributionsResponse(username, year), nil +} + +// Do implements APIClient +func (m *MockGitHubClient) Do(_ string, _ map[string]interface{}, response interface{}) error { + if m.Err != nil { + return m.Err + } + + switch v := response.(type) { + case *struct { + Viewer struct { + Login string `json:"login"` + } `json:"viewer"` + }: + v.Viewer.Login = m.Username + case *struct { + User struct { + CreatedAt time.Time `json:"createdAt"` + } `json:"user"` + }: + if m.JoinYear > 0 { + v.User.CreatedAt = time.Date(m.JoinYear, 1, 1, 0, 0, 0, 0, time.UTC) + } + case *types.ContributionsResponse: + // Always use generated mock data instead of empty response + mockResp := fixtures.GenerateContributionsResponse(m.Username, time.Now().Year()) + *v = *mockResp + } + return nil +} diff --git a/types/types.go b/types/types.go index f70f84d..13f6dd6 100644 --- a/types/types.go +++ b/types/types.go @@ -9,9 +9,8 @@ import ( ) // ContributionDay represents a single day of GitHub contributions. -// It contains the number of contributions made on a specific date. type ContributionDay struct { - ContributionCount int + ContributionCount int `json:"contributionCount"` Date string `json:"date"` } @@ -37,22 +36,19 @@ func (c ContributionDay) Validate() error { return nil } -// ContributionsResponse represents the GitHub GraphQL API response structure -// for fetching user contributions data. +// ContributionsResponse represents the contribution data returned by the GitHub API. type ContributionsResponse struct { - Data struct { - User struct { - Login string - ContributionsCollection struct { - ContributionCalendar struct { - TotalContributions int `json:"totalContributions"` - Weeks []struct { - ContributionDays []ContributionDay `json:"contributionDays"` - } `json:"weeks"` - } `json:"contributionCalendar"` - } `json:"contributionsCollection"` - } `json:"user"` - } `json:"data"` + User struct { + Login string `json:"login"` + ContributionsCollection struct { + ContributionCalendar struct { + TotalContributions int `json:"totalContributions"` + Weeks []struct { + ContributionDays []ContributionDay `json:"contributionDays"` + } `json:"weeks"` + } `json:"contributionCalendar"` + } `json:"contributionsCollection"` + } `json:"user"` } // Point3D represents a point in 3D space using float64 for accuracy in calculations. diff --git a/types/types_test.go b/types/types_test.go index 29a9be2..ccf1798 100644 --- a/types/types_test.go +++ b/types/types_test.go @@ -44,23 +44,21 @@ func TestContributionDaySerialization(t *testing.T) { // is properly parsed with nested fields. func TestContributionsResponseParsing(t *testing.T) { sampleResponse := `{ - "data": { - "user": { - "login": "testuser", - "contributionsCollection": { - "contributionCalendar": { - "totalContributions": 100, - "weeks": [ - { - "contributionDays": [ - { - "contributionCount": 5, - "date": "2024-03-21" - } - ] - } - ] - } + "user": { + "login": "testuser", + "contributionsCollection": { + "contributionCalendar": { + "totalContributions": 100, + "weeks": [ + { + "contributionDays": [ + { + "contributionCount": 5, + "date": "2024-03-21" + } + ] + } + ] } } } @@ -75,12 +73,12 @@ func TestContributionsResponseParsing(t *testing.T) { expectedUsername := "testuser" expectedTotalContributions := 100 - if parsedResponse.Data.User.Login != expectedUsername { - t.Errorf("username mismatch: got %q, want %q", parsedResponse.Data.User.Login, expectedUsername) + if parsedResponse.User.Login != expectedUsername { + t.Errorf("username mismatch: got %q, want %q", parsedResponse.User.Login, expectedUsername) } - if parsedResponse.Data.User.ContributionsCollection.ContributionCalendar.TotalContributions != expectedTotalContributions { + if parsedResponse.User.ContributionsCollection.ContributionCalendar.TotalContributions != expectedTotalContributions { t.Errorf("total contributions mismatch: got %d, want %d", - parsedResponse.Data.User.ContributionsCollection.ContributionCalendar.TotalContributions, + parsedResponse.User.ContributionsCollection.ContributionCalendar.TotalContributions, expectedTotalContributions) } } From 0940e5c29112526e2ceb207c332fabe309d95993 Mon Sep 17 00:00:00 2001 From: Laotree Date: Thu, 12 Dec 2024 11:28:31 +0800 Subject: [PATCH 44/94] Update CONTRIBUTING.md Reading this document, I found 2 improvements could occur. - a typo - a link turn to be 404 --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0e5f65f..3bd7be2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,10 +34,10 @@ The environment will be ready to use in a few minutes. ### Local development environment -These are one time installations required to be able to test your changes locally as part of the pull request (PR) submission process. +These are one-time installations required to be able to test your changes locally as part of the pull request (PR) submission process. 1. install Go [through download](https://go.dev/doc/install) | [through Homebrew](https://formulae.brew.sh/formula/go) -1. [install golangci-lint](https://golangci-lint.run/usage/install/#local-installation) +1. [install golangci-lint](https://golangci-lint.run/welcome/install/#local-installation) ### Building the extension From 8b4ac5bf77063ba3be42ce7a56cb38784f256afa Mon Sep 17 00:00:00 2001 From: Samuel Johnson Date: Fri, 13 Dec 2024 20:07:19 -0500 Subject: [PATCH 45/94] Add flag for terminal art only mode --- main.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main.go b/main.go index e815810..3880cc9 100644 --- a/main.go +++ b/main.go @@ -45,6 +45,7 @@ var ( full bool debug bool web bool + artOnly bool output string // new output path flag rootCmd = &cobra.Command{ @@ -106,6 +107,7 @@ func init() { rootCmd.Flags().BoolVarP(&full, "full", "f", false, "Generate contribution graph from join year to current year") rootCmd.Flags().BoolVarP(&debug, "debug", "d", false, "Enable debug logging") rootCmd.Flags().BoolVarP(&web, "web", "w", false, "Open GitHub profile (authenticated or specified user).") + rootCmd.Flags().BoolVar(&artOnly, "art-only", false, "Generate only ascii art and suppress skyline header.") rootCmd.Flags().StringVarP(&output, "output", "o", "", "Output file path (optional)") } From 73feeb1eabf8f8031f922ccd28e3649efe7e24ca Mon Sep 17 00:00:00 2001 From: Samuel Johnson Date: Fri, 13 Dec 2024 20:35:03 -0500 Subject: [PATCH 46/94] Add checks for new flag --- main.go | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/main.go b/main.go index 3880cc9..898fd7c 100644 --- a/main.go +++ b/main.go @@ -178,7 +178,7 @@ func generateSkyline(startYear, endYear int, targetUser string, full bool) error allContributions = append(allContributions, contributions) // Generate ASCII art for each year - asciiArt, err := ascii.GenerateASCII(contributions, targetUser, year, year == startYear) + asciiArt, err := ascii.GenerateASCII(contributions, targetUser, year, (year == startYear) && !artOnly) if err != nil { if warnErr := log.Warning("Failed to generate ASCII preview: %v", err); warnErr != nil { return warnErr @@ -204,14 +204,18 @@ func generateSkyline(startYear, endYear int, targetUser string, full bool) error } } - // Generate filename - outputPath := generateOutputFilename(targetUser, startYear, endYear) - - // Generate the STL file - if len(allContributions) == 1 { - return stl.GenerateSTL(allContributions[0], outputPath, targetUser, startYear) - } - return stl.GenerateSTLRange(allContributions, outputPath, targetUser, startYear, endYear) + if !artOnly { + // Generate filename + outputPath := generateOutputFilename(targetUser, startYear, endYear) + + // Generate the STL file + if len(allContributions) == 1 { + return stl.GenerateSTL(allContributions[0], outputPath, targetUser, startYear) + } + return stl.GenerateSTLRange(allContributions, outputPath, targetUser, startYear, endYear) + } + + return nil } // Variable for client initialization - allows for testing From c37b948a135242dad6b011250de52553d5cd87af Mon Sep 17 00:00:00 2001 From: Samuel Johnson Date: Fri, 13 Dec 2024 20:52:45 -0500 Subject: [PATCH 47/94] Split up header and footer options --- ascii/generator.go | 12 +++++++----- main.go | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/ascii/generator.go b/ascii/generator.go index cba3457..7ba2aeb 100644 --- a/ascii/generator.go +++ b/ascii/generator.go @@ -16,7 +16,7 @@ var ErrInvalidGrid = errors.New("invalid contribution grid") // GenerateASCII creates a 2D ASCII art representation of the contribution data. // It returns the generated ASCII art as a string and an error if the operation fails. // When includeHeader is true, the output includes the header template. -func GenerateASCII(contributionGrid [][]types.ContributionDay, username string, year int, includeHeader bool) (string, error) { +func GenerateASCII(contributionGrid [][]types.ContributionDay, username string, year int, includeHeader bool, includeUserInfo bool) (string, error) { if len(contributionGrid) == 0 { return "", ErrInvalidGrid } @@ -74,10 +74,12 @@ func GenerateASCII(contributionGrid [][]types.ContributionDay, username string, buffer.WriteRune('\n') } - // Add centered user info below - buffer.WriteString("\n") - buffer.WriteString(centerText(username)) - buffer.WriteString(centerText(fmt.Sprintf("%d", year))) + if includeUserInfo { + // Add centered user info below + buffer.WriteString("\n") + buffer.WriteString(centerText(username)) + buffer.WriteString(centerText(fmt.Sprintf("%d", year))) + } return buffer.String(), nil } diff --git a/main.go b/main.go index 898fd7c..3093434 100644 --- a/main.go +++ b/main.go @@ -178,7 +178,7 @@ func generateSkyline(startYear, endYear int, targetUser string, full bool) error allContributions = append(allContributions, contributions) // Generate ASCII art for each year - asciiArt, err := ascii.GenerateASCII(contributions, targetUser, year, (year == startYear) && !artOnly) + asciiArt, err := ascii.GenerateASCII(contributions, targetUser, year, (year == startYear) && !artOnly, !artOnly) if err != nil { if warnErr := log.Warning("Failed to generate ASCII preview: %v", err); warnErr != nil { return warnErr From a0497c92e24944e019fd1b3bb306b04094e2be3a Mon Sep 17 00:00:00 2001 From: Samuel Johnson Date: Fri, 13 Dec 2024 20:53:31 -0500 Subject: [PATCH 48/94] Add additional ascii generation tests --- ascii/generator_test.go | 62 ++++++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/ascii/generator_test.go b/ascii/generator_test.go index a0ed580..b35620b 100644 --- a/ascii/generator_test.go +++ b/ascii/generator_test.go @@ -9,39 +9,47 @@ import ( func TestGenerateASCII(t *testing.T) { tests := []struct { - name string - grid [][]types.ContributionDay - user string - year int - includeHeader bool - wantErr bool + name string + grid [][]types.ContributionDay + user string + year int + includeHeader bool + wantErr bool }{ { - name: "empty grid", - grid: [][]types.ContributionDay{}, - user: "testuser", - year: 2023, - includeHeader: false, - wantErr: true, + name: "empty grid", + grid: [][]types.ContributionDay{}, + user: "testuser", + year: 2023, + includeHeader: false, + wantErr: true, }, { - name: "valid grid", - grid: makeTestGrid(3, 7), - user: "testuser", - year: 2023, - includeHeader: false, - wantErr: false, + name: "valid grid", + grid: makeTestGrid(3, 7), + user: "testuser", + year: 2023, + includeHeader: false, + wantErr: false, }, + { + name: "no header", + grid: makeTestGrid(3, 7), + user: "testuser", + year: 2023, + includeHeader: false, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result, err := GenerateASCII(tt.grid, tt.user, tt.year, tt.includeHeader) + result, err := GenerateASCII(tt.grid, tt.user, tt.year, tt.includeHeader, tt.includeHeader) if (err != nil) != tt.wantErr { t.Errorf("GenerateASCII() error = %v, wantErr %v", err, tt.wantErr) return } - if !tt.wantErr { + if !tt.wantErr && tt.includeHeader { // Existing validation code... if !strings.Contains(result, "testuser") { t.Error("Generated ASCII should contain username") @@ -52,7 +60,21 @@ func TestGenerateASCII(t *testing.T) { if !strings.Contains(result, string(EmptyBlock)) { t.Error("Generated ASCII should contain empty blocks") } + if !strings.Contains(result, HeaderTemplate) { + t.Error("Generated ASCII should contain header") + } } + if !tt.wantErr && !tt.includeHeader { + if strings.Contains(result, "testuser") { + t.Error("Generated ASCII should exclude username when requested") + } + if strings.Contains(result, "2023") { + t.Error("Generated ASCII should exclude year when requested") + } + if strings.Contains(result, HeaderTemplate) { + t.Error("Generated ASCII should exclude header when requested") + } + } }) } } From cfe26b0abcc9d069269c04090b7c702e65133e60 Mon Sep 17 00:00:00 2001 From: Samuel Johnson Date: Fri, 13 Dec 2024 20:58:09 -0500 Subject: [PATCH 49/94] Reword cli help entry --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 3093434..b23ef4a 100644 --- a/main.go +++ b/main.go @@ -107,7 +107,7 @@ func init() { rootCmd.Flags().BoolVarP(&full, "full", "f", false, "Generate contribution graph from join year to current year") rootCmd.Flags().BoolVarP(&debug, "debug", "d", false, "Enable debug logging") rootCmd.Flags().BoolVarP(&web, "web", "w", false, "Open GitHub profile (authenticated or specified user).") - rootCmd.Flags().BoolVar(&artOnly, "art-only", false, "Generate only ascii art and suppress skyline header.") + rootCmd.Flags().BoolVar(&artOnly, "art-only", false, "Generate only ASCII preview") rootCmd.Flags().StringVarP(&output, "output", "o", "", "Output file path (optional)") } From 31f691d3bc77205d481dc7faaa881c7d5dcbb708 Mon Sep 17 00:00:00 2001 From: Samuel Johnson Date: Fri, 13 Dec 2024 21:11:16 -0500 Subject: [PATCH 50/94] Update CLI instructions in README --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 518bc6d..0470195 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ You can run the `gh skyline` command with the following flags: - Examples: `gh skyline --year 2020`, `gh skyline --year 2014-2024` - `-w`, `--web`: Open the GitHub profile for the authenticated or specified user. - Example: `gh skyline --web`, `gh skyline --user mona --web` +- `--art-only`: Show the ASCII art preview without generating an STL file. ### Examples @@ -78,6 +79,12 @@ Generate a skyline from the user's join year to the current year: gh skyline --full ``` +Generate only the ASCII preview for a skyline: + +```bash +gh skyline --art-only +``` + Enable debug logging: ```bash From 870c8163b0e05932f9cd3084b02a58e19a7d47a2 Mon Sep 17 00:00:00 2001 From: Samuel Johnson Date: Fri, 13 Dec 2024 21:16:05 -0500 Subject: [PATCH 51/94] Incorporate formatter corrections --- ascii/generator.go | 12 +++---- ascii/generator_test.go | 80 ++++++++++++++++++++--------------------- main.go | 24 ++++++------- 3 files changed, 58 insertions(+), 58 deletions(-) diff --git a/ascii/generator.go b/ascii/generator.go index 7ba2aeb..fa72b6c 100644 --- a/ascii/generator.go +++ b/ascii/generator.go @@ -74,12 +74,12 @@ func GenerateASCII(contributionGrid [][]types.ContributionDay, username string, buffer.WriteRune('\n') } - if includeUserInfo { - // Add centered user info below - buffer.WriteString("\n") - buffer.WriteString(centerText(username)) - buffer.WriteString(centerText(fmt.Sprintf("%d", year))) - } + if includeUserInfo { + // Add centered user info below + buffer.WriteString("\n") + buffer.WriteString(centerText(username)) + buffer.WriteString(centerText(fmt.Sprintf("%d", year))) + } return buffer.String(), nil } diff --git a/ascii/generator_test.go b/ascii/generator_test.go index b35620b..8e547d5 100644 --- a/ascii/generator_test.go +++ b/ascii/generator_test.go @@ -9,37 +9,37 @@ import ( func TestGenerateASCII(t *testing.T) { tests := []struct { - name string - grid [][]types.ContributionDay - user string - year int - includeHeader bool - wantErr bool + name string + grid [][]types.ContributionDay + user string + year int + includeHeader bool + wantErr bool }{ { - name: "empty grid", - grid: [][]types.ContributionDay{}, - user: "testuser", - year: 2023, - includeHeader: false, - wantErr: true, + name: "empty grid", + grid: [][]types.ContributionDay{}, + user: "testuser", + year: 2023, + includeHeader: false, + wantErr: true, }, { - name: "valid grid", - grid: makeTestGrid(3, 7), - user: "testuser", - year: 2023, - includeHeader: false, - wantErr: false, + name: "valid grid", + grid: makeTestGrid(3, 7), + user: "testuser", + year: 2023, + includeHeader: false, + wantErr: false, + }, + { + name: "no header", + grid: makeTestGrid(3, 7), + user: "testuser", + year: 2023, + includeHeader: false, + wantErr: false, }, - { - name: "no header", - grid: makeTestGrid(3, 7), - user: "testuser", - year: 2023, - includeHeader: false, - wantErr: false, - }, } for _, tt := range tests { @@ -60,21 +60,21 @@ func TestGenerateASCII(t *testing.T) { if !strings.Contains(result, string(EmptyBlock)) { t.Error("Generated ASCII should contain empty blocks") } - if !strings.Contains(result, HeaderTemplate) { - t.Error("Generated ASCII should contain header") - } + if !strings.Contains(result, HeaderTemplate) { + t.Error("Generated ASCII should contain header") + } + } + if !tt.wantErr && !tt.includeHeader { + if strings.Contains(result, "testuser") { + t.Error("Generated ASCII should exclude username when requested") + } + if strings.Contains(result, "2023") { + t.Error("Generated ASCII should exclude year when requested") + } + if strings.Contains(result, HeaderTemplate) { + t.Error("Generated ASCII should exclude header when requested") + } } - if !tt.wantErr && !tt.includeHeader { - if strings.Contains(result, "testuser") { - t.Error("Generated ASCII should exclude username when requested") - } - if strings.Contains(result, "2023") { - t.Error("Generated ASCII should exclude year when requested") - } - if strings.Contains(result, HeaderTemplate) { - t.Error("Generated ASCII should exclude header when requested") - } - } }) } } diff --git a/main.go b/main.go index b23ef4a..bdc4f46 100644 --- a/main.go +++ b/main.go @@ -204,18 +204,18 @@ func generateSkyline(startYear, endYear int, targetUser string, full bool) error } } - if !artOnly { - // Generate filename - outputPath := generateOutputFilename(targetUser, startYear, endYear) - - // Generate the STL file - if len(allContributions) == 1 { - return stl.GenerateSTL(allContributions[0], outputPath, targetUser, startYear) - } - return stl.GenerateSTLRange(allContributions, outputPath, targetUser, startYear, endYear) - } - - return nil + if !artOnly { + // Generate filename + outputPath := generateOutputFilename(targetUser, startYear, endYear) + + // Generate the STL file + if len(allContributions) == 1 { + return stl.GenerateSTL(allContributions[0], outputPath, targetUser, startYear) + } + return stl.GenerateSTLRange(allContributions, outputPath, targetUser, startYear, endYear) + } + + return nil } // Variable for client initialization - allows for testing From a249572432b821678d63e386105bd70f279ab9c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 19:05:17 +0000 Subject: [PATCH 52/94] Bump super-linter/super-linter in the github-actions group Bumps the github-actions group with 1 update: [super-linter/super-linter](https://github.com/super-linter/super-linter). Updates `super-linter/super-linter` from 7.2.0 to 7.2.1 - [Release notes](https://github.com/super-linter/super-linter/releases) - [Changelog](https://github.com/super-linter/super-linter/blob/main/CHANGELOG.md) - [Commits](https://github.com/super-linter/super-linter/compare/e1cb86b6e8d119f789513668b4b30bf17fe1efe4...85f7611e0f7b53c8573cca84aa0ed4344f6f6a4d) --- updated-dependencies: - dependency-name: super-linter/super-linter dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/linter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 8166258..bea4066 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -29,7 +29,7 @@ jobs: go-version: "1.23" - name: Run Super-Linter - uses: super-linter/super-linter/slim@e1cb86b6e8d119f789513668b4b30bf17fe1efe4 # v7.2.0 + uses: super-linter/super-linter/slim@85f7611e0f7b53c8573cca84aa0ed4344f6f6a4d # v7.2.1 env: VALIDATE_ALL_CODEBASE: true DEFAULT_BRANCH: "main" From b39cfcc54f96f3f60331cba7d9979c777c9e2f60 Mon Sep 17 00:00:00 2001 From: Siddharth Koli Date: Sat, 21 Dec 2024 00:14:25 +0530 Subject: [PATCH 53/94] Fix divide by 0 in case of 0 contributions --- ascii/generator.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ascii/generator.go b/ascii/generator.go index cba3457..bbf63a7 100644 --- a/ascii/generator.go +++ b/ascii/generator.go @@ -60,7 +60,10 @@ func GenerateASCII(contributionGrid [][]types.ContributionDay, username string, if day.ContributionCount == -1 { asciiGrid[dayIdx][weekIdx] = FutureBlock } else { - normalized := float64(day.ContributionCount) / float64(maxContributions) + normalized := 0.0 + if maxContributions != 0 { + normalized = float64(day.ContributionCount) / float64(maxContributions) + } asciiGrid[dayIdx][weekIdx] = getBlock(normalized, dayIdx, nonZeroCount) } } From 460d4057709e97f050853c94f509a7a8e2180b8f Mon Sep 17 00:00:00 2001 From: Siddharth Koli Date: Sat, 21 Dec 2024 00:15:44 +0530 Subject: [PATCH 54/94] Improve readability of conditions --- main.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index e815810..9addd7e 100644 --- a/main.go +++ b/main.go @@ -190,8 +190,9 @@ func generateSkyline(startYear, endYear int, targetUser string, full bool) error lines := strings.Split(asciiArt, "\n") gridStart := 0 for i, line := range lines { - if strings.Contains(line, string(ascii.EmptyBlock)) || - strings.Contains(line, string(ascii.FoundationLow)) { + containsEmptyBlock := strings.Contains(line, string(ascii.EmptyBlock)) + containsFoundationLow := strings.Contains(line, string(ascii.FoundationLow)) + isNotOnlyEmptyBlocks := strings.Trim(line, string(ascii.EmptyBlock)) != "" gridStart = i break } From 64066af916f865432e47a6bf260a673396c8860b Mon Sep 17 00:00:00 2001 From: Siddharth Koli Date: Sat, 21 Dec 2024 00:16:07 +0530 Subject: [PATCH 55/94] Add condition to skip over empty blocks (when -f is passed) --- main.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main.go b/main.go index 9addd7e..79ce61d 100644 --- a/main.go +++ b/main.go @@ -193,6 +193,8 @@ func generateSkyline(startYear, endYear int, targetUser string, full bool) error containsEmptyBlock := strings.Contains(line, string(ascii.EmptyBlock)) containsFoundationLow := strings.Contains(line, string(ascii.FoundationLow)) isNotOnlyEmptyBlocks := strings.Trim(line, string(ascii.EmptyBlock)) != "" + + if (containsEmptyBlock || containsFoundationLow) && isNotOnlyEmptyBlocks { gridStart = i break } From bb8c35fe0eb9e9efee2e948ccf88ae7c28841f2a Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Sat, 28 Dec 2024 10:50:09 +0000 Subject: [PATCH 56/94] Remove GitHub CodeQL extension from devcontainer configuration --- .devcontainer/devcontainer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 6c71182..8883a33 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -14,7 +14,6 @@ "github.copilot-workspace", "GitHub.vscode-pull-request-github", "GitHub.remotehub", - "GitHub.vscode-codeql", "golang.Go" ] } From 9b837dc564168f5c71363ac35d914c3934b9b515 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Sat, 28 Dec 2024 11:03:35 +0000 Subject: [PATCH 57/94] Remove baseline validation workflow from GitHub Actions --- .github/workflows/baseline.yml | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 .github/workflows/baseline.yml diff --git a/.github/workflows/baseline.yml b/.github/workflows/baseline.yml deleted file mode 100644 index 8fa8481..0000000 --- a/.github/workflows/baseline.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Validate Repository Configuration - -permissions: - contents: read - -on: - push: - branches: [main] - pull_request: - branches: [main] - workflow_dispatch: - -jobs: - validate: - name: Validate Baseline Configuration - uses: chrisreddington/reusable-workflows/.github/workflows/baseline-validator.yml@d62f6e0cbe864707a620bba0be92695711514442 - with: - required-features: "ghcr.io/devcontainers/features/github-cli:1" From 05293138966f23b02f0e29abbe837a3ee5db2600 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Sat, 28 Dec 2024 11:37:17 +0000 Subject: [PATCH 58/94] Add ConfigError type and improve GitHub profile URL generation --- errors/errors.go | 1 + main.go | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/errors/errors.go b/errors/errors.go index fc52f8a..ec3bca3 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -15,6 +15,7 @@ const ( NetworkError ErrorType = "NETWORK" // Network communication errors GraphQLError ErrorType = "GRAPHQL" // GitHub GraphQL API errors STLError ErrorType = "STL" // STL file generation errors + ConfigError ErrorType = "CONFIG" // Configuration errors ) // SkylineError provides structured error information including type and context diff --git a/main.go b/main.go index 253bfdf..8f93b95 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,7 @@ import ( "github.com/cli/go-gh/v2/pkg/api" "github.com/cli/go-gh/v2/pkg/browser" + "github.com/cli/go-gh/v2/pkg/config" "github.com/github/gh-skyline/ascii" "github.com/github/gh-skyline/errors" "github.com/github/gh-skyline/github" @@ -290,6 +291,16 @@ func openGitHubProfile(targetUser string, client GitHubClientInterface, b Browse targetUser = username } - profileURL := fmt.Sprintf("https://github.com/%s", targetUser) + cfg, err := config.Read(nil) + if err != nil { + return errors.New(errors.ConfigError, "failed to read gh config", err) + } + + hostname, _ := cfg.Get([]string{"host"}) + if hostname == "" { + hostname = "github.com" + } + + profileURL := fmt.Sprintf("https://%s/%s", hostname, targetUser) return b.Browse(profileURL) } From 31778c6befb383d5a9eb0e394bc379f8c72c3bb9 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Sat, 28 Dec 2024 11:48:01 +0000 Subject: [PATCH 59/94] Revert "Add ConfigError type and improve GitHub profile URL generation" --- errors/errors.go | 1 - main.go | 13 +------------ 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/errors/errors.go b/errors/errors.go index ec3bca3..fc52f8a 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -15,7 +15,6 @@ const ( NetworkError ErrorType = "NETWORK" // Network communication errors GraphQLError ErrorType = "GRAPHQL" // GitHub GraphQL API errors STLError ErrorType = "STL" // STL file generation errors - ConfigError ErrorType = "CONFIG" // Configuration errors ) // SkylineError provides structured error information including type and context diff --git a/main.go b/main.go index 8f93b95..253bfdf 100644 --- a/main.go +++ b/main.go @@ -11,7 +11,6 @@ import ( "github.com/cli/go-gh/v2/pkg/api" "github.com/cli/go-gh/v2/pkg/browser" - "github.com/cli/go-gh/v2/pkg/config" "github.com/github/gh-skyline/ascii" "github.com/github/gh-skyline/errors" "github.com/github/gh-skyline/github" @@ -291,16 +290,6 @@ func openGitHubProfile(targetUser string, client GitHubClientInterface, b Browse targetUser = username } - cfg, err := config.Read(nil) - if err != nil { - return errors.New(errors.ConfigError, "failed to read gh config", err) - } - - hostname, _ := cfg.Get([]string{"host"}) - if hostname == "" { - hostname = "github.com" - } - - profileURL := fmt.Sprintf("https://%s/%s", hostname, targetUser) + profileURL := fmt.Sprintf("https://github.com/%s", targetUser) return b.Browse(profileURL) } From 49c21c6d1b47fa966fe643c8923e82dcd284f2dd Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Sat, 28 Dec 2024 12:25:24 +0000 Subject: [PATCH 60/94] Add shorthand flag for 'art-only' option in CLI --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 956973a..2f3be30 100644 --- a/main.go +++ b/main.go @@ -107,7 +107,7 @@ func init() { rootCmd.Flags().BoolVarP(&full, "full", "f", false, "Generate contribution graph from join year to current year") rootCmd.Flags().BoolVarP(&debug, "debug", "d", false, "Enable debug logging") rootCmd.Flags().BoolVarP(&web, "web", "w", false, "Open GitHub profile (authenticated or specified user).") - rootCmd.Flags().BoolVar(&artOnly, "art-only", false, "Generate only ASCII preview") + rootCmd.Flags().BoolVarP(&artOnly, "art-only", "a", false, "Generate only ASCII preview") rootCmd.Flags().StringVarP(&output, "output", "o", "", "Output file path (optional)") } From 6ad84d2bdec0d8c560229ecae597064521e3e882 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Sat, 28 Dec 2024 12:29:50 +0000 Subject: [PATCH 61/94] Include -a option in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0470195..1d92e06 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ You can run the `gh skyline` command with the following flags: - Examples: `gh skyline --year 2020`, `gh skyline --year 2014-2024` - `-w`, `--web`: Open the GitHub profile for the authenticated or specified user. - Example: `gh skyline --web`, `gh skyline --user mona --web` -- `--art-only`: Show the ASCII art preview without generating an STL file. +- `-a`, `--art-only`: Show the ASCII art preview without generating an STL file. ### Examples From 91fd83e0861a10f7a74e6401a22116ae29a5eaab Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Sat, 28 Dec 2024 13:02:51 +0000 Subject: [PATCH 62/94] Fix README.md formatting issue --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1d92e06..c129177 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ You can run the `gh skyline` command with the following flags: - Examples: `gh skyline --year 2020`, `gh skyline --year 2014-2024` - `-w`, `--web`: Open the GitHub profile for the authenticated or specified user. - Example: `gh skyline --web`, `gh skyline --user mona --web` -- `-a`, `--art-only`: Show the ASCII art preview without generating an STL file. +- `-a`, `--art-only`: Show the ASCII art preview without generating an STL file. ### Examples From 3e4f27b3beb0880160240bfe26ce8aeeb0a45866 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Sat, 28 Dec 2024 14:07:38 +0000 Subject: [PATCH 63/94] Add tests for GenerateASCII function with zero contributions --- ascii/generator_test.go | 60 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/ascii/generator_test.go b/ascii/generator_test.go index a0ed580..758e4cb 100644 --- a/ascii/generator_test.go +++ b/ascii/generator_test.go @@ -97,3 +97,63 @@ func TestGetBlock(t *testing.T) { }) } } + +// TestGenerateASCIIZeroContributions tests the GenerateASCII function with zero contributions. +// It verifies that the skyline consists of empty blocks and appropriately handles the header. +func TestGenerateASCIIZeroContributions(t *testing.T) { + tests := []struct { + name string + includeHeader bool + }{ + { + name: "Zero contributions without header", + includeHeader: false, + }, + { + name: "Zero contributions with header", + includeHeader: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a test grid with zero contributions + grid := makeTestGrid(3, 7) + for i := range grid { + for j := range grid[i] { + grid[i][j].ContributionCount = 0 + } + } + + // Generate ASCII art + result, err := GenerateASCII(grid, "testuser", 2023, tt.includeHeader) + if err != nil { + t.Fatalf("GenerateASCII() returned an error: %v", err) + } + + lines := strings.Split(result, "\n") + + // Determine the starting line of the skyline + skylineStart := 0 + if tt.includeHeader { + // Assuming HeaderTemplate has a fixed number of lines + headerLines := strings.Count(HeaderTemplate, "\n") + skylineStart = headerLines + 1 // +1 for the additional newline after header + } + + // Verify the skyline has at least 7 lines + if len(lines) < skylineStart+7 { + t.Fatalf("Expected at least %d lines for skyline, got %d", skylineStart+7, len(lines)) + } + + // Check each line of the skyline for empty blocks + for i := skylineStart; i < skylineStart+7; i++ { + for _, ch := range lines[i] { + if ch != EmptyBlock { + t.Errorf("Expected empty block in skyline, got '%c' on line %d", ch, i+1) + } + } + } + }) + } +} From 10a6ff539a49e96036962270803f0b66aef89aad Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Sat, 28 Dec 2024 14:34:34 +0000 Subject: [PATCH 64/94] Refactor TestGenerateASCIIZeroContributions to include footer handling in the test cases --- ascii/generator_test.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ascii/generator_test.go b/ascii/generator_test.go index 0352c36..4ee56c9 100644 --- a/ascii/generator_test.go +++ b/ascii/generator_test.go @@ -121,19 +121,19 @@ func TestGetBlock(t *testing.T) { } // TestGenerateASCIIZeroContributions tests the GenerateASCII function with zero contributions. -// It verifies that the skyline consists of empty blocks and appropriately handles the header. +// It verifies that the skyline consists of empty blocks and appropriately handles the header and footer. func TestGenerateASCIIZeroContributions(t *testing.T) { tests := []struct { - name string - includeHeader bool + name string + includeHeaderAndFooter bool }{ { - name: "Zero contributions without header", - includeHeader: false, + name: "Zero contributions without header", + includeHeaderAndFooter: false, }, { - name: "Zero contributions with header", - includeHeader: true, + name: "Zero contributions with header", + includeHeaderAndFooter: true, }, } @@ -148,7 +148,7 @@ func TestGenerateASCIIZeroContributions(t *testing.T) { } // Generate ASCII art - result, err := GenerateASCII(grid, "testuser", 2023, tt.includeHeader) + result, err := GenerateASCII(grid, "testuser", 2023, tt.includeHeaderAndFooter, tt.includeHeaderAndFooter) if err != nil { t.Fatalf("GenerateASCII() returned an error: %v", err) } @@ -157,7 +157,7 @@ func TestGenerateASCIIZeroContributions(t *testing.T) { // Determine the starting line of the skyline skylineStart := 0 - if tt.includeHeader { + if tt.includeHeaderAndFooter { // Assuming HeaderTemplate has a fixed number of lines headerLines := strings.Count(HeaderTemplate, "\n") skylineStart = headerLines + 1 // +1 for the additional newline after header From 226902c99ca5712542596de5d2e883dc59496ad6 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Sat, 28 Dec 2024 15:13:13 +0000 Subject: [PATCH 65/94] Update year formatting in output to use YYYY-YY format --- main.go | 3 ++- main_test.go | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index f4c1f49..a5a0cdc 100644 --- a/main.go +++ b/main.go @@ -124,7 +124,8 @@ func formatYearRange(startYear, endYear int) string { if startYear == endYear { return fmt.Sprintf("%d", startYear) } - return fmt.Sprintf("%02d-%02d", startYear%100, endYear%100) + // Use YYYY-YY format for multi-year ranges + return fmt.Sprintf("%04d-%02d", startYear, endYear%100) } // generateOutputFilename creates a consistent filename for the STL output diff --git a/main_test.go b/main_test.go index be9ae03..242d6ef 100644 --- a/main_test.go +++ b/main_test.go @@ -39,7 +39,7 @@ func TestFormatYearRange(t *testing.T) { name: "different years", startYear: 2020, endYear: 2024, - want: "20-24", + want: "2020-24", }, } @@ -73,7 +73,7 @@ func TestGenerateOutputFilename(t *testing.T) { user: "testuser", startYear: 2020, endYear: 2024, - want: "testuser-20-24-github-skyline.stl", + want: "testuser-2020-24-github-skyline.stl", }, } From 06535d61f2d3c133b2916813f506fa2738115680 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Dec 2024 18:24:25 +0000 Subject: [PATCH 66/94] Bump github.com/thlib/go-timezone-local in the go-dependencies group Bumps the go-dependencies group with 1 update: [github.com/thlib/go-timezone-local](https://github.com/thlib/go-timezone-local). Updates `github.com/thlib/go-timezone-local` from 0.0.4 to 0.0.6 - [Release notes](https://github.com/thlib/go-timezone-local/releases) - [Commits](https://github.com/thlib/go-timezone-local/compare/v0.0.4...v0.0.6) --- updated-dependencies: - dependency-name: github.com/thlib/go-timezone-local dependency-type: indirect update-type: version-update:semver-patch dependency-group: go-dependencies ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8a2aa58..30744f5 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/muesli/termenv v0.15.2 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/thlib/go-timezone-local v0.0.4 // indirect + github.com/thlib/go-timezone-local v0.0.6 // indirect golang.org/x/image v0.23.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/term v0.27.0 // indirect diff --git a/go.sum b/go.sum index 63ad29a..777916e 100644 --- a/go.sum +++ b/go.sum @@ -48,8 +48,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/thlib/go-timezone-local v0.0.4 h1:9oqkZLirWUtrFmhF/7WxR8Y0TBAGaThe95w6K3pLKBk= -github.com/thlib/go-timezone-local v0.0.4/go.mod h1:/Tnicc6m/lsJE0irFMA0LfIwTBo4QP7A8IfyIv4zZKI= +github.com/thlib/go-timezone-local v0.0.6 h1:Ii3QJ4FhosL/+eCZl6Hsdr4DDU4tfevNoV83yAEo2tU= +github.com/thlib/go-timezone-local v0.0.6/go.mod h1:/Tnicc6m/lsJE0irFMA0LfIwTBo4QP7A8IfyIv4zZKI= golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From b242c7319820eb165ea100c1462dd3fe0f4fa198 Mon Sep 17 00:00:00 2001 From: haruna Date: Mon, 6 Jan 2025 03:48:12 +0900 Subject: [PATCH 67/94] release binaries for Android --- .github/workflows/release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9d93825..0c7089a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,5 +15,7 @@ jobs: - uses: actions/checkout@v4 - uses: cli/gh-extension-precompile@561b19deda1228a0edf856c3325df87416f8c9bd # v2.0.0 with: + release_android: true + android_sdk_version: 30 # Android 11.0 generate_attestations: true go_version_file: go.mod From 12040827e01118c26f59c603806739b14c91a807 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 19:05:01 +0000 Subject: [PATCH 68/94] Bump the go-dependencies group with 2 updates Bumps the go-dependencies group with 2 updates: [golang.org/x/sys](https://github.com/golang/sys) and [golang.org/x/term](https://github.com/golang/term). Updates `golang.org/x/sys` from 0.28.0 to 0.29.0 - [Commits](https://github.com/golang/sys/compare/v0.28.0...v0.29.0) Updates `golang.org/x/term` from 0.27.0 to 0.28.0 - [Commits](https://github.com/golang/term/compare/v0.27.0...v0.28.0) --- updated-dependencies: - dependency-name: golang.org/x/sys dependency-type: indirect update-type: version-update:semver-minor dependency-group: go-dependencies - dependency-name: golang.org/x/term dependency-type: indirect update-type: version-update:semver-minor dependency-group: go-dependencies ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 30744f5..3ee8cf7 100644 --- a/go.mod +++ b/go.mod @@ -28,8 +28,8 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/thlib/go-timezone-local v0.0.6 // indirect golang.org/x/image v0.23.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/term v0.27.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 777916e..49dd276 100644 --- a/go.sum +++ b/go.sum @@ -54,10 +54,10 @@ golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= From 8bd17ccfe2f183992e2d8d1ee90d0582230c1337 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 18:44:35 +0000 Subject: [PATCH 69/94] Bump github.com/cli/go-gh/v2 in the go-dependencies group Bumps the go-dependencies group with 1 update: [github.com/cli/go-gh/v2](https://github.com/cli/go-gh). Updates `github.com/cli/go-gh/v2` from 2.11.1 to 2.11.2 - [Release notes](https://github.com/cli/go-gh/releases) - [Commits](https://github.com/cli/go-gh/compare/v2.11.1...v2.11.2) --- updated-dependencies: - dependency-name: github.com/cli/go-gh/v2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: go-dependencies ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3ee8cf7..96e4328 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22 toolchain go1.23.3 require ( - github.com/cli/go-gh/v2 v2.11.1 + github.com/cli/go-gh/v2 v2.11.2 github.com/fogleman/gg v1.3.0 github.com/spf13/cobra v1.8.1 ) diff --git a/go.sum b/go.sum index 49dd276..a8d94af 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo= github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk= -github.com/cli/go-gh/v2 v2.11.1 h1:amAyfqMWQTBdue8iTmDUegGZK7c8kk6WCxD9l/wLtGI= -github.com/cli/go-gh/v2 v2.11.1/go.mod h1:MeRoKzXff3ygHu7zP+NVTT+imcHW6p3tpuxHAzRM2xE= +github.com/cli/go-gh/v2 v2.11.2 h1:oad1+sESTPNTiTvh3I3t8UmxuovNDxhwLzeMHk45Q9w= +github.com/cli/go-gh/v2 v2.11.2/go.mod h1:vVFhi3TfjseIW26ED9itAR8gQK0aVThTm8sYrsZ5QTI= github.com/cli/safeexec v1.0.1 h1:e/C79PbXF4yYTN/wauC4tviMxEV13BwljGj0N9j+N00= github.com/cli/safeexec v1.0.1/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q= github.com/cli/shurcooL-graphql v0.0.4 h1:6MogPnQJLjKkaXPyGqPRXOI2qCsQdqNfUY1QSJu2GuY= From 65d929cc1dbb37264fa5ad83da6f3ebaf98933fd Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Mon, 13 Jan 2025 15:56:17 -0800 Subject: [PATCH 70/94] Update .github/workflows/release.yml Co-authored-by: Andy Feller --- .github/workflows/release.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0c7089a..11eaa16 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,6 +16,8 @@ jobs: - uses: cli/gh-extension-precompile@561b19deda1228a0edf856c3325df87416f8c9bd # v2.0.0 with: release_android: true - android_sdk_version: 30 # Android 11.0 + # For more information see: https://developer.android.com/tools/releases/platforms + # At the time of writing, 35 had just come out of beta and 34 seems to be the most stable. + android_sdk_version: 34 generate_attestations: true go_version_file: go.mod From a9797f87db4b946b09a8f2b247255fcb8493032a Mon Sep 17 00:00:00 2001 From: 8-prime Date: Tue, 14 Jan 2025 13:14:28 +0100 Subject: [PATCH 71/94] Fix vertex ordering --- stl/geometry/shapes.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/stl/geometry/shapes.go b/stl/geometry/shapes.go index 53e6a6c..cbe5958 100644 --- a/stl/geometry/shapes.go +++ b/stl/geometry/shapes.go @@ -71,12 +71,12 @@ func createBox(x, y, z, width, height, depth float64) ([]types.Triangle, error) vertices := make([]types.Point3D, 8) // Pre-allocate vertices array quads := [6][4]int{ - {0, 1, 2, 3}, // front - {5, 4, 7, 6}, // back - {4, 0, 3, 7}, // left - {1, 5, 6, 2}, // right - {3, 2, 6, 7}, // top - {4, 5, 1, 0}, // bottom + {0, 3, 2, 1}, // front (viewed from front) + {5, 6, 7, 4}, // back (viewed from back) + {4, 7, 3, 0}, // left (viewed from left) + {1, 2, 6, 5}, // right (viewed from right) + {3, 7, 6, 2}, // top (viewed from top) + {4, 0, 1, 5}, // bottom (viewed from bottom) } // Fill vertices array From 57b00ca3ec915895512ae224c9af5e8bca9fb3a5 Mon Sep 17 00:00:00 2001 From: "Christopher W. Blake" Date: Mon, 20 Jan 2025 21:57:25 +0000 Subject: [PATCH 72/94] fix: Rendered username and year are low resolution causing poor prints. --- stl/geometry/text.go | 188 ++++++++++++++++++++++++------------------- 1 file changed, 104 insertions(+), 84 deletions(-) diff --git a/stl/geometry/text.go b/stl/geometry/text.go index 65e83d0..0c8a8d9 100644 --- a/stl/geometry/text.go +++ b/stl/geometry/text.go @@ -19,15 +19,6 @@ type renderConfig struct { depth float64 } -// TextConfig holds parameters for text rendering -type textRenderConfig struct { - renderConfig - text string - contextWidth int - contextHeight int - fontSize float64 -} - // ImageConfig holds parameters for image rendering type imageRenderConfig struct { renderConfig @@ -36,29 +27,22 @@ type imageRenderConfig struct { } const ( - imagePosition = 0.025 - usernameOffset = -0.01 - yearPosition = 0.77 - - defaultContextWidth = 800 - defaultContextHeight = 200 - textVoxelSize = 1.0 - textDepthOffset = 2.0 - frontEmbedDepth = 1.5 - - usernameContextWidth = 1000 - usernameContextHeight = 200 - usernameFontSize = 48.0 - usernameZOffset = 0.7 - - yearContextWidth = 800 - yearContextHeight = 200 - yearFontSize = 56.0 - yearZOffset = 0.4 - - defaultImageHeight = 9.0 - defaultImageScale = 0.8 - imageLeftMargin = 10.0 + skylineFaceWidth = 142.5 // millimeters? + skylineFaceHeight = 10.0 // millimeters? + skylineResolutionWidth = 2000 // voxels + + logoPosition = 0.025 + logoHeight = 9.0 + logoScale = 0.8 + logoLeftMargin = 10.0 + + voxelDepth = 1.0 + + usernameFontSize = 120.0 + usernameLeftOffset = 0.1 + + yearFontSize = 100.0 + yearLeftOffset = 0.85 ) // Create3DText generates 3D text geometry for the username and year. @@ -67,40 +51,20 @@ func Create3DText(username string, year string, innerWidth, baseHeight float64) username = "anonymous" } - usernameConfig := textRenderConfig{ - renderConfig: renderConfig{ - startX: innerWidth * usernameOffset, - startY: -textDepthOffset / 2, - startZ: baseHeight * usernameZOffset, - voxelScale: textVoxelSize, - depth: frontEmbedDepth, - }, - text: username, - contextWidth: usernameContextWidth, - contextHeight: usernameContextHeight, - fontSize: usernameFontSize, - } - - yearConfig := textRenderConfig{ - renderConfig: renderConfig{ - startX: innerWidth * yearPosition, - startY: -textDepthOffset / 2, - startZ: baseHeight * yearZOffset, - voxelScale: textVoxelSize * 0.75, - depth: frontEmbedDepth, - }, - text: year, - contextWidth: yearContextWidth, - contextHeight: yearContextHeight, - fontSize: yearFontSize, - } - - usernameTriangles, err := renderText(usernameConfig) + usernameTriangles, err := renderText( + username, + usernameLeftOffset, + usernameFontSize, + ) if err != nil { return nil, err } - yearTriangles, err := renderText(yearConfig) + yearTriangles, err := renderText( + year, + yearLeftOffset, + yearFontSize, + ) if err != nil { return nil, err } @@ -108,9 +72,22 @@ func Create3DText(username string, year string, innerWidth, baseHeight float64) return append(usernameTriangles, yearTriangles...), nil } -// renderText generates 3D geometry for the given text configuration. -func renderText(config textRenderConfig) ([]types.Triangle, error) { - dc := gg.NewContext(config.contextWidth, config.contextHeight) +// renderText places text on the face of a skyline, offset from the left and vertically-aligned. +// The function takes the text to be displayed, offset from left, and font size. +// It returns an array of types.Triangle. +// +// Parameters: +// text (string): The text to be displayed on the skyline's front face. +// leftOffsetPercent (float64): The percentage distance from the left to start displaying the text. +// fontSize (float64): How large to make the text. Note: It scales with the skylineResolutionWidth. +// +// Returns: +// ([]types.Triangle, error): A slice of triangles representing text. +func renderText(text string, leftOffsetPercent float64, fontSize float64) ([]types.Triangle, error) { + // Create a rendering context for the face of the skyline + faceWidthRes := skylineResolutionWidth + faceHeightRes := int(float64(faceWidthRes) * skylineFaceHeight/skylineFaceWidth) + dc := gg.NewContext(faceWidthRes, faceHeightRes) // Get temporary font file fontPath, cleanup, err := writeTempFont(PrimaryFont) @@ -122,30 +99,33 @@ func renderText(config textRenderConfig) ([]types.Triangle, error) { } } - if err := dc.LoadFontFace(fontPath, config.fontSize); err != nil { + if err := dc.LoadFontFace(fontPath, fontSize); err != nil { return nil, errors.New(errors.IOError, "failed to load font", err) } dc.SetRGB(0, 0, 0) dc.Clear() dc.SetRGB(1, 1, 1) - dc.DrawStringAnchored(config.text, float64(config.contextWidth)/8, float64(config.contextHeight)/2, 0.0, 0.5) var triangles []types.Triangle - for y := 0; y < config.contextHeight; y++ { - for x := 0; x < config.contextWidth; x++ { + // Draw pixelated text in image at desired location + dc.DrawStringAnchored( + text, + float64(faceWidthRes)*leftOffsetPercent, // Offset from left + float64(faceHeightRes)*0.5, // Offset from top + 0.0, // Left aligned + 0.5, // Vertically aligned + ) + + // Transfer image pixels onto face of skyline as voxels + for x := 0; x < faceWidthRes; x++ { + for y := 0; y < faceHeightRes; y++ { if isPixelActive(dc, x, y) { - xPos := config.startX + float64(x)*config.voxelScale/8 - zPos := config.startZ - float64(y)*config.voxelScale/8 - - voxel, err := CreateCube( - xPos, - config.startY, - zPos, - config.voxelScale/2, - config.depth, - config.voxelScale/2, + voxel, err := createVoxelOnFace( + float64(x), + float64(y), + voxelDepth, ) if err != nil { return nil, errors.New(errors.STLError, "failed to create cube", err) @@ -161,6 +141,46 @@ func renderText(config textRenderConfig) ([]types.Triangle, error) { return triangles, nil } +// createVoxelOnFace creates a voxel on the face of a skyline by generating a cube at the specified coordinates. +// The function takes in the x, y coordinates and height. +// It returns a slice of types.Triangle representing the cube and an error if the cube creation fails. +// +// Parameters: +// x (float64): The x-coordinate on the skyline face (left to right). +// y (float64): The y-coordinate on the skyline face (top to bottom). +// height (float64): Distance coming out of the face. +// +// Returns: +// ([]types.Triangle, error): A slice of triangles representing the cube and an error if any. +func createVoxelOnFace(x float64, y float64, height float64) ([]types.Triangle, error) { + // Mapping resolution + xResolution := float64(skylineResolutionWidth) + yResolution := xResolution * skylineFaceHeight / skylineFaceWidth + + // Pixel size + voxelSize := 1.0; + + // Scale coordinate to face resolution + x = (x / xResolution) * skylineFaceWidth + y = (y / yResolution) * skylineFaceHeight + voxelSizeX := (voxelSize / xResolution) * skylineFaceWidth; + voxelSizeY := (voxelSize / yResolution) * skylineFaceHeight; + + cube, err := CreateCube( + // Location (from top left corner of skyline face) + x, // x - Left to right + -height, // y - Negative comes out of face. Positive goes into face. + -voxelSizeY - y, // z - Bottom to top + + // Size + voxelSizeX, // x length - left to right from specified point + height, // thickness - distance coming out of face + voxelSizeY, // y length - bottom to top from specified point + ) + + return cube, err +} + // GenerateImageGeometry creates 3D geometry from the embedded logo image. func GenerateImageGeometry(innerWidth, baseHeight float64) ([]types.Triangle, error) { // Get temporary image file @@ -171,14 +191,14 @@ func GenerateImageGeometry(innerWidth, baseHeight float64) ([]types.Triangle, er config := imageRenderConfig{ renderConfig: renderConfig{ - startX: innerWidth * imagePosition, - startY: -frontEmbedDepth / 2.0, + startX: innerWidth * logoPosition, + startY: -voxelDepth / 2.0, startZ: -0.85 * baseHeight, - voxelScale: defaultImageScale, - depth: frontEmbedDepth, + voxelScale: logoScale, + depth: voxelDepth, }, imagePath: imgPath, - height: defaultImageHeight, + height: logoHeight, } defer cleanup() From af2ed2d5c13ad157d8387ff15fda75f4cbae4aff Mon Sep 17 00:00:00 2001 From: "Christopher W. Blake" Date: Tue, 21 Jan 2025 14:40:29 +0000 Subject: [PATCH 73/94] refactor: Simplify code for displaying logo. --- stl/geometry/text.go | 75 +++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 40 deletions(-) diff --git a/stl/geometry/text.go b/stl/geometry/text.go index 0c8a8d9..3685e8c 100644 --- a/stl/geometry/text.go +++ b/stl/geometry/text.go @@ -30,19 +30,18 @@ const ( skylineFaceWidth = 142.5 // millimeters? skylineFaceHeight = 10.0 // millimeters? skylineResolutionWidth = 2000 // voxels - - logoPosition = 0.025 - logoHeight = 9.0 - logoScale = 0.8 - logoLeftMargin = 10.0 voxelDepth = 1.0 + + logoScale = 0.4 + logoTopOffset = 0.15 // Percent + logoLeftOffset = 0.03 // Percent usernameFontSize = 120.0 - usernameLeftOffset = 0.1 + usernameLeftOffset = 0.1 // Percent yearFontSize = 100.0 - yearLeftOffset = 0.85 + yearLeftOffset = 0.85 // Percent ) // Create3DText generates 3D text geometry for the username and year. @@ -189,26 +188,26 @@ func GenerateImageGeometry(innerWidth, baseHeight float64) ([]types.Triangle, er return nil, err } - config := imageRenderConfig{ - renderConfig: renderConfig{ - startX: innerWidth * logoPosition, - startY: -voxelDepth / 2.0, - startZ: -0.85 * baseHeight, - voxelScale: logoScale, - depth: voxelDepth, - }, - imagePath: imgPath, - height: logoHeight, - } - defer cleanup() - return renderImage(config) + return renderImage( + imgPath, + logoScale, + voxelDepth, + logoLeftOffset, + logoTopOffset, + ) } // renderImage generates 3D geometry for the given image configuration. -func renderImage(config imageRenderConfig) ([]types.Triangle, error) { - reader, err := os.Open(config.imagePath) +// func renderImage(config imageRenderConfig) ([]types.Triangle, error) { +func renderImage(filePath string, scale float64, height float64, leftOffsetPercent float64, topOffsetPercent float64) ([]types.Triangle, error) { + + faceWidthRes := skylineResolutionWidth + faceHeightRes := int(float64(faceWidthRes) * skylineFaceHeight/skylineFaceWidth) + + // Load image from file + reader, err := os.Open(filePath) if err != nil { return nil, errors.New(errors.IOError, "failed to open image", err) } @@ -219,34 +218,30 @@ func renderImage(config imageRenderConfig) ([]types.Triangle, error) { fmt.Println(closeErr) } }() - img, err := png.Decode(reader) if err != nil { return nil, errors.New(errors.IOError, "failed to decode PNG", err) } + // Get image size bounds := img.Bounds() - width := bounds.Max.X - height := bounds.Max.Y - - scale := config.height / float64(height) + logoWidth := bounds.Max.X + logoHeight := bounds.Max.Y + // Transfer image pixels onto face of skyline as voxels var triangles []types.Triangle - - for y := height - 1; y >= 0; y-- { - for x := 0; x < width; x++ { + for x := 0; x < logoWidth; x++ { + for y := logoHeight - 1; y >= 0; y-- { + // Get pixel color and alpha r, _, _, a := img.At(x, y).RGBA() + + // If pixel is active (white) and not fully transparent, create a voxel if a > 32768 && r > 32768 { - xPos := config.startX + float64(x)*config.voxelScale*scale - zPos := config.startZ + float64(height-1-y)*config.voxelScale*scale - - voxel, err := CreateCube( - xPos, - config.startY, - zPos, - config.voxelScale*scale, - config.depth, - config.voxelScale*scale, + + voxel, err := createVoxelOnFace( + (leftOffsetPercent * float64(faceWidthRes)) + float64(x)*logoScale, + (topOffsetPercent * float64(faceHeightRes)) + float64(y)*logoScale, + height, ) if err != nil { From beede5fc9917bdbd73e54ddff1b8dd41f95bdc1e Mon Sep 17 00:00:00 2001 From: "Christopher W. Blake" Date: Tue, 21 Jan 2025 15:30:10 +0000 Subject: [PATCH 74/94] refactor: Get base height and width using geometry package. --- stl/geometry/text.go | 69 +++++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 30 deletions(-) diff --git a/stl/geometry/text.go b/stl/geometry/text.go index 3685e8c..c2155f7 100644 --- a/stl/geometry/text.go +++ b/stl/geometry/text.go @@ -27,13 +27,10 @@ type imageRenderConfig struct { } const ( - skylineFaceWidth = 142.5 // millimeters? - skylineFaceHeight = 10.0 // millimeters? - skylineResolutionWidth = 2000 // voxels - - voxelDepth = 1.0 + baseWidthVoxelResolution = 2000 // Number of voxels across the skyline face + voxelDepth = 1.0 // Distance to come out of face - logoScale = 0.4 + logoScale = 0.4 // Percent logoTopOffset = 0.15 // Percent logoLeftOffset = 0.03 // Percent @@ -45,7 +42,7 @@ const ( ) // Create3DText generates 3D text geometry for the username and year. -func Create3DText(username string, year string, innerWidth, baseHeight float64) ([]types.Triangle, error) { +func Create3DText(username string, year string, baseWidth float64, baseHeight float64) ([]types.Triangle, error) { if username == "" { username = "anonymous" } @@ -54,6 +51,8 @@ func Create3DText(username string, year string, innerWidth, baseHeight float64) username, usernameLeftOffset, usernameFontSize, + baseWidth, + baseHeight, ) if err != nil { return nil, err @@ -63,6 +62,8 @@ func Create3DText(username string, year string, innerWidth, baseHeight float64) year, yearLeftOffset, yearFontSize, + baseWidth, + baseHeight, ) if err != nil { return nil, err @@ -78,17 +79,22 @@ func Create3DText(username string, year string, innerWidth, baseHeight float64) // Parameters: // text (string): The text to be displayed on the skyline's front face. // leftOffsetPercent (float64): The percentage distance from the left to start displaying the text. -// fontSize (float64): How large to make the text. Note: It scales with the skylineResolutionWidth. +// fontSize (float64): How large to make the text. Note: It scales with the baseWidthVoxelResolution. // // Returns: // ([]types.Triangle, error): A slice of triangles representing text. -func renderText(text string, leftOffsetPercent float64, fontSize float64) ([]types.Triangle, error) { +func renderText(text string, leftOffsetPercent float64, fontSize float64, baseWidth float64, baseHeight float64) ([]types.Triangle, error) { // Create a rendering context for the face of the skyline - faceWidthRes := skylineResolutionWidth - faceHeightRes := int(float64(faceWidthRes) * skylineFaceHeight/skylineFaceWidth) + faceWidthRes := baseWidthVoxelResolution + faceHeightRes := int(float64(faceWidthRes) * baseHeight/baseWidth) + + // Create image representing the skyline face dc := gg.NewContext(faceWidthRes, faceHeightRes) + dc.SetRGB(0, 0, 0) + dc.Clear() + dc.SetRGB(1, 1, 1) - // Get temporary font file + // Load font into context fontPath, cleanup, err := writeTempFont(PrimaryFont) if err != nil { // Try fallback font @@ -97,27 +103,23 @@ func renderText(text string, leftOffsetPercent float64, fontSize float64) ([]typ return nil, errors.New(errors.IOError, "failed to load any fonts", err) } } - if err := dc.LoadFontFace(fontPath, fontSize); err != nil { return nil, errors.New(errors.IOError, "failed to load font", err) } - dc.SetRGB(0, 0, 0) - dc.Clear() - dc.SetRGB(1, 1, 1) - + // Draw text on image at desired location var triangles []types.Triangle // Draw pixelated text in image at desired location dc.DrawStringAnchored( text, - float64(faceWidthRes)*leftOffsetPercent, // Offset from left + float64(faceWidthRes)*leftOffsetPercent, // Offset from right float64(faceHeightRes)*0.5, // Offset from top 0.0, // Left aligned 0.5, // Vertically aligned ) - // Transfer image pixels onto face of skyline as voxels + // Convert context image pixels into voxels for x := 0; x < faceWidthRes; x++ { for y := 0; y < faceHeightRes; y++ { if isPixelActive(dc, x, y) { @@ -125,6 +127,8 @@ func renderText(text string, leftOffsetPercent float64, fontSize float64) ([]typ float64(x), float64(y), voxelDepth, + baseWidth, + baseHeight, ) if err != nil { return nil, errors.New(errors.STLError, "failed to create cube", err) @@ -151,19 +155,19 @@ func renderText(text string, leftOffsetPercent float64, fontSize float64) ([]typ // // Returns: // ([]types.Triangle, error): A slice of triangles representing the cube and an error if any. -func createVoxelOnFace(x float64, y float64, height float64) ([]types.Triangle, error) { +func createVoxelOnFace(x float64, y float64, height float64, baseWidth float64, baseHeight float64) ([]types.Triangle, error) { // Mapping resolution - xResolution := float64(skylineResolutionWidth) - yResolution := xResolution * skylineFaceHeight / skylineFaceWidth + xResolution := float64(baseWidthVoxelResolution) + yResolution := xResolution * baseHeight / baseWidth // Pixel size voxelSize := 1.0; // Scale coordinate to face resolution - x = (x / xResolution) * skylineFaceWidth - y = (y / yResolution) * skylineFaceHeight - voxelSizeX := (voxelSize / xResolution) * skylineFaceWidth; - voxelSizeY := (voxelSize / yResolution) * skylineFaceHeight; + x = (x / xResolution) * baseWidth + y = (y / yResolution) * baseHeight + voxelSizeX := (voxelSize / xResolution) * baseWidth; + voxelSizeY := (voxelSize / yResolution) * baseHeight; cube, err := CreateCube( // Location (from top left corner of skyline face) @@ -181,7 +185,7 @@ func createVoxelOnFace(x float64, y float64, height float64) ([]types.Triangle, } // GenerateImageGeometry creates 3D geometry from the embedded logo image. -func GenerateImageGeometry(innerWidth, baseHeight float64) ([]types.Triangle, error) { +func GenerateImageGeometry(baseWidth float64, baseHeight float64) ([]types.Triangle, error) { // Get temporary image file imgPath, cleanup, err := getEmbeddedImage() if err != nil { @@ -196,15 +200,18 @@ func GenerateImageGeometry(innerWidth, baseHeight float64) ([]types.Triangle, er voxelDepth, logoLeftOffset, logoTopOffset, + baseWidth, + baseHeight, ) } // renderImage generates 3D geometry for the given image configuration. // func renderImage(config imageRenderConfig) ([]types.Triangle, error) { -func renderImage(filePath string, scale float64, height float64, leftOffsetPercent float64, topOffsetPercent float64) ([]types.Triangle, error) { +func renderImage(filePath string, scale float64, height float64, leftOffsetPercent float64, topOffsetPercent float64, baseWidth float64, baseHeight float64) ([]types.Triangle, error) { - faceWidthRes := skylineResolutionWidth - faceHeightRes := int(float64(faceWidthRes) * skylineFaceHeight/skylineFaceWidth) + // Get voxel resolution of base face + faceWidthRes := baseWidthVoxelResolution + faceHeightRes := int(float64(faceWidthRes) * baseHeight/baseWidth) // Load image from file reader, err := os.Open(filePath) @@ -242,6 +249,8 @@ func renderImage(filePath string, scale float64, height float64, leftOffsetPerce (leftOffsetPercent * float64(faceWidthRes)) + float64(x)*logoScale, (topOffsetPercent * float64(faceHeightRes)) + float64(y)*logoScale, height, + baseWidth, + baseHeight, ) if err != nil { From cc764a83cbb6b9e7b98cf5ccdcab044930c5da19 Mon Sep 17 00:00:00 2001 From: "Christopher W. Blake" Date: Tue, 21 Jan 2025 15:31:09 +0000 Subject: [PATCH 75/94] feat: Modify year text to be right justified --- stl/geometry/text.go | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/stl/geometry/text.go b/stl/geometry/text.go index c2155f7..aa3e8a0 100644 --- a/stl/geometry/text.go +++ b/stl/geometry/text.go @@ -35,10 +35,12 @@ const ( logoLeftOffset = 0.03 // Percent usernameFontSize = 120.0 + usernameJustification= "left" // "left", "center", "right" usernameLeftOffset = 0.1 // Percent yearFontSize = 100.0 - yearLeftOffset = 0.85 // Percent + yearJustification = "right" // "left", "center", "right" + yearLeftOffset = 0.97 // Percent ) // Create3DText generates 3D text geometry for the username and year. @@ -49,6 +51,7 @@ func Create3DText(username string, year string, baseWidth float64, baseHeight fl usernameTriangles, err := renderText( username, + usernameJustification, usernameLeftOffset, usernameFontSize, baseWidth, @@ -60,6 +63,7 @@ func Create3DText(username string, year string, baseWidth float64, baseHeight fl yearTriangles, err := renderText( year, + yearJustification, yearLeftOffset, yearFontSize, baseWidth, @@ -83,7 +87,7 @@ func Create3DText(username string, year string, baseWidth float64, baseHeight fl // // Returns: // ([]types.Triangle, error): A slice of triangles representing text. -func renderText(text string, leftOffsetPercent float64, fontSize float64, baseWidth float64, baseHeight float64) ([]types.Triangle, error) { +func renderText(text string, justification string, leftOffsetPercent float64, fontSize float64, baseWidth float64, baseHeight float64) ([]types.Triangle, error) { // Create a rendering context for the face of the skyline faceWidthRes := baseWidthVoxelResolution faceHeightRes := int(float64(faceWidthRes) * baseHeight/baseWidth) @@ -110,12 +114,22 @@ func renderText(text string, leftOffsetPercent float64, fontSize float64, baseWi // Draw text on image at desired location var triangles []types.Triangle - // Draw pixelated text in image at desired location + // Convert justification to a number + var justificationPercent float64 + switch justification { + case "center": + justificationPercent = 0.5 + case "right": + justificationPercent = 1.0 + default: + justificationPercent = 0.0 + } + dc.DrawStringAnchored( text, float64(faceWidthRes)*leftOffsetPercent, // Offset from right float64(faceHeightRes)*0.5, // Offset from top - 0.0, // Left aligned + justificationPercent, // Justification (0.0=left, 0.5=center, 1.0=right) 0.5, // Vertically aligned ) From 486e389ac3cf6652acaf4274524d861135f4b1e9 Mon Sep 17 00:00:00 2001 From: "Christopher W. Blake" Date: Tue, 21 Jan 2025 15:48:49 +0000 Subject: [PATCH 76/94] feat: Update unit tests to match method inputs --- stl/geometry/text_test.go | 62 +++++++++++++++------------------------ 1 file changed, 24 insertions(+), 38 deletions(-) diff --git a/stl/geometry/text_test.go b/stl/geometry/text_test.go index c1500a7..4be3ecc 100644 --- a/stl/geometry/text_test.go +++ b/stl/geometry/text_test.go @@ -13,10 +13,6 @@ import ( // TestCreate3DText verifies text geometry generation functionality. func TestCreate3DText(t *testing.T) { - // Skip tests if fonts are not available - if _, err := os.Stat(FallbackFont); err != nil { - t.Skip("Skipping text tests as font files are not available") - } t.Run("verify basic text mesh generation", func(t *testing.T) { triangles, err := Create3DText("test", "2023", 100.0, 5.0) @@ -63,47 +59,37 @@ func TestCreate3DText(t *testing.T) { // TestRenderText verifies internal text rendering functionality func TestRenderText(t *testing.T) { - // Skip if fonts not available - if _, err := os.Stat(FallbackFont); err != nil { - t.Skip("Skipping text tests as font files are not available") - } + t.Run("verify text renders", func(t *testing.T) { + triangles, err := renderText( + "Mona", // text + "left", // justification + 0.1, // leftOffsetPercent + 10.0, // fontSize + 200.0, // baseWidth + 10.0, // baseHeight + ) - t.Run("verify text config validation", func(t *testing.T) { - invalidConfig := textRenderConfig{ - renderConfig: renderConfig{ - startX: 0, - startY: 0, - startZ: 0, - voxelScale: 0, // Invalid scale - depth: 1, - }, - text: "test", - contextWidth: 100, - contextHeight: 100, - fontSize: 10, - } - _, err := renderText(invalidConfig) - if err == nil { - t.Error("Expected error for invalid text config") + if err != nil { + t.Fatalf("renderText failed: %v", err) + } + if len(triangles) == 0 { + t.Error("Expected non-zero triangles for rendered text") } }) } // TestRenderImage verifies internal image rendering functionality func TestRenderImage(t *testing.T) { - t.Run("verify invalid image path", func(t *testing.T) { - config := imageRenderConfig{ - renderConfig: renderConfig{ - startX: 0, - startY: 0, - startZ: 0, - voxelScale: 1, - depth: 1, - }, - imagePath: "nonexistent.png", - height: 10, - } - _, err := renderImage(config) + t.Run("verify invalid image", func(t *testing.T) { + _, err := renderImage( + "nonexistent.png", // filePath + 0.5, // scale + 100.0, // height + 0.1, // leftOffsetPercent + 0.1, // topOffsetPercent + 200.0, // baseWidth + 10.0, // baseHeight + ) if err == nil { t.Error("Expected error for invalid image path") } From d220b6cfdb04bd7b1082cfd52b1b31c7cfed6c2e Mon Sep 17 00:00:00 2001 From: "Christopher W. Blake" Date: Tue, 21 Jan 2025 16:13:43 +0000 Subject: [PATCH 77/94] chore: cleanup from linting results --- stl/geometry/text.go | 91 +++++++++++++++++++------------------------- 1 file changed, 39 insertions(+), 52 deletions(-) diff --git a/stl/geometry/text.go b/stl/geometry/text.go index aa3e8a0..bee5245 100644 --- a/stl/geometry/text.go +++ b/stl/geometry/text.go @@ -10,37 +10,21 @@ import ( "github.com/github/gh-skyline/types" ) -// Common configuration for rendered elements -type renderConfig struct { - startX float64 - startY float64 - startZ float64 - voxelScale float64 - depth float64 -} - -// ImageConfig holds parameters for image rendering -type imageRenderConfig struct { - renderConfig - imagePath string - height float64 -} - const ( baseWidthVoxelResolution = 2000 // Number of voxels across the skyline face - voxelDepth = 1.0 // Distance to come out of face + voxelDepth = 1.0 // Distance to come out of face - logoScale = 0.4 // Percent + logoScale = 0.4 // Percent logoTopOffset = 0.15 // Percent logoLeftOffset = 0.03 // Percent - - usernameFontSize = 120.0 - usernameJustification= "left" // "left", "center", "right" - usernameLeftOffset = 0.1 // Percent - - yearFontSize = 100.0 - yearJustification = "right" // "left", "center", "right" - yearLeftOffset = 0.97 // Percent + + usernameFontSize = 120.0 + usernameJustification = "left" // "left", "center", "right" + usernameLeftOffset = 0.1 // Percent + + yearFontSize = 100.0 + yearJustification = "right" // "left", "center", "right" + yearLeftOffset = 0.97 // Percent ) // Create3DText generates 3D text geometry for the username and year. @@ -81,17 +65,19 @@ func Create3DText(username string, year string, baseWidth float64, baseHeight fl // It returns an array of types.Triangle. // // Parameters: -// text (string): The text to be displayed on the skyline's front face. -// leftOffsetPercent (float64): The percentage distance from the left to start displaying the text. -// fontSize (float64): How large to make the text. Note: It scales with the baseWidthVoxelResolution. +// +// text (string): The text to be displayed on the skyline's front face. +// leftOffsetPercent (float64): The percentage distance from the left to start displaying the text. +// fontSize (float64): How large to make the text. Note: It scales with the baseWidthVoxelResolution. // // Returns: -// ([]types.Triangle, error): A slice of triangles representing text. +// +// ([]types.Triangle, error): A slice of triangles representing text. func renderText(text string, justification string, leftOffsetPercent float64, fontSize float64, baseWidth float64, baseHeight float64) ([]types.Triangle, error) { // Create a rendering context for the face of the skyline faceWidthRes := baseWidthVoxelResolution - faceHeightRes := int(float64(faceWidthRes) * baseHeight/baseWidth) - + faceHeightRes := int(float64(faceWidthRes) * baseHeight / baseWidth) + // Create image representing the skyline face dc := gg.NewContext(faceWidthRes, faceHeightRes) dc.SetRGB(0, 0, 0) @@ -128,9 +114,9 @@ func renderText(text string, justification string, leftOffsetPercent float64, fo dc.DrawStringAnchored( text, float64(faceWidthRes)*leftOffsetPercent, // Offset from right - float64(faceHeightRes)*0.5, // Offset from top - justificationPercent, // Justification (0.0=left, 0.5=center, 1.0=right) - 0.5, // Vertically aligned + float64(faceHeightRes)*0.5, // Offset from top + justificationPercent, // Justification (0.0=left, 0.5=center, 1.0=right) + 0.5, // Vertically aligned ) // Convert context image pixels into voxels @@ -163,35 +149,37 @@ func renderText(text string, justification string, leftOffsetPercent float64, fo // It returns a slice of types.Triangle representing the cube and an error if the cube creation fails. // // Parameters: -// x (float64): The x-coordinate on the skyline face (left to right). -// y (float64): The y-coordinate on the skyline face (top to bottom). -// height (float64): Distance coming out of the face. +// +// x (float64): The x-coordinate on the skyline face (left to right). +// y (float64): The y-coordinate on the skyline face (top to bottom). +// height (float64): Distance coming out of the face. // // Returns: -// ([]types.Triangle, error): A slice of triangles representing the cube and an error if any. +// +// ([]types.Triangle, error): A slice of triangles representing the cube and an error if any. func createVoxelOnFace(x float64, y float64, height float64, baseWidth float64, baseHeight float64) ([]types.Triangle, error) { // Mapping resolution xResolution := float64(baseWidthVoxelResolution) yResolution := xResolution * baseHeight / baseWidth // Pixel size - voxelSize := 1.0; + voxelSize := 1.0 // Scale coordinate to face resolution x = (x / xResolution) * baseWidth y = (y / yResolution) * baseHeight - voxelSizeX := (voxelSize / xResolution) * baseWidth; - voxelSizeY := (voxelSize / yResolution) * baseHeight; + voxelSizeX := (voxelSize / xResolution) * baseWidth + voxelSizeY := (voxelSize / yResolution) * baseHeight cube, err := CreateCube( // Location (from top left corner of skyline face) - x, // x - Left to right - -height, // y - Negative comes out of face. Positive goes into face. - -voxelSizeY - y, // z - Bottom to top - + x, // x - Left to right + -height, // y - Negative comes out of face. Positive goes into face. + -voxelSizeY-y, // z - Bottom to top + // Size voxelSizeX, // x length - left to right from specified point - height, // thickness - distance coming out of face + height, // thickness - distance coming out of face voxelSizeY, // y length - bottom to top from specified point ) @@ -220,13 +208,12 @@ func GenerateImageGeometry(baseWidth float64, baseHeight float64) ([]types.Trian } // renderImage generates 3D geometry for the given image configuration. -// func renderImage(config imageRenderConfig) ([]types.Triangle, error) { func renderImage(filePath string, scale float64, height float64, leftOffsetPercent float64, topOffsetPercent float64, baseWidth float64, baseHeight float64) ([]types.Triangle, error) { // Get voxel resolution of base face faceWidthRes := baseWidthVoxelResolution - faceHeightRes := int(float64(faceWidthRes) * baseHeight/baseWidth) - + faceHeightRes := int(float64(faceWidthRes) * baseHeight / baseWidth) + // Load image from file reader, err := os.Open(filePath) if err != nil { @@ -260,8 +247,8 @@ func renderImage(filePath string, scale float64, height float64, leftOffsetPerce if a > 32768 && r > 32768 { voxel, err := createVoxelOnFace( - (leftOffsetPercent * float64(faceWidthRes)) + float64(x)*logoScale, - (topOffsetPercent * float64(faceHeightRes)) + float64(y)*logoScale, + (leftOffsetPercent*float64(faceWidthRes))+float64(x)*scale, + (topOffsetPercent*float64(faceHeightRes))+float64(y)*scale, height, baseWidth, baseHeight, From 6a347bc4613b4fb22371b0461db032d245355911 Mon Sep 17 00:00:00 2001 From: "Christopher W. Blake" Date: Tue, 21 Jan 2025 16:17:31 +0000 Subject: [PATCH 78/94] chore: cleanup from linting results --- stl/geometry/text_test.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/stl/geometry/text_test.go b/stl/geometry/text_test.go index 4be3ecc..229487a 100644 --- a/stl/geometry/text_test.go +++ b/stl/geometry/text_test.go @@ -63,10 +63,10 @@ func TestRenderText(t *testing.T) { triangles, err := renderText( "Mona", // text "left", // justification - 0.1, // leftOffsetPercent - 10.0, // fontSize - 200.0, // baseWidth - 10.0, // baseHeight + 0.1, // leftOffsetPercent + 10.0, // fontSize + 200.0, // baseWidth + 10.0, // baseHeight ) if err != nil { @@ -83,12 +83,12 @@ func TestRenderImage(t *testing.T) { t.Run("verify invalid image", func(t *testing.T) { _, err := renderImage( "nonexistent.png", // filePath - 0.5, // scale - 100.0, // height - 0.1, // leftOffsetPercent - 0.1, // topOffsetPercent - 200.0, // baseWidth - 10.0, // baseHeight + 0.5, // scale + 100.0, // height + 0.1, // leftOffsetPercent + 0.1, // topOffsetPercent + 200.0, // baseWidth + 10.0, // baseHeight ) if err == nil { t.Error("Expected error for invalid image path") From aa9ca4311560e121fd922c736470d59c2067845c Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Thu, 23 Jan 2025 21:02:25 +0000 Subject: [PATCH 79/94] refactor: move several packages to internal folder --- {ascii => internal/ascii}/block.go | 0 {ascii => internal/ascii}/block_test.go | 0 {ascii => internal/ascii}/generator.go | 2 +- {ascii => internal/ascii}/generator_test.go | 2 +- {ascii => internal/ascii}/text.go | 0 {ascii => internal/ascii}/text_test.go | 0 {errors => internal/errors}/errors.go | 0 {errors => internal/errors}/errors_test.go | 0 {github => internal/github}/client.go | 4 ++-- {github => internal/github}/client_test.go | 6 +++--- {logger => internal/logger}/logger.go | 0 {logger => internal/logger}/logger_test.go | 0 {stl => internal/stl}/generator.go | 8 ++++---- {stl => internal/stl}/generator_test.go | 2 +- {stl => internal/stl}/geometry/assets.go | 2 +- .../stl}/geometry/assets/invertocat.png | Bin .../stl}/geometry/assets/monasans-medium.ttf | Bin .../stl}/geometry/assets/monasans-regular.ttf | Bin {stl => internal/stl}/geometry/assets_test.go | 0 {stl => internal/stl}/geometry/geometry.go | 2 +- {stl => internal/stl}/geometry/geometry_test.go | 2 +- {stl => internal/stl}/geometry/shapes.go | 4 ++-- {stl => internal/stl}/geometry/shapes_test.go | 2 +- {stl => internal/stl}/geometry/text.go | 4 ++-- {stl => internal/stl}/geometry/text_test.go | 0 {stl => internal/stl}/geometry/vector.go | 4 ++-- {stl => internal/stl}/geometry/vector_test.go | 2 +- {stl => internal/stl}/stl.go | 6 +++--- {stl => internal/stl}/stl_test.go | 2 +- {testutil => internal/testutil}/fixtures/github.go | 2 +- {testutil => internal/testutil}/mocks/github.go | 4 ++-- {types => internal/types}/types.go | 0 {types => internal/types}/types_test.go | 0 main.go | 12 ++++++------ main_test.go | 6 +++--- 35 files changed, 39 insertions(+), 39 deletions(-) rename {ascii => internal/ascii}/block.go (100%) rename {ascii => internal/ascii}/block_test.go (100%) rename {ascii => internal/ascii}/generator.go (98%) rename {ascii => internal/ascii}/generator_test.go (99%) rename {ascii => internal/ascii}/text.go (100%) rename {ascii => internal/ascii}/text_test.go (100%) rename {errors => internal/errors}/errors.go (100%) rename {errors => internal/errors}/errors_test.go (100%) rename {github => internal/github}/client.go (97%) rename {github => internal/github}/client_test.go (97%) rename {logger => internal/logger}/logger.go (100%) rename {logger => internal/logger}/logger_test.go (100%) rename {stl => internal/stl}/generator.go (98%) rename {stl => internal/stl}/generator_test.go (99%) rename {stl => internal/stl}/geometry/assets.go (98%) rename {stl => internal/stl}/geometry/assets/invertocat.png (100%) rename {stl => internal/stl}/geometry/assets/monasans-medium.ttf (100%) rename {stl => internal/stl}/geometry/assets/monasans-regular.ttf (100%) rename {stl => internal/stl}/geometry/assets_test.go (100%) rename {stl => internal/stl}/geometry/geometry.go (98%) rename {stl => internal/stl}/geometry/geometry_test.go (98%) rename {stl => internal/stl}/geometry/shapes.go (97%) rename {stl => internal/stl}/geometry/shapes_test.go (99%) rename {stl => internal/stl}/geometry/text.go (98%) rename {stl => internal/stl}/geometry/text_test.go (100%) rename {stl => internal/stl}/geometry/vector.go (95%) rename {stl => internal/stl}/geometry/vector_test.go (98%) rename {stl => internal/stl}/stl.go (97%) rename {stl => internal/stl}/stl_test.go (98%) rename {testutil => internal/testutil}/fixtures/github.go (96%) rename {testutil => internal/testutil}/mocks/github.go (94%) rename {types => internal/types}/types.go (100%) rename {types => internal/types}/types_test.go (100%) diff --git a/ascii/block.go b/internal/ascii/block.go similarity index 100% rename from ascii/block.go rename to internal/ascii/block.go diff --git a/ascii/block_test.go b/internal/ascii/block_test.go similarity index 100% rename from ascii/block_test.go rename to internal/ascii/block_test.go diff --git a/ascii/generator.go b/internal/ascii/generator.go similarity index 98% rename from ascii/generator.go rename to internal/ascii/generator.go index 87954dc..1044619 100644 --- a/ascii/generator.go +++ b/internal/ascii/generator.go @@ -7,7 +7,7 @@ import ( "strings" "time" - "github.com/github/gh-skyline/types" + "github.com/github/gh-skyline/internal/types" ) // ErrInvalidGrid is returned when the contribution grid is invalid diff --git a/ascii/generator_test.go b/internal/ascii/generator_test.go similarity index 99% rename from ascii/generator_test.go rename to internal/ascii/generator_test.go index 4ee56c9..7cdaf8c 100644 --- a/ascii/generator_test.go +++ b/internal/ascii/generator_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/github/gh-skyline/types" + "github.com/github/gh-skyline/internal/types" ) func TestGenerateASCII(t *testing.T) { diff --git a/ascii/text.go b/internal/ascii/text.go similarity index 100% rename from ascii/text.go rename to internal/ascii/text.go diff --git a/ascii/text_test.go b/internal/ascii/text_test.go similarity index 100% rename from ascii/text_test.go rename to internal/ascii/text_test.go diff --git a/errors/errors.go b/internal/errors/errors.go similarity index 100% rename from errors/errors.go rename to internal/errors/errors.go diff --git a/errors/errors_test.go b/internal/errors/errors_test.go similarity index 100% rename from errors/errors_test.go rename to internal/errors/errors_test.go diff --git a/github/client.go b/internal/github/client.go similarity index 97% rename from github/client.go rename to internal/github/client.go index 54e641b..8694f44 100644 --- a/github/client.go +++ b/internal/github/client.go @@ -6,8 +6,8 @@ import ( "fmt" "time" - "github.com/github/gh-skyline/errors" - "github.com/github/gh-skyline/types" + "github.com/github/gh-skyline/internal/errors" + "github.com/github/gh-skyline/internal/types" ) // APIClient interface defines the methods we need from the client diff --git a/github/client_test.go b/internal/github/client_test.go similarity index 97% rename from github/client_test.go rename to internal/github/client_test.go index 1d822ad..c83ede9 100644 --- a/github/client_test.go +++ b/internal/github/client_test.go @@ -4,9 +4,9 @@ import ( "testing" "time" - "github.com/github/gh-skyline/errors" - "github.com/github/gh-skyline/testutil/mocks" - "github.com/github/gh-skyline/types" + "github.com/github/gh-skyline/internal/errors" + "github.com/github/gh-skyline/internal/testutil/mocks" + "github.com/github/gh-skyline/internal/types" ) func TestGetAuthenticatedUser(t *testing.T) { diff --git a/logger/logger.go b/internal/logger/logger.go similarity index 100% rename from logger/logger.go rename to internal/logger/logger.go diff --git a/logger/logger_test.go b/internal/logger/logger_test.go similarity index 100% rename from logger/logger_test.go rename to internal/logger/logger_test.go diff --git a/stl/generator.go b/internal/stl/generator.go similarity index 98% rename from stl/generator.go rename to internal/stl/generator.go index 98cde8b..8dfb5bb 100644 --- a/stl/generator.go +++ b/internal/stl/generator.go @@ -4,10 +4,10 @@ import ( "fmt" "sync" - "github.com/github/gh-skyline/errors" - "github.com/github/gh-skyline/logger" - "github.com/github/gh-skyline/stl/geometry" - "github.com/github/gh-skyline/types" + "github.com/github/gh-skyline/internal/errors" + "github.com/github/gh-skyline/internal/logger" + "github.com/github/gh-skyline/internal/stl/geometry" + "github.com/github/gh-skyline/internal/types" ) // GenerateSTL creates a 3D model from GitHub contribution data and writes it to an STL file. diff --git a/stl/generator_test.go b/internal/stl/generator_test.go similarity index 99% rename from stl/generator_test.go rename to internal/stl/generator_test.go index 3ad8b17..eea9421 100644 --- a/stl/generator_test.go +++ b/internal/stl/generator_test.go @@ -7,7 +7,7 @@ import ( "sync" "testing" - "github.com/github/gh-skyline/types" + "github.com/github/gh-skyline/internal/types" ) // Test data setup diff --git a/stl/geometry/assets.go b/internal/stl/geometry/assets.go similarity index 98% rename from stl/geometry/assets.go rename to internal/stl/geometry/assets.go index 6f8023e..0f09b77 100644 --- a/stl/geometry/assets.go +++ b/internal/stl/geometry/assets.go @@ -5,7 +5,7 @@ import ( "fmt" "os" - "github.com/github/gh-skyline/errors" + "github.com/github/gh-skyline/internal/errors" ) //go:embed assets/* diff --git a/stl/geometry/assets/invertocat.png b/internal/stl/geometry/assets/invertocat.png similarity index 100% rename from stl/geometry/assets/invertocat.png rename to internal/stl/geometry/assets/invertocat.png diff --git a/stl/geometry/assets/monasans-medium.ttf b/internal/stl/geometry/assets/monasans-medium.ttf similarity index 100% rename from stl/geometry/assets/monasans-medium.ttf rename to internal/stl/geometry/assets/monasans-medium.ttf diff --git a/stl/geometry/assets/monasans-regular.ttf b/internal/stl/geometry/assets/monasans-regular.ttf similarity index 100% rename from stl/geometry/assets/monasans-regular.ttf rename to internal/stl/geometry/assets/monasans-regular.ttf diff --git a/stl/geometry/assets_test.go b/internal/stl/geometry/assets_test.go similarity index 100% rename from stl/geometry/assets_test.go rename to internal/stl/geometry/assets_test.go diff --git a/stl/geometry/geometry.go b/internal/stl/geometry/geometry.go similarity index 98% rename from stl/geometry/geometry.go rename to internal/stl/geometry/geometry.go index e804771..89f3583 100644 --- a/stl/geometry/geometry.go +++ b/internal/stl/geometry/geometry.go @@ -3,7 +3,7 @@ package geometry import ( "math" - "github.com/github/gh-skyline/types" + "github.com/github/gh-skyline/internal/types" ) // Model dimension constants define the basic measurements for the 3D model. diff --git a/stl/geometry/geometry_test.go b/internal/stl/geometry/geometry_test.go similarity index 98% rename from stl/geometry/geometry_test.go rename to internal/stl/geometry/geometry_test.go index fe9906a..63879eb 100644 --- a/stl/geometry/geometry_test.go +++ b/internal/stl/geometry/geometry_test.go @@ -4,7 +4,7 @@ import ( "math" "testing" - "github.com/github/gh-skyline/types" + "github.com/github/gh-skyline/internal/types" ) const ( diff --git a/stl/geometry/shapes.go b/internal/stl/geometry/shapes.go similarity index 97% rename from stl/geometry/shapes.go rename to internal/stl/geometry/shapes.go index cbe5958..5f731db 100644 --- a/stl/geometry/shapes.go +++ b/internal/stl/geometry/shapes.go @@ -2,8 +2,8 @@ package geometry import ( - "github.com/github/gh-skyline/errors" - "github.com/github/gh-skyline/types" + "github.com/github/gh-skyline/internal/errors" + "github.com/github/gh-skyline/internal/types" ) // CreateQuad creates two triangles forming a quadrilateral from four vertices. diff --git a/stl/geometry/shapes_test.go b/internal/stl/geometry/shapes_test.go similarity index 99% rename from stl/geometry/shapes_test.go rename to internal/stl/geometry/shapes_test.go index 31e7b4d..7b9b785 100644 --- a/stl/geometry/shapes_test.go +++ b/internal/stl/geometry/shapes_test.go @@ -4,7 +4,7 @@ import ( "math" "testing" - "github.com/github/gh-skyline/types" + "github.com/github/gh-skyline/internal/types" ) // TestCreateCuboidBase verifies cuboid base generation functionality. diff --git a/stl/geometry/text.go b/internal/stl/geometry/text.go similarity index 98% rename from stl/geometry/text.go rename to internal/stl/geometry/text.go index bee5245..e29127a 100644 --- a/stl/geometry/text.go +++ b/internal/stl/geometry/text.go @@ -6,8 +6,8 @@ import ( "os" "github.com/fogleman/gg" - "github.com/github/gh-skyline/errors" - "github.com/github/gh-skyline/types" + "github.com/github/gh-skyline/internal/errors" + "github.com/github/gh-skyline/internal/types" ) const ( diff --git a/stl/geometry/text_test.go b/internal/stl/geometry/text_test.go similarity index 100% rename from stl/geometry/text_test.go rename to internal/stl/geometry/text_test.go diff --git a/stl/geometry/vector.go b/internal/stl/geometry/vector.go similarity index 95% rename from stl/geometry/vector.go rename to internal/stl/geometry/vector.go index 5c6b897..cdbd9d5 100644 --- a/stl/geometry/vector.go +++ b/internal/stl/geometry/vector.go @@ -4,8 +4,8 @@ package geometry import ( "math" - "github.com/github/gh-skyline/errors" - "github.com/github/gh-skyline/types" + "github.com/github/gh-skyline/internal/errors" + "github.com/github/gh-skyline/internal/types" ) // validateVector checks if a vector's components are valid numbers diff --git a/stl/geometry/vector_test.go b/internal/stl/geometry/vector_test.go similarity index 98% rename from stl/geometry/vector_test.go rename to internal/stl/geometry/vector_test.go index e836ebd..c0759af 100644 --- a/stl/geometry/vector_test.go +++ b/internal/stl/geometry/vector_test.go @@ -4,7 +4,7 @@ import ( "math" "testing" - "github.com/github/gh-skyline/types" + "github.com/github/gh-skyline/internal/types" ) // Epsilon is declared in geometry_test.go diff --git a/stl/stl.go b/internal/stl/stl.go similarity index 97% rename from stl/stl.go rename to internal/stl/stl.go index 8d6c03f..a4fb5e8 100644 --- a/stl/stl.go +++ b/internal/stl/stl.go @@ -20,9 +20,9 @@ import ( "math" "os" - "github.com/github/gh-skyline/errors" - "github.com/github/gh-skyline/logger" - "github.com/github/gh-skyline/types" + "github.com/github/gh-skyline/internal/errors" + "github.com/github/gh-skyline/internal/logger" + "github.com/github/gh-skyline/internal/types" ) const ( diff --git a/stl/stl_test.go b/internal/stl/stl_test.go similarity index 98% rename from stl/stl_test.go rename to internal/stl/stl_test.go index cee1cdc..8229cae 100644 --- a/stl/stl_test.go +++ b/internal/stl/stl_test.go @@ -6,7 +6,7 @@ import ( "path/filepath" "testing" - "github.com/github/gh-skyline/types" + "github.com/github/gh-skyline/internal/types" ) // Helper function to verify STL file header diff --git a/testutil/fixtures/github.go b/internal/testutil/fixtures/github.go similarity index 96% rename from testutil/fixtures/github.go rename to internal/testutil/fixtures/github.go index 491baba..5498f9c 100644 --- a/testutil/fixtures/github.go +++ b/internal/testutil/fixtures/github.go @@ -5,7 +5,7 @@ package fixtures import ( "time" - "github.com/github/gh-skyline/types" + "github.com/github/gh-skyline/internal/types" ) // GenerateContributionsResponse creates a mock contributions response diff --git a/testutil/mocks/github.go b/internal/testutil/mocks/github.go similarity index 94% rename from testutil/mocks/github.go rename to internal/testutil/mocks/github.go index b03409e..8d6f906 100644 --- a/testutil/mocks/github.go +++ b/internal/testutil/mocks/github.go @@ -5,8 +5,8 @@ import ( "fmt" "time" - "github.com/github/gh-skyline/testutil/fixtures" - "github.com/github/gh-skyline/types" + "github.com/github/gh-skyline/internal/testutil/fixtures" + "github.com/github/gh-skyline/internal/types" ) // MockGitHubClient implements both GitHubClientInterface and APIClient interfaces diff --git a/types/types.go b/internal/types/types.go similarity index 100% rename from types/types.go rename to internal/types/types.go diff --git a/types/types_test.go b/internal/types/types_test.go similarity index 100% rename from types/types_test.go rename to internal/types/types_test.go diff --git a/main.go b/main.go index a5a0cdc..d8b8179 100644 --- a/main.go +++ b/main.go @@ -11,12 +11,12 @@ import ( "github.com/cli/go-gh/v2/pkg/api" "github.com/cli/go-gh/v2/pkg/browser" - "github.com/github/gh-skyline/ascii" - "github.com/github/gh-skyline/errors" - "github.com/github/gh-skyline/github" - "github.com/github/gh-skyline/logger" - "github.com/github/gh-skyline/stl" - "github.com/github/gh-skyline/types" + "github.com/github/gh-skyline/internal/ascii" + "github.com/github/gh-skyline/internal/errors" + "github.com/github/gh-skyline/internal/github" + "github.com/github/gh-skyline/internal/logger" + "github.com/github/gh-skyline/internal/stl" + "github.com/github/gh-skyline/internal/types" "github.com/spf13/cobra" ) diff --git a/main_test.go b/main_test.go index 242d6ef..3bc8530 100644 --- a/main_test.go +++ b/main_test.go @@ -5,9 +5,9 @@ import ( "fmt" - "github.com/github/gh-skyline/github" - "github.com/github/gh-skyline/testutil/fixtures" - "github.com/github/gh-skyline/testutil/mocks" + "github.com/github/gh-skyline/internal/github" + "github.com/github/gh-skyline/internal/testutil/fixtures" + "github.com/github/gh-skyline/internal/testutil/mocks" ) // MockBrowser implements the Browser interface From 85822ea6f305a505274945c53c5ebc243016eab8 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Thu, 23 Jan 2025 23:29:02 +0000 Subject: [PATCH 80/94] Refactor main.go into cmd, utils packages and the github client. --- cmd/root.go | 122 ++++++++++++++ cmd/root_test.go | 98 +++++++++++ cmd/skyline/skyline.go | 122 ++++++++++++++ cmd/skyline/skyline_test.go | 81 +++++++++ internal/github/init.go | 20 +++ internal/utils/utils.go | 76 +++++++++ internal/utils/utils_test.go | 175 ++++++++++++++++++++ main.go | 301 ++------------------------------- main_test.go | 311 ----------------------------------- 9 files changed, 708 insertions(+), 598 deletions(-) create mode 100644 cmd/root.go create mode 100644 cmd/root_test.go create mode 100644 cmd/skyline/skyline.go create mode 100644 cmd/skyline/skyline_test.go create mode 100644 internal/github/init.go create mode 100644 internal/utils/utils.go create mode 100644 internal/utils/utils_test.go delete mode 100644 main_test.go diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..fcda959 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,122 @@ +// Package cmd is a package that contains the root command (entrypoint) for the GitHub Skyline CLI tool. +package cmd + +import ( + "context" + "fmt" + "os" + "time" + + "github.com/cli/go-gh/v2/pkg/browser" + "github.com/github/gh-skyline/cmd/skyline" + "github.com/github/gh-skyline/internal/errors" + "github.com/github/gh-skyline/internal/github" + "github.com/github/gh-skyline/internal/logger" + "github.com/github/gh-skyline/internal/utils" + "github.com/spf13/cobra" +) + +// Command line variables and root command configuration +var ( + yearRange string + user string + full bool + debug bool + web bool + artOnly bool + output string // new output path flag + + rootCmd = &cobra.Command{ + Use: "skyline", + Short: "Generate a 3D model of a user's GitHub contribution history", + Long: `GitHub Skyline creates 3D printable STL files from GitHub contribution data. +It can generate models for specific years or year ranges for the authenticated user or an optional specified user. + +While the STL file is being generated, an ASCII preview will be displayed in the terminal. + +ASCII Preview Legend: + ' ' Empty/Sky - No contributions + '.' Future dates - What contributions could you make? + '░' Low level - Light contribution activity + '▒' Medium level - Moderate contribution activity + '▓' High level - Heavy contribution activity + '╻┃╽' Top level - Last block with contributions in the week (Low, Medium, High) + +Layout: +Each column represents one week. Days within each week are reordered vertically +to create a "building" effect, with empty spaces (no contributions) at the top.`, + RunE: func(_ *cobra.Command, _ []string) error { + log := logger.GetLogger() + if debug { + log.SetLevel(logger.DEBUG) + if err := log.Debug("Debug logging enabled"); err != nil { + return err + } + } + + client, err := github.InitializeGitHubClient() + if err != nil { + return errors.New(errors.NetworkError, "failed to initialize GitHub client", err) + } + + if web { + b := browser.New("", os.Stdout, os.Stderr) + if err := openGitHubProfile(user, client, b); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + return nil + } + + startYear, endYear, err := utils.ParseYearRange(yearRange) + if err != nil { + return fmt.Errorf("invalid year range: %v", err) + } + + return skyline.GenerateSkyline(startYear, endYear, user, full, output, artOnly) + }, + } +) + +// init sets up command line flags for the skyline CLI tool +func initFlags() { + flags := rootCmd.Flags() + flags.StringVarP(&yearRange, "year", "y", fmt.Sprintf("%d", time.Now().Year()), "Year or year range (e.g., 2024 or 2014-2024)") + flags.StringVarP(&user, "user", "u", "", "GitHub username (optional, defaults to authenticated user)") + flags.BoolVarP(&full, "full", "f", false, "Generate contribution graph from join year to current year") + flags.BoolVarP(&debug, "debug", "d", false, "Enable debug logging") + flags.BoolVarP(&web, "web", "w", false, "Open GitHub profile (authenticated or specified user).") + flags.BoolVarP(&artOnly, "art-only", "a", false, "Generate only ASCII preview") + flags.StringVarP(&output, "output", "o", "", "Output file path (optional)") +} + +func init() { + initFlags() +} + +// Execute initializes and executes the root command for the GitHub Skyline CLI +func Execute(context context.Context) error { + if err := rootCmd.Execute(); err != nil { + return err + } + return nil +} + +// Browser interface matches browser.Browser functionality +type Browser interface { + Browse(url string) error +} + +// openGitHubProfile opens the GitHub profile page for the specified user or authenticated user +func openGitHubProfile(targetUser string, client skyline.GitHubClientInterface, b Browser) error { + if targetUser == "" { + username, err := client.GetAuthenticatedUser() + if err != nil { + return errors.New(errors.NetworkError, "failed to get authenticated user", err) + } + targetUser = username + } + + profileURL := fmt.Sprintf("https://github.com/%s", targetUser) + return b.Browse(profileURL) +} diff --git a/cmd/root_test.go b/cmd/root_test.go new file mode 100644 index 0000000..c6bd877 --- /dev/null +++ b/cmd/root_test.go @@ -0,0 +1,98 @@ +package cmd + +import ( + "fmt" + "testing" + + "github.com/github/gh-skyline/internal/testutil/mocks" +) + +// MockBrowser implements the Browser interface +type MockBrowser struct { + LastURL string + Err error +} + +// Browse implements the Browser interface +func (m *MockBrowser) Browse(url string) error { + m.LastURL = url + return m.Err +} + +func TestRootCmd(t *testing.T) { + cmd := rootCmd + if cmd.Use != "skyline" { + t.Errorf("expected command use to be 'skyline', got %s", cmd.Use) + } + if cmd.Short != "Generate a 3D model of a user's GitHub contribution history" { + t.Errorf("expected command short description to be 'Generate a 3D model of a user's GitHub contribution history', got %s", cmd.Short) + } + if cmd.Long == "" { + t.Error("expected command long description to be non-empty") + } +} + +func TestInit(t *testing.T) { + flags := rootCmd.Flags() + expectedFlags := []string{"year", "user", "full", "debug", "web", "art-only", "output"} + for _, flag := range expectedFlags { + if flags.Lookup(flag) == nil { + t.Errorf("expected flag %s to be initialized", flag) + } + } +} + +// TestOpenGitHubProfile tests the openGitHubProfile function +func TestOpenGitHubProfile(t *testing.T) { + tests := []struct { + name string + targetUser string + mockClient *mocks.MockGitHubClient + wantURL string + wantErr bool + }{ + { + name: "specific user", + targetUser: "testuser", + mockClient: &mocks.MockGitHubClient{}, + wantURL: "https://github.com/testuser", + wantErr: false, + }, + { + name: "authenticated user", + targetUser: "", + mockClient: &mocks.MockGitHubClient{ + Username: "authuser", + }, + wantURL: "https://github.com/authuser", + wantErr: false, + }, + { + name: "client error", + targetUser: "", + mockClient: &mocks.MockGitHubClient{ + Err: fmt.Errorf("mock error"), + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockBrowser := &MockBrowser{} + if tt.wantErr { + mockBrowser.Err = fmt.Errorf("mock error") + } + err := openGitHubProfile(tt.targetUser, tt.mockClient, mockBrowser) + + if (err != nil) != tt.wantErr { + t.Errorf("openGitHubProfile() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if !tt.wantErr && mockBrowser.LastURL != tt.wantURL { + t.Errorf("openGitHubProfile() URL = %v, want %v", mockBrowser.LastURL, tt.wantURL) + } + }) + } +} diff --git a/cmd/skyline/skyline.go b/cmd/skyline/skyline.go new file mode 100644 index 0000000..de62e5e --- /dev/null +++ b/cmd/skyline/skyline.go @@ -0,0 +1,122 @@ +// Package skyline provides the entry point for the GitHub Skyline Generator. +// It generates a 3D model of GitHub contributions in STL format. +package skyline + +import ( + "fmt" + "strings" + "time" + + "github.com/github/gh-skyline/internal/ascii" + "github.com/github/gh-skyline/internal/errors" + "github.com/github/gh-skyline/internal/github" + "github.com/github/gh-skyline/internal/logger" + "github.com/github/gh-skyline/internal/stl" + "github.com/github/gh-skyline/internal/types" + "github.com/github/gh-skyline/internal/utils" +) + +// GitHubClientInterface defines the methods for interacting with GitHub API +type GitHubClientInterface interface { + GetAuthenticatedUser() (string, error) + GetUserJoinYear(username string) (int, error) + FetchContributions(username string, year int) (*types.ContributionsResponse, error) +} + +// GenerateSkyline creates a 3D model with ASCII art preview of GitHub contributions for the specified year range, or "full lifetime" of the user +func GenerateSkyline(startYear, endYear int, targetUser string, full bool, output string, artOnly bool) error { + log := logger.GetLogger() + + client, err := github.InitializeGitHubClient() + if err != nil { + return errors.New(errors.NetworkError, "failed to initialize GitHub client", err) + } + + if targetUser == "" { + if err := log.Debug("No target user specified, using authenticated user"); err != nil { + return err + } + username, err := client.GetAuthenticatedUser() + if err != nil { + return errors.New(errors.NetworkError, "failed to get authenticated user", err) + } + targetUser = username + } + + if full { + joinYear, err := client.GetUserJoinYear(targetUser) + if err != nil { + return errors.New(errors.NetworkError, "failed to get user join year", err) + } + startYear = joinYear + endYear = time.Now().Year() + } + + var allContributions [][][]types.ContributionDay + for year := startYear; year <= endYear; year++ { + contributions, err := fetchContributionData(client, targetUser, year) + if err != nil { + return err + } + allContributions = append(allContributions, contributions) + + // Generate ASCII art for each year + asciiArt, err := ascii.GenerateASCII(contributions, targetUser, year, (year == startYear) && !artOnly, !artOnly) + if err != nil { + if warnErr := log.Warning("Failed to generate ASCII preview: %v", err); warnErr != nil { + return warnErr + } + } else { + if year == startYear { + // For first year, show full ASCII art including header + fmt.Println(asciiArt) + } else { + // For subsequent years, skip the header + lines := strings.Split(asciiArt, "\n") + gridStart := 0 + for i, line := range lines { + containsEmptyBlock := strings.Contains(line, string(ascii.EmptyBlock)) + containsFoundationLow := strings.Contains(line, string(ascii.FoundationLow)) + isNotOnlyEmptyBlocks := strings.Trim(line, string(ascii.EmptyBlock)) != "" + + if (containsEmptyBlock || containsFoundationLow) && isNotOnlyEmptyBlocks { + gridStart = i + break + } + } + // Print just the grid and user info + fmt.Println(strings.Join(lines[gridStart:], "\n")) + } + } + } + + if !artOnly { + // Generate filename + outputPath := utils.GenerateOutputFilename(targetUser, startYear, endYear, output) + + // Generate the STL file + if len(allContributions) == 1 { + return stl.GenerateSTL(allContributions[0], outputPath, targetUser, startYear) + } + return stl.GenerateSTLRange(allContributions, outputPath, targetUser, startYear, endYear) + } + + return nil +} + +// fetchContributionData retrieves and formats the contribution data for the specified year. +func fetchContributionData(client *github.Client, username string, year int) ([][]types.ContributionDay, error) { + response, err := client.FetchContributions(username, year) + if err != nil { + return nil, fmt.Errorf("failed to fetch contributions: %w", err) + } + + // Convert weeks data to 2D array for STL generation + weeks := response.User.ContributionsCollection.ContributionCalendar.Weeks + contributionGrid := make([][]types.ContributionDay, len(weeks)) + for i, week := range weeks { + contributionGrid[i] = week.ContributionDays + } + + return contributionGrid, nil +} diff --git a/cmd/skyline/skyline_test.go b/cmd/skyline/skyline_test.go new file mode 100644 index 0000000..05970a5 --- /dev/null +++ b/cmd/skyline/skyline_test.go @@ -0,0 +1,81 @@ +package skyline + +import ( + "testing" + + "github.com/github/gh-skyline/internal/github" + "github.com/github/gh-skyline/internal/testutil/fixtures" + "github.com/github/gh-skyline/internal/testutil/mocks" +) + +func TestGenerateSkyline(t *testing.T) { + // Save original initializer + originalInit := github.InitializeGitHubClient + defer func() { + github.InitializeGitHubClient = originalInit + }() + + tests := []struct { + name string + startYear int + endYear int + targetUser string + full bool + mockClient *mocks.MockGitHubClient + wantErr bool + }{ + { + name: "single year", + startYear: 2024, + endYear: 2024, + targetUser: "testuser", + full: false, + mockClient: &mocks.MockGitHubClient{ + Username: "testuser", + JoinYear: 2020, + MockData: fixtures.GenerateContributionsResponse("testuser", 2024), + }, + wantErr: false, + }, + { + name: "year range", + startYear: 2020, + endYear: 2024, + targetUser: "testuser", + full: false, + mockClient: &mocks.MockGitHubClient{ + Username: "testuser", + JoinYear: 2020, + MockData: fixtures.GenerateContributionsResponse("testuser", 2024), + }, + wantErr: false, + }, + { + name: "full range", + startYear: 2008, + endYear: 2024, + targetUser: "testuser", + full: true, + mockClient: &mocks.MockGitHubClient{ + Username: "testuser", + JoinYear: 2008, + MockData: fixtures.GenerateContributionsResponse("testuser", 2024), + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a closure that returns our mock client + github.InitializeGitHubClient = func() (*github.Client, error) { + return github.NewClient(tt.mockClient), nil + } + + err := GenerateSkyline(tt.startYear, tt.endYear, tt.targetUser, tt.full, "", false) + if (err != nil) != tt.wantErr { + t.Errorf("GenerateSkyline() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/github/init.go b/internal/github/init.go new file mode 100644 index 0000000..c12eeed --- /dev/null +++ b/internal/github/init.go @@ -0,0 +1,20 @@ +// Package github provides a function to initialize the GitHub client. +package github + +import ( + "fmt" + + "github.com/cli/go-gh/v2/pkg/api" +) + +// ClientInitializer is a function type for initializing GitHub clients +type ClientInitializer func() (*Client, error) + +// InitializeGitHubClient is the default client initializer +var InitializeGitHubClient ClientInitializer = func() (*Client, error) { + apiClient, err := api.DefaultGraphQLClient() + if err != nil { + return nil, fmt.Errorf("failed to create GraphQL client: %w", err) + } + return NewClient(apiClient), nil +} diff --git a/internal/utils/utils.go b/internal/utils/utils.go new file mode 100644 index 0000000..17f5f5c --- /dev/null +++ b/internal/utils/utils.go @@ -0,0 +1,76 @@ +// package utils are utility functions for the GitHub Skyline project +package utils + +import ( + "fmt" + "strconv" + "strings" + "time" +) + +// Constants for GitHub launch year and default output file format +const ( + githubLaunchYear = 2008 + outputFileFormat = "%s-%s-github-skyline.stl" +) + +// Parse year range string (e.g., "2024" or "2014-2024") +func ParseYearRange(yearRange string) (startYear, endYear int, err error) { + if strings.Contains(yearRange, "-") { + parts := strings.Split(yearRange, "-") + if len(parts) != 2 { + return 0, 0, fmt.Errorf("invalid year range format") + } + startYear, err = strconv.Atoi(parts[0]) + if err != nil { + return 0, 0, err + } + endYear, err = strconv.Atoi(parts[1]) + if err != nil { + return 0, 0, err + } + } else { + year, err := strconv.Atoi(yearRange) + if err != nil { + return 0, 0, err + } + startYear, endYear = year, year + } + return startYear, endYear, validateYearRange(startYear, endYear) +} + +// validateYearRange checks if the years are within the range +// of GitHub's launch year to the current year and if +// the start year is not greater than the end year. +func validateYearRange(startYear, endYear int) error { + currentYear := time.Now().Year() + if startYear < githubLaunchYear || endYear > currentYear { + return fmt.Errorf("years must be between %d and %d", githubLaunchYear, currentYear) + } + if startYear > endYear { + return fmt.Errorf("start year cannot be after end year") + } + return nil +} + +// FormatYearRange returns a formatted string representation of the year range +func FormatYearRange(startYear, endYear int) string { + if startYear == endYear { + return fmt.Sprintf("%d", startYear) + } + // Use YYYY-YY format for multi-year ranges + return fmt.Sprintf("%04d-%02d", startYear, endYear%100) +} + +// GenerateOutputFilename creates a consistent filename for the STL output +func GenerateOutputFilename(user string, startYear, endYear int, output string) string { + if output != "" { + // Ensure the filename ends with .stl + if !strings.HasSuffix(strings.ToLower(output), ".stl") { + return output + ".stl" + } + return output + } + yearStr := FormatYearRange(startYear, endYear) + return fmt.Sprintf(outputFileFormat, user, yearStr) +} diff --git a/internal/utils/utils_test.go b/internal/utils/utils_test.go new file mode 100644 index 0000000..8253ed1 --- /dev/null +++ b/internal/utils/utils_test.go @@ -0,0 +1,175 @@ +package utils + +import "testing" + +func TestParseYearRange(t *testing.T) { + tests := []struct { + name string + yearRange string + wantStart int + wantEnd int + wantErr bool + wantErrString string + }{ + { + name: "single year", + yearRange: "2024", + wantStart: 2024, + wantEnd: 2024, + wantErr: false, + }, + { + name: "year range", + yearRange: "2020-2024", + wantStart: 2020, + wantEnd: 2024, + wantErr: false, + }, + { + name: "invalid format", + yearRange: "2020-2024-2025", + wantErr: true, + wantErrString: "invalid year range format", + }, + { + name: "invalid number", + yearRange: "abc-2024", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + start, end, err := ParseYearRange(tt.yearRange) + if (err != nil) != tt.wantErr { + t.Errorf("parseYearRange() error = %v, wantErr %v", err, tt.wantErr) + return + } + if tt.wantErr && tt.wantErrString != "" && err.Error() != tt.wantErrString { + t.Errorf("parseYearRange() error = %v, wantErrString %v", err, tt.wantErrString) + return + } + if !tt.wantErr { + if start != tt.wantStart { + t.Errorf("parseYearRange() start = %v, want %v", start, tt.wantStart) + } + if end != tt.wantEnd { + t.Errorf("parseYearRange() end = %v, want %v", end, tt.wantEnd) + } + } + }) + } +} + +func TestValidateYearRange(t *testing.T) { + tests := []struct { + name string + startYear int + endYear int + wantErr bool + }{ + { + name: "valid range", + startYear: 2020, + endYear: 2024, + wantErr: false, + }, + { + name: "invalid start year", + startYear: 2007, + endYear: 2024, + wantErr: true, + }, + { + name: "start after end", + startYear: 2024, + endYear: 2020, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateYearRange(tt.startYear, tt.endYear) + if (err != nil) != tt.wantErr { + t.Errorf("validateYearRange() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestFormatYearRange(t *testing.T) { + tests := []struct { + name string + startYear int + endYear int + want string + }{ + { + name: "same year", + startYear: 2024, + endYear: 2024, + want: "2024", + }, + { + name: "different years", + startYear: 2020, + endYear: 2024, + want: "2020-24", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := FormatYearRange(tt.startYear, tt.endYear) + if got != tt.want { + t.Errorf("formatYearRange() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGenerateOutputFilename(t *testing.T) { + tests := []struct { + name string + user string + startYear int + endYear int + output string + want string + }{ + { + name: "single year", + user: "testuser", + startYear: 2024, + endYear: 2024, + output: "", + want: "testuser-2024-github-skyline.stl", + }, + { + name: "year range", + user: "testuser", + startYear: 2020, + endYear: 2024, + output: "", + want: "testuser-2020-24-github-skyline.stl", + }, + { + name: "override", + user: "testuser", + startYear: 2020, + endYear: 2024, + output: "myoutput.stl", + want: "myoutput.stl", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := GenerateOutputFilename(tt.user, tt.startYear, tt.endYear, tt.output) + if got != tt.want { + t.Errorf("generateOutputFilename() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/main.go b/main.go index d8b8179..0fc58be 100644 --- a/main.go +++ b/main.go @@ -1,305 +1,32 @@ -// Package main provides the entry point for the GitHub Skyline Generator. -// It generates a 3D model of GitHub contributions in STL format. +// Package main provides the entry point for the GitHub CLI gh-skyline extension. package main import ( - "fmt" + "context" "os" - "strconv" - "strings" - "time" - "github.com/cli/go-gh/v2/pkg/api" - "github.com/cli/go-gh/v2/pkg/browser" - "github.com/github/gh-skyline/internal/ascii" - "github.com/github/gh-skyline/internal/errors" - "github.com/github/gh-skyline/internal/github" - "github.com/github/gh-skyline/internal/logger" - "github.com/github/gh-skyline/internal/stl" - "github.com/github/gh-skyline/internal/types" - "github.com/spf13/cobra" + "github.com/github/gh-skyline/cmd" ) -// Browser interface matches browser.Browser functionality -type Browser interface { - Browse(url string) error -} - -// GitHubClientInterface defines the methods for interacting with GitHub API -type GitHubClientInterface interface { - GetAuthenticatedUser() (string, error) - GetUserJoinYear(username string) (int, error) - FetchContributions(username string, year int) (*types.ContributionsResponse, error) -} +type exitCode int -// Constants for GitHub launch year and default output file format const ( - githubLaunchYear = 2008 - outputFileFormat = "%s-%s-github-skyline.stl" -) - -// Command line variables and root command configuration -var ( - yearRange string - user string - full bool - debug bool - web bool - artOnly bool - output string // new output path flag - - rootCmd = &cobra.Command{ - Use: "skyline", - Short: "Generate a 3D model of a user's GitHub contribution history", - Long: `GitHub Skyline creates 3D printable STL files from GitHub contribution data. -It can generate models for specific years or year ranges for the authenticated user or an optional specified user. - -While the STL file is being generated, an ASCII preview will be displayed in the terminal. - -ASCII Preview Legend: - ' ' Empty/Sky - No contributions - '.' Future dates - What contributions could you make? - '░' Low level - Light contribution activity - '▒' Medium level - Moderate contribution activity - '▓' High level - Heavy contribution activity - '╻┃╽' Top level - Last block with contributions in the week (Low, Medium, High) - -Layout: -Each column represents one week. Days within each week are reordered vertically -to create a "building" effect, with empty spaces (no contributions) at the top.`, - RunE: func(_ *cobra.Command, _ []string) error { - log := logger.GetLogger() - if debug { - log.SetLevel(logger.DEBUG) - if err := log.Debug("Debug logging enabled"); err != nil { - return err - } - } - - client, err := initializeGitHubClient() - if err != nil { - return errors.New(errors.NetworkError, "failed to initialize GitHub client", err) - } - - if web { - b := browser.New("", os.Stdout, os.Stderr) - if err := openGitHubProfile(user, client, b); err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } - return nil - } - - startYear, endYear, err := parseYearRange(yearRange) - if err != nil { - return fmt.Errorf("invalid year range: %v", err) - } - - return generateSkyline(startYear, endYear, user, full) - }, - } + exitOK exitCode = 0 + exitError exitCode = 1 ) -// init sets up command line flags for the skyline CLI tool -func init() { - rootCmd.Flags().StringVarP(&yearRange, "year", "y", fmt.Sprintf("%d", time.Now().Year()), "Year or year range (e.g., 2024 or 2014-2024)") - rootCmd.Flags().StringVarP(&user, "user", "u", "", "GitHub username (optional, defaults to authenticated user)") - rootCmd.Flags().BoolVarP(&full, "full", "f", false, "Generate contribution graph from join year to current year") - rootCmd.Flags().BoolVarP(&debug, "debug", "d", false, "Enable debug logging") - rootCmd.Flags().BoolVarP(&web, "web", "w", false, "Open GitHub profile (authenticated or specified user).") - rootCmd.Flags().BoolVarP(&artOnly, "art-only", "a", false, "Generate only ASCII preview") - rootCmd.Flags().StringVarP(&output, "output", "o", "", "Output file path (optional)") -} - -// main initializes and executes the root command for the GitHub Skyline CLI func main() { - if err := rootCmd.Execute(); err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } -} - -// formatYearRange returns a formatted string representation of the year range -func formatYearRange(startYear, endYear int) string { - if startYear == endYear { - return fmt.Sprintf("%d", startYear) - } - // Use YYYY-YY format for multi-year ranges - return fmt.Sprintf("%04d-%02d", startYear, endYear%100) -} - -// generateOutputFilename creates a consistent filename for the STL output -func generateOutputFilename(user string, startYear, endYear int) string { - if output != "" { - // Ensure the filename ends with .stl - if !strings.HasSuffix(strings.ToLower(output), ".stl") { - return output + ".stl" - } - return output - } - yearStr := formatYearRange(startYear, endYear) - return fmt.Sprintf(outputFileFormat, user, yearStr) + code := mainRun() + os.Exit(int(code)) } -// generateSkyline creates a 3D model with ASCII art preview of GitHub contributions for the specified year range, or "full lifetime" of the user -func generateSkyline(startYear, endYear int, targetUser string, full bool) error { - log := logger.GetLogger() - - client, err := initializeGitHubClient() - if err != nil { - return errors.New(errors.NetworkError, "failed to initialize GitHub client", err) - } - - if targetUser == "" { - if err := log.Debug("No target user specified, using authenticated user"); err != nil { - return err - } - username, err := client.GetAuthenticatedUser() - if err != nil { - return errors.New(errors.NetworkError, "failed to get authenticated user", err) - } - targetUser = username - } - - if full { - joinYear, err := client.GetUserJoinYear(targetUser) - if err != nil { - return errors.New(errors.NetworkError, "failed to get user join year", err) - } - startYear = joinYear - endYear = time.Now().Year() - } - - var allContributions [][][]types.ContributionDay - for year := startYear; year <= endYear; year++ { - contributions, err := fetchContributionData(client, targetUser, year) - if err != nil { - return err - } - allContributions = append(allContributions, contributions) - - // Generate ASCII art for each year - asciiArt, err := ascii.GenerateASCII(contributions, targetUser, year, (year == startYear) && !artOnly, !artOnly) - if err != nil { - if warnErr := log.Warning("Failed to generate ASCII preview: %v", err); warnErr != nil { - return warnErr - } - } else { - if year == startYear { - // For first year, show full ASCII art including header - fmt.Println(asciiArt) - } else { - // For subsequent years, skip the header - lines := strings.Split(asciiArt, "\n") - gridStart := 0 - for i, line := range lines { - containsEmptyBlock := strings.Contains(line, string(ascii.EmptyBlock)) - containsFoundationLow := strings.Contains(line, string(ascii.FoundationLow)) - isNotOnlyEmptyBlocks := strings.Trim(line, string(ascii.EmptyBlock)) != "" - - if (containsEmptyBlock || containsFoundationLow) && isNotOnlyEmptyBlocks { - gridStart = i - break - } - } - // Print just the grid and user info - fmt.Println(strings.Join(lines[gridStart:], "\n")) - } - } - } - - if !artOnly { - // Generate filename - outputPath := generateOutputFilename(targetUser, startYear, endYear) - - // Generate the STL file - if len(allContributions) == 1 { - return stl.GenerateSTL(allContributions[0], outputPath, targetUser, startYear) - } - return stl.GenerateSTLRange(allContributions, outputPath, targetUser, startYear, endYear) - } - - return nil -} - -// Variable for client initialization - allows for testing -var initializeGitHubClient = defaultGitHubClient - -// defaultGitHubClient is the default implementation of client initialization -func defaultGitHubClient() (*github.Client, error) { - apiClient, err := api.DefaultGraphQLClient() - if err != nil { - return nil, fmt.Errorf("failed to create GraphQL client: %w", err) - } - return github.NewClient(apiClient), nil -} - -// fetchContributionData retrieves and formats the contribution data for the specified year. -func fetchContributionData(client *github.Client, username string, year int) ([][]types.ContributionDay, error) { - response, err := client.FetchContributions(username, year) - if err != nil { - return nil, fmt.Errorf("failed to fetch contributions: %w", err) - } - - // Convert weeks data to 2D array for STL generation - weeks := response.User.ContributionsCollection.ContributionCalendar.Weeks - contributionGrid := make([][]types.ContributionDay, len(weeks)) - for i, week := range weeks { - contributionGrid[i] = week.ContributionDays - } - - return contributionGrid, nil -} - -// Parse year range string (e.g., "2024" or "2014-2024") -func parseYearRange(yearRange string) (startYear, endYear int, err error) { - if strings.Contains(yearRange, "-") { - parts := strings.Split(yearRange, "-") - if len(parts) != 2 { - return 0, 0, fmt.Errorf("invalid year range format") - } - startYear, err = strconv.Atoi(parts[0]) - if err != nil { - return 0, 0, err - } - endYear, err = strconv.Atoi(parts[1]) - if err != nil { - return 0, 0, err - } - } else { - year, err := strconv.Atoi(yearRange) - if err != nil { - return 0, 0, err - } - startYear, endYear = year, year - } - return startYear, endYear, validateYearRange(startYear, endYear) -} - -// validateYearRange checks if the years are within the range -// of GitHub's launch year to the current year and if -// the start year is not greater than the end year. -func validateYearRange(startYear, endYear int) error { - currentYear := time.Now().Year() - if startYear < githubLaunchYear || endYear > currentYear { - return fmt.Errorf("years must be between %d and %d", githubLaunchYear, currentYear) - } - if startYear > endYear { - return fmt.Errorf("start year cannot be after end year") - } - return nil -} +func mainRun() exitCode { + exitCode := exitOK + ctx := context.Background() -// openGitHubProfile opens the GitHub profile page for the specified user or authenticated user -func openGitHubProfile(targetUser string, client GitHubClientInterface, b Browser) error { - if targetUser == "" { - username, err := client.GetAuthenticatedUser() - if err != nil { - return errors.New(errors.NetworkError, "failed to get authenticated user", err) - } - targetUser = username + if err := cmd.Execute(ctx); err != nil { + exitCode = exitError } - profileURL := fmt.Sprintf("https://github.com/%s", targetUser) - return b.Browse(profileURL) + return exitCode } diff --git a/main_test.go b/main_test.go deleted file mode 100644 index 3bc8530..0000000 --- a/main_test.go +++ /dev/null @@ -1,311 +0,0 @@ -package main - -import ( - "testing" - - "fmt" - - "github.com/github/gh-skyline/internal/github" - "github.com/github/gh-skyline/internal/testutil/fixtures" - "github.com/github/gh-skyline/internal/testutil/mocks" -) - -// MockBrowser implements the Browser interface -type MockBrowser struct { - LastURL string - Err error -} - -// Browse implements the Browser interface -func (m *MockBrowser) Browse(url string) error { - m.LastURL = url - return m.Err -} - -func TestFormatYearRange(t *testing.T) { - tests := []struct { - name string - startYear int - endYear int - want string - }{ - { - name: "same year", - startYear: 2024, - endYear: 2024, - want: "2024", - }, - { - name: "different years", - startYear: 2020, - endYear: 2024, - want: "2020-24", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := formatYearRange(tt.startYear, tt.endYear) - if got != tt.want { - t.Errorf("formatYearRange() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestGenerateOutputFilename(t *testing.T) { - tests := []struct { - name string - user string - startYear int - endYear int - want string - }{ - { - name: "single year", - user: "testuser", - startYear: 2024, - endYear: 2024, - want: "testuser-2024-github-skyline.stl", - }, - { - name: "year range", - user: "testuser", - startYear: 2020, - endYear: 2024, - want: "testuser-2020-24-github-skyline.stl", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := generateOutputFilename(tt.user, tt.startYear, tt.endYear) - if got != tt.want { - t.Errorf("generateOutputFilename() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestParseYearRange(t *testing.T) { - tests := []struct { - name string - yearRange string - wantStart int - wantEnd int - wantErr bool - wantErrString string - }{ - { - name: "single year", - yearRange: "2024", - wantStart: 2024, - wantEnd: 2024, - wantErr: false, - }, - { - name: "year range", - yearRange: "2020-2024", - wantStart: 2020, - wantEnd: 2024, - wantErr: false, - }, - { - name: "invalid format", - yearRange: "2020-2024-2025", - wantErr: true, - wantErrString: "invalid year range format", - }, - { - name: "invalid number", - yearRange: "abc-2024", - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - start, end, err := parseYearRange(tt.yearRange) - if (err != nil) != tt.wantErr { - t.Errorf("parseYearRange() error = %v, wantErr %v", err, tt.wantErr) - return - } - if tt.wantErr && tt.wantErrString != "" && err.Error() != tt.wantErrString { - t.Errorf("parseYearRange() error = %v, wantErrString %v", err, tt.wantErrString) - return - } - if !tt.wantErr { - if start != tt.wantStart { - t.Errorf("parseYearRange() start = %v, want %v", start, tt.wantStart) - } - if end != tt.wantEnd { - t.Errorf("parseYearRange() end = %v, want %v", end, tt.wantEnd) - } - } - }) - } -} - -func TestValidateYearRange(t *testing.T) { - tests := []struct { - name string - startYear int - endYear int - wantErr bool - }{ - { - name: "valid range", - startYear: 2020, - endYear: 2024, - wantErr: false, - }, - { - name: "invalid start year", - startYear: 2007, - endYear: 2024, - wantErr: true, - }, - { - name: "start after end", - startYear: 2024, - endYear: 2020, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := validateYearRange(tt.startYear, tt.endYear) - if (err != nil) != tt.wantErr { - t.Errorf("validateYearRange() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestGenerateSkyline(t *testing.T) { - // Save original client creation function - originalInitFn := initializeGitHubClient - defer func() { - initializeGitHubClient = originalInitFn - }() - - tests := []struct { - name string - startYear int - endYear int - targetUser string - full bool - mockClient *mocks.MockGitHubClient - wantErr bool - }{ - { - name: "single year", - startYear: 2024, - endYear: 2024, - targetUser: "testuser", - full: false, - mockClient: &mocks.MockGitHubClient{ - Username: "testuser", - JoinYear: 2020, - MockData: fixtures.GenerateContributionsResponse("testuser", 2024), - }, - wantErr: false, - }, - { - name: "year range", - startYear: 2020, - endYear: 2024, - targetUser: "testuser", - full: false, - mockClient: &mocks.MockGitHubClient{ - Username: "testuser", - JoinYear: 2020, - MockData: fixtures.GenerateContributionsResponse("testuser", 2024), - }, - wantErr: false, - }, - { - name: "full range", - startYear: 2008, - endYear: 2024, - targetUser: "testuser", - full: true, - mockClient: &mocks.MockGitHubClient{ - Username: "testuser", - JoinYear: 2008, - MockData: fixtures.GenerateContributionsResponse("testuser", 2024), - }, - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Override the client initialization for testing - initializeGitHubClient = func() (*github.Client, error) { - return github.NewClient(tt.mockClient), nil - } - - err := generateSkyline(tt.startYear, tt.endYear, tt.targetUser, tt.full) - if (err != nil) != tt.wantErr { - t.Errorf("generateSkyline() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -// TestOpenGitHubProfile tests the openGitHubProfile function -func TestOpenGitHubProfile(t *testing.T) { - tests := []struct { - name string - targetUser string - mockClient *mocks.MockGitHubClient - wantURL string - wantErr bool - }{ - { - name: "specific user", - targetUser: "testuser", - mockClient: &mocks.MockGitHubClient{}, - wantURL: "https://github.com/testuser", - wantErr: false, - }, - { - name: "authenticated user", - targetUser: "", - mockClient: &mocks.MockGitHubClient{ - Username: "authuser", - }, - wantURL: "https://github.com/authuser", - wantErr: false, - }, - { - name: "client error", - targetUser: "", - mockClient: &mocks.MockGitHubClient{ - Err: fmt.Errorf("mock error"), - }, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mockBrowser := &MockBrowser{} - if tt.wantErr { - mockBrowser.Err = fmt.Errorf("mock error") - } - err := openGitHubProfile(tt.targetUser, tt.mockClient, mockBrowser) - - if (err != nil) != tt.wantErr { - t.Errorf("openGitHubProfile() error = %v, wantErr %v", err, tt.wantErr) - return - } - - if !tt.wantErr && mockBrowser.LastURL != tt.wantURL { - t.Errorf("openGitHubProfile() URL = %v, want %v", mockBrowser.LastURL, tt.wantURL) - } - }) - } -} From 95b3bae548778452742c5337168715c8bd1915ce Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Sun, 26 Jan 2025 22:55:48 +0000 Subject: [PATCH 81/94] fix: update GitHub profile URL to use default hostname --- cmd/root.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/root.go b/cmd/root.go index fcda959..1bc1ae0 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -7,6 +7,7 @@ import ( "os" "time" + "github.com/cli/go-gh/v2/pkg/auth" "github.com/cli/go-gh/v2/pkg/browser" "github.com/github/gh-skyline/cmd/skyline" "github.com/github/gh-skyline/internal/errors" @@ -117,6 +118,7 @@ func openGitHubProfile(targetUser string, client skyline.GitHubClientInterface, targetUser = username } - profileURL := fmt.Sprintf("https://github.com/%s", targetUser) + hostname, _ := auth.DefaultHost() + profileURL := fmt.Sprintf("https://%s/%s", hostname, targetUser) return b.Browse(profileURL) } From b88e36aa554cbf065ed420a5ebe569aab1980843 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Wed, 29 Jan 2025 12:20:45 +0000 Subject: [PATCH 82/94] refactor: restructure root command and improve command handling in CLI tool --- cmd/root.go | 97 ++++++++++++++++++++++++++++------------------------- main.go | 4 +-- 2 files changed, 53 insertions(+), 48 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 1bc1ae0..38d2b97 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -26,11 +26,13 @@ var ( web bool artOnly bool output string // new output path flag +) - rootCmd = &cobra.Command{ - Use: "skyline", - Short: "Generate a 3D model of a user's GitHub contribution history", - Long: `GitHub Skyline creates 3D printable STL files from GitHub contribution data. +// rootCmd is the root command for the GitHub Skyline CLI tool. +var rootCmd = &cobra.Command{ + Use: "skyline", + Short: "Generate a 3D model of a user's GitHub contribution history", + Long: `GitHub Skyline creates 3D printable STL files from GitHub contribution data. It can generate models for specific years or year ranges for the authenticated user or an optional specified user. While the STL file is being generated, an ASCII preview will be displayed in the terminal. @@ -46,40 +48,23 @@ ASCII Preview Legend: Layout: Each column represents one week. Days within each week are reordered vertically to create a "building" effect, with empty spaces (no contributions) at the top.`, - RunE: func(_ *cobra.Command, _ []string) error { - log := logger.GetLogger() - if debug { - log.SetLevel(logger.DEBUG) - if err := log.Debug("Debug logging enabled"); err != nil { - return err - } - } - - client, err := github.InitializeGitHubClient() - if err != nil { - return errors.New(errors.NetworkError, "failed to initialize GitHub client", err) - } - - if web { - b := browser.New("", os.Stdout, os.Stderr) - if err := openGitHubProfile(user, client, b); err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } - return nil - } - - startYear, endYear, err := utils.ParseYearRange(yearRange) - if err != nil { - return fmt.Errorf("invalid year range: %v", err) - } - - return skyline.GenerateSkyline(startYear, endYear, user, full, output, artOnly) - }, + RunE: handleSkylineCommand, +} + +// init initializes command line flags for the skyline CLI tool. +func init() { + initFlags() +} + +// Execute initializes and executes the root command for the GitHub Skyline CLI. +func Execute(_ context.Context) error { + if err := rootCmd.Execute(); err != nil { + return err } -) + return nil +} -// init sets up command line flags for the skyline CLI tool +// initFlags sets up command line flags for the skyline CLI tool. func initFlags() { flags := rootCmd.Flags() flags.StringVarP(&yearRange, "year", "y", fmt.Sprintf("%d", time.Now().Year()), "Year or year range (e.g., 2024 or 2014-2024)") @@ -91,24 +76,44 @@ func initFlags() { flags.StringVarP(&output, "output", "o", "", "Output file path (optional)") } -func init() { - initFlags() -} +// executeRootCmd is the main execution function for the root command. +func handleSkylineCommand(_ *cobra.Command, _ []string) error { + log := logger.GetLogger() + if debug { + log.SetLevel(logger.DEBUG) + if err := log.Debug("Debug logging enabled"); err != nil { + return err + } + } -// Execute initializes and executes the root command for the GitHub Skyline CLI -func Execute(context context.Context) error { - if err := rootCmd.Execute(); err != nil { - return err + client, err := github.InitializeGitHubClient() + if err != nil { + return errors.New(errors.NetworkError, "failed to initialize GitHub client", err) } - return nil + + if web { + b := browser.New("", os.Stdout, os.Stderr) + if err := openGitHubProfile(user, client, b); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + return nil + } + + startYear, endYear, err := utils.ParseYearRange(yearRange) + if err != nil { + return fmt.Errorf("invalid year range: %v", err) + } + + return skyline.GenerateSkyline(startYear, endYear, user, full, output, artOnly) } -// Browser interface matches browser.Browser functionality +// Browser interface matches browser.Browser functionality. type Browser interface { Browse(url string) error } -// openGitHubProfile opens the GitHub profile page for the specified user or authenticated user +// openGitHubProfile opens the GitHub profile page for the specified user or authenticated user. func openGitHubProfile(targetUser string, client skyline.GitHubClientInterface, b Browser) error { if targetUser == "" { username, err := client.GetAuthenticatedUser() diff --git a/main.go b/main.go index 0fc58be..837dc3f 100644 --- a/main.go +++ b/main.go @@ -16,11 +16,11 @@ const ( ) func main() { - code := mainRun() + code := Start() os.Exit(int(code)) } -func mainRun() exitCode { +func Start() exitCode { exitCode := exitOK ctx := context.Background() From c5f4e6fd9eafbabbd55509ea9e30b84ab6e5220e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Feb 2025 18:27:23 +0000 Subject: [PATCH 83/94] Bump github.com/spf13/pflag in the go-dependencies group Bumps the go-dependencies group with 1 update: [github.com/spf13/pflag](https://github.com/spf13/pflag). Updates `github.com/spf13/pflag` from 1.0.5 to 1.0.6 - [Release notes](https://github.com/spf13/pflag/releases) - [Commits](https://github.com/spf13/pflag/compare/v1.0.5...v1.0.6) --- updated-dependencies: - dependency-name: github.com/spf13/pflag dependency-type: indirect update-type: version-update:semver-patch dependency-group: go-dependencies ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 96e4328..261c0eb 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/mattn/go-runewidth v0.0.16 // indirect github.com/muesli/termenv v0.15.2 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/pflag v1.0.6 // indirect github.com/thlib/go-timezone-local v0.0.6 // indirect golang.org/x/image v0.23.0 // indirect golang.org/x/sys v0.29.0 // indirect diff --git a/go.sum b/go.sum index a8d94af..b9fb70f 100644 --- a/go.sum +++ b/go.sum @@ -44,8 +44,9 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/thlib/go-timezone-local v0.0.6 h1:Ii3QJ4FhosL/+eCZl6Hsdr4DDU4tfevNoV83yAEo2tU= From 6f0782f881a3fd5a5a2c537ff14da2d3e21193c9 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Mon, 10 Feb 2025 13:14:47 +0000 Subject: [PATCH 84/94] fix: correct comments and function naming conventions in utils and main packages --- internal/utils/utils.go | 4 ++-- main.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 17f5f5c..61b681b 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -1,4 +1,4 @@ -// package utils are utility functions for the GitHub Skyline project +// Package utils are utility functions for the GitHub Skyline project package utils import ( @@ -14,7 +14,7 @@ const ( outputFileFormat = "%s-%s-github-skyline.stl" ) -// Parse year range string (e.g., "2024" or "2014-2024") +// ParseYearRange parses whether a year is a single year or a range of years. func ParseYearRange(yearRange string) (startYear, endYear int, err error) { if strings.Contains(yearRange, "-") { parts := strings.Split(yearRange, "-") diff --git a/main.go b/main.go index 837dc3f..ec1d8f9 100644 --- a/main.go +++ b/main.go @@ -16,11 +16,11 @@ const ( ) func main() { - code := Start() + code := start() os.Exit(int(code)) } -func Start() exitCode { +func start() exitCode { exitCode := exitOK ctx := context.Background() From adc30e7cd891f8151dafcf1059595060bb2ce8a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Feb 2025 18:58:24 +0000 Subject: [PATCH 85/94] Bump the go-dependencies group with 5 updates Bumps the go-dependencies group with 5 updates: | Package | From | To | | --- | --- | --- | | [github.com/spf13/cobra](https://github.com/spf13/cobra) | `1.8.1` | `1.9.1` | | [golang.org/x/image](https://github.com/golang/image) | `0.23.0` | `0.24.0` | | [golang.org/x/sys](https://github.com/golang/sys) | `0.29.0` | `0.30.0` | | [golang.org/x/term](https://github.com/golang/term) | `0.28.0` | `0.29.0` | | [golang.org/x/text](https://github.com/golang/text) | `0.21.0` | `0.22.0` | Updates `github.com/spf13/cobra` from 1.8.1 to 1.9.1 - [Release notes](https://github.com/spf13/cobra/releases) - [Commits](https://github.com/spf13/cobra/compare/v1.8.1...v1.9.1) Updates `golang.org/x/image` from 0.23.0 to 0.24.0 - [Commits](https://github.com/golang/image/compare/v0.23.0...v0.24.0) Updates `golang.org/x/sys` from 0.29.0 to 0.30.0 - [Commits](https://github.com/golang/sys/compare/v0.29.0...v0.30.0) Updates `golang.org/x/term` from 0.28.0 to 0.29.0 - [Commits](https://github.com/golang/term/compare/v0.28.0...v0.29.0) Updates `golang.org/x/text` from 0.21.0 to 0.22.0 - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.21.0...v0.22.0) --- updated-dependencies: - dependency-name: github.com/spf13/cobra dependency-type: direct:production update-type: version-update:semver-minor dependency-group: go-dependencies - dependency-name: golang.org/x/image dependency-type: indirect update-type: version-update:semver-minor dependency-group: go-dependencies - dependency-name: golang.org/x/sys dependency-type: indirect update-type: version-update:semver-minor dependency-group: go-dependencies - dependency-name: golang.org/x/term dependency-type: indirect update-type: version-update:semver-minor dependency-group: go-dependencies - dependency-name: golang.org/x/text dependency-type: indirect update-type: version-update:semver-minor dependency-group: go-dependencies ... Signed-off-by: dependabot[bot] --- go.mod | 10 +++++----- go.sum | 23 +++++++++++------------ 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index 261c0eb..7f88600 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ toolchain go1.23.3 require ( github.com/cli/go-gh/v2 v2.11.2 github.com/fogleman/gg v1.3.0 - github.com/spf13/cobra v1.8.1 + github.com/spf13/cobra v1.9.1 ) require ( @@ -27,9 +27,9 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/thlib/go-timezone-local v0.0.6 // indirect - golang.org/x/image v0.23.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/term v0.28.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/image v0.24.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/term v0.29.0 // indirect + golang.org/x/text v0.22.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index b9fb70f..0944f2e 100644 --- a/go.sum +++ b/go.sum @@ -8,7 +8,7 @@ github.com/cli/safeexec v1.0.1 h1:e/C79PbXF4yYTN/wauC4tviMxEV13BwljGj0N9j+N00= github.com/cli/safeexec v1.0.1/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q= github.com/cli/shurcooL-graphql v0.0.4 h1:6MogPnQJLjKkaXPyGqPRXOI2qCsQdqNfUY1QSJu2GuY= github.com/cli/shurcooL-graphql v0.0.4/go.mod h1:3waN4u02FiZivIV+p1y4d0Jo1jc6BViMA73C+sZo2fk= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -42,25 +42,24 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/thlib/go-timezone-local v0.0.6 h1:Ii3QJ4FhosL/+eCZl6Hsdr4DDU4tfevNoV83yAEo2tU= github.com/thlib/go-timezone-local v0.0.6/go.mod h1:/Tnicc6m/lsJE0irFMA0LfIwTBo4QP7A8IfyIv4zZKI= -golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= -golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= +golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ= +golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= -golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 043ab7d1c45100711c2045f897da6ef807da0936 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 19:40:00 +0000 Subject: [PATCH 86/94] Bump github.com/muesli/termenv in the go-dependencies group Bumps the go-dependencies group with 1 update: [github.com/muesli/termenv](https://github.com/muesli/termenv). Updates `github.com/muesli/termenv` from 0.15.2 to 0.16.0 - [Release notes](https://github.com/muesli/termenv/releases) - [Commits](https://github.com/muesli/termenv/compare/v0.15.2...v0.16.0) --- updated-dependencies: - dependency-name: github.com/muesli/termenv dependency-type: indirect update-type: version-update:semver-minor dependency-group: go-dependencies ... Signed-off-by: dependabot[bot] --- go.mod | 3 +-- go.sum | 7 ++----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 7f88600..6d23402 100644 --- a/go.mod +++ b/go.mod @@ -22,8 +22,7 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/muesli/termenv v0.15.2 // indirect + github.com/muesli/termenv v0.16.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/thlib/go-timezone-local v0.0.6 // indirect diff --git a/go.sum b/go.sum index 0944f2e..b051311 100644 --- a/go.sum +++ b/go.sum @@ -32,13 +32,10 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69 github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= -github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= From 8444e6e5193881e241f4613dbd0d239fd163bb88 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 18:50:34 +0000 Subject: [PATCH 87/94] Bump super-linter/super-linter in the github-actions group Bumps the github-actions group with 1 update: [super-linter/super-linter](https://github.com/super-linter/super-linter). Updates `super-linter/super-linter` from 7.2.1 to 7.3.0 - [Release notes](https://github.com/super-linter/super-linter/releases) - [Changelog](https://github.com/super-linter/super-linter/blob/main/CHANGELOG.md) - [Commits](https://github.com/super-linter/super-linter/compare/85f7611e0f7b53c8573cca84aa0ed4344f6f6a4d...4e8a7c2bf106c4c766c816b35ec612638dc9b6b2) --- updated-dependencies: - dependency-name: super-linter/super-linter dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/linter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index bea4066..3b139ba 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -29,7 +29,7 @@ jobs: go-version: "1.23" - name: Run Super-Linter - uses: super-linter/super-linter/slim@85f7611e0f7b53c8573cca84aa0ed4344f6f6a4d # v7.2.1 + uses: super-linter/super-linter/slim@4e8a7c2bf106c4c766c816b35ec612638dc9b6b2 # v7.3.0 env: VALIDATE_ALL_CODEBASE: true DEFAULT_BRANCH: "main" From 04247bad16f3cbf0c6b2060994779df8ef9fb8a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Mar 2025 10:00:31 +0000 Subject: [PATCH 88/94] Bump the go-dependencies group with 4 updates Bumps the go-dependencies group with 4 updates: [golang.org/x/image](https://github.com/golang/image), [golang.org/x/sys](https://github.com/golang/sys), [golang.org/x/term](https://github.com/golang/term) and [golang.org/x/text](https://github.com/golang/text). Updates `golang.org/x/image` from 0.24.0 to 0.25.0 - [Commits](https://github.com/golang/image/compare/v0.24.0...v0.25.0) Updates `golang.org/x/sys` from 0.30.0 to 0.31.0 - [Commits](https://github.com/golang/sys/compare/v0.30.0...v0.31.0) Updates `golang.org/x/term` from 0.29.0 to 0.30.0 - [Commits](https://github.com/golang/term/compare/v0.29.0...v0.30.0) Updates `golang.org/x/text` from 0.22.0 to 0.23.0 - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.22.0...v0.23.0) --- updated-dependencies: - dependency-name: golang.org/x/image dependency-type: indirect update-type: version-update:semver-minor dependency-group: go-dependencies - dependency-name: golang.org/x/sys dependency-type: indirect update-type: version-update:semver-minor dependency-group: go-dependencies - dependency-name: golang.org/x/term dependency-type: indirect update-type: version-update:semver-minor dependency-group: go-dependencies - dependency-name: golang.org/x/text dependency-type: indirect update-type: version-update:semver-minor dependency-group: go-dependencies ... Signed-off-by: dependabot[bot] --- go.mod | 11 +++++------ go.sum | 16 ++++++++-------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 6d23402..0c44b53 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,7 @@ module github.com/github/gh-skyline go 1.22 - -toolchain go1.23.3 +toolchain go1.24.1 require ( github.com/cli/go-gh/v2 v2.11.2 @@ -26,9 +25,9 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/thlib/go-timezone-local v0.0.6 // indirect - golang.org/x/image v0.24.0 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/term v0.29.0 // indirect - golang.org/x/text v0.22.0 // indirect + golang.org/x/image v0.25.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/term v0.30.0 // indirect + golang.org/x/text v0.23.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index b051311..58987b0 100644 --- a/go.sum +++ b/go.sum @@ -47,16 +47,16 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/thlib/go-timezone-local v0.0.6 h1:Ii3QJ4FhosL/+eCZl6Hsdr4DDU4tfevNoV83yAEo2tU= github.com/thlib/go-timezone-local v0.0.6/go.mod h1:/Tnicc6m/lsJE0irFMA0LfIwTBo4QP7A8IfyIv4zZKI= -golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ= -golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8= +golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= +golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= -golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From cb2c5932f1d185958d5177ac946b879347282732 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Tue, 25 Mar 2025 10:16:38 +0000 Subject: [PATCH 89/94] Update Go version to 1.24.1 in devcontainer and go.mod --- .devcontainer/devcontainer.json | 2 +- go.mod | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8883a33..ac35121 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,7 +3,7 @@ { "name": "Go", // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - "image": "mcr.microsoft.com/devcontainers/go:1-1.23-bookworm", + "image": "mcr.microsoft.com/devcontainers/go:1-1.24-bookworm", "customizations": { "vscode": { "extensions": [ diff --git a/go.mod b/go.mod index 0c44b53..b9c022b 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,6 @@ module github.com/github/gh-skyline -go 1.22 -toolchain go1.24.1 +go 1.24.1 require ( github.com/cli/go-gh/v2 v2.11.2 From 81424bdc896589325fa5965fcdfa99f75e0f0078 Mon Sep 17 00:00:00 2001 From: Chris Reddington <791642+chrisreddington@users.noreply.github.com> Date: Tue, 25 Mar 2025 10:17:47 +0000 Subject: [PATCH 90/94] Update Go version setup in linter workflow to use go.mod file --- .github/workflows/linter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 3b139ba..4ccaf2f 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -26,7 +26,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: "1.23" + go-version-file: go.mod - name: Run Super-Linter uses: super-linter/super-linter/slim@4e8a7c2bf106c4c766c816b35ec612638dc9b6b2 # v7.3.0 From 60cccfaaa406dd39aeb161873412be7d0b90c293 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Mar 2025 19:05:15 +0000 Subject: [PATCH 91/94] Bump github.com/cli/go-gh/v2 in the go-dependencies group Bumps the go-dependencies group with 1 update: [github.com/cli/go-gh/v2](https://github.com/cli/go-gh). Updates `github.com/cli/go-gh/v2` from 2.11.2 to 2.12.0 - [Release notes](https://github.com/cli/go-gh/releases) - [Commits](https://github.com/cli/go-gh/compare/v2.11.2...v2.12.0) --- updated-dependencies: - dependency-name: github.com/cli/go-gh/v2 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: go-dependencies ... Signed-off-by: dependabot[bot] --- go.mod | 5 ++--- go.sum | 11 +++++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index b9c022b..74e1cc1 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,8 @@ module github.com/github/gh-skyline go 1.24.1 - require ( - github.com/cli/go-gh/v2 v2.11.2 + github.com/cli/go-gh/v2 v2.12.0 github.com/fogleman/gg v1.3.0 github.com/spf13/cobra v1.9.1 ) @@ -17,7 +16,7 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/henvic/httpretty v0.1.4 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/kr/text v0.2.0 // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/muesli/termenv v0.16.0 // indirect diff --git a/go.sum b/go.sum index 58987b0..bea0950 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo= github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk= -github.com/cli/go-gh/v2 v2.11.2 h1:oad1+sESTPNTiTvh3I3t8UmxuovNDxhwLzeMHk45Q9w= -github.com/cli/go-gh/v2 v2.11.2/go.mod h1:vVFhi3TfjseIW26ED9itAR8gQK0aVThTm8sYrsZ5QTI= +github.com/cli/go-gh/v2 v2.12.0 h1:PIurZ13fXbWDbr2//6ws4g4zDbryO+iDuTpiHgiV+6k= +github.com/cli/go-gh/v2 v2.12.0/go.mod h1:+5aXmEOJsH9fc9mBHfincDwnS02j2AIA/DsTH0Bk5uw= github.com/cli/safeexec v1.0.1 h1:e/C79PbXF4yYTN/wauC4tviMxEV13BwljGj0N9j+N00= github.com/cli/safeexec v1.0.1/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q= github.com/cli/shurcooL-graphql v0.0.4 h1:6MogPnQJLjKkaXPyGqPRXOI2qCsQdqNfUY1QSJu2GuY= @@ -24,8 +24,8 @@ github.com/henvic/httpretty v0.1.4 h1:Jo7uwIRWVFxkqOnErcoYfH90o3ddQyVrSANeS4cxYm github.com/henvic/httpretty v0.1.4/go.mod h1:Dn60sQTZfbt2dYsdUSNsCljyF4AfdqnuJFDLJA1I4AM= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= @@ -34,10 +34,13 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= From c7bb9dd687350dcbea7d735706a978f9faaf611b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 19:19:07 +0000 Subject: [PATCH 92/94] Bump the go-dependencies group with 4 updates Bumps the go-dependencies group with 4 updates: [golang.org/x/image](https://github.com/golang/image), [golang.org/x/sys](https://github.com/golang/sys), [golang.org/x/term](https://github.com/golang/term) and [golang.org/x/text](https://github.com/golang/text). Updates `golang.org/x/image` from 0.25.0 to 0.26.0 - [Commits](https://github.com/golang/image/compare/v0.25.0...v0.26.0) Updates `golang.org/x/sys` from 0.31.0 to 0.32.0 - [Commits](https://github.com/golang/sys/compare/v0.31.0...v0.32.0) Updates `golang.org/x/term` from 0.30.0 to 0.31.0 - [Commits](https://github.com/golang/term/compare/v0.30.0...v0.31.0) Updates `golang.org/x/text` from 0.23.0 to 0.24.0 - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.23.0...v0.24.0) --- updated-dependencies: - dependency-name: golang.org/x/image dependency-version: 0.26.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: go-dependencies - dependency-name: golang.org/x/sys dependency-version: 0.32.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: go-dependencies - dependency-name: golang.org/x/term dependency-version: 0.31.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: go-dependencies - dependency-name: golang.org/x/text dependency-version: 0.24.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: go-dependencies ... Signed-off-by: dependabot[bot] --- go.mod | 9 +++++---- go.sum | 16 ++++++++-------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 74e1cc1..07441d0 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,7 @@ module github.com/github/gh-skyline go 1.24.1 + require ( github.com/cli/go-gh/v2 v2.12.0 github.com/fogleman/gg v1.3.0 @@ -23,9 +24,9 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/thlib/go-timezone-local v0.0.6 // indirect - golang.org/x/image v0.25.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/term v0.30.0 // indirect - golang.org/x/text v0.23.0 // indirect + golang.org/x/image v0.26.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/term v0.31.0 // indirect + golang.org/x/text v0.24.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index bea0950..6ddd757 100644 --- a/go.sum +++ b/go.sum @@ -50,16 +50,16 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/thlib/go-timezone-local v0.0.6 h1:Ii3QJ4FhosL/+eCZl6Hsdr4DDU4tfevNoV83yAEo2tU= github.com/thlib/go-timezone-local v0.0.6/go.mod h1:/Tnicc6m/lsJE0irFMA0LfIwTBo4QP7A8IfyIv4zZKI= -golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= -golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= +golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY= +golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= +golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From ac40174fae9b8dff40f12db8fb5b947c485698fe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 May 2025 15:36:11 +0000 Subject: [PATCH 93/94] Bump github.com/cli/go-gh/v2 in the go_modules group Bumps the go_modules group with 1 update: [github.com/cli/go-gh/v2](https://github.com/cli/go-gh). Updates `github.com/cli/go-gh/v2` from 2.12.0 to 2.12.1 - [Release notes](https://github.com/cli/go-gh/releases) - [Commits](https://github.com/cli/go-gh/compare/v2.12.0...v2.12.1) --- updated-dependencies: - dependency-name: github.com/cli/go-gh/v2 dependency-version: 2.12.1 dependency-type: direct:production dependency-group: go_modules ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 07441d0..d8b5e92 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/github/gh-skyline go 1.24.1 require ( - github.com/cli/go-gh/v2 v2.12.0 + github.com/cli/go-gh/v2 v2.12.1 github.com/fogleman/gg v1.3.0 github.com/spf13/cobra v1.9.1 ) diff --git a/go.sum b/go.sum index 6ddd757..6161575 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo= github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk= -github.com/cli/go-gh/v2 v2.12.0 h1:PIurZ13fXbWDbr2//6ws4g4zDbryO+iDuTpiHgiV+6k= -github.com/cli/go-gh/v2 v2.12.0/go.mod h1:+5aXmEOJsH9fc9mBHfincDwnS02j2AIA/DsTH0Bk5uw= +github.com/cli/go-gh/v2 v2.12.1 h1:SVt1/afj5FRAythyMV3WJKaUfDNsxXTIe7arZbwTWKA= +github.com/cli/go-gh/v2 v2.12.1/go.mod h1:+5aXmEOJsH9fc9mBHfincDwnS02j2AIA/DsTH0Bk5uw= github.com/cli/safeexec v1.0.1 h1:e/C79PbXF4yYTN/wauC4tviMxEV13BwljGj0N9j+N00= github.com/cli/safeexec v1.0.1/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q= github.com/cli/shurcooL-graphql v0.0.4 h1:6MogPnQJLjKkaXPyGqPRXOI2qCsQdqNfUY1QSJu2GuY= From f90cbbf62454c39a7e6f30ec1b4525b31c386119 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 07:29:35 +0000 Subject: [PATCH 94/94] Bump the go-dependencies group across 1 directory with 4 updates Bumps the go-dependencies group with 3 updates in the / directory: [golang.org/x/image](https://github.com/golang/image), [golang.org/x/sys](https://github.com/golang/sys) and [golang.org/x/term](https://github.com/golang/term). Updates `golang.org/x/image` from 0.26.0 to 0.27.0 - [Commits](https://github.com/golang/image/compare/v0.26.0...v0.27.0) Updates `golang.org/x/sys` from 0.32.0 to 0.33.0 - [Commits](https://github.com/golang/sys/compare/v0.32.0...v0.33.0) Updates `golang.org/x/term` from 0.31.0 to 0.32.0 - [Commits](https://github.com/golang/term/compare/v0.31.0...v0.32.0) Updates `golang.org/x/text` from 0.24.0 to 0.25.0 - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.24.0...v0.25.0) --- updated-dependencies: - dependency-name: golang.org/x/image dependency-version: 0.27.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: go-dependencies - dependency-name: golang.org/x/sys dependency-version: 0.33.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: go-dependencies - dependency-name: golang.org/x/term dependency-version: 0.32.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: go-dependencies - dependency-name: golang.org/x/text dependency-version: 0.25.0 dependency-type: indirect update-type: version-update:semver-minor dependency-group: go-dependencies ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index d8b5e92..e571982 100644 --- a/go.mod +++ b/go.mod @@ -24,9 +24,9 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/thlib/go-timezone-local v0.0.6 // indirect - golang.org/x/image v0.26.0 // indirect - golang.org/x/sys v0.32.0 // indirect - golang.org/x/term v0.31.0 // indirect - golang.org/x/text v0.24.0 // indirect + golang.org/x/image v0.27.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/term v0.32.0 // indirect + golang.org/x/text v0.25.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 6161575..61b5328 100644 --- a/go.sum +++ b/go.sum @@ -50,16 +50,16 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/thlib/go-timezone-local v0.0.6 h1:Ii3QJ4FhosL/+eCZl6Hsdr4DDU4tfevNoV83yAEo2tU= github.com/thlib/go-timezone-local v0.0.6/go.mod h1:/Tnicc6m/lsJE0irFMA0LfIwTBo4QP7A8IfyIv4zZKI= -golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY= -golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c= +golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w= +golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= -golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=