package bakeoff import ( "encoding/json" "errors" "io/fs" "os" "path/filepath" "slices" ) // ResultsDir is where results/.json live, relative to the repo root. var ResultsDir = "results" // Readability is Fable's blind rubric score (per-field median of 3 runs). type Readability struct { Naming, Structure, Traceability, Errors, Comments, Overall float64 Notes string } // Behaviour is one project's run of the shared suite. type Behaviour struct{ Passed, Failed, Skipped int } // AppResult is everything the report shows for one app. type AppResult struct { Slug, Language, Status, URL, Version, Stack string Source string BehaviourJSOn, BehaviourJSOff Behaviour TTFBEmpty, TTFB50 Stats Wake []float64 Load []LoadResult Weight Weight Memory Memory Code CodeStats Build Build TokensBuild, TokensFix, TokensSecurity Tokens TokensReview Tokens Readability Readability ReviewFindings []string Notes []string } // LoadIn reads dir/.json, or returns an empty result for slug. func LoadIn(dir, slug string) (AppResult, error) { r := AppResult{Slug: slug} b, err := os.ReadFile(filepath.Join(dir, slug+".json")) if errors.Is(err, fs.ErrNotExist) { return r, nil } if err != nil { return r, err } err = json.Unmarshal(b, &r) return r, err } // UpdateIn applies f to dir/.json and writes it back. func UpdateIn(dir, slug string, f func(*AppResult)) error { r, err := LoadIn(dir, slug) if err != nil { return err } f(&r) r.Slug = slug b, err := json.MarshalIndent(r, "", " ") if err != nil { return err } if err := os.MkdirAll(dir, 0o755); err != nil { return err } return os.WriteFile(filepath.Join(dir, slug+".json"), append(b, '\n'), 0o644) } // Update applies f to results/.json. func Update(slug string, f func(*AppResult)) error { return UpdateIn(ResultsDir, slug, f) } // Record is a partial update of an app's identity fields; empty fields // leave the stored value alone. type Record struct{ Language, Status, URL, Version, Stack, Source, Note string } // ApplyRecord copies the non-empty fields of rec into r. func ApplyRecord(r *AppResult, rec Record) { set := func(dst *string, v string) { if v != "" { *dst = v } } set(&r.Language, rec.Language) set(&r.Status, rec.Status) set(&r.URL, rec.URL) set(&r.Version, rec.Version) set(&r.Stack, rec.Stack) set(&r.Source, rec.Source) if rec.Note != "" && !slices.Contains(r.Notes, rec.Note) { r.Notes = append(r.Notes, rec.Note) } }