refactor-cmd-pkgs #1

Merged
rmcguire merged 2 commits from refactor-cmd-pkgs into main 2024-12-30 20:56:39 +00:00
62 changed files with 1025 additions and 811 deletions

10
.vscode/launch.json vendored
View File

@ -14,6 +14,16 @@
"https://gitea.libretechconsulting.com" "https://gitea.libretechconsulting.com"
] ]
}, },
{
"name": "List Aliases",
"type": "delve",
"request": "launch",
"program": "${workspaceFolder}",
"args": [
"alias",
"ls"
]
},
{ {
"name": "Generate Docs", "name": "Generate Docs",
"type": "delve", "type": "delve",

View File

@ -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
View 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
View 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))
}

View File

@ -1,19 +1,20 @@
package cmd package alias
import ( import (
"fmt" "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/pterm/pterm"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects"
) )
var aliasDeleteCmd = &cobra.Command{ var aliasDeleteCmd = &cobra.Command{
Use: "delete [fuzzy project or alias]", Use: "delete [fuzzy project or alias]",
Aliases: []string{"rm", "del", "d"}, Aliases: []string{"rm", "del", "d"},
Short: "Delete a project alias", Short: "Delete a project alias",
Long: aliasDeleteCmdLong, Long: util.AliasDeleteCmdLong,
PreRun: mustHaveAliases, PreRun: mustHaveAliases,
Run: runDeleteAliasCmd, Run: runDeleteAliasCmd,
} }
@ -22,32 +23,32 @@ func runDeleteAliasCmd(cmd *cobra.Command, args []string) {
var project *projects.Project var project *projects.Project
var err error var err error
fzfOpts := &fzfProjectOpts{ fzfOpts := &util.FzfProjectOpts{
Ctx: cmd.Context(), Ctx: cmd.Context(),
MustHaveAlias: true, MustHaveAlias: true,
} }
if len(args) > 0 { if len(args) > 0 {
fzfOpts.Search = searchStringFromArgs(args) fzfOpts.Search = utils.SearchStringFromArgs(args)
project = fzfFindProject(fzfOpts) project = utils.FzfFindProject(fzfOpts)
} else { } else {
project, err = fzfProject(fzfOpts) project, err = utils.FzfProject(fzfOpts)
} }
if project == nil || err != nil { 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, "error", err,
)) ))
} }
aliasStrings := projectCache.GetProjectAliasStrings(project) aliasStrings := utils.Cache().GetProjectAliasStrings(project)
deletionCandidates, err := pterm.DefaultInteractiveMultiselect. deletionCandidates, err := pterm.DefaultInteractiveMultiselect.
WithOptions(aliasStrings). WithOptions(aliasStrings).
Show() Show()
if err != nil || len(deletionCandidates) < 1 { 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, "error", err,
)) ))
} }
@ -61,26 +62,25 @@ func runDeleteAliasCmd(cmd *cobra.Command, args []string) {
Show() Show()
if !confirm { if !confirm {
plog.Warn("Alias deletion cancelled") utils.Logger().Warn("Alias deletion cancelled")
continue continue
} }
plog.Info("Deleting alias", plog.Args( utils.Logger().Info("Deleting alias", utils.Logger().Args(
"project", project.String(), "project", project.String(),
"alias", a, "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() { func init() {
aliasCmd.AddCommand(aliasDeleteCmd)
aliasDeleteCmd.PersistentFlags().Int("projectID", 0, "Specify a project by ID") 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")) viper.BindPFlag("alias.delete.projectID", aliasDeleteCmd.Flag("projectID"))
} }

View File

@ -1,8 +1,9 @@
package cmd package alias
import ( import (
"fmt" "fmt"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
"github.com/pterm/pterm" "github.com/pterm/pterm"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -14,20 +15,16 @@ var aliasListCmd = &cobra.Command{
Aliases: []string{"dump", "show", "ls", "ll", "l"}, Aliases: []string{"dump", "show", "ls", "ll", "l"},
Short: "List Aliases", Short: "List Aliases",
PreRun: mustHaveAliases, PreRun: mustHaveAliases,
Long: aliasListCmdLong, Long: util.AliasListCmdLong,
Run: runListAliasCmd, Run: runListAliasCmd,
} }
func runListAliasCmd(cmd *cobra.Command, args []string) { func runListAliasCmd(cmd *cobra.Command, args []string) {
remotes := viper.GetStringSlice(FlagRemote) remotes := viper.GetStringSlice(util.FlagRemote)
pterm.DefaultBox. pterm.DefaultBox.
WithLeftPadding(5).WithRightPadding(5). WithLeftPadding(5).WithRightPadding(5).
WithBoxStyle(&pterm.Style{pterm.FgLightBlue}). WithBoxStyle(&pterm.Style{pterm.FgLightBlue}).
WithTitle(pterm.Bold.Sprint(pterm.LightGreen("Aliases by Project"))). WithTitle(pterm.Bold.Sprint(pterm.LightGreen("Aliases by Project"))).
Print("\n" + projectCache.AliasesByProjectString(remotes...)) Print("\n" + utils.Cache().AliasesByProjectString(remotes...))
fmt.Print("\n\n") fmt.Print("\n\n")
} }
func init() {
aliasCmd.AddCommand(aliasListCmd)
}

View File

@ -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))
}

View File

@ -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
View 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)
}

View File

