diff --git a/cmd/oras/internal/display/handler.go b/cmd/oras/internal/display/handler.go index 24d52ac44..c3c931609 100644 --- a/cmd/oras/internal/display/handler.go +++ b/cmd/oras/internal/display/handler.go @@ -32,7 +32,7 @@ func NewPushHandler(format string, tty *os.File, out io.Writer, verbose bool) (s if tty != nil { statusHandler = status.NewTTYPushHandler(tty) } else if format == "" { - statusHandler = status.NewTextPushHandler(verbose) + statusHandler = status.NewTextPushHandler(out, verbose) } else { statusHandler = status.NewDiscardHandler() } @@ -56,7 +56,7 @@ func NewAttachHandler(format string, tty *os.File, out io.Writer, verbose bool) if tty != nil { statusHandler = status.NewTTYAttachHandler(tty) } else if format == "" { - statusHandler = status.NewTextAttachHandler(verbose) + statusHandler = status.NewTextAttachHandler(out, verbose) } else { statusHandler = status.NewDiscardHandler() } diff --git a/cmd/oras/internal/display/status/print.go b/cmd/oras/internal/display/status/print.go index 85fcd21e9..950b54e2b 100644 --- a/cmd/oras/internal/display/status/print.go +++ b/cmd/oras/internal/display/status/print.go @@ -19,6 +19,7 @@ import ( "context" "fmt" "io" + "os" "sync" ocispec "github.com/opencontainers/image-spec/specs-go/v1" @@ -27,28 +28,30 @@ import ( "oras.land/oras-go/v2/registry" ) -var printLock sync.Mutex - // PrintFunc is the function type returned by StatusPrinter. type PrintFunc func(ocispec.Descriptor) error -// Print objects to display concurrent-safely. -func Print(a ...any) error { - printLock.Lock() - defer printLock.Unlock() - _, err := fmt.Println(a...) - return err +// Printer prints for status handlers. +type Printer struct { + out io.Writer + lock sync.Mutex } -// StatusPrinter returns a tracking function for transfer status. -func StatusPrinter(status string, verbose bool) PrintFunc { - return func(desc ocispec.Descriptor) error { - return PrintStatus(desc, status, verbose) - } +// NewPrinter creates a new Printer. +func NewPrinter(out io.Writer) *Printer { + return &Printer{out: out} +} + +// Println prints objects concurrent-safely with newline. +func (p *Printer) Println(a ...any) error { + p.lock.Lock() + defer p.lock.Unlock() + _, err := fmt.Fprintln(p.out, a...) + return err } // PrintStatus prints transfer status. -func PrintStatus(desc ocispec.Descriptor, status string, verbose bool) error { +func (p *Printer) PrintStatus(desc ocispec.Descriptor, status string, verbose bool) error { name, ok := desc.Annotations[ocispec.AnnotationTitle] if !ok { // no status for unnamed content @@ -57,7 +60,14 @@ func PrintStatus(desc ocispec.Descriptor, status string, verbose bool) error { } name = desc.MediaType } - return Print(status, ShortDigest(desc), name) + return p.Println(status, ShortDigest(desc), name) +} + +// StatusPrinter returns a tracking function for transfer status. +func (p *Printer) StatusPrinter(status string, verbose bool) PrintFunc { + return func(desc ocispec.Descriptor) error { + return p.PrintStatus(desc, status, verbose) + } } // PrintSuccessorStatus prints transfer status of successors. @@ -78,18 +88,6 @@ func PrintSuccessorStatus(ctx context.Context, desc ocispec.Descriptor, fetcher return nil } -// NewTagStatusPrinter creates a wrapper type for printing tag status. -func NewTagStatusPrinter(target oras.Target) oras.Target { - if repo, ok := target.(registry.Repository); ok { - return &tagManifestStatusForRepo{ - Repository: repo, - } - } - return &tagManifestStatusForTarget{ - Target: target, - } -} - // NewTagStatusHintPrinter creates a wrapper type for printing // tag status and hint. func NewTagStatusHintPrinter(target oras.Target, refPrefix string) oras.Target { @@ -148,3 +146,34 @@ func (p *tagManifestStatusForTarget) Tag(ctx context.Context, desc ocispec.Descr } return Print("Tagged", reference) } + +// NewTagStatusPrinter creates a wrapper type for printing tag status. +func NewTagStatusPrinter(target oras.Target) oras.Target { + if repo, ok := target.(registry.Repository); ok { + return &tagManifestStatusForRepo{ + Repository: repo, + } + } + return &tagManifestStatusForTarget{ + Target: target, + } +} + +// printer is used by the code being deprecated. Related functions should be +// removed when no-longer referenced. +var printer = NewPrinter(os.Stdout) + +// Print objects to display concurrent-safely. +func Print(a ...any) error { + return printer.Println(a...) +} + +// StatusPrinter returns a tracking function for transfer status. +func StatusPrinter(status string, verbose bool) PrintFunc { + return printer.StatusPrinter(status, verbose) +} + +// PrintStatus prints transfer status. +func PrintStatus(desc ocispec.Descriptor, status string, verbose bool) error { + return printer.PrintStatus(desc, status, verbose) +} diff --git a/cmd/oras/internal/display/status/text.go b/cmd/oras/internal/display/status/text.go index a62c2a313..954f215ae 100644 --- a/cmd/oras/internal/display/status/text.go +++ b/cmd/oras/internal/display/status/text.go @@ -17,7 +17,7 @@ package status import ( "context" - "fmt" + "io" "sync" ocispec "github.com/opencontainers/image-spec/specs-go/v1" @@ -28,12 +28,14 @@ import ( // TextPushHandler handles text status output for push events. type TextPushHandler struct { verbose bool + printer *Printer } // NewTextPushHandler returns a new handler for push command. -func NewTextPushHandler(verbose bool) PushHandler { +func NewTextPushHandler(out io.Writer, verbose bool) PushHandler { return &TextPushHandler{ verbose: verbose, + printer: NewPrinter(out), } } @@ -42,14 +44,12 @@ func (ph *TextPushHandler) OnFileLoading(name string) error { if !ph.verbose { return nil } - _, err := fmt.Println("Preparing", name) - return err + return ph.printer.Println("Preparing", name) } // OnEmptyArtifact is called when an empty artifact is being uploaded. func (ph *TextPushHandler) OnEmptyArtifact() error { - _, err := fmt.Println("Uploading empty artifact") - return err + return ph.printer.Println("Uploading empty artifact") } // TrackTarget returns a tracked target. @@ -68,21 +68,21 @@ func (ph *TextPushHandler) UpdateCopyOptions(opts *oras.CopyGraphOptions, fetche committed := &sync.Map{} opts.OnCopySkipped = func(ctx context.Context, desc ocispec.Descriptor) error { committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle]) - return PrintStatus(desc, promptExists, ph.verbose) + return ph.printer.PrintStatus(desc, promptExists, ph.verbose) } opts.PreCopy = func(ctx context.Context, desc ocispec.Descriptor) error { - return PrintStatus(desc, promptUploading, ph.verbose) + return ph.printer.PrintStatus(desc, promptUploading, ph.verbose) } opts.PostCopy = func(ctx context.Context, desc ocispec.Descriptor) error { committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle]) - if err := PrintSuccessorStatus(ctx, desc, fetcher, committed, StatusPrinter(promptSkipped, ph.verbose)); err != nil { + if err := PrintSuccessorStatus(ctx, desc, fetcher, committed, ph.printer.StatusPrinter(promptSkipped, ph.verbose)); err != nil { return err } - return PrintStatus(desc, promptUploaded, ph.verbose) + return ph.printer.PrintStatus(desc, promptUploaded, ph.verbose) } } // NewTextAttachHandler returns a new handler for attach command. -func NewTextAttachHandler(verbose bool) AttachHandler { - return NewTextPushHandler(verbose) +func NewTextAttachHandler(out io.Writer, verbose bool) AttachHandler { + return NewTextPushHandler(out, verbose) }