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 }