Files
gosqldump/cmd/config.go
2024-08-30 11:25:28 +12:00

285 lines
6.6 KiB
Go

package cmd
import (
"errors"
"fmt"
"os"
"path/filepath"
"slices"
"sort"
"strconv"
"unsafe"
"github.com/charmbracelet/lipgloss"
"hub.cybercinch.nz/guisea/go-template/internal/style"
"hub.cybercinch.nz/guisea/go-template/internal/app"
levenshtein "github.com/ka-weihe/fast-levenshtein"
"github.com/samber/lo"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"hub.cybercinch.nz/guisea/go-template/internal/color"
"hub.cybercinch.nz/guisea/go-template/internal/config"
"hub.cybercinch.nz/guisea/go-template/internal/filesystem"
"hub.cybercinch.nz/guisea/go-template/internal/icon"
"hub.cybercinch.nz/guisea/go-template/internal/where"
)
// errUnknownKey will generate error for key that was not found and will provide a hint
func errUnknownKey(key string) error {
closest := lo.MinBy(lo.Keys(config.Default), func(a string, b string) bool {
return levenshtein.Distance(key, a) < levenshtein.Distance(key, b)
})
msg := fmt.Sprintf(
"unknown key %s, did you mean %s?",
lipgloss.NewStyle().Foreground(color.Red).Render(key),
lipgloss.NewStyle().Foreground(color.Yellow).Render(closest),
)
return errors.New(msg)
}
func completionConfigKeys(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
return lo.Keys(config.Default), cobra.ShellCompDirectiveNoFileComp
}
func init() {
rootCmd.AddCommand(configCmd)
}
var configCmd = &cobra.Command{
Use: "config",
Short: "Various config commands",
}
func init() {
configCmd.AddCommand(configInfoCmd)
configInfoCmd.Flags().StringP("key", "k", "", "The key to get the value for")
_ = configInfoCmd.RegisterFlagCompletionFunc("key", completionConfigKeys)
}
var configInfoCmd = &cobra.Command{
Use: "info",
Short: "Show the info for each config field with description",
Run: func(cmd *cobra.Command, args []string) {
var (
key = lo.Must(cmd.Flags().GetString("key"))
fields = lo.Values(config.Default)
)
if key != "" {
if field, ok := config.Default[key]; ok {
fields = []*config.Field{field}
} else {
handleErr(errUnknownKey(key))
}
}
sort.Slice(fields, func(i, j int) bool {
return fields[i].Key < fields[j].Key
})
for i, field := range fields {
fmt.Println(field.Pretty())
if i < len(fields)-1 {
fmt.Println()
}
}
},
}
func init() {
configCmd.AddCommand(configSetCmd)
configSetCmd.Flags().StringP("key", "k", "", "The key to set the value for")
lo.Must0(configSetCmd.MarkFlagRequired("key"))
_ = configSetCmd.RegisterFlagCompletionFunc("key", completionConfigKeys)
configSetCmd.Flags().StringP("value", "v", "", "The value to set. Leave empty to use default")
}
var configSetCmd = &cobra.Command{
Use: "set",
Short: "Set a config value",
Run: func(cmd *cobra.Command, args []string) {
var (
key = lo.Must(cmd.Flags().GetString("key"))
value = lo.Must(cmd.Flags().GetString("value"))
)
if _, ok := config.Default[key]; !ok {
handleErr(errUnknownKey(key))
}
var v any
if lo.IsEmpty(value) {
v = config.Default[key].DefaultValue
goto write
}
switch config.Default[key].DefaultValue.(type) {
case string:
v = value
case int:
parsedInt, err := strconv.ParseInt(value, 10, 64)
if err != nil {
handleErr(fmt.Errorf("invalid integer value: %s", value))
}
v = int(parsedInt)
case bool:
parsedBool, err := strconv.ParseBool(value)
if err != nil {
handleErr(fmt.Errorf("invalid boolean value: %s", value))
}
v = parsedBool
}
write:
viper.Set(key, v)
switch err := viper.WriteConfig(); err.(type) {
case viper.ConfigFileNotFoundError:
handleErr(viper.SafeWriteConfig())
default:
handleErr(err)
}
fmt.Printf(
"%s set %s to %s\n",
style.Success(icon.Check),
lipgloss.NewStyle().Foreground(color.Purple).Render(key),
lipgloss.NewStyle().Foreground(color.Yellow).Render(fmt.Sprint(v)),
)
},
}
func init() {
configCmd.AddCommand(configGetCmd)
configGetCmd.Flags().StringP("key", "k", "", "The key to get the value for")
lo.Must0(configGetCmd.MarkFlagRequired("key"))
_ = configGetCmd.RegisterFlagCompletionFunc("key", completionConfigKeys)
}
var configGetCmd = &cobra.Command{
Use: "get",
Short: "Get a config value",
Run: func(cmd *cobra.Command, args []string) {
var (
key = lo.Must(cmd.Flags().GetString("key"))
)
if _, ok := config.Default[key]; !ok {
handleErr(errUnknownKey(key))
}
fmt.Println(viper.Get(key))
},
}
func init() {
configCmd.AddCommand(configEnvCmd)
}
func fastBoolConv(b bool) int {
return int(*(*byte)(unsafe.Pointer(&b)))
}
var configEnvCmd = &cobra.Command{
Use: "env",
Short: "Show the env for each config field",
Run: func(cmd *cobra.Command, args []string) {
fields := lo.Values(config.Default)
fields = append(fields, &config.Field{Key: where.EnvConfigPath})
slices.SortStableFunc(fields, func(a *config.Field, b *config.Field) int {
return fastBoolConv(a.Key < b.Key)
})
for _, field := range fields {
envValue, isSet := os.LookupEnv(field.Env())
var value string
if isSet {
value = envValue
} else {
value = lipgloss.NewStyle().Faint(true).Render("unset")
}
_, err := fmt.Fprintf(cmd.OutOrStdout(), "%s=%s\n", field.Env(), value)
handleErr(err)
}
},
}
func init() {
configCmd.AddCommand(configWriteCmd)
configWriteCmd.Flags().BoolP("force", "f", false, "Force overwrite of existing config file")
}
var configWriteCmd = &cobra.Command{
Use: "write",
Short: "Write current config to the file",
Run: func(cmd *cobra.Command, args []string) {
var (
force = lo.Must(cmd.Flags().GetBool("force"))
configFilePath = filepath.Join(
where.Config(),
fmt.Sprintf("%s.%s", app.Name, config.ConfigFormat),
)
)
if force {
exists, err := filesystem.Api().Exists(configFilePath)
handleErr(err)
if exists {
err := filesystem.Api().Remove(configFilePath)
handleErr(err)
}
}
handleErr(viper.SafeWriteConfig())
fmt.Printf(
"%s wrote config to %s\n",
style.Success(icon.Check),
configFilePath,
)
},
}
func init() {
configCmd.AddCommand(configDeleteCmd)
}
var configDeleteCmd = &cobra.Command{
Use: "delete",
Short: "Delete the config file",
Aliases: []string{"remove"},
Run: func(cmd *cobra.Command, args []string) {
configFilePath := filepath.Join(where.Config(), fmt.Sprintf("%s.%s", app.Name, config.ConfigFormat))
exists, err := filesystem.Api().Exists(configFilePath)
handleErr(err)
if !exists {
fmt.Printf(
"%s nothing to delete\n",
style.Success(icon.Check),
)
return
}
err = filesystem.Api().Remove(configFilePath)
handleErr(err)
fmt.Printf(
"%s deleted config\n",
style.Success(icon.Check),
)
},
}