- suite rule дописывает правило: номер берётся следующим за наибольшим, блоки раскладываются в порядке норма, ПОЧЕМУ, ПРИМЕРЫ, --after ставит правило рядом с тем, которое оно уточняет - ступень называется категорией (requirement, prohibition, ...), а не словом языка, поэтому вызывающему не нужно знать, на каком языке записан набор - suite list показывает темы со слоями, а с осью — что возьмёт компонент; отбор слоёв вынесен в suite.Assemble, откуда его возьмут проектные команды - слои темы теперь всегда возвращаются базовым вперёд
139 lines
4.0 KiB
Go
139 lines
4.0 KiB
Go
// Package cli lays out the commands of the tool.
|
|
//
|
|
// The depth of a command reflects how often it runs and whom it addresses:
|
|
// project commands run in every repository and often, tending a suite runs in
|
|
// one repository and rarely. That is why `check` stays at the top level and
|
|
// whatever makes no sense in a project goes under `suite`. There are no
|
|
// synonyms: `convy suite pull` is not introduced next to `convy pull`.
|
|
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
)
|
|
|
|
// ExitCode is the exit status of the process.
|
|
type ExitCode int
|
|
|
|
const (
|
|
// OK means the check passed and the work is done.
|
|
OK ExitCode = 0
|
|
// Failed means the check found errors.
|
|
Failed ExitCode = 1
|
|
// Usage means the command was typed wrong or run in the wrong context.
|
|
Usage ExitCode = 2
|
|
)
|
|
|
|
// 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
|
|
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.
|
|
func Run(env Env, args []string) ExitCode {
|
|
if len(args) == 0 {
|
|
usage(env.Out)
|
|
return Usage
|
|
}
|
|
|
|
switch args[0] {
|
|
case "suite":
|
|
return runSuite(env, args[1:])
|
|
case "add", "pull", "list", "check":
|
|
fmt.Fprintf(env.Err, "the %q command is not implemented yet\n", args[0])
|
|
return Usage
|
|
case "help", "-h", "--help":
|
|
usage(env.Out)
|
|
return OK
|
|
default:
|
|
fmt.Fprintf(env.Err, "unknown command %q\n\n", args[0])
|
|
usage(env.Err)
|
|
return Usage
|
|
}
|
|
}
|
|
|
|
func runSuite(env Env, args []string) ExitCode {
|
|
if len(args) == 0 {
|
|
fmt.Fprintln(env.Err, "convy suite: a subcommand is required — init, add, rule, retire, list or check")
|
|
return Usage
|
|
}
|
|
switch args[0] {
|
|
case "check":
|
|
return runSuiteCheck(env, args[1:])
|
|
case "init":
|
|
return runSuiteInit(env, args[1:])
|
|
case "add":
|
|
return runSuiteAdd(env, args[1:])
|
|
case "rule":
|
|
return runSuiteRule(env, args[1:])
|
|
case "retire":
|
|
return runSuiteRetire(env, args[1:])
|
|
case "list":
|
|
return runSuiteList(env, args[1:])
|
|
default:
|
|
fmt.Fprintf(env.Err, "unknown subcommand %q for convy suite\n", args[0])
|
|
return Usage
|
|
}
|
|
}
|
|
|
|
// usage prints the help grouped under headings: a flat list hides the levels.
|
|
func usage(w io.Writer) {
|
|
fmt.Fprint(w, `convy — tending development conventions.
|
|
|
|
In a project:
|
|
convy add <topic> subscribe and assemble (not implemented)
|
|
convy pull reassemble what is subscribed (not implemented)
|
|
convy list what is wired up and available (not implemented)
|
|
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 rule add a rule: the next number, the blocks in order
|
|
convy suite retire retire a rule, a convention or a topic — never reusing it
|
|
convy suite list what the suite holds, and what a component would take
|
|
convy suite check suite integrity: prefixes, topics, axes, links, form
|
|
|
|
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.
|
|
|
|
`)
|
|
}
|
|
|
|
// Main is the entry point of the process.
|
|
func Main() int {
|
|
dir, err := os.Getwd()
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, "cannot determine the current directory:", err)
|
|
return int(Usage)
|
|
}
|
|
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
|
|
}
|