git-project-manager/cmd/root.go

183 lines
5.3 KiB
Go
Raw Permalink Normal View History

2023-12-04 21:53:01 +00:00
package cmd
import (
2023-12-05 21:56:47 +00:00
"context"
2023-12-04 21:53:01 +00:00
"os"
2023-12-05 21:56:47 +00:00
"os/signal"
2024-12-27 22:42:44 +00:00
"path/filepath"
"regexp"
2025-01-01 01:58:24 +00:00
"runtime/debug"
2023-12-05 21:56:47 +00:00
"strings"
2023-12-04 21:53:01 +00:00
2023-12-07 17:08:56 +00:00
"github.com/pterm/pterm"
2023-12-04 21:53:01 +00:00
"github.com/spf13/cobra"
"github.com/spf13/viper"
2024-10-01 18:29:14 +00:00
2024-12-30 20:50:31 +00:00
"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"
2024-12-30 19:54:32 +00:00
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
2024-12-19 19:55:49 +00:00
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/config"
2023-12-04 21:53:01 +00:00
)
var rootCmd = &cobra.Command{
2024-12-27 22:42:44 +00:00
Use: "git-project-manager",
2023-12-10 04:45:30 +00:00
Aliases: []string{"gpm"},
2024-12-27 22:42:44 +00:00
Short: "Find and use Git projects locally",
2024-12-30 19:54:32 +00:00
Long: util.RootCmdLong,
2023-12-05 21:56:47 +00:00
PersistentPreRun: initRootCmd,
}
2024-12-30 19:54:32 +00:00
var (
2025-01-01 01:51:04 +00:00
Version = "development"
2024-12-30 19:54:32 +00:00
configExemptCommands = regexp.MustCompile(`^(doc|conf)`)
2025-01-01 01:51:04 +00:00
utils *util.Utils
2024-12-30 19:54:32 +00:00
)
2024-12-27 22:42:44 +00:00
2023-12-05 21:56:47 +00:00
// Hook traversal is enabled, so this will be run for all
// sub-commands regardless of their registered pre-hooks
func initRootCmd(cmd *cobra.Command, args []string) {
2024-12-30 20:50:31 +00:00
cmd.SetContext(util.AddToCtx(cmd.Context(), utils))
2024-12-30 19:54:32 +00:00
utils.InitProjectPath(cmd, args)
2023-12-04 21:53:01 +00:00
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
2023-12-05 21:56:47 +00:00
ctx, cncl := signal.NotifyContext(context.Background(), os.Kill, os.Interrupt)
defer cncl()
err := rootCmd.ExecuteContext(ctx)
2023-12-04 21:53:01 +00:00
if err != nil {
2024-12-27 22:42:44 +00:00
pterm.Error.Printfln("%s", pterm.LightYellow("Command failed, "+err.Error()))
2023-12-04 21:53:01 +00:00
os.Exit(1)
}
}
func init() {
2023-12-05 21:56:47 +00:00
cobra.EnableTraverseRunHooks = true
2024-12-30 19:54:32 +00:00
utils = &util.Utils{}
cobra.OnInitialize(getInitConfigFunc(utils))
2023-12-05 21:56:47 +00:00
// Global flags
2024-12-30 19:54:32 +00:00
rootCmd.PersistentFlags().String(util.FlagConfig, "",
"config file (default is "+util.DefConfigPath+")")
rootCmd.PersistentFlags().String(util.FlagPath, "",
2023-12-05 21:56:47 +00:00
"Sets a path for local clones of projects")
2024-12-30 19:54:32 +00:00
rootCmd.PersistentFlags().String(util.FlagLogLevel, util.DefLogLevel,
2023-12-05 21:56:47 +00:00
"Default log level -- info, warn, error, debug")
2024-12-30 19:54:32 +00:00
rootCmd.PersistentFlags().StringSlice(util.FlagRemote, []string{},
"Specify remotes by host for any sub-command. Provide multiple times or comma delimited.")
2023-12-05 21:56:47 +00:00
2024-12-30 20:50:31 +00:00
viper.BindPFlags(rootCmd.PersistentFlags())
// Flag autocompletion
2024-12-30 19:54:32 +00:00
rootCmd.RegisterFlagCompletionFunc(util.FlagLogLevel, util.ValidLogLevelsFunc)
2024-12-30 20:50:31 +00:00
rootCmd.RegisterFlagCompletionFunc(util.FlagRemote, util.ValidRemotesFunc)
2024-12-30 20:50:31 +00:00
// Subcommands
rootCmd.AddCommand(alias.AliasCmd)
rootCmd.AddCommand(cache.CacheCmd)
rootCmd.AddCommand(conf.ConfigCmd)
rootCmd.AddCommand(project.ProjectCmd)
2025-01-01 01:51:04 +00:00
// Version
2025-01-01 01:58:24 +00:00
rootCmd.Version = getVersion()
2023-12-04 21:53:01 +00:00
}
// initConfig reads in config file and ENV variables if set.
2024-12-30 19:54:32 +00:00
func getInitConfigFunc(utils *util.Utils) func() {
return func() {
cfgFile := viper.GetString(util.FlagConfig)
if cfgFile != "" {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
// Find home directory.
home, err := os.UserHomeDir()
cobra.CheckErr(err)
// Search config in home directory with name ".git-project-manager" (without extension).
configPath := filepath.Join(home, ".config")
viper.AddConfigPath(configPath)
viper.SetConfigType("yaml")
viper.SetConfigName(util.GetConfigName(configPath))
}
viper.AutomaticEnv()
viper.ReadInConfig()
// Configure pretty logger
plog := pterm.DefaultLogger.
WithLevel(getPtermLogLevel(viper.GetString(util.FlagLogLevel))).
WithWriter(os.Stderr)
if plog.Level == pterm.LogLevelDebug {
pterm.EnableDebugMessages()
}
utils.SetLogger(plog)
// Load into struct to not be so darn pythonic, retrieving
// settings by untyped string "name"
conf := new(config.Config)
if err := viper.Unmarshal(&conf); err != nil {
plog.Error("Failed loading config", plog.Args("err", err))
}
2024-12-30 20:50:31 +00:00
utils.SetConfig(conf)
2024-12-30 19:54:32 +00:00
if len(os.Args) > 0 && configExemptCommands.Match([]byte(os.Args[1])) {
plog.Debug("Permitting missing config for config sub-command")
return
} else if conf.ProjectPath == "" {
plog.Fatal("Minimal configuration missing, must have projectPath", plog.Args(
"do",
"Try running `git-project-manager config default > "+util.DefConfigPath,
))
}
checkConfigPerms(viper.ConfigFileUsed()) // Abort on world-readable config
utils.Logger().Debug("Configuration loaded", plog.Args("conf", conf))
2023-12-04 21:53:01 +00:00
}
2023-12-05 21:56:47 +00:00
}
2023-12-07 17:08:56 +00:00
func getPtermLogLevel(level string) pterm.LogLevel {
var pLevel pterm.LogLevel
2023-12-05 21:56:47 +00:00
switch strings.ToLower(level) {
case "error":
2023-12-07 17:08:56 +00:00
pLevel = pterm.LogLevelError
2023-12-05 21:56:47 +00:00
case "warn":
2023-12-07 17:08:56 +00:00
pLevel = pterm.LogLevelWarn
2023-12-05 21:56:47 +00:00
case "info":
2023-12-07 17:08:56 +00:00
pLevel = pterm.LogLevelInfo
2023-12-05 21:56:47 +00:00
case "debug":
2023-12-07 17:08:56 +00:00
pLevel = pterm.LogLevelDebug
2023-12-05 21:56:47 +00:00
default:
2023-12-07 17:08:56 +00:00
pLevel = pterm.LogLevelInfo
2023-12-05 21:56:47 +00:00
}
2023-12-07 17:08:56 +00:00
return pLevel
2023-12-05 21:56:47 +00:00
}
// Don't allow world-readable configuration
func checkConfigPerms(file string) {
stat, err := os.Stat(file)
if err != nil {
2024-12-30 19:54:32 +00:00
utils.Logger().Error("Failure reading configuration", utils.Logger().Args("err", err))
2023-12-05 21:56:47 +00:00
return
}
2024-10-01 18:29:14 +00:00
if stat.Mode().Perm()&0o004 == 0o004 {
2024-12-30 19:54:32 +00:00
utils.Logger().Error("Configuration is world-readable. Recomment 0400.",
utils.Logger().Args("mode", stat.Mode().String()))
2023-12-05 21:56:47 +00:00
os.Exit(1)
2023-12-04 21:53:01 +00:00
}
}
2025-01-01 01:58:24 +00:00
func getVersion() string {
if info, ok := debug.ReadBuildInfo(); ok && info.Main.Version != "(devel)" {
return info.Main.Version
}
return Version
}