Skip to content

Commit 5d9e6e4

Browse files
authored
cmdio: wrap promptui so callers stop importing it directly (#5174)
## Summary - Add `cmdio.SelectOptions`/`RunSelect` and `cmdio.PromptOptions`/`RunPrompt` as a neutral surface around promptui. - Migrate 4 `RunSelect` callers and 6 `Prompt` callers across `cmd/auth`, `cmd/configure`, and `libs/databrickscfg`. After this change, promptui is only imported under `libs/cmdio`, making a future swap of the prompt library a one-package change. - Behavior is unchanged. This pull request and its description were written by Isaac.
1 parent b540fec commit 5d9e6e4

12 files changed

Lines changed: 154 additions & 82 deletions

File tree

cmd/auth/auth.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,21 +45,20 @@ func promptForHost(ctx context.Context) (string, error) {
4545
return "", errors.New("the command is being run in a non-interactive environment, please specify a host using --host")
4646
}
4747

48-
prompt := cmdio.Prompt(ctx)
49-
prompt.Label = "Databricks host (e.g. https://<databricks-instance>.cloud.databricks.com)"
50-
return prompt.Run()
48+
return cmdio.RunPrompt(ctx, cmdio.PromptOptions{
49+
Label: "Databricks host (e.g. https://<databricks-instance>.cloud.databricks.com)",
50+
})
5151
}
5252

5353
func promptForAccountID(ctx context.Context) (string, error) {
5454
if !cmdio.IsPromptSupported(ctx) {
5555
return "", errors.New("the command is being run in a non-interactive environment, please specify an account ID using --account-id")
5656
}
5757

58-
prompt := cmdio.Prompt(ctx)
59-
prompt.Label = "Databricks account ID"
60-
prompt.Default = ""
61-
prompt.AllowEdit = true
62-
return prompt.Run()
58+
return cmdio.RunPrompt(ctx, cmdio.PromptOptions{
59+
Label: "Databricks account ID",
60+
AllowEdit: true,
61+
})
6362
}
6463

6564
// validateProfileHostConflict checks that --profile and --host don't conflict.

cmd/auth/login.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@ func promptForProfile(ctx context.Context, defaultValue string) (string, error)
3232
return "", nil
3333
}
3434