@ -1,4 +1,4 @@
package cmd package cache
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -21,11 +21,10 @@ var clearCmd = &cobra.Command{
func clearCache(cmd *cobra.Command, args []string) { func clearCache(cmd *cobra.Command, args []string) {
slog.Debug("Preparing to clear local cache") slog.Debug("Preparing to clear local cache")
projectCache.Clear(conf.Cache.Clear.ClearAliases) utils.Cache().Clear(utils.Config().Cache.Clear.ClearAliases)
} }
func init() { func init() {
cacheCmd.AddCommand(clearCmd)
clearCmd.Flags().Bool("clearAliases", false, "Will also clear aliases from the cache, use with caution") clearCmd.Flags().Bool("clearAliases", false, "Will also clear aliases from the cache, use with caution")
viper.BindPFlag("cache.clear.clearAliases", clearCmd.LocalFlags().Lookup("clearAliases")) viper.BindPFlag("cache.clear.clearAliases", clearCmd.LocalFlags().Lookup("clearAliases"))
} }

View File

@ -1,10 +1,12 @@
package cmd package cache
import ( import (
"fmt" "fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
) )
var dumpCmd = &cobra.Command{ var dumpCmd = &cobra.Command{
@ -17,16 +19,15 @@ var dumpCmd = &cobra.Command{
} }
func runCacheDunpCmd(cmd *cobra.Command, args []string) { func runCacheDunpCmd(cmd *cobra.Command, args []string) {
remotes := viper.GetStringSlice(FlagRemote) remotes := viper.GetStringSlice(util.FlagRemote)
if conf.Dump.Full { if utils.Config().Dump.Full {
fmt.Println(projectCache.DumpString(true, searchStringFromArgs(args), remotes...)) fmt.Println(utils.Cache().DumpString(true, utils.SearchStringFromArgs(args), remotes...))
} else { } else {
plog.Info(projectCache.String()) utils.Logger().Info(utils.Cache().String())
} }
} }
func init() { func init() {
cacheCmd.AddCommand(dumpCmd)
dumpCmd.PersistentFlags().BoolP("full", "f", false, "Dumps entire cache") dumpCmd.PersistentFlags().BoolP("full", "f", false, "Dumps entire cache")
viper.BindPFlag("dump.full", dumpCmd.LocalFlags().Lookup("full")) viper.BindPFlag("dump.full", dumpCmd.LocalFlags().Lookup("full"))
} }

View File

@ -1,8 +1,10 @@
package cmd package cache
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
) )
var loadCmd = &cobra.Command{ var loadCmd = &cobra.Command{
@ -17,15 +19,13 @@ wants to find a new project.`,
} }
func loadCache(cmd *cobra.Command, args []string) { func loadCache(cmd *cobra.Command, args []string) {
remotes := viper.GetStringSlice(FlagRemote) remotes := viper.GetStringSlice(util.FlagRemote)
projectCache.Refresh(remotes...) utils.Cache().Refresh(remotes...)
} }
func init() { func init() {
cacheCmd.AddCommand(loadCmd) loadCmd.PersistentFlags().Bool(util.FlagOwnerOnly, true,
loadCmd.PersistentFlags().Bool(FlagOwnerOnly, true,
"Only load projects that you are owner of") "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
View 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))
}

View File

@ -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))
}

View File

@ -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
View 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)
}

View File

@ -10,6 +10,7 @@ import (
"github.com/spf13/viper" "github.com/spf13/viper"
"gopkg.in/yaml.v3" "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/config"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/info" "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/info"
) )
@ -18,7 +19,7 @@ var configGenerateCmd = &cobra.Command{
Use: "generate", Use: "generate",
Short: "Generate a default configuration", Short: "Generate a default configuration",
Aliases: []string{"gen", "new", "default"}, Aliases: []string{"gen", "new", "default"},
Long: configGenCmdLong, Long: util.ConfigGenCmdLong,
Run: runConfigGenerateCmd, Run: runConfigGenerateCmd,
} }
@ -27,34 +28,34 @@ func runConfigGenerateCmd(cmd *cobra.Command, args []string) {
if viper.ConfigFileUsed() != "" { if viper.ConfigFileUsed() != "" {
configFile = viper.ConfigFileUsed() configFile = viper.ConfigFileUsed()
} else { } 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. write, _ := pterm.DefaultInteractiveContinue.
WithDefaultText("Really write config file?"). WithDefaultText("Really write config file?").
WithOptions([]string{"yes", "no"}). WithOptions([]string{"yes", "no"}).
Show() Show()
if write != "yes" { if write != "yes" {
plog.Fatal("Aborting config file write") utils.Logger().Fatal("Aborting config file write")
} }
writeConfigFile(newConf, configFile) writeConfigFile(newConf, configFile)
plog.Info("Wrote config to file", utils.Logger().Info("Wrote config to file",
plog.Args("file", configFile)) utils.Logger().Args("file", configFile))
} else { } else {
var c bytes.Buffer var c bytes.Buffer
enc := yaml.NewEncoder(&c) enc := yaml.NewEncoder(&c)
enc.SetIndent(2) enc.SetIndent(2)
enc.Encode(newConf) 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()) fmt.Print(c.String())
} }
} }
@ -62,11 +63,11 @@ func runConfigGenerateCmd(cmd *cobra.Command, args []string) {
func writeConfigFile(c *config.Config, path string) { func writeConfigFile(c *config.Config, path string) {
file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o640) file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o640)
if err != nil { 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) enc := yaml.NewEncoder(file)
if err := enc.Encode(c); err != nil { 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() { func init() {
configCmd.AddCommand(configGenerateCmd) configGenerateCmd.PersistentFlags().Bool(util.FlagPrompt, false, "Prompt for settings")
configGenerateCmd.PersistentFlags().Bool(FlagPrompt, false, "Prompt for settings") configGenerateCmd.PersistentFlags().Bool(util.FlagWrite, false, "Write config to file")
configGenerateCmd.PersistentFlags().Bool(FlagWrite, false, "Write config to file")
} }

View File

@ -6,6 +6,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
) )
var configShowCmd = &cobra.Command{ var configShowCmd = &cobra.Command{
@ -16,16 +18,16 @@ var configShowCmd = &cobra.Command{
} }
func runConfigShowCmd(cmd *cobra.Command, args []string) { 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 { 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 { for _, r := range c.Remotes {
r.Token = strings.Repeat("*", len(r.Token)) r.Token = strings.Repeat("*", len(r.Token))
} }
} else { } else {
plog.Warn("Displaying sensitive fields!") utils.Logger().Warn("Displaying sensitive fields!")
} }
enc := yaml.NewEncoder(os.Stdout) enc := yaml.NewEncoder(os.Stdout)
@ -34,6 +36,5 @@ func runConfigShowCmd(cmd *cobra.Command, args []string) {
} }
func init() { func init() {
configCmd.AddCommand(configShowCmd) configShowCmd.Flags().BoolP(util.FlagSensitive, "s", false, "Set to show sensitive fields")
configShowCmd.Flags().BoolP(FlagSensitive, "s", false, "Set to show sensitive fields")
} }

View File

@ -5,6 +5,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/cobra/doc" "github.com/spf13/cobra/doc"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
) )
var docsCmd = &cobra.Command{ var docsCmd = &cobra.Command{
@ -17,9 +19,9 @@ var docsCmd = &cobra.Command{
} }
func runDocsCmd(cmd *cobra.Command, args []string) { func runDocsCmd(cmd *cobra.Command, args []string) {
outDir, err := cmd.Flags().GetString(FlagDocsPath) outDir, err := cmd.Flags().GetString(util.FlagDocsPath)
if err != nil { if err != nil {
plog.Error("missing docs path") utils.Logger().Error("missing docs path")
} }
prepareDocsDir(cmd, outDir) prepareDocsDir(cmd, outDir)
@ -35,10 +37,10 @@ func runDocsCmd(cmd *cobra.Command, args []string) {
case "yaml": case "yaml":
err = doc.GenYamlTree(cmd.Root(), outDir) err = doc.GenYamlTree(cmd.Root(), outDir)
default: 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)) "type", args[0], "docsDir", outDir, "err", err))
} }
@ -47,14 +49,14 @@ func prepareDocsDir(_ *cobra.Command, outDir string) {
if err != nil { if err != nil {
err = os.Mkdir(outDir, 0o755) err = os.Mkdir(outDir, 0o755)
if err != nil { 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)) "error", err.Error(), "docsDir", outDir))
} }
} }
} }
func init() { 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) rootCmd.AddCommand(docsCmd)
} }

View File

@ -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
View 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)
}

View File

@ -1,21 +1,19 @@
package cmd package project
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
) )
var projectAddCmd = &cobra.Command{ var projectAddCmd = &cobra.Command{
Use: "add", Use: "add",
Short: "Add a new Git project", Short: "Add a new Git project",
Aliases: []string{"a", "alias"}, Aliases: []string{"a", "alias"},
Long: projAddCmdLong, Long: util.ProjAddCmdLong,
Run: projectAddCmdRun, Run: projectAddCmdRun,
} }
func projectAddCmdRun(cmd *cobra.Command, args []string) { func projectAddCmdRun(cmd *cobra.Command, args []string) {
getProject(args) getProject(cmd, args)
}
func init() {
projectCmd.AddCommand(projectAddCmd)
} }

View File

@ -1,4 +1,4 @@
package cmd package project
import ( import (
"fmt" "fmt"
@ -7,6 +7,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
) )
var projectGoCmd = &cobra.Command{ var projectGoCmd = &cobra.Command{
@ -15,30 +17,30 @@ var projectGoCmd = &cobra.Command{
Aliases: []string{"goto", "cd"}, Aliases: []string{"goto", "cd"},
Args: cobra.MaximumNArgs(1), Args: cobra.MaximumNArgs(1),
ArgAliases: []string{"project"}, ArgAliases: []string{"project"},
ValidArgsFunction: validAliasesFunc, ValidArgsFunction: util.ValidAliasesFunc,
Long: projGoCmdLong, Long: util.ProjGoCmdLong,
Run: projectGoCmdRun, Run: projectGoCmdRun,
} }
func projectGoCmdRun(cmd *cobra.Command, args []string) { func projectGoCmdRun(cmd *cobra.Command, args []string) {
remotes := viper.GetStringSlice(FlagRemote) remotes := viper.GetStringSlice(util.FlagRemote)
fzfOpts := &fzfProjectOpts{ fzfOpts := &util.FzfProjectOpts{
Ctx: cmd.Context(), Ctx: cmd.Context(),
Search: searchStringFromArgs(args), Search: utils.SearchStringFromArgs(args),
MustHaveAlias: true, MustHaveAlias: true,
Remotes: remotes, Remotes: remotes,
} }
project := fzfSearchProjectAliases(fzfOpts) project := utils.FzfSearchProjectAliases(fzfOpts)
if project == nil { if project == nil {
plog.Fatal("No project selected, nowhere to go") utils.Logger().Fatal("No project selected, nowhere to go")
} }
projectCache.GoTo(project) utils.Cache().GoTo(project)
project.SetRepo(projectCache.OpenProject(cmd.Context(), project)) project.SetRepo(utils.Cache().OpenProject(cmd.Context(), project))
plog.Debug("Project ready", plog.Args( utils.Logger().Debug("Project ready", utils.Logger().Args(
"path", projectCache.GetProjectPath(project), "path", utils.Cache().GetProjectPath(project),
"project", project, "project", project,
)) ))
@ -46,10 +48,6 @@ func projectGoCmdRun(cmd *cobra.Command, args []string) {
// This should be read by any source command, for instance // This should be read by any source command, for instance
// `cd "$(git-project-manager projects cd somealias)"` // `cd "$(git-project-manager projects cd somealias)"`
fmt.Println(projectCache.GetProjectPath(project)) fmt.Println(utils.Cache().GetProjectPath(project))
exec.Command("cd", projectCache.GetProjectPath(project)).Run() exec.Command("cd", utils.Cache().GetProjectPath(project)).Run()
}
func init() {
projectCmd.AddCommand(projectGoCmd)
} }

View 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))
}

View File

@ -1,6 +1,7 @@
package cmd package project
import ( import (
"context"
"errors" "errors"
"os" "os"
"os/exec" "os/exec"
@ -8,6 +9,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
) )
var projectOpenCmd = &cobra.Command{ var projectOpenCmd = &cobra.Command{
@ -15,8 +18,8 @@ var projectOpenCmd = &cobra.Command{
Short: "Open project in your IDE", Short: "Open project in your IDE",
Aliases: []string{"goto", "cd"}, Aliases: []string{"goto", "cd"},
Args: cobra.OnlyValidArgs, Args: cobra.OnlyValidArgs,
ValidArgsFunction: validAliasesFunc, ValidArgsFunction: util.ValidAliasesFunc,
Long: projOpenCmdLong, Long: util.ProjOpenCmdLong,
Run: projectOpenCmdRun, Run: projectOpenCmdRun,
} }
@ -48,47 +51,47 @@ func projectOpenCmdRun(cmd *cobra.Command, args []string) {
// Find an editor // Find an editor
editor := findEditor() editor := findEditor()
if editor == "" { if editor == "" {
plog.Fatal("No usable editor found") utils.Logger().Fatal("No usable editor found")
} }
remotes := viper.GetStringSlice(FlagRemote) remotes := viper.GetStringSlice(util.FlagRemote)
fzfOpts := &fzfProjectOpts{ fzfOpts := &util.FzfProjectOpts{
Ctx: cmd.Context(), Ctx: cmd.Context(),
Search: searchStringFromArgs(args), Search: utils.SearchStringFromArgs(args),
Remotes: remotes, Remotes: remotes,
} }
project := fzfCwdOrSearchProjectAliases(fzfOpts) project := utils.FzfCwdOrSearchProjectAliases(fzfOpts)
if project == nil { 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 // Check the project
path := projectCache.GetProjectPath(project) path := utils.Cache().GetProjectPath(project)
if _, err := os.Stat(path); err != nil { 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 // Open the project with the editor
file := getEntrypointFile(path) 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 // Compile arguments
args := make([]string, 0, 1) args := make([]string, 0, 1)
if conf.Editor.OpenFlags != "" { if utils.Config().Editor.OpenFlags != "" {
args = append(args, conf.Editor.OpenFlags) args = append(args, utils.Config().Editor.OpenFlags)
} }
args = append(args, path) args = append(args, path)
// Launch editor // Launch editor
cmd := exec.CommandContext(rootCmd.Context(), editor, args...) cmd := exec.CommandContext(ctx, editor, args...)
cmd.Dir = filepath.Dir(path) cmd.Dir = filepath.Dir(path)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil { 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(), "error", err.Error(),
"command", cmd.String(), "command", cmd.String(),
)) ))
@ -100,7 +103,7 @@ func openEditor(editor string, path string) {
func getEntrypointFile(projectPath string) string { func getEntrypointFile(projectPath string) string {
if err := os.Chdir(projectPath); err != nil { if err := os.Chdir(projectPath); err != nil {
return "" return ""
} else if conf.Editor.OpenDirectory { } else if utils.Config().Editor.OpenDirectory {
return "." return "."
} }
@ -119,10 +122,10 @@ func findEditor() string {
var err error var err error
// First try configured editor // First try configured editor
if conf.Editor.Binary != "" { if utils.Config().Editor.Binary != "" {
editor, err = getEditor(conf.Editor.Binary) editor, err = getEditor(utils.Config().Editor.Binary)
if err != nil || editor == "" { 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, "error", err,
)) ))
} }
@ -130,7 +133,7 @@ func findEditor() string {
// Then try to find a known editor // Then try to find a known editor
if editor == "" || err != nil { if editor == "" || err != nil {
conf.Editor.OpenFlags = "" utils.Config().Editor.OpenFlags = ""
for _, e := range knownEditors { for _, e := range knownEditors {
path, err := getEditor(e) path, err := getEditor(e)
if err == nil && path != "" { 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, "editor", editor,
"command", editor+" "+conf.Editor.OpenFlags, "command", editor+" "+utils.Config().Editor.OpenFlags,
)) ))
return editor return editor
@ -164,7 +167,7 @@ func getEditorPath(editor string) (string, error) {
editor, _ = exec.LookPath(editor) editor, _ = exec.LookPath(editor)
} }
return resolvePath(editor) return util.ResolvePath(editor)
} }
func isEditorExecutable(editor string) bool { func isEditorExecutable(editor string) bool {
@ -180,7 +183,6 @@ func isEditorExecutable(editor string) bool {
} }
func init() { func init() {
projectCmd.AddCommand(projectOpenCmd)
projectOpenCmd.PersistentFlags().String("displayName", "", "Set display name of editor (meant for config file)") projectOpenCmd.PersistentFlags().String("displayName", "", "Set display name of editor (meant for config file)")
projectOpenCmd.PersistentFlags().String("binary", "", "Path to editor binary") projectOpenCmd.PersistentFlags().String("binary", "", "Path to editor binary")
projectOpenCmd.PersistentFlags().String("openFlags", "", "Optional flags when opening project (e.g. --reuse-window)") projectOpenCmd.PersistentFlags().String("openFlags", "", "Optional flags when opening project (e.g. --reuse-window)")

View File

@ -1,4 +1,4 @@
package cmd package project
import ( import (
"fmt" "fmt"
@ -7,35 +7,37 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
) )
var projectRunCmd = &cobra.Command{ var projectRunCmd = &cobra.Command{
Use: "run", Use: "run",
Short: "Run the project (e.g. go run .)", Short: "Run the project (e.g. go run .)",
Aliases: []string{"exec", "r"}, Aliases: []string{"exec", "r"},
Long: projRunCmdLong, Long: util.ProjRunCmdLong,
Run: projectRunCmdRun, Run: projectRunCmdRun,
} }
func projectRunCmdRun(cmd *cobra.Command, args []string) { func projectRunCmdRun(cmd *cobra.Command, args []string) {
remotes := viper.GetStringSlice(FlagRemote) remotes := viper.GetStringSlice(util.FlagRemote)
fzfOpts := &fzfProjectOpts{ fzfOpts := &util.FzfProjectOpts{
Ctx: cmd.Context(), Ctx: cmd.Context(),
Search: searchStringFromArgs(args), Search: utils.SearchStringFromArgs(args),
Remotes: remotes, Remotes: remotes,
} }
project := fzfCwdOrSearchProjectAliases(fzfOpts) project := utils.FzfCwdOrSearchProjectAliases(fzfOpts)
if project == nil { if project == nil {
plog.Fatal("No project selected, nothing to open") utils.Logger().Fatal("No project selected, nothing to open")
} }
lang := project.GetLanguage() lang := project.GetLanguage()
if lang == nil { 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 { switch lang.Name {
case "Go": case "Go":
@ -50,5 +52,5 @@ func projectRunCmdRun(cmd *cobra.Command, args []string) {
} }
func init() { func init() {
projectCmd.AddCommand(projectRunCmd) ProjectCmd.AddCommand(projectRunCmd)
} }

View File

@ -1,4 +1,4 @@
package cmd package project
import ( import (
"fmt" "fmt"
@ -8,6 +8,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects" "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", Short: "Show detail for a Git project",
Aliases: []string{"cat", "s"}, Aliases: []string{"cat", "s"},
Args: cobra.ArbitraryArgs, Args: cobra.ArbitraryArgs,
Long: projShowCmdLong, Long: util.ProjShowCmdLong,
Run: projectShowCmdRun, Run: projectShowCmdRun,
} }
@ -24,24 +25,24 @@ func projectShowCmdRun(cmd *cobra.Command, args []string) {
var project *projects.Project var project *projects.Project
var inCwd bool var inCwd bool
remotes := viper.GetStringSlice(FlagRemote) remotes := viper.GetStringSlice(util.FlagRemote)
fzfOpts := &fzfProjectOpts{ fzfOpts := &util.FzfProjectOpts{
Ctx: cmd.Context(), Ctx: cmd.Context(),
Search: searchStringFromArgs(args), Search: utils.SearchStringFromArgs(args),
Remotes: remotes, Remotes: remotes,
} }
// Try to find project from current directory // Try to find project from current directory
if viper.GetBool(ViperProjectShowCurrent) { if viper.GetBool(util.ViperProjectShowCurrent) {
var err error var err error
project, err = projectCache.GetProjectFromCwd() project, err = utils.Cache().GetProjectFromCwd()
if err != nil { if err != nil {
// Not an error because we're still going to try to find a project // 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, "error", err,
)) ))
} else if project == nil { } 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 { } else {
inCwd = true inCwd = true
} }
@ -49,15 +50,15 @@ func projectShowCmdRun(cmd *cobra.Command, args []string) {
// Otherwise find from the given search string // Otherwise find from the given search string
if project == nil { if project == nil {
project = fzfFindProject(fzfOpts) project = utils.FzfFindProject(fzfOpts)
} }
// Do a full fuzzy find if all else fails // Do a full fuzzy find if all else fails
if project == nil { if project == nil {
var err error var err error
project, err = fzfProject(fzfOpts) project, err = utils.FzfProject(fzfOpts)
if err != nil || project == nil { 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, "error", err,
)) ))
} }
@ -68,17 +69,17 @@ func projectShowCmdRun(cmd *cobra.Command, args []string) {
WithLeftPadding(5).WithRightPadding(5). WithLeftPadding(5).WithRightPadding(5).
WithBoxStyle(&pterm.Style{pterm.FgLightBlue}). WithBoxStyle(&pterm.Style{pterm.FgLightBlue}).
WithTitle(pterm.Bold.Sprint(pterm.LightGreen("Project Information"))). WithTitle(pterm.Bold.Sprint(pterm.LightGreen("Project Information"))).
Println(projectCache.ProjectString(project)) Println(utils.Cache().ProjectString(project))
fmt.Println() fmt.Println()
if inCwd { if inCwd {
project.SetRepo(projectCache.OpenProject(cmd.Context(), project)) project.SetRepo(utils.Cache().OpenProject(cmd.Context(), project))
fmt.Fprintln(os.Stderr, project.GetGitInfo()+"\n") fmt.Fprintln(os.Stderr, project.GetGitInfo()+"\n")
} }
} }
func init() { func init() {
projectCmd.AddCommand(projectShowCmd) ProjectCmd.AddCommand(projectShowCmd)
projectShowCmd.PersistentFlags().Bool(FlagCurrent, false, "Use project in CWD rather than fuzzy find") projectShowCmd.PersistentFlags().Bool(util.FlagCurrent, false, "Use project in CWD rather than fuzzy find")
viper.BindPFlag(ViperProjectShowCurrent, projectShowCmd.Flag(FlagCurrent)) viper.BindPFlag(util.ViperProjectShowCurrent, projectShowCmd.Flag(util.FlagCurrent))
} }

View File

@ -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))
}

View File

@ -12,28 +12,32 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "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" "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/config"
) )
var (
conf config.Config
plog *pterm.Logger
)
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "git-project-manager", Use: "git-project-manager",
Aliases: []string{"gpm"}, Aliases: []string{"gpm"},
Short: "Find and use Git projects locally", Short: "Find and use Git projects locally",
Long: rootCmdLong, Long: util.RootCmdLong,
PersistentPreRun: initRootCmd, 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 // Hook traversal is enabled, so this will be run for all
// sub-commands regardless of their registered pre-hooks // sub-commands regardless of their registered pre-hooks
func initRootCmd(cmd *cobra.Command, args []string) { 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. // Execute adds all child commands to the root command and sets flags appropriately.
@ -51,28 +55,37 @@ func Execute() {
func init() { func init() {
cobra.EnableTraverseRunHooks = true cobra.EnableTraverseRunHooks = true
cobra.OnInitialize(initConfig)
utils = &util.Utils{}
cobra.OnInitialize(getInitConfigFunc(utils))
// Global flags // Global flags
rootCmd.PersistentFlags().String(FlagConfig, "", rootCmd.PersistentFlags().String(util.FlagConfig, "",
"config file (default is "+defConfigPath+")") "config file (default is "+util.DefConfigPath+")")
rootCmd.PersistentFlags().String(FlagPath, "", rootCmd.PersistentFlags().String(util.FlagPath, "",
"Sets a path for local clones of projects") "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") "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.") "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()) 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. // initConfig reads in config file and ENV variables if set.
func initConfig() { func getInitConfigFunc(utils *util.Utils) func() {
cfgFile := viper.GetString(FlagConfig) return func() {
cfgFile := viper.GetString(util.FlagConfig)
if cfgFile != "" { if cfgFile != "" {
// Use config file from the flag. // Use config file from the flag.
viper.SetConfigFile(cfgFile) viper.SetConfigFile(cfgFile)
@ -85,39 +98,44 @@ func initConfig() {
configPath := filepath.Join(home, ".config") configPath := filepath.Join(home, ".config")
viper.AddConfigPath(configPath) viper.AddConfigPath(configPath)
viper.SetConfigType("yaml") viper.SetConfigType("yaml")
viper.SetConfigName(getConfigName(configPath)) viper.SetConfigName(util.GetConfigName(configPath))
} }
viper.AutomaticEnv() viper.AutomaticEnv()
viper.ReadInConfig() viper.ReadInConfig()
// Configure pretty logger // Configure pretty logger
plog = pterm.DefaultLogger. plog := pterm.DefaultLogger.
WithLevel(getPtermLogLevel(viper.GetString(FlagLogLevel))). WithLevel(getPtermLogLevel(viper.GetString(util.FlagLogLevel))).
WithWriter(os.Stderr) WithWriter(os.Stderr)
if plog.Level == pterm.LogLevelDebug { if plog.Level == pterm.LogLevelDebug {
pterm.EnableDebugMessages() pterm.EnableDebugMessages()
} }
utils.SetLogger(plog)
// Load into struct to not be so darn pythonic, retrieving // Load into struct to not be so darn pythonic, retrieving
// settings by untyped string "name" // settings by untyped string "name"
conf := new(config.Config)
if err := viper.Unmarshal(&conf); err != nil { if err := viper.Unmarshal(&conf); err != nil {
plog.Error("Failed loading config", plog.Args("err", err)) plog.Error("Failed loading config", plog.Args("err", err))
} }
utils.SetConfig(conf)
if len(os.Args) > 0 && configExemptCommands.Match([]byte(os.Args[1])) { if len(os.Args) > 0 && configExemptCommands.Match([]byte(os.Args[1])) {
plog.Debug("Permitting missing config for config sub-command") plog.Debug("Permitting missing config for config sub-command")
return return
} else if conf.ProjectPath == "" { } else if conf.ProjectPath == "" {
plog.Fatal("Minimal configuration missing, must have projectPath", plog.Args( plog.Fatal("Minimal configuration missing, must have projectPath", plog.Args(
"do", "do",
"Try running `git-project-manager config default > "+defConfigPath, "Try running `git-project-manager config default > "+util.DefConfigPath,
)) ))
} }
checkConfigPerms(viper.ConfigFileUsed()) // Abort on world-readable config checkConfigPerms(viper.ConfigFileUsed()) // Abort on world-readable config
plog.Debug("Configuration loaded", plog.Args("conf", conf)) utils.Logger().Debug("Configuration loaded", plog.Args("conf", conf))
}
} }
func getPtermLogLevel(level string) pterm.LogLevel { func getPtermLogLevel(level string) pterm.LogLevel {
@ -141,12 +159,12 @@ func getPtermLogLevel(level string) pterm.LogLevel {
func checkConfigPerms(file string) { func checkConfigPerms(file string) {
stat, err := os.Stat(file) stat, err := os.Stat(file)
if err != nil { if err != nil {
plog.Error("Failure reading configuration", plog.Args("err", err)) utils.Logger().Error("Failure reading configuration", utils.Logger().Args("err", err))
return return
} }
if stat.Mode().Perm()&0o004 == 0o004 { if stat.Mode().Perm()&0o004 == 0o004 {
plog.Error("Configuration is world-readable. Recomment 0400.", utils.Logger().Error("Configuration is world-readable. Recomment 0400.",
plog.Args("mode", stat.Mode().String())) utils.Logger().Args("mode", stat.Mode().String()))
os.Exit(1) os.Exit(1)
} }
} }

121
cmd/util/util.go Normal file
View 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
}

View 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
}

View File

@ -1,4 +1,4 @@
package cmd package util
const ( const (
// Cobra Flags // Cobra Flags
@ -25,62 +25,62 @@ const (
) )
const ( const (
defGitlabHost = "https://gitlab.com" DefGitlabHost = "https://gitlab.com"
defLogLevel = "info" DefLogLevel = "info"
defConfigPath = "~/.config/git-project-manager.yaml" DefConfigPath = "~/.config/git-project-manager.yaml"
configName = "git-project-manager" ConfigName = "git-project-manager"
legacyConfigName = "gitlab-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.` 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` 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` 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 The project cache keeps this speedy, without smashing against the Git
API every time a new project is added / searched for` 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 your chosen term for the project as an alias, and offers helpful
shortcuts for moving around in projects and opening your code` 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 If not found, will enter fzf mode. If not cloned, will clone
the project locally.` 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 If project is not already cloned, its path will be built and it
will be cloned from source control. will be cloned from source control.
If conf.projects.alwaysPull, a git pull will be ran automatically` 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 .)` 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` 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` 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` 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). 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 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.` 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` 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` to seed the configuration file`

