Golang中CLI标志参数问题求助
Golang中CLI标志参数问题求助 我正在开发一个使用标准库的命令行界面工具。
我尝试为一个新标志实现一个功能,该标志接受一个选项。
例如:poke-cli pokemon tyranitar --image=lg
调用此标志时会生成一张图片。
我已经实现了另外三个标志,但它们只接受 Bool 值。
这里涉及三个不同的文件:
./flags/pokemonflagset.go:我在此创建标志及其相关函数。./cmd/pokemon.go:我在此调用标志。./cli.go:我在此验证并调用命令。
在 ./flags/pokemonflagset.go 中,我定义了生成图片的函数:
func ImageFlag(endpoint string, pokemonName string, size string) error {
baseURL := "https://pokeapi.co/api/v2/"
pokemonStruct, _, _, _, _ := connections.PokemonApiCall(endpoint, pokemonName, baseURL)
header("Image")
// Anonymous function to transform the image to a string
// ToString generates an ASCII representation of the image with color
ToString := func(width int, height int, img image.Image) string {
// Resize the image to the specified width, preserving aspect ratio
img = imaging.Resize(img, width, height, imaging.NearestNeighbor)
b := img.Bounds()
imageWidth := b.Max.X - 2 // Adjust width to exclude margins
h := b.Max.Y - 4 // Adjust height to exclude margins
str := strings.Builder{}
// Loop through the image pixels, two rows at a time
for heightCounter := 2; heightCounter < h; heightCounter += 2 {
for x := 1; x < imageWidth; x++ {
// Get the color of the current and next row's pixels
c1, _ := colorful.MakeColor(img.At(x, heightCounter))
color1 := lipgloss.Color(c1.Hex())
c2, _ := colorful.MakeColor(img.At(x, heightCounter+1))
color2 := lipgloss.Color(c2.Hex())
// Render the half-block character with the two colors
str.WriteString(lipgloss.NewStyle().
Foreground(color1).
Background(color2).
Render("▀"))
}
// Add a newline after each row
str.WriteString("\n")
}
return str.String()
}
imageResp, err := http.Get(pokemonStruct.Sprites.FrontDefault)
if err != nil {
fmt.Println("Error downloading sprite image:", err)
os.Exit(1)
}
defer imageResp.Body.Close()
img, err := imaging.Decode(imageResp.Body)
if err != nil {
fmt.Println("Error decoding image:", err)
os.Exit(1)
}
// Define size map
sizeMap := map[string][2]int{
"lg": {120, 120},
"md": {90, 90},
"sm": {55, 55},
}
// Validate size
dimensions, exists := sizeMap[strings.ToLower(size)]
if !exists {
errMessage := errorBorder.Render(errorColor.Render("Error!"), "\nInvalid image size. Valid sizes are: lg, md, sm")
return fmt.Errorf("%s", errMessage)
}
imgStr := ToString(dimensions[0], dimensions[1], img)
fmt.Println(imgStr)
return nil
}
在 ./cmd/pokemon.go 中,我列出了标志,以便在使用时调用其相关函数:
// PokemonCommand processes the Pokémon command
func PokemonCommand() {
flag.Usage = func() {
helpMessage := helpBorder.Render(
"Get details about a specific Pokémon.\n\n",
styleBold.Render("USAGE:"),
fmt.Sprintf("\n\t%s %s %s %s", "poke-cli", styleBold.Render("pokemon"), "<pokemon-name>", "[flag]"),
fmt.Sprintf("\n\t%-30s", styleItalic.Render("Use a hyphen when typing a name with a space.")),
"\n\n",
styleBold.Render("FLAGS:"),
fmt.Sprintf("\n\t%-30s %s", "-a, --abilities", "Prints the Pokémon's abilities."),
fmt.Sprintf("\n\t%-30s %s", "-i, --image", "Prints out the Pokémon's default sprite."),
fmt.Sprintf("\n\t%-30s %s", "-s, --stats", "Prints the Pokémon's base stats."),
fmt.Sprintf("\n\t%-30s %s", "-t, --types", "Prints the Pokémon's typing."),
fmt.Sprintf("\n\t%-30s %s", "-h, --help", "Prints the help menu."),
)
fmt.Println(helpMessage)
}
flag.Parse()
pokeFlags, abilitiesFlag, shortAbilitiesFlag, imageFlag, shortImageFlag, statsFlag, shortStatsFlag, typesFlag, shortTypesFlag := flags.SetupPokemonFlagSet()
args := os.Args
err := ValidatePokemonArgs(args)
if err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
endpoint := strings.ToLower(args[1])
pokemonName := strings.ToLower(args[2])
if err := pokeFlags.Parse(args[3:]); err != nil {
fmt.Printf("error parsing flags: %v\n", err)
pokeFlags.Usage()
os.Exit(1)
}
_, pokemonName, pokemonID, pokemonWeight, pokemonHeight := connections.PokemonApiCall(endpoint, pokemonName, "https://pokeapi.co/api/v2/")
capitalizedString := cases.Title(language.English).String(strings.Replace(pokemonName, "-", " ", -1))
// Weight calculation
weightKilograms := float64(pokemonWeight) / 10
weightPounds := float64(weightKilograms) * 2.20462
// Height calculation
heightMeters := float64(pokemonHeight) / 10
heightFeet := heightMeters * 3.28084
feet := int(heightFeet)
inches := int(math.Round((heightFeet - float64(feet)) * 12)) // Use math.Round to avoid truncation
// Adjust for rounding to 12 inches (carry over to the next foot)
if inches == 12 {
feet++
inches = 0
}
fmt.Printf(
"Your selected Pokémon: %s\nNational Pokédex #: %d\nWeight: %.1fkg (%.1f lbs)\nHeight: %.1fm (%d′%02d″)\n",
capitalizedString, pokemonID, weightKilograms, weightPounds, heightFeet, feet, inches,
)
if *abilitiesFlag || *shortAbilitiesFlag {
if err := flags.AbilitiesFlag(endpoint, pokemonName); err != nil {
fmt.Printf("Error: %s\n", err)
os.Exit(1)
}
}
if *imageFlag != "md" || *shortImageFlag != "md" {
// Determine the size based on the provided flags
size := *imageFlag
if *shortImageFlag != "" {
size = *shortImageFlag
}
// Call the ImageFlag function with the specified size
if err := flags.ImageFlag(endpoint, pokemonName, size); err != nil {
fmt.Printf("Error: %s\n", err)
os.Exit(1)
}
}
if *typesFlag || *shortTypesFlag {
if err := flags.TypesFlag(endpoint, pokemonName); err != nil {
fmt.Printf("Error: %s\n", err)
os.Exit(1)
}
}
if *statsFlag || *shortStatsFlag {
if err := flags.StatsFlag(endpoint, pokemonName); err != nil {
fmt.Printf("Error: %s\n", err)
os.Exit(1)
}
}
}
最后,在 ./cli.go 中,我解析参数并检查用户输入的命令是否有效:
//.....
commands := map[string]func(){
"pokemon": cmd.PokemonCommand,
"natures": cmd.NaturesCommand,
"types": cmd.TypesCommand,
}
if len(os.Args) < 2 {
mainFlagSet.Usage()
return 1
} else if *latestFlag || *shortLatestFlag {
flags.LatestFlag()
return 0
} else if *currentVersionFlag || *shortCurrentVersionFlag {
currentVersion()
return 0
} else if cmdFunc, exists := commands[os.Args[1]]; exists {
cmdFunc()
return 0
} else {
command := os.Args[1]
errMessage := errorBorder.Render(
errorColor.Render("Error!"),
fmt.Sprintf("\n\t%-15s", fmt.Sprintf("'%s' is not a valid command.\n", command)),
styleBold.Render("\nCommands:"),
fmt.Sprintf("\n\t%-15s %s", "natures", "Get details about Pokémon natures"),
fmt.Sprintf("\n\t%-15s %s", "pokemon", "Get details about a specific Pokémon"),
fmt.Sprintf("\n\t%-15s %s", "types", "Get details about a specific typing"),
fmt.Sprintf("\n\nAlso run %s for more info!", styleBold.Render("poke-cli -h")),
)
fmt.Printf("%s\n", errMessage)
return 1
}
//.....
我遇到问题的地方是在使用 -i | --image 标志并传递尺寸选项时。具体是在这里:
if *imageFlag != "" || *shortImageFlag != "" {
// Determine the size based on the provided flags
size := *imageFlag
if *shortImageFlag != "" {
size = *shortImageFlag
}
// Call the ImageFlag function with the specified size
if err := flags.ImageFlag(endpoint, pokemonName, size); err != nil {
fmt.Printf("Error: %s\n", err)
os.Exit(1)
}
}
像这样设置(空字符串或除 [sm, md, lg] 之外的任何字符)并在不使用 -i | --image 标志的情况下运行工具时,图片仍然会打印出来:
poke-cli pokemon tyranitar

