suite init и suite add: заведение набора и конвенции в двух режимах

- suite init создаёт директорию и манифест со скелетом таблиц; suite add пишет
  файл конвенции и вправляет запись в suite.toml, сохраняя комментарии
- без аргументов команды спрашивают поля с подсказками, с флагами берут всё
  сразу и не спрашивают ничего; без терминала пустой вызов отказывает
- строка о версии языка генерируется из словаря набора, поэтому созданный файл
  проходит suite check без правок
- починено разрешение extends: короткая форма бралась по суффиксу и могла
  указать на сам файл; теперь неоднозначность либо избегается при записи, либо
  сообщается ошибкой
This commit is contained in:
av
2026-07-27 10:40:57 +03:00
parent 709157237d
commit b29b5b5e6f
9 changed files with 1247 additions and 21 deletions
+36 -11
View File
@@ -28,11 +28,14 @@ const (
// Env is the environment of a run. It is pulled out so that commands can be
// tested without a process.
type Env struct {
Dir string
Out io.Writer
Err io.Writer
NoTTY bool
Colors bool
Dir string
In io.Reader
Out io.Writer
Err io.Writer
// Interactive says whether input comes from a terminal. A command that
// asks questions refuses to start without one rather than blocking on an
// answer nobody is there to give.
Interactive bool
}
// Run parses the arguments and executes the command.
@@ -60,15 +63,16 @@ func Run(env Env, args []string) ExitCode {
func runSuite(env Env, args []string) ExitCode {
if len(args) == 0 {
fmt.Fprintln(env.Err, "convy suite: a subcommand is required — check")
fmt.Fprintln(env.Err, "convy suite: a subcommand is required — init, add or check")
return Usage
}
switch args[0] {
case "check":
return runSuiteCheck(env, args[1:])
case "new":
fmt.Fprintln(env.Err, "the \"suite new\" command is not implemented yet")
return Usage
case "init":
return runSuiteInit(env, args[1:])
case "add":
return runSuiteAdd(env, args[1:])
default:
fmt.Fprintf(env.Err, "unknown subcommand %q for convy suite\n", args[0])
return Usage
@@ -86,8 +90,13 @@ In a project:
convy check check the form of what is here (not implemented)
In a suite:
convy suite init start a suite: a directory and a manifest
convy suite add add a convention: a file, a topic and a prefix
convy suite check suite integrity: prefixes, topics, axes, links, form
convy suite new a new topic (not implemented)
The commands that change something run in two modes. Bare, they ask for every
field with a hint attached — that mode is for a person. With flags, they take
everything at once and ask nothing — that mode is for agents and scripts.
`)
}
@@ -99,6 +108,22 @@ func Main() int {
fmt.Fprintln(os.Stderr, "cannot determine the current directory:", err)
return int(Usage)
}
env := Env{Dir: dir, Out: os.Stdout, Err: os.Stderr}
env := Env{
Dir: dir,
In: os.Stdin,
Out: os.Stdout,
Err: os.Stderr,
Interactive: terminal(os.Stdin),
}
return int(Run(env, os.Args[1:]))
}
// terminal reports whether a file is a character device, which is as close as
// the standard library gets to asking whether a person is on the other end.
func terminal(f *os.File) bool {
info, err := f.Stat()
if err != nil {
return false
}
return info.Mode()&os.ModeCharDevice != 0
}