Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 19 из 25 ~14 мин +50 XP

Генерация документации: man и markdown

cobra generate docs умеет всё сама: пакет github.com/spf13/cobra/doc превращает дерево команд в man-страницы и markdown-файлы за один вызов, из тех же Long и Short, что вы уже написали для справки. Здесь разберём GenMarkdownTree, GenManTree, служебную docs-команду в проекте и правило хранения сгенерированных файлов.

Содержание урока · 10 разделов

Флаг --help выручает за терминалом, но не на сайте проекта, не в man-пейджере и не в тексте для новых сотрудников. У всех крупных утилит на Cobra — gh, kubectl, hugo — есть сайт с документацией по каждой команде и man-страницы в дистрибутивных пакетах, и ни одна команда не пишет это руками: тексты справки переливаются в документацию автоматически. Источник один — поля Use, Short, Long и Example, которые вы заполняли с первого урока; из них пакет doc собирает и markdown для сайта, и troff для man. Подход снимает главный страх документации — рассинхрон: справка и страницы обновляются одним релизом, потому что физически один и тот же текст. Сюда же примыкает и README: его раздел про команды собирается из тех же Long, а не из отдельного файла, который умеет устаревать к каждому релизу. В этом уроке добавим к утилите notes служебную команду docs, посмотрим, какие файлы получаются и где их хранить, и обсудим, что делать с README, который живёт рядом с генерацией.

cobra generate docs: пакет spf13/cobra/doc#

Генераторы живут в отдельном модуле github.com/spf13/cobra/doc — он ставится go get и тянет зависимости только для man: markdown-генератору достаточно стандартной библиотеки. Основные функции: doc.GenMarkdownTree(cmd, dir) рекурсивно обходит дерево команд от корня и пишет один markdown-файл на команду, doc.GenManTree(cmd, header, dir) делает то же для man-страниц, есть и yaml-вариант для дата-центричных конвейеров. Имена файлов выводятся из пути команды: для notes list получится notes_list.md, для вложенной notes list done — notes_list_done.md. Скрытые и недоступные команды в обход не попадают: completion и ваши Hidden-хелперы останутся внутренним делом утилиты, что обычно и нужно. Содержимое страницы — Long как вступление, usage с флагами, алиасы, примеры; то есть качество документации на выходе равно качеству полей справки. Плохой Long в исходнике не станет хорошей страницей на сайте.

Служебная команда docs

GO
var docsCmd = &cobra.Command{
        Use:    "docs",
        Short:  "Генерирует markdown-документацию",
        Hidden: true,
        RunE: func(cmd *cobra.Command, args []string) error {
                return doc.GenMarkdownTree(cmd.Root(), "./docs")
        },
}

func init() {
        rootCmd.AddCommand(docsCmd)
}

Команда спрятана: генерация — забота сборочного процесса, а не пользователя. cmd.Root() даёт корень дерева, GenMarkdownTree обходит его целиком и создаёт каталог, если его нет. RunE возвращает ошибку генератора наружу — коды выхода и обработка ошибок работают как у любой команды, а вызов notes docs в Makefile закрывает весь раздел документации.

Проверка: что попадёт в документацию

В проекте есть скрытая команда gen-completion и стандартная команда completion. Что о них напишет doc.GenMarkdownTree?

Какой файл получится

Прочитайте код и выберите, что он выведет:

// дерево команд: notes -> list -> done
err := doc.GenMarkdownTree(rootCmd, "./docs")
// имя файла для самой глубокой команды?

Man-страницы: GenManTree и section 1#

Man-страница — формат troff, который читает команда man; для CLI-утилит это раздел 1, пользовательские программы. Заголовок задаётся структурой doc.GenManHeader: Title — имя утилиты капсом, NOTES, Section — строка раздела, «1». На выходе GenManTree кладёт в каталог файлы вида notes.1 и notes-list.1 — с дефисами, в отличие от подчёркиваний в markdown. Установка — копирование в /usr/share/man/man1/ с сжатием gzip, после чего man notes открывает полноценную страницу с секциями SYNOPSIS и EXAMPLES, собранными из Use и Example. Сгенерированные страницы ставят в пакеты: deb и rpm без man-страницы выглядят небрежно. Внизу каждой страницы и markdown-файла генератор печатает строку об автогенерации с датой; она удобна для аудита, но мешает диффам между релизами, и cmd.DisableAutoGenTag = true у корня отключает её для всего дерева. Секции страницы собираются сами: NAME — из Use, SYNOPSIS — из usage, DESCRIPTION — из Long, EXAMPLES — из Example. Своей секции вставить некуда: если нужен блок «Безопасность» или «Совместимость», он живёт абзацем внутри Long.

