New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

worktable.cc/pkgz/cli

Package Overview
Dependencies
Versions
2
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

worktable.cc/pkgz/cli

Go Modules
Version
v0.0.0-20261021102818-20a616bba714
Version published
Created
Source

cli

Небольшой пакет для 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.

API

ЭлементНазначение
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

Package last updated on 21 Oct 2026

Related posts