consolidate checks whether stdin/stdout is terminal

This commit is contained in:
Michael Eischer
2025-09-07 14:19:29 +02:00
parent 93ccc548c8
commit 0b0dd07f15
8 changed files with 54 additions and 43 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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)...)

View File

@@ -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
}