манифест набора переименован в .conventions-suite.toml
- оба манифеста стали данными, которые инструмент переписывает целиком, так что и наборный получает точку в начале: он служебный файл, а не документ репозитория - переименование механическое, одна константа manifest.Name; литералы в фикстурах и ссылки в README.md и CLAUDE.md обновлены следом
This commit is contained in:
@@ -34,7 +34,7 @@ CLI для управления конвенциями. Модель здесь
|
||||
```
|
||||
internal/lang словарь: реестр «версия языка × естественный язык»
|
||||
internal/source ссылки между уровнями: путь на диске, git-репозиторий
|
||||
internal/manifest suite.toml и .conventions.toml — чтение и запись
|
||||
internal/manifest оба манифеста: чтение и запись
|
||||
internal/doc разбор документа: шапка, области правил, блоки
|
||||
internal/suite сборка набора в память, отбор слоёв под компонент
|
||||
internal/project сборка копий в проекте: разделы, маркер, READING.md
|
||||
|
||||
@@ -13,7 +13,7 @@ CLI для управления конвенциями разработки: в
|
||||
| Уровень | Что это |
|
||||
|---|---|
|
||||
| язык | как записывается правило: слова, версия, `READING.md` |
|
||||
| набор | репозиторий с конвенциями, манифестом `suite.toml` и обвязкой |
|
||||
| набор | репозиторий с конвенциями, манифестом `.conventions-suite.toml` и обвязкой |
|
||||
| проект | репозиторий-потребитель с манифестом `.conventions.toml` |
|
||||
| тема | набор правил об одном фокусе разработки; единица подписки |
|
||||
| слой | один файл темы: базовый, языковой или стековый |
|
||||
@@ -90,7 +90,7 @@ go build -o convy .
|
||||
convy check проверить форму того, что здесь
|
||||
```
|
||||
|
||||
Контекст определяется по манифесту рядом: `suite.toml` — набор,
|
||||
Контекст определяется по манифесту рядом: `.conventions-suite.toml` — набор,
|
||||
`.conventions.toml` — проект. Наугад не делается ничего: команда не того уровня
|
||||
отказывает и подсказывает нужную.
|
||||
|
||||
@@ -194,9 +194,9 @@ topics = ["logging", "time"]
|
||||
|
||||
```
|
||||
$ convy suite add --topic time --about "время" --prefix TIME --title "Время"
|
||||
suite.toml holds 1 key the tool does not know (language.descriptoin); a write
|
||||
goes out of what the tool understands, so the key would be dropped — fix the
|
||||
spelling first
|
||||
.conventions-suite.toml holds 1 key the tool does not know
|
||||
(language.descriptoin); a write goes out of what the tool understands, so the
|
||||
key would be dropped — fix the spelling first
|
||||
```
|
||||
|
||||
## Манифест — источник истины
|
||||
@@ -278,7 +278,8 @@ errors: 1, warnings: 0
|
||||
|
||||
Слова, которыми записаны модальность и метки, — свойство версии языка и
|
||||
естественного языка набора, а не самого набора. Инструмент знает их сам, и
|
||||
`suite.toml` их не дублирует: достаточно `[language] version` и `lang`.
|
||||
`.conventions-suite.toml` их не дублирует: хватает `[language] version` и
|
||||
`lang`.
|
||||
|
||||
Поэтому ступень называется категорией, а не словом:
|
||||
|
||||
|
||||
@@ -74,10 +74,10 @@ type files map[string]string
|
||||
|
||||
func base() files {
|
||||
return files{
|
||||
"suite.toml": baseManifest,
|
||||
"LANGUAGE.md": "# Язык конвенций\n\nОписание языка.\n",
|
||||
"READING.md": reading,
|
||||
"conventions/time.md": baseTime,
|
||||
".conventions-suite.toml": baseManifest,
|
||||
"LANGUAGE.md": "# Язык конвенций\n\nОписание языка.\n",
|
||||
"READING.md": reading,
|
||||
"conventions/time.md": baseTime,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,14 +270,14 @@ func TestChecks(t *testing.T) {
|
||||
}, {
|
||||
name: "topic listed among the retired ones",
|
||||
setup: func(f files) {
|
||||
f["suite.toml"] = strings.Replace(baseManifest,
|
||||
f[".conventions-suite.toml"] = strings.Replace(baseManifest,
|
||||
"[topics.retired]", `[topics.retired]`+"\ntime = \"снята 2026-07-01\"", 1)
|
||||
},
|
||||
want: "is listed both live and retired",
|
||||
}, {
|
||||
name: "live topic without layers",
|
||||
setup: func(f files) {
|
||||
f["suite.toml"] = strings.Replace(baseManifest,
|
||||
f[".conventions-suite.toml"] = strings.Replace(baseManifest,
|
||||
`time = "время: хранение, зоны, форматы"`,
|
||||
`time = "время"`+"\nlogging = \"логирование\"", 1)
|
||||
},
|
||||
@@ -285,7 +285,7 @@ func TestChecks(t *testing.T) {
|
||||
}, {
|
||||
name: "declared file is missing",
|
||||
setup: func(f files) {
|
||||
f["suite.toml"] = strings.Replace(baseManifest,
|
||||
f[".conventions-suite.toml"] = strings.Replace(baseManifest,
|
||||
`TIME = "conventions/time.md"`,
|
||||
`TIME = "conventions/time.md"`+"\nSLOG = \"conventions/logging.md\"", 1)
|
||||
},
|
||||
@@ -299,7 +299,7 @@ func TestChecks(t *testing.T) {
|
||||
}, {
|
||||
name: "prefix starts with X",
|
||||
setup: func(f files) {
|
||||
f["suite.toml"] = strings.Replace(baseManifest,
|
||||
f[".conventions-suite.toml"] = strings.Replace(baseManifest,
|
||||
`TIME = "conventions/time.md"`, `XTIM = "conventions/time.md"`, 1)
|
||||
f["conventions/time.md"] = strings.ReplaceAll(baseTime, "TIME", "XTIM")
|
||||
},
|
||||
@@ -307,7 +307,7 @@ func TestChecks(t *testing.T) {
|
||||
}, {
|
||||
name: "one file with two prefixes declared",
|
||||
setup: func(f files) {
|
||||
f["suite.toml"] = strings.Replace(baseManifest,
|
||||
f[".conventions-suite.toml"] = strings.Replace(baseManifest,
|
||||
`TIME = "conventions/time.md"`,
|
||||
`TIME = "conventions/time.md"`+"\nGTIM = \"conventions/time.md\"", 1)
|
||||
},
|
||||
@@ -315,7 +315,7 @@ func TestChecks(t *testing.T) {
|
||||
}, {
|
||||
name: "unknown manifest key",
|
||||
setup: func(f files) {
|
||||
f["suite.toml"] = baseManifest + "\n[extra]\nkey = 1\n"
|
||||
f[".conventions-suite.toml"] = baseManifest + "\n[extra]\nkey = 1\n"
|
||||
},
|
||||
want: "is unknown to the tool",
|
||||
}, {
|
||||
@@ -362,21 +362,21 @@ func TestChecks(t *testing.T) {
|
||||
setup: func(f files) {
|
||||
// A bare non-ASCII key TOML rejects on its own; a quoted one
|
||||
// passes straight through, which is what the check is for.
|
||||
f["suite.toml"] = strings.Replace(baseManifest, "time =", `"время" =`, 1)
|
||||
f[".conventions-suite.toml"] = strings.Replace(baseManifest, "time =", `"время" =`, 1)
|
||||
f["conventions/time.md"] = strings.Replace(baseTime, "topic: time", "topic: время", 1)
|
||||
},
|
||||
want: "is not usable as a file name",
|
||||
}, {
|
||||
name: "topic name is not lower kebab-case",
|
||||
setup: func(f files) {
|
||||
f["suite.toml"] = strings.Replace(baseManifest, "time =", "Time_Zone =", 1)
|
||||
f[".conventions-suite.toml"] = strings.Replace(baseManifest, "time =", "Time_Zone =", 1)
|
||||
f["conventions/time.md"] = strings.Replace(baseTime, "topic: time", "topic: Time_Zone", 1)
|
||||
},
|
||||
want: "is not lower kebab-case",
|
||||
}, {
|
||||
name: "a second document without a topic",
|
||||
setup: func(f files) {
|
||||
f["suite.toml"] = strings.Replace(baseManifest,
|
||||
f[".conventions-suite.toml"] = strings.Replace(baseManifest,
|
||||
`TIME = "conventions/time.md"`,
|
||||
`TIME = "conventions/time.md"`+"\nMETA = \"GUIDE.md\"\nRULE = \"conventions/rules.md\"", 1)
|
||||
f["GUIDE.md"] = "---\nprefix: META\n---\n\n# Как мы ведём конвенции\n\n" + versionLine + "\n"
|
||||
@@ -392,7 +392,7 @@ func TestChecks(t *testing.T) {
|
||||
}, {
|
||||
name: "document without a topic carries layer keys",
|
||||
setup: func(f files) {
|
||||
f["suite.toml"] = strings.Replace(baseManifest,
|
||||
f[".conventions-suite.toml"] = strings.Replace(baseManifest,
|
||||
`TIME = "conventions/time.md"`,
|
||||
`TIME = "conventions/time.md"`+"\nGTIM = \"conventions/go.md\"", 1)
|
||||
f["conventions/go.md"] = "---\nprefix: GTIM\nlang: go\n---\n\n# Go\n\n" + versionLine + "\n"
|
||||
|
||||
@@ -93,7 +93,7 @@ prefix: SLOG
|
||||
|
||||
func layered() files {
|
||||
return files{
|
||||
"suite.toml": layeredManifest,
|
||||
".conventions-suite.toml": layeredManifest,
|
||||
"LANGUAGE.md": "# Язык конвенций\n\nОписание языка.\n",
|
||||
"READING.md": reading,
|
||||
"conventions/arch/time.md": archTime,
|
||||
@@ -136,7 +136,7 @@ func TestLayeredChecks(t *testing.T) {
|
||||
}, {
|
||||
name: "topic with two layers lacking axis keys",
|
||||
setup: func(f files) {
|
||||
f["suite.toml"] = strings.Replace(layeredManifest,
|
||||
f[".conventions-suite.toml"] = strings.Replace(layeredManifest,
|
||||
`GTIM = "conventions/lang/go/time.md"`,
|
||||
`GTIM = "conventions/second/time.md"`, 1)
|
||||
f["conventions/lang/go/time.md"] = ""
|
||||
@@ -273,7 +273,7 @@ func TestNoFalsePositives(t *testing.T) {
|
||||
}, {
|
||||
name: "the single document without a topic is the one the suite governs itself by",
|
||||
setup: func(f files) {
|
||||
f["suite.toml"] = strings.Replace(layeredManifest,
|
||||
f[".conventions-suite.toml"] = strings.Replace(layeredManifest,
|
||||
`SLOG = "conventions/arch/logging.md"`,
|
||||
`SLOG = "conventions/arch/logging.md"`+"\nMETA = \"GUIDE.md\"", 1)
|
||||
f["GUIDE.md"] = "---\nprefix: META\n---\n\n# Как мы ведём конвенции\n\n" + versionLine + "\n"
|
||||
|
||||
@@ -127,7 +127,7 @@ func TestAddInInteractiveMode(t *testing.T) {
|
||||
t.Errorf("the file lost %q:\n%s", want, body)
|
||||
}
|
||||
}
|
||||
if !strings.Contains(read(t, root, "suite.toml"), `logging = "логирование: уровни"`) {
|
||||
if !strings.Contains(read(t, root, ".conventions-suite.toml"), `logging = "логирование: уровни"`) {
|
||||
t.Error("the topic did not reach the manifest")
|
||||
}
|
||||
checkClean(t, root)
|
||||
|
||||
@@ -144,7 +144,7 @@ func TestRetirePrefixMovesTheNameAndTakesTheFile(t *testing.T) {
|
||||
t.Error("the file stayed behind")
|
||||
}
|
||||
|
||||
toml := read(t, root, "suite.toml")
|
||||
toml := read(t, root, ".conventions-suite.toml")
|
||||
if strings.Contains(toml, `SLOG = "conventions/logging.md"`) {
|
||||
t.Errorf("the prefix stayed in the live half:\n%s", toml)
|
||||
}
|
||||
@@ -179,7 +179,7 @@ func TestRetireTopicWaitsForItsLayers(t *testing.T) {
|
||||
t.Fatalf("retiring an empty topic returned %d: %s", code, out)
|
||||
}
|
||||
|
||||
toml := read(t, root, "suite.toml")
|
||||
toml := read(t, root, ".conventions-suite.toml")
|
||||
if strings.Contains(toml, `time = "время"`) {
|
||||
t.Errorf("the topic stayed in the live half:\n%s", toml)
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ particular suite is for and how it is kept.
|
||||
|
||||
## The manifest
|
||||
|
||||
` + "`suite.toml`" + ` holds the identity of the suite: the language its rules are
|
||||
` + "`.conventions-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.
|
||||
|
||||
@@ -30,9 +30,11 @@ import (
|
||||
)
|
||||
|
||||
// Name is the name of a suite manifest. Which of the two manifests lies next
|
||||
// to you tells you where you are: suite.toml means a suite, .conventions.toml
|
||||
// means a project.
|
||||
const Name = "suite.toml"
|
||||
// to you tells you where you are, and both start with a dot for the same
|
||||
// reason: a manifest is data the tool writes, not a document of the repository,
|
||||
// and it sits with the rest of the service files rather than among the
|
||||
// conventions themselves.
|
||||
const Name = ".conventions-suite.toml"
|
||||
|
||||
// DefaultLanguageCode is the suite's natural language when the manifest says
|
||||
// nothing about it. The key is optional on purpose: the vocabulary lives in the
|
||||
@@ -83,7 +85,7 @@ func (s *Section) Retire(key, note string) {
|
||||
s.Retired[key] = note
|
||||
}
|
||||
|
||||
// Manifest is a parsed suite.toml.
|
||||
// Manifest is a parsed suite manifest.
|
||||
type Manifest struct {
|
||||
Language Language `toml:"language"`
|
||||
Topics Section `toml:"topics,omitempty"`
|
||||
|
||||
@@ -118,13 +118,13 @@ func TestOpenGitTakesTheCommittedState(t *testing.T) {
|
||||
t.Skip("no git in PATH")
|
||||
}
|
||||
repo := t.TempDir()
|
||||
name := filepath.Join(repo, "suite.toml")
|
||||
name := filepath.Join(repo, ".conventions-suite.toml")
|
||||
if err := os.WriteFile(name, []byte("committed\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, args := range [][]string{
|
||||
{"init", "--quiet", "-b", "main"},
|
||||
{"-c", "user.email=t@example.org", "-c", "user.name=t", "add", "suite.toml"},
|
||||
{"-c", "user.email=t@example.org", "-c", "user.name=t", "add", ".conventions-suite.toml"},
|
||||
{"-c", "user.email=t@example.org", "-c", "user.name=t", "commit", "--quiet", "-m", "first"},
|
||||
} {
|
||||
cmd := exec.Command("git", args...)
|
||||
@@ -145,7 +145,7 @@ func TestOpenGitTakesTheCommittedState(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("cloning a local repository: %v", err)
|
||||
}
|
||||
body, err := os.ReadFile(filepath.Join(tree.Dir(), "suite.toml"))
|
||||
body, err := os.ReadFile(filepath.Join(tree.Dir(), ".conventions-suite.toml"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user