// Wand provides utilities for constructing command line applications from functions, with automatic parameter // binding from command line arguments, environment variables and configuration files. package wand import ( "fmt" "os" "path" ) type Config struct { file func(Cmd) *file merge []Config fromOption bool optional bool test string } type Cmd struct { name string impl any group bool subcommands []Cmd isDefault bool minPositional int maxPositional int shortForms []string isHelp bool version string } // name needs to be valid symbol. The application name should also be a valid symbol, // though not mandatory. If it is not, the environment variables may not work properly. func Command(name string, impl any, subcmds ...Cmd) Cmd { return Cmd{name: name, impl: impl, subcommands: subcmds} } func Group(name string, subcmds ...Cmd) Cmd { return Cmd{name: name, group: true, subcommands: subcmds} } func Default(cmd Cmd) Cmd { cmd.isDefault = true return cmd } func Args(cmd Cmd, min, max int) Cmd { cmd.minPositional = min cmd.maxPositional = max return cmd } func ShortForm(cmd Cmd, f ...string) Cmd { if len(f)%2 != 0 { f = append(f, "") } cmd.shortForms = append(cmd.shortForms, f...) for i := range cmd.subcommands { cmd.subcommands[i] = ShortForm( cmd.subcommands[i], f..., ) } return cmd } func Version(cmd Cmd, version string) Cmd { cmd.subcommands = append( cmd.subcommands, Cmd{name: "version", version: version}, ) return cmd } func MergeConfig(conf ...Config) Config { return Config{ merge: conf, } } func OptionalConfig(conf Config) Config { conf.optional = true for i := range conf.merge { conf.merge[i] = OptionalConfig(conf.merge[i]) } return conf } func Etc() Config { return OptionalConfig(Config{ file: func(cmd Cmd) *file { return fileReader(path.Join("/etc", cmd.name, "config")) }, }) } func UserConfig() Config { return OptionalConfig( MergeConfig( Config{ file: func(cmd Cmd) *file { return fileReader(path.Join(os.Getenv("HOME"), fmt.Sprintf(".%s", cmd.name), "config")) }, }, Config{ file: func(cmd Cmd) *file { return fileReader(path.Join(os.Getenv("HOME"), ".config", cmd.name, "config")) }, }, ), ) } func ConfigFromOption() Config { return Config{fromOption: true} } func SystemConfig() Config { return MergeConfig(Etc(), UserConfig(), ConfigFromOption()) } func Exec(impl any, conf ...Config) { exec(os.Stdin, os.Stdout, os.Stderr, os.Exit, wrap(impl), MergeConfig(conf...), os.Environ(), os.Args) }