Генерация markdown и man одной командой

GO
var docsCmd = &cobra.Command{
        Use:    "docs",
        Hidden: true,
        RunE: func(cmd *cobra.Command, args []string) error {
                if err := doc.GenMarkdownTree(cmd.Root(), "./docs/md"); err != nil {
                        return fmt.Errorf("markdown: %w", err)
                }
                header := &doc.GenManHeader{Title: "NOTES", Section: "1"}
                if err := doc.GenManTree(cmd.Root(), header, "./docs/man"); err != nil {
                        return fmt.Errorf("man: %w", err)
                }
                return nil
        },
}

Обе ошибки оборачиваются через %w с контекстом: в логе сборки будет видно, какой формат упал. Заголовок man вынесен в переменную — Title капсом по конвенции man, Section 1 для пользовательских утилит. Каталоги docs/md и docs/man разнесены, чтобы CI-джоба публиковала их по разным путям без разборки содержимого.

Где хранить сгенерированные документы#

Первое решение — коммитить или нет. Коммит сгенерированных markdown-страниц даёт читабельные диффы и сайт на gh-pages без CI, но каждая правка справки раздувает pull request сотнями строк и порождает конфликты при параллельной работе. Генерация в CI чистит диффы, но требует пайплайна. Разумная середина для CLI: markdown — коммит, man — генерация при сборке пакета. Отдельный вопрос — хостинг: страницы из GenMarkdownTree без подготовки едут на любой генератор сайтов, но выглядят сырыми — без оглавления и перекрёстных ссылок между разделами. README-секцию про команды разумно собирать из тех же Long: небольшой скрипт поверх doc.GenMarkdownTreeCustom с linkHandler расставит ссылки между страницами под конкретный генератор сайта — hugo, mkdocs, docusaurus. Требование у всех схем одно: Long в исходнике — единственный источник текста. Появился второй абзац «того же смысла» в README, и через два релиза они разъезжаются.

GenMarkdownTreeCustom: своя шапка и ссылки#

Обычный генератор пишет файлы без всяких изменений, а сайты обычно ждут служебной шапки: frontmatter с заголовком и датой для hugo, навигационные метаданные для mkdocs. Для этого у пакета есть GenMarkdownTreeCustom — та же генерация с двумя хуками. filePrepender вызывается перед содержимым каждого файла и возвращает строку — сюда кладут frontmatter: имя команды, заголовок, дату релиза. linkHandler получает имя файла ссылки и возвращает URL: под hugo ссылка на notes list переписывается из notes_list.md в /notes/notes_list/, под репозиторий — в относительный путь. Через те же хуки удобно вставлять обложки, бейджи версий и баннеры. Порядок действий простой: сначала гоняют обычный генератор и смотрят на диффы сайта, потом переносят отличия в два хука — и оба остаются в одном файле, который тоже коммитится.

Docs-команда, Makefile и CI#

Скрытая команда docs из блоков выше — не единственное место для генерации; выбирайте по размеру проекта. Малому CLI хватает вызова notes docs из Makefile: цель docs в две строки, разработчик обновляет страницы перед коммитом. Средним проектам удобнее go generate: файл gen.go с директивой рядом с командами, IDE и make invoke одинаково. Крупным — генерация в CI: джоба после сборки вызывает утилиту, артефакты уезжают на сайт и в пакет; локальный запуск не обязателен. Признак зрелой схемы — генерация man и markdown в одном пайплайне с completion-скриптами: все производные артефакты справки обновляются одним тегом релиза. Что точно не стоит делать — держать три места правды: Makefile, CI и скрытую команду с разными путями вывода; рано или поздно они разъедутся, и на сайте появится документация двух версий. Дешёвая страховка в CI: после генерации гоняйте git diff --exit-code по каталогу docs — документация разъехалась со справкой, джоба покраснеет до релиза.

Чек-лист документации перед релизом#

  • Long заполнен у всех видимых команд: пустые Long превращаются в пустые страницы сайта.
  • Example есть хотя бы у корня и тяжёлых подкоманд — в man он станет секцией примеров.
  • notes docs проходит чисто: ошибок генерации нет, файлы перезаписались без конфликтов.
  • Скрытые команды не попали в обход — проверьте отсутствие completion и gen-* в docs/.
  • Решение по DisableAutoGenTag принято и зафиксировано: с датой или без, но одинаково у всех страниц.
  • README не редактировался руками рядом со сгенерированными файлами — правки только через Long.

Проверка: раздел man для CLI

В каком разделе man живут пользовательские программы вроде вашей утилиты?

Соберите вызов GenManTree

Впишите поля заголовка man-страницы.

header := &doc.GenManHeader{ : "NOTES", : "1", } err := doc.GenManTree(rootCmd, header, "./docs/man")

