diff --git a/.vscode/launch.json b/.vscode/launch.json index 87b5782..76accb6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -14,6 +14,16 @@ "https://gitea.libretechconsulting.com" ] }, + { + "name": "List Aliases", + "type": "delve", + "request": "launch", + "program": "${workspaceFolder}", + "args": [ + "alias", + "ls" + ] + }, { "name": "Generate Docs", "type": "delve", diff --git a/cmd/alias.go b/cmd/alias.go deleted file mode 100644 index 0021967..0000000 --- a/cmd/alias.go +++ /dev/null @@ -1,25 +0,0 @@ -package cmd - -import ( - "github.com/spf13/cobra" -) - -var aliasCmd = &cobra.Command{ - Use: "alias", - Aliases: []string{"aliases", "a"}, - Short: "Manage project aliases", - Long: aliasCmdLong, - // Just re-use the hooks for project - PersistentPreRun: initProjectCmd, - PersistentPostRun: postProjectCmd, -} - -func init() { - rootCmd.AddCommand(aliasCmd) -} - -func mustHaveAliases(cmd *cobra.Command, args []string) { - if len(projectCache.Aliases) == 0 { - plog.Fatal("No aliases set, nothing to " + cmd.Name()) - } -} diff --git a/cmd/alias/alias.go b/cmd/alias/alias.go new file mode 100644 index 0000000..b8ac709 --- /dev/null +++ b/cmd/alias/alias.go @@ -0,0 +1,38 @@ +package alias + +import ( + "github.com/spf13/cobra" + + "gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util" +) + +var AliasCmd = &cobra.Command{ + Use: "alias", + Aliases: []string{"aliases", "a"}, + Short: "Manage project aliases", + Long: util.AliasCmdLong, + // Just re-use the hooks for project + PersistentPreRun: util.InitProjects, + PersistentPostRun: util.PostProjectCmd, +} + +var utils *util.Utils + +func aliasCmdPreRun(cmd *cobra.Command, args []string) { + utils = util.MustFromCtx(cmd.Context()) + util.InitProjects(cmd, args) +} + +func mustHaveAliases(cmd *cobra.Command, args []string) { + utils = util.MustFromCtx(cmd.Context()) + + if len(utils.Cache().Aliases) == 0 { + utils.Logger().Fatal("No aliases set, nothing to " + cmd.Name()) + } +} + +func init() { + AliasCmd.AddCommand(aliasAddCmd) + AliasCmd.AddCommand(aliasListCmd) + AliasCmd.AddCommand(aliasDeleteCmd) +} diff --git a/cmd/alias/alias_add.go b/cmd/alias/alias_add.go new file mode 100644 index 0000000..0d57196 --- /dev/null +++ b/cmd/alias/alias_add.go @@ -0,0 +1,108 @@ +package alias + +import ( + "fmt" + "strings" + + "github.com/pterm/pterm" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util" + "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/cache" + "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects" +) + +var aliasAddCmd = &cobra.Command{ + Use: "add", + Aliases: []string{"set", "a", "s"}, + Short: "Add a project alias", + Args: cobra.ArbitraryArgs, + Long: util.AliasAddCmdLong, + Run: runAddAliasCmd, +} + +func runAddAliasCmd(cmd *cobra.Command, args []string) { + var project *projects.Project + + // Check by flag + if projectID := viper.GetInt(util.ViperAliasAddPID); projectID > 0 { + utils.Logger().Debug(fmt.Sprintf("Adding for inbound project ID %d", projectID)) + project = utils.Cache().GetProjectByID(projectID) + } + + // Check by arg + if len(args) > 0 { + project = utils.FzfFindProject(&util.FzfProjectOpts{Ctx: cmd.Context(), Search: utils.SearchStringFromArgs(args)}) + } + + // Collect by fzf + if project == nil { + var err error + project, err = utils.FzfProject(&util.FzfProjectOpts{Ctx: cmd.Context()}) + if err != nil || project == nil { + utils.Logger().Fatal("No project to alias, nothing to do", utils.Logger().Args("error", err)) + } + } + + AddNewAliases(cmd, project.ID) +} + +func AddNewAliases(cmd *cobra.Command, projectID int) { + u := util.MustFromCtx(cmd.Context()) + project := u.Cache().GetProjectByID(projectID) + if project == nil { + u.Logger().Error("Failed to find project to alias", u.Logger().Args("projectID", projectID)) + return + } + + // Collect the aliases + aliases := PromptAliasesForProject(cmd, project) + + // Add aliases + for _, a := range aliases { + a = strings.Trim(a, " '\"%<>|`") + if a == "" { + continue + } + if err := u.Cache().AddAlias(a, project.ID, project.Remote); err != nil { + u.Logger().Debug("Skipping alias add", u.Logger().Args( + "error", err, + "alias", a, + )) + } else { + u.Logger().Info("Successfully added alias to project", u.Logger().Args( + "project", project.String(), + "alias", a, + )) + } + } +} + +func PromptAliasesForProject(cmd *cobra.Command, p *projects.Project) []string { + u := util.MustFromCtx(cmd.Context()) + aliases := u.Cache().GetProjectAliases(p) + if len(aliases) > 0 { + u.Logger().Info("Adding aliases to project", u.Logger().Args( + "project", p.String(), + "existingAliases", cache.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() { + aliasAddCmd.PersistentFlags().Int(util.FlagProjectID, 0, "Specify a project by ID") + + aliasAddCmd.RegisterFlagCompletionFunc(util.FlagProjectID, util.ValidProjectIdFunc) + + viper.BindPFlag(util.ViperAliasAddPID, aliasAddCmd.Flag(util.FlagProjectID)) +} diff --git a/cmd/alias_delete.go b/cmd/alias/alias_delete.go similarity index 61% rename from cmd/alias_delete.go rename to cmd/alias/alias_delete.go index d53f481..ca8c27e 100644 --- a/cmd/alias_delete.go +++ b/cmd/alias/alias_delete.go @@ -1,19 +1,20 @@ -package cmd +package alias import ( "fmt" + "gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util" + "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects" "github.com/pterm/pterm" "github.com/spf13/cobra" "github.com/spf13/viper" - "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects" ) var aliasDeleteCmd = &cobra.Command{ Use: "delete [fuzzy project or alias]", Aliases: []string{"rm", "del", "d"}, Short: "Delete a project alias", - Long: aliasDeleteCmdLong, + Long: util.AliasDeleteCmdLong, PreRun: mustHaveAliases, Run: runDeleteAliasCmd, } @@ -22,32 +23,32 @@ func runDeleteAliasCmd(cmd *cobra.Command, args []string) { var project *projects.Project var err error - fzfOpts := &fzfProjectOpts{ + fzfOpts := &util.FzfProjectOpts{ Ctx: cmd.Context(), MustHaveAlias: true, } if len(args) > 0 { - fzfOpts.Search = searchStringFromArgs(args) - project = fzfFindProject(fzfOpts) + fzfOpts.Search = utils.SearchStringFromArgs(args) + project = utils.FzfFindProject(fzfOpts) } else { - project, err = fzfProject(fzfOpts) + project, err = utils.FzfProject(fzfOpts) } if project == nil || err != nil { - plog.Fatal("Failed to find project to delete aliases from", plog.Args( + utils.Logger().Fatal("Failed to find project to delete aliases from", utils.Logger().Args( "error", err, )) } - aliasStrings := projectCache.GetProjectAliasStrings(project) + aliasStrings := utils.Cache().GetProjectAliasStrings(project) deletionCandidates, err := pterm.DefaultInteractiveMultiselect. WithOptions(aliasStrings). Show() if err != nil || len(deletionCandidates) < 1 { - plog.Fatal("Failed to find project to delete aliases from", plog.Args( + utils.Logger().Fatal("Failed to find project to delete aliases from", utils.Logger().Args( "error", err, )) } @@ -61,26 +62,25 @@ func runDeleteAliasCmd(cmd *cobra.Command, args []string) { Show() if !confirm { - plog.Warn("Alias deletion cancelled") + utils.Logger().Warn("Alias deletion cancelled") continue } - plog.Info("Deleting alias", plog.Args( + utils.Logger().Info("Deleting alias", utils.Logger().Args( "project", project.String(), "alias", a, )) - projectCache.DeleteAlias(projectCache.GetAliasByName(a)) + utils.Cache().DeleteAlias(utils.Cache().GetAliasByName(a)) } - fmt.Println(projectCache.ProjectString(project)) + fmt.Println(utils.Cache().ProjectString(project)) } func init() { - aliasCmd.AddCommand(aliasDeleteCmd) aliasDeleteCmd.PersistentFlags().Int("projectID", 0, "Specify a project by ID") - aliasDeleteCmd.RegisterFlagCompletionFunc("projectID", validProjectIdFunc) + aliasDeleteCmd.RegisterFlagCompletionFunc("projectID", util.ValidProjectIdFunc) viper.BindPFlag("alias.delete.projectID", aliasDeleteCmd.Flag("projectID")) } diff --git a/cmd/alias_list.go b/cmd/alias/alias_list.go similarity index 71% rename from cmd/alias_list.go rename to cmd/alias/alias_list.go index 9588746..bc3dce5 100644 --- a/cmd/alias_list.go +++ b/cmd/alias/alias_list.go @@ -1,8 +1,9 @@ -package cmd +package alias import ( "fmt" + "gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util" "github.com/pterm/pterm" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -14,20 +15,16 @@ var aliasListCmd = &cobra.Command{ Aliases: []string{"dump", "show", "ls", "ll", "l"}, Short: "List Aliases", PreRun: mustHaveAliases, - Long: aliasListCmdLong, + Long: util.AliasListCmdLong, Run: runListAliasCmd, } func runListAliasCmd(cmd *cobra.Command, args []string) { - remotes := viper.GetStringSlice(FlagRemote) + remotes := viper.GetStringSlice(util.FlagRemote) pterm.DefaultBox. WithLeftPadding(5).WithRightPadding(5). WithBoxStyle(&pterm.Style{pterm.FgLightBlue}). WithTitle(pterm.Bold.Sprint(pterm.LightGreen("Aliases by Project"))). - Print("\n" + projectCache.AliasesByProjectString(remotes...)) + Print("\n" + utils.Cache().AliasesByProjectString(remotes...)) fmt.Print("\n\n") } - -func init() { - aliasCmd.AddCommand(aliasListCmd) -} diff --git a/cmd/alias_add.go b/cmd/alias_add.go deleted file mode 100644 index 3139136..0000000 --- a/cmd/alias_add.go +++ /dev/null @@ -1,106 +0,0 @@ -package cmd - -import ( - "fmt" - "strings" - - "github.com/pterm/pterm" - "github.com/spf13/cobra" - "github.com/spf13/viper" - - "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/cache" - "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects" -) - -var aliasAddCmd = &cobra.Command{ - Use: "add", - Aliases: []string{"set", "a", "s"}, - Short: "Add a project alias", - Args: cobra.ArbitraryArgs, - Long: aliasAddCmdLong, - Run: runAddAliasCmd, -} - -func runAddAliasCmd(cmd *cobra.Command, args []string) { - var project *projects.Project - - // Check by flag - if projectID := viper.GetInt(ViperAliasAddPID); projectID > 0 { - plog.Debug(fmt.Sprintf("Adding for inbound project ID %d", projectID)) - project = projectCache.GetProjectByID(projectID) - } - - // Check by arg - if len(args) > 0 { - project = fzfFindProject(&fzfProjectOpts{Ctx: cmd.Context(), Search: searchStringFromArgs(args)}) - } - - // Collect by fzf - if project == nil { - var err error - project, err = fzfProject(&fzfProjectOpts{Ctx: 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 := projectCache.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 := projectCache.AddAlias(a, project.ID, project.Remote); 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 *projects.Project) []string { - aliases := projectCache.GetProjectAliases(p) - if len(aliases) > 0 { - plog.Info("Adding aliases to project", plog.Args( - "project", p.String(), - "existingAliases", cache.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(FlagProjectID, 0, "Specify a project by ID") - - aliasAddCmd.RegisterFlagCompletionFunc(FlagProjectID, validProjectIdFunc) - - viper.BindPFlag(ViperAliasAddPID, aliasAddCmd.Flag(FlagProjectID)) -} diff --git a/cmd/cache.go b/cmd/cache.go deleted file mode 100644 index c0119f1..0000000 --- a/cmd/cache.go +++ /dev/null @@ -1,38 +0,0 @@ -package cmd - -import ( - "time" - - "github.com/spf13/cobra" - "github.com/spf13/viper" - - "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/cache" -) - -var projectCache *cache.Cache - -var cacheCmd = &cobra.Command{ - Use: "cache", - Aliases: []string{"a", "ln"}, - Short: "Manage Git project cache", - Long: cacheCmdLong, -} - -func runCacheCmd(cmd *cobra.Command, args []string) { - initProjectCache(cmd, args) - projectCache.LockCache() -} - -func postCacheCmd(cmd *cobra.Command, args []string) { - postProjectCache(cmd, args) - projectCache.UnlockCache() -} - -func init() { - rootCmd.AddCommand(cacheCmd) - - cacheCmd.PersistentFlags().Duration("ttl", 48*time.Hour, - "Duration before cache is re-built in go time.Duration format") - - viper.BindPFlags(cacheCmd.Flags()) -} diff --git a/cmd/cache/cache.go b/cmd/cache/cache.go new file mode 100644 index 0000000..56f1fca --- /dev/null +++ b/cmd/cache/cache.go @@ -0,0 +1,43 @@ +package cache + +import ( + "time" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util" +) + +var CacheCmd = &cobra.Command{ + Use: "cache", + Aliases: []string{"a", "ln"}, + Short: "Manage Git project cache", + Long: util.CacheCmdLong, +} + +var utils *util.Utils + +func runCacheCmd(cmd *cobra.Command, args []string) { + utils = util.MustFromCtx(cmd.Context()) + + utils.InitProjectCache(cmd, args) + utils.Cache().LockCache() +} + +func postCacheCmd(cmd *cobra.Command, args []string) { + utils.PostProjectCache(cmd, args) + utils.Cache().UnlockCache() +} + +func init() { + CacheCmd.PersistentFlags().Duration("ttl", 48*time.Hour, + "Duration before cache is re-built in go time.Duration format") + + viper.BindPFlags(CacheCmd.Flags()) + + CacheCmd.AddCommand(clearCmd) + CacheCmd.AddCommand(dumpCmd) + CacheCmd.AddCommand(loadCmd) + CacheCmd.AddCommand(unlockCmd) +} diff --git a/cmd/cache_clear.go b/cmd/cache/cache_clear.go similarity index 88% rename from cmd/cache_clear.go rename to cmd/cache/cache_clear.go index f03bcd4..a6c1a15 100644 --- a/cmd/cache_clear.go +++ b/cmd/cache/cache_clear.go @@ -1,4 +1,4 @@ -package cmd +package cache import ( "github.com/spf13/cobra" @@ -21,11 +21,10 @@ var clearCmd = &cobra.Command{ func clearCache(cmd *cobra.Command, args []string) { slog.Debug("Preparing to clear local cache") - projectCache.Clear(conf.Cache.Clear.ClearAliases) + utils.Cache().Clear(utils.Config().Cache.Clear.ClearAliases) } func init() { - cacheCmd.AddCommand(clearCmd) clearCmd.Flags().Bool("clearAliases", false, "Will also clear aliases from the cache, use with caution") viper.BindPFlag("cache.clear.clearAliases", clearCmd.LocalFlags().Lookup("clearAliases")) } diff --git a/cmd/cache_dump.go b/cmd/cache/cache_dump.go similarity index 62% rename from cmd/cache_dump.go rename to cmd/cache/cache_dump.go index 7210038..68b03d9 100644 --- a/cmd/cache_dump.go +++ b/cmd/cache/cache_dump.go @@ -1,10 +1,12 @@ -package cmd +package cache import ( "fmt" "github.com/spf13/cobra" "github.com/spf13/viper" + + "gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util" ) var dumpCmd = &cobra.Command{ @@ -17,16 +19,15 @@ var dumpCmd = &cobra.Command{ } func runCacheDunpCmd(cmd *cobra.Command, args []string) { - remotes := viper.GetStringSlice(FlagRemote) - if conf.Dump.Full { - fmt.Println(projectCache.DumpString(true, searchStringFromArgs(args), remotes...)) + remotes := viper.GetStringSlice(util.FlagRemote) + if utils.Config().Dump.Full { + fmt.Println(utils.Cache().DumpString(true, utils.SearchStringFromArgs(args), remotes...)) } else { - plog.Info(projectCache.String()) + utils.Logger().Info(utils.Cache().String()) } } func init() { - cacheCmd.AddCommand(dumpCmd) dumpCmd.PersistentFlags().BoolP("full", "f", false, "Dumps entire cache") viper.BindPFlag("dump.full", dumpCmd.LocalFlags().Lookup("full")) } diff --git a/cmd/cache_load.go b/cmd/cache/cache_load.go similarity index 62% rename from cmd/cache_load.go rename to cmd/cache/cache_load.go index 33028e0..a4518c1 100644 --- a/cmd/cache_load.go +++ b/cmd/cache/cache_load.go @@ -1,8 +1,10 @@ -package cmd +package cache import ( "github.com/spf13/cobra" "github.com/spf13/viper" + + "gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util" ) var loadCmd = &cobra.Command{ @@ -17,15 +19,13 @@ wants to find a new project.`, } func loadCache(cmd *cobra.Command, args []string) { - remotes := viper.GetStringSlice(FlagRemote) - projectCache.Refresh(remotes...) + remotes := viper.GetStringSlice(util.FlagRemote) + utils.Cache().Refresh(remotes...) } func init() { - cacheCmd.AddCommand(loadCmd) - - loadCmd.PersistentFlags().Bool(FlagOwnerOnly, true, + loadCmd.PersistentFlags().Bool(util.FlagOwnerOnly, true, "Only load projects that you are owner of") - viper.BindPFlag(ViperCacheLoadOwnerOnly, loadCmd.Flag(FlagOwnerOnly)) + viper.BindPFlag(util.ViperCacheLoadOwnerOnly, loadCmd.Flag(util.FlagOwnerOnly)) } diff --git a/cmd/cache/cache_unlock.go b/cmd/cache/cache_unlock.go new file mode 100644 index 0000000..93ed97b --- /dev/null +++ b/cmd/cache/cache_unlock.go @@ -0,0 +1,33 @@ +package cache + +import ( + "github.com/pterm/pterm" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util" +) + +var unlockCmd = &cobra.Command{ + Use: "unlock", + Short: "unlock Git project cache", + Long: `unlocks cache to display`, + Run: func(cmd *cobra.Command, args []string) { + utils = util.MustFromCtx(cmd.Context()) + utils.InitProjectCache(cmd, args) + if viper.GetBool(util.ViperCacheUnlockForce) { + utils.Cache().UnlockCache() + } else if yes, _ := pterm.DefaultInteractiveConfirm. + WithDefaultValue(false). + Show("Are you sure you want to manually unlock?"); yes { + utils.Cache().UnlockCache() + } else { + utils.Logger().Error("You failed to confirm cache unlock") + } + }, +} + +func init() { + unlockCmd.PersistentFlags().BoolP(util.FlagCacheForce, "f", false, "force unlocks cache (don't ask)") + viper.BindPFlag(util.ViperCacheUnlockForce, unlockCmd.LocalFlags().Lookup(util.FlagCacheForce)) +} diff --git a/cmd/cache_unlock.go b/cmd/cache_unlock.go deleted file mode 100644 index 595b02e..0000000 --- a/cmd/cache_unlock.go +++ /dev/null @@ -1,31 +0,0 @@ -package cmd - -import ( - "github.com/pterm/pterm" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -var unlockCmd = &cobra.Command{ - Use: "unlock", - Short: "unlock Git project cache", - Long: `unlocks cache to display`, - Run: func(cmd *cobra.Command, args []string) { - initProjectCache(cmd, args) - if viper.GetBool(ViperCacheUnlockForce) { - projectCache.UnlockCache() - } else if yes, _ := pterm.DefaultInteractiveConfirm. - WithDefaultValue(false). - Show("Are you sure you want to manually unlock?"); yes { - projectCache.UnlockCache() - } else { - plog.Error("You failed to confirm cache unlock") - } - }, -} - -func init() { - cacheCmd.AddCommand(unlockCmd) - unlockCmd.PersistentFlags().BoolP(FlagCacheForce, "f", false, "force unlocks cache (don't ask)") - viper.BindPFlag(ViperCacheUnlockForce, unlockCmd.LocalFlags().Lookup(FlagCacheForce)) -} diff --git a/cmd/config.go b/cmd/config.go deleted file mode 100644 index 262bf37..0000000 --- a/cmd/config.go +++ /dev/null @@ -1,16 +0,0 @@ -package cmd - -import ( - "github.com/spf13/cobra" -) - -var configCmd = &cobra.Command{ - Use: "config", - Short: "Git Project Manager Configuration", - Aliases: []string{"conf"}, - Long: configCmdLong, -} - -func init() { - rootCmd.AddCommand(configCmd) -} diff --git a/cmd/config/config.go b/cmd/config/config.go new file mode 100644 index 0000000..54cab1a --- /dev/null +++ b/cmd/config/config.go @@ -0,0 +1,26 @@ +package cmd + +import ( + "github.com/spf13/cobra" + + "gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util" +) + +var ConfigCmd = &cobra.Command{ + Use: "config", + Short: "Git Project Manager Configuration", + Aliases: []string{"conf"}, + Long: util.ConfigCmdLong, + PersistentPreRun: configCmdPreRun, +} + +var utils *util.Utils + +func configCmdPreRun(cmd *cobra.Command, args []string) { + utils = util.MustFromCtx(cmd.Context()) +} + +func init() { + ConfigCmd.AddCommand(configGenerateCmd) + ConfigCmd.AddCommand(configShowCmd) +} diff --git a/cmd/config_generate.go b/cmd/config/config_generate.go similarity index 78% rename from cmd/config_generate.go rename to cmd/config/config_generate.go index 9cff9d5..7e83ed4 100644 --- a/cmd/config_generate.go +++ b/cmd/config/config_generate.go @@ -10,6 +10,7 @@ import ( "github.com/spf13/viper" "gopkg.in/yaml.v3" + "gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util" "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/config" "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/info" ) @@ -18,7 +19,7 @@ var configGenerateCmd = &cobra.Command{ Use: "generate", Short: "Generate a default configuration", Aliases: []string{"gen", "new", "default"}, - Long: configGenCmdLong, + Long: util.ConfigGenCmdLong, Run: runConfigGenerateCmd, } @@ -27,34 +28,34 @@ func runConfigGenerateCmd(cmd *cobra.Command, args []string) { if viper.ConfigFileUsed() != "" { configFile = viper.ConfigFileUsed() } else { - configFile = defConfigPath + configFile = util.DefConfigPath } - plog.Debug("Using config file " + configFile) + utils.Logger().Debug("Using config file " + configFile) - configFile, _ = resolvePath(configFile) + configFile, _ = util.ResolvePath(configFile) - newConf := promptConfigSettings(&conf) + newConf := promptConfigSettings(utils.Config()) - if write, _ := cmd.Flags().GetBool(FlagWrite); write { + if write, _ := cmd.Flags().GetBool(util.FlagWrite); write { write, _ := pterm.DefaultInteractiveContinue. WithDefaultText("Really write config file?"). WithOptions([]string{"yes", "no"}). Show() if write != "yes" { - plog.Fatal("Aborting config file write") + utils.Logger().Fatal("Aborting config file write") } writeConfigFile(newConf, configFile) - plog.Info("Wrote config to file", - plog.Args("file", configFile)) + utils.Logger().Info("Wrote config to file", + utils.Logger().Args("file", configFile)) } else { var c bytes.Buffer enc := yaml.NewEncoder(&c) enc.SetIndent(2) enc.Encode(newConf) - plog.Info("Suggest running with --write or redirect (> " + configFile + ")") + utils.Logger().Info("Suggest running with --write or redirect (> " + configFile + ")") fmt.Print(c.String()) } } @@ -62,11 +63,11 @@ func runConfigGenerateCmd(cmd *cobra.Command, args []string) { func writeConfigFile(c *config.Config, path string) { file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o640) if err != nil { - plog.Error("Failed to prepare config for writing", plog.Args("error", err)) + utils.Logger().Error("Failed to prepare config for writing", utils.Logger().Args("error", err)) } enc := yaml.NewEncoder(file) if err := enc.Encode(c); err != nil { - plog.Fatal("Failed writing config file", plog.Args("file", path)) + utils.Logger().Fatal("Failed writing config file", utils.Logger().Args("file", path)) } } @@ -149,7 +150,6 @@ func promptConfigSettings(c *config.Config) *config.Config { } func init() { - configCmd.AddCommand(configGenerateCmd) - configGenerateCmd.PersistentFlags().Bool(FlagPrompt, false, "Prompt for settings") - configGenerateCmd.PersistentFlags().Bool(FlagWrite, false, "Write config to file") + configGenerateCmd.PersistentFlags().Bool(util.FlagPrompt, false, "Prompt for settings") + configGenerateCmd.PersistentFlags().Bool(util.FlagWrite, false, "Write config to file") } diff --git a/cmd/config_show.go b/cmd/config/config_show.go similarity index 58% rename from cmd/config_show.go rename to cmd/config/config_show.go index e0390be..97185ef 100644 --- a/cmd/config_show.go +++ b/cmd/config/config_show.go @@ -6,6 +6,8 @@ import ( "github.com/spf13/cobra" "gopkg.in/yaml.v3" + + "gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util" ) var configShowCmd = &cobra.Command{ @@ -16,16 +18,16 @@ var configShowCmd = &cobra.Command{ } func runConfigShowCmd(cmd *cobra.Command, args []string) { - c := conf + c := *utils.Config() - showSensitive, _ := cmd.Flags().GetBool(FlagSensitive) + showSensitive, _ := cmd.Flags().GetBool(util.FlagSensitive) if !showSensitive { - plog.Info("Sensitive fields hidden, do not use unreviewed as config") + utils.Logger().Info("Sensitive fields hidden, do not use unreviewed as config") for _, r := range c.Remotes { r.Token = strings.Repeat("*", len(r.Token)) } } else { - plog.Warn("Displaying sensitive fields!") + utils.Logger().Warn("Displaying sensitive fields!") } enc := yaml.NewEncoder(os.Stdout) @@ -34,6 +36,5 @@ func runConfigShowCmd(cmd *cobra.Command, args []string) { } func init() { - configCmd.AddCommand(configShowCmd) - configShowCmd.Flags().BoolP(FlagSensitive, "s", false, "Set to show sensitive fields") + configShowCmd.Flags().BoolP(util.FlagSensitive, "s", false, "Set to show sensitive fields") } diff --git a/cmd/docs.go b/cmd/docs.go index 26b0ae3..eeb2f31 100644 --- a/cmd/docs.go +++ b/cmd/docs.go @@ -5,6 +5,8 @@ import ( "github.com/spf13/cobra" "github.com/spf13/cobra/doc" + + "gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util" ) var docsCmd = &cobra.Command{ @@ -17,9 +19,9 @@ var docsCmd = &cobra.Command{ } func runDocsCmd(cmd *cobra.Command, args []string) { - outDir, err := cmd.Flags().GetString(FlagDocsPath) + outDir, err := cmd.Flags().GetString(util.FlagDocsPath) if err != nil { - plog.Error("missing docs path") + utils.Logger().Error("missing docs path") } prepareDocsDir(cmd, outDir) @@ -35,10 +37,10 @@ func runDocsCmd(cmd *cobra.Command, args []string) { case "yaml": err = doc.GenYamlTree(cmd.Root(), outDir) default: - plog.Error("invalid docs type", plog.Args("type", args[0])) + utils.Logger().Error("invalid docs type", utils.Logger().Args("type", args[0])) } - plog.Info("docs generation complete", plog.Args( + utils.Logger().Info("docs generation complete", utils.Logger().Args( "type", args[0], "docsDir", outDir, "err", err)) } @@ -47,14 +49,14 @@ func prepareDocsDir(_ *cobra.Command, outDir string) { if err != nil { err = os.Mkdir(outDir, 0o755) if err != nil { - plog.Error("failed to create docs path", plog.Args( + utils.Logger().Error("failed to create docs path", utils.Logger().Args( "error", err.Error(), "docsDir", outDir)) } } } func init() { - docsCmd.PersistentFlags().StringP(FlagDocsPath, "d", "./docs", "specify output directory for documentation") + docsCmd.PersistentFlags().StringP(util.FlagDocsPath, "d", "./docs", "specify output directory for documentation") rootCmd.AddCommand(docsCmd) } diff --git a/cmd/project.go b/cmd/project.go deleted file mode 100644 index 7724603..0000000 --- a/cmd/project.go +++ /dev/null @@ -1,67 +0,0 @@ -package cmd - -import ( - "github.com/spf13/cobra" - "github.com/spf13/viper" - - "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/cache" - "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects" -) - -var projectCmd = &cobra.Command{ - Use: "project [fuzzy alias search]", - Short: "Use a Git project", - Aliases: []string{"proj", "projects", "p"}, - Args: cobra.MaximumNArgs(1), - ArgAliases: []string{"alias"}, - Long: projCmdLong, - PersistentPreRun: initProjectCmd, - PersistentPostRun: postProjectCmd, - // Run: projectGoCmdRun, -} - -func getProject(args []string) *projects.Project { - remotes := viper.GetStringSlice(FlagRemote) - fzfOpts := &fzfProjectOpts{ - Ctx: rootCmd.Context(), - Search: searchStringFromArgs(args), - Remotes: remotes, - } - project := fzfFindProject(fzfOpts) - - if project == nil { - plog.Fatal("Failed to find a project, nothing to do") - } else { - plog.Debug("Houston, we have a project", plog.Args( - "project", project.String(), - "aliases", cache.ProjectAliasesString( - projectCache.GetProjectAliases(project)), - )) - } - - if len(projectCache.GetProjectAliases(project)) == 0 { - plog.Info("New project, set aliases or press enter for default") - addNewAliases(project.ID) - } - - return project -} - -func initProjectCmd(cmd *cobra.Command, args []string) { - initProjectCache(cmd, args) - mustHaveProjects(cmd, args) -} - -func postProjectCmd(cmd *cobra.Command, args []string) { - postProjectCache(cmd, args) -} - -func init() { - rootCmd.AddCommand(projectCmd) -} - -func mustHaveProjects(cmd *cobra.Command, _ []string) { - if len(projectCache.Projects) == 0 { - plog.Fatal("No projects to " + cmd.Name() + ", try running cache load") - } -} diff --git a/cmd/project/project.go b/cmd/project/project.go new file mode 100644 index 0000000..59cc261 --- /dev/null +++ b/cmd/project/project.go @@ -0,0 +1,66 @@ +package project + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/alias" + "gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util" + "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/cache" + "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects" +) + +var ProjectCmd = &cobra.Command{ + Use: "project [fuzzy alias search]", + Short: "Use a Git project", + Aliases: []string{"proj", "projects", "p"}, + Args: cobra.MaximumNArgs(1), + ArgAliases: []string{"alias"}, + Long: util.ProjCmdLong, + PersistentPreRun: projectCmdPreRun, + PersistentPostRun: util.PostProjectCmd, + // Run: projectGoCmdRun, +} + +var utils *util.Utils + +func projectCmdPreRun(cmd *cobra.Command, args []string) { + utils = util.MustFromCtx(cmd.Context()) + util.InitProjects(cmd, args) +} + +func getProject(cmd *cobra.Command, args []string) *projects.Project { + remotes := viper.GetStringSlice(util.FlagRemote) + fzfOpts := &util.FzfProjectOpts{ + Ctx: cmd.Context(), + Search: utils.SearchStringFromArgs(args), + Remotes: remotes, + } + project := utils.FzfFindProject(fzfOpts) + + if project == nil { + utils.Logger().Fatal("Failed to find a project, nothing to do") + } else { + utils.Logger().Debug("Houston, we have a project", utils.Logger().Args( + "project", project.String(), + "aliases", cache.ProjectAliasesString( + utils.Cache().GetProjectAliases(project)), + )) + } + + if len(utils.Cache().GetProjectAliases(project)) == 0 { + utils.Logger().Info("New project, set aliases or press enter for default") + alias.AddNewAliases(cmd, project.ID) + } + + return project +} + +func init() { + ProjectCmd.AddCommand(projectAddCmd) + ProjectCmd.AddCommand(projectGoCmd) + ProjectCmd.AddCommand(projectListCmd) + ProjectCmd.AddCommand(projectOpenCmd) + ProjectCmd.AddCommand(projectRunCmd) + ProjectCmd.AddCommand(projectShowCmd) +} diff --git a/cmd/project_add.go b/cmd/project/project_add.go similarity index 64% rename from cmd/project_add.go rename to cmd/project/project_add.go index 5942fbe..d71b353 100644 --- a/cmd/project_add.go +++ b/cmd/project/project_add.go @@ -1,21 +1,19 @@ -package cmd +package project import ( "github.com/spf13/cobra" + + "gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util" ) var projectAddCmd = &cobra.Command{ Use: "add", Short: "Add a new Git project", Aliases: []string{"a", "alias"}, - Long: projAddCmdLong, + Long: util.ProjAddCmdLong, Run: projectAddCmdRun, } func projectAddCmdRun(cmd *cobra.Command, args []string) { - getProject(args) -} - -func init() { - projectCmd.AddCommand(projectAddCmd) + getProject(cmd, args) } diff --git a/cmd/project_go.go b/cmd/project/project_go.go similarity index 50% rename from cmd/project_go.go rename to cmd/project/project_go.go index 60c5e88..f2109df 100644 --- a/cmd/project_go.go +++ b/cmd/project/project_go.go @@ -1,4 +1,4 @@ -package cmd +package project import ( "fmt" @@ -7,6 +7,8 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" + + "gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util" ) var projectGoCmd = &cobra.Command{ @@ -15,30 +17,30 @@ var projectGoCmd = &cobra.Command{ Aliases: []string{"goto", "cd"}, Args: cobra.MaximumNArgs(1), ArgAliases: []string{"project"}, - ValidArgsFunction: validAliasesFunc, - Long: projGoCmdLong, + ValidArgsFunction: util.ValidAliasesFunc, + Long: util.ProjGoCmdLong, Run: projectGoCmdRun, } func projectGoCmdRun(cmd *cobra.Command, args []string) { - remotes := viper.GetStringSlice(FlagRemote) - fzfOpts := &fzfProjectOpts{ + remotes := viper.GetStringSlice(util.FlagRemote) + fzfOpts := &util.FzfProjectOpts{ Ctx: cmd.Context(), - Search: searchStringFromArgs(args), + Search: utils.SearchStringFromArgs(args), MustHaveAlias: true, Remotes: remotes, } - project := fzfSearchProjectAliases(fzfOpts) + project := utils.FzfSearchProjectAliases(fzfOpts) if project == nil { - plog.Fatal("No project selected, nowhere to go") + utils.Logger().Fatal("No project selected, nowhere to go") } - projectCache.GoTo(project) - project.SetRepo(projectCache.OpenProject(cmd.Context(), project)) + utils.Cache().GoTo(project) + project.SetRepo(utils.Cache().OpenProject(cmd.Context(), project)) - plog.Debug("Project ready", plog.Args( - "path", projectCache.GetProjectPath(project), + utils.Logger().Debug("Project ready", utils.Logger().Args( + "path", utils.Cache().GetProjectPath(project), "project", project, )) @@ -46,10 +48,6 @@ func projectGoCmdRun(cmd *cobra.Command, args []string) { // This should be read by any source command, for instance // `cd "$(git-project-manager projects cd somealias)"` - fmt.Println(projectCache.GetProjectPath(project)) - exec.Command("cd", projectCache.GetProjectPath(project)).Run() -} - -func init() { - projectCmd.AddCommand(projectGoCmd) + fmt.Println(utils.Cache().GetProjectPath(project)) + exec.Command("cd", utils.Cache().GetProjectPath(project)).Run() } diff --git a/cmd/project/project_list.go b/cmd/project/project_list.go new file mode 100644 index 0000000..4a10646 --- /dev/null +++ b/cmd/project/project_list.go @@ -0,0 +1,28 @@ +package project + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util" +) + +var projectListCmd = &cobra.Command{ + Use: "list", + Short: "List Git Projects", + Aliases: []string{"ls", "l"}, + Long: util.ProjListCmdLong, + Run: projectListCmdRun, +} + +func projectListCmdRun(cmd *cobra.Command, args []string) { + remotes := viper.GetStringSlice(util.FlagRemote) + fmt.Println(utils.Cache().DumpString(viper.GetBool(util.ViperProjectListAll), utils.SearchStringFromArgs(args), remotes...)) +} + +func init() { + projectListCmd.PersistentFlags().Bool(util.FlagAll, false, "List all, not just cloned locally") + viper.BindPFlag(util.ViperProjectListAll, projectListCmd.Flag(util.FlagAll)) +} diff --git a/cmd/project_open.go b/cmd/project/project_open.go similarity index 69% rename from cmd/project_open.go rename to cmd/project/project_open.go index 9d6eb35..16048da 100644 --- a/cmd/project_open.go +++ b/cmd/project/project_open.go @@ -1,6 +1,7 @@ -package cmd +package project import ( + "context" "errors" "os" "os/exec" @@ -8,6 +9,8 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" + + "gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util" ) var projectOpenCmd = &cobra.Command{ @@ -15,8 +18,8 @@ var projectOpenCmd = &cobra.Command{ Short: "Open project in your IDE", Aliases: []string{"goto", "cd"}, Args: cobra.OnlyValidArgs, - ValidArgsFunction: validAliasesFunc, - Long: projOpenCmdLong, + ValidArgsFunction: util.ValidAliasesFunc, + Long: util.ProjOpenCmdLong, Run: projectOpenCmdRun, } @@ -48,47 +51,47 @@ func projectOpenCmdRun(cmd *cobra.Command, args []string) { // Find an editor editor := findEditor() if editor == "" { - plog.Fatal("No usable editor found") + utils.Logger().Fatal("No usable editor found") } - remotes := viper.GetStringSlice(FlagRemote) - fzfOpts := &fzfProjectOpts{ + remotes := viper.GetStringSlice(util.FlagRemote) + fzfOpts := &util.FzfProjectOpts{ Ctx: cmd.Context(), - Search: searchStringFromArgs(args), + Search: utils.SearchStringFromArgs(args), Remotes: remotes, } - project := fzfCwdOrSearchProjectAliases(fzfOpts) + project := utils.FzfCwdOrSearchProjectAliases(fzfOpts) if project == nil { - plog.Fatal("No project to open, nothing to do") + utils.Logger().Fatal("No project to open, nothing to do") } // Check the project - path := projectCache.GetProjectPath(project) + path := utils.Cache().GetProjectPath(project) if _, err := os.Stat(path); err != nil { - plog.Fatal("Unable to open project", plog.Args("error", err)) + utils.Logger().Fatal("Unable to open project", utils.Logger().Args("error", err)) } // Open the project with the editor file := getEntrypointFile(path) - openEditor(editor, path+"/"+file) + openEditor(cmd.Context(), editor, path+"/"+file) } -func openEditor(editor string, path string) { +func openEditor(ctx context.Context, editor string, path string) { // Compile arguments args := make([]string, 0, 1) - if conf.Editor.OpenFlags != "" { - args = append(args, conf.Editor.OpenFlags) + if utils.Config().Editor.OpenFlags != "" { + args = append(args, utils.Config().Editor.OpenFlags) } args = append(args, path) // Launch editor - cmd := exec.CommandContext(rootCmd.Context(), editor, args...) + cmd := exec.CommandContext(ctx, editor, args...) cmd.Dir = filepath.Dir(path) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { - plog.Error("Failed to open project", plog.Args( + utils.Logger().Error("Failed to open project", utils.Logger().Args( "error", err.Error(), "command", cmd.String(), )) @@ -100,7 +103,7 @@ func openEditor(editor string, path string) { func getEntrypointFile(projectPath string) string { if err := os.Chdir(projectPath); err != nil { return "" - } else if conf.Editor.OpenDirectory { + } else if utils.Config().Editor.OpenDirectory { return "." } @@ -119,10 +122,10 @@ func findEditor() string { var err error // First try configured editor - if conf.Editor.Binary != "" { - editor, err = getEditor(conf.Editor.Binary) + if utils.Config().Editor.Binary != "" { + editor, err = getEditor(utils.Config().Editor.Binary) if err != nil || editor == "" { - plog.Error("Configured editor is not usable, finding others", plog.Args( + utils.Logger().Error("Configured editor is not usable, finding others", utils.Logger().Args( "error", err, )) } @@ -130,7 +133,7 @@ func findEditor() string { // Then try to find a known editor if editor == "" || err != nil { - conf.Editor.OpenFlags = "" + utils.Config().Editor.OpenFlags = "" for _, e := range knownEditors { path, err := getEditor(e) if err == nil && path != "" { @@ -140,9 +143,9 @@ func findEditor() string { } } - plog.Debug("Editor found for open", plog.Args( + utils.Logger().Debug("Editor found for open", utils.Logger().Args( "editor", editor, - "command", editor+" "+conf.Editor.OpenFlags, + "command", editor+" "+utils.Config().Editor.OpenFlags, )) return editor @@ -164,7 +167,7 @@ func getEditorPath(editor string) (string, error) { editor, _ = exec.LookPath(editor) } - return resolvePath(editor) + return util.ResolvePath(editor) } func isEditorExecutable(editor string) bool { @@ -180,7 +183,6 @@ func isEditorExecutable(editor string) bool { } func init() { - projectCmd.AddCommand(projectOpenCmd) projectOpenCmd.PersistentFlags().String("displayName", "", "Set display name of editor (meant for config file)") projectOpenCmd.PersistentFlags().String("binary", "", "Path to editor binary") projectOpenCmd.PersistentFlags().String("openFlags", "", "Optional flags when opening project (e.g. --reuse-window)") diff --git a/cmd/project_run.go b/cmd/project/project_run.go similarity index 58% rename from cmd/project_run.go rename to cmd/project/project_run.go index 4c8c531..49335f4 100644 --- a/cmd/project_run.go +++ b/cmd/project/project_run.go @@ -1,4 +1,4 @@ -package cmd +package project import ( "fmt" @@ -7,35 +7,37 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" + + "gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util" ) var projectRunCmd = &cobra.Command{ Use: "run", Short: "Run the project (e.g. go run .)", Aliases: []string{"exec", "r"}, - Long: projRunCmdLong, + Long: util.ProjRunCmdLong, Run: projectRunCmdRun, } func projectRunCmdRun(cmd *cobra.Command, args []string) { - remotes := viper.GetStringSlice(FlagRemote) - fzfOpts := &fzfProjectOpts{ + remotes := viper.GetStringSlice(util.FlagRemote) + fzfOpts := &util.FzfProjectOpts{ Ctx: cmd.Context(), - Search: searchStringFromArgs(args), + Search: utils.SearchStringFromArgs(args), Remotes: remotes, } - project := fzfCwdOrSearchProjectAliases(fzfOpts) + project := utils.FzfCwdOrSearchProjectAliases(fzfOpts) if project == nil { - plog.Fatal("No project selected, nothing to open") + utils.Logger().Fatal("No project selected, nothing to open") } lang := project.GetLanguage() if lang == nil { - plog.Fatal("Git remote isn't sure what language this project is... can't run.") + utils.Logger().Fatal("Git remote isn't sure what language this project is... can't run.") } - plog.Debug(fmt.Sprintf("Project is written in %s, %.2f%% coverage", lang.Name, lang.Percentage)) + utils.Logger().Debug(fmt.Sprintf("Project is written in %s, %.2f%% coverage", lang.Name, lang.Percentage)) switch lang.Name { case "Go": @@ -50,5 +52,5 @@ func projectRunCmdRun(cmd *cobra.Command, args []string) { } func init() { - projectCmd.AddCommand(projectRunCmd) + ProjectCmd.AddCommand(projectRunCmd) } diff --git a/cmd/project_show.go b/cmd/project/project_show.go similarity index 55% rename from cmd/project_show.go rename to cmd/project/project_show.go index fd90325..f92c2da 100644 --- a/cmd/project_show.go +++ b/cmd/project/project_show.go @@ -1,4 +1,4 @@ -package cmd +package project import ( "fmt" @@ -8,6 +8,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" + "gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util" "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects" ) @@ -16,7 +17,7 @@ var projectShowCmd = &cobra.Command{ Short: "Show detail for a Git project", Aliases: []string{"cat", "s"}, Args: cobra.ArbitraryArgs, - Long: projShowCmdLong, + Long: util.ProjShowCmdLong, Run: projectShowCmdRun, } @@ -24,24 +25,24 @@ func projectShowCmdRun(cmd *cobra.Command, args []string) { var project *projects.Project var inCwd bool - remotes := viper.GetStringSlice(FlagRemote) - fzfOpts := &fzfProjectOpts{ + remotes := viper.GetStringSlice(util.FlagRemote) + fzfOpts := &util.FzfProjectOpts{ Ctx: cmd.Context(), - Search: searchStringFromArgs(args), + Search: utils.SearchStringFromArgs(args), Remotes: remotes, } // Try to find project from current directory - if viper.GetBool(ViperProjectShowCurrent) { + if viper.GetBool(util.ViperProjectShowCurrent) { var err error - project, err = projectCache.GetProjectFromCwd() + project, err = utils.Cache().GetProjectFromCwd() if err != nil { // Not an error because we're still going to try to find a project - plog.Warn("Failed to get project from current directory", plog.Args( + utils.Logger().Warn("Failed to get project from current directory", utils.Logger().Args( "error", err, )) } else if project == nil { - plog.Warn("Failed to use --current flag, project not found in current path") + utils.Logger().Warn("Failed to use --current flag, project not found in current path") } else { inCwd = true } @@ -49,15 +50,15 @@ func projectShowCmdRun(cmd *cobra.Command, args []string) { // Otherwise find from the given search string if project == nil { - project = fzfFindProject(fzfOpts) + project = utils.FzfFindProject(fzfOpts) } // Do a full fuzzy find if all else fails if project == nil { var err error - project, err = fzfProject(fzfOpts) + project, err = utils.FzfProject(fzfOpts) if err != nil || project == nil { - plog.Fatal("Failed to find project, nothing to show", plog.Args( + utils.Logger().Fatal("Failed to find project, nothing to show", utils.Logger().Args( "error", err, )) } @@ -68,17 +69,17 @@ func projectShowCmdRun(cmd *cobra.Command, args []string) { WithLeftPadding(5).WithRightPadding(5). WithBoxStyle(&pterm.Style{pterm.FgLightBlue}). WithTitle(pterm.Bold.Sprint(pterm.LightGreen("Project Information"))). - Println(projectCache.ProjectString(project)) + Println(utils.Cache().ProjectString(project)) fmt.Println() if inCwd { - project.SetRepo(projectCache.OpenProject(cmd.Context(), project)) + project.SetRepo(utils.Cache().OpenProject(cmd.Context(), project)) fmt.Fprintln(os.Stderr, project.GetGitInfo()+"\n") } } func init() { - projectCmd.AddCommand(projectShowCmd) - projectShowCmd.PersistentFlags().Bool(FlagCurrent, false, "Use project in CWD rather than fuzzy find") - viper.BindPFlag(ViperProjectShowCurrent, projectShowCmd.Flag(FlagCurrent)) + ProjectCmd.AddCommand(projectShowCmd) + projectShowCmd.PersistentFlags().Bool(util.FlagCurrent, false, "Use project in CWD rather than fuzzy find") + viper.BindPFlag(util.ViperProjectShowCurrent, projectShowCmd.Flag(util.FlagCurrent)) } diff --git a/cmd/project_list.go b/cmd/project_list.go deleted file mode 100644 index 79b242d..0000000 --- a/cmd/project_list.go +++ /dev/null @@ -1,27 +0,0 @@ -package cmd - -import ( - "fmt" - - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -var projectListCmd = &cobra.Command{ - Use: "list", - Short: "List Git Projects", - Aliases: []string{"ls", "l"}, - Long: projListCmdLong, - Run: projectListCmdRun, -} - -func projectListCmdRun(cmd *cobra.Command, args []string) { - remotes := viper.GetStringSlice(FlagRemote) - fmt.Println(projectCache.DumpString(viper.GetBool(ViperProjectListAll), searchStringFromArgs(args), remotes...)) -} - -func init() { - projectCmd.AddCommand(projectListCmd) - projectListCmd.PersistentFlags().Bool(FlagAll, false, "List all, not just cloned locally") - viper.BindPFlag(ViperProjectListAll, projectListCmd.Flag(FlagAll)) -} diff --git a/cmd/root.go b/cmd/root.go index 60fac1f..23768a5 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -12,28 +12,32 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" + "gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/alias" + "gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/cache" + conf "gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/config" + "gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/project" + "gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util" "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/config" ) -var ( - conf config.Config - plog *pterm.Logger -) - var rootCmd = &cobra.Command{ Use: "git-project-manager", Aliases: []string{"gpm"}, Short: "Find and use Git projects locally", - Long: rootCmdLong, + Long: util.RootCmdLong, PersistentPreRun: initRootCmd, } -var configExemptCommands = regexp.MustCompile(`^(doc|conf)`) +var ( + utils *util.Utils + configExemptCommands = regexp.MustCompile(`^(doc|conf)`) +) // Hook traversal is enabled, so this will be run for all // sub-commands regardless of their registered pre-hooks func initRootCmd(cmd *cobra.Command, args []string) { - initProjectPath(cmd, args) + cmd.SetContext(util.AddToCtx(cmd.Context(), utils)) + utils.InitProjectPath(cmd, args) } // Execute adds all child commands to the root command and sets flags appropriately. @@ -51,73 +55,87 @@ func Execute() { func init() { cobra.EnableTraverseRunHooks = true - cobra.OnInitialize(initConfig) + + utils = &util.Utils{} + cobra.OnInitialize(getInitConfigFunc(utils)) // Global flags - rootCmd.PersistentFlags().String(FlagConfig, "", - "config file (default is "+defConfigPath+")") - rootCmd.PersistentFlags().String(FlagPath, "", + rootCmd.PersistentFlags().String(util.FlagConfig, "", + "config file (default is "+util.DefConfigPath+")") + rootCmd.PersistentFlags().String(util.FlagPath, "", "Sets a path for local clones of projects") - rootCmd.PersistentFlags().String(FlagLogLevel, defLogLevel, + rootCmd.PersistentFlags().String(util.FlagLogLevel, util.DefLogLevel, "Default log level -- info, warn, error, debug") - rootCmd.PersistentFlags().StringSlice(FlagRemote, []string{}, + rootCmd.PersistentFlags().StringSlice(util.FlagRemote, []string{}, "Specify remotes by host for any sub-command. Provide multiple times or comma delimited.") - // Flag autocompletion - rootCmd.RegisterFlagCompletionFunc(FlagLogLevel, validLogLevelsFunc) - rootCmd.RegisterFlagCompletionFunc(FlagRemote, validRemotesFunc) - viper.BindPFlags(rootCmd.PersistentFlags()) + + // Flag autocompletion + rootCmd.RegisterFlagCompletionFunc(util.FlagLogLevel, util.ValidLogLevelsFunc) + rootCmd.RegisterFlagCompletionFunc(util.FlagRemote, util.ValidRemotesFunc) + + // Subcommands + rootCmd.AddCommand(alias.AliasCmd) + rootCmd.AddCommand(cache.CacheCmd) + rootCmd.AddCommand(conf.ConfigCmd) + rootCmd.AddCommand(project.ProjectCmd) } // initConfig reads in config file and ENV variables if set. -func initConfig() { - cfgFile := viper.GetString(FlagConfig) - if cfgFile != "" { - // Use config file from the flag. - viper.SetConfigFile(cfgFile) - } else { - // Find home directory. - home, err := os.UserHomeDir() - cobra.CheckErr(err) +func getInitConfigFunc(utils *util.Utils) func() { + return func() { + cfgFile := viper.GetString(util.FlagConfig) + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := os.UserHomeDir() + cobra.CheckErr(err) - // Search config in home directory with name ".git-project-manager" (without extension). - configPath := filepath.Join(home, ".config") - viper.AddConfigPath(configPath) - viper.SetConfigType("yaml") - viper.SetConfigName(getConfigName(configPath)) + // Search config in home directory with name ".git-project-manager" (without extension). + configPath := filepath.Join(home, ".config") + viper.AddConfigPath(configPath) + viper.SetConfigType("yaml") + viper.SetConfigName(util.GetConfigName(configPath)) + } + + viper.AutomaticEnv() + viper.ReadInConfig() + + // Configure pretty logger + plog := pterm.DefaultLogger. + WithLevel(getPtermLogLevel(viper.GetString(util.FlagLogLevel))). + WithWriter(os.Stderr) + if plog.Level == pterm.LogLevelDebug { + pterm.EnableDebugMessages() + } + utils.SetLogger(plog) + + // Load into struct to not be so darn pythonic, retrieving + // settings by untyped string "name" + conf := new(config.Config) + if err := viper.Unmarshal(&conf); err != nil { + plog.Error("Failed loading config", plog.Args("err", err)) + } + + utils.SetConfig(conf) + + if len(os.Args) > 0 && configExemptCommands.Match([]byte(os.Args[1])) { + plog.Debug("Permitting missing config for config sub-command") + return + } else if conf.ProjectPath == "" { + plog.Fatal("Minimal configuration missing, must have projectPath", plog.Args( + "do", + "Try running `git-project-manager config default > "+util.DefConfigPath, + )) + } + + checkConfigPerms(viper.ConfigFileUsed()) // Abort on world-readable config + + utils.Logger().Debug("Configuration loaded", plog.Args("conf", conf)) } - - viper.AutomaticEnv() - viper.ReadInConfig() - - // Configure pretty logger - plog = pterm.DefaultLogger. - WithLevel(getPtermLogLevel(viper.GetString(FlagLogLevel))). - WithWriter(os.Stderr) - if plog.Level == pterm.LogLevelDebug { - pterm.EnableDebugMessages() - } - - // Load into struct to not be so darn pythonic, retrieving - // settings by untyped string "name" - if err := viper.Unmarshal(&conf); err != nil { - plog.Error("Failed loading config", plog.Args("err", err)) - } - - if len(os.Args) > 0 && configExemptCommands.Match([]byte(os.Args[1])) { - plog.Debug("Permitting missing config for config sub-command") - return - } else if conf.ProjectPath == "" { - plog.Fatal("Minimal configuration missing, must have projectPath", plog.Args( - "do", - "Try running `git-project-manager config default > "+defConfigPath, - )) - } - - checkConfigPerms(viper.ConfigFileUsed()) // Abort on world-readable config - - plog.Debug("Configuration loaded", plog.Args("conf", conf)) } func getPtermLogLevel(level string) pterm.LogLevel { @@ -141,12 +159,12 @@ func getPtermLogLevel(level string) pterm.LogLevel { func checkConfigPerms(file string) { stat, err := os.Stat(file) if err != nil { - plog.Error("Failure reading configuration", plog.Args("err", err)) + utils.Logger().Error("Failure reading configuration", utils.Logger().Args("err", err)) return } if stat.Mode().Perm()&0o004 == 0o004 { - plog.Error("Configuration is world-readable. Recomment 0400.", - plog.Args("mode", stat.Mode().String())) + utils.Logger().Error("Configuration is world-readable. Recomment 0400.", + utils.Logger().Args("mode", stat.Mode().String())) os.Exit(1) } } diff --git a/cmd/util/util.go b/cmd/util/util.go new file mode 100644 index 0000000..b53e0b8 --- /dev/null +++ b/cmd/util/util.go @@ -0,0 +1,121 @@ +// Common utilities used by various subcommands +package util + +import ( + "context" + "errors" + "sync" + + "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/cache" + "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/config" + "github.com/pterm/pterm" +) + +type Utils struct { + ctx context.Context + config *config.Config + logger *pterm.Logger + cache *cache.Cache + sync.RWMutex +} + +type UtilOpts struct { + Ctx context.Context + Config *config.Config + Logger *pterm.Logger + Cache *cache.Cache +} + +type utilsCtxKey uint8 + +const UtilsKey utilsCtxKey = iota + +func AddToCtx(ctx context.Context, u *Utils) context.Context { + return context.WithValue(ctx, UtilsKey, u) +} + +func MustFromCtx(ctx context.Context) *Utils { + utils, err := FromCtx(ctx) + if err != nil { + panic(err) + } + return utils +} + +func FromCtx(ctx context.Context) (*Utils, error) { + utils, avail := ctx.Value(UtilsKey).(*Utils) + if !avail { + return nil, errors.New("invalid util in context") + } else if utils == nil { + return nil, errors.New("util never set in context") + } + + return utils, nil +} + +func (u *Utils) Init(opts *UtilOpts) { + u.ctx = opts.Ctx + u.SetConfig(opts.Config) + u.SetLogger(opts.Logger) + u.SetCache(opts.Cache) +} + +func (u *Utils) SetCache(c *cache.Cache) { + if c == nil { + return + } + + u.Lock() + defer u.Unlock() + u.cache = c +} + +func (u *Utils) Cache() *cache.Cache { + u.RLock() + defer u.RUnlock() + return u.cache +} + +func (u *Utils) SetLogger(l *pterm.Logger) { + if l == nil { + return + } + + u.Lock() + defer u.Unlock() + u.logger = l +} + +func (u *Utils) Logger() *pterm.Logger { + u.RLock() + defer u.RUnlock() + return u.logger +} + +func (u *Utils) SetConfig(conf *config.Config) { + if conf == nil { + return + } + + u.Lock() + defer u.Unlock() + u.config = conf +} + +func (u *Utils) Config() *config.Config { + u.RLock() + defer u.RUnlock() + return u.config +} + +func (u *Utils) SetContext(c context.Context) { + u.Lock() + defer u.Unlock() + u.ctx = c +} + +func (u *Utils) Context() context.Context { + u.RLock() + defer u.RUnlock() + return u.ctx +} diff --git a/cmd/util/util_completion.go b/cmd/util/util_completion.go new file mode 100644 index 0000000..40fc800 --- /dev/null +++ b/cmd/util/util_completion.go @@ -0,0 +1,74 @@ +package util + +import ( + "strconv" + "strings" + + "github.com/spf13/cobra" + "golang.org/x/exp/slices" +) + +func ValidProjectsFunc(cmd *cobra.Command, args []string, toComplete string) ( + []string, cobra.ShellCompDirective, +) { + u := MustFromCtx(cmd.Context()) + u.InitProjectCache(cmd, args) + return u.Cache().ProjectStrings(toComplete), cobra.ShellCompDirectiveNoFileComp +} + +func ValidAliasesFunc(cmd *cobra.Command, args []string, toComplete string) ( + []string, cobra.ShellCompDirective, +) { + utils := MustFromCtx(cmd.Context()) + utils.InitProjectCache(cmd, args) + return utils.Cache().AliasStrings(toComplete), cobra.ShellCompDirectiveNoFileComp +} + +func (u *Utils) 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 +} + +func ValidRemotesFunc(cmd *cobra.Command, _ []string, toComplete string) ( + []string, cobra.ShellCompDirective, +) { + u := MustFromCtx(cmd.Context()) + remotes := make([]string, 0, len(u.Config().Remotes)) + for _, remote := range u.Config().Remotes { + if strings.HasPrefix(remote.Host, toComplete) { + remotes = append(remotes, remote.Host) + } + } + return remotes, cobra.ShellCompDirectiveNoFileComp +} + +func ValidLogLevelsFunc(_ *cobra.Command, _ []string, toComplete string) ( + []string, cobra.ShellCompDirective, +) { + levels := []string{"info", "warn", "error", "debug"} + matchingLevels := make([]string, 0, len(levels)) + for _, level := range levels { + if strings.HasPrefix(level, toComplete) { + matchingLevels = append(matchingLevels, level) + } + } + return matchingLevels, cobra.ShellCompDirectiveNoFileComp +} + +func ValidProjectIdFunc(cmd *cobra.Command, args []string, toComplete string) ( + []string, cobra.ShellCompDirective, +) { + u := MustFromCtx(cmd.Context()) + u.InitProjectCache(cmd, args) + matchingIds := make([]string, 0, len(u.Cache().Projects)) + for _, p := range u.Cache().Projects { + idString := strconv.FormatInt(int64(p.ID), 10) + if strings.HasPrefix(idString, toComplete) { + matchingIds = append(matchingIds, idString) + } + } + return slices.Clip(matchingIds), cobra.ShellCompDirectiveNoFileComp +} diff --git a/cmd/util_constants.go b/cmd/util/util_constants.go similarity index 61% rename from cmd/util_constants.go rename to cmd/util/util_constants.go index c269bfe..b6d0d77 100644 --- a/cmd/util_constants.go +++ b/cmd/util/util_constants.go @@ -1,4 +1,4 @@ -package cmd +package util const ( // Cobra Flags @@ -25,62 +25,62 @@ const ( ) const ( - defGitlabHost = "https://gitlab.com" - defLogLevel = "info" - defConfigPath = "~/.config/git-project-manager.yaml" - configName = "git-project-manager" - legacyConfigName = "gitlab-project-manager" + DefGitlabHost = "https://gitlab.com" + DefLogLevel = "info" + DefConfigPath = "~/.config/git-project-manager.yaml" + ConfigName = "git-project-manager" + LegacyConfigName = "gitlab-project-manager" ) -const aliasCmdLong = `Manages project aliases, with options for +const AliasCmdLong = `Manages project aliases, with options for listing, adding, and deleting.` -const aliasListCmdLong = `Lists all aliases by project` +const AliasListCmdLong = `Lists all aliases by project` -const aliasAddCmdLong = `Adds a project alias to a project +const AliasAddCmdLong = `Adds a project alias to a project project ID can be provided, or will otherwise use fuzzy find` -const aliasDeleteCmdLong = `Deletes aliases from projects +const AliasDeleteCmdLong = `Deletes aliases from projects project ID can be provided, or will otherwise use fuzzy find` -const cacheCmdLong = `Contains sub-commands for managing project cache. +const CacheCmdLong = `Contains sub-commands for managing project cache. The project cache keeps this speedy, without smashing against the Git API every time a new project is added / searched for` -const rootCmdLong = `Finds Git projects using fuzzy-find, remembering +const RootCmdLong = `Finds Git 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 Git project by name or alias +const ProjCmdLong = `Switches to a Git project by name or alias If not found, will enter fzf mode. If not cloned, will clone the project locally.` -const projGoCmdLong = `Go to a project, searching by alias +const ProjGoCmdLong = `Go to a project, searching by alias If project is not already cloned, its path will be built and it 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 +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 +const ProjListCmdLong = `List locally cloned projects. Optionally lists all projects in project cache` -const projAddCmdLong = `Adds a new project to the local project path +const ProjAddCmdLong = `Adds a new project to the local project path uses fuzzy find to locate the project` -const projShowCmdLong = `Shows detail for a particular project +const ProjShowCmdLong = `Shows detail for a particular project Will always fuzzy find` -const projOpenCmdLong = `Opens the given project directory in the editor +const ProjOpenCmdLong = `Opens the given project directory in the editor of your choice. Will find certain well-known entrypoints (e.g. main.go). If your editor is set in your config file, it will be used, otherwise one will be found in your path from a list of known defaults.` -const configCmdLong = `Commands for managing configuration, particulary +const ConfigCmdLong = `Commands for managing configuration, particulary useful for seeding a new config file` -const configGenCmdLong = `Produces yaml to stdout that can be used +const ConfigGenCmdLong = `Produces yaml to stdout that can be used to seed the configuration file` diff --git a/cmd/util_fzf.go b/cmd/util/util_fzf.go similarity index 60% rename from cmd/util_fzf.go rename to cmd/util/util_fzf.go index 902b1f9..dc8a861 100644 --- a/cmd/util_fzf.go +++ b/cmd/util/util_fzf.go @@ -1,4 +1,4 @@ -package cmd +package util import ( "context" @@ -10,7 +10,7 @@ import ( "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects" ) -type fzfProjectOpts struct { +type FzfProjectOpts struct { Ctx context.Context Search string MustHaveAlias bool @@ -19,14 +19,14 @@ type fzfProjectOpts struct { // This will try to find a project by alias if a search term // is given, otherwise will fuzzy find by project -func fzfFindProject(opts *fzfProjectOpts) *projects.Project { +func (u *Utils) FzfFindProject(opts *FzfProjectOpts) *projects.Project { var project *projects.Project if opts.Search != "" { - project = fzfSearchProjectAliases(opts) + project = u.FzfSearchProjectAliases(opts) } else { var err error - project, err = fzfProject(opts) + project, err = u.FzfProject(opts) if project == nil || err != nil { return nil } @@ -38,35 +38,35 @@ func fzfFindProject(opts *fzfProjectOpts) *projects.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(opts *fzfProjectOpts) *projects.Project { +func (u *Utils) FzfCwdOrSearchProjectAliases(opts *FzfProjectOpts) *projects.Project { var project *projects.Project if opts.Search == "." { - project, _ = projectCache.GetProjectFromCwd() + project, _ = u.Cache().GetProjectFromCwd() } else { - project = fzfSearchProjectAliases(opts) + project = u.FzfSearchProjectAliases(opts) } return project } // This will fuzzy search only aliases, preferring an exact // match if one is given -func fzfSearchProjectAliases(opts *fzfProjectOpts) *projects.Project { +func (u *Utils) FzfSearchProjectAliases(opts *FzfProjectOpts) *projects.Project { var project *projects.Project var alias *cache.ProjectAlias - if alias = projectCache.GetAliasByName(opts.Search, opts.Remotes...); alias != nil { - project = projectCache.GetProjectByAlias(alias) - plog.Info("Perfect alias match... flawless") + if alias = u.Cache().GetAliasByName(opts.Search, opts.Remotes...); alias != nil { + project = u.Cache().GetProjectByAlias(alias) + u.Logger().Info("Perfect alias match... flawless") } else { // Get fuzzy if we don't have an exact match - aliases := projectCache.FuzzyFindAlias(opts.Search) + aliases := u.Cache().FuzzyFindAlias(opts.Search) if len(aliases) > 1 { // If multiple aliases were found, switch over to project // by alias mode with merging // alias = fzfAliasFromAliases(rootCmd.Context(), aliases) - project, _ = fzfProjectFromAliases(opts, aliases) + project, _ = u.FzfProjectFromAliases(opts, aliases) } else if len(aliases) == 1 { alias = aliases[0] - project = projectCache.GetProjectByAlias(alias) + project = u.Cache().GetProjectByAlias(alias) } } return project @@ -76,18 +76,18 @@ func fzfSearchProjectAliases(opts *fzfProjectOpts) *projects.Project { // a single one. Replaced by fzfProjectFromAliases in fzfSearchProjectAliases // as merging is preferred, but can be used if it's ever desirable to // return a single alias from all aliases -func fzfAliasFromAliases(opts *fzfProjectOpts, aliases []*cache.ProjectAlias) *cache.ProjectAlias { +func (u *Utils) FzfAliasFromAliases(opts *FzfProjectOpts, aliases []*cache.ProjectAlias) *cache.ProjectAlias { var alias *cache.ProjectAlias i, err := fzf.Find( aliases, func(i int) string { - return aliases[i].Alias + " -> " + projectCache.GetProjectByAlias(aliases[i]).PathWithNamespace + return aliases[i].Alias + " -> " + u.Cache().GetProjectByAlias(aliases[i]).PathWithNamespace }, fzf.WithContext(opts.Ctx), fzf.WithHeader("Choose an Alias"), ) if err != nil { - plog.Error("Failed to fzf alias slice", plog.Args("error", err)) + u.Logger().Error("Failed to fzf alias slice", u.Logger().Args("error", err)) } else { alias = aliases[i] } @@ -96,21 +96,21 @@ func fzfAliasFromAliases(opts *fzfProjectOpts, aliases []*cache.ProjectAlias) *c // Given a list of aliases, merge them together and use the resulting // list of projects to return a project -func fzfProjectFromAliases(opts *fzfProjectOpts, aliases []*cache.ProjectAlias) ( +func (u *Utils) FzfProjectFromAliases(opts *FzfProjectOpts, aliases []*cache.ProjectAlias) ( *projects.Project, error, ) { - mergedProjects := projectsFromAliases(aliases) + mergedProjects := u.projectsFromAliases(aliases) if len(mergedProjects) == 1 { return mergedProjects[0], nil } - return fzfProjectFromProjects(opts, mergedProjects) + return u.FzfProjectFromProjects(opts, mergedProjects) } -func projectsFromAliases(aliases []*cache.ProjectAlias) []*projects.Project { +func (u *Utils) projectsFromAliases(aliases []*cache.ProjectAlias) []*projects.Project { projects := make([]*projects.Project, 0, len(aliases)) for _, a := range aliases { - project := projectCache.GetProjectByAlias(a) + project := u.Cache().GetProjectByAlias(a) if project != nil && !slices.Contains(projects, project) { projects = append(projects, project) } @@ -121,30 +121,30 @@ func projectsFromAliases(aliases []*cache.ProjectAlias) []*projects.Project { // If opts.MustHaveAlias, will only allow selection of projects // that have at least one alias defined -func fzfProject(opts *fzfProjectOpts) (*projects.Project, error) { +func (u *Utils) FzfProject(opts *FzfProjectOpts) (*projects.Project, error) { var searchableProjects []*projects.Project if opts.MustHaveAlias { - searchableProjects = projectCache.GetProjectsWithAliases() + searchableProjects = u.Cache().GetProjectsWithAliases() } else { - searchableProjects = projectCache.Projects + searchableProjects = u.Cache().Projects } // Filter out unwanted remotes if provided - searchableProjects = filterProjectsWithRemotes(searchableProjects, opts.Remotes...) - return fzfProjectFromProjects(opts, searchableProjects) + searchableProjects = u.FilterProjectsWithRemotes(searchableProjects, opts.Remotes...) + return u.FzfProjectFromProjects(opts, searchableProjects) } // Takes a list of projects and performs a fuzzyfind -func fzfProjectFromProjects(opts *fzfProjectOpts, projects []*projects.Project) ( +func (u *Utils) FzfProjectFromProjects(opts *FzfProjectOpts, projects []*projects.Project) ( *projects.Project, error, ) { i, err := fzf.Find(projects, func(i int) string { // Display the project along with its aliases - return projectCache.GetProjectStringWithAliases(projects[i]) + return u.Cache().GetProjectStringWithAliases(projects[i]) }, fzf.WithPreviewWindow( func(i, width, height int) string { - return projectCache.ProjectString(projects[i]) + return u.Cache().ProjectString(projects[i]) }, ), fzf.WithContext(opts.Ctx), @@ -156,12 +156,12 @@ func fzfProjectFromProjects(opts *fzfProjectOpts, projects []*projects.Project) return projects[i], nil } -func fzfPreviewWindow(i, _, _ int) string { - p := projectCache.Projects[i] - return projectCache.ProjectString(p) +func (u *Utils) FzfPreviewWindow(i, _, _ int) string { + p := u.Cache().Projects[i] + return u.Cache().ProjectString(p) } -func filterProjectsWithRemotes(gitProjects []*projects.Project, remotes ...string) []*projects.Project { +func (u *Utils) FilterProjectsWithRemotes(gitProjects []*projects.Project, remotes ...string) []*projects.Project { filteredProjects := make([]*projects.Project, 0, len(gitProjects)) if len(remotes) > 0 { for _, p := range gitProjects { @@ -177,7 +177,7 @@ func filterProjectsWithRemotes(gitProjects []*projects.Project, remotes ...strin // 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 { +func (u *Utils) SearchStringFromArgs(args []string) string { var term string if len(args) > 0 { term = args[0] diff --git a/cmd/util/util_init.go b/cmd/util/util_init.go new file mode 100644 index 0000000..f01a6a3 --- /dev/null +++ b/cmd/util/util_init.go @@ -0,0 +1,163 @@ +// This file contains init methods that may be used by +// multiple sub-commands. For instance, the cach and projects +// sub-commands both depend on a cache and may both call the initProjectCache +// func (u *Util) from their PersistentPreRun commands +package util + +import ( + "fmt" + "os" + "os/user" + "path/filepath" + "strings" + + "github.com/pterm/pterm" + "github.com/spf13/cobra" + "golang.org/x/sys/unix" + + "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/cache" + "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/config" + "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes" + gitearemote "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/gitea" + githubremote "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/github" + gitlabremote "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/gitlab" + "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/remote" +) + +func InitProjects(cmd *cobra.Command, args []string) { + utils, _ := FromCtx(cmd.Context()) + utils.InitProjectCache(cmd, args) + utils.mustHaveProjects(cmd, args) +} + +func (u *Utils) mustHaveProjects(cmd *cobra.Command, _ []string) { + if len(u.Cache().Projects) == 0 { + u.Logger().Fatal("No projects to " + cmd.Name() + ", try running cache load") + } +} + +func (u *Utils) InitProjectCache(cmd *cobra.Command, _ []string) { + var err error + u.Logger().Debug("Running pre-run for cacheCmd") + u.Config().Cache.File = u.Config().ProjectPath + "/.cache.yaml" + + gitRemotes := remotes.NewRemotes() + gitRemotes.AddRemotes(*u.GetRemotes(cmd)...) + + cacheOpts := &cache.CacheOpts{ + ProjectsPath: u.Config().ProjectPath, + Path: u.Config().Cache.File, + TTL: u.Config().Cache.Ttl, + Logger: u.Logger(), + Remotes: gitRemotes, + Config: u.Config(), + } + + projectCache, err := cache.NewProjectCache(cacheOpts) + if err != nil { + u.Logger().Error("Failed to prepare project cache", u.Logger().Args("error", err)) + os.Exit(1) + } + u.SetCache(projectCache) + + if err := u.Cache().Read(); err != nil { + u.Logger().Error("Cache load failed", u.Logger().Args("error", err)) + os.Exit(1) + } + + u.Logger().Debug("Remotes Loaded", u.Logger().Args("remotes", cacheOpts.Remotes)) +} + +// Generically loads remotes from info.RemoteInfo in config.Remotes +func (u *Utils) GetRemotes(cmd *cobra.Command) *remotes.Remotes { + gitRemotes := new(remotes.Remotes) + *gitRemotes = make([]remote.Remote, 0) + for _, r := range u.Config().Remotes { + // Create a copy, set context + gitRemoteInfo := r + gitRemoteInfo.SetContext(cmd.Context()) + var gitRemote remote.Remote + var err error + switch r.Type { + case "gitlab": + gitRemote, err = gitlabremote.NewGitlabRemote(&gitRemoteInfo) + case "gitea": + gitRemote, err = gitearemote.NewGiteaRemote(&gitRemoteInfo) + case "github": + gitRemote, err = githubremote.NewGithubRemote(&gitRemoteInfo) + } + if err != nil { + u.Logger().Error("Failed to prepare remote", u.Logger().Args( + "error", err, + "type", r.Type)) + } else { + *gitRemotes = append(*gitRemotes, gitRemote) + } + } + return gitRemotes +} + +func PostProjectCmd(cmd *cobra.Command, args []string) { + utils, _ := FromCtx(cmd.Context()) + utils.PostProjectCache(cmd, args) +} + +func (u *Utils) PostProjectCache(_ *cobra.Command, _ []string) { + u.Cache().Write() +} + +func (u *Utils) InitProjectPath(_ *cobra.Command, _ []string) { + u.Logger().Debug("Running persistent pre-run for rootCmd") + var err error + if u.Config().ProjectPath == "" { + u.Config().ProjectPath = config.DefaultConfig.ProjectPath + return + } + if u.Config().ProjectPath, err = ResolvePath(u.Config().ProjectPath); err != nil { + u.Logger().Error("Failed to determine project path", u.Logger().Args("error", err)) + os.Exit(1) + } + _, err = os.Stat(u.Config().ProjectPath) + if err != nil { + u.Logger().Error("Failed to stat project path, trying to create", u.Logger().Args("error", err)) + if err = os.MkdirAll(u.Config().ProjectPath, 0o750); err != nil { + u.Logger().Error("Failed to create project path", u.Logger().Args("error", err)) + os.Exit(1) + } + u.Logger().Info("Project path created", u.Logger().Args("path", u.Config().ProjectPath)) + } else { + if err = unix.Access(u.Config().ProjectPath, unix.W_OK); err != nil { + u.Logger().Error("Unable to write to project path", u.Logger().Args( + "path", u.Config().ProjectPath, + "error", err)) + os.Exit(1) + } + } +} + +func ResolvePath(path string) (string, error) { + if strings.HasPrefix(path, "~/") { + usr, _ := user.Current() + path = filepath.Join(usr.HomeDir, path[2:]) + } + return filepath.Abs(path) +} + +func GetConfigName(configPath string) string { + // Check existing config + for _, ext := range []string{"yml", "yaml"} { + configFile := fmt.Sprintf("%s/%s.%s", configPath, ConfigName, ext) + legacyConfigFile := fmt.Sprintf("%s/%s.%s", configPath, LegacyConfigName, ext) + + if _, err := os.Stat(configFile); err == nil { + return ConfigName + } else if _, err := os.Stat(legacyConfigFile); err == nil { + pterm.DefaultLogger.WithWriter(os.Stderr). + Warn(fmt.Sprintf("using legacy config path, suggest using %s/%s.yaml", configPath, ConfigName)) + return LegacyConfigName + } + } + + // Nothing found, do what we want + return ConfigName +} diff --git a/cmd/util_completion.go b/cmd/util_completion.go deleted file mode 100644 index e07b2b5..0000000 --- a/cmd/util_completion.go +++ /dev/null @@ -1,64 +0,0 @@ -package cmd - -import ( - "strconv" - "strings" - - "github.com/spf13/cobra" - "golang.org/x/exp/slices" -) - -func validProjectsFunc(cmd *cobra.Command, args []string, toComplete string) ( - []string, cobra.ShellCompDirective) { - initProjectCache(cmd, args) - return projectCache.ProjectStrings(toComplete), cobra.ShellCompDirectiveNoFileComp -} - -func validAliasesFunc(cmd *cobra.Command, args []string, toComplete string) ( - []string, cobra.ShellCompDirective) { - initProjectCache(cmd, args) - return projectCache.AliasStrings(toComplete), cobra.ShellCompDirectiveNoFileComp -} - -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 -} - -func validRemotesFunc(cmd *cobra.Command, args []string, toComplete string) ( - []string, cobra.ShellCompDirective) { - remotes := make([]string, 0, len(conf.Remotes)) - for _, remote := range conf.Remotes { - if strings.HasPrefix(remote.Host, toComplete) { - remotes = append(remotes, remote.Host) - } - } - return remotes, cobra.ShellCompDirectiveNoFileComp -} - -func validLogLevelsFunc(cmd *cobra.Command, args []string, toComplete string) ( - []string, cobra.ShellCompDirective) { - levels := []string{"info", "warn", "error", "debug"} - matchingLevels := make([]string, 0, len(levels)) - for _, level := range levels { - if strings.HasPrefix(level, toComplete) { - matchingLevels = append(matchingLevels, level) - } - } - return matchingLevels, cobra.ShellCompDirectiveNoFileComp -} - -func validProjectIdFunc(cmd *cobra.Command, args []string, toComplete string) ( - []string, cobra.ShellCompDirective) { - initProjectCache(cmd, args) - matchingIds := make([]string, 0, len(projectCache.Projects)) - for _, p := range projectCache.Projects { - idString := strconv.FormatInt(int64(p.ID), 10) - if strings.HasPrefix(idString, toComplete) { - matchingIds = append(matchingIds, idString) - } - } - return slices.Clip(matchingIds), cobra.ShellCompDirectiveNoFileComp -} diff --git a/cmd/util_init.go b/cmd/util_init.go deleted file mode 100644 index 18aaebb..0000000 --- a/cmd/util_init.go +++ /dev/null @@ -1,143 +0,0 @@ -// This file contains init methods that may be used by -// multiple sub-commands. For instance, the cach and projects -// sub-commands both depend on a cache and may both call the initProjectCache -// func from their PersistentPreRun commands -package cmd - -import ( - "fmt" - "os" - "os/user" - "path/filepath" - "strings" - - "github.com/pterm/pterm" - "github.com/spf13/cobra" - "golang.org/x/sys/unix" - - "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/cache" - "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/config" - "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes" - gitearemote "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/gitea" - githubremote "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/github" - gitlabremote "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/gitlab" - "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/remote" -) - -func initProjectCache(cmd *cobra.Command, _ []string) { - var err error - plog.Debug("Running pre-run for cacheCmd") - conf.Cache.File = conf.ProjectPath + "/.cache.yaml" - - gitRemotes := remotes.NewRemotes() - gitRemotes.AddRemotes(*getRemotes(cmd)...) - - cacheOpts := &cache.CacheOpts{ - ProjectsPath: conf.ProjectPath, - Path: conf.Cache.File, - TTL: conf.Cache.Ttl, - Logger: plog, - Remotes: gitRemotes, - Config: &conf, - } - if projectCache, err = cache.NewProjectCache(cacheOpts); err != nil { - plog.Error("Failed to prepare project cache", plog.Args("error", err)) - os.Exit(1) - } - - if err := projectCache.Read(); err != nil { - plog.Error("Cache load failed", plog.Args("error", err)) - os.Exit(1) - } - - plog.Debug("Remotes Loaded", plog.Args("remotes", cacheOpts.Remotes)) -} - -// Generically loads remotes from info.RemoteInfo in config.Remotes -func getRemotes(cmd *cobra.Command) *remotes.Remotes { - gitRemotes := new(remotes.Remotes) - *gitRemotes = make([]remote.Remote, 0) - for _, r := range conf.Remotes { - // Create a copy, set context - gitRemoteInfo := r - gitRemoteInfo.SetContext(cmd.Context()) - var gitRemote remote.Remote - var err error - switch r.Type { - case "gitlab": - gitRemote, err = gitlabremote.NewGitlabRemote(&gitRemoteInfo) - case "gitea": - gitRemote, err = gitearemote.NewGiteaRemote(&gitRemoteInfo) - case "github": - gitRemote, err = githubremote.NewGithubRemote(&gitRemoteInfo) - } - if err != nil { - plog.Error("Failed to prepare remote", plog.Args( - "error", err, - "type", r.Type)) - } else { - *gitRemotes = append(*gitRemotes, gitRemote) - } - } - return gitRemotes -} - -func postProjectCache(_ *cobra.Command, _ []string) { - projectCache.Write() -} - -func initProjectPath(_ *cobra.Command, _ []string) { - plog.Debug("Running persistent pre-run for rootCmd") - var err error - if conf.ProjectPath == "" { - conf.ProjectPath = config.DefaultConfig.ProjectPath - return - } - if conf.ProjectPath, err = resolvePath(conf.ProjectPath); err != nil { - plog.Error("Failed to determine project path", plog.Args("error", err)) - os.Exit(1) - } - _, err = os.Stat(conf.ProjectPath) - if err != nil { - plog.Error("Failed to stat project path, trying to create", plog.Args("error", err)) - if err = os.MkdirAll(conf.ProjectPath, 0o750); err != nil { - plog.Error("Failed to create project path", plog.Args("error", err)) - os.Exit(1) - } - plog.Info("Project path created", plog.Args("path", conf.ProjectPath)) - } else { - if err = unix.Access(conf.ProjectPath, unix.W_OK); err != nil { - plog.Error("Unable to write to project path", plog.Args( - "path", conf.ProjectPath, - "error", err)) - os.Exit(1) - } - } -} - -func resolvePath(path string) (string, error) { - if strings.HasPrefix(path, "~/") { - usr, _ := user.Current() - path = filepath.Join(usr.HomeDir, path[2:]) - } - return filepath.Abs(path) -} - -func getConfigName(configPath string) string { - // Check existing config - for _, ext := range []string{"yml", "yaml"} { - configFile := fmt.Sprintf("%s/%s.%s", configPath, configName, ext) - legacyConfigFile := fmt.Sprintf("%s/%s.%s", configPath, legacyConfigName, ext) - - if _, err := os.Stat(configFile); err == nil { - return configName - } else if _, err := os.Stat(legacyConfigFile); err == nil { - pterm.DefaultLogger.WithWriter(os.Stderr). - Warn(fmt.Sprintf("using legacy config path, suggest using %s/%s.yaml", configPath, configName)) - return legacyConfigName - } - } - - // Nothing found, do what we want - return configName -} diff --git a/docs/git-project-manager.md b/docs/git-project-manager.md index 738c65e..c770212 100644 --- a/docs/git-project-manager.md +++ b/docs/git-project-manager.md @@ -27,4 +27,4 @@ shortcuts for moving around in projects and opening your code * [git-project-manager docs](git-project-manager_docs.md) - Generate documentation for git-project-manager * [git-project-manager project](git-project-manager_project.md) - Use a Git project -###### Auto generated by spf13/cobra on 27-Dec-2024 +###### Auto generated by spf13/cobra on 30-Dec-2024 diff --git a/docs/git-project-manager_alias.md b/docs/git-project-manager_alias.md index ce5ce4d..0f448ba 100644 --- a/docs/git-project-manager_alias.md +++ b/docs/git-project-manager_alias.md @@ -29,4 +29,4 @@ listing, adding, and deleting. * [git-project-manager alias delete](git-project-manager_alias_delete.md) - Delete a project alias * [git-project-manager alias list](git-project-manager_alias_list.md) - List Aliases -###### Auto generated by spf13/cobra on 27-Dec-2024 +###### Auto generated by spf13/cobra on 30-Dec-2024 diff --git a/docs/git-project-manager_alias_add.md b/docs/git-project-manager_alias_add.md index bcc77c5..83b5568 100644 --- a/docs/git-project-manager_alias_add.md +++ b/docs/git-project-manager_alias_add.md @@ -31,4 +31,4 @@ git-project-manager alias add [flags] * [git-project-manager alias](git-project-manager_alias.md) - Manage project aliases -###### Auto generated by spf13/cobra on 27-Dec-2024 +###### Auto generated by spf13/cobra on 30-Dec-2024 diff --git a/docs/git-project-manager_alias_delete.md b/docs/git-project-manager_alias_delete.md index e5ae2be..64761ab 100644 --- a/docs/git-project-manager_alias_delete.md +++ b/docs/git-project-manager_alias_delete.md @@ -31,4 +31,4 @@ git-project-manager alias delete [fuzzy project or alias] [flags] * [git-project-manager alias](git-project-manager_alias.md) - Manage project aliases -###### Auto generated by spf13/cobra on 27-Dec-2024 +###### Auto generated by spf13/cobra on 30-Dec-2024 diff --git a/docs/git-project-manager_alias_list.md b/docs/git-project-manager_alias_list.md index 097f06c..4b72b4c 100644 --- a/docs/git-project-manager_alias_list.md +++ b/docs/git-project-manager_alias_list.md @@ -29,4 +29,4 @@ git-project-manager alias list [flags] * [git-project-manager alias](git-project-manager_alias.md) - Manage project aliases -###### Auto generated by spf13/cobra on 27-Dec-2024 +###### Auto generated by spf13/cobra on 30-Dec-2024 diff --git a/docs/git-project-manager_cache.md b/docs/git-project-manager_cache.md index ad73377..6c0d343 100644 --- a/docs/git-project-manager_cache.md +++ b/docs/git-project-manager_cache.md @@ -32,4 +32,4 @@ API every time a new project is added / searched for * [git-project-manager cache load](git-project-manager_cache_load.md) - Load Git Project Cache * [git-project-manager cache unlock](git-project-manager_cache_unlock.md) - unlock Git project cache -###### Auto generated by spf13/cobra on 27-Dec-2024 +###### Auto generated by spf13/cobra on 30-Dec-2024 diff --git a/docs/git-project-manager_cache_clear.md b/docs/git-project-manager_cache_clear.md index 21c73ba..4e2792f 100644 --- a/docs/git-project-manager_cache_clear.md +++ b/docs/git-project-manager_cache_clear.md @@ -33,4 +33,4 @@ git-project-manager cache clear [flags] * [git-project-manager cache](git-project-manager_cache.md) - Manage Git project cache -###### Auto generated by spf13/cobra on 27-Dec-2024 +###### Auto generated by spf13/cobra on 30-Dec-2024 diff --git a/docs/git-project-manager_cache_dump.md b/docs/git-project-manager_cache_dump.md index d2524d5..8f13059 100644 --- a/docs/git-project-manager_cache_dump.md +++ b/docs/git-project-manager_cache_dump.md @@ -31,4 +31,4 @@ git-project-manager cache dump [flags] * [git-project-manager cache](git-project-manager_cache.md) - Manage Git project cache -###### Auto generated by spf13/cobra on 27-Dec-2024 +###### Auto generated by spf13/cobra on 30-Dec-2024 diff --git a/docs/git-project-manager_cache_load.md b/docs/git-project-manager_cache_load.md index 44e3612..86f80f8 100644 --- a/docs/git-project-manager_cache_load.md +++ b/docs/git-project-manager_cache_load.md @@ -33,4 +33,4 @@ git-project-manager cache load [flags] * [git-project-manager cache](git-project-manager_cache.md) - Manage Git project cache -###### Auto generated by spf13/cobra on 27-Dec-2024 +###### Auto generated by spf13/cobra on 30-Dec-2024 diff --git a/docs/git-project-manager_cache_unlock.md b/docs/git-project-manager_cache_unlock.md index 7444b8e..2b16aca 100644 --- a/docs/git-project-manager_cache_unlock.md +++ b/docs/git-project-manager_cache_unlock.md @@ -31,4 +31,4 @@ git-project-manager cache unlock [flags] * [git-project-manager cache](git-project-manager_cache.md) - Manage Git project cache -###### Auto generated by spf13/cobra on 27-Dec-2024 +###### Auto generated by spf13/cobra on 30-Dec-2024 diff --git a/docs/git-project-manager_completion.md b/docs/git-project-manager_completion.md index a144a59..0027f61 100644 --- a/docs/git-project-manager_completion.md +++ b/docs/git-project-manager_completion.md @@ -31,4 +31,4 @@ See each sub-command's help for details on how to use the generated script. * [git-project-manager completion powershell](git-project-manager_completion_powershell.md) - Generate the autocompletion script for powershell * [git-project-manager completion zsh](git-project-manager_completion_zsh.md) - Generate the autocompletion script for zsh -###### Auto generated by spf13/cobra on 27-Dec-2024 +###### Auto generated by spf13/cobra on 30-Dec-2024 diff --git a/docs/git-project-manager_completion_bash.md b/docs/git-project-manager_completion_bash.md index 2dcfe2d..b04e37f 100644 --- a/docs/git-project-manager_completion_bash.md +++ b/docs/git-project-manager_completion_bash.md @@ -50,4 +50,4 @@ git-project-manager completion bash * [git-project-manager completion](git-project-manager_completion.md) - Generate the autocompletion script for the specified shell -###### Auto generated by spf13/cobra on 27-Dec-2024 +###### Auto generated by spf13/cobra on 30-Dec-2024 diff --git a/docs/git-project-manager_completion_fish.md b/docs/git-project-manager_completion_fish.md index bba4515..bc15512 100644 --- a/docs/git-project-manager_completion_fish.md +++ b/docs/git-project-manager_completion_fish.md @@ -41,4 +41,4 @@ git-project-manager completion fish [flags] * [git-project-manager completion](git-project-manager_completion.md) - Generate the autocompletion script for the specified shell -###### Auto generated by spf13/cobra on 27-Dec-2024 +###### Auto generated by spf13/cobra on 30-Dec-2024 diff --git a/docs/git-project-manager_completion_powershell.md b/docs/git-project-manager_completion_powershell.md index c840dec..31fbf81 100644 --- a/docs/git-project-manager_completion_powershell.md +++ b/docs/git-project-manager_completion_powershell.md @@ -38,4 +38,4 @@ git-project-manager completion powershell [flags] * [git-project-manager completion](git-project-manager_completion.md) - Generate the autocompletion script for the specified shell -###### Auto generated by spf13/cobra on 27-Dec-2024 +###### Auto generated by spf13/cobra on 30-Dec-2024 diff --git a/docs/git-project-manager_completion_zsh.md b/docs/git-project-manager_completion_zsh.md index cc01d68..2212675 100644 --- a/docs/git-project-manager_completion_zsh.md +++ b/docs/git-project-manager_completion_zsh.md @@ -52,4 +52,4 @@ git-project-manager completion zsh [flags] * [git-project-manager completion](git-project-manager_completion.md) - Generate the autocompletion script for the specified shell -###### Auto generated by spf13/cobra on 27-Dec-2024 +###### Auto generated by spf13/cobra on 30-Dec-2024 diff --git a/docs/git-project-manager_config.md b/docs/git-project-manager_config.md index 8d5ef78..55e6326 100644 --- a/docs/git-project-manager_config.md +++ b/docs/git-project-manager_config.md @@ -28,4 +28,4 @@ useful for seeding a new config file * [git-project-manager config generate](git-project-manager_config_generate.md) - Generate a default configuration * [git-project-manager config show](git-project-manager_config_show.md) - Show Git Project Manager Configuration -###### Auto generated by spf13/cobra on 27-Dec-2024 +###### Auto generated by spf13/cobra on 30-Dec-2024 diff --git a/docs/git-project-manager_config_generate.md b/docs/git-project-manager_config_generate.md index 78d44c5..7bb459d 100644 --- a/docs/git-project-manager_config_generate.md +++ b/docs/git-project-manager_config_generate.md @@ -32,4 +32,4 @@ git-project-manager config generate [flags] * [git-project-manager config](git-project-manager_config.md) - Git Project Manager Configuration -###### Auto generated by spf13/cobra on 27-Dec-2024 +###### Auto generated by spf13/cobra on 30-Dec-2024 diff --git a/docs/git-project-manager_config_show.md b/docs/git-project-manager_config_show.md index 96f390f..449d68e 100644 --- a/docs/git-project-manager_config_show.md +++ b/docs/git-project-manager_config_show.md @@ -26,4 +26,4 @@ git-project-manager config show [flags] * [git-project-manager config](git-project-manager_config.md) - Git Project Manager Configuration -###### Auto generated by spf13/cobra on 27-Dec-2024 +###### Auto generated by spf13/cobra on 30-Dec-2024 diff --git a/docs/git-project-manager_docs.md b/docs/git-project-manager_docs.md index 1773093..817240a 100644 --- a/docs/git-project-manager_docs.md +++ b/docs/git-project-manager_docs.md @@ -26,4 +26,4 @@ git-project-manager docs [flags] * [git-project-manager](git-project-manager.md) - Find and use Git projects locally -###### Auto generated by spf13/cobra on 27-Dec-2024 +###### Auto generated by spf13/cobra on 30-Dec-2024 diff --git a/docs/git-project-manager_project.md b/docs/git-project-manager_project.md index 2fd79d4..5718a21 100644 --- a/docs/git-project-manager_project.md +++ b/docs/git-project-manager_project.md @@ -31,6 +31,8 @@ the project locally. * [git-project-manager project list](git-project-manager_project_list.md) - List Git Projects * [git-project-manager project open](git-project-manager_project_open.md) - Open project in your IDE * [git-project-manager project run](git-project-manager_project_run.md) - Run the project (e.g. go run .) +* [git-project-manager project run](git-project-manager_project_run.md) - Run the project (e.g. go run .) +* [git-project-manager project show](git-project-manager_project_show.md) - Show detail for a Git project * [git-project-manager project show](git-project-manager_project_show.md) - Show detail for a Git project -###### Auto generated by spf13/cobra on 27-Dec-2024 +###### Auto generated by spf13/cobra on 30-Dec-2024 diff --git a/docs/git-project-manager_project_add.md b/docs/git-project-manager_project_add.md index fd7e53d..838cecf 100644 --- a/docs/git-project-manager_project_add.md +++ b/docs/git-project-manager_project_add.md @@ -30,4 +30,4 @@ git-project-manager project add [flags] * [git-project-manager project](git-project-manager_project.md) - Use a Git project -###### Auto generated by spf13/cobra on 27-Dec-2024 +###### Auto generated by spf13/cobra on 30-Dec-2024 diff --git a/docs/git-project-manager_project_go.md b/docs/git-project-manager_project_go.md index 22f81ae..541e59d 100644 --- a/docs/git-project-manager_project_go.md +++ b/docs/git-project-manager_project_go.md @@ -33,4 +33,4 @@ git-project-manager project go [fuzzy alias search] [flags] * [git-project-manager project](git-project-manager_project.md) - Use a Git project -###### Auto generated by spf13/cobra on 27-Dec-2024 +###### Auto generated by spf13/cobra on 30-Dec-2024 diff --git a/docs/git-project-manager_project_list.md b/docs/git-project-manager_project_list.md index 609995d..8863325 100644 --- a/docs/git-project-manager_project_list.md +++ b/docs/git-project-manager_project_list.md @@ -31,4 +31,4 @@ git-project-manager project list [flags] * [git-project-manager project](git-project-manager_project.md) - Use a Git project -###### Auto generated by spf13/cobra on 27-Dec-2024 +###### Auto generated by spf13/cobra on 30-Dec-2024 diff --git a/docs/git-project-manager_project_open.md b/docs/git-project-manager_project_open.md index 0b57b19..80d8fdb 100644 --- a/docs/git-project-manager_project_open.md +++ b/docs/git-project-manager_project_open.md @@ -36,4 +36,4 @@ git-project-manager project open [fuzzy alias search] [flags] * [git-project-manager project](git-project-manager_project.md) - Use a Git project -###### Auto generated by spf13/cobra on 27-Dec-2024 +###### Auto generated by spf13/cobra on 30-Dec-2024 diff --git a/docs/git-project-manager_project_run.md b/docs/git-project-manager_project_run.md index 2745841..06749c1 100644 --- a/docs/git-project-manager_project_run.md +++ b/docs/git-project-manager_project_run.md @@ -30,4 +30,4 @@ git-project-manager project run [flags] * [git-project-manager project](git-project-manager_project.md) - Use a Git project -###### Auto generated by spf13/cobra on 27-Dec-2024 +###### Auto generated by spf13/cobra on 30-Dec-2024 diff --git a/docs/git-project-manager_project_show.md b/docs/git-project-manager_project_show.md index c976b9c..9c4a9e3 100644 --- a/docs/git-project-manager_project_show.md +++ b/docs/git-project-manager_project_show.md @@ -31,4 +31,4 @@ git-project-manager project show [fuzzy alias search] [flags] * [git-project-manager project](git-project-manager_project.md) - Use a Git project -###### Auto generated by spf13/cobra on 27-Dec-2024 +###### Auto generated by spf13/cobra on 30-Dec-2024