Files
av 23d88c4048 проектные команды и ссылки на источник
- заведён internal/source: уровни ссылаются друг на друга путём на диске
  или git-репозиторием, ревизия закрепляется хвостом #ref; клон делается
  заново и удаляется, кэша нет
- добавлены init, add, pull, list, check в проекте — манифест
  .conventions.toml, сборка копий по разу на компонент, маркер локальной
  части, READING.md рядом
- проверки формы развязаны с набором: принимают lang.Vocabulary, а язык
  копии узнаётся по строке о версии — манифеста рядом с ней нет
2026-07-27 20:42:18 +03:00

81 lines
2.5 KiB
Go

package suite
import (
"slices"
"git.vakhrushev.me/av/convy/internal/doc"
)
// Component is what a copy is assembled for: one language, one set of tools,
// one kind of application. Both axes may stay empty — a flat suite has no axes
// at all, and a component of it selects the base layer and nothing else.
//
// Each axis is a list because a component may take two stack layers at once:
// sqlite and postgres in the schema topic hold together, being different tables
// of one service. Two languages never hold together, and that the axis allows
// the list anyway is a matter of one shape for both rather than a licence.
type Component struct {
Lang []string
Stack []string
}
// Assemble picks the layers of a topic a component takes, in the order they go
// into a copy: base, then language, then stack.
//
// A layer is chosen by the axis keys of its front matter rather than by where
// its file lies (META-38). A layer with no keys is the base one and travels
// always; a key the layer does not declare puts no demand on the component, so
// a language layer with no stack key fits any stack.
//
// This is the read-only half of assembly. The project commands write files out
// of the same selection, so the two must never come to differ — which is why
// the selection lives here rather than inside whichever command needs it.
func (s *Suite) Assemble(topic string, c Component) (taken, left []*doc.Document) {
for _, d := range s.Layers(topic) {
if fits(d, c) {
taken = append(taken, d)
continue
}
left = append(left, d)
}
return taken, left
}
// fits reports whether a component takes a layer.
func fits(d *doc.Document, c Component) bool {
if d.Front.Lang != "" && !slices.Contains(c.Lang, d.Front.Lang) {
return false
}
if d.Front.Stack != "" && !slices.Contains(c.Stack, d.Front.Stack) {
return false
}
return true
}
// rank orders the layers of a topic the way a copy carries them. A layer on
// both axes comes last: it narrows the most.
func rank(d *doc.Document) int {
switch {
case d.Front.Lang == "" && d.Front.Stack == "":
return 0
case d.Front.Stack == "":
return 1
case d.Front.Lang == "":
return 2
}
return 3
}
// Axis describes a layer in one word, for a report.
func Axis(d *doc.Document) string {
switch {
case d.Front.Lang != "" && d.Front.Stack != "":
return "lang=" + d.Front.Lang + " stack=" + d.Front.Stack
case d.Front.Lang != "":
return "lang=" + d.Front.Lang
case d.Front.Stack != "":
return "stack=" + d.Front.Stack
}
return "base"
}