Skip to content

Commit

Permalink
Merge pull request #189 from WillAbides/wrap
Browse files Browse the repository at this point in the history
split out wrap command
  • Loading branch information
WillAbides authored Nov 28, 2023
2 parents f07457e + 7337d23 commit 9ccce7d
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 197 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ Commands:
download download a dependency but don't extract or install it
extract download and extract a dependency but don't install it
install download, extract and install a dependency
wrap create a wrapper script for a dependency
format formats the config file
dependency list list configured dependencies
dependency add add a template-based dependency
Expand Down
188 changes: 89 additions & 99 deletions cmd/bindown/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"io"
"os"
"slices"
"time"

"github.com/alecthomas/kong"
Expand All @@ -16,6 +17,7 @@ var kongVars = kong.Vars{
"configfile_help": `file with bindown config. default is the first one of bindown.yml, bindown.yaml, bindown.json, .bindown.yml, .bindown.yaml or .bindown.json`,
"cache_help": `directory downloads will be cached`,
"install_help": `download, extract and install a dependency`,
"wrap_help": `create a wrapper script for a dependency`,
"system_default": string(bindown.CurrentSystem),
"system_help": `target system in the format of <os>/<architecture>`,
"systems_help": `target systems in the format of <os>/<architecture>`,
Expand All @@ -27,13 +29,11 @@ var kongVars = kong.Vars{
"config_install_completions_help": `install shell completions`,
"config_extract_path_help": `output path to directory where the downloaded archive is extracted`,
"install_force_help": `force install even if it already exists`,
"install_target_file_help": `where to write the file. when multiple dependencies are selected, this is the directory to write to.`,
"output_help": `where to write the file. when multiple dependencies are selected, this is the directory to write to.`,
"download_force_help": `force download even if the file already exists`,
"download_target_file_help": `filename and path for the downloaded file. Default downloads to cache.`,
"allow_missing_checksum": `allow missing checksums`,
"download_help": `download a dependency but don't extract or install it`,
"extract_help": `download and extract a dependency but don't install it`,
"extract_target_dir_help": `path to extract to. Default extracts to cache.`,
"checksums_dep_help": `name of the dependency to update`,
"all_deps_help": `select all dependencies`,
"dependency_help": `name of dependency`,
Expand All @@ -52,6 +52,7 @@ type rootCmd struct {
Download downloadCmd `kong:"cmd,help=${download_help}"`
Extract extractCmd `kong:"cmd,help=${extract_help}"`
Install installCmd `kong:"cmd,help=${install_help}"`
Wrap wrapCmd `kong:"cmd,help=${wrap_help}"`
Format fmtCmd `kong:"cmd,help=${config_format_help}"`
Dependency dependencyCmd `kong:"cmd,help='manage dependencies'"`
Template templateCmd `kong:"cmd,help='manage templates'"`
Expand All @@ -66,6 +67,20 @@ type rootCmd struct {
InstallCompletions kongplete.InstallCompletions `kong:"cmd,help=${config_install_completions_help}"`
}

func (r *rootCmd) BeforeApply(k *kong.Context) error {
// set dependency positional to optional for install, wrap, download and extract.
// We do this because we want to allow --all to be equivalent to specifying all
// dependencies but want the help output to indicate that a dependency is required.
if slices.Contains([]string{"install", "wrap", "download", "extract"}, k.Selected().Name) {
for _, pos := range k.Selected().Positional {
if pos.Name == "dependency" {
pos.Required = false
}
}
}
return nil
}

var defaultConfigFilenames = []string{
"bindown.yml",
"bindown.yaml",
Expand Down Expand Up @@ -120,6 +135,7 @@ type runContext struct {
parent context.Context
stdin fileReader
stdout fileWriter
stderr fileWriter
rootCmd *rootCmd
}

Expand Down Expand Up @@ -148,7 +164,7 @@ func (r *runContext) Value(key any) any {
type runOpts struct {
stdin fileReader
stdout fileWriter
stderr io.Writer
stderr fileWriter
cmdName string
exitHandler func(int)
}
Expand All @@ -169,17 +185,17 @@ func Run(ctx context.Context, args []string, opts *runOpts) {
if runCtx.stdout == nil {
runCtx.stdout = os.Stdout
}
stderr := opts.stderr
if stderr == nil {
stderr = os.Stderr
runCtx.stderr = opts.stderr
if runCtx.stderr == nil {
runCtx.stderr = os.Stderr
}

kongOptions := []kong.Option{
kong.HelpOptions{Compact: true},
kong.BindTo(runCtx, &runCtx),
kongVars,
kong.UsageOnError(),
kong.Writers(runCtx.stdout, stderr),
kong.Writers(runCtx.stdout, runCtx.stderr),
}
if opts.exitHandler != nil {
kongOptions = append(kongOptions, kong.Exit(opts.exitHandler))
Expand All @@ -206,6 +222,7 @@ func runCompletion(ctx context.Context, parser *kong.Kong) {
defer cancel()
kongplete.Complete(parser,
kongplete.WithPredictor("bin", binCompleter(ctx)),
kongplete.WithPredictor("wrap_bin", binCompleter(ctx)),
kongplete.WithPredictor("allSystems", allSystemsCompleter),
kongplete.WithPredictor("templateSource", templateSourceCompleter(ctx)),
kongplete.WithPredictor("system", systemCompleter(ctx)),
Expand Down Expand Up @@ -254,55 +271,77 @@ func (c fmtCmd) Run(ctx *runContext, cli *rootCmd) error {
}

type installCmd struct {
dependencySelector
Dependency []string `kong:"arg,name=dependency,help=${dependency_help},predictor=bin"`
All bool `kong:"help=${all_deps_help}"`
Force bool `kong:"help=${install_force_help}"`
TargetFile string `kong:"type=path,name=output,type=file,help=${install_target_file_help}"`
Output string `kong:"type=path,name=output,type=file,help=${output_help}"`
System bindown.System `kong:"name=system,default=${system_default},help=${system_help},predictor=allSystems"`
AllowMissingChecksum bool `kong:"name=allow-missing-checksum,help=${allow_missing_checksum}"`
ToCache bool `kong:"name=to-cache,help=${install_to_cache_help}"`
Wrapper bool `kong:"name=wrapper,help=${install_wrapper_help}"`
BindownExec string `kong:"name=bindown,help=${install_bindown_help}"`

// hidden options to be removed
Wrapper bool `kong:"hidden,name=wrapper"`
BindownExec string `kong:"hidden,name=bindown"`
}

func (d *installCmd) Run(ctx *runContext) error {
if d.Wrapper {
fmt.Fprintln(ctx.stderr, `--wrapper is deprecated and will be removed in a future version. Use "bindown wrap" instead.`)
if d.ToCache {
return fmt.Errorf("cannot use --to-cache and --wrapper together")
}
if d.Force {
return fmt.Errorf("cannot use --force and --wrapper together")
}
cmd := &wrapCmd{
Dependency: d.Dependency,
All: d.All,
Output: d.Output,
AllowMissingChecksum: d.AllowMissingChecksum,
BindownExec: d.BindownExec,
}
return cmd.Run(ctx)
}
config, err := loadConfigFile(ctx, false)
if err != nil {
return err
}
err = d.setDependencies(config)

return config.InstallDependencies(d.Dependency, d.System, &bindown.ConfigInstallDependenciesOpts{
Output: d.Output,
Force: d.Force,
AllowMissingChecksum: d.AllowMissingChecksum,
ToCache: d.ToCache,
Stdout: ctx.stdout,
AllDeps: d.All,
})
}

type wrapCmd struct {
Dependency []string `kong:"arg,name=dependency,help=${dependency_help},predictor=bin"`
All bool `kong:"help=${all_deps_help}"`
Output string `kong:"type=path,name=output,type=file,help=${output_help}"`
AllowMissingChecksum bool `kong:"name=allow-missing-checksum,help=${allow_missing_checksum}"`
BindownExec string `kong:"name=bindown,help=${install_bindown_help}"`
}

func (d *wrapCmd) Run(ctx *runContext) error {
config, err := loadConfigFile(ctx, false)
if err != nil {
return err
}
if d.ToCache && d.Wrapper {
return fmt.Errorf("cannot use --to-cache and --wrapper together")
}
if d.BindownExec != "" && !d.Wrapper {
return fmt.Errorf("--bindown can only be used with --wrapper")
}
if d.Force && d.Wrapper {
return fmt.Errorf("cannot use --force and --wrapper together")
}
opts := bindown.ConfigInstallDependenciesOpts{
TargetFile: d.TargetFile,
Force: d.Force,
return config.WrapDependencies(d.Dependency, &bindown.ConfigWrapDependenciesOpts{
Output: d.Output,
AllowMissingChecksum: d.AllowMissingChecksum,
ToCache: d.ToCache,
Wrapper: d.Wrapper,
BindownPath: d.BindownExec,
Stdout: ctx.stdout,
}
if d.All || len(d.Dependency) > 1 {
opts.TargetFile = ""
opts.TargetDir = d.TargetFile
if opts.TargetDir == "" {
opts.TargetDir = config.InstallDir
}
}
return config.InstallDependencies(d.Dependency, d.System, &opts)
AllDeps: d.All,
})
}

type downloadCmd struct {
dependencySelector
Dependency []string `kong:"arg,name=dependency,help=${dependency_help},predictor=bin"`
All bool `kong:"help=${all_deps_help}"`
Force bool `kong:"help=${download_force_help}"`
System bindown.System `kong:"name=system,default=${system_default},help=${system_help},predictor=allSystems"`
AllowMissingChecksum bool `kong:"name=allow-missing-checksum,help=${allow_missing_checksum}"`
Expand All @@ -313,26 +352,17 @@ func (d *downloadCmd) Run(ctx *runContext) error {
if err != nil {
return err
}
err = d.setDependencies(config)
if err != nil {
return err
}
for _, dep := range d.Dependency {
var pth string
pth, err = config.DownloadDependency(dep, d.System, &bindown.ConfigDownloadDependencyOpts{
Force: d.Force,
AllowMissingChecksum: d.AllowMissingChecksum,
})
if err != nil {
return err
}
fmt.Fprintf(ctx.stdout, "downloaded %s to %s\n", dep, pth)
}
return nil
return config.DownloadDependencies(d.Dependency, d.System, &bindown.ConfigDownloadDependenciesOpts{
Force: d.Force,
AllowMissingChecksum: d.AllowMissingChecksum,
AllDeps: d.All,
Stdout: ctx.stdout,
})
}

type extractCmd struct {
dependencySelector
Dependency []string `kong:"arg,name=dependency,help=${dependency_help},predictor=bin"`
All bool `kong:"help=${all_deps_help}"`
System bindown.System `kong:"name=system,default=${system_default},help=${system_help},predictor=allSystems"`
AllowMissingChecksum bool `kong:"name=allow-missing-checksum,help=${allow_missing_checksum}"`
}
Expand All @@ -342,49 +372,9 @@ func (d *extractCmd) Run(ctx *runContext) error {
if err != nil {
return err
}
err = d.setDependencies(config)
if err != nil {
return err
}
for _, dep := range d.Dependency {
var pth string
pth, err = config.ExtractDependency(dep, d.System, &bindown.ConfigExtractDependencyOpts{
AllowMissingChecksum: d.AllowMissingChecksum,
})
if err != nil {
return err
}
fmt.Fprintf(ctx.stdout, "extracted %s to %s\n", dep, pth)
}
return nil
}

type dependencySelector struct {
Dependency []string `kong:"arg,name=dependency,help=${dependency_help},predictor=bin"`
All bool `kong:"help=${all_deps_help}"`
}

func (d *dependencySelector) BeforeApply(k *kong.Context) error {
// sets dependency positional to optional. We do this because we want to allow --all to be
// equivalent to specifying all dependencies but want the help output to indicate that a
// dependency is required.
for _, pos := range k.Selected().Positional {
if pos.Name == "dependency" {
pos.Required = false
}
}
return nil
}

func (d *dependencySelector) setDependencies(config *bindown.Config) error {
if d.All {
if len(d.Dependency) > 0 {
return fmt.Errorf("cannot specify dependencies when using --all")
}
d.Dependency = allDependencies(config)
}
if len(d.Dependency) == 0 {
return fmt.Errorf("must specify at least one dependency")
}
return nil
return config.ExtractDependencies(d.Dependency, d.System, &bindown.ConfigExtractDependenciesOpts{
AllowMissingChecksum: d.AllowMissingChecksum,
AllDeps: d.All,
Stdout: ctx.stdout,
})
}
26 changes: 7 additions & 19 deletions cmd/bindown/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package main
import (
"context"
"os"
"slices"
"strings"

"github.com/alecthomas/kong"
Expand Down Expand Up @@ -71,23 +70,6 @@ func completionConfig(ctx context.Context, args []string) *bindown.Config {
return configFile
}

func allDependencies(cfg *bindown.Config) []string {
if cfg == nil {
return []string{}
}
system := bindown.CurrentSystem
dependencies := make([]string, 0, len(cfg.Dependencies))
for depName := range cfg.Dependencies {
bn, err := cfg.BinName(depName, system)
if err != nil {
return []string{}
}
dependencies = append(dependencies, bn)
}
slices.Sort(dependencies)
return dependencies
}

func templateSourceCompleter(ctx context.Context) complete.PredictFunc {
return func(a complete.Args) []string {
cfg := completionConfig(ctx, a.Completed)
Expand Down Expand Up @@ -164,13 +146,19 @@ func localTemplateFromSourceCompleter(ctx context.Context) complete.PredictFunc
func binCompleter(ctx context.Context) complete.PredictFunc {
return func(a complete.Args) []string {
cfg := completionConfig(ctx, a.Completed)
return complete.PredictSet(allDependencies(cfg)...).Predict(a)
if cfg == nil {
return []string{}
}
return complete.PredictSet(cfg.DependencyNames()...).Predict(a)
}
}

func systemCompleter(ctx context.Context) complete.PredictFunc {
return func(a complete.Args) []string {
cfg := completionConfig(ctx, a.Completed)
if cfg == nil {
return []string{}
}
opts := make([]string, 0, len(cfg.Systems))
for _, system := range cfg.Systems {
opts = append(opts, string(system))
Expand Down
2 changes: 1 addition & 1 deletion cmd/bindown/dependency.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ func (c *dependencyListCmd) Run(ctx *runContext) error {
if err != nil {
return err
}
fmt.Fprintln(ctx.stdout, strings.Join(allDependencies(cfg), "\n"))
fmt.Fprintln(ctx.stdout, strings.Join(cfg.DependencyNames(), "\n"))
return nil
}

Expand Down
4 changes: 2 additions & 2 deletions cmd/bindown/testutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func (c *cmdRunner) run(commandLine ...string) *runCmdResult {
&runOpts{
stdin: simpleFileReader{c.stdin},
stdout: SimpleFileWriter{&result.stdOut},
stderr: &result.stdErr,
stderr: SimpleFileWriter{&result.stdErr},
cmdName: "cmd",
exitHandler: func(i int) {
result.exited = true
Expand Down Expand Up @@ -102,7 +102,7 @@ func (c *cmdRunner) runExpect(expectFunc func(*expect.Console), commandLine ...s
&runOpts{
stdin: console.Tty(),
stdout: console.Tty(),
stderr: &result.stdErr,
stderr: console.Tty(),
cmdName: "cmd",
exitHandler: func(i int) {
result.exited = true
Expand Down
1 change: 1 addition & 0 deletions docs/clihelp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Commands:
download download a dependency but don't extract or install it
extract download and extract a dependency but don't install it
install download, extract and install a dependency
wrap create a wrapper script for a dependency
format formats the config file
dependency list list configured dependencies
dependency add add a template-based dependency
Expand Down
Loading

0 comments on commit 9ccce7d

Please sign in to comment.