- манифест читается так, как записан: решётка внутри строки не открывает комментарий, скобка внутри комментария не закрывает массив, имя внутри комментария не становится подпиской; новый ключ встаёт после массива, а не внутрь него - всё записываемое проходит через manifest.Quote — обратный слэш в пути делал файл, который инструмент сам не читает - маркер локальной части переехал в doc и пропускает огороженные блоки: процитированный в примере маркер больше не считается границей, а копия без маркера не перезаписывается молча - лишний позиционный аргумент отсекается: flag прекращал разбор и прятал флаги после себя, из-за чего pull, list и check игнорировали --for - заведены тесты проверок копий, включая молчание на исправной копии
152 lines
4.1 KiB
Go
152 lines
4.1 KiB
Go
package cli
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"sort"
|
|
|
|
"git.vakhrushev.me/av/convy/internal/doc"
|
|
"git.vakhrushev.me/av/convy/internal/lang"
|
|
"git.vakhrushev.me/av/convy/internal/suite"
|
|
)
|
|
|
|
// suite list answers two questions. Bare, it says what the suite holds: topics,
|
|
// their layers, the prefixes taken. Given an axis, it says what a component
|
|
// would take — which is the read-only half of assembly, and the reason to build
|
|
// it before anything starts writing copies into other repositories.
|
|
|
|
func runSuiteList(env Env, args []string) ExitCode {
|
|
fs := flag.NewFlagSet("convy suite list", flag.ContinueOnError)
|
|
fs.SetOutput(env.Err)
|
|
root := fs.String("root", "", "root of the suite; by default it is looked up upwards")
|
|
topic := fs.String("topic", "", "show one topic only")
|
|
langAxis := fs.String("lang", "", "language of the component, to show what it would take")
|
|
stackAxis := fs.String("stack", "", "stack of the component, comma-separated, to show what it would take")
|
|
retired := fs.Bool("retired", false, "show the retired names instead of the live ones")
|
|
if err := fs.Parse(args); err != nil {
|
|
return Usage
|
|
}
|
|
|
|
dir, code := suiteRoot(env, *root)
|
|
if code != OK {
|
|
return code
|
|
}
|
|
s, err := suite.Load(dir)
|
|
if err != nil {
|
|
fmt.Fprintln(env.Err, err)
|
|
return Usage
|
|
}
|
|
|
|
if *retired {
|
|
listRetired(env.Out, s)
|
|
return OK
|
|
}
|
|
|
|
topics := s.Manifest.LiveTopics()
|
|
if *topic != "" {
|
|
if !s.Manifest.TopicLive(*topic) {
|
|
fmt.Fprintf(env.Err, "the suite declares no live topic %q\n", *topic)
|
|
return Usage
|
|
}
|
|
topics = []string{*topic}
|
|
}
|
|
|
|
// The path column is sized to the suite rather than guessed: a name that
|
|
// overruns a fixed width breaks every row below it.
|
|
width := 0
|
|
for _, d := range s.Docs {
|
|
width = max(width, len(d.Path))
|
|
}
|
|
|
|
selecting := *langAxis != "" || *stackAxis != ""
|
|
component := suite.Component{Lang: split(*langAxis), Stack: split(*stackAxis)}
|
|
for i, name := range topics {
|
|
if i > 0 {
|
|
fmt.Fprintln(env.Out)
|
|
}
|
|
listTopic(env.Out, s, name, component, selecting, width)
|
|
}
|
|
|
|
if !selecting {
|
|
fmt.Fprintf(env.Out, "\n%s, %s, language version %d (%s)\n",
|
|
plural(len(topics), "topic"), plural(len(s.Docs), "file"),
|
|
s.Manifest.Language.Version, s.Manifest.Language.Lang)
|
|
}
|
|
return OK
|
|
}
|
|
|
|
func listTopic(w io.Writer, s *suite.Suite, name string, c suite.Component, selecting bool, width int) {
|
|
fmt.Fprintf(w, "%s — %s\n", name, s.Manifest.Topics.Live[name])
|
|
|
|
if !selecting {
|
|
for _, d := range s.Layers(name) {
|
|
fmt.Fprintln(w, " "+layerLine(s, d, width))
|
|
}
|
|
return
|
|
}
|
|
|
|
taken, left := s.Assemble(name, c)
|
|
if len(taken) == 0 {
|
|
fmt.Fprintln(w, " nothing: the topic has no layer this component takes")
|
|
}
|
|
for _, d := range taken {
|
|
fmt.Fprintln(w, " "+layerLine(s, d, width))
|
|
}
|
|
for _, d := range left {
|
|
fmt.Fprintln(w, " · "+layerLine(s, d, width)+" left out")
|
|
}
|
|
}
|
|
|
|
// layerLine describes one layer: its prefix, where it lies, what axis it is on
|
|
// and how much of it there is.
|
|
func layerLine(s *suite.Suite, d *doc.Document, width int) string {
|
|
rules, retired := 0, 0
|
|
for _, r := range d.Rules {
|
|
rules++
|
|
if _, ok := r.Block(lang.Retired); ok {
|
|
retired++
|
|
}
|
|
}
|
|
count := plural(rules, "rule")
|
|
if retired > 0 {
|
|
count += fmt.Sprintf(", %d retired", retired)
|
|
}
|
|
return fmt.Sprintf("%-6s %-*s %-22s %s", s.Prefix(d), width, d.Path, suite.Axis(d), count)
|
|
}
|
|
|
|
func listRetired(w io.Writer, s *suite.Suite) {
|
|
sections := []struct {
|
|
title string
|
|
entries map[string]string
|
|
}{
|
|
{"topics", s.Manifest.Topics.Retired},
|
|
{"prefixes", s.Manifest.Prefixes.Retired},
|
|
}
|
|
empty := true
|
|
for _, section := range sections {
|
|
if len(section.entries) == 0 {
|
|
continue
|
|
}
|
|
empty = false
|
|
fmt.Fprintf(w, "%s\n", section.title)
|
|
for _, key := range sortedKeys(section.entries) {
|
|
fmt.Fprintf(w, " %-16s %s\n", key, section.entries[key])
|
|
}
|
|
}
|
|
if empty {
|
|
fmt.Fprintln(w, "nothing has been retired yet")
|
|
return
|
|
}
|
|
fmt.Fprintln(w, "\nnone of these names is ever handed out again")
|
|
}
|
|
|
|
func sortedKeys(m map[string]string) []string {
|
|
keys := make([]string, 0, len(m))
|
|
for k := range m {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
return keys
|
|
}
|