проектные команды и ссылки на источник

- заведён internal/source: уровни ссылаются друг на друга путём на диске
  или git-репозиторием, ревизия закрепляется хвостом #ref; клон делается
  заново и удаляется, кэша нет
- добавлены init, add, pull, list, check в проекте — манифест
  .conventions.toml, сборка копий по разу на компонент, маркер локальной
  части, READING.md рядом
- проверки формы развязаны с набором: принимают lang.Vocabulary, а язык
  копии узнаётся по строке о версии — манифеста рядом с ней нет
This commit is contained in:
av
2026-07-27 20:42:18 +03:00
parent 4615de6e86
commit 23d88c4048
27 changed files with 3009 additions and 57 deletions
+104
View File
@@ -0,0 +1,104 @@
package cli
import (
"flag"
"fmt"
"git.vakhrushev.me/av/convy/internal/project"
)
// convy pull reassembles what the manifest lists. It never reports what
// changed: after a pull that is shown by git diff, and the decision to accept,
// to fix or to roll back is taken by a person before the commit. A second
// mechanism for comparing files, standing next to git, would answer the same
// question worse.
func runPull(env Env, args []string) ExitCode {
fs := flag.NewFlagSet("convy pull", 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 reassemble; every one of them by default")
if err := fs.Parse(args); err != nil {
return Usage
}
o, code := openProject(env, *root)
if code != OK {
return code
}
defer o.Close()
if err := distinctDirs(o.Manifest); err != nil {
fmt.Fprintln(env.Err, err)
return Usage
}
names, code := components(env, o.Manifest, *forComponent)
if code != OK {
return code
}
failed := 0
written := 0
for i, name := range names {
c := o.Manifest.Components[name]
if i > 0 {
fmt.Fprintln(env.Out)
}
fmt.Fprintf(env.Out, "%s → %s\n", name, c.Dir)
if c.Dir == "" {
fmt.Fprintln(env.Err, " the component names no dir, and a copy has to be written somewhere")
failed++
continue
}
if len(c.Topics) == 0 {
fmt.Fprintln(env.Out, " subscribed to nothing yet")
}
// The path column is sized to the component rather than guessed: a
// name that overruns a fixed width breaks every row below it.
width := len(c.Dir) + 1 + len(project.ReadingName)
for _, topic := range c.Topics {
width = max(width, len(c.Dir)+len(topic)+4)
}
for _, topic := range c.Topics {
made, err := project.Assemble(o.Suite, o.Root, c, topic)
if err != nil {
fmt.Fprintf(env.Err, " %s: %s\n", topic, err)
failed++
continue
}
written++
fmt.Fprintf(env.Out, " %-*s %s%s\n", width, made.Path, plural(len(made.Layers), "layer"), kept(made))
}
guide, err := project.Reading(o.LangRoot, o.Suite.Manifest.Language.Reading, o.Root, c.Dir)
if err != nil {
fmt.Fprintf(env.Err, " %s\n", err)
failed++
continue
}
fmt.Fprintf(env.Out, " %-*s the guide to reading a rule\n", width, guide)
}
fmt.Fprintf(env.Out, "\n%s assembled", plural(written, "file"))
if failed > 0 {
fmt.Fprintf(env.Out, ", %s\n", plural(failed, "failure"))
return Failed
}
fmt.Fprintln(env.Out)
fmt.Fprintln(env.Out, "git diff says what changed")
return OK
}
// kept notes that a local part was carried over, because that is the one thing
// a reassembly could have destroyed and did not.
func kept(c project.Copy) string {
switch {
case c.Created:
return ", new"
case c.Kept:
return ", local part kept"
}
return ""
}