package main import ( "fmt" "io/fs" "os" "os/exec" "path/filepath" "sort" "strings" "time" ) // Color returns a HTML color code between green and yellow based on the // number of days that passed since ds. func (ds DateString) Color() string { date, _ := time.Parse("2006-01-02T15:04:05Z", string(ds)) days := int(time.Since(date).Hours() / 24) if days > 255 { days = 255 } return fmt.Sprintf("#%02xff00", days) } func fetchLogs(dirs chan<- NamedFS) { err := fs.WalkDir(bsdirFS, ".", func(path string, d fs.DirEntry, err error) error { if err != nil { return err } if path[0] == '.' { return nil } if d.IsDir() && len(strings.Split(path, string(filepath.Separator))) == 4 { dirs <- NamedFS{ FS: bsdirFS, Name: path, } } return nil }) if err != nil { fmt.Fprintf(os.Stderr, "Reading logs failed: %v\n", err) } close(dirs) } func collectLogs(dirs <-chan NamedFS) { data.Logs = make(map[Timeframe][]Log) data.VendorBoardDate = make(map[string]DateString) data.VendorBoardReferenced = make(map[string]bool) timeframes := make(map[Timeframe]bool) gitcache := make(map[string]string) for dir := range dirs { upstream := "" revB, err := fs.ReadFile(dir.FS, filepath.Join(dir.Name, "revision.txt")) if err != nil { continue } rev := string(revB) skipDir := false for _, line := range strings.Split(rev, "\n") { item := strings.SplitN(line, ":", 2) if len(item) != 2 { // This is an error, but let's try to extract // as much value out of revision.txt files as // possible, even if some lines are erroneous. continue } if item[0] == "Upstream revision" { upstream = strings.TrimSpace(item[1]) // tried using go-git, but its resolver // couldn't expand short hashes despite the // docs claiming that it can. if val, ok := gitcache[upstream]; ok { upstream = val } else { res, err := exec.Command("/usr/bin/git", "-C", cbdir, "log", "-n1", "--format=%H", upstream).Output() if err != nil { fmt.Fprintf(os.Stderr, "revision %s not found \n", upstream) skipDir = true break } gitcache[upstream] = strings.TrimSpace(string(res)) upstream = gitcache[upstream] } } } if skipDir { continue } rawFiles, err := fs.Glob(dir.FS, filepath.Join(dir.Name, "*")) if err != nil { fmt.Fprintf(os.Stderr, "Could not fetch log data, skipping: %v\n", err) continue } pieces := strings.Split(dir.Name, string(filepath.Separator)) if len(pieces) < 4 { fmt.Fprintf(os.Stderr, "log directory %s is malformed, skipping\n", dir.Name) continue } vendorBoard := strings.Join(pieces[:2], "/") // TODO: these need to become "second to last" and "last" item // but only after compatibility to the current system has been ensured. dateTimePath := pieces[3] dateTime, err := time.Parse(time.RFC3339, strings.ReplaceAll(dateTimePath, "_", ":")) if err != nil { fmt.Fprintf(os.Stderr, "Could not parse timestamp from %s: %v\n", dir.Name, err) continue } dateTimeNormal := dateTime.UTC().Format("2006-01-02T15:04:05Z") dateTimeHuman := dateTime.UTC().Format(time.UnixDate) tfYear, tfWeek := dateTime.ISOWeek() timeframe := Timeframe(fmt.Sprintf("%dW%02d", tfYear, tfWeek)) if !timeframes[timeframe] { timeframes[timeframe] = true data.Timeframes = append(data.Timeframes, timeframe) data.Logs[timeframe] = []Log{} } files := []Path{} l := len(dir.Name) + 1 for _, file := range rawFiles { if file[l:] == "revision.txt" { continue } files = append(files, Path{ Path: dir.Name + "/", Basename: file[l:], }) } data.Logs[timeframe] = append(data.Logs[timeframe], Log{ VendorBoard: vendorBoard, Time: dateTimeNormal, TimeReadable: dateTimeHuman, Upstream: upstream, Files: files, }) } sort.Slice(data.Timeframes, func(i, j int) bool { // reverse sort return data.Timeframes[i] > data.Timeframes[j] }) for bi := range data.Logs { bucket := data.Logs[bi] sort.Slice(data.Logs[bi], func(i, j int) bool { if bucket[i].Time == bucket[j].Time { return (bucket[i].VendorBoard > bucket[j].VendorBoard) } return (bucket[i].Time > bucket[j].Time) }) } for _, ts := range data.Timeframes { for li, l := range data.Logs[ts] { if _, match := data.VendorBoardDate[l.VendorBoard]; match { continue } data.VendorBoardDate[l.VendorBoard] = DateString(l.Time) data.Logs[ts][li].Reference = true } } }