- манифест читается так, как записан: решётка внутри строки не открывает комментарий, скобка внутри комментария не закрывает массив, имя внутри комментария не становится подпиской; новый ключ встаёт после массива, а не внутрь него - всё записываемое проходит через manifest.Quote — обратный слэш в пути делал файл, который инструмент сам не читает - маркер локальной части переехал в doc и пропускает огороженные блоки: процитированный в примере маркер больше не считается границей, а копия без маркера не перезаписывается молча - лишний позиционный аргумент отсекается: flag прекращал разбор и прятал флаги после себя, из-за чего pull, list и check игнорировали --for - заведены тесты проверок копий, включая молчание на исправной копии
convy
CLI для управления конвенциями разработки: ведёт набор конвенций и собирает копии в проектах.
Модель, которую инструмент реализует, описана не здесь, а в репозитории набора
(dev-conventions): README.md — устройство набора и копий, LANGUAGE.md —
форма правила, GUIDE.md — правила ведения набора, TOOL.md — решения об этом
инструменте. При расхождении истина там.
Термины
| Уровень | Что это |
|---|---|
| язык | как записывается правило: слова, версия, READING.md |
| набор | репозиторий с конвенциями, манифестом suite.toml и обвязкой |
| проект | репозиторий-потребитель с манифестом .conventions.toml |
| тема | набор правил об одном фокусе разработки; единица подписки |
| слой | один файл темы: базовый, языковой или стековый |
| компонент | адресат сборки в проекте: один язык, один стек, один вид приложения |
| префикс | четыре заглавные латинские буквы, адрес правила: GTIM-3 |
Тему и ось слоя объявляет шапка файла, а не путь: topic:, lang:, stack:.
Слой без ключей оси — базовый, он попадает в копию всегда.
Три уровня и ссылки между ними
Уровни стоят стопкой, и каждый нижний называет верхний ссылкой:
язык → слова, версия, короткое описание для читателя
набор → ссылается на язык: [language] version, lang, source
проект → ссылается на набор: source в .conventions.toml
Каждый уровень — набор файлов, а где эти файлы лежат, решает не модель, а ссылка. Реализовано два транспорта:
| Ссылка | Что это |
|---|---|
../dev-conventions, /srv/conventions |
директория на диске, как она лежит |
https://git.example.org/av/conventions.git |
git-репозиторий, клонируется |
file:///srv/conventions#v2 |
тот же репозиторий, но в закоммиченном виде |
Относительный путь считается от манифеста, который ссылку несёт. Хвост
#ветка, #тег или #коммит закрепляет ревизию и осмыслен только у git.
ssh:// и git@host:path работают тем же клонированием, но проверены хуже.
Клон делается заново на каждый вызов и удаляется. Кэш сэкономил бы второй клон и вернул бы вопрос, что в нём протухло, — а на «что было в прошлый раз» отвечает git в репозитории-потребителе.
Ключ [language] source в наборе пока обычно пуст: описание языка живёт в
самом наборе. Когда спецификация уедет в свой репозиторий, тот же ключ её
назовёт, и больше ничего не изменится.
Установка
Внешняя зависимость одна (BurntSushi/toml), сборка обычная:
go install git.vakhrushev.me/av/convy@latest
или из клона репозитория:
go build -o convy .
Способ раздачи готовых бинарей пока не выбран — вопрос открыт в TOOL.md.
Команды
В наборе:
convy suite init завести набор: директория и манифест
convy suite add завести конвенцию: файл, тема и префикс
convy suite rule дописать правило: следующий номер, блоки по порядку
convy suite retire снять правило, конвенцию или тему — без переиспользования
convy suite list что в наборе и что возьмёт компонент
convy suite check целостность набора: префиксы, темы, оси, ссылки, форма
В проекте:
convy init подключить конвенции: источник и первый компонент
convy add <тема> подписаться и собрать
convy pull пересобрать подписанное
convy list что подключено и что ещё есть в наборе
convy check проверить форму того, что здесь
Контекст определяется по манифесту рядом: suite.toml — набор,
.conventions.toml — проект. Наугад не делается ничего: команда не того уровня
отказывает и подсказывает нужную.
Два режима
Команды, которые что-то меняют, работают в двух режимах, и режим читается по командной строке.
Без аргументов — диалог. Каждое поле спрашивается с подсказкой о том, что туда кладут и почему. Это для человека.
$ convy suite add
The focus the rules are about: time, config, db-schema. A topic is the unit of
subscription — a consumer takes it whole. The name never changes and is never reused.
Topic: logging
It goes into the manifest and builds the table of conventions in a consumer's README.
One line about the topic: логирование: уровни, структура записи
...
С флагами — автоматика. Всё передаётся сразу, вопросов не задаётся, все недостающие поля называются разом. Это для агентов и скриптов.
$ convy suite add --topic logging --about "логирование: уровни" \
--prefix SLOG --title "Логирование"
Без терминала пустой вызов отказывает, а не виснет в ожидании ответа, которого некому дать.
Проверка набора
$ convy suite check
conventions/lang/go/logging.md
error:5 extends points at "arch/time.md" with topic "time", while the file
carries topic "logging": the layers of one topic declare one name [spread]
suite: 13 files, 8 topics, language version 1 (ru)
errors: 1, warnings: 0
Строка находки — уровень:строка что не так [семейство]. Семейств четыре:
- manifest — префиксы, темы, объявленные файлы, незарегистрированные файлы;
- form — форма правила: нумерация, блоки, модальные слова, словарь. Проверяется в любом файле, который язык употребляет;
- spread — то, что относится к отъезду к потребителю: тема в шапке, ось,
extends, пути канона, самодостаточность нормы. Только в конвенциях; - links — разрешение ссылок вида
PREFIX-N.
Третья часть списка проверок языка — взаимоисключительность строк таблицы, покрытие области действия, отвечает ли обоснование на «что сломается» — разбором текста не даётся и остаётся работой читателя.
Коды возврата: 0 — чисто, 1 — есть ошибки, 2 — команда набрана неверно или
не в том контексте.
Подключение в проект
$ convy init --source ../dev-conventions --component backend \
--dir backend/docs/conventions --lang go --stack sqlite
$ convy add time
$ convy add logging --for backend
init проверяет источник до того, как писать манифест: набор, до которого
никто не доберётся, проходит любую проверку и никому не помогает. Получается
.conventions.toml:
source = "../dev-conventions"
[components.backend]
dir = "backend/docs/conventions"
lang = ["go"]
stack = ["sqlite"]
topics = ["logging", "time"]
Компонент пишется всегда, даже когда он один; при нескольких команда без
--for не угадывает, а перечисляет имена. stack — список: sqlite и
postgres действуют вместе, это разные таблицы одного сервиса. Два языка не
действуют вместе никогда — ради этого компонент и заведён.
Копия плоская, файл на тему. Первый слой — сам документ; каждый следующий становится его разделом, и заголовки внутри опускаются на уровень: слой реализует и сужает базу, а не стоит рядом с ней. Строка о версии языка остаётся одна.
---
origin: time
---
# Время
...
### TIME-1. Единый формат — RFC 3339, UTC
...
## Время: реализация на Go
...
#### GTIM-1. «Сейчас» берётся у слоя хранилища
...
<!-- conv:local -->
Всё ниже <!-- conv:local --> принадлежит репозиторию и переживает pull;
всё выше перезаписывается. Рядом с копиями кладётся READING.md — он
приезжает с уровня языка. README.md в той же директории принадлежит проекту
и не трогается, а файл, у которого убрали origin:, перестаёт быть копией:
pull его не перезапишет и скажет почему.
Проверка проекта
convy check до набора не дотягивается и сети не требует: форма правила одна
и та же, локальные правила проекта на X-префиксах записаны по ней же.
Проверяются шапка origin:, маркер локальной части, нумерация по каждому
префиксу, блоки правила, словарь — и то, чего в наборе не бывает:
$ convy check
docs/conventions/time.md
error:22 rule XTIM-1 is a rule of the repository standing above the
marker: a reassembly would wipe it [spread]
project: 2 files in 1 component
errors: 1, warnings: 0
Язык копии определяется по строке о версии, которую она несёт: манифест рядом с ней не лежит, и больше сказать некому.
Ступени и словарь
Слова, которыми записаны модальность и метки, — свойство версии языка и
естественного языка набора, а не самого набора. Инструмент знает их сам, и
suite.toml их не дублирует: достаточно [language] version и lang.
Поэтому ступень называется категорией, а не словом:
--modality requirement | prohibition | recommendation | not-recommended | permission
prohibition в русском наборе превращается в **НЕ ДОЛЖЕН.**, в английском —
в **MUST NOT.**. Вызывающему не нужно знать, на каком языке записан набор.
По той же причине convy умеет написать строку о версии языка, и созданный им
файл проходит suite check без единой правки.
Чего инструмент не делает
- не сливает трёхсторонне и не разрешает конфликты: правка выше маркера локальной части теряется, и это заявленное поведение;
- не ведёт лок-файл: копии закоммичены, ответ на «что было в прошлый раз» даёт git;
- не хранит список подписчиков: подписка — свойство проекта;
- не переносит правки из проекта в набор: операция ручная и редкая;
- не перенумеровывает правила: номер — идентификатор, а не позиция;
- не кэширует источник: клон делается заново и удаляется;
- не знает нескольких наборов сразу:
sourceв проекте один.