
Security News
Free Certified Patch for Critical Rails Active Storage Vulnerability
Socket has released a free Certified Patch for a critical Rails Active Storage vulnerability, giving developers time to upgrade to a secure Rails version.
Небольшой пакет для Go-приложений с флагами и одним уровнем команд. Флаги регистрируются через
pflag, их значения хранятся в структурах и передаются типизированным обработчикам. Справка,
версия, проверка значений и отмена по сигналам встроены в запуск приложения.
Путь импорта: worktable.cc/pkgz/cli. Имя Go-пакета: cli.
Требуется Go 1.27.3 или новее.
Для регистрации и разбора флагов используется github.com/spf13/pflag. Зависимость устанавливается
вместе с пакетом; её версия указана в go.mod.
После публикации модуля по его пути:
go get worktable.cc/pkgz/cli
package main
import (
"context"
"fmt"
"github.com/spf13/pflag"
"worktable.cc/pkgz/cli"
)
type Flags struct {
Name string
}
func (f *Flags) SetFlags(fset *pflag.FlagSet) {
fset.StringVarP(&f.Name, "name", "n", "world", "Имя")
}
func (f *Flags) Validate() error {
if f.Name == "" {
return fmt.Errorf("имя не должно быть пустым")
}
return nil
}
func run(ctx context.Context, flags *Flags) error {
fmt.Printf("Hello, %s!\n", flags.Name)
return nil
}
func main() {
cli.Single("hello", run, &Flags{}, cli.Version("1.0.0"))
}
hello --name Alice
hello -n Alice
hello --help
hello --version
Первый аргумент Single задаёт постоянное имя приложения в справке. Оно не вычисляется из
os.Args[0]. nil вместо обработчика включает вывод справки при обычном запуске; при этом тип
флагов можно указать явно: cli.Single[*Flags]("hello", nil, &Flags{}).
Глобальные флаги и флаги каждой команды имеют отдельные структуры. Все команды приложения используют один тип глобальных флагов; типы их собственных флагов могут различаться.
package main
import (
"context"
"fmt"
"github.com/spf13/pflag"
"worktable.cc/pkgz/cli"
)
type GlobalFlags struct {
Verbose bool
}
func (f *GlobalFlags) SetFlags(fset *pflag.FlagSet) {
fset.BoolVarP(&f.Verbose, "verbose", "v", false, "Подробный вывод")
}
func (*GlobalFlags) Validate() error { return nil }
type ServeFlags struct {
Port int
}
func (f *ServeFlags) SetFlags(fset *pflag.FlagSet) {
fset.IntVar(&f.Port, "port", 8080, "Порт сервера")
}
func (f *ServeFlags) Validate() error {
if f.Port < 1 || f.Port > 65535 {
return fmt.Errorf("недопустимый порт: %d", f.Port)
}
return nil
}
func serve(ctx context.Context, global *GlobalFlags, flags *ServeFlags) error {
fmt.Printf("verbose=%t port=%d\n", global.Verbose, flags.Port)
return nil
}
func main() {
cli.Multi(
"app",
nil,
&GlobalFlags{},
cli.Commands(
cli.NewCommand("serve", serve, &ServeFlags{},
cli.Example("app serve --port 8080\napp -v serve --port 9000")),
),
cli.Version("1.0.0"),
)
}
app # справка корня: корневой обработчик равен nil
app -v serve --port 9000 # verbose=true, port=9000
app serve --help # справка команды
app --help # справка корня
app --version # версия приложения
Вместо nil можно передать корневой обработчик func(context.Context, *GlobalFlags) error. Он
выполняется, когда команда не указана. При запуске команды корневой обработчик не вызывается: общую
проверку значений следует помещать в GlobalFlags.Validate.
В Multi можно передать nil или пустой срез команд. Тогда приложение работает без команд, как
Single.
| Элемент | Назначение |
|---|---|
Flags | Интерфейс SetFlags(*pflag.FlagSet) и Validate() error |
Arguments | Необязательный интерфейс SetArgs(*ArgSet) |
ArgSet | Регистрация описания и функции разбора через Parse(name, parse, usage) |
Run[F] | Корневой обработчик func(context.Context, F) error |
RunCmd[G, F] | Обработчик команды func(context.Context, G, F) error |
Single(app, run, globalFlags, opts...) | Запуск без команд |
Multi(app, run, globalFlags, cmds, opts...) | Запуск с необязательными командами |
NewCommand(name, run, commandFlags, opts...) | Создание Command[G] |
Commands(cmds...) | Сборка []Command[G] с выводом типа G |
Version(text) | Опция приложения: корневой флаг --version |
Output(writer) | Опция приложения: поток справки и версии корня и команд |
Usage(text) | Опция команды: описание в корневом блоке «Команды» |
Example(text) | Опция команды: секция «Примеры» |
Option и CommandOption — разные типы: Version и Output передаются в Single/Multi, а
Usage и Example — в NewCommand. Структуры настроек приватные; пользовательские функции опций
вне пакета не предусмотрены.
Создавайте команды через NewCommand: нулевое значение Command не является настроенной командой.
Имена команд должны быть непустыми и уникальными, сравниваются с учётом регистра. Проверка имён
выполняется до разбора аргументов, в том числе при запросе справки.
Если обработчик команды равен nil, команда выводит справку после успешного разбора флагов.
Например:
command := cli.NewCommand[*GlobalFlags]("serve", nil, &ServeFlags{})
При обычной функции-обработчике параметры типов выводятся автоматически. Альтернатива Commands —
явно типизированный срез []cli.Command[*GlobalFlags].
Описание команды в корневой справке задаётся через Usage:
cli.NewCommand("check", check, &CheckFlags{}, cli.Usage("Проверяет указанные файлы"))
В блоке «Команды» описания выравниваются по самому длинному имени. При пустом описании выводится только имя; порядок команд сохраняется.
При наличии команд порядок запуска такой:
app [глобальные флаги] команда [флаги команды]
Корневой разбор останавливается на первом аргументе, который не является флагом или значением флага. Этот аргумент выбирает команду. Оставшиеся аргументы разбираются только её набором флагов.
app --verbose serve --port 9000 # корректно
app serve --verbose # ошибка: verbose не зарегистрирован в serve
app --port 9000 serve # ошибка: port не зарегистрирован в корне
В обработчик команды передаются те же объекты глобальных и локальных флагов, которые использовались при регистрации и разборе. Обычно следует передавать указатели на структуры и создавать отдельный экземпляр флагов для каждой команды.
Глобальные и локальные флаги могут иметь одинаковые имена: их различает положение относительно имени команды. Справка команды показывает пользовательские глобальные флаги отдельной секцией. Эта секция не означает, что глобальные флаги можно указать после имени команды.
Позиционные аргументы поддерживаются, если структура Flags реализует необязательный интерфейс
Arguments:
type Arguments interface {
SetArgs(*ArgSet)
}
SetArgs регистрирует описание для справки и функцию разбора через ArgSet.Parse. Например:
func (f *CheckFlags) SetArgs(set *cli.ArgSet) {
set.Parse("<файлы...>", func(args []string) error {
f.Files = args
return nil
}, "Один или несколько файлов для проверки")
}
Parse только сохраняет регистрацию; повторный вызов заменяет её. Пакет вызывает зарегистрированную
функцию с остатком после разбора флагов, даже с пустым списком. Она сопоставляет строки полям и
преобразует их типы. Затем Validate проверяет все значения, включая сочетания флагов и аргументов.
Без зарегистрированной функции лишние аргументы остаются ошибкой; nil не поддерживает аргументы.
При справке и nil-обработчике SetArgs вызывается для получения описания, но функция разбора,
Validate и обработчик не вызываются. Версия не вызывает SetArgs. Регистрация не должна разбирать
значения или выполнять действия приложения.
Без команд аргументы получает структура глобальных флагов. При наличии команд первый позиционный
аргумент выбирает команду; её SetArgs регистрирует разбор остатка после командных флагов.
Глобальный SetArgs при запуске команды не вызывается; при запуске корневого обработчика функция
разбора получает пустой список. Глобальные позиционные аргументы перед именем команды не
поддерживаются.
name добавляется после флагов в строке использования, а usage выводится в блоке «Аргументы»:
Использование:
example [глобальные флаги] check [флаги команды] <файлы...>
Аргументы:
<файлы...> Один или несколько файлов для проверки
Без команд name добавляется после [флаги]. Пустое имя не добавляет пробел. При пустом usage
блок «Аргументы» не выводится; многострочное пояснение выводится с отступом. Корневая справка с
командами сохраняет общий синтаксис выбора команды и не выводит аргументы корня. Имя является
обычным текстом; пакет не проверяет по нему обязательность или количество аргументов.
-- завершает разбор флагов. Без команд корень разрешает перемежающийся разбор флагов; команды
используют настройки pflag, заданные в SetFlags.
В SetFlags доступны обычные возможности pflag: значения по умолчанию, сокращения,
пользовательские типы значений, скрытые флаги, нормализация имён и настройка SortFlags. При
наличии команд пакет принудительно отключает перемежающийся разбор корневых флагов после вызова
SetFlags.
--help и -h добавляются автоматически в корень и в каждую команду. Эти имена зарезервированы в
соответствующем наборе флагов. Нельзя регистрировать собственный help или сокращение h, в том
числе через нормализацию имён.
Version("1.0.0") добавляет --version только в корень. Пустая строка не добавляет этот флаг. При
включённой версии пользовательский глобальный version запрещён. Команда может зарегистрировать
собственный флаг version, но встроенная версия приложения через него недоступна.
Справка корня с командами выводит секции в порядке:
Multi, с описаниями из Usage;help и, если включён, version.Без команд после «Использование» выводится «Аргументы», если задан непустой usage, затем «Флаги».
Справка команды выводит:
Example;usage;help;Корневые встроенные help и version в секцию глобальных флагов команды не включаются. Если
доступных глобальных флагов нет, эта секция и соответствующий плейсхолдер использования отсутствуют.
Каждая строка примера получает отступ в два пробела; текст и переводы строк сохраняются.
Использование:
app [глобальные флаги] serve [флаги команды]
Примеры:
app serve --port 8080
app -v serve --port 9000
Справка и версия не вызывают функцию разбора аргументов, Validate и обработчики. Однако сначала
выполняется разбор флагов: неизвестный флаг или некорректное значение остаются ошибкой даже при
--help. При успешно разобранном запросе справки лишние позиционные аргументы игнорируются.
Корневой --help имеет приоритет над --version; версия без справки не допускает оставшихся
аргументов, включая имя команды.
Для корня порядок действий: регистрация и разбор флагов → необязательный globalFlags.SetArgs() →
разбор аргументов → globalFlags.Validate() → корневой обработчик.
Для команды: регистрация и разбор глобальных флагов → выбор команды → регистрация и разбор её флагов
→ необязательный commandFlags.SetArgs() → разбор аргументов → globalFlags.Validate() →
commandFlags.Validate() → обработчик команды.
Только выбранная команда регистрирует и проверяет собственные флаги. При ошибке следующая стадия не
выполняется. Если соответствующий обработчик равен nil, вместо валидации и выполнения выводится
справка.
SetFlags должен привязывать флаги к полям структуры, а Validate — проверять уже разобранные
значения. Не передавайте nil вместо объектов флагов или функций опций. Для приложения без
пользовательских флагов достаточно структуры с двумя пустыми методами:
type EmptyFlags struct{}
func (*EmptyFlags) SetFlags(*pflag.FlagSet) {}
func (*EmptyFlags) Validate() error { return nil }
Single и Multi предназначены для вызова из main. Они читают os.Args[1:] и создают контекст,
который отменяется при os.Interrupt (обычно SIGINT) или SIGTERM. Обработчик должен сам отслеживать
ctx.Done() или передавать контекст операциям, поддерживающим отмену. Отмена сама по себе не
прерывает обработчик и не задаёт код завершения.
Справка и версия по умолчанию выводятся в stdout. Output(writer) задаёт для них другой поток,
включая справку команд; например, cli.Output(os.Stderr) сохраняет stdout для результата обработки.
При ошибке пакет пишет в stderr строку Ошибка: <причина> и вызывает os.Exit(1). Если обработчик
завершился успешно, функция запуска возвращается. При os.Exit отложенные функции вызывающей
программы не выполняются.
Ошибки разбора флагов получают префикс разбор флагов:, аргументов — разбор аргументов:,
валидации — проверка значений:, обработчика — выполнение:. Причина сохраняется через %w внутри
пакета, но публичные функции запуска не возвращают ошибку вызывающему коду.
Публичного API для собственного контекста или среза аргументов нет. Output управляет только
справкой и версией. Вывод приложения из обработчиков контролируется самим приложением. Ошибки записи
справки и версии не возвращаются.
Пакет поддерживает один уровень команд. Вложенные команды, алиасы, completion и хуки до/после выполнения не предусмотрены. Формат справки фиксирован, заголовки и служебные сообщения написаны на русском языке.
Из корня модуля:
go test ./...
go vet ./...
go run ./example --help
go run ./example serve --help
go run ./example -v serve --port 9000
go run ./example check --path . README.md cli.go
Тесты проверяют разбор и передачу флагов, выбор команды, порядок валидации, справку и версию,
примеры, конфликты зарезервированных флагов, настройки pflag, отсутствие команд и ошибочные
аргументы.
FAQs
Unknown package

Security News
Socket has released a free Certified Patch for a critical Rails Active Storage vulnerability, giving developers time to upgrade to a secure Rails version.

Security News
/Threat Research
A malicious Ruby gem runs a credential-stealing worm during installation, poisons AI coding tools, and can wipe files when a stolen GitHub token is revoked.

Security News
GitHub is tightening GitHub Actions security with new restrictions on privileged pull request workflows. Here's what changes and how maintainers are affected.