Something went wrong. Try again.
Tangled-compatible personal CI server based on Nix
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146// Package render prints run events for a terminal: `ci run` live and// `ci view` afterwards render the same events.ndjson the same way.package render
import ( "fmt" "io" "os" "strings" "sync" "time"
"git.kilimanjaro.io/ci/internal/lex")
// Terminal renders events to w.type Terminal struct { mu sync.Mutex w io.Writer color bool starts map[int]time.Time}
// New returns a renderer for w, with colour if w is a terminal and NO_COLOR// is unset.func New(w io.Writer) *Terminal { color := false if f, ok := w.(*os.File); ok && os.Getenv("NO_COLOR") == "" { if fi, err := f.Stat(); err == nil && fi.Mode()&os.ModeCharDevice != 0 { color = true } } return &Terminal{w: w, color: color, starts: map[int]time.Time{}}}
func (t *Terminal) paint(code, s string) string { if !t.color { return s } return "\x1b[" + code + "m" + s + "\x1b[0m"}
func (t *Terminal) statusColor(status string) string { switch status { case lex.Success: return "32" case lex.Warning, "paused": return "33" case lex.Failure, "cancelled": return "31" } return "0"}
// Event renders one event.func (t *Terminal) Event(ev lex.RunEvent) { t.mu.Lock() defer t.mu.Unlock() ts, _ := time.Parse(time.RFC3339Nano, ev.Time) switch ev.Kind { case lex.KindRun: if ev.Status == "running" { fmt.Fprintf(t.w, "%s %s\n", t.paint("1", "ci"), ev.Content) return } line := fmt.Sprintf("%s %s", t.paint("1", "ci"), t.paint(t.statusColor(ev.Status), ev.Status)) if ev.Content != "" { line += ": " + ev.Content } fmt.Fprintln(t.w, line) case lex.KindCheckout: fmt.Fprintf(t.w, " checkout %s @ %s -> %s\n", ev.Repo, short(ev.Rev), ev.Path) case lex.KindStart: t.starts[ev.Attempt] = ts fmt.Fprintf(t.w, "%s %s %s\n", t.paint("1;36", "▶"), t.paint("1", ev.Action), t.paint("2", firstLine(ev.Command))) case lex.KindLog: prefix := t.paint("2", "│ ") if ev.Stream == "stderr" { prefix = t.paint("33", "│ ") } fmt.Fprintln(t.w, prefix+ev.Content) case lex.KindEnd: mark := "✔" switch ev.Status { case lex.Failure: mark = "✘" case lex.Warning: mark = "!" } took := "" if s, ok := t.starts[ev.Attempt]; ok && !ts.IsZero() { took = " " + ts.Sub(s).Round(100*time.Millisecond).String() } code := "" if ev.ExitCode != nil && *ev.ExitCode != 0 { code = fmt.Sprintf(" exit %d", *ev.ExitCode) } fmt.Fprintf(t.w, "%s %s %s%s%s\n", t.paint(t.statusColor(ev.Status), mark), ev.Action, t.paint(t.statusColor(ev.Status), ev.Status), code, t.paint("2", took)) case lex.KindResult: if r := ev.Result; r != nil { if r.Summary != "" { for _, l := range strings.Split(strings.TrimRight(r.Summary, "\n"), "\n") { fmt.Fprintln(t.w, " "+l) } } for _, a := range r.Attachments { label := a.Label if label == "" { label = a.Path } fmt.Fprintf(t.w, " attachment: %s (%s)\n", label, a.Path) } } case lex.KindTransition: if tr := ev.Transition; tr != nil && tr.To != "" && tr.Reason != lex.ReasonDefault && tr.Reason != lex.ReasonStart { fmt.Fprintf(t.w, "%s %s (%s)\n", t.paint("2", "→"), tr.To, tr.Reason) } case lex.KindGate: if g := ev.Gate; g != nil { fmt.Fprintf(t.w, "%s gate %s", t.paint("1;33", "⏸"), g.State) if g.Choice != "" { fmt.Fprintf(t.w, ": %s", g.Choice) } if g.Actor != "" { fmt.Fprintf(t.w, " by %s", g.Actor) } fmt.Fprintln(t.w) } }}
func firstLine(s string) string { s = strings.TrimSpace(s) if i := strings.IndexByte(s, '\n'); i >= 0 { return s[:i] + " …" } return s}
func short(s string) string { if len(s) > 12 { return s[:12] } return s}