манифесты стали данными, заведён convy sync
- убраны комментарии из suite.toml и .conventions.toml: файл, который машина переписывает, комментарий через круг не проносит; объяснения ушли в README рядом, который suite init теперь заводит - удалена текстовая правка манифеста целиком — 520 строк ручного лексера TOML вместе со всем классом ошибок порчи данных - запись идёт из структур энкодером; ключ, которого инструмент не знает, запись останавливает, а не теряется молча - convy sync сверяет манифест и подводит под него раскладку файлов: чего не хватает — собирает, что осиротело — удаляет, копию с локальной частью не трогает никогда
This commit is contained in:
+59
-39
@@ -10,52 +10,56 @@ import (
|
||||
"git.vakhrushev.me/av/convy/internal/manifest"
|
||||
)
|
||||
|
||||
// manifestSkeleton is what a suite starts as. The tables stand empty but
|
||||
// present: `convy suite add` splices entries into them, and a table that is not
|
||||
// there is a table an edit cannot find.
|
||||
// A suite starts as a manifest holding only what it knows about itself, and a
|
||||
// README next to it holding everything a person needs to know to fill it in.
|
||||
//
|
||||
// The two documents about the language are left commented out on purpose. The
|
||||
// suite is expected to carry them, but the tool cannot author them, and a
|
||||
// manifest pointing at a file that does not exist is a manifest that fails its
|
||||
// own check on the first run.
|
||||
const manifestSkeleton = `# The manifest of a conventions suite.
|
||||
#
|
||||
# Two manifests exist in the model, each named after what it describes:
|
||||
# suite.toml here, in the suite, describes the suite itself; .conventions.toml
|
||||
# in a project describes what that project subscribed to. Which of the two lies
|
||||
# next to you tells you where you are.
|
||||
// The two are split because the manifest is data the tool rewrites on every
|
||||
// `suite add` and every `suite retire`, while the README is prose nothing
|
||||
// touches. Keeping the explanations inside the manifest would mean losing them
|
||||
// the first time a command wrote the file.
|
||||
//
|
||||
// The README is a starting point, in the tool's own language, for its author to
|
||||
// replace. What the suite itself is about, only its author knows.
|
||||
const readmeSkeleton = `# Conventions suite
|
||||
|
||||
[language]
|
||||
version = %d
|
||||
lang = "%s"
|
||||
# description = "LANGUAGE.md" # the full account of the language, stays with the author
|
||||
# reading = "READING.md" # the short guide for a reader, travels into every copy
|
||||
The rules live in ` + "`conventions/`" + `. What each file is and what it may do is
|
||||
settled by the conventions language; this README is the place to say what this
|
||||
particular suite is for and how it is kept.
|
||||
|
||||
# ─── Topics ─────────────────────────────────────────────────────────────────
|
||||
#
|
||||
# A topic is a set of rules about one focus of development, and the unit of
|
||||
# subscription. The value is the one line about what the topic is for; the
|
||||
# table of conventions in a consumer's README is built out of it.
|
||||
## The manifest
|
||||
|
||||
[topics.live]
|
||||
` + "`suite.toml`" + ` holds the identity of the suite: the language its rules are
|
||||
written in, its topics and its rule prefixes. It is written by ` + "`convy`" + ` and
|
||||
carries no comments — a command rewrites the whole file, and a comment would not
|
||||
survive that. Explanations belong here instead.
|
||||
|
||||
# Retired names land here together with a reason and a date, so that they can
|
||||
# never be handed to another topic: the name lives on in foreign repositories.
|
||||
## Topics
|
||||
|
||||
[topics.retired]
|
||||
A topic is a set of rules about one focus of development — time, configuration,
|
||||
the database schema — and it is the unit of subscription: a consumer takes it
|
||||
whole. A name is never renamed and never reissued, because it lives on in
|
||||
foreign repositories: in the ` + "`origin:`" + ` header of every copy and in the
|
||||
subscription of every consumer.
|
||||
|
||||
# ─── Rule prefixes ──────────────────────────────────────────────────────────
|
||||
#
|
||||
# A prefix is four uppercase Latin letters, unique across the suite, chosen for
|
||||
# a file rather than derived by a formula. The letter X is reserved for the
|
||||
# local rules of consumers and is never taken here. Paths are given from the
|
||||
# root of the repository.
|
||||
Since the name is permanent, a topic is named after a decision and whom it
|
||||
addresses rather than after the role some part of today's project plays.
|
||||
|
||||
[prefixes.live]
|
||||
## Prefixes
|
||||
|
||||
# Prefixes of deleted and split files land here, likewise never to be reissued.
|
||||
A prefix is four uppercase Latin letters, unique across the suite, chosen for a
|
||||
file rather than derived by a formula. Rules are addressed by identifier —
|
||||
` + "`KEYS-5`" + ` — with no path, so the identifier survives a file moving between
|
||||
axes. A prefix is never reissued either.
|
||||
|
||||
[prefixes.retired]
|
||||
The letter X in first position is reserved for the local rules of consuming
|
||||
repositories. The suite never takes it, so a local prefix can never collide with
|
||||
a future one here.
|
||||
|
||||
## Retirement
|
||||
|
||||
Nothing leaves the manifest. A topic or a prefix that is done moves to the
|
||||
retired half together with a reason and a date, so that the name can never be
|
||||
handed to something else.
|
||||
`
|
||||
|
||||
func runSuiteInit(env Env, args []string) ExitCode {
|
||||
@@ -108,17 +112,33 @@ func runSuiteInit(env Env, args []string) ExitCode {
|
||||
return Usage
|
||||
}
|
||||
|
||||
body := fmt.Sprintf(manifestSkeleton, *version, given["lang"])
|
||||
name := filepath.Join(root, manifest.Name)
|
||||
if err := os.WriteFile(name, []byte(body), 0o644); err != nil {
|
||||
m := &manifest.Manifest{
|
||||
Language: manifest.Language{Version: *version, Lang: given["lang"]},
|
||||
Path: name,
|
||||
}
|
||||
if err := m.Save(); err != nil {
|
||||
fmt.Fprintln(env.Err, err)
|
||||
return Usage
|
||||
}
|
||||
|
||||
fmt.Fprintf(env.Out, "\ncreated %s\ncreated %s\n", name, filepath.Join(root, "conventions"))
|
||||
|
||||
// The README is written only when there is none: it is a starting point,
|
||||
// and a starting point that overwrites what somebody already wrote is not
|
||||
// one.
|
||||
readme := filepath.Join(root, "README.md")
|
||||
if !exists(readme) {
|
||||
if err := os.WriteFile(readme, []byte(readmeSkeleton), 0o644); err != nil {
|
||||
fmt.Fprintln(env.Err, err)
|
||||
return Usage
|
||||
}
|
||||
fmt.Fprintf(env.Out, "created %s\n", readme)
|
||||
}
|
||||
|
||||
fmt.Fprintf(env.Out, "\nthe suite speaks %s, conventions language version %d\n", given["lang"], *version)
|
||||
fmt.Fprint(env.Out, `
|
||||
next:
|
||||
rewrite README.md — it says what a topic and a prefix are, not what this suite is for
|
||||
write the two documents about the language and name them in [language]
|
||||
convy suite add add the first convention
|
||||
convy suite check verify the suite holds together
|
||||
|
||||
Reference in New Issue
Block a user