2024-10-05 20:35:30 +03:00
|
|
|
package cmd
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
|
|
|
|
"github.com/charmbracelet/log"
|
2024-11-09 18:52:33 +03:00
|
|
|
"github.com/numtide/treefmt/v2/build"
|
|
|
|
"github.com/numtide/treefmt/v2/cmd/format"
|
|
|
|
_init "github.com/numtide/treefmt/v2/cmd/init"
|
|
|
|
"github.com/numtide/treefmt/v2/config"
|
|
|
|
"github.com/numtide/treefmt/v2/stats"
|
2024-10-05 20:35:30 +03:00
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/spf13/viper"
|
|
|
|
)
|
|
|
|
|
2024-10-09 17:34:21 +03:00
|
|
|
func NewRoot() (*cobra.Command, *stats.Stats) {
|
2024-10-05 20:35:30 +03:00
|
|
|
var (
|
|
|
|
treefmtInit bool
|
|
|
|
configFile string
|
|
|
|
)
|
|
|
|
|
|
|
|
// create a viper instance for reading in config
|
|
|
|
v, err := config.NewViper()
|
|
|
|
if err != nil {
|
|
|
|
cobra.CheckErr(fmt.Errorf("failed to create viper instance: %w", err))
|
|
|
|
}
|
|
|
|
|
2024-10-09 17:34:21 +03:00
|
|
|
// create a new stats instance
|
|
|
|
statz := stats.New()
|
|
|
|
|
2024-10-05 20:35:30 +03:00
|
|
|
// create out root command
|
|
|
|
cmd := &cobra.Command{
|
2024-10-15 12:16:48 +03:00
|
|
|
Use: fmt.Sprintf("%s <paths...>", build.Name),
|
2024-10-05 20:35:30 +03:00
|
|
|
Short: "One CLI to format your repo",
|
|
|
|
Version: build.Version,
|
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
2024-10-09 17:34:21 +03:00
|
|
|
return runE(v, &statz, cmd, args)
|
2024-10-05 20:35:30 +03:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// update version template
|
|
|
|
cmd.SetVersionTemplate("treefmt {{.Version}}")
|
|
|
|
|
|
|
|
fs := cmd.Flags()
|
|
|
|
|
|
|
|
// add our config flags to the command's flag set
|
|
|
|
config.SetFlags(fs)
|
|
|
|
|
|
|
|
// xor tree-root and tree-root-file flags
|
|
|
|
cmd.MarkFlagsMutuallyExclusive("tree-root", "tree-root-file")
|
|
|
|
|
|
|
|
cmd.HelpTemplate()
|
|
|
|
|
|
|
|
// add a couple of special flags which don't have a corresponding entry in treefmt.toml
|
2024-10-15 12:16:48 +03:00
|
|
|
fs.StringVar(
|
|
|
|
&configFile, "config-file", "",
|
|
|
|
"Load the config file from the given path (defaults to searching upwards for treefmt.toml or "+
|
|
|
|
".treefmt.toml).",
|
|
|
|
)
|
|
|
|
fs.BoolVarP(
|
|
|
|
&treefmtInit, "init", "i", false,
|
|
|
|
"Create a treefmt.toml file in the current directory.",
|
|
|
|
)
|
2024-10-05 20:35:30 +03:00
|
|
|
|
|
|
|
// bind our command's flags to viper
|
|
|
|
if err := v.BindPFlags(fs); err != nil {
|
|
|
|
cobra.CheckErr(fmt.Errorf("failed to bind global config to viper: %w", err))
|
|
|
|
}
|
|
|
|
|
|
|
|
// bind prj_root to the tree-root flag, allowing viper to handle environment override for us
|
|
|
|
// conforms with https://github.com/numtide/prj-spec/blob/main/PRJ_SPEC.md
|
|
|
|
cobra.CheckErr(v.BindPFlag("prj_root", fs.Lookup("tree-root")))
|
|
|
|
|
2024-10-09 17:34:21 +03:00
|
|
|
return cmd, &statz
|
2024-10-05 20:35:30 +03:00
|
|
|
}
|
|
|
|
|
2024-10-09 17:34:21 +03:00
|
|
|
func runE(v *viper.Viper, statz *stats.Stats, cmd *cobra.Command, args []string) error {
|
2024-10-05 20:35:30 +03:00
|
|
|
flags := cmd.Flags()
|
|
|
|
|
|
|
|
// change working directory if required
|
|
|
|
workingDir, err := filepath.Abs(v.GetString("working-dir"))
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to get absolute path for working directory: %w", err)
|
|
|
|
} else if err = os.Chdir(workingDir); err != nil {
|
|
|
|
return fmt.Errorf("failed to change working directory: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if we are running the init command
|
|
|
|
if init, err := flags.GetBool("init"); err != nil {
|
|
|
|
return fmt.Errorf("failed to read init flag: %w", err)
|
|
|
|
} else if init {
|
|
|
|
return _init.Run()
|
|
|
|
}
|
|
|
|
|
|
|
|
// otherwise attempt to load the config file
|
|
|
|
|
|
|
|
// use the path specified by the flag
|
|
|
|
configFile, err := flags.GetString("config-file")
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to read config-file flag: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// fallback to env
|
|
|
|
if configFile == "" {
|
|
|
|
configFile = os.Getenv("TREEFMT_CONFIG")
|
|
|
|
}
|
|
|
|
|
2024-11-06 15:46:42 +03:00
|
|
|
filenames := []string{"treefmt.toml", ".treefmt.toml"}
|
|
|
|
|
|
|
|
// look in PRJ_ROOT if set
|
|
|
|
if prjRoot := os.Getenv("PRJ_ROOT"); configFile == "" && prjRoot != "" {
|
|
|
|
configFile, _ = config.Find(prjRoot, filenames...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// search up from the working directory
|
2024-10-05 20:35:30 +03:00
|
|
|
if configFile == "" {
|
2024-11-06 15:46:42 +03:00
|
|
|
configFile, _, err = config.FindUp(workingDir, filenames...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// error out if we couldn't find the config file
|
|
|
|
if err != nil {
|
|
|
|
cmd.SilenceUsage = true
|
|
|
|
|
|
|
|
return fmt.Errorf("failed to find treefmt config file: %w", err)
|
2024-10-05 20:35:30 +03:00
|
|
|
}
|
|
|
|
|
2024-11-06 15:46:42 +03:00
|
|
|
log.Infof("using config file: %s", configFile)
|
|
|
|
|
2024-10-05 20:35:30 +03:00
|
|
|
// read in the config
|
|
|
|
v.SetConfigFile(configFile)
|
2024-10-15 12:16:48 +03:00
|
|
|
|
2024-10-05 20:35:30 +03:00
|
|
|
if err := v.ReadInConfig(); err != nil {
|
|
|
|
cobra.CheckErr(fmt.Errorf("failed to read config file '%s': %w", configFile, err))
|
|
|
|
}
|
|
|
|
|
|
|
|
// configure logging
|
|
|
|
log.SetOutput(os.Stderr)
|
|
|
|
log.SetReportTimestamp(false)
|
|
|
|
|
|
|
|
switch v.GetInt("verbose") {
|
|
|
|
case 0:
|
|
|
|
log.SetLevel(log.WarnLevel)
|
|
|
|
case 1:
|
|
|
|
log.SetLevel(log.InfoLevel)
|
|
|
|
default:
|
|
|
|
log.SetLevel(log.DebugLevel)
|
|
|
|
}
|
|
|
|
|
|
|
|
// format
|
2024-10-09 17:34:21 +03:00
|
|
|
return format.Run(v, statz, cmd, args)
|
2024-10-05 20:35:30 +03:00
|
|
|
}
|