设计要求
-
核心任务,就是模仿 cobra 库的 command.go 重写一个 Command.go
- 仅允许使用的第三方库 flag “github.com/spf13/pflag”
- 可以参考、甚至复制原来的代码
- 必须实现简化版的 type Command struct 定义和方法
- 不一定完全兼容 github.com/spf13/cobra
- 可支持简单带子命令的命令行程序开发
-
包必须包括以下内容:
- 生成的中文 api 文档
- 有较好的 Readme 文件,包括一个简单的使用案例
- 每个go文件必须有对应的测试文件
设计说明
下载使用官方包,可以总结有以下几个关键部分:
- 命令的读取并划分
- 命令的运行
具体实现过程,就到了喜闻乐见的代码翻译环节
定义结构体
参考官方文件,只保留基础内容。这里有一个自定义的sun *Command,方便运行子命令。
//Command is a struct store the information of command
type Command struct {
Use string
// Short is the short description shown in the '-h' output.
Short string
// Long is the long message shown in the '--help' output.
Long string
// Run: Typically the actual work function. Most commands will only implement this.
Run func(cmd *Command, args []string)
// commands is the list of commands supported by this program.
commands []*Command
// parent is a parent command for this command.
parent *Command
//use it as the sub command
sun *Command
// args is actual args parsed from flags.
args []string
// pflags contains persistent flags.
pflags *flag.FlagSet
}
读取命令、划分
- 添加命令到命令列表:
/**
* AddCommand is a function used to add commands.<br />
* 相当于初始化命令列表
*/
func (c *Command) AddCommand(sub *Command) {
for _, v := range c.commands {
if v == sub {
return
}
}
c.commands = append(c.commands, sub)
sub.parent = c
//c.sun = sub
}
- 命令、参数划分
注:默认根命令与子命令之间没有参数,参数都在最后
/**
* ParseArgs retrieve and store all the args for every command.<br />
* 将args分配给对应的命令
*/
func ParseArgs(c *Command, args []string) {
//fmt.Printf("%v", args)
if len(args) < 1 {
//fmt.Println("ttttttttt")
return
}
for _, v := range c.commands {
if v.Use == args[0] { //there is any sub command fit
c.args = args[:1]
c.sun = v
//fmt.Println("0000000")
ParseArgs(v, args[1:])
return
}
}
c.args = args // there is no sub command, then all args belong to current command
//fmt.Println("2222222222")
c.persistentFlags().Parse(c.args)
}
func (c *Command) persistentFlags() *flag.FlagSet {
if c.pflags == nil {
c.pflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
}
return c.pflags
}
命令运行
没有子命令就直接运行,并考虑参数;
有子命令就需要去运行子命令。
此处包括一个带错误类型的Execute函数,参考自官方包,可以方便运行,并且增强健壮性。
/**
* Execute is a function used to run the command.<br />
* 调用execute(),并带有错误判断
*/
func (c *Command) Execute() error {
if c == nil {
return fmt.Errorf("Called Execute() on a nil Command")
}
if c.parent == nil {
ParseArgs(c, os.Args[2:])
//fmt.Println("%v", os.Args[2:])
}
c.execute()
return nil
}
/**
* execute complete the command running section.<br />
* 实现命令运行的具体逻辑
*/
func (c *Command) execute() {
//fmt.Printf("%v", c.commands)
if c.sun == nil {
for _, v := range c.args {
if v == "-v" || v == "--version" {
c.Print_version()
return
}
if v == "-h" {
c.Print_help_simple()
return
}
if v == "--help" {
c.Print_help_detaile()
return
}
}
c.Run(c, c.args)
//fmt.Println("111111111")
return
}
c.sun.execute()
//fmt.Println("??????")
}
最后就是几个用于输出参数信息的函数
// Name returns the command's name: the first word in the use line.
func (c *Command) Name() string {
name := c.Use
i := strings.Index(name, " ")
if i >= 0 {
name = name[:i]
}
return name
}
/**
* Print_version is a func used to print the version message.<br />
* 输出内容是当前使用命令的版本,当然在这里version没有设置一个可以
* 修改的参数,而是直接输出的,可以考虑在后续版本优化。
*/
func (c *Command) Print_version() {
fmt.Printf("Version: \n")
fmt.Printf("\t%s: the version is v1.0\n", c.Name())
}
/**
* Print_help_simple is a func used to print the help message.<br />
* 输出内容包括简短命令介绍、使用方法、可使用flag,
* 以及最后提示使用 --help 来获取更多信息
*/
func (c *Command) Print_help_simple() {
fmt.Printf("%s\n\n", c.Short)
fmt.Printf("Usage: ")
fmt.Printf("\t%s [flags] || [sub_command]\n\n", c.Name())
fmt.Printf("\nFlags:\n")
fmt.Printf("\t-%1s, --%-7s%s%s\n", "v", "version", " get the version of ", c.Name())
fmt.Printf("\t-%1s, --%-4s%s%s\n", "h", "help", " get the help information of ", c.Name())
fmt.Println()
if len(c.commands) > 0 {
fmt.Printf("Use \"%s --help\" for more information about the command.\n", c.Name())
}
fmt.Println()
}
/**
* Print_help_simple is a func used to print the help message.<br />
* 输出内容包括详细命令介绍、使用方法、使用例子、可使用flag,
* 以及最后提示使用 --help 来获取更多信息
*/
func (c *Command) Print_help_detaile() {
fmt.Printf("%s\n\n", c.Long)
fmt.Printf("Usage: ")
fmt.Printf("\t%s [flags] || [sub_command]\n\n", c.Name())
if len(c.commands) > 0 {
fmt.Printf("Available Commands:\n")
for _, v := range c.commands {
fmt.Printf("\t%-10s%s\n", v.Name(), v.Short)
}
}
fmt.Printf("\nExampleUse:\n")
fmt.Printf("\t\"[Parent_Command] -h\"")
fmt.Printf("\t\"[Parent_Command] [Sub_Command]\"")
fmt.Println()
fmt.Printf("\nFlags:\n")
fmt.Printf("\t-%1s, --%-7s%s%s\n", "v", "version", " get the version of ", c.Name())
fmt.Printf("\t-%1s, --%-4s%s%s\n", "h", "help", " get the help information of ", c.Name())
fmt.Println()
}
测试
功能测试
main.go的实现:
package main
import (
"fmt"
cobra "github.com/github-user/myCobra"
)
//new a rootCmd for testing
var rootCmd = &cobra.Command{
Use: "root",
Short: "root command for testing",
Long: "root command is a command which construct for testing, it print a string",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("running root command ...\n")
fmt.Println()
},
}
var subCmd = &cobra.Command{
Use: "sub",
Short: "sub command for testing",
Long: "sub command is a command which construct for testing, it print a string",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("running sub command...")
fmt.Println()
},
}
func main() {
rootCmd.AddCommand(subCmd)
rootCmd.Execute()
}
功能测试结果:
单元测试
command_test.go实现:
package myCobra
import (
"fmt"
"os"
"testing"
)
//new a rootCmd for testing
var rootCmd = &Command{
Use: "root",
Short: "root command for testing",
Long: "root command is a command which construct for testing, it print a string",
Run: func(cmd *Command, args []string) {
fmt.Printf("running root command ...\n")
},
}
func TestAddCommand(t *testing.T) {
exampleCmd1 := &Command{
Use: "example1",
Short: "subCmd test 1",
Long: "subCmd test 1",
Run: func(cmd *Command, args []string) {
fmt.Printf("Example test 1\n")
},
}
rootCmd.AddCommand(exampleCmd1)
for _, v := range rootCmd.commands {
if v == exampleCmd1 {
return
}
}
t.Errorf("expected: %v\n", []*Command{exampleCmd1})
t.Errorf("got: %v\n", rootCmd.commands)
}
func TestExecute(t *testing.T) {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func TestParseArgs(t *testing.T) {
//add another subCmd to test ParseArgs
exampleCmd2 := &Command{
Use: "example2",
Short: "subCmd test 2",
Long: "subCmd test 2",
Run: func(cmd *Command, args []string) {
fmt.Printf("Example test 2\n")
},
}
rootCmd.AddCommand(exampleCmd2)
args := []string{"example2", "-v"}
ParseArgs(rootCmd, args)
if len(rootCmd.args) != 1 || rootCmd.args[0] != "example2" || len(exampleCmd2.args) != 1 || exampleCmd2.args[0] != "-v" {
t.Errorf("expected: rootCmd.args = %v, exampleCmd2.args = %v\n", []string{"example2"}, []string{"-v"})
t.Errorf("got: rootCmd.args = %v, exampleCmd2.args = %v", rootCmd.args, exampleCmd2.args)
}
}
func TestName(t *testing.T) {
name := rootCmd.Name()
if name != "root" {
t.Errorf("expected: %s", "root")
t.Errorf("got: %s", name)
}
}
单元测试结果:
使用godoc生成中文API文档
直接使用godoc命令,然后在路径http://localhost:6060/pkg/github.com/github-user/myCobra/下查看生成的文档