Export UI handlers for reuse in other tools (#113)

* export UI handlers for reuse in other tools

* bump jotframe to fix cursor issues at frame close
This commit is contained in:
Alex Goodman
2020-07-30 15:16:58 -04:00
committed by GitHub
parent 857f41b04b
commit 271ba35c85
10 changed files with 108 additions and 59 deletions
@@ -1,4 +1,4 @@
package etui
package common
import (
"strings"
+16 -27
View File
@@ -6,9 +6,10 @@ import (
"os"
"sync"
stereoscopeEvent "github.com/anchore/stereoscope/pkg/event"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/ui/common"
"github.com/anchore/syft/ui"
"github.com/anchore/syft/internal/log"
syftEvent "github.com/anchore/syft/syft/event"
"github.com/wagoodman/go-partybus"
"github.com/wagoodman/jotframe/pkg/frame"
@@ -47,6 +48,7 @@ func OutputToEphemeralTUI(workerErrs <-chan error, subscription *partybus.Subscr
var isClosed bool
defer func() {
if !isClosed {
fr.Close()
frame.Close()
}
}()
@@ -55,6 +57,7 @@ func OutputToEphemeralTUI(workerErrs <-chan error, subscription *partybus.Subscr
var wg = &sync.WaitGroup{}
events := subscription.Events()
ctx := context.Background()
syftUIHandler := ui.NewHandler()
eventLoop:
for {
@@ -67,41 +70,27 @@ eventLoop:
if !ok {
break eventLoop
}
switch e.Type {
case syftEvent.AppUpdateAvailable:
err = appUpdateAvailableHandler(ctx, fr, e, wg)
if err != nil {
log.Errorf("unable to show AppUpdateAvailable event: %+v", err)
switch {
case syftUIHandler.RespondsTo(e):
if err = syftUIHandler.Handle(ctx, fr, e, wg); err != nil {
log.Errorf("unable to show %s event: %+v", e.Type, err)
}
case stereoscopeEvent.ReadImage:
err = imageReadHandler(ctx, fr, e, wg)
if err != nil {
log.Errorf("unable to show ReadImage event: %+v", err)
case e.Type == syftEvent.AppUpdateAvailable:
if err = appUpdateAvailableHandler(ctx, fr, e, wg); err != nil {
log.Errorf("unable to show %s event: %+v", e.Type, err)
}
case stereoscopeEvent.FetchImage:
err = imageFetchHandler(ctx, fr, e, wg)
if err != nil {
log.Errorf("unable to show FetchImage event: %+v", err)
}
case syftEvent.CatalogerStarted:
err = catalogerStartedHandler(ctx, fr, e, wg)
if err != nil {
log.Errorf("unable to show CatalogerStarted event: %+v", err)
}
case syftEvent.CatalogerFinished:
case e.Type == syftEvent.CatalogerFinished:
// we may have other background processes still displaying progress, wait for them to
// finish before discontinuing dynamic content and showing the final report
wg.Wait()
fr.Close()
frame.Close()
isClosed = true
fmt.Println()
err := common.CatalogerFinishedHandler(e)
if err != nil {
log.Errorf("unable to show CatalogerFinished event: %+v", err)
if err := common.CatalogerFinishedHandler(e); err != nil {
log.Errorf("unable to show %s event: %+v", e.Type, err)
}
// this is the last expected event
-162
View File
@@ -1,162 +0,0 @@
package etui
import (
"context"
"fmt"
"io"
"sync"
"time"
stereoEventParsers "github.com/anchore/stereoscope/pkg/event/parsers"
syftEventParsers "github.com/anchore/syft/syft/event/parsers"
"github.com/gookit/color"
"github.com/wagoodman/go-partybus"
"github.com/wagoodman/go-progress"
"github.com/wagoodman/go-progress/format"
"github.com/wagoodman/jotframe/pkg/frame"
)
const maxBarWidth = 50
const statusSet = SpinnerDotSet // SpinnerCircleOutlineSet
const completedStatus = "✔" //"●"
const tileFormat = color.Bold
const statusTitleTemplate = " %s %-28s "
var auxInfoFormat = color.HEX("#777777")
func startProcess() (format.Simple, *Spinner) {
width, _ := frame.GetTerminalSize()
barWidth := int(0.25 * float64(width))
if barWidth > maxBarWidth {
barWidth = maxBarWidth
}
formatter := format.NewSimpleWithTheme(barWidth, format.HeavyNoBarTheme, format.ColorCompleted, format.ColorTodo)
spinner := NewSpinner(statusSet)
return formatter, &spinner
}
func imageFetchHandler(ctx context.Context, fr *frame.Frame, event partybus.Event, wg *sync.WaitGroup) error {
_, prog, err := stereoEventParsers.ParseFetchImage(event)
if err != nil {
return fmt.Errorf("bad FetchImage event: %w", err)
}
line, err := fr.Append()
if err != nil {
return err
}
wg.Add(1)
go func() {
defer wg.Done()
formatter, spinner := startProcess()
stream := progress.Stream(ctx, prog, 150*time.Millisecond)
title := tileFormat.Sprint("Fetching image...")
for p := range stream {
progStr, err := formatter.Format(p)
spin := color.Magenta.Sprint(spinner.Next())
if err != nil {
_, _ = io.WriteString(line, fmt.Sprintf("Error: %+v", err))
} else {
auxInfo := auxInfoFormat.Sprintf("[%s]", prog.Stage())
_, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate+"%s %s", spin, title, progStr, auxInfo))
}
}
spin := color.Green.Sprint(completedStatus)
title = tileFormat.Sprint("Fetched image")
_, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate, spin, title))
}()
return err
}
func imageReadHandler(ctx context.Context, fr *frame.Frame, event partybus.Event, wg *sync.WaitGroup) error {
_, prog, err := stereoEventParsers.ParseReadImage(event)
if err != nil {
return fmt.Errorf("bad ReadImage event: %w", err)
}
line, err := fr.Append()
if err != nil {
return err
}
wg.Add(1)
go func() {
defer wg.Done()
formatter, spinner := startProcess()
stream := progress.Stream(ctx, prog, 150*time.Millisecond)
title := tileFormat.Sprint("Reading image...")
for p := range stream {
progStr, err := formatter.Format(p)
spin := color.Magenta.Sprint(spinner.Next())
if err != nil {
_, _ = io.WriteString(line, fmt.Sprintf("Error: %+v", err))
} else {
_, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate+"%s", spin, title, progStr))
}
}
spin := color.Green.Sprint(completedStatus)
title = tileFormat.Sprint("Read image")
_, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate, spin, title))
}()
return nil
}
func catalogerStartedHandler(ctx context.Context, fr *frame.Frame, event partybus.Event, wg *sync.WaitGroup) error {
monitor, err := syftEventParsers.ParseCatalogerStarted(event)
if err != nil {
return fmt.Errorf("bad CatalogerStarted event: %w", err)
}
line, err := fr.Append()
if err != nil {
return err
}
wg.Add(1)
go func() {
defer wg.Done()
_, spinner := startProcess()
stream := progress.StreamMonitors(ctx, []progress.Monitorable{monitor.FilesProcessed, monitor.PackagesDiscovered}, 50*time.Millisecond)
title := tileFormat.Sprint("Cataloging image...")
for p := range stream {
spin := color.Magenta.Sprint(spinner.Next())
auxInfo := auxInfoFormat.Sprintf("[packages %d]", p[1])
_, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate+"%s", spin, title, auxInfo))
}
spin := color.Green.Sprint(completedStatus)
title = tileFormat.Sprint("Cataloged image")
auxInfo := auxInfoFormat.Sprintf("[%d packages]", monitor.PackagesDiscovered.Current())
_, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate+"%s", spin, title, auxInfo))
}()
return nil
}
func appUpdateAvailableHandler(_ context.Context, fr *frame.Frame, event partybus.Event, _ *sync.WaitGroup) error {
newVersion, err := syftEventParsers.ParseAppUpdateAvailable(event)
if err != nil {
return fmt.Errorf("bad AppUpdateAvailable event: %w", err)
}
line, err := fr.Prepend()
if err != nil {
return err
}
message := color.Magenta.Sprintf("New Update Available: %s", newVersion)
_, _ = io.WriteString(line, message)
return nil
}
@@ -0,0 +1,31 @@
package etui
import (
"context"
"fmt"
"io"
"sync"
"github.com/anchore/syft/internal"
syftEventParsers "github.com/anchore/syft/syft/event/parsers"
"github.com/gookit/color"
"github.com/wagoodman/go-partybus"
"github.com/wagoodman/jotframe/pkg/frame"
)
func appUpdateAvailableHandler(_ context.Context, fr *frame.Frame, event partybus.Event, _ *sync.WaitGroup) error {
newVersion, err := syftEventParsers.ParseAppUpdateAvailable(event)
if err != nil {
return fmt.Errorf("bad AppUpdateAvailable event: %w", err)
}
line, err := fr.Prepend()
if err != nil {
return err
}
message := color.Magenta.Sprintf("New version of %s is available: %s", internal.ApplicationName, newVersion)
_, _ = io.WriteString(line, message)
return nil
}