From 156ddc2009eaf0474d9e01169df1566c80048ca8 Mon Sep 17 00:00:00 2001 From: Ryan D McGuire Date: Fri, 29 Dec 2023 10:24:12 -0500 Subject: [PATCH] Update config, add manual cache unlocking --- cmd/cache.go | 10 ++++------ cmd/cache_clear.go | 10 ++++++---- cmd/cache_dump.go | 8 +++++--- cmd/cache_load.go | 4 +++- cmd/cache_unlock.go | 31 +++++++++++++++++++++++++++++++ cmd/project.go | 7 +------ cmd/project_go.go | 7 +------ cmd/project_open.go | 19 ++----------------- cmd/project_run.go | 30 ++++++++++++++++++++++++++++++ cmd/util_constants.go | 3 +++ cmd/util_fzf.go | 23 +++++++++++++++++++++++ internal/config/config.go | 17 ++++++++++------- internal/gitlab/gitlab.go | 22 ++++++++++++++++++++++ 13 files changed, 141 insertions(+), 50 deletions(-) create mode 100644 cmd/cache_unlock.go create mode 100644 cmd/project_run.go diff --git a/cmd/cache.go b/cmd/cache.go index afc1afb..c8d5a84 100644 --- a/cmd/cache.go +++ b/cmd/cache.go @@ -11,12 +11,10 @@ import ( var cache *projects.Cache var cacheCmd = &cobra.Command{ - Use: "cache", - Aliases: []string{"a", "ln"}, - Short: "Manage GitLab project cache", - Long: cacheCmdLong, - PersistentPreRun: runCacheCmd, - PersistentPostRun: postCacheCmd, + Use: "cache", + Aliases: []string{"a", "ln"}, + Short: "Manage GitLab project cache", + Long: cacheCmdLong, } func runCacheCmd(cmd *cobra.Command, args []string) { diff --git a/cmd/cache_clear.go b/cmd/cache_clear.go index 5ccc45a..a0b42dc 100644 --- a/cmd/cache_clear.go +++ b/cmd/cache_clear.go @@ -11,10 +11,12 @@ const longDesc = `Used to reset a project cache, forcing it to be rebuilt. If --clearAliases is provided, will also reset aliases. Use with caution.` var clearCmd = &cobra.Command{ - Use: "clear", - Short: "Clear GitLab Project Cache", - Long: longDesc, - Run: clearCache, + Use: "clear", + Short: "Clear GitLab Project Cache", + PreRun: runCacheCmd, + PostRun: postCacheCmd, + Long: longDesc, + Run: clearCache, } func clearCache(cmd *cobra.Command, args []string) { diff --git a/cmd/cache_dump.go b/cmd/cache_dump.go index f754c8e..37f2ef8 100644 --- a/cmd/cache_dump.go +++ b/cmd/cache_dump.go @@ -8,9 +8,11 @@ import ( ) var dumpCmd = &cobra.Command{ - Use: "dump", - Short: "Dump GitLab project cache", - Long: `Dumps cache to display`, + Use: "dump", + Short: "Dump GitLab project cache", + Long: `Dumps cache to display`, + PreRun: runCacheCmd, + PostRun: postCacheCmd, Run: func(cmd *cobra.Command, args []string) { if conf.Dump.Full { fmt.Println(cache.DumpString(true)) diff --git a/cmd/cache_load.go b/cmd/cache_load.go index 3c30474..49ff84c 100644 --- a/cmd/cache_load.go +++ b/cmd/cache_load.go @@ -11,7 +11,9 @@ var loadCmd = &cobra.Command{ Long: `Used to initialize or update a new GitLab cache. With thousands of projects, it would be too much work to hit the API every time a user wants to find a new project.`, - Run: loadCache, + PreRun: runCacheCmd, + PostRun: postCacheCmd, + Run: loadCache, } func loadCache(cmd *cobra.Command, args []string) { diff --git a/cmd/cache_unlock.go b/cmd/cache_unlock.go new file mode 100644 index 0000000..8dea365 --- /dev/null +++ b/cmd/cache_unlock.go @@ -0,0 +1,31 @@ +package cmd + +import ( + "github.com/pterm/pterm" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var unlockCmd = &cobra.Command{ + Use: "unlock", + Short: "unlock GitLab project cache", + Long: `unlocks cache to display`, + Run: func(cmd *cobra.Command, args []string) { + initProjectCache(cmd, args) + if viper.GetBool("cache.unlock.force") { + cache.UnlockCache() + } else if yes, _ := pterm.DefaultInteractiveConfirm. + WithDefaultValue(false). + Show("Are you sure you want to manually unlock?"); yes { + cache.UnlockCache() + } else { + plog.Error("You failed to confirm cache unlock") + } + }, +} + +func init() { + cacheCmd.AddCommand(unlockCmd) + unlockCmd.PersistentFlags().BoolP("force", "f", false, "force unlocks cache (don't ask)") + viper.BindPFlag("cache.unlock.force", unlockCmd.LocalFlags().Lookup("force")) +} diff --git a/cmd/project.go b/cmd/project.go index cf4ba3c..7f484f1 100644 --- a/cmd/project.go +++ b/cmd/project.go @@ -19,12 +19,7 @@ var projectCmd = &cobra.Command{ } func getProject(args []string) *gitlab.Project { - var searchString string - if len(args) > 0 { - searchString = args[0] - } - - project := fzfFindProject(searchString) + project := fzfFindProject(searchStringFromArgs(args)) if project == nil { plog.Fatal("Failed to find a project, nothing to do") diff --git a/cmd/project_go.go b/cmd/project_go.go index 35ec27c..a494e72 100644 --- a/cmd/project_go.go +++ b/cmd/project_go.go @@ -20,12 +20,7 @@ var projectGoCmd = &cobra.Command{ } func projectGoCmdRun(cmd *cobra.Command, args []string) { - var term string - if len(args) > 0 { - term = args[0] - } - - project := fzfSearchProjectAliases(term) + project := fzfSearchProjectAliases(searchStringFromArgs(args)) if project == nil { plog.Fatal("No project selected, nowhere to go") diff --git a/cmd/project_open.go b/cmd/project_open.go index 97bddd3..6bbb7f0 100644 --- a/cmd/project_open.go +++ b/cmd/project_open.go @@ -8,7 +8,6 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - "gitlab.sweetwater.com/it/devops/tools/gitlab-project-manager/internal/gitlab" ) var projectOpenCmd = &cobra.Command{ @@ -53,23 +52,9 @@ func projectOpenCmdRun(cmd *cobra.Command, args []string) { } // Identify search terms - var searchTerm string - if len(args) > 0 { - searchTerm = args[0] - } - - // Try to open local project - var project *gitlab.Project - if searchTerm == "." { - project, _ = cache.GetProjectFromCwd() - } - - // Find a project if not local + project := fzfCwdOrSearchProjectAliases(searchStringFromArgs(args)) if project == nil { - project = fzfSearchProjectAliases(searchTerm) - if project == nil { - plog.Fatal("No project to open, nothing to do") - } + plog.Fatal("No project to open, nothing to do") } // Check the project diff --git a/cmd/project_run.go b/cmd/project_run.go new file mode 100644 index 0000000..daa9934 --- /dev/null +++ b/cmd/project_run.go @@ -0,0 +1,30 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +var projectRunCmd = &cobra.Command{ + Use: "run", + Short: "Run the project (e.g. go run .)", + Aliases: []string{"exec", "r"}, + Long: projRunCmdLong, + Run: projectRunCmdRun, +} + +func projectRunCmdRun(cmd *cobra.Command, args []string) { + project := fzfCwdOrSearchProjectAliases(searchStringFromArgs(args)) + if project == nil { + plog.Fatal("No project selected, nothing to open") + } + + plog.Info("Running Projet", plog.Args("lang", project.Language)) + + if project.Language == nil { + plog.Fatal("GitLab isn't sure what language this project is... can't run.") + } +} + +func init() { + projectCmd.AddCommand(projectRunCmd) +} diff --git a/cmd/util_constants.go b/cmd/util_constants.go index 62990d8..d2f5c60 100644 --- a/cmd/util_constants.go +++ b/cmd/util_constants.go @@ -35,6 +35,9 @@ will be cloned from source control. If conf.projects.alwaysPull, a git pull will be ran automatically` +const projRunCmdLong = `Runs the current project. Tries to detect +the language and runs accordingly (e.g. go run .)` + const projListCmdLong = `List locally cloned projects. Optionally lists all projects in project cache` diff --git a/cmd/util_fzf.go b/cmd/util_fzf.go index 3495e2b..6627daa 100644 --- a/cmd/util_fzf.go +++ b/cmd/util_fzf.go @@ -26,6 +26,19 @@ func fzfFindProject(searchString string) *gitlab.Project { return project } +// If . is given as a project, will open project from the +// current working directory. Otherwise, will attempt to fuzzy-find +// a project given a search term if provided +func fzfCwdOrSearchProjectAliases(searchString string) *gitlab.Project { + var project *gitlab.Project + if searchString == "." { + project, _ = cache.GetProjectFromCwd() + } else { + project = fzfSearchProjectAliases(searchString) + } + return project +} + // This will fuzzy search only aliases, preferring an exact // match if one is given func fzfSearchProjectAliases(searchString string) *gitlab.Project { @@ -138,3 +151,13 @@ func fzfPreviewWindow(i, w, h int) string { p := cache.Projects[i] return cache.ProjectString(p) } + +// Nearly useless function that simply returns either an +// empty string, or a string from the first arg if one is provided +func searchStringFromArgs(args []string) string { + var term string + if len(args) > 0 { + term = args[0] + } + return term +} diff --git a/internal/config/config.go b/internal/config/config.go index 9251744..19a6ff3 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -5,7 +5,7 @@ import "time" type Config struct { Editor editorConfig `yaml:"editor" json:"editor"` GitlabHost string `yaml:"gitlabHost" json:"gitlabHost"` - GitlabToken string `yaml:"gitlabToken" json:"gitlabToken"` + GitlabToken string `yaml:"gitlabToken,omitempty" json:"gitlabToken,omitempty"` LogLevel string `yaml:"logLevel" json:"logLevel" enum:"info,warn,debug,error"` ProjectPath string `yaml:"projectPath" json:"projectPath"` Cache cacheConfig `yaml:"cache" json:"cache"` @@ -15,9 +15,9 @@ type Config struct { } type editorConfig struct { - DisplayName string `yaml:"displanName,omitempty" json:"displanName"` - Binary string `yaml:"binary,omitempty" json:"binary"` - OpenFlags string `yaml:"openFlags,omitempty" json:"openFlags"` + DisplayName string `yaml:"displanName,omitempty" json:"displanName,omitempty"` + Binary string `yaml:"binary,omitempty" json:"binary,omitempty"` + OpenFlags string `yaml:"openFlags,omitempty" json:"openFlags,omitempty"` OpenDirectory bool `yaml:"openDirectory" json:"openDirectory" description:"Don't open well-known files, open directory"` } @@ -26,9 +26,12 @@ type loadConfig struct { } type cacheConfig struct { - Ttl time.Duration `yaml:"ttl,omitempty" json:"ttl,omitempty"` - File string `yaml:"file,omitempty" json:"file,omitempty"` - Load loadConfig `yaml:"load" json:"load"` + Ttl time.Duration `yaml:"ttl,omitempty" json:"ttl,omitempty"` + File string `yaml:"file,omitempty" json:"file,omitempty"` + Load loadConfig `yaml:"load" json:"load"` + Unlock struct { + Force bool `yaml:"force" json:"force"` + } `yaml:"unlock" json:"unlock"` Clear struct { ClearAliases bool `yaml:"clearAliases,omitempty" json:"clearAliases,omitempty"` } `yaml:"clear,omitempty" json:"clear,omitempty"` diff --git a/internal/gitlab/gitlab.go b/internal/gitlab/gitlab.go index 0fa2c3d..a40c9ba 100644 --- a/internal/gitlab/gitlab.go +++ b/internal/gitlab/gitlab.go @@ -31,6 +31,7 @@ type Project struct { AvatarURL string LastActivityAt time.Time Readme string + Language *string gitRepo *git.Repository } @@ -196,12 +197,33 @@ func (c *Client) handleProjects(projects []*gitlab.Project) []*Project { AvatarURL: project.AvatarURL, LastActivityAt: *project.LastActivityAt, Readme: project.ReadmeURL, + Language: c.GetProjectLanguage(project), } pList = append(pList, p) } return pList } +// A nil return indicates an API error or GitLab doesn't know what +// language the project uses. +func (c *Client) GetProjectLanguage(project *gitlab.Project) *string { + l, _, e := c.gitlab.Projects.GetProjectLanguages(project.ID, gitlab.WithContext(c.Ctx)) + if e != nil { + pterm.Error.Printfln("Failed requesting project languages: %s", e.Error()) + return nil + } + + var mainLang *string + var mostUsed float32 + for name, p := range *l { + if p > mostUsed { + mainLang = &name + } + } + + return mainLang +} + func NewGitlabClient(ctx context.Context, host, token string) (*Client, error) { client, err := gitlab.NewClient(token, gitlab.WithBaseURL(host)) if err != nil {