服务计算之支持子命令命令行程序支持包开发

任务

  • 了解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

实现根命令

  1. 在how5文件夹下创建cmd文件夹
  2. 在cmd文件夹下创建root.go文件
  3. 在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)
	}
}

测试通过

在这里插入图片描述

项目地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值