Files
mesh-catalog/checks/templates/main.go
T

179 lines
5.9 KiB
Go

// 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": "<dir>/<name>"`;
// 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 <the trunk's inline>] <catalogue root>
//
// 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))
}