但是,使用尺寸选项是有效的。例如:
poke-cli pokemon tyranitar -i=md | --image=lg
如果我确实设置了 if *imageFlag != "md" || *shortImageFlag != "md" 并使用可用选项中的一个值,那么该图片尺寸不会打印任何内容,但其他尺寸会正常打印(–image=sm | -i=lg 打印正常)。
我不确定是遗漏了一些简单的东西,还是整个标志的调用方式错了。我有其他使用 flag.Bool 的标志,它们可以正常工作:
if *abilitiesFlag || *shortAbilitiesFlag {
if err := flags.AbilitiesFlag(endpoint, pokemonName); err != nil {
fmt.Printf("Error: %s\n", err)
os.Exit(1)
}
}
更多关于Golang中CLI标志参数问题求助的实战教程也可以访问 https://www.itying.com/category-94-b0.html
我曾尝试集成自己的 CLI 框架,但显然在短时间内无法满足各种需求。如果 CLI 设计较为复杂,我建议使用现有的 CLI 开发框架,例如:
GitHub - spf13/cobra: 用于现代 Go CLI 交互的指挥官
用于现代 Go CLI 交互的指挥官
你的问题在于标志初始化和条件判断逻辑。在 pokemon.go 中,imageFlag 和 shortImageFlag 被初始化为默认值 "md",导致条件判断出错。
首先检查 ./flags/pokemonflagset.go 中的标志定义:
func SetupPokemonFlagSet() (*flag.FlagSet, *bool, *bool, *string, *string, *bool, *bool, *bool, *bool) {
pokeFlags := flag.NewFlagSet("pokemon", flag.ExitOnError)
abilitiesFlag := pokeFlags.Bool("abilities", false, "Prints the Pokémon's abilities.")
shortAbilitiesFlag := pokeFlags.Bool("a", false, "Prints the Pokémon's abilities.")
imageFlag := pokeFlags.String("image", "md", "Prints out the Pokémon's default sprite. Options: sm, md, lg")
shortImageFlag := pokeFlags.String("i", "md", "Prints out the Pokémon's default sprite. Options: sm, md, lg")
statsFlag := pokeFlags.Bool("stats", false, "Prints the Pokémon's base stats.")
shortStatsFlag := pokeFlags.Bool("s", false, "Prints the Pokémon's base stats.")
typesFlag := pokeFlags.Bool("types", false, "Prints the Pokémon's typing.")
shortTypesFlag := pokeFlags.Bool("t", false, "Prints the Pokémon's typing.")
return pokeFlags, abilitiesFlag, shortAbilitiesFlag, imageFlag, shortImageFlag, statsFlag, shortStatsFlag, typesFlag, shortTypesFlag
}
问题在于 imageFlag 和 shortImageFlag 的默认值都是 "md"。当用户不提供 -i 或 --image 标志时,这些标志的值仍然是 "md",而不是空字符串。
修改 ./cmd/pokemon.go 中的条件判断:
// 修改前的错误逻辑
if *imageFlag != "" || *shortImageFlag != "" {
// 这会触发,因为默认值是 "md"
}
// 修改后的正确逻辑
if pokeFlags.Lookup("image").Changed || pokeFlags.Lookup("i").Changed {
// 确定尺寸
size := *imageFlag
if *shortImageFlag != "md" && *shortImageFlag != "" {
size = *shortImageFlag
}
// 调用 ImageFlag 函数
if err := flags.ImageFlag(endpoint, pokemonName, size); err != nil {
fmt.Printf("Error: %s\n", err)
os.Exit(1)
}
}
或者使用更简洁的方法,检查标志是否被显式设置:
// 检查是否提供了 image 标志
imageProvided := false
pokeFlags.Visit(func(f *flag.Flag) {
if f.Name == "image" || f.Name == "i" {
imageProvided = true
}
})
if imageProvided {
// 确定尺寸
size := *imageFlag
if *shortImageFlag != "md" {
size = *shortImageFlag
}
// 调用 ImageFlag 函数
if err := flags.ImageFlag(endpoint, pokemonName, size); err != nil {
fmt.Printf("Error: %s\n", err)
os.Exit(1)
}
}
另一种解决方案是修改标志定义,使用空字符串作为默认值:
// 在 SetupPokemonFlagSet 函数中修改
imageFlag := pokeFlags.String("image", "", "Prints out the Pokémon's default sprite. Options: sm, md, lg")
shortImageFlag := pokeFlags.String("i", "", "Prints out the Pokémon's default sprite. Options: sm, md, lg")
然后保持原来的条件判断:
if *imageFlag != "" || *shortImageFlag != "" {
// 确定尺寸
size := *imageFlag
if *shortImageFlag != "" {
size = *shortImageFlag
}
// 调用 ImageFlag 函数
if err := flags.ImageFlag(endpoint, pokemonName, size); err != nil {
fmt.Printf("Error: %s\n", err)
os.Exit(1)
}
}
这样当用户不提供 -i 或 --image 标志时,条件判断为 false,不会执行图片生成代码。当用户提供标志时(如 -i=md 或 --image=lg),条件判断为 true,会执行相应的图片生成逻辑。

