Cli
Ранее я всегда рекомендовал вместо spf13/cobra использовать urfave/cli а конкретно urfave/cli/v2
. Пришло время переобуться.
В чем проблема с spf13/cobra
: ужасный апи, основанный на мутировании переменных. Пошло это видимо из flag в котором обьявляешь глобальную переменную, а потом биндишь к ней флаг. Разумеется это никак не композируется, поэтому надо бы как то разделять команды и флаги и распихивать их по пакетам, но в кобре это выглядит как куча функций настройки команд и флагов с использованием init только чтобы добавить себя в родительскую команду. В общем нифига не композируемо и не удобно и используются странные практики. Им даже пришлось сделать генератор проекта с либой, а то вообще не понятно, как этим пользоваться.
Почему не urfave/cli
: нужно повторять имена флагов которые являются просто строковыми литералами и никто их кроме тебя не проверит. То есть тебе надо обьявить флаг cli.StringFlag с Name: "name" и потом использовать как ctx.String("name"). И НИКТО не поругается если ты сделаешь ctx.Int("name"), ctx.String("mane") или даже ctx.Uint64("aboba") когда этот флаг даже не обьявлен. В частности это ведет к той же проблеме переиспользуемости. Композировать команды можно просто великолепно: родительская команда декларирует список подкомманд, которые она включает и все. С флагами все печальнее, т.к. их надо обьявлять в двух местах: списке флагов и функции Action. И если структуру флага можно переиспользовать, получение флага в Action придется отслеживать руками. Ну либо опять биндить на глобальные переменные и в помойку летит изолированность комманд друг от друга. Другой момент про изолированность: команда декларирует свое имя. Почему это плохо: пусть CmdRoot хочет использовать CmdGitInit и CmdK8sInit, но проблема в том, что обе подкоманды "init" и не получится использовать обе. Это как если бы вы хотели использовать две библиотеки с одинаковым именем, но никто вам не дает возможности их заалиасить и поменять имя. Еще одна проблема с urfave/cli
в автокомплишне. Его нет. В доке что-то написано про это, но я хуй знает кем надо быть, чтобы в этом разобраться. Плюс кастомные комплишны как я понимаю придется делать через парсинг команд заново. Например если я хочу сделать флаг --branch и в комплишне к нему показывать все ветки, мне придется в подкоманде искать этот флаг, искать значение после него, и только потом искать и показывать ветки. Но схуяли я это должен делать, если я пользуюсь cli либой именно для того чтобы она парсила аргументы за меня. Замечу, что в spf13/cobra с комплишнами все гораздо лучше и можно спокойно их обьявлять на отдельные аргументы.
Что же тогда использовать? Самый подходящий вариант из которых я видел на данный момент: jessevdk/go-flags но у него есть свои минусы(ну сколько можно блять). Перечислю их сразу:
- принтит сообщения ошибок в консоль, не спрашивая разрешения, если вы будете хендлить ошибки через if err := flags.Run(); err != nil { log.Fatal(err) }
ошибка будет напечатана два раза.
- лишний функционал с неймспейсами, енвами и ini конфигурацией. Хз зачем это, конфиги я могу и сам распарсить, и не только ini, польза мапать в них флаги сомнительна.
- help некастомизируемый от слова никак, придется жить с тем текстом который зашит в либе
- неочевидно, как начать пользоваться либой, куча паблик символов из которых 90% либо не нужны либо повторяют функционал
- entrypoint функция и методы Execute используют остатки позициональных аргументов, хотя их можно аналогично флагам получить через поля структуры
- из дочерней команды нельзя получить флаги родительской команды (мб и можно, хз, судя по дизайну нельзя), кмк не минус вообще, т.к. меньше проблем у клиента и разраба либы разбираться куда какой флаг надо запихивать и где он используется/не используется
Теперь к плюсам и почему я советую все таки брать ее вместо spf13/cobra
или urfave/cli
(пока не написал свой форк с фиксами проблем выше):
- конфигурация команд определяется декларативно, для этого используются структуры с field тэгами и методы этих структур
- имя подкоманды определяет родительская команда тегом command:"name"
- структуры флагов спокойно переиспользуются, достаточно их заэмбедить, если нужно точно повторение, либо сделать поле с типом флага и проставить другие теги
- структура команды получается строго типизированной, невозможно использовать неопределенные флаги либо флаг одного типа под другим типом
- можно определять кастомные флаги через UnmarshalFlag
и сразу же там валидировать (в либе есть отдельный метод для этого, хз зачем) и получать любое значение: хендлер на файл, коннект к бд, etc.
- можно определять кастомные комплишны на флаги. Комплишн подкоманд и названий флагов работает из коробки, для своих флагов достаточно определить метод Complete(prefix string) []flags.Completion
и использовать простейший баш скрипт из репы, чтобы они заработали
Постом чуть выше я нарисовал как эта либа парсит аргументы и как это мапается на структуры команд. Хотя это больше к документации моего форка, который хз когда будет готов, приходится переписывать либу с нуля, ибо в коде оригинальной либы я не разобрался.