Skip to content

Commit

Permalink
Merge pull request #156 from WillAbides/depadd
Browse files Browse the repository at this point in the history
show variables in dependency add
  • Loading branch information
WillAbides authored Jun 2, 2023
2 parents a11c35f + cfbe4b9 commit fa72a96
Show file tree
Hide file tree
Showing 14 changed files with 396 additions and 75 deletions.
38 changes: 33 additions & 5 deletions cmd/bindown/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,38 @@ func loadConfigFile(ctx *runContext, noDefaultDirs bool) (*bindown.Config, error
return configFile, nil
}

// fileWriter covers terminal.FileWriter. Needed for survey
type fileWriter interface {
io.Writer
Fd() uintptr
}

type SimpleFileWriter struct {
io.Writer
}

func (s SimpleFileWriter) Fd() uintptr {
return 0
}

// fileReader covers terminal.FileReader. Needed for survey
type fileReader interface {
io.Reader
Fd() uintptr
}

type SimpleFileReader struct {
io.Reader
}

func (s SimpleFileReader) Fd() uintptr {
return 0
}

type runContext struct {
parent context.Context
stdin io.Reader
stdout io.Writer
stdin fileReader
stdout fileWriter
rootCmd *rootCmd
}

Expand All @@ -123,8 +151,8 @@ func (r *runContext) Value(key any) any {
}

