diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..b50121c --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,66 @@ +name: "CodeQL Advanced" + +on: + push: + branches: [ "main" ] + paths: + - ".github/workflows/**" + - "**.go" + - "go.mod" + - "go.sum" + pull_request: + branches: [ "main" ] + paths: + - ".github/workflows/**" + - "**.go" + - "go.mod" + - "go.sum" + schedule: + - cron: '25 23 * * 5' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + security-events: write + packages: read + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: actions + build-mode: none + - language: go + build-mode: autobuild + + steps: + - name: Checkout repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + + - name: Initialize CodeQL + uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + + - name: Run manual build steps + if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 + with: + category: "/language:${{matrix.language}}" diff --git a/.gitignore b/.gitignore index aaadf73..ff4c8bd 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,6 @@ go.work.sum # Editor/IDE # .idea/ # .vscode/ + +# Local issues data +.issues/ diff --git a/cmd/create.go b/cmd/create.go index e3279b7..1a3dd03 100644 --- a/cmd/create.go +++ b/cmd/create.go @@ -6,6 +6,7 @@ import ( "os/exec" "path/filepath" + "github.com/fatih/color" "github.com/jamesjohnsdev/issues/internal/issue" "github.com/spf13/cobra" ) @@ -57,7 +58,7 @@ var createCmd = &cobra.Command{ _ = c.Run() } - fmt.Printf("Created %s\n", path) + fmt.Printf("%s %s\n", color.GreenString("Created"), path) return nil }, } diff --git a/cmd/init.go b/cmd/init.go index c9baaf8..485c3cf 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" + "github.com/fatih/color" "github.com/spf13/cobra" ) @@ -26,7 +27,7 @@ var initCmd = &cobra.Command{ return err } } - fmt.Printf("Initialized %s\n", root) + fmt.Printf("%s %s\n", color.GreenString("Initialized"), root) return nil }, } diff --git a/cmd/list.go b/cmd/list.go index d12fb90..133df5d 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -3,9 +3,13 @@ package cmd import ( "fmt" + "github.com/fatih/color" + "github.com/jamesjohnsdev/issues/internal/issue" "github.com/spf13/cobra" ) +var listAll, listClosed bool + var listCmd = &cobra.Command{ Use: "list", Short: "List local issues", @@ -18,13 +22,34 @@ var listCmd = &cobra.Command{ if err != nil { return err } - if len(issues) == 0 { + var filtered []*issue.Issue + for _, iss := range issues { + if listAll || (listClosed && iss.State == "closed") || (!listClosed && iss.State == "open") { + filtered = append(filtered, iss) + } + } + if len(filtered) == 0 { fmt.Println("No local issues. Run `issue pull` to fetch from GitHub.") return nil } - for _, iss := range issues { - fmt.Printf("%-8s %-8s %s\n", idFromPath(iss.Path), "["+iss.State+"]", iss.Title) + idColor := color.New(color.FgCyan) + openColor := color.New(color.FgGreen) + closedColor := color.New(color.FgHiBlack) + for _, iss := range filtered { + id := idColor.Sprintf("%-8s", idFromPath(iss.Path)) + var state string + if iss.State == "open" { + state = openColor.Sprintf("%-8s", "["+iss.State+"]") + } else { + state = closedColor.Sprintf("%-8s", "["+iss.State+"]") + } + fmt.Printf("%s %s %s\n", id, state, iss.Title) } return nil }, } + +func init() { + listCmd.Flags().BoolVar(&listAll, "all", false, "show open and closed issues") + listCmd.Flags().BoolVar(&listClosed, "closed", false, "show only closed issues") +} diff --git a/cmd/pull.go b/cmd/pull.go index 445a402..fc0252c 100644 --- a/cmd/pull.go +++ b/cmd/pull.go @@ -7,6 +7,7 @@ import ( "path/filepath" "time" + "github.com/fatih/color" "github.com/jamesjohnsdev/issues/internal/gh" "github.com/jamesjohnsdev/issues/internal/issue" "github.com/spf13/cobra" @@ -43,7 +44,7 @@ var pullCmd = &cobra.Command{ return err } } - fmt.Printf("Pulled %d issue(s)\n", len(remotes)) + fmt.Printf("%s %d issue(s)\n", color.GreenString("Pulled"), len(remotes)) return nil }, } diff --git a/cmd/push.go b/cmd/push.go index 8e437e8..5416b72 100644 --- a/cmd/push.go +++ b/cmd/push.go @@ -6,6 +6,7 @@ import ( "path/filepath" "time" + "github.com/fatih/color" "github.com/jamesjohnsdev/issues/internal/gh" "github.com/jamesjohnsdev/issues/internal/issue" "github.com/spf13/cobra" @@ -59,9 +60,9 @@ var pushCmd = &cobra.Command{ } } if pushed == 0 { - fmt.Println("Nothing to push.") + color.New(color.FgHiBlack).Println("Nothing to push.") } else { - fmt.Printf("Pushed %d issue(s)\n", pushed) + fmt.Printf("%s %d issue(s)\n", color.GreenString("Pushed"), pushed) } return nil }, @@ -85,7 +86,7 @@ func pushOne(root string, iss *issue.Issue) error { if err != nil { return err } - fmt.Printf("Created #%d: %s\n", number, iss.Title) + fmt.Printf("%s #%d: %s\n", color.GreenString("Created"), number, iss.Title) return pullOne(root, remote) } @@ -104,6 +105,6 @@ func pushOne(root string, iss *issue.Issue) error { return err } - fmt.Printf("Pushed #%d: %s\n", iss.Number, iss.Title) + fmt.Printf("%s #%d: %s\n", color.GreenString("Pushed"), iss.Number, iss.Title) return nil } diff --git a/cmd/root.go b/cmd/root.go index 4586a68..c4df505 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + "github.com/fatih/color" "github.com/spf13/cobra" ) @@ -20,6 +21,40 @@ func Execute() { } func init() { + bold := color.New(color.Bold).SprintFunc() + cobra.AddTemplateFunc("bold", bold) + + rootCmd.SetUsageTemplate(`{{bold "Usage:"}}{{if .Runnable}} + {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} + {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}} + +{{bold "Aliases:"}} + {{.NameAndAliases}}{{end}}{{if .HasExample}} + +{{bold "Examples:"}} +{{.Example}}{{end}}{{if .HasAvailableSubCommands}}{{$cmds := .Commands}}{{if eq (len .Groups) 0}} + +{{bold "Available Commands:"}}{{range $cmds}}{{if (or .IsAvailableCommand (eq .Name "help"))}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{else}}{{range $group := .Groups}} + +{{bold .Title}}{{range $cmds}}{{if (and (eq .GroupID $group.ID) (or .IsAvailableCommand (eq .Name "help")))}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if not .AllChildCommandsHaveGroup}} + +{{bold "Additional Commands:"}}{{range $cmds}}{{if (and (eq .GroupID "") (or .IsAvailableCommand (eq .Name "help")))}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} + +{{bold "Flags:"}} +{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} + +{{bold "Global Flags:"}} +{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}} + +{{bold "Additional help topics:"}}{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} + {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}} + +Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} +`) + rootCmd.AddCommand(initCmd) rootCmd.AddCommand(listCmd) rootCmd.AddCommand(createCmd) diff --git a/cmd/sync.go b/cmd/sync.go index 724506d..1ebb3ce 100644 --- a/cmd/sync.go +++ b/cmd/sync.go @@ -6,6 +6,7 @@ import ( "os" "strings" + "github.com/fatih/color" "github.com/jamesjohnsdev/issues/internal/gh" "github.com/spf13/cobra" ) @@ -27,7 +28,7 @@ var syncCmd = &cobra.Command{ newCount := 0 for _, iss := range local { if iss.Number == 0 { - fmt.Printf("Pushing new issue: %s\n", iss.Title) + fmt.Printf("Pushing new issue: %s\n", color.CyanString(iss.Title)) if err := pushOne(root, iss); err != nil { return err } @@ -35,7 +36,7 @@ var syncCmd = &cobra.Command{ } } if newCount > 0 { - fmt.Printf("Pushed %d new issue(s)\n\n", newCount) + fmt.Printf("%s %d new issue(s)\n\n", color.GreenString("Pushed"), newCount) } // Reload — T-issues now have real numbers @@ -60,14 +61,14 @@ var syncCmd = &cobra.Command{ } if len(modified) > 0 { - fmt.Println("Warning: the following issues have local changes that will be overwritten by pull:") + fmt.Printf("%s the following issues have local changes that will be overwritten by pull:\n", color.YellowString("Warning:")) for _, m := range modified { - fmt.Println(m) + fmt.Println(color.YellowString(m)) } fmt.Print("\nContinue? [y/N] ") line, _ := bufio.NewReader(os.Stdin).ReadString('\n') if strings.ToLower(strings.TrimSpace(line)) != "y" { - fmt.Println("Aborted. Use `issue push` to send your local changes to GitHub first.") + fmt.Println(color.YellowString("Aborted.") + " Use `issue push` to send your local changes to GitHub first.") return nil } fmt.Println() @@ -83,7 +84,7 @@ var syncCmd = &cobra.Command{ return err } } - fmt.Printf("Synced %d issue(s) from GitHub\n", len(remotes)) + fmt.Printf("%s %d issue(s) from GitHub\n", color.GreenString("Synced"), len(remotes)) return nil }, } diff --git a/go.mod b/go.mod index 42b46f3..cb7e79d 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,12 @@ module github.com/jamesjohnsdev/issues go 1.26.4 require ( + github.com/fatih/color v1.19.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/pflag v1.0.9 // indirect + golang.org/x/sys v0.42.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index ff4d6ec..cbb0168 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,21 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= +github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=