Соберите скрипт генерации man-страниц

Расставьте: заголовок, вызов генератора с обработкой ошибки, вывод ошибки и аварийный выход, финальное сообщение.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Карточки: документация

Термин

spf13/cobra/doc

нажмите, чтобы перевернуть
Определение

Отдельный модуль с генераторами документации: GenMarkdownTree, GenManTree, GenYamlTree — обходят дерево команд и пишут по файлу на команду.

1 / 4

Что дальше#

Документация готова к релизу; осталась защита от регрессий: тестирование команд CLI — следующий урок, где Execute прогоняется в памяти с буферами вместо stdout. Собирает всё в пакет урок про сборку и goreleaser: там man-страницы и completion-скрипты ложатся в релизные артефакты, а структура большого проекта покажет, где в реальном репозитории живёт файл с docs-командой. Обзор курса — на странице Cobra. Практика: сгенерируйте markdown и man для своей утилиты, откройте man-страницу через man ./docs/man/notes.1 и убедитесь, что Example попал в секцию примеров.

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Вывод

Запустите код, чтобы увидеть вывод.

Коротко: что запомнить

  • Пакет spf13/cobra/doc генерирует markdown, man и yaml из тех же Long и Short, что и справка.
  • GenMarkdownTree пишет один файл на команду; скрытые и недоступные команды пропускаются.
  • Имя markdown-файла — путь команды с подчёркиваниями: notes_list_done.md; man-файлы — с дефисами: notes-list.1.
  • GenManHeader задаёт Title капсом и Section "1" для пользовательских утилит.
  • Сгенерированные файлы не редактируются руками: правится Long и повторяется генерация.
  • Схема хранения: markdown — коммит, man — генерация при сборке пакета; кастомные ссылки — через GenMarkdownTreeCustom.
  • README-секция про команды собирается из тех же Long — единственного источника текста.

Домашнее задание

Добавьте к своей утилите скрытую команду docs с генерацией markdown и man: GenMarkdownTree в docs/md и GenManTree с заголовком Title капсом и Section 1 в docs/man. Прочитайте страницу через man, проверьте, что скрытые команды не попали в обход, и добавьте DisableAutoGenTag, посмотрев, как после этого выглядят диффы между двумя генерациями. И последняя проверка: поменяйте Short одной команды, перегенерируйте и убедитесь, что diff по docs/md показывает ровно одну изменённую страницу.

Личные заметкиПусто
Хранятся только в вашем браузере (Cobra · Генерация документации: man и markdown)

Вопросы и ответы по уроку

Как сгенерировать документацию для Cobra-проекта?

Поставьте модуль github.com/spf13/cobra/doc и вызовите doc.GenMarkdownTree(rootCmd, "./docs") для markdown или doc.GenManTree(rootCmd, header, dir) для man-страниц. Обычно это делает скрытая команда вроде notes docs, которую дергают Makefile и CI. Один файл создаётся на каждую доступную команду, скрытые пропускаются.

Нужно ли коммитить сгенерированные документы?

Зависит от пайплайна. Коммит markdown даёт читабельные диффы и сайт без CI, но шумит в pull request и конфликтует при параллельной работе; генерация в CI чище, но требует пайплайна. Частая схема: markdown в репозитории, man — при сборке пакета. В любом случае сгенерированные файлы не редактируют руками — правят Long и перегенерируют.

Почему man-страница не открывается по имени утилиты?

Файл должен лежать в каталоге man-пейджера, например /usr/share/man/man1/notes.1.gz, и иметь правильное расширение раздела. Для локальной проверки сгенерируйте страницу и откройте напрямую: man ./docs/man/notes.1. Не забудьте задать GenManHeader с Title капсом и Section "1" — иначе страница получит дефолтный заголовок.

Термины урока

Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.

Сравните с альтернативами

Практический вопрос после первых уроков: а почему именно эта библиотека? Мы разобрали конкурентов по одним критериям, с кодом одной задачи рядом и персональным подбором под ваши приоритеты.

Следующая ступень учебного пути

Bubble Tea25 уроков~10 чпрактика25 уроков уже доступны

Bubble Tea: TUI-приложения в терминале

Интерактивный курс Bubble Tea — TUI-фреймворка на Go: Elm-архитектура, bubbles, Lip Gloss, мышь и клавиатура. 25 уроков с примерами и итоговым приложением.

  • Понимать Elm-архитектуру: Model, Update, View, Init
  • Обрабатывать клавиатуру и мышь в терминале
  • Писать асинхронные операции через tea.Cmd, Batch и Sequence
  • Использовать bubbles: list, textinput, textarea, table, viewport, progress, spinner