В данной статье расскажу как на Go создать консольную TUI утилиту с помощью готовых компонентов, покажу примеры интерфейсов и соберем приложение с помощью Goreleaser
То есть пройдем весь цикл создания приложения до его релиза
Все что будет описано в данной статье, я собрал в данном репозитории
https://github.com/deniskorbakov
Предыстория
Мне очень нравятся TUI, что с помощью него утилиты в терминале начинают играть новыми красками и повышается удовольствие от использования данных программ
Недавно с командой https://жыбийрыр.рф/ участвовали на хакатоне, где создавали утилиту (Go + Ansible) для автоматического деплоя и билда на сервере всех возможных приложений (антиутопическая задача)
Для данного решения так же использовал TUI компоненты для утилиты, что помогло нам больше выделиться среди участников, а в конечном итоге и победить
Что используем для создания утилиты
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 - буду рад вашей подписки на меня
Благодарю вас за то, что прочитали данную статью
alexs963
Для Go есть аналог TurboVision?