diff --git a/cmd/restic/cmd_dump.go b/cmd/restic/cmd_dump.go index 978b64616..921309101 100644 --- a/cmd/restic/cmd_dump.go +++ b/cmd/restic/cmd_dump.go @@ -11,6 +11,7 @@ import ( "github.com/restic/restic/internal/dump" "github.com/restic/restic/internal/errors" "github.com/restic/restic/internal/restic" + "github.com/restic/restic/internal/terminal" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -199,7 +200,7 @@ func runDump(ctx context.Context, opts DumpOptions, gopts GlobalOptions, args [] } func checkStdoutArchive() error { - if stdoutIsTerminal() { + if terminal.StdoutIsTerminal() { return fmt.Errorf("stdout is the terminal, please redirect output") } return nil diff --git a/cmd/restic/cmd_generate.go b/cmd/restic/cmd_generate.go index 5ced5fe01..3c5ddffd5 100644 --- a/cmd/restic/cmd_generate.go +++ b/cmd/restic/cmd_generate.go @@ -6,6 +6,7 @@ import ( "time" "github.com/restic/restic/internal/errors" + "github.com/restic/restic/internal/terminal" "github.com/spf13/cobra" "github.com/spf13/cobra/doc" "github.com/spf13/pflag" @@ -71,7 +72,7 @@ func writeManpages(root *cobra.Command, dir string) error { } func writeCompletion(filename string, shell string, generate func(w io.Writer) error) (err error) { - if stdoutIsTerminal() { + if terminal.StdoutIsTerminal() { Verbosef("writing %s completion file to %v\n", shell, filename) } var outWriter io.Writer diff --git a/cmd/restic/cmd_restore.go b/cmd/restic/cmd_restore.go index bb32333c9..f0c84c628 100644 --- a/cmd/restic/cmd_restore.go +++ b/cmd/restic/cmd_restore.go @@ -10,6 +10,7 @@ import ( "github.com/restic/restic/internal/filter" "github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/restorer" + "github.com/restic/restic/internal/terminal" "github.com/restic/restic/internal/ui" restoreui "github.com/restic/restic/internal/ui/restore" "github.com/restic/restic/internal/ui/termstatus" @@ -260,7 +261,7 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions, } var count int t0 := time.Now() - bar := newTerminalProgressMax(!gopts.Quiet && !gopts.JSON && stdoutIsTerminal(), 0, "files verified", term) + bar := newTerminalProgressMax(!gopts.Quiet && !gopts.JSON && terminal.StdoutIsTerminal(), 0, "files verified", term) count, err = res.VerifyFiles(ctx, opts.Target, countRestoredFiles, bar) if err != nil { return err diff --git a/cmd/restic/global.go b/cmd/restic/global.go index 56a3f5f18..e603673df 100644 --- a/cmd/restic/global.go +++ b/cmd/restic/global.go @@ -199,28 +199,6 @@ func collectBackends() *location.Registry { return backends } -func stdinIsTerminal() bool { - return term.IsTerminal(int(os.Stdin.Fd())) -} - -func stdoutIsTerminal() bool { - // mintty on windows can use pipes which behave like a posix terminal, - // but which are not a terminal handle - return term.IsTerminal(int(os.Stdout.Fd())) || stdoutCanUpdateStatus() -} - -func stdoutCanUpdateStatus() bool { - return terminal.CanUpdateStatus(os.Stdout.Fd()) -} - -func stdoutTerminalWidth() int { - w, _, err := term.GetSize(int(os.Stdout.Fd())) - if err != nil { - return 0 - } - return w -} - // ClearLine creates a platform dependent string to clear the current // line, so it can be overwritten. // @@ -232,7 +210,7 @@ func clearLine(w int) string { // ANSI sequences are not supported on Windows cmd shell. if w <= 0 { - if w = stdoutTerminalWidth(); w <= 0 { + if w = terminal.StdoutTerminalWidth(); w <= 0 { return "" } } @@ -399,11 +377,11 @@ func ReadPassword(ctx context.Context, opts GlobalOptions, prompt string) (strin err error ) - if stdinIsTerminal() { + if terminal.StdinIsTerminal() { password, err = readPasswordTerminal(ctx, os.Stdin, os.Stderr, prompt) } else { password, err = readPassword(os.Stdin) - if stdoutIsTerminal() { + if terminal.StdoutIsTerminal() { Verbosef("reading repository password from stdin\n") } } @@ -427,7 +405,7 @@ func ReadPasswordTwice(ctx context.Context, gopts GlobalOptions, prompt1, prompt if err != nil { return "", err } - if stdinIsTerminal() { + if terminal.StdinIsTerminal() { pw2, err := ReadPassword(ctx, gopts, prompt2) if err != nil { return "", err @@ -490,7 +468,7 @@ func OpenRepository(ctx context.Context, opts GlobalOptions) (*repository.Reposi } passwordTriesLeft := 1 - if stdinIsTerminal() && opts.password == "" && !opts.InsecureNoPassword { + if terminal.StdinIsTerminal() && opts.password == "" && !opts.InsecureNoPassword { passwordTriesLeft = 3 } @@ -520,7 +498,7 @@ func OpenRepository(ctx context.Context, opts GlobalOptions) (*repository.Reposi return nil, errors.Fatalf("%s", err) } - if stdoutIsTerminal() && !opts.JSON { + if terminal.StdoutIsTerminal() && !opts.JSON { id := s.Config().ID if len(id) > 8 { id = id[:8] @@ -544,7 +522,7 @@ func OpenRepository(ctx context.Context, opts GlobalOptions) (*repository.Reposi return s, nil } - if c.Created && !opts.JSON && stdoutIsTerminal() { + if c.Created && !opts.JSON && terminal.StdoutIsTerminal() { Verbosef("created new cache in %v\n", c.Base) } @@ -563,7 +541,7 @@ func OpenRepository(ctx context.Context, opts GlobalOptions) (*repository.Reposi // cleanup old cache dirs if instructed to do so if opts.CleanupCache { - if stdoutIsTerminal() && !opts.JSON { + if terminal.StdoutIsTerminal() && !opts.JSON { Verbosef("removing %d old cache dirs from %v\n", len(oldCacheDirs), c.Base) } for _, item := range oldCacheDirs { @@ -574,7 +552,7 @@ func OpenRepository(ctx context.Context, opts GlobalOptions) (*repository.Reposi } } } else { - if stdoutIsTerminal() { + if terminal.StdoutIsTerminal() { Verbosef("found %d old cache directories in %v, run `restic cache --cleanup` to remove them\n", len(oldCacheDirs), c.Base) } diff --git a/cmd/restic/progress.go b/cmd/restic/progress.go index 6f1626772..5af108944 100644 --- a/cmd/restic/progress.go +++ b/cmd/restic/progress.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/restic/restic/internal/terminal" "github.com/restic/restic/internal/ui" "github.com/restic/restic/internal/ui/progress" "github.com/restic/restic/internal/ui/termstatus" @@ -23,7 +24,7 @@ func calculateProgressInterval(show bool, json bool) time.Duration { fps = 60 } interval = time.Duration(float64(time.Second) / fps) - } else if !json && !stdoutCanUpdateStatus() || !show { + } else if !json && !terminal.StdoutCanUpdateStatus() || !show { interval = 0 } return interval @@ -68,9 +69,9 @@ func newProgressMax(show bool, max uint64, description string) *progress.Counter func printProgress(status string, final bool) { - canUpdateStatus := stdoutCanUpdateStatus() + canUpdateStatus := terminal.StdoutCanUpdateStatus() - w := stdoutTerminalWidth() + w := terminal.StdoutTerminalWidth() if w > 0 { if w < 3 { status = termstatus.Truncate(status, w) @@ -103,11 +104,11 @@ func printProgress(status string, final bool) { } func newIndexProgress(quiet bool, json bool) *progress.Counter { - return newProgressMax(!quiet && !json && stdoutIsTerminal(), 0, "index files loaded") + return newProgressMax(!quiet && !json && terminal.StdoutIsTerminal(), 0, "index files loaded") } func newIndexTerminalProgress(quiet bool, json bool, term *termstatus.Terminal) *progress.Counter { - return newTerminalProgressMax(!quiet && !json && stdoutIsTerminal(), 0, "index files loaded", term) + return newTerminalProgressMax(!quiet && !json && terminal.StdoutIsTerminal(), 0, "index files loaded", term) } type terminalProgressPrinter struct { diff --git a/internal/terminal/stdio.go b/internal/terminal/stdio.go new file mode 100644 index 000000000..4b11731c9 --- /dev/null +++ b/internal/terminal/stdio.go @@ -0,0 +1,29 @@ +package terminal + +import ( + "os" + + "golang.org/x/term" +) + +func StdinIsTerminal() bool { + return term.IsTerminal(int(os.Stdin.Fd())) +} + +func StdoutIsTerminal() bool { + // mintty on windows can use pipes which behave like a posix terminal, + // but which are not a terminal handle + return term.IsTerminal(int(os.Stdout.Fd())) || StdoutCanUpdateStatus() +} + +func StdoutCanUpdateStatus() bool { + return CanUpdateStatus(os.Stdout.Fd()) +} + +func StdoutTerminalWidth() int { + w, _, err := term.GetSize(int(os.Stdout.Fd())) + if err != nil { + return 0 + } + return w +} diff --git a/internal/terminal/terminal_posix.go b/internal/terminal/terminal_posix.go index cd9820f10..b279b0728 100644 --- a/internal/terminal/terminal_posix.go +++ b/internal/terminal/terminal_posix.go @@ -13,7 +13,7 @@ const ( PosixControlClearLine = "\x1b[2K" ) -// posixClearCurrentLine removes all characters from the current line and resets the +// PosixClearCurrentLine removes all characters from the current line and resets the // cursor position to the first column. func PosixClearCurrentLine(wr io.Writer, _ uintptr) { // clear current line @@ -24,7 +24,7 @@ func PosixClearCurrentLine(wr io.Writer, _ uintptr) { } } -// posixMoveCursorUp moves the cursor to the line n lines above the current one. +// PosixMoveCursorUp moves the cursor to the line n lines above the current one. func PosixMoveCursorUp(wr io.Writer, _ uintptr, n int) { data := []byte(PosixControlMoveCursorHome) data = append(data, bytes.Repeat([]byte(PosixControlMoveCursorUp), n)...) diff --git a/internal/terminal/terminal_unix.go b/internal/terminal/terminal_unix.go index 8893d21e9..65e353d9f 100644 --- a/internal/terminal/terminal_unix.go +++ b/internal/terminal/terminal_unix.go @@ -10,13 +10,13 @@ import ( "golang.org/x/term" ) -// clearCurrentLine removes all characters from the current line and resets the +// ClearCurrentLine removes all characters from the current line and resets the // cursor position to the first column. func ClearCurrentLine(_ uintptr) func(io.Writer, uintptr) { return PosixClearCurrentLine } -// moveCursorUp moves the cursor to the line n lines above the current one. +// MoveCursorUp moves the cursor to the line n lines above the current one. func MoveCursorUp(_ uintptr) func(io.Writer, uintptr, int) { return PosixMoveCursorUp }