проектные команды и ссылки на источник
- заведён internal/source: уровни ссылаются друг на друга путём на диске или git-репозиторием, ревизия закрепляется хвостом #ref; клон делается заново и удаляется, кэша нет - добавлены init, add, pull, list, check в проекте — манифест .conventions.toml, сборка копий по разу на компонент, маркер локальной части, READING.md рядом - проверки формы развязаны с набором: принимают lang.Vocabulary, а язык копии узнаётся по строке о версии — манифеста рядом с ней нет
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"git.vakhrushev.me/av/convy/internal/check"
|
||||
"git.vakhrushev.me/av/convy/internal/doc"
|
||||
"git.vakhrushev.me/av/convy/internal/manifest"
|
||||
"git.vakhrushev.me/av/convy/internal/project"
|
||||
)
|
||||
|
||||
// convy check stays at the top level and reaches for no suite. The form of a
|
||||
// rule is one and the same, the local rules of the repository on X prefixes are
|
||||
// written by that same form, and checking what lies here has to work without a
|
||||
// network and without knowing where the copies came from.
|
||||
|
||||
func runCheck(env Env, args []string) ExitCode {
|
||||
fs := flag.NewFlagSet("convy check", flag.ContinueOnError)
|
||||
fs.SetOutput(env.Err)
|
||||
root := fs.String("root", "", "root of the project; it is looked up upwards by default")
|
||||
forComponent := fs.String("for", "", "component to check; every one of them by default")
|
||||
quiet := fs.Bool("quiet", false, "print findings only")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return Usage
|
||||
}
|
||||
|
||||
dir, code := projectRoot(env, *root)
|
||||
if code != OK {
|
||||
return code
|
||||
}
|
||||
m, err := manifest.LoadProject(dir)
|
||||
if err != nil {
|
||||
fmt.Fprintln(env.Err, err)
|
||||
return Usage
|
||||
}
|
||||
names, code := components(env, m, *forComponent)
|
||||
if code != OK {
|
||||
return code
|
||||
}
|
||||
|
||||
var docs []*doc.Document
|
||||
var broken []error
|
||||
for _, name := range names {
|
||||
found, errs := copies(dir, m.Components[name].Dir)
|
||||
docs = append(docs, found...)
|
||||
broken = append(broken, errs...)
|
||||
}
|
||||
|
||||
rep := check.Copies(docs)
|
||||
for _, err := range broken {
|
||||
fmt.Fprintf(env.Err, "%s\n", err)
|
||||
}
|
||||
printCopyReport(env.Out, rep, len(docs), len(names), *quiet)
|
||||
if rep.Errors() > 0 || len(broken) > 0 {
|
||||
return Failed
|
||||
}
|
||||
return OK
|
||||
}
|
||||
|
||||
// copies collects the assembled conventions of one component directory.
|
||||
//
|
||||
// What is a copy is decided by the origin key rather than by the name of the
|
||||
// file: README.md belongs to the repository, READING.md belongs to the suite,
|
||||
// and a file whose origin key was taken away has become a document of the
|
||||
// repository — none of the three answers to the form of a rule.
|
||||
func copies(root, dir string) ([]*doc.Document, []error) {
|
||||
var docs []*doc.Document
|
||||
var broken []error
|
||||
base := filepath.Join(root, filepath.FromSlash(dir))
|
||||
|
||||
err := filepath.WalkDir(base, func(name string, entry fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if entry.IsDir() || filepath.Ext(entry.Name()) != ".md" {
|
||||
return nil
|
||||
}
|
||||
rel, err := filepath.Rel(root, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rel = filepath.ToSlash(rel)
|
||||
d, err := doc.Load(rel, name)
|
||||
if err != nil {
|
||||
broken = append(broken, err)
|
||||
return nil
|
||||
}
|
||||
if d.Front.Origin == "" {
|
||||
return nil
|
||||
}
|
||||
docs = append(docs, d)
|
||||
return nil
|
||||
})
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
broken = append(broken, fmt.Errorf("walking %s: %w", dir, err))
|
||||
}
|
||||
sort.Slice(docs, func(i, j int) bool { return docs[i].Path < docs[j].Path })
|
||||
return docs, broken
|
||||
}
|
||||
|
||||
func printCopyReport(w io.Writer, rep *check.Report, files, comps int, quiet bool) {
|
||||
findings := rep.Findings()
|
||||
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)
|
||||
}
|
||||
|
||||
if quiet {
|
||||
return
|
||||
}
|
||||
if len(findings) > 0 {
|
||||
fmt.Fprintln(w)
|
||||
}
|
||||
fmt.Fprintf(w, "project: %s in %s\n", plural(files, "file"), plural(comps, "component"))
|
||||
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())
|
||||
case files == 0:
|
||||
fmt.Fprintf(w, "nothing to check: no file carries an origin key; convy pull assembles the copies\n")
|
||||
default:
|
||||
fmt.Fprintf(w, "the copies hold the form; everything below %s is the repository's own\n", project.LocalMarker)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user