Files
av b6b0976c19 исправлены находки ревью проектной стороны
- манифест читается так, как записан: решётка внутри строки не открывает
  комментарий, скобка внутри комментария не закрывает массив, имя внутри
  комментария не становится подпиской; новый ключ встаёт после массива,
  а не внутрь него
- всё записываемое проходит через manifest.Quote — обратный слэш в пути
  делал файл, который инструмент сам не читает
- маркер локальной части переехал в doc и пропускает огороженные блоки:
  процитированный в примере маркер больше не считается границей, а копия
  без маркера не перезаписывается молча
- лишний позиционный аргумент отсекается: flag прекращал разбор и прятал
  флаги после себя, из-за чего pull, list и check игнорировали --for
- заведены тесты проверок копий, включая молчание на исправной копии
2026-07-27 21:16:29 +03:00

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
}