// Command templates refuses a file's text kept inline in a manifest, outside the modules still to move (novox/hq // ADR 0349 §2, design 57 package T5, issue 658). // // A file resource names its text as a template in its module's templates directory (`"template": "/"`; // the directory is the manifest's `"templates"`, files/ when it says none), which the module's build packs into an // archive; the controller reads each template back into the file's content, so the // mesh holds and composes what it would for the same text inline. A module whose file text has not yet moved is // listed in the file `inline` beside this check, one per line. Every other module's file resources say no // `content`; a module on the list that keeps none inline is taken off it; and a line the trunk's copy (the merge // base's, given with -base) does not hold is refused, so the list only shrinks; while the trunk holds no copy, the // modules in seed are the trunk's. When it is empty, no manifest in the catalogue carries file text. // // Only file resources are judged: a window-manager addition (`contributions`) takes no template and moves with the // collections (design 57 package L2, issue 681). // // go run . [-base ] // // Every refusal names the refused module's own templates directory. Exits 1 on a finding. package main import ( "bufio" "encoding/json" "flag" "fmt" "os" "path/filepath" "sort" "strings" ) const ( listFile = "inline" listPathInCatalog = "checks/templates/" + listFile ) // Check answers, per module that keeps any, the ids of its file resources that say their text inline. func Check(root string) (map[string][]string, error) { manifests, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json")) if err != nil { return nil, err } sort.Strings(manifests) out := map[string][]string{} for _, path := range manifests { raw, err := os.ReadFile(path) if err != nil { return nil, err } var m struct { Module string `json:"module"` Resources []map[string]any `json:"resources"` } if err := json.Unmarshal(raw, &m); err != nil { return nil, fmt.Errorf("%s: %w", path, err) } for _, r := range m.Resources { if _, inline := r["content"]; inline && r["type"] == "file" { out[m.Module] = append(out[m.Module], fmt.Sprint(r["id"])) } } } return out, nil } // TemplatesDir answers the directory a module keeps its templates in, with a trailing slash: its manifest's // `templates`, files/ when the manifest says none or cannot be read here. func TemplatesDir(root, module string) string { dir := "files/" raw, err := os.ReadFile(filepath.Join(root, "modules", module, "module.json")) if err != nil { return dir } var m struct { Templates string `json:"templates"` } if json.Unmarshal(raw, &m) == nil && m.Templates != "" { dir = strings.TrimSuffix(m.Templates, "/") + "/" } return dir } // Refusal is the finding for a module that keeps file text inline and is not on the list. func Refusal(module string, ids []string, dir string) string { return fmt.Sprintf("%s keeps the text of %s inline and is not on %s: keep each file's text in %s and name it as "+ "the file's template, packed by the build's archive from %s (novox/hq ADR 0349 §2)", module, strings.Join(ids, ", "), listPathInCatalog, dir, dir) } // Gained is the finding for a module the list gains against the trunk's copy. func Gained(module, dir string) string { return fmt.Sprintf("%s gains %s, which the trunk's copy does not hold: the list only shrinks — move its file text "+ "to %s instead (novox/hq ADR 0349 §2)", listPathInCatalog, module, dir) } // seed is the list as it was first written, standing for the trunk's copy until the trunk holds one. var seed = map[string]bool{"asus-zephyrus-g14": true, "power": true, "showcase": true} // Listed is the modules a list names, one per line; a line starting with # and a blank line are not modules. func Listed(path string) (map[string]bool, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() out := map[string]bool{} s := bufio.NewScanner(f) for s.Scan() { line := strings.TrimSpace(s.Text()) if line == "" || strings.HasPrefix(line, "#") { continue } out[line] = true } return out, s.Err() } // Judge answers the modules that keep file text inline and are not on the list (refused), the modules on the list // that keep none (stale), and the modules on the list that the trunk's list does not hold (added). func Judge(found map[string][]string, listed, base map[string]bool) (refused, stale, added []string) { for m := range found { if !listed[m] { refused = append(refused, m) } } for m := range listed { if _, keeps := found[m]; !keeps { stale = append(stale, m) } if !base[m] { added = append(added, m) } } sort.Strings(refused) sort.Strings(stale) sort.Strings(added) return refused, stale, added } func main() { basePath := flag.String("base", "", "the trunk's copy of inline (the merge base's); seed when absent") flag.Parse() root := "." if flag.NArg() > 0 { root = flag.Arg(0) } found, err := Check(root) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } listed, err := Listed(listFile) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } base := seed if *basePath != "" { if base, err = Listed(*basePath); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } refused, stale, added := Judge(found, listed, base) for _, m := range refused { fmt.Println(Refusal(m, found[m], TemplatesDir(root, m))) } for _, m := range stale { fmt.Printf("%s names %s, which keeps no file text inline: take it off the list\n", listPathInCatalog, m) } for _, m := range added { fmt.Println(Gained(m, TemplatesDir(root, m))) } if len(refused) > 0 || len(stale) > 0 || len(added) > 0 { os.Exit(1) } fmt.Printf("templates: no module keeps a file's text inline but the %d listed\n", len(listed)) }