Compare commits
	
		
			3 Commits
		
	
	
		
			v0.1.0
			...
			refactor-c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| b9d7d5a4f2 | |||
| 96378d047e | |||
| 888ee5ea4a | 
							
								
								
									
										10
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							| @@ -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", | ||||
|   | ||||
							
								
								
									
										25
									
								
								cmd/alias.go
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								cmd/alias.go
									
									
									
									
									
								
							| @@ -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()) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										38
									
								
								cmd/alias/alias.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								cmd/alias/alias.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| } | ||||
							
								
								
									
										108
									
								
								cmd/alias/alias_add.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								cmd/alias/alias_add.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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)) | ||||
| } | ||||
| @@ -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")) | ||||
| } | ||||
| @@ -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) | ||||
| } | ||||
							
								
								
									
										106
									
								
								cmd/alias_add.go
									
									
									
									
									
								
							
							
						
						
									
										106
									
								
								cmd/alias_add.go
									
									
									
									
									
								
							| @@ -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)) | ||||
| } | ||||
							
								
								
									
										38
									
								
								cmd/cache.go
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								cmd/cache.go
									
									
									
									
									
								
							| @@ -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()) | ||||
| } | ||||
							
								
								
									
										43
									
								
								cmd/cache/cache.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								cmd/cache/cache.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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) | ||||
| } | ||||
| @@ -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")) | ||||
| } | ||||
							
								
								
									
										13
									
								
								cmd/cache_dump.go → cmd/cache/cache_dump.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								cmd/cache_dump.go → cmd/cache/cache_dump.go
									
									
									
									
										vendored
									
									
								
							| @@ -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")) | ||||
| } | ||||
							
								
								
									
										14
									
								
								cmd/cache_load.go → cmd/cache/cache_load.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								cmd/cache_load.go → cmd/cache/cache_load.go
									
									
									
									
										vendored
									
									
								
							| @@ -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)) | ||||
| } | ||||
							
								
								
									
										33
									
								
								cmd/cache/cache_unlock.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								cmd/cache/cache_unlock.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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)) | ||||
| } | ||||
| @@ -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)) | ||||
| } | ||||
| @@ -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) | ||||
| } | ||||
							
								
								
									
										26
									
								
								cmd/config/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								cmd/config/config.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| } | ||||
| @@ -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") | ||||
| } | ||||
| @@ -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") | ||||
| } | ||||
							
								
								
									
										14
									
								
								cmd/docs.go
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								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) | ||||
| } | ||||
|   | ||||
| @@ -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") | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										66
									
								
								cmd/project/project.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								cmd/project/project.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| } | ||||
| @@ -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) | ||||
| } | ||||
| @@ -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() | ||||
| } | ||||
							
								
								
									
										28
									
								
								cmd/project/project_list.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								cmd/project/project_list.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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)) | ||||
| } | ||||
| @@ -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)") | ||||
| @@ -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) | ||||
| } | ||||
| @@ -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)) | ||||
| } | ||||
| @@ -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)) | ||||
| } | ||||
							
								
								
									
										150
									
								
								cmd/root.go
									
									
									
									
									
								
							
							
						
						
									
										150
									
								
								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) | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										121
									
								
								cmd/util/util.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								cmd/util/util.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										74
									
								
								cmd/util/util_completion.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								cmd/util/util_completion.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
| @@ -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` | ||||
| @@ -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, w, h 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] | ||||
							
								
								
									
										163
									
								
								cmd/util/util_init.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								cmd/util/util_init.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
| @@ -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 | ||||
| } | ||||
							
								
								
									
										143
									
								
								cmd/util_init.go
									
									
									
									
									
								
							
							
						
						
									
										143
									
								
								cmd/util_init.go
									
									
									
									
									
								
							| @@ -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 | ||||
| } | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
							
								
								
									
										3
									
								
								internal/cache/cache.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								internal/cache/cache.go
									
									
									
									
										vendored
									
									
								
							| @@ -67,11 +67,10 @@ func (c *Cache) LockCache() { | ||||
| 	c.checkLock() | ||||
|  | ||||
| 	file, err := os.OpenFile(c.file+".lock", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o640) | ||||
| 	defer file.Close() | ||||
|  | ||||
| 	if err != nil { | ||||
| 		c.log.Fatal("Failed to lock cache", c.log.Args("error", err)) | ||||
| 	} | ||||
| 	file.Close() | ||||
| } | ||||
|  | ||||
| func (c *Cache) checkLock() { | ||||
|   | ||||
							
								
								
									
										5
									
								
								internal/cache/cache_aliases.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								internal/cache/cache_aliases.go
									
									
									
									
										vendored
									
									
								
							| @@ -3,8 +3,9 @@ package cache | ||||
| import ( | ||||
| 	"errors" | ||||
|  | ||||
| 	"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects" | ||||
| 	"golang.org/x/exp/slices" | ||||
|  | ||||
| 	"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects" | ||||
| ) | ||||
|  | ||||
| func (c *Cache) deleteAlias(alias *ProjectAlias) { | ||||
| @@ -23,7 +24,7 @@ func (c *Cache) DeleteAlias(alias *ProjectAlias) { | ||||
|  | ||||
| func (c *Cache) addAlias(alias string, projectID int, remote string) error { | ||||
| 	if c.GetAliasByName(alias) != nil { | ||||
| 		return errors.New("Failed to add alias, already exists") | ||||
| 		return errors.New("failed to add alias, already exists") | ||||
| 	} | ||||
|  | ||||
| 	c.Aliases = append(c.Aliases, | ||||
|   | ||||
							
								
								
									
										2
									
								
								internal/cache/projects_fs.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								internal/cache/projects_fs.go
									
									
									
									
										vendored
									
									
								
							| @@ -32,7 +32,7 @@ func (c *Cache) GetProjectFromCwd() (*projects.Project, error) { | ||||
| 	if err != nil { | ||||
| 		return project, err | ||||
| 	} else if !strings.HasPrefix(cwd, c.path) { | ||||
| 		return project, errors.New("Not in any project path") | ||||
| 		return project, errors.New("not in any project path") | ||||
| 	} | ||||
|  | ||||
| 	// Strip projects dir from path | ||||
|   | ||||
							
								
								
									
										3
									
								
								internal/cache/projects_git.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								internal/cache/projects_git.go
									
									
									
									
										vendored
									
									
								
							| @@ -5,6 +5,7 @@ import ( | ||||
| 	"time" | ||||
|  | ||||
| 	git "github.com/go-git/go-git/v5" | ||||
|  | ||||
| 	"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects" | ||||
| ) | ||||
|  | ||||
| @@ -31,7 +32,7 @@ func (c *Cache) OpenProject(ctx context.Context, project *projects.Project) *git | ||||
| 		// Check to make sure we can connect before we time out | ||||
| 		// shouldn't be necessary, but go-git does not properly | ||||
| 		// honor its context | ||||
| 		if err := project.CheckHost(projects.GitProtoSSH); err != nil { | ||||
| 		if err = project.CheckHost(projects.GitProtoSSH); err != nil { | ||||
| 			c.log.Fatal("Git remote unreachable, giving up", c.log.Args("error", err)) | ||||
| 		} | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user