type runOpts struct {
stdin io.Reader
stdout io.Writer
stdin fileReader
stdout fileWriter
stderr io.Writer
cmdName string
exitHandler func(int)
Expand Down Expand Up @@ -171,7 +199,7 @@ func Run(ctx context.Context, args []string, opts *runOpts) {
kongCtx, err := parser.Parse(args)
parser.FatalIfErrorf(err)
if root.Quiet {
runCtx.stdout = io.Discard
runCtx.stdout = SimpleFileWriter{io.Discard}
kongCtx.Stdout = io.Discard
}
err = kongCtx.Run()
Expand Down
121 changes: 97 additions & 24 deletions cmd/bindown/dependency.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package main

import (
"bufio"
"encoding/json"
"fmt"
"regexp"
"strings"

"github.com/AlecAivazis/survey/v2"
"github.com/willabides/bindown/v4/internal/bindown"
"github.com/willabides/bindown/v4/internal/builddep"
)
Expand Down Expand Up @@ -157,10 +157,10 @@ func (c *dependencyRemoveCmd) Run(ctx *runContext) error {

type dependencyAddCmd struct {
Name string `kong:"arg"`
Template string `kong:"arg,predictor=template"`
Template string `kong:"arg,optional,predictor=template"`
TemplateSource string `kong:"name=source,help='template source',predictor=templateSource"`
Vars map[string]string `kong:"name=var"`
SkipRequiredVars bool `kong:"name=skipvars,help='do not prompt for required vars'"`
SkipRequiredVars bool `kong:"name=skipvars,help='do not prompt for required vars. implies --skipchecksums'"`
SkipChecksums bool `kong:"name=skipchecksums,help='do not add checksums for this dependency'"`
}

Expand All @@ -170,46 +170,55 @@ func (c *dependencyAddCmd) Run(ctx *runContext) error {
return err
}
tmpl := c.Template
if tmpl == "" {
tmpl = c.Name
}
tmplSrc := c.TemplateSource
if tmplSrc == "" {
tmplParts := strings.SplitN(tmpl, "#", 2)
if len(tmplParts) == 2 {
tmpl = tmplParts[1]
tmplSrc = tmplParts[0]
ts, t, ok := strings.Cut(tmpl, "#")
if ok {
tmplSrc, tmpl = ts, t
}
}

if c.Vars == nil {
c.Vars = map[string]string{}
}
err = config.AddDependencyFromTemplate(ctx, tmpl, &bindown.AddDependencyFromTemplateOpts{
dep, varVals, err := config.AddDependencyFromTemplate(ctx, tmpl, &bindown.AddDependencyFromTemplateOpts{
DependencyName: c.Name,
TemplateSource: tmplSrc,
Vars: c.Vars,
})
if err != nil {
return err
}
missingVars, err := config.MissingDependencyVars(c.Name)
// This shouldn't be possible, but just in case
if dep.Template == nil || config.Templates == nil || config.Templates[*dep.Template] == nil {
return fmt.Errorf("template not found: %q", tmpl)
}
if varVals == nil {
varVals = map[string][]string{}
}
tmplCfg := config.Templates[*dep.Template]
// Don't need to output the list of systems
systems := tmplCfg.Systems
tmplCfg.Systems = nil
fmt.Fprintf(ctx.stdout, "Adding dependency %q from template ", c.Name)
err = bindown.EncodeYaml(ctx.stdout, map[string]bindown.Dependency{
*dep.Template: *tmplCfg,
})
if err != nil {
return err
}
hasMissingVars := len(missingVars) > 0
if hasMissingVars && !c.SkipRequiredVars {
hasMissingVars = false
scanner := bufio.NewScanner(ctx.stdin)
for _, missingVar := range missingVars {
fmt.Fprintf(ctx.stdout, "Please enter a value for required variable %q:\t", missingVar)
scanner.Scan()
err = scanner.Err()
if err != nil {
return err
}
val := scanner.Text()
config.Dependencies[c.Name].Vars[missingVar] = val
}
tmplCfg.Systems = systems

err = c.promptForVars(ctx, config, dep, varVals)
if err != nil {
return err
}
if !hasMissingVars && !c.SkipChecksums {

skipChecksums := c.SkipChecksums || c.SkipRequiredVars
if !skipChecksums {
err = config.AddChecksums([]string{c.Name}, nil)
if err != nil {
return err
Expand All @@ -218,6 +227,70 @@ func (c *dependencyAddCmd) Run(ctx *runContext) error {
return config.WriteFile(ctx.rootCmd.JSONConfig)
}

func (c *dependencyAddCmd) promptForVars(ctx *runContext, config *bindown.Config, dep *bindown.Dependency, varVals map[string][]string) error {
if c.SkipRequiredVars {
return nil
}
missingVars, err := config.MissingDependencyVars(c.Name)
if err != nil {
return err
}
if len(missingVars) == 0 {
return nil
}
for _, d := range config.Dependencies {
if d.Template == nil || dep.Template == nil || *d.Template != *dep.Template {
continue
}
for k, v := range d.Vars {
varVals[k] = append([]string{v}, varVals[k]...)
}
}

questions := make([]*survey.Question, 0, len(missingVars))
for _, missingVar := range missingVars {
prompt := survey.Input{
Message: missingVar,
// Help: `The thing you need to do is...`,
}
knownVals := varVals[missingVar]
bindown.SortBySemverOrString(knownVals)
knownVals = bindown.Unique(knownVals, knownVals[:0])
if len(knownVals) > 0 {
prompt.Default = knownVals[0]
prompt.Suggest = func(toComplete string) []string {
suggestions := make([]string, 0, len(knownVals))
for _, v := range knownVals {
if strings.HasPrefix(v, toComplete) {
suggestions = append(suggestions, v)
}
}
return suggestions
}
}
questions = append(questions, &survey.Question{
Name: missingVar,
Prompt: &prompt,
})
}
answers := map[string]any{}
err = survey.Ask(questions, &answers, survey.WithStdio(ctx.stdin, ctx.stdout, nil), survey.WithShowCursor(true))
if err != nil {
return err
}
for k, v := range answers {
s, ok := v.(string)
if !ok {
return fmt.Errorf("expected string for %q, got %T", k, v)
}
config.Dependencies[c.Name].Vars[k] = s
}

fmt.Fprintf(ctx.stdout, "Adding dependency %q from template\n", c.Name)

return nil
}

type dependencyAddByUrlsCmd struct {
Name string `kong:"arg,help='dependency name'"`
Version string `kong:"arg,help='dependency version'"`
Expand Down
48 changes: 36 additions & 12 deletions cmd/bindown/dependency_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import (
"os"
"path/filepath"
"runtime"
"strings"
"testing"

"github.com/Netflix/go-expect"
"github.com/stretchr/testify/require"
"github.com/willabides/bindown/v4/internal/testutil"
)
Expand Down Expand Up @@ -362,7 +362,9 @@ url_checksums:
foo-darwin-amd64-1.2.3: deadbeef
`)
result := runner.run("dependency", "add", "dep1", "tmpl", "--var=version=1.2.3")
result.assertState(resultState{})
result.assertState(resultState{
stdout: `Adding dependency "dep1" from template tmpl`,
})
cfg := runner.getConfigFile()
wantDep := mustConfigFromYAML(t, `
dependencies:
Expand Down Expand Up @@ -417,7 +419,9 @@ templates:
`), 0o600)
require.NoError(t, err)
result := runner.run("dependency", "add", "dep1", "tmpl", "--var=version=1.2.3", "--source=origin")
result.assertState(resultState{})
result.assertState(resultState{
stdout: `Adding dependency "dep1" from template origin#tmpl`,
})
wantDep := mustConfigFromYAML(t, `
dependencies:
dep1:
Expand Down Expand Up @@ -448,7 +452,9 @@ templates:
`), 0o600)
require.NoError(t, err)
result := runner.run("dependency", "add", "dep1", "origin#tmpl", "--var=version=1.2.3")
result.assertState(resultState{})
result.assertState(resultState{
stdout: `Adding dependency "dep1" from template origin#tmpl`,
})
wantDep := mustConfigFromYAML(t, `
dependencies:
dep1:
Expand All @@ -463,6 +469,12 @@ dependencies:
runner := newCmdRunner(t)
runner.writeConfigYaml(`
systems: ["linux/amd64", "darwin/amd64"]
dependencies:
dep2:
template: tmpl
vars:
version: "1.2.3"
foo: bar
templates:
tmpl:
url: foo-{{ .os }}-{{ .arch }}-{{ .version }}
Expand All @@ -472,10 +484,19 @@ url_checksums:
foo-darwin-amd64-1.2.3: deadbeef
foo-windows-amd64-1.2.3: deadbeef
`)
runner.stdin = strings.NewReader("1.2.3\nbar")
result := runner.run("dependency", "add", "dep1", "tmpl")

ex := func(console *expect.Console) {
_, err := console.ExpectString("(bar) ")
require.NoError(t, err)
_, err = console.SendLine("")
require.NoError(t, err)
_, err = console.ExpectString("template")
require.NoError(t, err)
require.NoError(t, console.Close())
}
result := runner.runExpect(ex, "dependency", "add", "dep1", "tmpl", "--var=version=1.2.3")
result.assertState(resultState{
stdout: `Please enter a value for required variable "version": Please enter a value for required variable "foo":`,
stdout: `.*`,
})
cfg := runner.getConfigFile()
wantDep := mustConfigFromYAML(t, `
Expand Down Expand Up @@ -506,11 +527,14 @@ systems: ["darwin/amd64", "darwin/arm64", "linux/amd64", "windows/amd64"]
template_sources:
origin: %q
`, srcPath))
wantStdout := `Please enter a value for required variable "version": Please enter a value for required variable "addr": `
runner.stdin = strings.NewReader(fmt.Sprintf("%s\n%s", "1.2.3", server.URL))
result := runner.run("dependency", "add", "foo", "tmpl1", "--source", "origin")
require.Equal(t, 0, result.exitVal)
require.Equal(t, wantStdout, result.stdOut.String())
result := runner.run("dependency", "add", "foo", "tmpl1",
"--source", "origin",
"--var", "version=1.2.3",
"--var", "addr="+server.URL,
)
result.assertState(resultState{
stdout: `Adding dependency "foo" from template origin#tmpl`,
})
gotCfg := runner.getConfigFile()
wantDep := mustConfigFromYAML(t, fmt.Sprintf(`
dependencies:
Expand Down
Loading

0 comments on commit fa72a96

Please sign in to comment.