- манифест читается так, как записан: решётка внутри строки не открывает комментарий, скобка внутри комментария не закрывает массив, имя внутри комментария не становится подпиской; новый ключ встаёт после массива, а не внутрь него - всё записываемое проходит через manifest.Quote — обратный слэш в пути делал файл, который инструмент сам не читает - маркер локальной части переехал в doc и пропускает огороженные блоки: процитированный в примере маркер больше не считается границей, а копия без маркера не перезаписывается молча - лишний позиционный аргумент отсекается: flag прекращал разбор и прятал флаги после себя, из-за чего pull, list и check игнорировали --for - заведены тесты проверок копий, включая молчание на исправной копии
114 lines
3.1 KiB
Go
114 lines
3.1 KiB
Go
package cli
|
|
|
|
import (
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"git.vakhrushev.me/av/convy/internal/check"
|
|
"git.vakhrushev.me/av/convy/internal/manifest"
|
|
"git.vakhrushev.me/av/convy/internal/suite"
|
|
)
|
|
|
|
func runSuiteCheck(env Env, args []string) ExitCode {
|
|
fs := flag.NewFlagSet("convy suite check", flag.ContinueOnError)
|
|
fs.SetOutput(env.Err)
|
|
root := fs.String("root", "", "root of the suite; by default it is looked up upwards from the current directory")
|
|
quiet := fs.Bool("quiet", false, "print findings only")
|
|
if err := fs.Parse(args); err != nil {
|
|
return Usage
|
|
}
|
|
|
|
dir := *root
|
|
if dir == "" {
|
|
found, err := manifest.Find(env.Dir)
|
|
if err != nil {
|
|
if errors.Is(err, manifest.ErrNotFound) {
|
|
fmt.Fprintf(env.Err, "not a conventions suite: no %s here or above\n", manifest.Name)
|
|
if _, err := os.Stat(filepath.Join(env.Dir, ".conventions.toml")); err == nil {
|
|
fmt.Fprintln(env.Err, "this is a project — checking what is here is called \"convy check\"")
|
|
}
|
|
return Usage
|
|
}
|
|
fmt.Fprintln(env.Err, err)
|
|
return Usage
|
|
}
|
|
dir = found
|
|
}
|
|
|
|
s, err := suite.Load(dir)
|
|
if err != nil {
|
|
fmt.Fprintln(env.Err, err)
|
|
return Usage
|
|
}
|
|
|
|
rep := check.Suite(s)
|
|
printReport(env.Out, rep, s, *quiet)
|
|
if rep.Errors() > 0 {
|
|
return Failed
|
|
}
|
|
return OK
|
|
}
|
|
|
|
func printReport(w io.Writer, rep *check.Report, s *suite.Suite, quiet bool) {
|
|
findings := rep.Findings()
|
|
printFindings(w, findings)
|
|
|
|
if quiet {
|
|
return
|
|
}
|
|
if len(findings) > 0 {
|
|
fmt.Fprintln(w)
|
|
}
|
|
fmt.Fprintf(w, "suite: %s, %s, language version %d (%s)\n",
|
|
plural(len(s.Docs), "file"),
|
|
plural(len(s.Manifest.LiveTopics()), "topic"),
|
|
s.Manifest.Language.Version, s.Manifest.Language.Lang)
|
|
// A check that was skipped says so. Reaching the language costs a fetch and
|
|
// this check runs on every edit, so the documents about the language go
|
|
// unread — and a check silently not run reads exactly like a check passed.
|
|
if spec := s.Manifest.Language.Source; spec != "" {
|
|
fmt.Fprintf(w, "the language lies at %s and was not fetched: its documents went unchecked\n", spec)
|
|
}
|
|
switch {
|
|
case rep.Errors() > 0:
|
|
fmt.Fprintf(w, "errors: %d, warnings: %d\n", rep.Errors(), rep.Warnings())
|
|
case rep.Warnings() > 0:
|
|
fmt.Fprintf(w, "no errors, warnings: %d\n", rep.Warnings())
|
|
default:
|
|
fmt.Fprintln(w, "suite integrity holds")
|
|
}
|
|
}
|
|
|
|
// plural agrees a noun with a count: 1 file, 4 files.
|
|
func plural(n int, noun string) string {
|
|
if n == 1 {
|
|
return fmt.Sprintf("%d %s", n, noun)
|
|
}
|
|
return fmt.Sprintf("%d %ss", n, noun)
|
|
}
|
|
|
|
// printFindings lays the findings out grouped by file. Both checks print them
|
|
// the same way: a finding of the suite and a finding of a copy are read by the
|
|
// same person, and two layouts would be two things to learn.
|
|
func printFindings(w io.Writer, findings []check.Finding) {
|
|
current := ""
|
|
for _, f := range findings {
|
|
if f.Path != current {
|
|
if current != "" {
|
|
fmt.Fprintln(w)
|
|
}
|
|
fmt.Fprintf(w, "%s\n", f.Path)
|
|
current = f.Path
|
|
}
|
|
where := ""
|
|
if f.Line > 0 {
|
|
where = fmt.Sprintf(":%d", f.Line)
|
|
}
|
|
fmt.Fprintf(w, " %s%s %s [%s]\n", f.Severity, where, f.Msg, f.Family)
|
|
}
|
|
}
|