В данной статье расскажу как на Go создать консольную TUI утилиту с помощью готовых компонентов, покажу примеры интерфейсов и соберем приложение с помощью Goreleaser
То есть пройдем весь цикл создания приложения до его релиза
Все что будет описано в данной статье, я собрал в данном репозитории
https://github.com/deniskorbakov
Предыстория
Мне очень нравятся TUI, что с помощью него утилиты в терминале начинают играть новыми красками и повышается удовольствие от использования данных программ
Недавно с командой https://жыбийрыр.рф/ участвовали на хакатоне, где создавали утилиту (Go + Ansible) для автоматического деплоя и билда на сервере всех возможных приложений (антиутопическая задача)
Для данного решения так же использовал TUI компоненты для утилиты, что помогло нам больше выделиться среди участников, а в конечном итоге и победить
https://github.com/deniskorbakov/ivan
Что используем для создания утилиты
Cobra - https://github.com/spf13/cobra
Данная библиотека используется для создания консольных приложений на Go
Она предоставляет необходимый набор инструментария для создания утилит
Удобное API для создания команд и описание логики
Поддержка флагов
Подсказки при введении неправильной командой и тд
Данная библиотека изменяет описание утилиты делая ее еще более информативнее и ярче, с коробки интегрируется с Cobra
При добавлении Fang вы получаете следующий интерфейс:

Данная библиотека предоставляет набор готовых компонентов TUI и прекрасно вписываются в цветовую палитру Fang
Вот такие компоненты вы можете получить при использовании данной библиотеки:

Создание утилиты
Клонируем шаблон
git clone https://github.com/deniskorbakov/skeleton-cli-go.git
Переходим в папку с проектом
cd skeleton-cli-go
Собираем наше приложение
make build
Запускаем шаблонную утилиту
./cli
Архитектура приложения

cmd/cli - содержит директорию с названием нашего приложения в ней находится main файл, который инициализирует cobra
Скрытый текст
package main import "github.com/deniskorbakov/skeleton-cli-go/internal/command" func main() { command.Run() }
configs/constname - содержит константы, которые описывают (название, короткое и длинное описание команд)
example.go
Скрытый текст
package constname const ( // UseExampleCmd Name example command UseExampleCmd = `example` // ShortExampleCmd Short description example command ShortExampleCmd = `Example test command` ... )
root.go
Скрытый текст
package constname const ( // UseRootCmd App name and default name command UseRootCmd = `cli` // LongRootCmd Long description root command LongRootCmd = `cli is a example util` ... )
internal/command - содержит команды приложения
example.go
Скрытый текст
package command import ( "github.com/deniskorbakov/skeleton-cli-go/configs/constname" "github.com/deniskorbakov/skeleton-cli-go/internal/component/form" "github.com/deniskorbakov/skeleton-cli-go/internal/component/output" "github.com/spf13/cobra" ) // exampleCmd Command for build pipeline var exampleCmd = &cobra.Command{ Use: constname.UseExampleCmd, Short: constname.ShortExampleCmd, Long: constname.LongExampleCmd, RunE: func(cmd *cobra.Command, args []string) error { fields, err := form.Run() if err != nil { return err } output.Green("Success green output: ", fields.ExampleInput) output.Red("This command will be run successfully") return nil }, }
Здесь пример команды которую мы должны будем добавить в root.go
root.go
Скрытый текст
package command import ( "context" "os" "github.com/charmbracelet/fang" "github.com/deniskorbakov/skeleton-cli-go/configs/constname" "github.com/deniskorbakov/skeleton-cli-go/internal/component/output" "github.com/deniskorbakov/skeleton-cli-go/internal/version" "github.com/spf13/cobra" ) // Run Start app with cobra cmd func Run() { cmd := &cobra.Command{ Use: constname.UseRootCmd, Long: constname.LongRootCmd, Example: constname.ExampleRootCmd, } // Disable default options cmd cmd.Root().CompletionOptions.DisableDefaultCmd = true // Add all command in your app cmd.AddCommand( exampleCmd, ) if err := fang.Execute( context.Background(), cmd, fang.WithVersion(version.Get()), ); err != nil { output.Red("The app is broken") os.Exit(1) } }
Данный файл содержит логику описания главной команды, настройку утилиты, список добавленных команд и инициализацию Cobra
internal/component - содержит компоненты приложения
internal/component/form/form.go
Скрытый текст
package form import "github.com/charmbracelet/huh" // Run Main function that runs an interactive form func Run() (*Fields, error) { fields := &Fields{} err := huh.NewForm( huh.NewGroup( huh.NewInput(). Title("Example Title"). Description("Example description input"). Value(&fields.ExampleInput), ), ).WithShowHelp(true).Run() if err != nil { return nil, err } return fields, nil }
Здесь мы создаем форму с помощью huh
internal/component/form/fields.go
Скрытый текст
package form // Fields struct for huh form type Fields struct { ExampleInput string }
Данная структура содержит поля которые мы указываем при создании формы
В своем приложении вы можете убрать данную реализацию если считаете ее излишней и использовать любой другой вывод
internal/component/output/output.go
Скрытый текст
package output import ( "fmt" "github.com/charmbracelet/lipgloss/v2") var ( green = lipgloss.Color("#04B575") red = lipgloss.Color("#D4634C") ) func output(colorText string) { fmt.Println(colorText) } func Green(str ...string) { output(lipgloss.NewStyle().Foreground(green).Render(str...)) } func Red(str ...string) { output(lipgloss.NewStyle().Foreground(red).Render(str...)) }
Данный компонент служит для вывода текста в консоль
Так же вы можете спокойно расширять данный компонент, добавляя свои цвета, для этого используется - lipgloss
internal/version - находится логика парсинга версии приложения
internal/version/version.go
Скрытый текст
package version import ( "regexp" "runtime/debug") var ( regexVersion = `^v?(\d+\.\d+\.\d+)` emptyVersion = "none version" ) // Get return version from debug main version func Get() string { if info, ok := debug.ReadBuildInfo(); ok { version := info.Main.Version re := regexp.MustCompile(regexVersion) if matches := re.FindStringSubmatch(version); len(matches) > 1 { return matches[1] } return emptyVersion } else { return emptyVersion } }
Получаем указанную версию при билде приложения и через регулярку получаем значения версии, если ее не находим то выводим, что версия не найдена
Изменение название утилиты и ее описание
Сейчас в нашем проекте утилита называется cli - давайте заменим ее на ваше название утилиты
Поменяем путь к вашему приложению на имя вашей утилиты cmd/cli -> cmd/your_name_cli
В файле .goreleaser.yaml вам нужно изменить название cli на имя вашей утилиту
Скрытый текст
version: 2 env: - GO111MODULE=on project_name: your_name_cli
И так же надо заменить имя приложения в Makefile
Скрытый текст
build: go mod vendor go build -ldflags "-w -s" -o your_name_cli cmd/your_name_cli/main.go
Меняем описание приложения в команде root - configs/constname/root.go
Goreleaser
Это инструмент для автоматизации процесса создания релизов проектов под разные дистрибутивы и OS
В проекте уже есть файл .goreleaser.yaml и так же настроен GitHub Action
Вам останется только добавить секрет в репозиторий с именем GO_RELEASER который будет содержать личный GitHub токен
Итог
В данной статье я показал вам как с помощью шаблона вы можете создать TUI свою утилиту на Go
GitHub - буду рад вашей подписки на меня
Благодарю вас за то, что прочитали данную статью
