From 76b2cdd4fbec324947b13c1238517ea1070e4f28 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sat, 20 Sep 2025 23:06:28 +0200 Subject: [PATCH] replace globalOptions.stdout with termstatus.OutputWriter --- cmd/restic/cmd_cache.go | 2 +- cmd/restic/cmd_check_integration_test.go | 16 +++++++--------- cmd/restic/cmd_debug.go | 10 +++++----- cmd/restic/cmd_diff.go | 4 ++-- cmd/restic/cmd_diff_integration_test.go | 6 ++---- cmd/restic/cmd_features.go | 2 +- cmd/restic/cmd_find_integration_test.go | 6 ++---- cmd/restic/cmd_forget.go | 8 ++++---- cmd/restic/cmd_generate.go | 2 +- cmd/restic/cmd_generate_integration_test.go | 6 ++---- cmd/restic/cmd_init.go | 2 +- cmd/restic/cmd_key_integration_test.go | 6 ++---- cmd/restic/cmd_key_list.go | 4 ++-- cmd/restic/cmd_list_integration_test.go | 6 ++---- cmd/restic/cmd_ls.go | 4 ++-- cmd/restic/cmd_ls_integration_test.go | 6 ++---- cmd/restic/cmd_prune_integration_test.go | 6 ++---- .../cmd_repair_index_integration_test.go | 4 ++-- cmd/restic/cmd_snapshots.go | 6 +++--- cmd/restic/cmd_snapshots_integration_test.go | 6 ++---- cmd/restic/cmd_stats.go | 2 +- cmd/restic/cmd_version.go | 2 +- cmd/restic/global.go | 2 -- cmd/restic/integration_helpers_test.go | 18 ++++++++++-------- cmd/restic/main.go | 2 -- internal/ui/mock.go | 4 ++++ internal/ui/terminal.go | 3 +++ internal/ui/termstatus/status.go | 13 +++++++++++++ internal/ui/termstatus/stdio_wrapper.go | 6 ------ 29 files changed, 79 insertions(+), 85 deletions(-) diff --git a/cmd/restic/cmd_cache.go b/cmd/restic/cmd_cache.go index 0640716cd..da6537fcb 100644 --- a/cmd/restic/cmd_cache.go +++ b/cmd/restic/cmd_cache.go @@ -161,7 +161,7 @@ func runCache(opts CacheOptions, gopts GlobalOptions, args []string, term ui.Ter }) } - _ = tab.Write(gopts.stdout) + _ = tab.Write(gopts.term.OutputWriter()) printer.S("%d cache dirs in %s", len(dirs), cachedir) return nil diff --git a/cmd/restic/cmd_check_integration_test.go b/cmd/restic/cmd_check_integration_test.go index fd913c4cb..c28a3ed4f 100644 --- a/cmd/restic/cmd_check_integration_test.go +++ b/cmd/restic/cmd_check_integration_test.go @@ -23,15 +23,13 @@ func testRunCheckMustFail(t testing.TB, gopts GlobalOptions) { } func testRunCheckOutput(t testing.TB, gopts GlobalOptions, checkUnused bool) (string, error) { - buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error { - return withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error { - opts := CheckOptions{ - ReadData: true, - CheckUnused: checkUnused, - } - _, err := runCheck(context.TODO(), opts, gopts, nil, gopts.term) - return err - }) + buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts GlobalOptions) error { + opts := CheckOptions{ + ReadData: true, + CheckUnused: checkUnused, + } + _, err := runCheck(context.TODO(), opts, gopts, nil, gopts.term) + return err }) return buf.String(), err } diff --git a/cmd/restic/cmd_debug.go b/cmd/restic/cmd_debug.go index a168327eb..4709fffec 100644 --- a/cmd/restic/cmd_debug.go +++ b/cmd/restic/cmd_debug.go @@ -200,20 +200,20 @@ func runDebugDump(ctx context.Context, gopts GlobalOptions, args []string, term switch tpe { case "indexes": - return dumpIndexes(ctx, repo, gopts.stdout, printer) + return dumpIndexes(ctx, repo, gopts.term.OutputWriter(), printer) case "snapshots": - return debugPrintSnapshots(ctx, repo, gopts.stdout) + return debugPrintSnapshots(ctx, repo, gopts.term.OutputWriter()) case "packs": - return printPacks(ctx, repo, gopts.stdout, printer) + return printPacks(ctx, repo, gopts.term.OutputWriter(), printer) case "all": printer.S("snapshots:") - err := debugPrintSnapshots(ctx, repo, gopts.stdout) + err := debugPrintSnapshots(ctx, repo, gopts.term.OutputWriter()) if err != nil { return err } printer.S("indexes:") - err = dumpIndexes(ctx, repo, gopts.stdout, printer) + err = dumpIndexes(ctx, repo, gopts.term.OutputWriter(), printer) if err != nil { return err } diff --git a/cmd/restic/cmd_diff.go b/cmd/restic/cmd_diff.go index c10f9898d..f1e832cb8 100644 --- a/cmd/restic/cmd_diff.go +++ b/cmd/restic/cmd_diff.go @@ -424,7 +424,7 @@ func runDiff(ctx context.Context, opts DiffOptions, gopts GlobalOptions, args [] } if gopts.JSON { - enc := json.NewEncoder(gopts.stdout) + enc := json.NewEncoder(gopts.term.OutputWriter()) c.printChange = func(change *Change) { err := enc.Encode(change) if err != nil { @@ -458,7 +458,7 @@ func runDiff(ctx context.Context, opts DiffOptions, gopts GlobalOptions, args [] updateBlobs(repo, stats.BlobsAfter.Sub(both).Sub(stats.BlobsCommon), &stats.Added, printer.E) if gopts.JSON { - err := json.NewEncoder(gopts.stdout).Encode(stats) + err := json.NewEncoder(gopts.term.OutputWriter()).Encode(stats) if err != nil { printer.E("JSON encode failed: %v", err) } diff --git a/cmd/restic/cmd_diff_integration_test.go b/cmd/restic/cmd_diff_integration_test.go index 4143789c7..bf62f1e19 100644 --- a/cmd/restic/cmd_diff_integration_test.go +++ b/cmd/restic/cmd_diff_integration_test.go @@ -15,13 +15,11 @@ import ( ) func testRunDiffOutput(t testing.TB, gopts GlobalOptions, firstSnapshotID string, secondSnapshotID string) (string, error) { - buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error { + buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts GlobalOptions) error { opts := DiffOptions{ ShowMetadata: false, } - return withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error { - return runDiff(ctx, opts, gopts, []string{firstSnapshotID, secondSnapshotID}, gopts.term) - }) + return runDiff(ctx, opts, gopts, []string{firstSnapshotID, secondSnapshotID}, gopts.term) }) return buf.String(), err } diff --git a/cmd/restic/cmd_features.go b/cmd/restic/cmd_features.go index e705ff080..ec3b93db5 100644 --- a/cmd/restic/cmd_features.go +++ b/cmd/restic/cmd_features.go @@ -49,7 +49,7 @@ Exit status is 1 if there was any error. for _, flag := range flags { tab.AddRow(flag) } - return tab.Write(globalOptions.stdout) + return tab.Write(globalOptions.term.OutputWriter()) }, } diff --git a/cmd/restic/cmd_find_integration_test.go b/cmd/restic/cmd_find_integration_test.go index f4dca38b4..f49d34127 100644 --- a/cmd/restic/cmd_find_integration_test.go +++ b/cmd/restic/cmd_find_integration_test.go @@ -11,12 +11,10 @@ import ( ) func testRunFind(t testing.TB, wantJSON bool, opts FindOptions, gopts GlobalOptions, pattern string) []byte { - buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error { + buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts GlobalOptions) error { gopts.JSON = wantJSON - return withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error { - return runFind(ctx, opts, gopts, []string{pattern}, gopts.term) - }) + return runFind(ctx, opts, gopts, []string{pattern}, gopts.term) }) rtest.OK(t, err) return buf.Bytes() diff --git a/cmd/restic/cmd_forget.go b/cmd/restic/cmd_forget.go index edb702842..51613987c 100644 --- a/cmd/restic/cmd_forget.go +++ b/cmd/restic/cmd_forget.go @@ -251,7 +251,7 @@ func runForget(ctx context.Context, opts ForgetOptions, pruneOptions PruneOption } if gopts.Verbose >= 1 && !gopts.JSON { - err = PrintSnapshotGroupHeader(gopts.stdout, k) + err = PrintSnapshotGroupHeader(gopts.term.OutputWriter(), k) if err != nil { return err } @@ -274,7 +274,7 @@ func runForget(ctx context.Context, opts ForgetOptions, pruneOptions PruneOption } if len(keep) != 0 && !gopts.Quiet && !gopts.JSON { printer.P("keep %d snapshots:\n", len(keep)) - if err := PrintSnapshots(gopts.stdout, keep, reasons, opts.Compact); err != nil { + if err := PrintSnapshots(gopts.term.OutputWriter(), keep, reasons, opts.Compact); err != nil { return err } printer.P("\n") @@ -283,7 +283,7 @@ func runForget(ctx context.Context, opts ForgetOptions, pruneOptions PruneOption if len(remove) != 0 && !gopts.Quiet && !gopts.JSON { printer.P("remove %d snapshots:\n", len(remove)) - if err := PrintSnapshots(gopts.stdout, remove, nil, opts.Compact); err != nil { + if err := PrintSnapshots(gopts.term.OutputWriter(), remove, nil, opts.Compact); err != nil { return err } printer.P("\n") @@ -328,7 +328,7 @@ func runForget(ctx context.Context, opts ForgetOptions, pruneOptions PruneOption } if gopts.JSON && len(jsonGroups) > 0 { - err = printJSONForget(gopts.stdout, jsonGroups) + err = printJSONForget(gopts.term.OutputWriter(), jsonGroups) if err != nil { return err } diff --git a/cmd/restic/cmd_generate.go b/cmd/restic/cmd_generate.go index e35035b77..580e704dc 100644 --- a/cmd/restic/cmd_generate.go +++ b/cmd/restic/cmd_generate.go @@ -84,7 +84,7 @@ func writeCompletion(filename string, shell string, generate func(w io.Writer) e defer func() { err = outFile.Close() }() outWriter = outFile } else { - outWriter = gopts.stdout + outWriter = gopts.term.OutputWriter() } err = generate(outWriter) diff --git a/cmd/restic/cmd_generate_integration_test.go b/cmd/restic/cmd_generate_integration_test.go index e6a426a9f..97248d370 100644 --- a/cmd/restic/cmd_generate_integration_test.go +++ b/cmd/restic/cmd_generate_integration_test.go @@ -9,10 +9,8 @@ import ( ) func testRunGenerate(t testing.TB, gopts GlobalOptions, opts generateOptions) ([]byte, error) { - buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error { - return withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error { - return runGenerate(opts, gopts, []string{}, gopts.term) - }) + buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runGenerate(opts, gopts, []string{}, gopts.term) }) return buf.Bytes(), err } diff --git a/cmd/restic/cmd_init.go b/cmd/restic/cmd_init.go index ca5b5b770..1858cd22c 100644 --- a/cmd/restic/cmd_init.go +++ b/cmd/restic/cmd_init.go @@ -132,7 +132,7 @@ func runInit(ctx context.Context, opts InitOptions, gopts GlobalOptions, args [] ID: s.Config().ID, Repository: location.StripPassword(gopts.backends, gopts.Repo), } - return json.NewEncoder(gopts.stdout).Encode(status) + return json.NewEncoder(gopts.term.OutputWriter()).Encode(status) } return nil diff --git a/cmd/restic/cmd_key_integration_test.go b/cmd/restic/cmd_key_integration_test.go index 48fbbe6ba..3a127946a 100644 --- a/cmd/restic/cmd_key_integration_test.go +++ b/cmd/restic/cmd_key_integration_test.go @@ -16,10 +16,8 @@ import ( ) func testRunKeyListOtherIDs(t testing.TB, gopts GlobalOptions) []string { - buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error { - return withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error { - return runKeyList(ctx, gopts, []string{}, gopts.term) - }) + buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runKeyList(ctx, gopts, []string{}, gopts.term) }) rtest.OK(t, err) diff --git a/cmd/restic/cmd_key_list.go b/cmd/restic/cmd_key_list.go index 6394da75e..91c14f6c0 100644 --- a/cmd/restic/cmd_key_list.go +++ b/cmd/restic/cmd_key_list.go @@ -95,7 +95,7 @@ func listKeys(ctx context.Context, s *repository.Repository, gopts GlobalOptions } if gopts.JSON { - return json.NewEncoder(gopts.stdout).Encode(keys) + return json.NewEncoder(gopts.term.OutputWriter()).Encode(keys) } tab := table.New() @@ -108,5 +108,5 @@ func listKeys(ctx context.Context, s *repository.Repository, gopts GlobalOptions tab.AddRow(key) } - return tab.Write(gopts.stdout) + return tab.Write(gopts.term.OutputWriter()) } diff --git a/cmd/restic/cmd_list_integration_test.go b/cmd/restic/cmd_list_integration_test.go index d18c0715e..d9fb2a562 100644 --- a/cmd/restic/cmd_list_integration_test.go +++ b/cmd/restic/cmd_list_integration_test.go @@ -11,10 +11,8 @@ import ( ) func testRunList(t testing.TB, gopts GlobalOptions, tpe string) restic.IDs { - buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error { - return withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error { - return runList(ctx, gopts, []string{tpe}, gopts.term) - }) + buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runList(ctx, gopts, []string{tpe}, gopts.term) }) rtest.OK(t, err) return parseIDsFromReader(t, buf) diff --git a/cmd/restic/cmd_ls.go b/cmd/restic/cmd_ls.go index c3c48b97f..2c973586c 100644 --- a/cmd/restic/cmd_ls.go +++ b/cmd/restic/cmd_ls.go @@ -382,11 +382,11 @@ func runLs(ctx context.Context, opts LsOptions, gopts GlobalOptions, args []stri if gopts.JSON { printer = &jsonLsPrinter{ - enc: json.NewEncoder(gopts.stdout), + enc: json.NewEncoder(gopts.term.OutputWriter()), } } else if opts.Ncdu { printer = &ncduLsPrinter{ - out: gopts.stdout, + out: gopts.term.OutputWriter(), } } else { printer = &textLsPrinter{ diff --git a/cmd/restic/cmd_ls_integration_test.go b/cmd/restic/cmd_ls_integration_test.go index cda8bbb54..4f13b89e3 100644 --- a/cmd/restic/cmd_ls_integration_test.go +++ b/cmd/restic/cmd_ls_integration_test.go @@ -13,11 +13,9 @@ import ( ) func testRunLsWithOpts(t testing.TB, gopts GlobalOptions, opts LsOptions, args []string) []byte { - buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error { + buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts GlobalOptions) error { gopts.Quiet = true - return withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error { - return runLs(context.TODO(), opts, gopts, args, gopts.term) - }) + return runLs(context.TODO(), opts, gopts, args, gopts.term) }) rtest.OK(t, err) return buf.Bytes() diff --git a/cmd/restic/cmd_prune_integration_test.go b/cmd/restic/cmd_prune_integration_test.go index d56aa4afd..70e66e383 100644 --- a/cmd/restic/cmd_prune_integration_test.go +++ b/cmd/restic/cmd_prune_integration_test.go @@ -89,7 +89,7 @@ func createPrunableRepo(t *testing.T, env *testEnvironment) { } func testRunForgetJSON(t testing.TB, gopts GlobalOptions, args ...string) { - buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error { + buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts GlobalOptions) error { gopts.JSON = true opts := ForgetOptions{ DryRun: true, @@ -98,9 +98,7 @@ func testRunForgetJSON(t testing.TB, gopts GlobalOptions, args ...string) { pruneOpts := PruneOptions{ MaxUnused: "5%", } - return withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error { - return runForget(context.TODO(), opts, pruneOpts, gopts, gopts.term, args) - }) + return runForget(context.TODO(), opts, pruneOpts, gopts, gopts.term, args) }) rtest.OK(t, err) diff --git a/cmd/restic/cmd_repair_index_integration_test.go b/cmd/restic/cmd_repair_index_integration_test.go index 0319a77b4..7b8488889 100644 --- a/cmd/restic/cmd_repair_index_integration_test.go +++ b/cmd/restic/cmd_repair_index_integration_test.go @@ -17,7 +17,7 @@ import ( func testRunRebuildIndex(t testing.TB, gopts GlobalOptions) { rtest.OK(t, withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error { - gopts.stdout = io.Discard + gopts.Quiet = true return runRebuildIndex(context.TODO(), RepairIndexOptions{}, gopts, gopts.term) })) } @@ -129,7 +129,7 @@ func TestRebuildIndexFailsOnAppendOnly(t *testing.T) { return &appendOnlyBackend{r}, nil } err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error { - gopts.stdout = io.Discard + gopts.Quiet = true return runRebuildIndex(context.TODO(), RepairIndexOptions{}, gopts, gopts.term) }) diff --git a/cmd/restic/cmd_snapshots.go b/cmd/restic/cmd_snapshots.go index 908ad90b8..fc2930e85 100644 --- a/cmd/restic/cmd_snapshots.go +++ b/cmd/restic/cmd_snapshots.go @@ -103,7 +103,7 @@ func runSnapshots(ctx context.Context, opts SnapshotOptions, gopts GlobalOptions } if gopts.JSON { - err := printSnapshotGroupJSON(gopts.stdout, snapshotGroups, grouped) + err := printSnapshotGroupJSON(gopts.term.OutputWriter(), snapshotGroups, grouped) if err != nil { printer.E("error printing snapshots: %v", err) } @@ -116,12 +116,12 @@ func runSnapshots(ctx context.Context, opts SnapshotOptions, gopts GlobalOptions } if grouped { - err := PrintSnapshotGroupHeader(gopts.stdout, k) + err := PrintSnapshotGroupHeader(gopts.term.OutputWriter(), k) if err != nil { return err } } - err := PrintSnapshots(gopts.stdout, list, nil, opts.Compact) + err := PrintSnapshots(gopts.term.OutputWriter(), list, nil, opts.Compact) if err != nil { return err } diff --git a/cmd/restic/cmd_snapshots_integration_test.go b/cmd/restic/cmd_snapshots_integration_test.go index 00e60afd8..d962ddfc8 100644 --- a/cmd/restic/cmd_snapshots_integration_test.go +++ b/cmd/restic/cmd_snapshots_integration_test.go @@ -10,13 +10,11 @@ import ( ) func testRunSnapshots(t testing.TB, gopts GlobalOptions) (newest *Snapshot, snapmap map[restic.ID]Snapshot) { - buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error { + buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts GlobalOptions) error { gopts.JSON = true opts := SnapshotOptions{} - return withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error { - return runSnapshots(ctx, opts, gopts, []string{}, gopts.term) - }) + return runSnapshots(ctx, opts, gopts, []string{}, gopts.term) }) rtest.OK(t, err) diff --git a/cmd/restic/cmd_stats.go b/cmd/restic/cmd_stats.go index 27a0bd012..651e7ad2f 100644 --- a/cmd/restic/cmd_stats.go +++ b/cmd/restic/cmd_stats.go @@ -169,7 +169,7 @@ func runStats(ctx context.Context, opts StatsOptions, gopts GlobalOptions, args } if gopts.JSON { - err = json.NewEncoder(gopts.stdout).Encode(stats) + err = json.NewEncoder(gopts.term.OutputWriter()).Encode(stats) if err != nil { return fmt.Errorf("encoding output: %v", err) } diff --git a/cmd/restic/cmd_version.go b/cmd/restic/cmd_version.go index 7bf968d6c..f2aa47eb2 100644 --- a/cmd/restic/cmd_version.go +++ b/cmd/restic/cmd_version.go @@ -43,7 +43,7 @@ Exit status is 1 if there was any error. GoArch: runtime.GOARCH, } - err := json.NewEncoder(globalOptions.stdout).Encode(jsonS) + err := json.NewEncoder(globalOptions.term.OutputWriter()).Encode(jsonS) if err != nil { printer.E("JSON encode failed: %v\n", err) return diff --git a/cmd/restic/global.go b/cmd/restic/global.go index 7cbbad651..bcd7d52fc 100644 --- a/cmd/restic/global.go +++ b/cmd/restic/global.go @@ -3,7 +3,6 @@ package main import ( "context" "fmt" - "io" "os" "os/exec" "path/filepath" @@ -74,7 +73,6 @@ type GlobalOptions struct { limiter.Limits password string - stdout io.Writer term ui.Terminal backends *location.Registry diff --git a/cmd/restic/integration_helpers_test.go b/cmd/restic/integration_helpers_test.go index 790c54c36..892f61e10 100644 --- a/cmd/restic/integration_helpers_test.go +++ b/cmd/restic/integration_helpers_test.go @@ -212,10 +212,6 @@ func withTestEnvironment(t testing.TB) (env *testEnvironment, cleanup func()) { Quiet: true, CacheDir: env.cache, password: rtest.TestPassword, - // stdout and stderr are written to by Warnf etc. That is the written data - // usually consists of one or multiple lines and therefore can be handled well - // by t.Log. - stdout: &logOutputter{t}, extended: make(options.Options), // replace this hook with "nil" if listing a filetype more than once is necessary @@ -416,18 +412,24 @@ func testFileSize(filename string, size int64) error { return nil } -func withCaptureStdout(gopts GlobalOptions, inner func(gopts GlobalOptions) error) (*bytes.Buffer, error) { +func withCaptureStdout(t testing.TB, gopts GlobalOptions, callback func(ctx context.Context, gopts GlobalOptions) error) (*bytes.Buffer, error) { buf := bytes.NewBuffer(nil) - gopts.stdout = buf - err := inner(gopts) + err := withTermStatusRaw(os.Stdin, buf, &logOutputter{t: t}, gopts, callback) return buf, err } func withTermStatus(t testing.TB, gopts GlobalOptions, callback func(ctx context.Context, gopts GlobalOptions) error) error { + // stdout and stderr are written to by printer functions etc. That is the written data + // usually consists of one or multiple lines and therefore can be handled well + // by t.Log. + return withTermStatusRaw(os.Stdin, &logOutputter{t: t}, &logOutputter{t: t}, gopts, callback) +} + +func withTermStatusRaw(stdin io.ReadCloser, stdout, stderr io.Writer, gopts GlobalOptions, callback func(ctx context.Context, gopts GlobalOptions) error) error { ctx, cancel := context.WithCancel(context.TODO()) var wg sync.WaitGroup - term := termstatus.New(os.Stdin, gopts.stdout, &logOutputter{t: t}, gopts.Quiet) + term := termstatus.New(stdin, stdout, stderr, gopts.Quiet) gopts.term = term wg.Add(1) go func() { diff --git a/cmd/restic/main.go b/cmd/restic/main.go index b5b8d592f..43e1f0b7a 100644 --- a/cmd/restic/main.go +++ b/cmd/restic/main.go @@ -173,13 +173,11 @@ func main() { version, runtime.Version(), runtime.GOOS, runtime.GOARCH) globalOptions := GlobalOptions{ - stdout: os.Stdout, backends: collectBackends(), } func() { term, cancel := termstatus.Setup(os.Stdin, os.Stdout, os.Stderr, globalOptions.Quiet) defer cancel() - globalOptions.stdout = termstatus.WrapStdout(term) globalOptions.term = term ctx := createGlobalContext(os.Stderr) err = newRootCommand(&globalOptions).ExecuteContext(ctx) diff --git a/internal/ui/mock.go b/internal/ui/mock.go index edc9050f9..fe18e647a 100644 --- a/internal/ui/mock.go +++ b/internal/ui/mock.go @@ -40,6 +40,10 @@ func (m *MockTerminal) ReadPassword(_ context.Context, _ string) (string, error) return "password", nil } +func (m *MockTerminal) OutputWriter() io.Writer { + return nil +} + func (m *MockTerminal) OutputRaw() io.Writer { return nil } diff --git a/internal/ui/terminal.go b/internal/ui/terminal.go index c53de7bf2..e3052c5e1 100644 --- a/internal/ui/terminal.go +++ b/internal/ui/terminal.go @@ -16,9 +16,12 @@ type Terminal interface { SetStatus(lines []string) // CanUpdateStatus returns true if the terminal can update the status lines. CanUpdateStatus() bool + InputRaw() io.ReadCloser InputIsTerminal() bool ReadPassword(ctx context.Context, prompt string) (string, error) + + OutputWriter() io.Writer // OutputRaw returns the output writer. Should only be used if there is no // other option. Must not be used in combination with Print, Error, SetStatus // or any other method that writes to the terminal. diff --git a/internal/ui/termstatus/status.go b/internal/ui/termstatus/status.go index 5ee21eb37..3d9b2d857 100644 --- a/internal/ui/termstatus/status.go +++ b/internal/ui/termstatus/status.go @@ -30,6 +30,9 @@ type Terminal struct { outputIsTerminal bool canUpdateStatus bool + outputWriter io.WriteCloser + outputWriterOnce sync.Once + // will be closed when the goroutine which runs Run() terminates, so it'll // yield a default value immediately closed chan struct{} @@ -73,6 +76,9 @@ func Setup(stdin io.ReadCloser, stdout, stderr io.Writer, quiet bool) (*Terminal }() return term, func() { + if term.outputWriter != nil { + _ = term.outputWriter.Close() + } // shutdown termstatus cancel() wg.Wait() @@ -158,6 +164,13 @@ func (t *Terminal) CanUpdateStatus() bool { return t.canUpdateStatus } +func (t *Terminal) OutputWriter() io.Writer { + t.outputWriterOnce.Do(func() { + t.outputWriter = newLineWriter(t.Print) + }) + return t.outputWriter +} + // OutputRaw returns the output writer. Should only be used if there is no // other option. Must not be used in combination with Print, Error, SetStatus // or any other method that writes to the terminal. diff --git a/internal/ui/termstatus/stdio_wrapper.go b/internal/ui/termstatus/stdio_wrapper.go index 384357d98..662e3b848 100644 --- a/internal/ui/termstatus/stdio_wrapper.go +++ b/internal/ui/termstatus/stdio_wrapper.go @@ -6,12 +6,6 @@ import ( "sync" ) -// WrapStdout returns line-buffering replacements for os.Stdout. -// On Close, the remaining bytes are written, followed by a line break. -func WrapStdout(term *Terminal) (stdout io.WriteCloser) { - return newLineWriter(term.Print) -} - type lineWriter struct { m sync.Mutex buf bytes.Buffer