wip
This commit is contained in:
parent
424e572fe0
commit
f17ce69ef8
49
cmd/alias.go
49
cmd/alias.go
@ -1,57 +1,18 @@
|
|||||||
/*
|
|
||||||
Copyright © 2023 Ryan McGuire <ryand_mcguire@sweetwater.com>
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// aliasCmd represents the alias command
|
|
||||||
var aliasCmd = &cobra.Command{
|
var aliasCmd = &cobra.Command{
|
||||||
Use: "alias",
|
Use: "alias",
|
||||||
Short: "A brief description of your command",
|
Short: "Manage project aliases",
|
||||||
Long: `A longer description that spans multiple lines and likely contains examples
|
Long: aliasCmdLong,
|
||||||
and usage of using your command. For example:
|
// Just re-use the hooks for project
|
||||||
|
PersistentPreRun: initProjectCmd,
|
||||||
Cobra is a CLI library for Go that empowers applications.
|
PersistentPostRun: postProjectCmd,
|
||||||
This application is a tool to generate the needed files
|
|
||||||
to quickly create a Cobra application.`,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
fmt.Println("alias called")
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(aliasCmd)
|
rootCmd.AddCommand(aliasCmd)
|
||||||
|
|
||||||
// Here you will define your flags and configuration settings.
|
|
||||||
|
|
||||||
// Cobra supports Persistent Flags which will work for this command
|
|
||||||
// and all subcommands, e.g.:
|
|
||||||
// aliasCmd.PersistentFlags().String("foo", "", "A help for foo")
|
|
||||||
|
|
||||||
// Cobra supports local flags which will only run when this command
|
|
||||||
// is called directly, e.g.:
|
|
||||||
// aliasCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
|
||||||
}
|
}
|
||||||
|
95
cmd/alias_add.go
Normal file
95
cmd/alias_add.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pterm/pterm"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"gitlab.sweetwater.com/it/devops/tools/gitlab-project-manager/internal/gitlab"
|
||||||
|
"gitlab.sweetwater.com/it/devops/tools/gitlab-project-manager/internal/projects"
|
||||||
|
)
|
||||||
|
|
||||||
|
var aliasAddCmd = &cobra.Command{
|
||||||
|
Use: "add",
|
||||||
|
Short: "Add a project alias",
|
||||||
|
Long: aliasAddCmdLong,
|
||||||
|
Run: runAddAliasCmd,
|
||||||
|
}
|
||||||
|
|
||||||
|
func runAddAliasCmd(cmd *cobra.Command, args []string) {
|
||||||
|
var project *gitlab.Project
|
||||||
|
|
||||||
|
// Check by flag
|
||||||
|
if projectID := viper.GetInt("alias.add.projectid"); projectID > 0 {
|
||||||
|
plog.Debug(fmt.Sprintf("Adding for inbound project ID %d", projectID))
|
||||||
|
project = cache.GetProjectByID(projectID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect by fzf
|
||||||
|
if project == nil {
|
||||||
|
var err error
|
||||||
|
project, err = fzfProject(cmd.Context())
|
||||||
|
if err != nil || project == nil {
|
||||||
|
plog.Fatal("No project to alias, nothing to do", plog.Args("error", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addNewAliases(project.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addNewAliases(projectID int) {
|
||||||
|
project := cache.GetProjectByID(projectID)
|
||||||
|
if project == nil {
|
||||||
|
plog.Error("Failed to find project to alias", plog.Args("projectID", projectID))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect the aliases
|
||||||
|
aliases := promptAliasesForProject(project)
|
||||||
|
|
||||||
|
// Add aliases
|
||||||
|
for _, a := range aliases {
|
||||||
|
a = strings.Trim(a, " '\"%")
|
||||||
|
if a == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := cache.AddAlias(a, project.ID); err != nil {
|
||||||
|
plog.Debug("Skipping alias add", plog.Args(
|
||||||
|
"error", err,
|
||||||
|
"alias", a,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
plog.Info("Successfully added alias to project", plog.Args(
|
||||||
|
"project", project.String(),
|
||||||
|
"alias", a,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func promptAliasesForProject(p *gitlab.Project) []string {
|
||||||
|
aliases := cache.GetProjectAliases(p)
|
||||||
|
if len(aliases) > 0 {
|
||||||
|
plog.Info("Adding aliases to project", plog.Args(
|
||||||
|
"project", p.String(),
|
||||||
|
"existingAliases", projects.ProjectAliasesString(aliases),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
pterm.Info.Printfln("Adding aliases to %s", p.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
response, _ := pterm.DefaultInteractiveTextInput.
|
||||||
|
WithMultiLine(false).
|
||||||
|
WithDefaultValue(p.Path + " ").
|
||||||
|
Show("Enter aliases separated by space")
|
||||||
|
|
||||||
|
return strings.Split(response, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
aliasCmd.AddCommand(aliasAddCmd)
|
||||||
|
aliasAddCmd.PersistentFlags().Int("projectID", 0, "Specify a project by ID")
|
||||||
|
viper.BindPFlag("alias.add.projectID", aliasAddCmd.Flag("projectID"))
|
||||||
|
}
|
95
cmd/alias_delete.go
Normal file
95
cmd/alias_delete.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pterm/pterm"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"gitlab.sweetwater.com/it/devops/tools/gitlab-project-manager/internal/gitlab"
|
||||||
|
"gitlab.sweetwater.com/it/devops/tools/gitlab-project-manager/internal/projects"
|
||||||
|
)
|
||||||
|
|
||||||
|
var aliasAddCmd = &cobra.Command{
|
||||||
|
Use: "add",
|
||||||
|
Short: "Add a project alias",
|
||||||
|
Long: aliasAddCmdLong,
|
||||||
|
Run: runAddAliasCmd,
|
||||||
|
}
|
||||||
|
|
||||||
|
func runAddAliasCmd(cmd *cobra.Command, args []string) {
|
||||||
|
var project *gitlab.Project
|
||||||
|
|
||||||
|
// Check by flag
|
||||||
|
if projectID := viper.GetInt("alias.add.projectid"); projectID > 0 {
|
||||||
|
plog.Debug(fmt.Sprintf("Adding for inbound project ID %d", projectID))
|
||||||
|
project = cache.GetProjectByID(projectID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect by fzf
|
||||||
|
if project == nil {
|
||||||
|
var err error
|
||||||
|
project, err = fzfProject(cmd.Context())
|
||||||
|
if err != nil || project == nil {
|
||||||
|
plog.Fatal("No project to alias, nothing to do", plog.Args("error", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addNewAliases(project.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addNewAliases(projectID int) {
|
||||||
|
project := cache.GetProjectByID(projectID)
|
||||||
|
if project == nil {
|
||||||
|
plog.Error("Failed to find project to alias", plog.Args("projectID", projectID))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect the aliases
|
||||||
|
aliases := promptAliasesForProject(project)
|
||||||
|
|
||||||
|
// Add aliases
|
||||||
|
for _, a := range aliases {
|
||||||
|
a = strings.Trim(a, " '\"%")
|
||||||
|
if a == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := cache.AddAlias(a, project.ID); err != nil {
|
||||||
|
plog.Debug("Skipping alias add", plog.Args(
|
||||||
|
"error", err,
|
||||||
|
"alias", a,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
plog.Info("Successfully added alias to project", plog.Args(
|
||||||
|
"project", project.String(),
|
||||||
|
"alias", a,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func promptAliasesForProject(p *gitlab.Project) []string {
|
||||||
|
aliases := cache.GetProjectAliases(p)
|
||||||
|
if len(aliases) > 0 {
|
||||||
|
plog.Info("Adding aliases to project", plog.Args(
|
||||||
|
"project", p.String(),
|
||||||
|
"existingAliases", projects.ProjectAliasesString(aliases),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
pterm.Info.Printfln("Adding aliases to %s", p.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
response, _ := pterm.DefaultInteractiveTextInput.
|
||||||
|
WithMultiLine(false).
|
||||||
|
WithDefaultValue(p.Path + " ").
|
||||||
|
Show("Enter aliases separated by space")
|
||||||
|
|
||||||
|
return strings.Split(response, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
aliasCmd.AddCommand(aliasAddCmd)
|
||||||
|
aliasAddCmd.PersistentFlags().Int("projectID", 0, "Specify a project by ID")
|
||||||
|
viper.BindPFlag("alias.add.projectID", aliasAddCmd.Flag("projectID"))
|
||||||
|
}
|
@ -8,16 +8,12 @@ import (
|
|||||||
"gitlab.sweetwater.com/it/devops/tools/gitlab-project-manager/internal/projects"
|
"gitlab.sweetwater.com/it/devops/tools/gitlab-project-manager/internal/projects"
|
||||||
)
|
)
|
||||||
|
|
||||||
const desc = `Contains sub-commands for managing project cache.
|
|
||||||
The project cache keeps this speedy, without smashing against the GitLab
|
|
||||||
API every time a new project is added / searched for`
|
|
||||||
|
|
||||||
var cache *projects.Cache
|
var cache *projects.Cache
|
||||||
|
|
||||||
var cacheCmd = &cobra.Command{
|
var cacheCmd = &cobra.Command{
|
||||||
Use: "cache",
|
Use: "cache",
|
||||||
Short: "Manage GitLab project cache",
|
Short: "Manage GitLab project cache",
|
||||||
Long: desc,
|
Long: cacheCmdLong,
|
||||||
PersistentPreRun: initCacheCmd,
|
PersistentPreRun: initCacheCmd,
|
||||||
PersistentPostRun: postCacheCmd,
|
PersistentPostRun: postCacheCmd,
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
var dumpCmd = &cobra.Command{
|
var dumpCmd = &cobra.Command{
|
||||||
@ -11,10 +12,16 @@ var dumpCmd = &cobra.Command{
|
|||||||
Short: "Dump GitLab project cache",
|
Short: "Dump GitLab project cache",
|
||||||
Long: `Dumps cache to display`,
|
Long: `Dumps cache to display`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
fmt.Println(cache.String())
|
if conf.Dump.Full {
|
||||||
|
fmt.Println(cache.DumpString())
|
||||||
|
} else {
|
||||||
|
plog.Info(cache.String())
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cacheCmd.AddCommand(dumpCmd)
|
cacheCmd.AddCommand(dumpCmd)
|
||||||
|
dumpCmd.PersistentFlags().BoolP("full", "f", false, "Dumps entire cache")
|
||||||
|
viper.BindPFlag("dump.full", dumpCmd.LocalFlags().Lookup("full"))
|
||||||
}
|
}
|
48
cmd/project.go
Normal file
48
cmd/project.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"gitlab.sweetwater.com/it/devops/tools/gitlab-project-manager/internal/projects"
|
||||||
|
)
|
||||||
|
|
||||||
|
var projectCmd = &cobra.Command{
|
||||||
|
Use: "project [fuzzy alias search]",
|
||||||
|
Short: "Use a GitLab project",
|
||||||
|
Args: cobra.MaximumNArgs(1),
|
||||||
|
ArgAliases: []string{"alias"},
|
||||||
|
ValidArgsFunction: validAliasesFunc,
|
||||||
|
Long: projCmdLong,
|
||||||
|
PersistentPreRun: initProjectCmd,
|
||||||
|
PersistentPostRun: postProjectCmd,
|
||||||
|
Run: projectCmdRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
func projectCmdRun(cmd *cobra.Command, args []string) {
|
||||||
|
var searchString string
|
||||||
|
if len(args) > 0 {
|
||||||
|
searchString = args[0]
|
||||||
|
}
|
||||||
|
project := fzfFindProject(searchString)
|
||||||
|
|
||||||
|
if project == nil {
|
||||||
|
plog.Fatal("Failed to find a project, nothing to do")
|
||||||
|
} else {
|
||||||
|
plog.Info("Houston, we have a project", plog.Args(
|
||||||
|
"project", project.String(),
|
||||||
|
"aliases", projects.ProjectAliasesString(
|
||||||
|
cache.GetProjectAliases(project)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initProjectCmd(cmd *cobra.Command, args []string) {
|
||||||
|
initProjectCache(cmd, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func postProjectCmd(cmd *cobra.Command, args []string) {
|
||||||
|
postProjectCache(cmd, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(projectCmd)
|
||||||
|
}
|
44
cmd/project_show.go
Normal file
44
cmd/project_show.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/pterm/pterm"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var projectShowCmd = &cobra.Command{
|
||||||
|
Use: "show [fuzzy alias search]",
|
||||||
|
Short: "Show detail for a GitLab project",
|
||||||
|
Args: cobra.ArbitraryArgs,
|
||||||
|
ValidArgsFunction: validProjectsOrAliasesFunc,
|
||||||
|
Long: projShowCmdLong,
|
||||||
|
Run: projectShowCmdRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
func projectShowCmdRun(cmd *cobra.Command, args []string) {
|
||||||
|
var searchString string
|
||||||
|
if len(args) > 0 {
|
||||||
|
searchString = args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
project := fzfFindProject(searchString)
|
||||||
|
|
||||||
|
if project == nil {
|
||||||
|
var err error
|
||||||
|
project, err = fzfProject(cmd.Context())
|
||||||
|
if err != nil || project == nil {
|
||||||
|
plog.Fatal("Failed to find project, nothing to show", plog.Args(
|
||||||
|
"error", err,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(pterm.LightGreen("\n--------------"))
|
||||||
|
fmt.Println(cache.ProjectString(project))
|
||||||
|
fmt.Println(pterm.LightGreen("--------------\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
projectCmd.AddCommand(projectShowCmd)
|
||||||
|
}
|
10
cmd/root.go
10
cmd/root.go
@ -12,21 +12,13 @@ import (
|
|||||||
"gitlab.sweetwater.com/it/devops/tools/gitlab-project-manager/internal/config"
|
"gitlab.sweetwater.com/it/devops/tools/gitlab-project-manager/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
defGitlabHost = "gitlab.sweetwater.com"
|
|
||||||
defProjectsPath = "~/work/projects"
|
|
||||||
defLogLevel = "info"
|
|
||||||
)
|
|
||||||
|
|
||||||
var conf config.Config
|
var conf config.Config
|
||||||
var plog *pterm.Logger
|
var plog *pterm.Logger
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
Use: "gitlab-project-manager",
|
Use: "gitlab-project-manager",
|
||||||
Short: "Find and use GitLab projects locally",
|
Short: "Find and use GitLab projects locally",
|
||||||
Long: `Finds GitLab projects using fuzzy-find, remembering
|
Long: rootCmdLong,
|
||||||
your chosen term for the project as an alias, and offers helpful
|
|
||||||
shortcuts for moving around in projects and opening your code`,
|
|
||||||
PersistentPreRun: initRootCmd,
|
PersistentPreRun: initRootCmd,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
20
cmd/util_completion.go
Normal file
20
cmd/util_completion.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import "github.com/spf13/cobra"
|
||||||
|
|
||||||
|
func validProjectsFunc(cmd *cobra.Command, args []string, toComplete string) (
|
||||||
|
[]string, cobra.ShellCompDirective) {
|
||||||
|
return cache.ProjectStrings(), cobra.ShellCompDirectiveDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
func validAliasesFunc(cmd *cobra.Command, args []string, toComplete string) (
|
||||||
|
[]string, cobra.ShellCompDirective) {
|
||||||
|
return cache.AliasStrings(), cobra.ShellCompDirectiveDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
func validProjectsOrAliasesFunc(cmd *cobra.Command, args []string, toComplete string) (
|
||||||
|
[]string, cobra.ShellCompDirective) {
|
||||||
|
projectStrings, _ := validAliasesFunc(cmd, args, toComplete)
|
||||||
|
aliasStrings, _ := validProjectsFunc(cmd, args, toComplete)
|
||||||
|
return append(projectStrings, aliasStrings...), cobra.ShellCompDirectiveDefault
|
||||||
|
}
|
28
cmd/util_constants.go
Normal file
28
cmd/util_constants.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
const (
|
||||||
|
defGitlabHost = "gitlab.sweetwater.com"
|
||||||
|
defProjectsPath = "~/work/projects"
|
||||||
|
defLogLevel = "info"
|
||||||
|
)
|
||||||
|
|
||||||
|
const aliasCmdLong = `Manages project aliases, with options for
|
||||||
|
listing, adding, and deleting.`
|
||||||
|
|
||||||
|
const aliasAddCmdLong = `Adds a project alias to a project
|
||||||
|
project ID can be provided, or will otherwise use fuzzy find`
|
||||||
|
|
||||||
|
const cacheCmdLong = `Contains sub-commands for managing project cache.
|
||||||
|
The project cache keeps this speedy, without smashing against the GitLab
|
||||||
|
API every time a new project is added / searched for`
|
||||||
|
|
||||||
|
const rootCmdLong = `Finds GitLab projects using fuzzy-find, remembering
|
||||||
|
your chosen term for the project as an alias, and offers helpful
|
||||||
|
shortcuts for moving around in projects and opening your code`
|
||||||
|
|
||||||
|
const projCmdLong = `Switches to a GitLab project by name or alias
|
||||||
|
If not found, will enter fzf mode. If not cloned, will clone
|
||||||
|
the project locally.`
|
||||||
|
|
||||||
|
const projShowCmdLong = `Shows detail for a particular project
|
||||||
|
Will always fuzzy find`
|
83
cmd/util_fzf.go
Normal file
83
cmd/util_fzf.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
fzf "github.com/ktr0731/go-fuzzyfinder"
|
||||||
|
"gitlab.sweetwater.com/it/devops/tools/gitlab-project-manager/internal/gitlab"
|
||||||
|
"gitlab.sweetwater.com/it/devops/tools/gitlab-project-manager/internal/projects"
|
||||||
|
)
|
||||||
|
|
||||||
|
func fzfFindProject(searchString string) *gitlab.Project {
|
||||||
|
var project *gitlab.Project
|
||||||
|
|
||||||
|
if searchString != "" {
|
||||||
|
project = fzfSearchProjectAliases(searchString)
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
project, err = fzfProject(rootCmd.Context())
|
||||||
|
if project == nil || err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return project
|
||||||
|
}
|
||||||
|
|
||||||
|
func fzfSearchProjectAliases(searchString string) *gitlab.Project {
|
||||||
|
var project *gitlab.Project
|
||||||
|
var alias *projects.ProjectAlias
|
||||||
|
if alias = cache.GetAliasByName(searchString); alias != nil {
|
||||||
|
project = cache.GetProjectByAlias(alias)
|
||||||
|
plog.Info("Perfect alias match... flawless")
|
||||||
|
} else {
|
||||||
|
// Get fuzzy if we don't have an exact match
|
||||||
|
aliases := cache.FuzzyFindAlias(searchString)
|
||||||
|
if len(aliases) > 1 {
|
||||||
|
alias = fzfAliasFromAliases(aliasAddCmd.Context(), aliases)
|
||||||
|
} else if len(aliases) == 1 {
|
||||||
|
alias = aliases[0]
|
||||||
|
}
|
||||||
|
project = cache.GetProjectByAlias(alias)
|
||||||
|
}
|
||||||
|
return project
|
||||||
|
}
|
||||||
|
|
||||||
|
func fzfAliasFromAliases(ctx context.Context, aliases []*projects.ProjectAlias) *projects.ProjectAlias {
|
||||||
|
var alias *projects.ProjectAlias
|
||||||
|
i, err := fzf.Find(
|
||||||
|
aliases,
|
||||||
|
func(i int) string {
|
||||||
|
return aliases[i].Alias + " -> " + cache.GetProjectByAlias(aliases[i]).PathWithNamespace
|
||||||
|
},
|
||||||
|
fzf.WithContext(ctx),
|
||||||
|
fzf.WithHeader("Choose an Alias"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
plog.Error("Failed to fzf alias slice", plog.Args("error", err))
|
||||||
|
} else {
|
||||||
|
alias = aliases[i]
|
||||||
|
}
|
||||||
|
return alias
|
||||||
|
}
|
||||||
|
|
||||||
|
func fzfProject(ctx context.Context) (*gitlab.Project, error) {
|
||||||
|
i, err := fzf.Find(cache.Projects, fzfProjectString,
|
||||||
|
fzf.WithPreviewWindow(fzfPreviewWindow),
|
||||||
|
fzf.WithContext(ctx),
|
||||||
|
fzf.WithHeader("Fuzzy find yourself a project"),
|
||||||
|
)
|
||||||
|
if err != nil || i < 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cache.Projects[i], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fzfPreviewWindow(i, w, h int) string {
|
||||||
|
p := cache.Projects[i]
|
||||||
|
return cache.ProjectString(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fzfProjectString(i int) string {
|
||||||
|
return cache.Projects[i].String()
|
||||||
|
}
|
@ -18,8 +18,8 @@ import (
|
|||||||
// func from their PersistentPreRun commands
|
// func from their PersistentPreRun commands
|
||||||
|
|
||||||
func initProjectCache(cmd *cobra.Command, args []string) {
|
func initProjectCache(cmd *cobra.Command, args []string) {
|
||||||
plog.Debug("Running persistent pre-run for cacheCmd")
|
plog.Debug("Running pre-run for cacheCmd")
|
||||||
conf.Cache.File = conf.ProjectPath + "/.cache.json"
|
conf.Cache.File = conf.ProjectPath + "/.cache.yaml"
|
||||||
|
|
||||||
gitlabClient, err := gitlab.NewGitlabClient(cmd.Context(), conf.GitlabHost, conf.GitlabToken)
|
gitlabClient, err := gitlab.NewGitlabClient(cmd.Context(), conf.GitlabHost, conf.GitlabToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -38,7 +38,7 @@ func initProjectCache(cmd *cobra.Command, args []string) {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := cache.Load(); err != nil {
|
if err := cache.Read(); err != nil {
|
||||||
plog.Error("Cache load failed", plog.Args("error", err))
|
plog.Error("Cache load failed", plog.Args("error", err))
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
9
go.mod
9
go.mod
@ -3,6 +3,8 @@ module gitlab.sweetwater.com/it/devops/tools/gitlab-project-manager
|
|||||||
go 1.21.2
|
go 1.21.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/ktr0731/go-fuzzyfinder v0.8.0
|
||||||
|
github.com/lithammer/fuzzysearch v1.1.8
|
||||||
github.com/pterm/pterm v0.12.71
|
github.com/pterm/pterm v0.12.71
|
||||||
github.com/spf13/cobra v1.8.0
|
github.com/spf13/cobra v1.8.0
|
||||||
github.com/spf13/viper v1.17.0
|
github.com/spf13/viper v1.17.0
|
||||||
@ -18,6 +20,8 @@ require (
|
|||||||
atomicgo.dev/schedule v0.1.0 // indirect
|
atomicgo.dev/schedule v0.1.0 // indirect
|
||||||
github.com/containerd/console v1.0.3 // indirect
|
github.com/containerd/console v1.0.3 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||||
|
github.com/gdamore/encoding v1.0.0 // indirect
|
||||||
|
github.com/gdamore/tcell/v2 v2.6.0 // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/google/go-querystring v1.1.0 // indirect
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
github.com/gookit/color v1.5.4 // indirect
|
github.com/gookit/color v1.5.4 // indirect
|
||||||
@ -25,11 +29,14 @@ require (
|
|||||||
github.com/hashicorp/go-retryablehttp v0.7.2 // indirect
|
github.com/hashicorp/go-retryablehttp v0.7.2 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/lithammer/fuzzysearch v1.1.8 // indirect
|
github.com/ktr0731/go-ansisgr v0.1.0 // indirect
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/magiconair/properties v1.8.7 // indirect
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
|
github.com/nsf/termbox-go v1.1.1 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/rivo/uniseg v0.4.4 // indirect
|
github.com/rivo/uniseg v0.4.4 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.3.0 // indirect
|
github.com/sagikazarmark/locafero v0.3.0 // indirect
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||||
|
22
go.sum
22
go.sum
@ -83,6 +83,10 @@ github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0X
|
|||||||
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||||
|
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||||
|
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||||
|
github.com/gdamore/tcell/v2 v2.6.0 h1:OKbluoP9VYmJwZwq/iLb4BxwKcwGthaa1YNBJIyCySg=
|
||||||
|
github.com/gdamore/tcell/v2 v2.6.0/go.mod h1:be9omFATkdr0D9qewWW3d+MEvl5dha+Etb5y65J2H8Y=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
@ -126,10 +130,12 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||||
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
@ -183,21 +189,32 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/ktr0731/go-ansisgr v0.1.0 h1:fbuupput8739hQbEmZn1cEKjqQFwtCCZNznnF6ANo5w=
|
||||||
|
github.com/ktr0731/go-ansisgr v0.1.0/go.mod h1:G9lxwgBwH0iey0Dw5YQd7n6PmQTwTuTM/X5Sgm/UrzE=
|
||||||
|
github.com/ktr0731/go-fuzzyfinder v0.8.0 h1:+yobwo9lqZZ7jd1URPdCgZXTE2U1mpIVTkQoo4roi6w=
|
||||||
|
github.com/ktr0731/go-fuzzyfinder v0.8.0/go.mod h1:Bjpz5im+tppKE9Ii6UK1h+6RaX/lUvJ0ruO4LIYRkqo=
|
||||||
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
|
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
|
||||||
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
|
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
|
github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY=
|
||||||
|
github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo=
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
@ -214,6 +231,7 @@ github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkG
|
|||||||
github.com/pterm/pterm v0.12.71 h1:KcEJ98EiVCbzDkFbktJ2gMlr4pn8IzyGb9bwK6ffkuA=
|
github.com/pterm/pterm v0.12.71 h1:KcEJ98EiVCbzDkFbktJ2gMlr4pn8IzyGb9bwK6ffkuA=
|
||||||
github.com/pterm/pterm v0.12.71/go.mod h1:SUAcoZjRt+yjPWlWba+/Fd8zJJ2lSXBQWf0Z0HbFiIQ=
|
github.com/pterm/pterm v0.12.71/go.mod h1:SUAcoZjRt+yjPWlWba+/Fd8zJJ2lSXBQWf0Z0HbFiIQ=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
@ -8,6 +8,9 @@ type Config struct {
|
|||||||
LogLevel string `yaml:"logLevel" json:"logLevel" enum:"info,warn,debug,error"`
|
LogLevel string `yaml:"logLevel" json:"logLevel" enum:"info,warn,debug,error"`
|
||||||
ProjectPath string `yaml:"projectPath" json:"projectPath"`
|
ProjectPath string `yaml:"projectPath" json:"projectPath"`
|
||||||
Cache cacheConfig `yaml:"cache" json:"cache"`
|
Cache cacheConfig `yaml:"cache" json:"cache"`
|
||||||
|
Dump struct {
|
||||||
|
Full bool
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type cacheConfig struct {
|
type cacheConfig struct {
|
||||||
|
@ -2,12 +2,13 @@ package gitlab
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/xanzy/go-gitlab"
|
"github.com/xanzy/go-gitlab"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defProjectsPerPage = 100
|
const defProjectsPerPage = 30
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
Ctx context.Context
|
Ctx context.Context
|
||||||
@ -20,7 +21,6 @@ type Project struct {
|
|||||||
SSHURLToRepo string
|
SSHURLToRepo string
|
||||||
HTTPURLToRepo string
|
HTTPURLToRepo string
|
||||||
WebURL string
|
WebURL string
|
||||||
// Owner User
|
|
||||||
Name string
|
Name string
|
||||||
NameWithNamespace string
|
NameWithNamespace string
|
||||||
Path string
|
Path string
|
||||||
@ -51,6 +51,10 @@ type Progress struct {
|
|||||||
TotalProjects int
|
TotalProjects int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Project) String() string {
|
||||||
|
return fmt.Sprintf("%s (%s)", p.Path, p.PathWithNamespace)
|
||||||
|
}
|
||||||
|
|
||||||
// Given there may be thousands of projects, this will return
|
// Given there may be thousands of projects, this will return
|
||||||
// channels that stream progress info and then finally the full
|
// channels that stream progress info and then finally the full
|
||||||
// list of projects on separate channels
|
// list of projects on separate channels
|
||||||
@ -77,6 +81,7 @@ func (c *Client) streamProjects(pi *ProgressInfo) {
|
|||||||
Page: 1,
|
Page: 1,
|
||||||
},
|
},
|
||||||
Archived: new(bool),
|
Archived: new(bool),
|
||||||
|
Owned: gitlab.Ptr[bool](true),
|
||||||
}
|
}
|
||||||
|
|
||||||
var numProjects int
|
var numProjects int
|
||||||
|
@ -2,8 +2,8 @@ package projects
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ func (c *Cache) Load() error {
|
|||||||
|
|
||||||
// Saves the current state of the cache to disk
|
// Saves the current state of the cache to disk
|
||||||
func (c *Cache) write() {
|
func (c *Cache) write() {
|
||||||
file, err := os.OpenFile(c.file, os.O_RDWR, fs.ModeAppend)
|
file, err := os.OpenFile(c.file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0640)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.log.Error("Failed to write cache to disk", c.log.Args("error", err))
|
c.log.Error("Failed to write cache to disk", c.log.Args("error", err))
|
||||||
}
|
}
|
||||||
@ -66,7 +66,7 @@ func (c *Cache) Write() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Loads and unmarshals the project cache from disk.
|
// Loads and unmarshals the project cache from disk.
|
||||||
func (c *Cache) Read() {
|
func (c *Cache) Read() error {
|
||||||
c.lock.Lock()
|
c.lock.Lock()
|
||||||
defer c.lock.Unlock()
|
defer c.lock.Unlock()
|
||||||
c.log.Debug("Reading project cache from disk", c.log.Args("file", c.file))
|
c.log.Debug("Reading project cache from disk", c.log.Args("file", c.file))
|
||||||
@ -74,18 +74,14 @@ func (c *Cache) Read() {
|
|||||||
file, err := os.Open(c.file)
|
file, err := os.Open(c.file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.log.Error("Failed to read project cache", c.log.Args("error", err))
|
c.log.Error("Failed to read project cache", c.log.Args("error", err))
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
d := yaml.NewDecoder(file)
|
d := yaml.NewDecoder(file)
|
||||||
d.Decode(c)
|
d.Decode(c)
|
||||||
|
|
||||||
if time.Since(c.Updated) > c.ttl {
|
|
||||||
c.refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
c.readFromFile = true
|
c.readFromFile = true
|
||||||
c.log.Debug(c.String())
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resets projects cache and also optionally clears
|
// Resets projects cache and also optionally clears
|
||||||
@ -98,7 +94,6 @@ func (c *Cache) clear(clearAliases bool) {
|
|||||||
c.Aliases = make([]*ProjectAlias, 0)
|
c.Aliases = make([]*ProjectAlias, 0)
|
||||||
}
|
}
|
||||||
c.setUpdated()
|
c.setUpdated()
|
||||||
c.log.Debug(c.String())
|
|
||||||
}
|
}
|
||||||
func (c *Cache) Clear(clearAliases bool) {
|
func (c *Cache) Clear(clearAliases bool) {
|
||||||
c.lock.Lock()
|
c.lock.Lock()
|
||||||
@ -127,6 +122,24 @@ func (c *Cache) String() string {
|
|||||||
len(c.Aliases))
|
len(c.Aliases))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Cache) DumpString() string {
|
||||||
|
str := strings.Builder{}
|
||||||
|
str.WriteString(c.String() + "\n\nProjects:\n")
|
||||||
|
for _, project := range c.Projects {
|
||||||
|
str.WriteString(" - " + pterm.FgLightBlue.Sprint(project.Name) + " (")
|
||||||
|
str.WriteString(project.PathWithNamespace + ")\n")
|
||||||
|
aliases := c.GetProjectAliases(project)
|
||||||
|
if len(aliases) > 0 {
|
||||||
|
str.WriteString(pterm.FgLightGreen.Sprint(" aliases:"))
|
||||||
|
for _, a := range aliases {
|
||||||
|
str.WriteString(" [" + pterm.FgCyan.Sprint(a.Alias) + "]")
|
||||||
|
}
|
||||||
|
str.WriteRune('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str.String()
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Cache) setUpdated() {
|
func (c *Cache) setUpdated() {
|
||||||
c.Updated = time.Now()
|
c.Updated = time.Now()
|
||||||
}
|
}
|
||||||
|
23
internal/projects/cache_aliases.go
Normal file
23
internal/projects/cache_aliases.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package projects
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
func (c *Cache) addAlias(alias string, projectID int) error {
|
||||||
|
if c.GetAliasByName(alias) != nil {
|
||||||
|
return errors.New("Failed to add alias, already exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Aliases = append(c.Aliases,
|
||||||
|
&ProjectAlias{
|
||||||
|
Alias: alias,
|
||||||
|
ProjectID: projectID,
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) AddAlias(alias string, projectID int) error {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
return c.addAlias(alias, projectID)
|
||||||
|
}
|
37
internal/projects/fuzz.go
Normal file
37
internal/projects/fuzz.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package projects
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/lithammer/fuzzysearch/fuzzy"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Performs a fuzzy find on the input string, returning the closest
|
||||||
|
// matched based on its Levenshtein distance, along with an integer
|
||||||
|
// indicating number of matches found
|
||||||
|
func (c *Cache) FuzzyFindAlias(name string) []*ProjectAlias {
|
||||||
|
ranks := fuzzy.RankFindFold(name, c.AliasStrings())
|
||||||
|
if ranks.Len() == 1 {
|
||||||
|
c.log.Debug("Fuzzy found alias result",
|
||||||
|
c.log.Args(
|
||||||
|
"searchTerm", ranks[0].Source,
|
||||||
|
"foundAlias", ranks[0].Target,
|
||||||
|
"levenshteinDistance", ranks[0].Distance,
|
||||||
|
))
|
||||||
|
} else if ranks.Len() > 1 {
|
||||||
|
found := make([]string, ranks.Len())
|
||||||
|
for i, r := range ranks {
|
||||||
|
found[i] = r.Target
|
||||||
|
}
|
||||||
|
c.log.Warn("Fuzzy found multiple aliases, try being more specific",
|
||||||
|
c.log.Args("foundAliases", strings.Join(found, ",")))
|
||||||
|
}
|
||||||
|
var aliases []*ProjectAlias
|
||||||
|
if ranks.Len() > 0 {
|
||||||
|
aliases = make([]*ProjectAlias, ranks.Len())
|
||||||
|
for i, r := range ranks {
|
||||||
|
aliases[i] = c.GetAliasByName(r.Target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return aliases
|
||||||
|
}
|
@ -2,6 +2,7 @@ package projects
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/pterm/pterm"
|
"github.com/pterm/pterm"
|
||||||
"gitlab.sweetwater.com/it/devops/tools/gitlab-project-manager/internal/gitlab"
|
"gitlab.sweetwater.com/it/devops/tools/gitlab-project-manager/internal/gitlab"
|
||||||
@ -9,8 +10,93 @@ import (
|
|||||||
|
|
||||||
type ProjectAlias struct {
|
type ProjectAlias struct {
|
||||||
Alias string
|
Alias string
|
||||||
ProjectID string
|
ProjectID int
|
||||||
Project *gitlab.Project
|
}
|
||||||
|
|
||||||
|
func ProjectAliasesString(aliases []*ProjectAlias) string {
|
||||||
|
var str string
|
||||||
|
for _, a := range aliases {
|
||||||
|
str += "[" + pterm.LightCyan(a.Alias) + "] "
|
||||||
|
}
|
||||||
|
return strings.Trim(str, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) ProjectString(p *gitlab.Project) string {
|
||||||
|
info := strings.Builder{}
|
||||||
|
|
||||||
|
info.WriteString(pterm.LightBlue(p.Name))
|
||||||
|
info.WriteRune('\n')
|
||||||
|
if p.Description != "" {
|
||||||
|
info.WriteString(p.Description)
|
||||||
|
info.WriteRune('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
info.WriteString("\nPath: " + pterm.LightGreen(p.PathWithNamespace))
|
||||||
|
info.WriteString("\nProjectID: " + pterm.LightGreen(p.ID))
|
||||||
|
info.WriteString("\nURL: " + pterm.LightGreen(p.WebURL))
|
||||||
|
info.WriteString("\nLastActivity: " + pterm.LightMagenta(p.LastActivityAt.String()))
|
||||||
|
info.WriteString("\nAliases: ")
|
||||||
|
|
||||||
|
aliases := c.GetProjectAliases(p)
|
||||||
|
info.WriteString(ProjectAliasesString(aliases))
|
||||||
|
|
||||||
|
return info.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) ProjectStrings() []string {
|
||||||
|
projects := make([]string, len(c.Projects))
|
||||||
|
for i, p := range c.Projects {
|
||||||
|
projects[i] = p.NameWithNamespace
|
||||||
|
}
|
||||||
|
return projects
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) AliasStrings() []string {
|
||||||
|
aliases := make([]string, len(c.Aliases))
|
||||||
|
for i, a := range c.Aliases {
|
||||||
|
aliases[i] = a.Alias
|
||||||
|
}
|
||||||
|
return aliases
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) GetAliasByName(name string) *ProjectAlias {
|
||||||
|
for _, a := range c.Aliases {
|
||||||
|
if name == a.Alias {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) GetProjectByID(id int) *gitlab.Project {
|
||||||
|
for _, p := range c.Projects {
|
||||||
|
if p.ID == id {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) GetProjectByAlias(alias *ProjectAlias) *gitlab.Project {
|
||||||
|
if alias == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, p := range c.Projects {
|
||||||
|
if p.ID == alias.ProjectID {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) GetProjectAliases(project *gitlab.Project) []*ProjectAlias {
|
||||||
|
aliases := make([]*ProjectAlias, 0)
|
||||||
|
for _, alias := range c.Aliases {
|
||||||
|
if alias.ProjectID == project.ID {
|
||||||
|
aliases = append(aliases, alias)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return aliases
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) LoadProjects() {
|
func (c *Cache) LoadProjects() {
|
||||||
@ -46,6 +132,7 @@ func (c *Cache) LoadProjects() {
|
|||||||
c.log.Warn("LoadProjects cancelled", c.log.Args("reason", c.gitlab.Ctx.Err()))
|
c.log.Warn("LoadProjects cancelled", c.log.Args("reason", c.gitlab.Ctx.Err()))
|
||||||
return
|
return
|
||||||
case <-progressInfo.DoneChan:
|
case <-progressInfo.DoneChan:
|
||||||
|
pBar.Add(pBar.Total - curProjects)
|
||||||
c.log.Info("Project load complete")
|
c.log.Info("Project load complete")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
package gitlab
|
|
||||||
|
|
||||||
import "github.com/xanzy/go-gitlab"
|
|
||||||
|
|
||||||
type Client struct {
|
|
||||||
gitlab *gitlab.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGitlabClient(host, token string) (*Client, error) {
|
|
||||||
client, err := gitlab.NewClient(token, gitlab.WithBaseURL(host))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
gitlabClient := &Client{
|
|
||||||
gitlab: client,
|
|
||||||
}
|
|
||||||
return gitlabClient, nil
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user