交互输入
CLI 工具经常需要引导用户输入配置(数据库连接信息、密钥等)。直接用 fmt.Scan 体验很差,huh 是 Charm 生态下的表单库,可以用少量代码构建出带标题、占位符、密码掩码和帮助提示的终端交互表单。
安装:
go get github.com/charmbracelet/huh
核心概念:
huh.NewInput()创建一个输入框,Title设置提示标题,Value(&v)把用户输入绑定到变量EchoMode(huh.EchoModePassword)让输入以*掩码显示,适合密码字段huh.NewForm(huh.NewGroup(inp)).Run()运行表单,一次可以包含多个输入项- 按
Ctrl+C取消时Run返回err,上层据此优雅退出
下面的示例来自 onboard 初始化命令:引导用户输入 PostgreSQL 连接信息(直接回车采用默认值),最后拼装成 DSN 连接串。promptString 的逻辑是「空输入返回默认值」,promptPassword 则使用密码掩码模式:
package main
import (
"fmt"
"net/url"
"os"
"github.com/charmbracelet/huh"
)
func main() {
fmt.Println("── Database Connection ──")
fmt.Println(" Enter your PostgreSQL connection details (press Enter for defaults).")
fmt.Println()
dsn, err := promptPostgresFields()
if err != nil {
fmt.Println("Cancelled.")
os.Exit(1)
}
fmt.Println()
fmt.Println(" Assembled DSN:")
fmt.Printf(" %s\n", dsn)
}
// promptPostgresFields 对应 goclaw/cmd/onboard_helpers.go。
func promptPostgresFields() (string, error) {
host, err := promptString("Host", "", "localhost")
if err != nil {
return "", err
}
port, err := promptString("Port", "", "5432")
if err != nil {
return "", err
}
dbName, err := promptString("Database name", "", "goclaw")
if err != nil {
return "", err
}
user, err := promptString("Username", "", "postgres")
if err != nil {
return "", err
}
password, err := promptPassword("Password", "Leave empty if no password")
if err != nil {
return "", err
}
sslMode, err := promptString("SSL mode", "", "disable")
if err != nil {
return "", err
}
var userInfo *url.Userinfo
if password != "" {
userInfo = url.UserPassword(user, password)
} else {
userInfo = url.User(user)
}
dsn := fmt.Sprintf("postgres://%s@%s:%s/%s?sslmode=%s", userInfo.String(), host, port, dbName, sslMode)
return dsn, nil
}
// promptString:空输入(直接 Enter)返回 defaultVal。对照 goclaw/cmd/prompt.go。
func promptString(title, description, defaultVal string) (string, error) {
var value string
inp := huh.NewInput().
Title(title).
Value(&value)
if description != "" {
inp = inp.Description(description)
}
if defaultVal != "" {
inp = inp.Placeholder(defaultVal)
}
if err := huh.NewForm(huh.NewGroup(inp)).WithShowHelp(true).Run(); err != nil {
return "", err
}
if value == "" {
return defaultVal, nil
}
return value, nil
}
func promptPassword(title, description string) (string, error) {
var value string
inp := huh.NewInput().
Title(title).
EchoMode(huh.EchoModePassword).
Value(&value)
if description != "" {
inp = inp.Description(description)
}
if err := huh.NewForm(huh.NewGroup(inp)).WithShowHelp(true).Run(); err != nil {
return "", err
}
return value, nil
}