命令行应用

关于 Hiboot 命令行应用

Hiboot 命令行应用基于 Cobra 构建,并集成了 Hiboot 的依赖注入和自动配置功能。

功能特性

  • 命令的依赖注入
  • 支持层级结构的子命令
  • 标志解析和验证
  • 属性值注入
  • 自动配置支持

项目结构

.
├── cmd
│   ├── root.go
│   ├── root_test.go
│   ├── sub.go
│   └── sub_test.go
├── config
│   └── application.yml
├── main.go
└── main_test.go

基础示例

main.go

package main

import (
	"github.com/hidevopsio/hiboot/pkg/app"
	"github.com/hidevopsio/hiboot/pkg/app/cli"
)

func main() {
	cli.NewApplication(NewRootCommand).
		SetProperty(app.BannerDisabled, true).
		Run()
}

根命令

每个命令行应用都有一个根命令。在结构体中嵌入 cli.RootCommand 来标记它为根命令:

package main

import (
	"fmt"

	"github.com/hidevopsio/hiboot/pkg/app/cli"
)

// RootCommand 是根命令
type RootCommand struct {
	cli.RootCommand

	name    string
	verbose bool
}

// NewRootCommand 创建根命令
func NewRootCommand() *RootCommand {
	c := new(RootCommand)
	c.Use = "myapp"
	c.Short = "我的命令行应用"
	c.Long = "使用 Hiboot 构建的命令行应用"
	c.Example = `
myapp -n 张三
myapp --name 张三 --verbose
`
	// 定义标志
	c.PersistentFlags().StringVarP(&c.name, "name", "n", "World", "要问候的名字")
	c.PersistentFlags().BoolVarP(&c.verbose, "verbose", "v", false, "启用详细输出")
	return c
}

// Run 执行命令
func (c *RootCommand) Run(args []string) error {
	if c.verbose {
		fmt.Printf("已启用详细模式\n")
	}
	fmt.Printf("你好,%s!\n", c.name)
	return nil
}

带子命令的高级示例

main.go

package main

import (
	"github.com/hidevopsio/hiboot/pkg/app"
	"github.com/hidevopsio/hiboot/pkg/app/cli"
	"github.com/hidevopsio/hiboot/pkg/starter/logging"

	"myapp/cmd"
)

func main() {
	cli.NewApplication(cmd.NewRootCommand).
		SetProperty(app.BannerDisabled, true).
		SetProperty(app.ProfilesInclude, logging.Profile).
		Run()
}

cmd/root.go

package cmd

import (
	"github.com/hidevopsio/hiboot/pkg/app/cli"
	"github.com/hidevopsio/hiboot/pkg/log"
)

// RootCommand 是根命令
type RootCommand struct {
	cli.RootCommand

	profile string
	timeout int

	// 属性注入
	AppName string `value:"${app.name}"`
}

// NewRootCommand 创建根命令,子命令通过依赖注入传入
func NewRootCommand(userCmd *userCommand, configCmd *configCommand) *RootCommand {
	c := new(RootCommand)
	c.Use = "myapp"
	c.Short = "我的命令行应用"
	c.Long = "带子命令的命令行应用"

	pf := c.PersistentFlags()
	pf.StringVarP(&c.profile, "profile", "p", "dev", "环境配置")
	pf.IntVarP(&c.timeout, "timeout", "t", 30, "超时时间(秒)")

	// 添加子命令
	c.Add(userCmd, configCmd)
	return c
}

// Run 在没有指定子命令时执行
func (c *RootCommand) Run(args []string) error {
	log.Infof("应用: %s, 配置: %s, 超时: %d", c.AppName, c.profile, c.timeout)
	return nil
}

cmd/user.go(子命令)

package cmd

import (
	"github.com/hidevopsio/hiboot/pkg/app"
	"github.com/hidevopsio/hiboot/pkg/app/cli"
	"github.com/hidevopsio/hiboot/pkg/log"
)

type userCommand struct {
	cli.SubCommand
}

func init() {
	app.Register(newUserCommand)
}

func newUserCommand(listCmd *listCommand, addCmd *addCommand) *userCommand {
	c := new(userCommand)
	c.Use = "user"
	c.Short = "用户管理"
	c.Long = "管理应用中的用户"
	c.Add(listCmd, addCmd)
	return c
}

func (c *userCommand) Run(args []string) error {
	log.Info("用户命令 - 请使用子命令")
	return nil
}

cmd/list.go(嵌套子命令)

package cmd

import (
	"fmt"

	"github.com/hidevopsio/hiboot/pkg/app"
	"github.com/hidevopsio/hiboot/pkg/app/cli"
)

type listCommand struct {
	cli.SubCommand

	all bool
}

func init() {
	app.Register(newListCommand)
}

func newListCommand() *listCommand {
	c := new(listCommand)
	c.Use = "list"
	c.Short = "列出用户"
	c.Long = "列出系统中的所有用户"
	c.Flags().BoolVarP(&c.all, "all", "a", false, "显示所有用户(包括非活跃用户)")
	return c
}

func (c *listCommand) Run(args []string) error {
	if c.all {
		fmt.Println("正在列出所有用户(包括非活跃用户)...")
	} else {
		fmt.Println("正在列出活跃用户...")
	}
	return nil
}

运行应用

# 运行根命令
go run main.go

# 带标志运行
go run main.go --profile=prod --timeout=60

# 运行子命令
go run main.go user list

# 带标志运行子命令
go run main.go user list --all

# 获取帮助
go run main.go --help
go run main.go user --help

配置

命令行应用可以像 Web 应用一样使用配置文件:

config/application.yml

app:
  name: my-cli-app

logging:
  level: info

可以使用 value:"" 标签将属性注入到命令中:

type RootCommand struct {
	cli.RootCommand

	AppName  string `value:"${app.name}"`
	LogLevel string `value:"${logging.level}"`
}

单元测试

package cmd

import (
	"testing"

	"github.com/hidevopsio/hiboot/pkg/app/cli"
	"github.com/stretchr/testify/assert"
)

func TestRootCommand(t *testing.T) {
	testApp := cli.NewTestApplication(t, NewRootCommand)

	t.Run("应该运行根命令", func(t *testing.T) {
		_, err := testApp.Run("--name", "Test")
		assert.NoError(t, err)
	})

	t.Run("应该使用详细标志运行", func(t *testing.T) {
		_, err := testApp.Run("--name", "Test", "--verbose")
		assert.NoError(t, err)
	})
}

下一步