35-
prompt := cmdio.Prompt(ctx)
36-
prompt.Label = "Databricks profile name [" + defaultValue + "]"
37-
prompt.AllowEdit = true
38-
result, err := prompt.Run()
35+
result, err := cmdio.RunPrompt(ctx, cmdio.PromptOptions{
36+
Label: "Databricks profile name [" + defaultValue + "]",
37+
AllowEdit: true,
38+
})
3939
if result == "" {
4040
// Manually return the default value. We could use the prompt.Default
4141
// field, but be inconsistent with other prompts in the CLI.
@@ -756,10 +756,10 @@ func promptForWorkspaceSelection(ctx context.Context, authArguments *auth.AuthAr
756756
// promptForWorkspaceID asks the user to manually enter a workspace ID.
757757
// Returns empty string if the user provides no input.
758758
func promptForWorkspaceID(ctx context.Context) (string, error) {
759-
prompt := cmdio.Prompt(ctx)
760-
prompt.Label = "Enter workspace ID (empty to skip)"
761-
prompt.AllowEdit = true
762-
result, err := prompt.Run()
759+
result, err := cmdio.RunPrompt(ctx, cmdio.PromptOptions{
760+
Label: "Enter workspace ID (empty to skip)",
761+
AllowEdit: true,
762+
})
763763
if err != nil {
764764
return "", err
765765
}

cmd/auth/switch.go

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"github.com/databricks/cli/libs/databrickscfg"
1111
"github.com/databricks/cli/libs/databrickscfg/profile"
1212
"github.com/databricks/cli/libs/env"
13-
"github.com/manifoldco/promptui"
1413
"github.com/spf13/cobra"
1514
)
1615

@@ -87,7 +86,7 @@ func promptForSwitchProfile(ctx context.Context, profiles profile.Profiles, curr
8786
label = fmt.Sprintf("Current default: %s. Select a new default", currentDefault)
8887
}
8988

90-
i, _, err := cmdio.RunSelect(ctx, &promptui.Select{
89+
i, err := cmdio.RunSelect(ctx, cmdio.SelectOptions{
9190
Label: label,
9291
Items: items,
9392
StartInSearchMode: len(profiles) > 5,
@@ -97,12 +96,10 @@ func promptForSwitchProfile(ctx context.Context, profiles profile.Profiles, curr
9796
host := strings.ToLower(items[index].Host)
9897
return strings.Contains(name, input) || strings.Contains(host, input)
9998
},
100-
Templates: &promptui.SelectTemplates{
101-
Label: "{{ . | faint }}",
102-
Active: `{{.Name | bold}}{{if .Host}} ({{.Host|faint}}){{end}}`,
103-
Inactive: `{{.Name}}{{if .Host}} ({{.Host}}){{end}}`,
104-
Selected: `{{ "Default profile" | faint }}: {{ .Name | bold }}`,
105-
},
99+
LabelTemplate: "{{ . | faint }}",
100+
Active: `{{.Name | bold}}{{if .Host}} ({{.Host|faint}}){{end}}`,
101+
Inactive: `{{.Name}}{{if .Host}} ({{.Host}}){{end}}`,
102+
Selected: `{{ "Default profile" | faint }}: {{ .Name | bold }}`,
106103
})
107104
if err != nil {
108105
return "", err

cmd/auth/token.go

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
"github.com/databricks/databricks-sdk-go/config"
2222
"github.com/databricks/databricks-sdk-go/credentials/u2m"
2323
"github.com/databricks/databricks-sdk-go/credentials/u2m/cache"
24-
"github.com/manifoldco/promptui"
2524
"github.com/spf13/cobra"
2625
"golang.org/x/oauth2"
2726
)
@@ -379,8 +378,8 @@ type profileSelectItem struct {
379378
Host string
380379
}
381380

382-
// promptForProfileSelection shows a promptui select list with all configured
383-
// profiles plus "Enter a host URL" and "Create a new profile" options.
381+
// promptForProfileSelection shows a select list with all configured profiles
382+
// plus "Enter a host URL" and "Create a new profile" options.
384383
// Returns the selection type and, when a profile is selected, its name.
385384
func promptForProfileSelection(ctx context.Context, profiles profile.Profiles) (profileSelectionResult, string, error) {
386385
items := make([]profileSelectItem, 0, len(profiles)+2)
@@ -392,7 +391,7 @@ func promptForProfileSelection(ctx context.Context, profiles profile.Profiles) (
392391
enterHostIdx := len(items)
393392
items = append(items, profileSelectItem{Name: "Enter a host URL manually"})
394393

395-
i, _, err := cmdio.RunSelect(ctx, &promptui.Select{
394+
i, err := cmdio.RunSelect(ctx, cmdio.SelectOptions{
396395
Label: "Select a profile",
397396
Items: items,
398397
StartInSearchMode: len(profiles) > 5,
@@ -402,12 +401,10 @@ func promptForProfileSelection(ctx context.Context, profiles profile.Profiles) (
402401
host := strings.ToLower(items[index].Host)
403402
return strings.Contains(name, input) || strings.Contains(host, input)
404403
},
405-
Templates: &promptui.SelectTemplates{
406-
Label: "{{ . | faint }}",
407-
Active: `{{.Name | bold}}{{if .Host}} ({{.Host|faint}}){{end}}`,
408-
Inactive: `{{.Name}}{{if .Host}} ({{.Host}}){{end}}`,
409-
Selected: `{{ "Using profile" | faint }}: {{ .Name | bold }}`,
410-
},
404+
LabelTemplate: "{{ . | faint }}",
405+
Active: `{{.Name | bold}}{{if .Host}} ({{.Host|faint}}){{end}}`,
406+
Inactive: `{{.Name}}{{if .Host}} ({{.Host}}){{end}}`,
407+
Selected: `{{ "Using profile" | faint }}: {{ .Name | bold }}`,
411408
})
412409
if err != nil {
413410
return 0, "", err

cmd/configure/configure.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@ func configureInteractive(cmd *cobra.Command, flags *configureFlags, cfg *config
2727

2828
// Ask user to specify the host if not already set.
2929
if cfg.Host == "" {
30-
prompt := cmdio.Prompt(ctx)
31-
prompt.Label = "Databricks workspace host (https://...)"
32-
prompt.AllowEdit = true
33-
prompt.Validate = func(input string) error {
34-
normalized := normalizeHost(input)
35-
return validateHost(normalized)
36-
}
37-
out, err := prompt.Run()
30+
out, err := cmdio.RunPrompt(ctx, cmdio.PromptOptions{
31+
Label: "Databricks workspace host (https://...)",
32+
AllowEdit: true,
33+
Validate: func(input string) error {
34+
normalized := normalizeHost(input)
35+
return validateHost(normalized)
36+
},
37+
})
3838
if err != nil {
3939
return err
4040
}
@@ -43,10 +43,10 @@ func configureInteractive(cmd *cobra.Command, flags *configureFlags, cfg *config
4343

4444
// Ask user to specify the token is not already set.
4545
if cfg.Token == "" {
46-
prompt := cmdio.Prompt(ctx)
47-
prompt.Label = "Personal access token"
48-
prompt.Mask = '*'
49-
out, err := prompt.Run()
46+
out, err := cmdio.RunPrompt(ctx, cmdio.PromptOptions{
47+
Label: "Personal access token",
48+
Mask: '*',
49+
})
5050
if err != nil {
5151
return err
5252
}

cmd/root/bundle_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ workspace:
265265
}()
266266

267267
// Verify the prompt fires by reading output from stderr.
268-
// promptui with StartInSearchMode writes a search cursor first.
268+
// cmdio.RunSelect with StartInSearchMode writes a search cursor first.
269269
line, _, readErr := io.Stderr.ReadLine()
270270
if assert.NoError(t, readErr, "expected prompt output on stderr") {
271271
assert.Contains(t, string(line), "Search:")

libs/cmdio/io.go

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -161,21 +161,6 @@ func (nopWriteCloser) Close() error {
161161
return nil
162162
}
163163

164-
func Prompt(ctx context.Context) *promptui.Prompt {
165-
c := fromContext(ctx)
166-
return &promptui.Prompt{
167-
Stdin: c.promptStdin(),
168-
Stdout: nopWriteCloser{c.err},
169-
}
170-
}
171-
172-
func RunSelect(ctx context.Context, prompt *promptui.Select) (int, string, error) {
173-
c := fromContext(ctx)
174-
prompt.Stdin = c.promptStdin()
175-
prompt.Stdout = nopWriteCloser{c.err}
176-
return prompt.Run()
177-
}
178-
179164
// NewSpinner creates a new spinner for displaying progress indicators.
180165
// The returned spinner should be closed when done to release resources.
181166
//

libs/cmdio/prompt.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package cmdio
2+
3+
import (
4+
"context"
5+
6+
"github.com/manifoldco/promptui"
7+
)
8+
9+
// PromptOptions configures a single-line text prompt shown by [RunPrompt].
10+
type PromptOptions struct {
11+
// Label is shown before the input field. Required.
12+
Label string
13+
14+
// Default is the value pre-filled in the input field.
15+
Default string
16+
17+
// Mask, when non-zero, replaces typed characters with the given rune
18+
// (use '*' for password-style input).
19+
Mask rune
20+
21+
// AllowEdit lets the user edit Default rather than overwriting it.
22+
AllowEdit bool
23+
24+
// Validate, when set, is called on every keystroke; returning a non-nil
25+
// error keeps the prompt open and shows the error to the user.
26+
Validate func(input string) error
27+
}
28+
29+
// RunPrompt shows a single-line text prompt and returns the entered value.
30+
func RunPrompt(ctx context.Context, opts PromptOptions) (string, error) {
31+
c := fromContext(ctx)
32+
p := promptui.Prompt{
33+
Label: opts.Label,
34+
Default: opts.Default,
35+
Mask: opts.Mask,
36+
AllowEdit: opts.AllowEdit,
37+
Validate: opts.Validate,
38+
Stdin: c.promptStdin(),
39+
Stdout: nopWriteCloser{c.err},
40+
}
41+
return p.Run()
42+
}

libs/cmdio/select.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package cmdio
2+
3+
import (
4+
"context"
5+
6+
"github.com/manifoldco/promptui"
7+
)
8+
9+
// SelectOptions configures an interactive single-choice picker shown by
10+
// [RunSelect]. Template strings use text/template syntax and have access
11+
// to the fields of the items in Items.
12+
type SelectOptions struct {
13+
// Label is shown above the list. Required.
14+
Label string
15+
16+
// Items is the slice of values to choose from. Templates reference
17+
// fields on the element type.
18+
Items any
19+
20+
// Searcher, when set, narrows the list as the user types.
21+
Searcher func(input string, index int) bool
22+
23+
// StartInSearchMode opens the prompt with the search input focused.
24+
StartInSearchMode bool
25+
26+
// LabelTemplate renders Label. Empty uses the default.
27+
LabelTemplate string
28+
29+
// Active renders the highlighted item.
30+
Active string
31+
32+
// Inactive renders non-highlighted items.
33+
Inactive string
34+
35+
// Selected renders the chosen item after the prompt closes.
36+
Selected string
37+
}
38+
39+
// RunSelect shows an interactive picker and returns the index of the chosen item.
40+
func RunSelect(ctx context.Context, opts SelectOptions) (int, error) {
41+
c := fromContext(ctx)
42+
sel := &promptui.Select{
43+
Label: opts.Label,
44+
Items: opts.Items,
45+
Searcher: opts.Searcher,
46+
StartInSearchMode: opts.StartInSearchMode,
47+
Templates: &promptui.SelectTemplates{
48+
Label: opts.LabelTemplate,
49+
Active: opts.Active,
50+
Inactive: opts.Inactive,
51+
Selected: opts.Selected,
52+
},
53+
Stdin: c.promptStdin(),
54+
Stdout: nopWriteCloser{c.err},
55+
}
56+
idx, _, err := sel.Run()
57+
return idx, err
58+
}

libs/databrickscfg/cfgpickers/clusters.go

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"github.com/databricks/databricks-sdk-go"
1212
"github.com/databricks/databricks-sdk-go/service/compute"
1313
"github.com/databricks/databricks-sdk-go/service/iam"
14-
"github.com/manifoldco/promptui"
1514
"golang.org/x/mod/semver"
1615
)
1716

@@ -193,20 +192,18 @@ func AskForCluster(ctx context.Context, w *databricks.WorkspaceClient, filters .
193192
if len(compatible) == 1 {
194193
return compatible[0].ClusterId, nil
195194
}
196-
i, _, err := cmdio.RunSelect(ctx, &promptui.Select{
195+
i, err := cmdio.RunSelect(ctx, cmdio.SelectOptions{
197196
Label: "Choose compatible cluster",
198197
Items: compatible,
199198
Searcher: func(input string, idx int) bool {
200199
lower := strings.ToLower(compatible[idx].ClusterName)
201200
return strings.Contains(lower, strings.ToLower(input))
202201
},
203202
StartInSearchMode: true,
204-
Templates: &promptui.SelectTemplates{
205-
Label: "{{.ClusterName | faint}}",
206-
Active: `{{.ClusterName | bold}} ({{.State}} {{.Access}} Runtime {{.Runtime}}) ({{.ClusterId | faint}})`,
207-
Inactive: `{{.ClusterName}} ({{.State}} {{.Access}} Runtime {{.Runtime}})`,
208-
Selected: `{{ "Configured cluster" | faint }}: {{ .ClusterName | bold }} ({{.ClusterId | faint}})`,
209-
},
203+
LabelTemplate: "{{.ClusterName | faint}}",
204+
Active: `{{.ClusterName | bold}} ({{.State}} {{.Access}} Runtime {{.Runtime}}) ({{.ClusterId | faint}})`,
205+
Inactive: `{{.ClusterName}} ({{.State}} {{.Access}} Runtime {{.Runtime}})`,
206+
Selected: `{{ "Configured cluster" | faint }}: {{ .ClusterName | bold }} ({{.ClusterId | faint}})`,
210207
})
211208
if err != nil {
212209
return "", err

0 commit comments

Comments
 (0)