mirror of
https://github.com/chenasraf/wand.git
synced 2026-05-17 17:38:02 +00:00
163 lines
3.5 KiB
Go
163 lines
3.5 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
|
|
"strings"
|
|
|
|
"github.com/samber/lo"
|
|
"github.com/spf13/viper"
|
|
"go.yaml.in/yaml/v3"
|
|
)
|
|
|
|
type Flag struct {
|
|
Alias string `yaml:"alias"`
|
|
Description string `yaml:"description"`
|
|
Default interface{} `yaml:"default"`
|
|
Type string `yaml:"type"`
|
|
}
|
|
|
|
type Command struct {
|
|
Description string `yaml:"description"`
|
|
Cmd string `yaml:"cmd"`
|
|
Children map[string]Command `yaml:"children"`
|
|
Flags map[string]Flag `yaml:"flags"`
|
|
Env map[string]string `yaml:"env"`
|
|
WorkingDir string `yaml:"working_dir"`
|
|
Aliases []string `yaml:"aliases"`
|
|
Confirm interface{} `yaml:"confirm"`
|
|
ConfirmDefault string `yaml:"confirm_default"`
|
|
}
|
|
|
|
// GetConfirmMessage returns the confirm prompt message and whether confirmation is enabled.
|
|
func (c Command) GetConfirmMessage() (string, bool) {
|
|
switch v := c.Confirm.(type) {
|
|
case bool:
|
|
if v {
|
|
return "Are you sure?", true
|
|
}
|
|
return "", false
|
|
case string:
|
|
return v, true
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
// GetConfirmDefault returns true if the default answer is yes.
|
|
func (c Command) GetConfirmDefault() bool {
|
|
return strings.EqualFold(c.ConfirmDefault, "yes") || strings.EqualFold(c.ConfirmDefault, "y")
|
|
}
|
|
|
|
type Config struct {
|
|
Shell interface{} `yaml:"shell"`
|
|
Env map[string]string `yaml:"env"`
|
|
}
|
|
|
|
func (c *Config) GetShell() string {
|
|
switch v := c.Shell.(type) {
|
|
case string:
|
|
return v
|
|
case map[string]interface{}:
|
|
key := runtimeOS()
|
|
if shell, ok := v[key]; ok {
|
|
if s, ok := shell.(string); ok {
|
|
return s
|
|
}
|
|
}
|
|
}
|
|
|
|
// fall back to $SHELL or sh
|
|
return lo.CoalesceOrEmpty(os.Getenv("SHELL"), "sh")
|
|
}
|
|
|
|
func runtimeOS() string {
|
|
switch runtime.GOOS {
|
|
case "darwin":
|
|
return "macos"
|
|
default:
|
|
return runtime.GOOS
|
|
}
|
|
}
|
|
|
|
type rawConfig struct {
|
|
DotConfig *Config `yaml:".config"`
|
|
Commands map[string]Command `yaml:",inline"`
|
|
}
|
|
|
|
// expandPath expands a leading ~ to the user's home directory.
|
|
func expandPath(path string) string {
|
|
if path == "~" || strings.HasPrefix(path, "~/") {
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return path
|
|
}
|
|
return filepath.Join(home, path[1:])
|
|
}
|
|
return path
|
|
}
|
|
|
|
func loadConfig(explicitPath string) (*Config, map[string]Command, error) {
|
|
var configPath string
|
|
var err error
|
|
if explicitPath != "" {
|
|
configPath = expandPath(explicitPath)
|
|
} else {
|
|
configPath, err = findConfigFile()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
|
|
data, err := os.ReadFile(configPath)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to read config: %w", err)
|
|
}
|
|
|
|
var raw rawConfig
|
|
if err := yaml.Unmarshal(data, &raw); err != nil {
|
|
return nil, nil, fmt.Errorf("failed to parse config: %w", err)
|
|
}
|
|
|
|
cfg := lo.FromPtrOr(raw.DotConfig, Config{})
|
|
commands := lo.OmitByKeys(raw.Commands, []string{".config"})
|
|
|
|
return &cfg, commands, nil
|
|
}
|
|
|
|
func findConfigFile() (string, error) {
|
|
viper.SetConfigName("wand")
|
|
viper.SetConfigType("yaml")
|
|
|
|
// ./wand.yml
|
|
viper.AddConfigPath(".")
|
|
|
|
// search up from cwd
|
|
dir, err := os.Getwd()
|
|
if err == nil {
|
|
for {
|
|
parent := filepath.Dir(dir)
|
|
if parent == dir {
|
|
break
|
|
}
|
|
dir = parent
|
|
viper.AddConfigPath(dir)
|
|
}
|
|
}
|
|
|
|
// ~/.wand.yml and ~/.config/wand.yml
|
|
home, err := os.UserHomeDir()
|
|
if err == nil {
|
|
viper.AddConfigPath(home)
|
|
viper.AddConfigPath(filepath.Join(home, ".config"))
|
|
}
|
|
|
|
if err := viper.ReadInConfig(); err != nil {
|
|
return "", fmt.Errorf("config file not found: %w", err)
|
|
}
|
|
|
|
return viper.ConfigFileUsed(), nil
|
|
}
|