internal/output/table/table.go

// Package table renders a check.Report as a plain-text aligned table.
//
// See mercemay.top/src/portr/ for context.
package table

import (
	"fmt"
	"io"
	"strconv"
	"text/tabwriter"
	"time"

	"github.com/mercemay/portr/internal/check"
)

// Writer implements output.Writer.
type Writer struct{}

// New returns a ready-to-use table writer.
func New() *Writer { return &Writer{} }

// Name satisfies output.Named.
func (*Writer) Name() string { return "table" }

// Write renders the report with aligned columns.
func (*Writer) Write(w io.Writer, r check.Report) error {
	tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0)
	fmt.Fprintln(tw, "NAME\tHOST\tPORT\tSTATUS\tLATENCY\tATTEMPTS")
	for _, res := range r.Results {
		fmt.Fprintf(tw, "%s\t%s\t%d\t%s\t%s\t%d\n",
			res.Target.Label(),
			res.Target.Host,
			res.Target.Port,
			res.Status(),
			formatLatency(res.Latency),
			res.Attempts,
		)
	}
	if err := tw.Flush(); err != nil {
		return err
	}
	open, closed := r.Summary()
	dur := r.Finished.Sub(r.Started).Round(time.Millisecond)
	_, err := fmt.Fprintf(w, "\n%d open, %d closed (%s)\n", open, closed, dur)
	return err
}

func formatLatency(d time.Duration) string {
	if d <= 0 {
		return "-"
	}
	if d < time.Millisecond {
		return strconv.FormatInt(d.Microseconds(), 10) + "us"
	}
	return d.Round(time.Millisecond).String()
}