Catalogue: a module's needs include its templates' text (ADR 0632 §2; issue 1966)
docker's daemon file and artifact-store-tools' store address move to the image seat's reach in their templates while their manifests stay the same, so needs read from the manifest alone let an engine before the reading lose the file. ModuleNeeds reads the templates TemplatePaths names; the merge message carries the result per directory for the indexer. Claude-Session: https://claude.ai/code/session_01JG73KG7AfSdh6FP8VrBqCn
This commit is contained in:
@@ -163,12 +163,8 @@ func ManifestNeeds(raw []byte) ([]string, error) {
|
||||
return nil, err
|
||||
}
|
||||
used := map[string]bool{}
|
||||
for _, f := range referenceForms {
|
||||
if bytes.Contains(raw, []byte("${"+f+":")) {
|
||||
used[ReadForm+f] = true
|
||||
}
|
||||
}
|
||||
if ofSeatReach.Match(raw) || claimsAtMesh(top, ContainerImageRegistrySeat) {
|
||||
textNeeds(raw, used)
|
||||
if claimsAtMesh(top, ContainerImageRegistrySeat) {
|
||||
used[ReadForm+ReferenceFormSeatReach] = true
|
||||
}
|
||||
if _, has := top[ConfigField]; has || takesConfigKey.Match(raw) {
|
||||
@@ -429,6 +425,39 @@ func asList(v any) []any {
|
||||
return list
|
||||
}
|
||||
|
||||
// textNeeds adds the reference forms a manifest's or a template's text writes to used: a form the engine fills in a
|
||||
// file's text is read there whether the text is inline or in a template file.
|
||||
func textNeeds(text []byte, used map[string]bool) {
|
||||
for _, f := range referenceForms {
|
||||
if bytes.Contains(text, []byte("${"+f+":")) {
|
||||
used[ReadForm+f] = true
|
||||
}
|
||||
}
|
||||
if ofSeatReach.Match(text) {
|
||||
used[ReadForm+ReferenceFormSeatReach] = true
|
||||
}
|
||||
}
|
||||
|
||||
// ModuleNeeds is ManifestNeeds of a module whose files take their text from templates (template.go): what the manifest
|
||||
// needs and what each template's text writes, by template path (TemplatePaths), sorted. A module's needs are both: an
|
||||
// engine renders the template's text as it renders inline content, so a template naming ${seat:x:reach} needs the
|
||||
// reading as the same content inline does, though the manifest itself is unchanged (novox/hq ADR 0632 §2). A template
|
||||
// named and not given is no need; the reader of the files says it could not read one.
|
||||
func ModuleNeeds(raw []byte, templates map[string]string) ([]string, error) {
|
||||
needs, err := ManifestNeeds(raw)
|
||||
if err != nil || len(templates) == 0 {
|
||||
return needs, err
|
||||
}
|
||||
used := map[string]bool{}
|
||||
for _, n := range needs {
|
||||
used[n] = true
|
||||
}
|
||||
for _, text := range templates {
|
||||
textNeeds([]byte(text), used)
|
||||
}
|
||||
return sortedSet(used), nil
|
||||
}
|
||||
|
||||
func sortedSet(set map[string]bool) []string {
|
||||
out := make([]string, 0, len(set))
|
||||
for k := range set {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package catalogue
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"os"
|
||||
"slices"
|
||||
"testing"
|
||||
@@ -87,12 +88,26 @@ func TestOwnManifestUsesNoWordTheRunningControllerRefuses(t *testing.T) {
|
||||
{"the store's holder", `{"module":"distribution","version":"1","claims":[{"name":"container-image-registry",` +
|
||||
`"scope":"mesh","serves":["list"]}],"listens":[{"name":"registry","port":5000,"from":"mesh"}]}`,
|
||||
[]string{"form:seat-reach", "seat:container-image-registry"}, []string{"form:seat-reach", "seat:container-image-registry"}},
|
||||
// docker's daemon file as ADR 0632 leaves it: the manifest unchanged, its template naming the image seat's reach;
|
||||
// TemplatePaths names the template a reader of the files reads.
|
||||
{"a template naming a seat's reach", `{"module":"d","version":"1","resources":[{"id":"daemon","type":"file",` +
|
||||
`"path":"/etc/d.json","template":"files/daemon.json"}]}`, []string{"form:seat", "form:seat-reach"},
|
||||
[]string{"form:seat-reach"}},
|
||||
{"a word this contract does not know", `{"module":"u","version":"1","newer-word":{"inside":1}}`, []string{"word:module"}, nil},
|
||||
} {
|
||||
needs, err := ManifestNeeds([]byte(row.raw))
|
||||
templates := map[string]map[string]string{ // the templates' text a row's module names
|
||||
"a template naming a seat's reach": {"files/daemon.json": `{"x":"${seat:container-image-registry:reach}"}`},
|
||||
}[row.why]
|
||||
needs, err := ModuleNeeds([]byte(row.raw), templates)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", row.why, err)
|
||||
}
|
||||
if templates != nil {
|
||||
m, err := Decode([]byte(row.raw))
|
||||
if paths := TemplatePaths(m); err != nil || !slices.Equal(paths, slices.Sorted(maps.Keys(templates))) {
|
||||
t.Errorf("%s: TemplatePaths = %v (%v), want %v", row.why, paths, err, slices.Sorted(maps.Keys(templates)))
|
||||
}
|
||||
}
|
||||
for _, n := range row.needs {
|
||||
if !slices.Contains(needs, n) {
|
||||
t.Errorf("%s: needs %v, not %s", row.why, needs, n)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
@@ -184,6 +185,25 @@ func fillItemTemplates(m Manifest, items []Item, text func(string) (string, bool
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// TemplatePaths is every template the manifest's files and items name, by its path in the module's directory, sorted
|
||||
// and once each: what a reader of the module's files reads for ModuleNeeds. A template whose name is wrong is left out;
|
||||
// the judge refuses it by name.
|
||||
func TemplatePaths(m Manifest) []string {
|
||||
var out []string
|
||||
for _, r := range m.Resources {
|
||||
if name, has, wrong := templateOf(m.Module, m.TemplatesDir(), r); has && wrong == "" {
|
||||
out = append(out, name)
|
||||
}
|
||||
}
|
||||
for _, it := range m.Items {
|
||||
if it.Template != "" && itemTemplateProblem(m, it.Template) == "" {
|
||||
out = append(out, it.Template)
|
||||
}
|
||||
}
|
||||
slices.Sort(out)
|
||||
return slices.Compact(out)
|
||||
}
|
||||
|
||||
// ReadTemplates is the text of each template the manifest names, read from the module's directory in a repository tree,
|
||||
// by template path. Refused by name: a template the directory lacks, and anything under its templates directory
|
||||
// (TemplatesDir) the build's packing refuses — that directory itself, or any directory or file under it, that is a
|
||||
|
||||
Reference in New Issue
Block a user