- заведён internal/source: уровни ссылаются друг на друга путём на диске или git-репозиторием, ревизия закрепляется хвостом #ref; клон делается заново и удаляется, кэша нет - добавлены init, add, pull, list, check в проекте — манифест .conventions.toml, сборка копий по разу на компонент, маркер локальной части, READING.md рядом - проверки формы развязаны с набором: принимают lang.Vocabulary, а язык копии узнаётся по строке о версии — манифеста рядом с ней нет
81 lines
2.5 KiB
Go
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"
|
|
}
|