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 }