diff --git a/cmd/bindown/completion.go b/cmd/bindown/completion.go new file mode 100644 index 0000000..966691c --- /dev/null +++ b/cmd/bindown/completion.go @@ -0,0 +1,110 @@ +package main + +import ( + "os" + "path/filepath" + "strings" + + "github.com/killa-beez/gopkgs/sets/builtins" + "github.com/posener/complete" + "github.com/willabides/bindown/v2" +) + +func findConfigFileForPredictor(args []string) string { + for i, arg := range args { + if len(args) == i+1 { + continue + } + if arg != "--configfile" { + continue + } + return multifileFindExisting(args[i+1]) + } + cf, ok := os.LookupEnv("BINDOWN_CONFIG_FILE") + if ok { + return multifileFindExisting(cf) + } + return multifileFindExisting(kongVars["configfile_default"]) +} + +func predictorConfig(args []string) *bindown.ConfigFile { + path := findConfigFileForPredictor(args) + if path == "" { + return nil + } + configFile, err := bindown.LoadConfigFile(path) + if err != nil { + return nil + } + return configFile +} + +func allBins(cfg *bindown.ConfigFile) []string { + if cfg == nil { + return []string{} + } + bins := builtins.NewStringSet(len(cfg.Downloaders) * 10) + for dlName, downloaders := range cfg.Downloaders { + for _, dl := range downloaders { + if dl.BinName == "" { + bins.Add(dlName) + continue + } + bins.Add(dl.BinName) + } + } + return bins.Values() +} + +var binPredictor = complete.PredictFunc(func(a complete.Args) []string { + cfg := predictorConfig(a.Completed) + return complete.PredictSet(allBins(cfg)...).Predict(a) +}) + +var binPathPredictor = complete.PredictFunc(func(a complete.Args) []string { + cfg := predictorConfig(a.Completed) + bins := allBins(cfg) + dir, _ := filepath.Split(a.Last) + for i, bin := range bins { + bins[i] = filepath.Join(dir, bin) + } + return complete.PredictOr( + complete.PredictDirs("*"), + complete.PredictSet(bins...), + ).Predict(a) +}) + +var osPredictor = complete.PredictSet(strings.Split(goosVals, "\n")...) + +//from `go tool dist list | cut -f 1 -d '/' | sort | uniq` +const goosVals = `aix +android +darwin +dragonfly +freebsd +illumos +js +linux +nacl +netbsd +openbsd +plan9 +solaris +windows` + +var archPredictor = complete.PredictSet(strings.Split(goarchVals, "\n")...) + +//from `go tool dist list | cut -f 2 -d '/' | sort | uniq` +const goarchVals = `386 +amd64 +amd64p32 +arm +arm64 +mips +mips64 +mips64le +mipsle +ppc64 +ppc64le +s390x +wasm` diff --git a/cmd/bindown/config.go b/cmd/bindown/config.go index 8a4ee20..2326676 100644 --- a/cmd/bindown/config.go +++ b/cmd/bindown/config.go @@ -40,7 +40,7 @@ func (c configFmtCmd) Run() error { } type configUpdateChecksumsCmd struct { - TargetFile string `kong:"required=true,arg,help=${config_checksums_bin_help}"` + TargetFile string `kong:"required=true,arg,help=${config_checksums_bin_help},predictor=bin"` } func (d *configUpdateChecksumsCmd) Run(kctx *kong.Context) error { @@ -87,7 +87,7 @@ func (d *configUpdateChecksumsCmd) Run(kctx *kong.Context) error { } type configValidateCmd struct { - Bin string `kong:"required=true,arg,help=${config_validate_bin_help}"` + Bin string `kong:"required=true,arg,help=${config_validate_bin_help},predictor=bin"` } func (d configValidateCmd) Run(kctx *kong.Context) error { diff --git a/cmd/bindown/download.go b/cmd/bindown/download.go index 2b3a39d..135df2b 100644 --- a/cmd/bindown/download.go +++ b/cmd/bindown/download.go @@ -19,10 +19,10 @@ var downloadKongVars = kong.Vars{ } type downloadCmd struct { - Arch string `kong:"help=${download_arch_help},default=${download_arch_default}"` - OS string `kong:"help=${download_os_help},default=${download_os_default}"` + Arch string `kong:"help=${download_arch_help},default=${download_arch_default},predictor=arch"` + OS string `kong:"help=${download_os_help},default=${download_os_default},predictor=os"` Force bool `kong:"help=${download_force_help}"` - TargetFile string `kong:"required=true,arg,help=${download_target_file_help}"` + TargetFile string `kong:"required=true,arg,help=${download_target_file_help},predictor=binpath"` } func (d *downloadCmd) Run(*kong.Context) error { diff --git a/cmd/bindown/main.go b/cmd/bindown/main.go index 7bc5d92..22234da 100644 --- a/cmd/bindown/main.go +++ b/cmd/bindown/main.go @@ -1,12 +1,10 @@ package main import ( - "fmt" "os" - "reflect" - "strings" "github.com/alecthomas/kong" + "github.com/posener/complete" "github.com/willabides/kongplete" ) @@ -21,33 +19,8 @@ var cli struct { Version versionCmd `kong:"cmd"` Download downloadCmd `kong:"cmd,help=${download_help}"` Config configCmd `kong:"cmd"` - Configfile string `kong:"type=multipath,help=${configfile_help},default=${configfile_default},env='BINDOWN_CONFIG_FILE'"` - CellarDir string `kong:"type=path,help=${cellar_dir_help},env='BINDOWN_CELLAR'"` -} - -func multipathMapper(ctx *kong.DecodeContext, target reflect.Value) error { - if target.Kind() != reflect.String { - return fmt.Errorf("\"multipath\" type must be applied to a string not %s", target.Type()) - } - var path string - err := ctx.Scan.PopValueInto("file", &path) - if err != nil { - return err - } - - for _, configFile := range strings.Split(path, "|") { - configFile = kong.ExpandPath(configFile) - stat, err := os.Stat(configFile) - if err != nil { - continue - } - if stat.IsDir() { - continue - } - target.SetString(configFile) - return nil - } - return fmt.Errorf("not found") + Configfile string `kong:"type=multipath,help=${configfile_help},default=${configfile_default},env='BINDOWN_CONFIG_FILE',predictor=file"` + CellarDir string `kong:"type=path,help=${cellar_dir_help},env='BINDOWN_CELLAR',predictor=dir"` } func main() { @@ -60,7 +33,16 @@ func main() { kong.NamedMapper("multipath", kong.MapperFunc(multipathMapper)), ) - kongplete.Complete(parser) + kongplete.Complete(parser, + kongplete.WithPredictors(map[string]complete.Predictor{ + "file": complete.PredictFiles("*"), + "dir": complete.PredictDirs("*"), + "binpath": binPathPredictor, + "arch": archPredictor, + "os": osPredictor, + "bin": binPredictor, + }), + ) kongCtx, err := parser.Parse(os.Args[1:]) parser.FatalIfErrorf(err) diff --git a/cmd/bindown/multipath.go b/cmd/bindown/multipath.go new file mode 100644 index 0000000..1ddfa06 --- /dev/null +++ b/cmd/bindown/multipath.go @@ -0,0 +1,43 @@ +package main + +import ( + "fmt" + "os" + "reflect" + "strings" + + "github.com/alecthomas/kong" +) + +func multipathMapper(ctx *kong.DecodeContext, target reflect.Value) error { + if target.Kind() != reflect.String { + return fmt.Errorf("\"multipath\" type must be applied to a string not %s", target.Type()) + } + var path string + err := ctx.Scan.PopValueInto("file", &path) + if err != nil { + return err + } + + existing := multifileFindExisting(path) + if existing == "" { + return fmt.Errorf("not found") + } + target.SetString(existing) + return nil +} + +func multifileFindExisting(multiFile string) string { + for _, configFile := range strings.Split(multiFile, "|") { + configFile = kong.ExpandPath(configFile) + stat, err := os.Stat(configFile) + if err != nil { + continue + } + if stat.IsDir() { + continue + } + return configFile + } + return "" +} diff --git a/go.mod b/go.mod index a8e4c1f..2648503 100644 --- a/go.mod +++ b/go.mod @@ -6,9 +6,10 @@ require ( github.com/alecthomas/kong v0.2.2 github.com/andybalholm/brotli v1.0.0 // indirect github.com/frankban/quicktest v1.4.2 // indirect + github.com/killa-beez/gopkgs/sets/builtins v0.0.0-20191206232703-3018f97f77a9 github.com/mholt/archiver/v3 v3.3.0 - github.com/mitchellh/mapstructure v1.1.2 // indirect github.com/pierrec/lz4 v2.3.0+incompatible // indirect + github.com/posener/complete v1.2.3 github.com/stretchr/testify v1.4.0 github.com/udhos/equalfile v0.3.0 github.com/willabides/kongplete v0.0.0-20200218163413-c50671632cd8 diff --git a/go.sum b/go.sum index bbf19ef..cdbedb4 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/alecthomas/kong v0.2.1 h1:V1tLBhyQBC4rsbXbcOvm3GBaytJSwRNX69fp1WJxbqQ= -github.com/alecthomas/kong v0.2.1/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI= github.com/alecthomas/kong v0.2.2 h1:sk9ucwuUP/T4+byYEdNU13ZNYzoQRML4IsrMbbUUKLk= github.com/alecthomas/kong v0.2.2/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE= github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6/go.mod h1:+lx6/Aqd1kLJ1GQfkvOnaZ1WGmLpMpbprPuIOOZX30U= @@ -22,6 +20,8 @@ github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/U github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/killa-beez/gopkgs/sets/builtins v0.0.0-20191206232703-3018f97f77a9 h1:BStNA6fVmKPuDXvzJnZQQq++4FSoUYAxlJPH2J5UjNE= +github.com/killa-beez/gopkgs/sets/builtins v0.0.0-20191206232703-3018f97f77a9/go.mod h1:ZP4kZV3WGKCESlO7hZkyyKM1Zprcf2FQ5rDq44f799M= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.2 h1:LfVyl+ZlLlLDeQ/d2AqfGIIH4qEDu0Ed2S5GyhCWIWY= github.com/klauspost/compress v1.9.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= @@ -35,7 +35,6 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mholt/archiver/v3 v3.3.0 h1:vWjhY8SQp5yzM9P6OJ/eZEkmi3UAbRrxCq48MxjAzig= github.com/mholt/archiver/v3 v3.3.0/go.mod h1:YnQtqsp+94Rwd0D/rk5cnLrxusUBUXg+08Ebtr1Mqao= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs= github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=