-
Notifications
You must be signed in to change notification settings - Fork 5.8k
cmd/prompt: remove uses of github.com/AlecAivazis/survey/v2 #14161
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,9 +19,10 @@ package prompt | |
| import ( | ||
| "fmt" | ||
| "io" | ||
| "strings" | ||
|
|
||
| "github.com/AlecAivazis/survey/v2" | ||
| "github.com/docker/cli/cli/streams" | ||
| "golang.org/x/term" | ||
|
|
||
| "github.com/docker/compose/v5/pkg/utils" | ||
| ) | ||
|
|
@@ -74,17 +75,34 @@ func (s streamsFileReader) Fd() uintptr { | |
|
|
||
| // Confirm asks for yes or no input | ||
| func (u User) Confirm(message string, defaultValue bool) (bool, error) { | ||
| qs := &survey.Confirm{ | ||
| Message: message, | ||
| Default: defaultValue, | ||
| if err := u.stdin.stream.SetRawTerminal(); err != nil { | ||
| return false, err | ||
| } | ||
| defer u.stdin.stream.RestoreTerminal() | ||
|
|
||
| terminal := term.NewTerminal(struct { | ||
| io.Reader | ||
| io.Writer | ||
| }{ | ||
| Reader: u.stdin, | ||
| Writer: u.stdout, | ||
| }, message) | ||
|
|
||
| for { | ||
| answer, err := terminal.ReadLine() | ||
| if err != nil { | ||
| return false, err | ||
| } | ||
|
|
||
| switch strings.ToLower(answer) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: |
||
| case "": | ||
| return defaultValue, nil | ||
| case "y", "yes": | ||
| return true, nil | ||
| case "n", "no": | ||
| return false, nil | ||
| } | ||
| } | ||
| var b bool | ||
| err := survey.AskOne(qs, &b, func(options *survey.AskOptions) error { | ||
| options.Stdio.In = u.stdin | ||
| options.Stdio.Out = u.stdout | ||
| return nil | ||
| }) | ||
| return b, err | ||
| } | ||
|
|
||
| // Pipe - aggregates prompt methods | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| /* | ||
| Copyright 2020 Docker Compose CLI authors | ||
|
|
||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
|
|
||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| */ | ||
|
|
||
| package prompt | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "io" | ||
| "strings" | ||
| "testing" | ||
| "time" | ||
|
|
||
| "github.com/creack/pty" | ||
| "github.com/docker/cli/cli/streams" | ||
| "gotest.tools/v3/assert" | ||
| ) | ||
|
|
||
| // TestPipeConfirmSequential verifies consecutive piped confirmations consume | ||
| // one line of input at a time. | ||
| func TestPipeConfirmSequential(t *testing.T) { | ||
| var stdout bytes.Buffer | ||
| pipe := Pipe{ | ||
| stdin: strings.NewReader("y\nn\n"), | ||
| stdout: &stdout, | ||
| } | ||
|
|
||
| got, err := pipe.Confirm("first? ", false) | ||
| assert.NilError(t, err) | ||
| assert.Assert(t, got) | ||
|
|
||
| got, err = pipe.Confirm("second? ", true) | ||
| assert.NilError(t, err) | ||
| assert.Assert(t, !got) | ||
| } | ||
|
|
||
| // TestUserConfirm verifies that an interactive terminal confirmation returns | ||
| // the expected answer and retries invalid input. | ||
| func TestUserConfirm(t *testing.T) { | ||
| ptmx, tty, err := pty.Open() | ||
| assert.NilError(t, err) | ||
| t.Cleanup(func() { | ||
| _ = tty.Close() | ||
| _ = ptmx.Close() | ||
| }) | ||
|
|
||
| user := User{ | ||
| stdin: streamsFileReader{streams.NewIn(tty)}, | ||
| stdout: streamsFileWriter{streams.NewOut(tty)}, | ||
| } | ||
|
|
||
| done := make(chan struct { | ||
| answer bool | ||
| err error | ||
| }, 1) | ||
|
|
||
| go func() { | ||
| answer, err := user.Confirm("Continue? ", false) | ||
| done <- struct { | ||
| answer bool | ||
| err error | ||
| }{answer, err} | ||
| }() | ||
|
|
||
| readUntil(t, ptmx, "Continue? ") | ||
|
|
||
| _, err = ptmx.Write([]byte("maybe\r")) | ||
| assert.NilError(t, err) | ||
|
|
||
| readUntil(t, ptmx, "Continue? ") | ||
|
|
||
| _, err = ptmx.Write([]byte("y\r")) | ||
| assert.NilError(t, err) | ||
|
|
||
| result := <-done | ||
| assert.NilError(t, result.err) | ||
| assert.Assert(t, result.answer) | ||
| } | ||
|
|
||
| // TestUserConfirmInterrupt verifies that Ctrl+C interrupts an interactive | ||
| // terminal confirmation. | ||
| func TestUserConfirmInterrupt(t *testing.T) { | ||
| ptmx, tty, err := pty.Open() | ||
| assert.NilError(t, err) | ||
| t.Cleanup(func() { | ||
| _ = tty.Close() | ||
| _ = ptmx.Close() | ||
| }) | ||
|
|
||
| user := User{ | ||
| stdin: streamsFileReader{streams.NewIn(tty)}, | ||
| stdout: streamsFileWriter{streams.NewOut(tty)}, | ||
| } | ||
|
|
||
| done := make(chan error, 1) | ||
| go func() { | ||
| _, err := user.Confirm("Continue? ", false) | ||
| done <- err | ||
| }() | ||
|
|
||
| readUntil(t, ptmx, "Continue? ") | ||
|
|
||
| _, err = ptmx.Write([]byte{3}) // Ctrl+C | ||
| assert.NilError(t, err) | ||
|
|
||
| select { | ||
| case err := <-done: | ||
| assert.ErrorIs(t, err, io.EOF) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Behavior note this test locks in: Ctrl+C now surfaces as |
||
| case <-time.After(time.Second): | ||
| t.Fatal("timed out waiting for prompt to return") | ||
| } | ||
| } | ||
|
|
||
| // readUntil reads until the expected string is observed. | ||
| func readUntil(t *testing.T, r io.Reader, want string) { | ||
| t.Helper() | ||
|
|
||
| var got strings.Builder | ||
| buf := make([]byte, 64) | ||
| for !strings.Contains(got.String(), want) { | ||
| n, err := r.Read(buf) | ||
| assert.NilError(t, err, "reading until %q; got %q", want, got.String()) | ||
| got.Write(buf[:n]) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The "(y/N)" hint survey auto-appended is lost, and three call sites relied on it — the description's "current callers already include the confirmation hint" holds for the two prompts in
cmd/compose/options.goonly. These don't carry any hint in their message:pkg/compose/publish.go— "Are you ok to publish these bind mount declarations?" and "…these sensitive data?" (viaconfirmOrCancel);pkg/bridge/convert.go— "Output directory … will be permanently deleted. Continue?".After this change those render as a bare question: the user no longer sees the expected input format nor the default — on destructive confirmations. Minimal fix that preserves every caller at once: have
Confirmappend" [y/N]: "/" [Y/n]: "(perdefaultValue) when the message doesn't already end with a hint; alternatively, fix the three messages in this same PR.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you go with option 2 (fixing individual messages rather than the centralized approach), don't miss two more call sites through the same
Confirmwith the same bare-question issue:pkg/compose/publish.go:619—buildEnvPromptMessage: "...Are you ok to publish these env declarations?"pkg/compose/publish.go:629—buildConfigContentPromptMessage: "...Are you ok to publish these config contents?"