Содержание урока · 11 разделов
- AddGroup и GroupID: группы команд в справке
- Aliases: несколько имён одной команды
- «Did you mean this?»: автоподсказки опечаток
- Как Cobra ищет команду: поиск и кандидаты
- Deprecated и Hidden: уход команды на пенсию
- Четыре механизма рядом
- Сводка: когда что применять
- Что дальше
- Песочница урока
- Коротко: что запомнить
- Вопросы и ответы
Утилита растёт, и вместе с ней растут две боли. Первая: help корня превращается в простыню из двадцати команд подряд, где setup живёт рядом с ежедневными операциями, и найти нужное — работа. Вторая: пользователь печатает notes ad вместо add, получает unknown command и либо перечитывает справку, либо уходит писать issue. Обе решаются штатными средствами Cobra, без самодельных обработчиков: секции в справке дают группы, дополнительные имена — алиасы, а опечатки ловит встроенный движок подсказок. Оба симптома видны невооружённым глазом на одном экране --help, и оба лечатся за вечер, если знать четыре поля и два метода. Отдельный случай — команды на пенсии: переименование нельзя делать рубильником, пользователи держат скрипты на старых именах, и тут нужен управляемый период устаревания. Разберём все четыре механизма в порядке появления в жизни утилиты.
AddGroup и GroupID: группы команд в справке#
Группа объявляется структурой cobra.Group: ID — внутренний идентификатор без пробелов, Title — человекочитаемый заголовок, по конвенции Cobra заканчивающийся двоеточием. rootCmd.AddGroup(&cobra.Group{ID: "core", Title: "Работа с заметками:"}) создаёт секцию, а поле GroupID у команды привязывает её к секции по ID. В справке корня сначала печатаются команды из групп — каждая под своим заголовком, — затем оставшиеся в списке Additional Commands. Команды без группы не пропадают: они просто собираются в общий хвост. На выполнение, автодополнение и поиск команды группы не влияют — это чистая косметика справки, и заводить их имеет смысл, когда команд набирается восемь и больше: раньше секции только шумят, потом спасают. Порядок групп на странице — порядок AddGroup; порядок команд внутри группы — алфавитный, поменять его вручную нельзя. Поэтому в Title не пишут приоритеты вроде «новое» — единственный способ управлять порядком секций, это порядок их регистрации в коде.
AddGroup и GroupID на корне
GOvar rootCmd = &cobra.Command{
Use: "notes",
Short: "Менеджер заметок",
}
func init() {
rootCmd.AddGroup(&cobra.Group{
ID: "core",
Title: "Работа с заметками:",
})
rootCmd.AddGroup(&cobra.Group{
ID: "setup",
Title: "Настройка:",
})
addCmd.GroupID = "core"
listCmd.GroupID = "core"
initCmd.GroupID = "setup"
rootCmd.AddCommand(addCmd, listCmd, initCmd)
}Два вызова AddGroup создают секции, GroupID привязывает команды по ID. В --help корня сначала пойдёт «Работа с заметками:» с add и list, затем «Настройка:» с init. ID задаётся латиницей без пробелов, а Title по конвенции kubectl и gh заканчивается двоеточием — так заголовок выглядит секцией, а не командой. Забытый GroupID оставит команду в Additional Commands, ничего не сломав.
Проверка: группа без регистрации
У команды задан GroupID: "core", но вызов AddGroup с ID "core" забыли. Что произойдёт?
Aliases: несколько имён одной команды#
Aliases — срез строк с дополнительными именами. serveCmd.Aliases = []string{"start", "run"} — и утилита отвечает на все три: notes serve, notes start, notes run. Алиас полноценный: он находится при поиске команды наравне с основным именем, работает во вложенных путях и показывается в справке первой строкой — «run, start — Запустить сервер». Живут алиасы для трёх случаев: короткие формы частых команд, исторические имена до переименования и забота о моторной памяти пользователей соседних утилит. Мера нужна и здесь: десять алиасов на команду превращают справку в телефонную книгу, а новое имя не добавляют «на всякий случай» — каждое живёт в документации и в багтрекере. Отдельная тонкость: в bash-автодополнение по умолчанию попадает только основное имя, так что обещать пользователю «все имена работают везде» не стоит — обещайте основное, а алиасы называйте удобным бонусом. И держите алиасы стабильными: имя, которое вы дали и убрали, ломает чужие скрипты так же надёжно, как удаление команды.
Aliases и SuggestFor в одной команде
GOvar serveCmd = &cobra.Command{
Use: "serve",
Aliases: []string{"start", "run"},
Short: "Запустить сервер заметок",
SuggestFor: []string{"server", "srv"},
RunE: func(cmd *cobra.Command, args []string) error {
return runServer()
},
}Aliases даёт два дополнительных имени: notes start и notes run выполнят ту же команду, в справке появится строка «run, start». SuggestFor — явные подсказки: пользователь напечатал server или srv, и Cobra предложит serve, даже если до порога расстояния Левенштейна далеко. RunE один — поведение идентично при любом имени.
Что случится с notes srv
Прочитайте код и выберите, что он выведет:
// serveCmd объявлен как выше:
// Use "serve", Aliases ["start", "run"],
// SuggestFor ["server", "srv"]
_ = rootCmd.Execute()
// Вызвано: notes srv«Did you mean this?»: автоподсказки опечаток#
За подсказки отвечает движок в поиске команды: когда введённое имя не найдено, Cobra обходит соседей и считает расстояние Левенштейна между тем, что напечатали, и каждым именем. Порог задаёт SuggestionsMinimumDistance, по умолчанию 2; вдобавок работает префиксное совпадение: если имя начинается с введённого, подсказка сработает независимо от дистанции. Результат выглядит как знакомый по kubectl вывод: unknown command и ниже — Did you mean this? add. Порог поднимается одной строкой rootCmd.SuggestionsMinimumDistance = 3 для «толстых пальцев», но выше трёх залезать не стоит: подсказки начнут предлагать далёкие команды и перестанут помогать. Полное отключение — DisableSuggestions: true; для скриптов, где лишний вывод мешает парсингу, это оправдано, в человеческих утилитах — редко. Предложение ничего не выполняет: пользователь обязан перебить команду сам, за автозамену тут никто не отвечает.
Вывод при опечатке
GO$ notes ad "встреча в 15:00"
Error: unknown command "ad" for "notes"
Run 'notes --help' for usage.
Did you mean this?
addОшибка печатается в stderr, подсказка идёт после неё: имя с отступом, а при нескольких кандидатах заголовок меняется на «Did you mean this?» со списком. Подсказка не меняет поведение команды — она только текст; за саму ошибку и код выхода отвечает то, что мы разбирали в командах и будем усиливать в уроке про коды выхода.
Проверка: какой механизм сработает
Пользователь печатает notes rm — команды rm нет, но есть remove. Расстояние Левенштейна между rm и remove равно 4. Что нужно, чтобы Cobra предложила remove?
Как Cobra ищет команду: поиск и кандидаты#
Полезно понимать, где в этом механизме живёт поиск. Внутри Execute вызывается Find: он спускается по дереву команд, сопоставляя каждый следующий аргумент с именем или алиасом дочерних команд. Пока совпадение есть — путь продолжается; как только аргумент не совпал ни с одной командой, срабатывает ветка с suggestions: строится список кандидатов по дистанции, префиксу и SuggestFor, и всё это попадает в сообщение об ошибке. Отсюда два практических вывода. Первый: подсказки видят только команды, зарегистрированные в дереве через AddCommand, — незарегистрированная команда не подсказывается никогда. Второй: алиас участвует в поиске, но не в подсказках — кандидаты собираются по основному имени и SuggestFor, и это ещё один аргумент к тому, чтобы SuggestFor заполнять для команд с историческими именами.
Deprecated и Hidden: уход команды на пенсию#
Переименование команды — ритуал с тремя шагами, и Deprecated — первый. Полю Deprecated ставят человеческий текст: Deprecated: "используйте notes add". Cobra печатает предупреждение при каждом вызове, команда продолжает работать, а из справки и автодополнения исчезает: новые пользователи её не увидят, старые получают время на переезд. Второй шаг — через пару релизов поле снимается, а старое имя переезжает в Aliases: вызовы работают молча, без предупреждений. Третий — в мажорной версии алиас удаляется совсем. Поле Hidden: true — другой инструмент: команда жива и работает, но не показывается в справке и дополнении; так прячут внутренние команды для скриптов и отладочные хвосты. Не путайте смыслы: Deprecated — «уйдёт, переходите на новое», Hidden — «существует, но вам знать не положено». Первое про миграцию, второе про границы публичного интерфейса. Мой любимый порядок действий при переименовании: сначала Deprecated на старом имени, затем — в следующем релизе — перенос в Aliases, и только потом удаление; спешка на любом шаге превращается в чужие сломанные скрипты в CI.
Устаревающая команда с общим RunE
GOvar addOldCmd = &cobra.Command{
Use: "new",
Short: "Добавить заметку (устарела)",
Deprecated: "команда new устарела — используйте notes add",
Aliases: []string{"create"},
Hidden: false,
RunE: func(cmd *cobra.Command, args []string) error {
return runAdd(args)
},
}Deprecated печатает предупреждение перед выполнением и не мешает работе: скрипты на старом имени продолжают жить. Команда исчезает из --help и автодополнения, так что новички не начнут с устаревшего пути. RunE общий с новой командой — поведение идентично, разница только в уведомлении; это и делает миграцию предсказуемой.
Предупреждение об устаревании печатается перед выполнением и должно оставаться машинно-читаемым по месту: не пишите его в stdout и не украшайте цветом. Скрипты, парсящие результат команды, сломаются ровно в тот момент, когда вы предупреждали о переименовании. Разделение потоков и коды выхода при ошибках — тема урока про ошибки и коды выхода.
Четыре механизма рядом#
| Механизм | Что делает | На что не влияет |
|---|---|---|
| AddGroup + GroupID | секции в справке корня | выполнение и автодополнение |
| Aliases | дополнительные имена, работает поиск | автодополнение по умолчанию |
| SuggestFor | явные подсказки «Did you mean» | выполнение: srv не запустит serve |
| Deprecated | предупреждение + скрытие из справки | работоспособность команды |
| Hidden | скрытие из справки и дополнения | работоспособность команды |
Сводка: когда что применять#
Практическая раскладка по жизненному циклу утилиты выглядит так. Восемь команд и больше — заводите группы и назначьте GroupID каждой новой команде сразу, это дешевле задним числом. Частые команды с длинными именами — один-два алиаса, и только те, что сами используете. Растёт поток issue с опечатками — поднимите порог на единицу и добавьте SuggestFor для предсказуемых промахов. Переименование — Deprecated сегодня, алиас через два релиза, удаление в мажорной версии. Внутренние команды — Hidden с первого дня, пока они не обросли зависимостями. Каждый пункт проверяется одной командой локально: соберите утилиту, опечатайтесь, позовите старое имя и посмотрите --help — все четыре механизма видны на экране без отладчика. Нормальная скорость для утилиты среднего размера — вечер с чаем: группы и алиасы занимают минуты, Deprecated с миграцией — полчаса вместе с текстом предупреждения.
Соберите группу и алиасы
Впишите метод регистрации группы, ID и поле алиасов.
Соберите определение команды из шести строк
Расставьте: объявление, имена, алиас, подсказки, группа, закрытие структуры.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Карточки: группы, алиасы, подсказки
Что дальше#
Справка корня теперь сгруппирована, и настала очередь самой страницы справки: в уроке о кастомизации help и usage разберём шаблоны SetHelpTemplate, SetUsageFunc и секции Long и Example, из которых эта страница складывается. Подсказки «Did you mean» — про команды; про аргументы отвечает автодополнение, и ему посвящён отдельный урок про completion для bash, zsh и fish. Обзор курса — на странице Cobra. И стандартная оговорка: все четыре механизма видны только локально — соберите утилиту, опечатайтесь и позовите устаревшее имя; песочница терминальный ввод не эмулирует. Зато перенос этих механизмов в свой проект не требует ничего сверх уже установленной библиотеки: все четыре живут в полях структуры cobra.Command и двух методах корня.
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Запустите код, чтобы увидеть вывод.
Коротко: что запомнить
- AddGroup создаёт секцию справки, GroupID привязывает команду по ID; группы — косметика, на выполнение они не влияют.
- Команды без группы собираются в Additional Commands; порядок внутри группы алфавитный.
- Aliases даёт дополнительные имена: они работают в поиске и справке, но не попадают в автодополнение по умолчанию.
- SuggestFor добавляет явные подсказки «Did you mean» там, где расстояние Левенштейна больше порога.
- SuggestionsMinimumDistance по умолчанию 2; префиксное совпадение подсказывает независимо от дистанции.
- Deprecated печатает предупреждение, скрывает команду из справки и не ломает скрипты; Hidden молчит и просто прячет.
- Переименование — ритуал: Deprecated сегодня, Aliases через два релиза, удаление в мажорной версии.
Домашнее задание
Добавьте в свою утилиту две группы — «Работа с заметками» и «Настройка» — и разложите команды по GroupID. Затем сделайте алиасы для двух самых частых команд, а самую старую команду переведите в Deprecated с человеческим текстом предупреждения. Проверьте локально: --help показывает секции, опечатка вроде notes ad получает подсказку, устаревшее имя работает с предупреждением.
Личные заметкиПусто
Вопросы и ответы по уроку
Как сгруппировать команды в справке Cobra?
rootCmd.AddGroup(&cobra.Group{ID: "core", Title: "Основные команды:"}) и поле GroupID: "core" у каждой команды группы. Команды без группы попадут в Additional Commands. Группы влияют только на отображение справки.
Почему Cobra не предлагает команду при опечатке?
Либо расстояние Левенштейна больше порога SuggestionsMinimumDistance (по умолчанию 2), либо нет префиксного совпадения. Добавьте нужное имя в SuggestFor команды — это явный список подсказок, который работает независимо от дистанции.
Чем Deprecated отличается от Hidden?
Deprecated печатает предупреждение при каждом вызове и прячет команду из справки — это путь переименования с миграцией пользователей. Hidden молча скрывает команду из справки и автодополнения без предупреждений — это для внутренних и отладочных команд.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Сравните с альтернативами
Практический вопрос после первых уроков: а почему именно эта библиотека? Мы разобрали конкурентов по одним критериям, с кодом одной задачи рядом и персональным подбором под ваши приоритеты.
Изучайте дальше
Следующая ступень учебного пути
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