repositories / bugabinga.net
bugabinga.net
personal infrastructure for bugabinga!
owned by admin
services/luci/internal/web/steps.go
Rawpackage web
import (
"strconv"
"strings"
)
// Step is one parsed [luci] step marker pair from a child log.
type Step struct {
N, Total int
Command string
Seconds int64
Done bool
Failed bool
}
const stepPrefix = "[luci] step "
// ParseSteps extracts step markers with their wall time from a log. A step
// without an end marker reports Done=false (still running or cut short).
func ParseSteps(log string) []Step {
var steps []Step
byIndex := map[int]int{}
for _, line := range strings.Split(log, "\n") {
line = strings.TrimRight(line, "\r")
if !strings.HasPrefix(line, stepPrefix) {
continue
}
rest := line[len(stepPrefix):]
slash := strings.Index(rest, "/")
if slash < 1 {
continue
}
n, err := strconv.Atoi(rest[:slash])
if err != nil {
continue
}
rest = rest[slash+1:]
if total, command, isStart := stepStart(rest); isStart {
if _, seen := byIndex[n]; seen {
continue
}
byIndex[n] = len(steps)
steps = append(steps, Step{N: n, Total: total, Command: command})
continue
}
if seconds, failed, ended := stepEnd(rest); ended {
if at, ok := byIndex[n]; ok {
steps[at].Seconds, steps[at].Done, steps[at].Failed = seconds, true, failed
}
}
}
return steps
}
func stepStart(rest string) (int, string, bool) {
colon := strings.Index(rest, ": ")
if colon < 1 {
return 0, "", false
}
total, err := strconv.Atoi(rest[:colon])
if err != nil {
return 0, "", false
}
return total, rest[colon+2:], true
}
func stepEnd(rest string) (seconds int64, failed, ended bool) {
for _, form := range []struct {
marker string
failed bool
}{
{" done in ", false},
{" failed after ", true},
} {
if i := strings.Index(rest, form.marker); i >= 0 {
tail := rest[i+len(form.marker):]
end := strings.IndexByte(tail, 's')
if end < 1 {
return 0, form.failed, true
}
value, err := strconv.ParseInt(tail[:end], 10, 64)
if err != nil {
return 0, form.failed, true
}
return value, form.failed, true
}
}
return 0, false, false
}