View File

@ -1,4 +1,4 @@
package cmd package util
import ( import (
"context" "context"
@ -10,7 +10,7 @@ import (
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects" "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects"
) )
type fzfProjectOpts struct { type FzfProjectOpts struct {
Ctx context.Context Ctx context.Context
Search string Search string
MustHaveAlias bool MustHaveAlias bool
@ -19,14 +19,14 @@ type fzfProjectOpts struct {
// This will try to find a project by alias if a search term // This will try to find a project by alias if a search term
// is given, otherwise will fuzzy find by project // 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 var project *projects.Project
if opts.Search != "" { if opts.Search != "" {
project = fzfSearchProjectAliases(opts) project = u.FzfSearchProjectAliases(opts)
} else { } else {
var err error var err error
project, err = fzfProject(opts) project, err = u.FzfProject(opts)
if project == nil || err != nil { if project == nil || err != nil {
return nil return nil
} }
@ -38,35 +38,35 @@ func fzfFindProject(opts *fzfProjectOpts) *projects.Project {
// If . is given as a project, will open project from the // If . is given as a project, will open project from the
// current working directory. Otherwise, will attempt to fuzzy-find // current working directory. Otherwise, will attempt to fuzzy-find
// a project given a search term if provided // 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 var project *projects.Project
if opts.Search == "." { if opts.Search == "." {
project, _ = projectCache.GetProjectFromCwd() project, _ = u.Cache().GetProjectFromCwd()
} else { } else {
project = fzfSearchProjectAliases(opts) project = u.FzfSearchProjectAliases(opts)
} }
return project return project
} }
// This will fuzzy search only aliases, preferring an exact // This will fuzzy search only aliases, preferring an exact
// match if one is given // match if one is given
func fzfSearchProjectAliases(opts *fzfProjectOpts) *projects.Project { func (u *Utils) FzfSearchProjectAliases(opts *FzfProjectOpts) *projects.Project {
var project *projects.Project var project *projects.Project
var alias *cache.ProjectAlias var alias *cache.ProjectAlias
if alias = projectCache.GetAliasByName(opts.Search, opts.Remotes...); alias != nil { if alias = u.Cache().GetAliasByName(opts.Search, opts.Remotes...); alias != nil {
project = projectCache.GetProjectByAlias(alias) project = u.Cache().GetProjectByAlias(alias)
plog.Info("Perfect alias match... flawless") u.Logger().Info("Perfect alias match... flawless")
} else { } else {
// Get fuzzy if we don't have an exact match // 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 len(aliases) > 1 {
// If multiple aliases were found, switch over to project // If multiple aliases were found, switch over to project
// by alias mode with merging // by alias mode with merging
// alias = fzfAliasFromAliases(rootCmd.Context(), aliases) // alias = fzfAliasFromAliases(rootCmd.Context(), aliases)
project, _ = fzfProjectFromAliases(opts, aliases) project, _ = u.FzfProjectFromAliases(opts, aliases)
} else if len(aliases) == 1 { } else if len(aliases) == 1 {
alias = aliases[0] alias = aliases[0]
project = projectCache.GetProjectByAlias(alias) project = u.Cache().GetProjectByAlias(alias)
} }
} }
return project return project
@ -76,18 +76,18 @@ func fzfSearchProjectAliases(opts *fzfProjectOpts) *projects.Project {
// a single one. Replaced by fzfProjectFromAliases in fzfSearchProjectAliases // a single one. Replaced by fzfProjectFromAliases in fzfSearchProjectAliases
// as merging is preferred, but can be used if it's ever desirable to // as merging is preferred, but can be used if it's ever desirable to
// return a single alias from all aliases // 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 var alias *cache.ProjectAlias
i, err := fzf.Find( i, err := fzf.Find(
aliases, aliases,
func(i int) string { 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.WithContext(opts.Ctx),
fzf.WithHeader("Choose an Alias"), fzf.WithHeader("Choose an Alias"),
) )
if err != nil { 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 { } else {
alias = aliases[i] 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 // Given a list of aliases, merge them together and use the resulting
// list of projects to return a project // 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, *projects.Project, error,
) { ) {
mergedProjects := projectsFromAliases(aliases) mergedProjects := u.projectsFromAliases(aliases)
if len(mergedProjects) == 1 { if len(mergedProjects) == 1 {
return mergedProjects[0], nil 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)) projects := make([]*projects.Project, 0, len(aliases))
for _, a := range aliases { for _, a := range aliases {
project := projectCache.GetProjectByAlias(a) project := u.Cache().GetProjectByAlias(a)
if project != nil && !slices.Contains(projects, project) { if project != nil && !slices.Contains(projects, project) {
projects = append(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 // If opts.MustHaveAlias, will only allow selection of projects
// that have at least one alias defined // 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 var searchableProjects []*projects.Project
if opts.MustHaveAlias { if opts.MustHaveAlias {
searchableProjects = projectCache.GetProjectsWithAliases() searchableProjects = u.Cache().GetProjectsWithAliases()
} else { } else {
searchableProjects = projectCache.Projects searchableProjects = u.Cache().Projects
} }
// Filter out unwanted remotes if provided // Filter out unwanted remotes if provided
searchableProjects = filterProjectsWithRemotes(searchableProjects, opts.Remotes...) searchableProjects = u.FilterProjectsWithRemotes(searchableProjects, opts.Remotes...)
return fzfProjectFromProjects(opts, searchableProjects) return u.FzfProjectFromProjects(opts, searchableProjects)
} }
// Takes a list of projects and performs a fuzzyfind // 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, *projects.Project, error,
) { ) {
i, err := fzf.Find(projects, i, err := fzf.Find(projects,
func(i int) string { func(i int) string {
// Display the project along with its aliases // Display the project along with its aliases
return projectCache.GetProjectStringWithAliases(projects[i]) return u.Cache().GetProjectStringWithAliases(projects[i])
}, },
fzf.WithPreviewWindow( fzf.WithPreviewWindow(
func(i, width, height int) string { func(i, width, height int) string {
return projectCache.ProjectString(projects[i]) return u.Cache().ProjectString(projects[i])
}, },
), ),
fzf.WithContext(opts.Ctx), fzf.WithContext(opts.Ctx),
@ -156,12 +156,12 @@ func fzfProjectFromProjects(opts *fzfProjectOpts, projects []*projects.Project)
return projects[i], nil return projects[i], nil
} }
func fzfPreviewWindow(i, _, _ int) string { func (u *Utils) FzfPreviewWindow(i, _, _ int) string {
p := projectCache.Projects[i] p := u.Cache().Projects[i]
return projectCache.ProjectString(p) 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)) filteredProjects := make([]*projects.Project, 0, len(gitProjects))
if len(remotes) > 0 { if len(remotes) > 0 {
for _, p := range gitProjects { for _, p := range gitProjects {
@ -177,7 +177,7 @@ func filterProjectsWithRemotes(gitProjects []*projects.Project, remotes ...strin
// Nearly useless function that simply returns either an // Nearly useless function that simply returns either an
// empty string, or a string from the first arg if one is provided // 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 var term string
if len(args) > 0 { if len(args) > 0 {
term = args[0] term = args[0]

163
cmd/util/util_init.go Normal file
View 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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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 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 * [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

View File

@ -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 delete](git-project-manager_alias_delete.md) - Delete a project alias
* [git-project-manager alias list](git-project-manager_alias_list.md) - List Aliases * [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

View File

@ -31,4 +31,4 @@ git-project-manager alias add [flags]
* [git-project-manager alias](git-project-manager_alias.md) - Manage project aliases * [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

View File

@ -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 * [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

View File

@ -29,4 +29,4 @@ git-project-manager alias list [flags]
* [git-project-manager alias](git-project-manager_alias.md) - Manage project aliases * [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

View File

@ -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 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 * [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

View File

@ -33,4 +33,4 @@ git-project-manager cache clear [flags]
* [git-project-manager cache](git-project-manager_cache.md) - Manage Git project cache * [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

View File

@ -31,4 +31,4 @@ git-project-manager cache dump [flags]
* [git-project-manager cache](git-project-manager_cache.md) - Manage Git project cache * [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

View File

@ -33,4 +33,4 @@ git-project-manager cache load [flags]
* [git-project-manager cache](git-project-manager_cache.md) - Manage Git project cache * [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

View File

@ -31,4 +31,4 @@ git-project-manager cache unlock [flags]
* [git-project-manager cache](git-project-manager_cache.md) - Manage Git project cache * [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

View File

@ -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 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 * [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

View File

@ -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 * [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

View File

@ -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 * [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

View File

@ -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 * [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

View File

@ -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 * [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

View File

@ -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 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 * [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

View File

@ -32,4 +32,4 @@ git-project-manager config generate [flags]
* [git-project-manager config](git-project-manager_config.md) - Git Project Manager Configuration * [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

View File

@ -26,4 +26,4 @@ git-project-manager config show [flags]
* [git-project-manager config](git-project-manager_config.md) - Git Project Manager Configuration * [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

View File

@ -26,4 +26,4 @@ git-project-manager docs [flags]
* [git-project-manager](git-project-manager.md) - Find and use Git projects locally * [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

View File

@ -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 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 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 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 * [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

View File

@ -30,4 +30,4 @@ git-project-manager project add [flags]
* [git-project-manager project](git-project-manager_project.md) - Use a Git project * [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

View File

@ -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 * [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

View File

@ -31,4 +31,4 @@ git-project-manager project list [flags]
* [git-project-manager project](git-project-manager_project.md) - Use a Git project * [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

View File

@ -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 * [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

View File

@ -30,4 +30,4 @@ git-project-manager project run [flags]
* [git-project-manager project](git-project-manager_project.md) - Use a Git project * [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

View File

@ -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 * [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