支持子命令命令行程序支持包开发
任务
- 了解Cobra包,使用 cobra 命令行生成一个简单的带子命令的命令行程序
- 模仿 cobra.Command 编写一个 myCobra 库
- 将带子命令的命令行处理程序的 import (“github.com/spf13/cobra”) 改为 import (corbra “gitee.com/yourId/yourRepo”)
- 使得命令行处理程序修改代价最小,即可正常运行
文件结构
实现过程
实现一个简单的子命令
了解Cobra包,使用 cobra 命令行生成一个简单的带子命令的命令行程序
安装cobra包
go get -u github.com/spf13/cobra
实现根命令
- 在how5文件夹下创建cmd文件夹
- 在cmd文件夹下创建root.go文件
- 在how5文件夹下创建main.go文件
root.go定义了一个根命令,cobra.Command是一个结构体,通过命令来执行Run函数。在此根命令中,通过执行Run函数可以打印一串字符串Hello World
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "test",
Short: "a test of command",
Long: `testing a simple command`,
Run: func(cmd *cobra.Command, args []string) {
// 执行程序
fmt.Println("Hello World")
},
}
func Execute() {
err := rootCmd.Execute()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
main.go中执行执行root.go中的根命令
package main
import (
"github.com/user/how5/cmd"
)
func main() {
cmd.Execute()
}
运行结果
实现子命令
在cmd文件夹下创建version.go文件
package cmd
import (
"fmt"
"github.com/spf13/cobra"
//cobra "github.com/user/how5/myCobra"
)
func init() {
rootCmd.AddCommand(versionCmd)
}
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the version number of test",
Long: `All software has versions`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("The version is v2.5")
},
}
给命令增加标志
标志分为Persistent Flags和Local Flags,举例如下:
持久性标志
rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")
局部标志
localCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from")
现在对root.go文件进行修改:
package cmd
import (
"os"
"fmt"
"github.com/spf13/cobra"
//cobra "github.com/user/how5/myCobra"
)
var name string
var age int
var rootCmd = &cobra.Command{
Use: "test",
Short: "a test of command",
Long: `testing a simple command`,
Run: func(cmd *cobra.Command, args []string) {
if name != "" && age != 0 {
fmt.Println("User: ", name, ", a ", age, " years old student")
}else {
fmt.Println("Hello World")
}
},
}
func init() {
rootCmd.AddCommand(versionCmd)
rootCmd.PersistentFlags().StringVarP(&name, "name", "n", "", "user's name")
rootCmd.PersistentFlags().IntVarP(&age, "age", "a", 0, "user's age")
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
运行结果
编写 myCobra 库
模仿 cobra 库的 command.go 重写一个 Command.go
- 仅允许使用的第三方库 flag “github.com/spf13/pflag”
- 可以参考、甚至复制原来的代码
- 必须实现简化版的 type Command struct 定义和方法
- 不一定完全兼容 github.com/spf13/cobra
- 可支持简单带子命令的命令行程序开发
新建一个myCobra文件夹,然后创建command.go文件
command结构体
command结构体中存储Use、Short、Long、Run等基本信息
type Command struct {
Use string // 命令名称,根命令可任意取
Short string // 命令描述,-h显示的帮助信息
Long string
subCom *Command // 子命令
parentCom *Command // 父命令
args []string // 命令参数
pflags *flag.FlagSet // 标志选项
Run func(cmd *Command, args []string) // 执行的操作
commands []*Command // 存储命令
}
AddCommand方法
AddCommand方法作用是添加子命令,如果添加的子命令与自身相同时则返回一条错误信息“Command can’t be a child of itself”,否则就将子命令添加
func (c *Command) AddCommand(cmds ...*Command) {
for _, x := range cmds {
if x == c {
panic("Command can't be a child of itself")
}
x.parentCom = c
c.commands = append(c.commands, x)
}
}
Execute、execute方法
Execute、execute方法:执行相应命令操作,即执行对应Run函数
执行命令时如果有子命令那么就优先执行子命令,不执行当前命令,但是如果带有参数,那么就执行当前命令(通过PersistentFlags().Parse()方法来获得参数)
how5 -n wsj -a 20 version
该命令不会执行version子命令,而是执行根命令
how5 version
该命令直接执行子命令version,而不是根命令
func (c *Command) Execute() error {
err := c.execute()
return err
}
func (c *Command) execute() (err error) {
var e error
if c == nil {
return fmt.Errorf("Called Execute() on a nil Command")
}
if c.args == nil && len(os.Args)>1 && c.parentCom == nil{
c.args = os.Args[1:]
}
c.PersistentFlags().Parse(c.args)
for i, x := range c.commands{
if len(c.args) == 0 {
break
}
if c.args[0] == "-h" || c.args[0] == "-help"{
c.Printhelp()
return e;
}
if x.Use == c.args[0]{
if len(c.args) > 1{
x.args = c.args[1:]
}
x.execute()
break
}
if i == len(c.commands)-1{
c.Run(c,c.args)
}
}
if len(c.args) == 0 || len(c.commands) == 0{
if len(c.args) > 0{
if c.args[0] == "-h" || c.args[0] == "-help"{
c.Printhelp()
return e;
}
}
c.Run(c,c.args)
}
return e
}
PersistentFlags方法
PersistentFlags方法作用是添加对应标志或者选项
func (c *Command) PersistentFlags() *flag.FlagSet {
if c.pflags == nil {
c.pflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
}
return c.pflags
}
Printhelp方法
Printhelp方法作用是打印帮助信息(模仿Cobra包中command的帮助信息格式)
func (c *Command) Printhelp() {
fmt.Printf("%s\n\n", c.Long)
fmt.Printf("Usage:\n")
fmt.Printf("\t%s [flags]\n", c.Name())
if len(c.commands) > 0 {
fmt.Printf("\t%s [command]\n\n", c.Name())
fmt.Printf("Available Commands:\n")
for _, v := range c.commands {
fmt.Printf("\t%-10s%s\n", v.Name(), v.Short)
}
}
fmt.Printf("\nFlags:\n")
c.PersistentFlags().VisitAll(func(flag *flag.Flag) {
fmt.Printf("\t-%1s, --%-6s %-12s%s (default \"%s\")\n", flag.Shorthand, flag.Name, flag.Value.Type(), flag.Usage, flag.DefValue)
})
fmt.Printf("\t-%1s, --%-19s%s%s\n", "h", "help", "help for ", c.Name())
fmt.Println()
if len(c.commands) > 0 {
fmt.Printf("Use \"%s [command] --help\" for more information about a command.\n", c.Name())
}
fmt.Println()
}
功能测试
要想使用我们自己写的包,那么就要将root.go和version.go中import部分修改为我们自己的包路径
import (
"os"
"fmt"
//"github.com/spf13/cobra"
cobra "github.com/user/how5/myCobra"
)
import (
"fmt"
//"github.com/spf13/cobra"
cobra "github.com/user/how5/myCobra"
)
安装myCobra包以及how5命令后即可使用
go install github.com/user/how5/myCobra
go install github.com/user/how5
命令 how5 -n wsj -a 20 version
命令 how5 version
命令 how5
命令 how5 -h
单元测试
主要对execute函数和Addcommand函数进行测试
package myCobra
import (
"testing"
"fmt"
"os/exec"
)
var name string
var age int
var rootCmd = &Command{
Use: "test",
Short: "a test of command",
Long: `testing a simple command`,
Run: func(cmd *Command, args []string) {
if name != "" && age != 0 {
fmt.Println("User: ", name, ", a ", age, " years old student")
}else {
fmt.Println("Hello world")
}
},
}
var versionCmd = &Command{
Use: "version",
Short: "Print the version number of test",
Long: `All software has versions`,
Run: func(cmd *Command, args []string) {
fmt.Println("The version is v2.5")
},
}
func TestAddCommand(t *testing.T) {
for _, x := range rootCmd.commands {
if x == versionCmd {
t.Errorf("expected: nil got: %v\n",rootCmd.commands)
}
}
rootCmd.AddCommand(versionCmd)
for _, x := range rootCmd.commands {
if x == versionCmd {
return
}
}
t.Errorf("expected: %v got: %v\n", versionCmd,rootCmd.commands)
}
func TestExecute(t *testing.T){
stdout, err := exec.Command("bash", "-c", "how5 -n wsj -a 20 version").Output()
str := string(stdout)
str2 := "User: wsj , a 20 years old student"
for i:=0;i<len(str2);i++{
if str[i] != str2[i]{
t.Errorf("expected: %s but got %s\n",str2,str)
}
}
if err != nil {
t.Error(err)
}
stdout, err = exec.Command("bash", "-c", "how5 version").Output()
str = string(stdout)
str2 = "The version is v2.5"
for i:=0;i<len(str2);i++{
if str[i] != str2[i]{
t.Errorf("expected: %s but got %s\n",str2,str)
break
}
}
if err != nil {
t.Error(err)
}
}
测试通过