github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/cmd/syft/internal/ui/post_ui_event_writer.go (about)

     1  package ui
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"strings"
     7  
     8  	"github.com/charmbracelet/lipgloss"
     9  	"github.com/hashicorp/go-multierror"
    10  	"github.com/wagoodman/go-partybus"
    11  
    12  	"github.com/anchore/syft/internal/log"
    13  	"github.com/anchore/syft/syft/event"
    14  	"github.com/anchore/syft/syft/event/parsers"
    15  )
    16  
    17  type postUIEventWriter struct {
    18  	handles []postUIHandle
    19  }
    20  
    21  type postUIHandle struct {
    22  	respectQuiet bool
    23  	event        partybus.EventType
    24  	writer       io.Writer
    25  	dispatch     eventWriter
    26  }
    27  
    28  type eventWriter func(io.Writer, ...partybus.Event) error
    29  
    30  func newPostUIEventWriter(stdout, stderr io.Writer) *postUIEventWriter {
    31  	return &postUIEventWriter{
    32  		handles: []postUIHandle{
    33  			{
    34  				event:        event.CLIReport,
    35  				respectQuiet: false,
    36  				writer:       stdout,
    37  				dispatch:     writeReports,
    38  			},
    39  			{
    40  				event:        event.CLINotification,
    41  				respectQuiet: true,
    42  				writer:       stderr,
    43  				dispatch:     writeNotifications,
    44  			},
    45  			{
    46  				event:        event.CLIAppUpdateAvailable,
    47  				respectQuiet: true,
    48  				writer:       stderr,
    49  				dispatch:     writeAppUpdate,
    50  			},
    51  		},
    52  	}
    53  }
    54  
    55  func (w postUIEventWriter) write(quiet bool, events ...partybus.Event) error {
    56  	var errs error
    57  	for _, h := range w.handles {
    58  		if quiet && h.respectQuiet {
    59  			continue
    60  		}
    61  
    62  		for _, e := range events {
    63  			if e.Type != h.event {
    64  				continue
    65  			}
    66  
    67  			if err := h.dispatch(h.writer, e); err != nil {
    68  				errs = multierror.Append(errs, err)
    69  			}
    70  		}
    71  	}
    72  	return errs
    73  }
    74  
    75  func writeReports(writer io.Writer, events ...partybus.Event) error {
    76  	var reports []string
    77  	for _, e := range events {
    78  		_, report, err := parsers.ParseCLIReport(e)
    79  		if err != nil {
    80  			log.WithFields("error", err).Warn("failed to gather final report")
    81  			continue
    82  		}
    83  
    84  		// remove all whitespace padding from the end of the report
    85  		reports = append(reports, strings.TrimRight(report, "\n ")+"\n")
    86  	}
    87  
    88  	// prevent the double new-line at the end of the report
    89  	report := strings.Join(reports, "\n")
    90  
    91  	if _, err := fmt.Fprint(writer, report); err != nil {
    92  		return fmt.Errorf("failed to write final report to stdout: %w", err)
    93  	}
    94  	return nil
    95  }
    96  
    97  func writeNotifications(writer io.Writer, events ...partybus.Event) error {
    98  	// 13 = high intensity magenta (ANSI 16 bit code)
    99  	style := lipgloss.NewStyle().Foreground(lipgloss.Color("13"))
   100  
   101  	for _, e := range events {
   102  		_, notification, err := parsers.ParseCLINotification(e)
   103  		if err != nil {
   104  			log.WithFields("error", err).Warn("failed to parse notification")
   105  			continue
   106  		}
   107  
   108  		if _, err := fmt.Fprintln(writer, style.Render(notification)); err != nil {
   109  			// don't let this be fatal
   110  			log.WithFields("error", err).Warn("failed to write final notifications")
   111  		}
   112  	}
   113  	return nil
   114  }
   115  
   116  func writeAppUpdate(writer io.Writer, events ...partybus.Event) error {
   117  	// 13 = high intensity magenta (ANSI 16 bit code) + italics
   118  	style := lipgloss.NewStyle().Foreground(lipgloss.Color("13")).Italic(true)
   119  
   120  	for _, e := range events {
   121  		updateCheck, err := parsers.ParseCLIAppUpdateAvailable(e)
   122  		if err != nil {
   123  			log.WithFields("error", err).Warn("failed to parse app update notification")
   124  			continue
   125  		}
   126  
   127  		if updateCheck.Current == updateCheck.New {
   128  			log.Tracef("update check event with identical versions: %s", updateCheck.Current)
   129  			continue
   130  		}
   131  
   132  		notice := fmt.Sprintf("A newer version of syft is available for download: %s (installed version is %s)", updateCheck.New, updateCheck.Current)
   133  
   134  		if _, err := fmt.Fprintln(writer, style.Render(notice)); err != nil {
   135  			// don't let this be fatal
   136  			log.WithFields("error", err).Warn("failed to write app update notification")
   137  		}
   138  	}
   139  	return nil
   140  }