服务计算 作业五 实现myCobra包——设计文档

设计要求

  • 核心任务,就是模仿 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/下查看生成的文档
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值