Skip to content

Commit

Permalink
Merge pull request #102 from planetscale/vmg/isatty
Browse files Browse the repository at this point in the history
cmd: support non-interactive shells
  • Loading branch information
fatih authored Mar 19, 2021
2 parents 076f3dd + 3eec7e5 commit 453e30a
Show file tree
Hide file tree
Showing 9 changed files with 57 additions and 41 deletions.
3 changes: 3 additions & 0 deletions internal/cmd/auth/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ func LoginCmd(cfg *config.Config) *cobra.Command {
Args: cobra.ExactArgs(0),
Short: "Authenticate with PlanetScale",
RunE: func(cmd *cobra.Command, args []string) error {
if !cmdutil.IsTTY {
return errors.New("The 'login' command requires an interactive shell")
}
authenticator, err := auth.New(cleanhttp.DefaultClient(), clientID, clientSecret, auth.SetBaseURL(authURL))
if err != nil {
return err
Expand Down
7 changes: 5 additions & 2 deletions internal/cmd/auth/logout.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@ func LogoutCmd(cfg *config.Config) *cobra.Command {
fmt.Println("Already logged out. Exiting...")
return nil
}
fmt.Println("Press Enter to log out of the PlanetScale API.")
_ = waitForEnter(cmd.InOrStdin())

if cmdutil.IsTTY {
fmt.Println("Press Enter to log out of the PlanetScale API.")
_ = waitForEnter(cmd.InOrStdin())
}

authenticator, err := auth.New(cleanhttp.DefaultClient(), clientID, clientSecret, auth.SetBaseURL(apiURL))
if err != nil {
Expand Down
5 changes: 4 additions & 1 deletion internal/cmd/branch/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,17 @@ func DeleteCmd(cfg *config.Config) *cobra.Command {

if !force {
confirmationName := fmt.Sprintf("%s/%s", source, branch)
userInput := ""
if !cmdutil.IsTTY {
return fmt.Errorf("Cannot confirm deletion of branch %q (run with -force to override)", confirmationName)
}

confirmationMessage := fmt.Sprintf("%s %s %s", cmdutil.Bold("Please type"), cmdutil.BoldBlue(confirmationName), cmdutil.Bold("to confirm:"))

prompt := &survey.Input{
Message: confirmationMessage,
}

var userInput string
err := survey.AskOne(prompt, &userInput)
if err != nil {
if err == terminal.InterruptErr {
Expand Down
5 changes: 4 additions & 1 deletion internal/cmd/database/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,16 @@ func DeleteCmd(cfg *config.Config) *cobra.Command {
name := args[0]

if !force {
userInput := ""
if !cmdutil.IsTTY {
return fmt.Errorf("Cannot confirm deletion of database %q (run with -force to override)", name)
}
confirmationMessage := fmt.Sprintf("%s %s %s", cmdutil.Bold("Please type"), cmdutil.BoldBlue(name), cmdutil.Bold("to confirm:"))

prompt := &survey.Input{
Message: confirmationMessage,
}

var userInput string
err := survey.AskOne(prompt, &userInput)
if err != nil {
if err == terminal.InterruptErr {
Expand Down
4 changes: 4 additions & 0 deletions internal/cmd/merge/merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ func MergeCmd(cfg *config.Config) *cobra.Command {
// If mergeInto is blank, then we need to prompt the user to select a
// branch.
if mergeInto == "" {
if !cmdutil.IsTTY {
return fmt.Errorf("no '-into' branch given")
}

err := selectBranch(ctx, client, fromBranch, &mergeInto, database, cfg.Organization)
if err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/org/switch.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func SwitchCmd(cfg *config.Config) *cobra.Command {
}
end()
organization = org.Name
} else if len(args) == 0 {
} else if len(args) == 0 && cmdutil.IsTTY {
// Get organization names to show the user
end := cmdutil.PrintProgress("Fetching organizations...")
defer end()
Expand Down
3 changes: 3 additions & 0 deletions internal/cmdutil/browser.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ const ApplicationURL = "https://app.planetscaledb.io"

// OpenBrowser opens a web browser at the specified url.
func OpenBrowser(goos, url string) *exec.Cmd {
if !IsTTY {
panic("OpenBrowser called without a TTY")
}
exe := "open"
var args []string
switch goos {
Expand Down
17 changes: 17 additions & 0 deletions internal/cmdutil/terminal.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,25 @@ import (

"github.com/briandowns/spinner"
"github.com/fatih/color"
"github.com/mattn/go-isatty"
)

var IsTTY = isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd())

func Emoji(emoji string) string {
if IsTTY {
return emoji
}
return ""
}

// PrintProgress starts a spinner with the relevant message.
func PrintProgress(message string) func() {
if !IsTTY {
fmt.Println(message)
return func() {}
}

// Output to STDERR so we don't polluate STDOUT.
s := spinner.New(spinner.CharSets[14], 100*time.Millisecond, spinner.WithWriter(os.Stderr))
s.Suffix = fmt.Sprintf(" %s", message)
Expand All @@ -24,10 +39,12 @@ func PrintProgress(message string) func() {

// BoldBlue returns a string formatted with blue and bold.
func BoldBlue(msg string) string {
// the 'color' package already handles IsTTY gracefully
return color.New(color.FgBlue).Add(color.Bold).Sprint(msg)
}

// Bold returns a string formatted with bold.
func Bold(msg string) string {
// the 'color' package already handles IsTTY gracefully
return color.New(color.Bold).Sprint(msg)
}
52 changes: 16 additions & 36 deletions internal/promptutil/branch.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ package promptutil

import (
"context"
"errors"
"fmt"
"time"

"github.com/AlecAivazis/survey/v2"
ps "github.com/planetscale/planetscale-go/planetscale"

"github.com/planetscale/cli/internal/cmdutil"
)

// GetBranch returns the database branch. If there is only one branch, it
Expand All @@ -31,42 +31,22 @@ func GetBranch(ctx context.Context, client *ps.Client, org, db string) (string,
return branches[0].Name, nil
}

branchNames := make([]string, 0, len(branches)-1)
for _, b := range branches {
branchNames = append(branchNames, b.Name)
}

prompt := &survey.Select{
Message: "Select a branch to connect to:",
Options: branchNames,
VimMode: true,
}

type result struct {
branch string
err error
}

resp := make(chan result)
if cmdutil.IsTTY {
branchNames := make([]string, 0, len(branches)-1)
for _, b := range branches {
branchNames = append(branchNames, b.Name)
}

go func() {
var branch string
err := survey.AskOne(prompt, &branch)
resp <- result{
branch: branch,
err: err,
prompt := &survey.Select{
Message: "Select a branch to connect to:",
Options: branchNames,
VimMode: true,
}
}()

// timeout so CLI is not blocked forever if the user accidently called it
select {
case <-time.After(time.Minute * 5):
// TODO(fatih): this is buggy. Because there is no proper cancellation
// in the survey.AskOne() function, it holds to stdin, which causes the
// terminal to malfunction. But the timeout is not intended for regular
// users, it's meant to catch script invocations, so let's still use it.
return "", errors.New("pscale connect timeout: no branch is selected")
case r := <-resp:
return r.branch, r.err
var branch string
err = survey.AskOne(prompt, &branch)
return branch, err
}

return "", fmt.Errorf("more than one branch exists for database %q", db)
}

0 comments on commit 453e30a

Please sign in to comment.