171 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			171 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package cmd
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"os"
 | |
| 	"os/signal"
 | |
| 	"path/filepath"
 | |
| 	"regexp"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/pterm/pterm"
 | |
| 	"github.com/spf13/cobra"
 | |
| 	"github.com/spf13/viper"
 | |
| 
 | |
| 	"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/alias"
 | |
| 	"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/cache"
 | |
| 	conf "gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/config"
 | |
| 	"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/project"
 | |
| 	"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
 | |
| 	"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/config"
 | |
| )
 | |
| 
 | |
| var rootCmd = &cobra.Command{
 | |
| 	Use:              "git-project-manager",
 | |
| 	Aliases:          []string{"gpm"},
 | |
| 	Short:            "Find and use Git projects locally",
 | |
| 	Long:             util.RootCmdLong,
 | |
| 	PersistentPreRun: initRootCmd,
 | |
| }
 | |
| 
 | |
| var (
 | |
| 	utils                *util.Utils
 | |
| 	configExemptCommands = regexp.MustCompile(`^(doc|conf)`)
 | |
| )
 | |
| 
 | |
| // Hook traversal is enabled, so this will be run for all
 | |
| // sub-commands regardless of their registered pre-hooks
 | |
| func initRootCmd(cmd *cobra.Command, args []string) {
 | |
| 	cmd.SetContext(util.AddToCtx(cmd.Context(), utils))
 | |
| 	utils.InitProjectPath(cmd, args)
 | |
| }
 | |
| 
 | |
| // 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() {
 | |
| 	ctx, cncl := signal.NotifyContext(context.Background(), os.Kill, os.Interrupt)
 | |
| 	defer cncl()
 | |
| 
 | |
| 	err := rootCmd.ExecuteContext(ctx)
 | |
| 	if err != nil {
 | |
| 		pterm.Error.Printfln("%s", pterm.LightYellow("Command failed, "+err.Error()))
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func init() {
 | |
| 	cobra.EnableTraverseRunHooks = true
 | |
| 
 | |
| 	utils = &util.Utils{}
 | |
| 	cobra.OnInitialize(getInitConfigFunc(utils))
 | |
| 
 | |
| 	// Global flags
 | |
| 	rootCmd.PersistentFlags().String(util.FlagConfig, "",
 | |
| 		"config file (default is "+util.DefConfigPath+")")
 | |
| 	rootCmd.PersistentFlags().String(util.FlagPath, "",
 | |
| 		"Sets a path for local clones of projects")
 | |
| 	rootCmd.PersistentFlags().String(util.FlagLogLevel, util.DefLogLevel,
 | |
| 		"Default log level -- info, warn, error, debug")
 | |
| 	rootCmd.PersistentFlags().StringSlice(util.FlagRemote, []string{},
 | |
| 		"Specify remotes by host for any sub-command. Provide multiple times or comma delimited.")
 | |
| 
 | |
| 	viper.BindPFlags(rootCmd.PersistentFlags())
 | |
| 
 | |
| 	// Flag autocompletion
 | |
| 	rootCmd.RegisterFlagCompletionFunc(util.FlagLogLevel, util.ValidLogLevelsFunc)
 | |
| 	rootCmd.RegisterFlagCompletionFunc(util.FlagRemote, util.ValidRemotesFunc)
 | |
| 
 | |
| 	// Subcommands
 | |
| 	rootCmd.AddCommand(alias.AliasCmd)
 | |
| 	rootCmd.AddCommand(cache.CacheCmd)
 | |
| 	rootCmd.AddCommand(conf.ConfigCmd)
 | |
| 	rootCmd.AddCommand(project.ProjectCmd)
 | |
| }
 | |
| 
 | |
| // initConfig reads in config file and ENV variables if set.
 | |
| func 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))
 | |
| 		}
 | |
| 
 | |
| 		utils.SetConfig(conf)
 | |
| 
 | |
| 		if len(os.Args) > 0 && configExemptCommands.Match([]byte(os.Args[1])) {
 | |
| 			plog.Debug("Permitting missing config for config sub-command")
 | |
| 			return
 | |
| 		} else if conf.ProjectPath == "" {
 | |
| 			plog.Fatal("Minimal configuration missing, must have projectPath", plog.Args(
 | |
| 				"do",
 | |
| 				"Try running `git-project-manager config default > "+util.DefConfigPath,
 | |
| 			))
 | |
| 		}
 | |
| 
 | |
| 		checkConfigPerms(viper.ConfigFileUsed()) // Abort on world-readable config
 | |
| 
 | |
| 		utils.Logger().Debug("Configuration loaded", plog.Args("conf", conf))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func getPtermLogLevel(level string) pterm.LogLevel {
 | |
| 	var pLevel pterm.LogLevel
 | |
| 	switch strings.ToLower(level) {
 | |
| 	case "error":
 | |
| 		pLevel = pterm.LogLevelError
 | |
| 	case "warn":
 | |
| 		pLevel = pterm.LogLevelWarn
 | |
| 	case "info":
 | |
| 		pLevel = pterm.LogLevelInfo
 | |
| 	case "debug":
 | |
| 		pLevel = pterm.LogLevelDebug
 | |
| 	default:
 | |
| 		pLevel = pterm.LogLevelInfo
 | |
| 	}
 | |
| 	return pLevel
 | |
| }
 | |
| 
 | |
| // Don't allow world-readable configuration
 | |
| func checkConfigPerms(file string) {
 | |
| 	stat, err := os.Stat(file)
 | |
| 	if err != nil {
 | |
| 		utils.Logger().Error("Failure reading configuration", utils.Logger().Args("err", err))
 | |
| 		return
 | |
| 	}
 | |
| 	if stat.Mode().Perm()&0o004 == 0o004 {
 | |
| 		utils.Logger().Error("Configuration is world-readable. Recomment 0400.",
 | |
| 			utils.Logger().Args("mode", stat.Mode().String()))
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| }
 |