package resolution import ( "fmt" "regexp" "strings" mcat "git.novox.be/novox/mesh-sdk/go/catalogue" ) // ofContribution is where a holder places a kind: ${contribution::}. Loose inside the // braces, so a misspelt seat or kind is found and refused rather than written out as text. var ofContribution = regexp.MustCompile(`\$\{contribution:([^}]*)\}`) // receivable is what a seat receives of a kind, with the seat's canonical name; false when the seat // is unknown or does not receive it. func receivable(seat, kind string) (mcat.Seat, mcat.Receivable, bool) { s, known := mcat.SeatNamed(seat) if !known { return mcat.Seat{}, mcat.Receivable{}, false } for _, r := range s.Receives { if r.Kind == kind { return s, r, true } } return s, mcat.Receivable{}, false } // kindsOf names a seat's receivable kinds for a refusal. func kindsOf(s mcat.Seat) string { if len(s.Receives) == 0 { return "it receives no contributions" } var kinds []string for _, r := range s.Receives { kinds = append(kinds, r.Kind) } return "it receives " + strings.Join(kinds, ", ") } // seatPlaceholderProblems is what is wrong with one resource's ${contribution:…}: placed only in a // file's content, naming a seat and a kind it receives, and only by a module that claims that seat // — another would be a second writer of a file there is one of (novox/hq ADR 0212 §3). func seatPlaceholderProblems(m mcat.Manifest, r map[string]any) []string { var problems []string for _, field := range sortedKeys(r) { v, ok := r[field].(string) if !ok { continue } found := ofContribution.FindAllStringSubmatch(v, -1) if len(found) == 0 { continue } if field != "content" { problems = append(problems, fmt.Sprintf( "%s's resource %v names %s in its %s; contributions are placed only in a file's content", m.Module, r["id"], found[0][0], field)) continue } for _, f := range found { seat, rest, two := strings.Cut(f[1], ":") // A kind received as data may be narrowed by its shape's placing fields (novox/hq ADR 0255). kind, where, _ := strings.Cut(rest, ":") s, rcv, ok := receivable(seat, kind) if two && ok { if _, err := placeholderWhere(rcv, where); err != nil { problems = append(problems, fmt.Sprintf("%s's resource %v names %s: %v", m.Module, r["id"], f[0], err)) } } if !two || !ok { detail := "the mesh defines no seat " + fmt.Sprintf("%q", seat) if s.Name != "" { detail = s.Name + ": " + kindsOf(s) } problems = append(problems, fmt.Sprintf( "%s's resource %v names %s; a contribution is ${contribution::} (%s)", m.Module, r["id"], f[0], detail)) continue } if !m.ClaimsSeat(s.Name) { problems = append(problems, fmt.Sprintf( "%s's resource %v names %s and %s does not claim %s; every module's contributions to a "+ "seat are placed by its holder alone (novox/hq ADR 0212)", m.Module, r["id"], f[0], m.Module, s.Name)) } } } return problems } // seatContributions is every module's contribution of one kind to one seat (novox/hq ADR 0212 §3): // in module order, each module's pieces in the order it declared them, each module's preceded by a // comment line naming it in the tool's grammar, and empty when nothing is contributed. A kind that // takes directories has each contributor's `${dir:}` filled with where that module's directory // is on this machine (novox/hq to-be 43). // // A directory placed in a person's home is named `${machine:account-home}/…` (novox/hq to-be 29); in a // kind that takes directories that is filled from the machine's facts as well, so the holder reads a // path, never a placeholder (novox/hq ADR 0233: an item in the operator's home is backed up too). // // A kind received as data is rendered through the holder's template instead, narrowed by where // (novox/hq ADR 0255); and a contribution naming a capability this machine did not report is left // out, of either form. // // What it could not render is answered as unplaced, each naming its module and why, and left out: one // piece the holder's template does not know is that piece's fault, not the machine's. func seatContributions(modules []mcat.Manifest, holder mcat.Manifest, placeholder string, with mcat.Rendering, facts map[string]string, caps map[string]bool) (string, []string, error) { seat, rest, _ := strings.Cut(placeholder, ":") kind, whereText, _ := strings.Cut(rest, ":") s, r, ok := receivable(seat, kind) if !ok { return "", nil, nil } if r.Shape != nil { where, err := placeholderWhere(r, whereText) if err != nil { return "", nil, err } return shapedContributions(modules, holder, facts["name"], facts, s, r, where, caps) } var failed error var unplacedLines []string var blocks []textBlock for _, m := range inModuleOrder(modules) { var old strings.Builder // A left-out module's lines are best effort (novox/hq ADR 0262): what cannot be placed is said, // and the machine is declared without it. A placement setting that does not read is not // replaced by the definition's own path, which may not be where the data is. if m.BestEffort && r.Dirs { if _, err := mcat.Places(m, with.Settings[m.Module]); err != nil { unplacedLines = append(unplacedLines, fmt.Sprintf("%s's %s for %s, kept while it is left out, "+ "is not placed: its placement setting does not read (%v)", m.Module, kind, s.Name, err)) continue } } for _, c := range manifestAllContributions(m) { if c.Kind != kind || !capable(c, caps) { continue } if cs, known := mcat.SeatNamed(c.Seat); !known || cs.Name != s.Name { continue } content := c.Content var lineFailed error if r.Dirs { filled, err := dirFill(content, dirsFor(m, with), m.Module) if err != nil && lineFailed == nil { lineFailed = err } // An operator's path the module was given, as an item of data on it (novox/hq ADR 0233). if accessRef.MatchString(filled) { _, byID, err := accessesFor(m, with.Settings[m.Module]) if err == nil { filled, err = accessFill(filled, byID, m.Module) } if err != nil && lineFailed == nil { lineFailed = err } } for _, key := range machineUsed(filled) { value, has := facts[key] if !has { if lineFailed == nil { lineFailed = fmt.Errorf("%s's %s for %s says ${machine:%s}, and this machine says %s", m.Module, kind, s.Name, key, orNothing(namesOfFacts(facts))) } continue } filled = strings.ReplaceAll(filled, "${machine:"+key+"}", value) } content = filled } if lineFailed != nil { if m.BestEffort { unplacedLines = append(unplacedLines, fmt.Sprintf("%s's %s for %s, kept while it is left "+ "out, is not placed: %v", m.Module, kind, s.Name, lineFailed)) continue } if failed == nil { failed = lineFailed } } if old.Len() == 0 { fmt.Fprintf(&old, "%s %s\n", r.Comment, m.Module) } old.WriteString(content) if !strings.HasSuffix(content, "\n") { old.WriteString("\n") } } var held []heldItem if !r.Dirs { held = heldItems(m, "contribution:"+s.Name+":"+kind, nil, facts, caps) } blocks = append(blocks, moduleBlocks(m, r.Comment, old.String(), held)...) } return joinBlocks(blocks), unplacedLines, failed }