支持子命令命令行程序支持包开发

服务计算第五次作业

支持子命令命令行程序支持包开发

一. 概述

命令行实用程序并不是都象 cat、more、grep 是简单命令。go项目管理程序,类似 java 项目管理 maven、Nodejs 项目管理程序npm 、git 命令行客户端、 docker 与 kubernetes 容器管理工具等等都是采用了较复杂的命令行。即一个实用程序同时支持多个子命令,每个子命令有各自独立的参数,命令之间可能存在共享的代码或逻辑,同时随着产品的发展,这些命令可能发生功能变化、添加新命令等。因此,符合 OCP 原则 的设计是至关重要的编程需求。

二. 课程任务

任务一
了解Cobra包,使用 cobra 命令行生成一个简单的带子命令的命令行程序
  • cobra简介:

    cobra是一个构建命令行应用接口的工具 ,同时是一个用于生成应用以及命令文件的工具,cobra已经被许多go工程使用,如Github,以及Hugo等;

  • cobra 应用过程:

    a). cobra包的安装:

    因为golang并不自带cobra包,所以我们需要自行安装cobra包,以前使用的安装方式:

    go get -v github.com/spf13/cobra/cobra  
    

    在golang.org官网无法在中国访问的情况下已经不能使用,作为替代,我们使用如下的三条指令来代替上方的安装指令:

     git clone https://github.com/golang/text
     git clone https://github.com/golang/sys
     go install github.com/spf13/cobra/cobra
    

    安装成功之后,你将在$GOPATH\bin下找到cobra.exe的可执行程序;

    b). 创建cobra应用:

    我们在$GOPATH/src的目录下创建一个cobra应用,叫做demo;

    先进入到$GOPATH/src目录下,再使用以下指令来创建一个新的cobra目录:

         cobra init demo --pkg-name=demo
    

    命令解释:

    cobra创建一个新的cobra应用的基本语法如下:

      cobra init [name] [flags]
    

    根据我们上面的命令来分析,demo为新创建的cobra应用的名字,--pkg-name=demo为创建demo应用时的命令行参数;需要指出的一点是:--pkg-name=demo这一个命令行参数必须给出,不然,cobra不会创建demo应用。还有需要注意的是--pkg-name后面的参数值必须和name相同,否则在创建出来的main.go中,它将访问不到demo/cmd中的文件。

    正确运行了cobra的init指令之后,我们将在$GOPATH/src目录下新建一个名叫demo的cobra应用,它的目录结构如下:

    demo\
    	cmd\
    		root.go
    	LICENSE
    	main.go
    

    为了了解我们的cobra到底做了什么,我们打开root.go以及main.go文件,查看它们的实现;

    • root.go:

      package cmd
      
      import (
      	"fmt"
      	"github.com/spf13/cobra"
      	"os"
      	
      	// homedir "github.com/mitchellh/go-homedir"
      	// "github.com/spf13/viper"
      )
      
      var cfgFile string
      
      // rootCmd represents the base command when called without any subcommands
      var rootCmd = &cobra.Command{
      	Use:   "demo",
      	Short: "A brief description of your application",
      	Long: `A longer description that spans multiple lines and likely contains
      examples and usage of using your application. For example:
      
      Cobra is a CLI library for Go that empowers applications.
      This application is a tool to generate the needed files
      to quickly create a Cobra application.`,
      	// Uncomment the following line if your bare application
      	// has an action associated with it:
      	//	Run: func(cmd *cobra.Command, args []string) { },
      }
      
      // Execute adds all child commands to the root command and sets flags appropriately.
      // This is called by main.main(). It only needs to happen once to the rootCmd.
      func Execute() {
      	if err := rootCmd.Execute(); err != nil {
      		fmt.Println(err)
      		os.Exit(1)
      	}
      }
      

      因为原文件代码量较大,我们只分析root.go中当前涉及的一部分;

      root.go实际上定义了根命令,当我们使用go run main.go指令时,我们将调用根命令;而所有的命令的具体定义都被保存在cobra/command.go中的Command结构体中。上方用于初始化命令的几个变量的含义如下:

      • Use:为使用命令时所用的命令名称,如使用根命令时在命令行输入demo即可;
      • Short:在使用help命令查看时输出的描述信息;
      • long:在使用 ”help “命令查看命令使用方法时输出的描述信息;
    • main.go

      package main
      
      import "demo/cmd"
      
      func main() {
      	cmd.Execute()
      }
      

      相比于root.go的代码量,main.go的代码量极少,只是调用了cmd.Execute函数便结束了。实际上main.go调用的Execute函数即为root.go中的Execute函数,那么main.go函数实际上是调用了根命令rootCmd的Execute函数;

      那么,我们来看看这个程序的输出:

      先使用go install demo编译安装demo,再使用demo运行这个程序;

      可以看到,我们的程序输出了long中的描述字段;

    c). 为根命令增加动作:

    ​ 在根命令的初始化过程中,cobra默认注释掉了命令的Run参数,而Run参数是为根命令运行时服务的。因为在命令结构体的Execute函数中,Execute函数实际上执行的就是命令的Run参数中所定义的函数,而在Run参数没有定义的时候,Execute函数将默认打印出命令的long参数中的命令描述。Execute函数的具体定义可以见cobra/command.go。那么我们给根命令初始化Run参数,使它按照我们定义的函数运行;

    package cmd
    
    import (
    	"fmt"
    	"github.com/spf13/cobra"
    	"os"
    	
    	 homedir "github.com/mitchellh/go-homedir"
    	 "github.com/spf13/viper"
    )
    
    var cfgFile string
    
    // rootCmd represents the base command when called without any subcommands
    var rootCmd = &cobra.Command{
    	Use:   "demo",
    	Short: "A brief description of your application",
    	Long: `A longer description that spans multiple lines and likely contains
    examples and usage of using your application. For example:
    
    Cobra is a CLI library for Go that empowers applications.
    This application is a tool to generate the needed files
    to quickly create a Cobra application.`,
    	// Uncomment the following line if your bare application
    	// has an action associated with it:
    	Run: func(cmd *cobra.Command, args []string) { 
            fmt.Println("this is a test to run")
        },
    }
    
    // Execute adds all child commands to the root command and sets flags appropriately.
    // This is called by main.main(). It only needs to happen once to the rootCmd.
    func Execute() {
    	if err := rootCmd.Execute(); err != nil {
    		fmt.Println(err)
    		os.Exit(1)
    	}
    }
    

    可以看到,我们在根命令的初始化中加上了Run参数,使它输出"this is a test for run"的一段字符串。我们重新编译安装demo之后,再运行一次demo以检测我们的函数是否正确。

    可以看到,命令执行的结果和预期一致;

    d). 为应用增加子命令:

    ​ 现在,我们为demo应用增加一个名为test的子命令;方法很简单,在demo的目录下运行

      cobra add test
    

    即可,增加子命令文件之后的文件目录树

    demo\
    	cmd\
    		test.go
    		root.go
        LICENSE
        main.go
    

    同样的,我们打开test.go,看看它是怎么实现的:

    package cmd
    
    import (
    	"fmt"
    
    	"github.com/spf13/cobra"
    )
    
    // testCmd represents the test command
    var testCmd = &cobra.Command{
    	Use:   "test",
    	Short: "A brief description of your command",
    	Long: `A longer description that spans multiple lines and likely contains examples
    and usage of using your command. For example:
    
    Cobra is a CLI library for Go that empowers applications.
    This application is a tool to generate the needed files
    to quickly create a Cobra application.`,
    	Run: func(cmd *cobra.Command, args []string) {
    		fmt.Println("test called")
    	},
    }
    
    func init() {
    	rootCmd.AddCommand(testCmd)
    
    	// Here you will define your flags and configuration settings.
    
    	// Cobra supports Persistent Flags which will work for this command
    	// and all subcommands, e.g.:
    	// testCmd.PersistentFlags().String("foo", "", "A help for foo")
    
    	// Cobra supports local flags which will only run when this command
    	// is called directly, e.g.:
    	// testCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
    }
    

    可以看到,在test.go中,我们和root.go中一样,利用cobra.command结构体定义了一个命令,在test.go中我们定义的命令名为testCmd,并给出了Run参数。在Run参数中,我们输出了字符串“test called”,那么我们调用test子命令时应当会输出字符串“test called”。而在这个.go文件的初始化函数init函数中,我们将代表test子命令的testCmd加入到代表根命令的rootCmd中,成为它的子命令。AddCommand函数的定义同样可以在cobra/command.go中找到;那么我们重新编译安装demo,然后运行test子命令,得到以下输出

    可以看到,我们使用子命令的时候执行的只有子命令的Run函数,而没有使用根命令的Run函数,因此不会输出“this is a test for run”;

    e). 为应用增加参数:

    命令行参数是执行一个命令所必不可少的,或者说极为重要的部分。因此,为应用增加参数十分重要。我们从test.go的init注解中便可以了解到,如果要为一个指令增加命令行参数,那么就调用命令的Flags或是PersistentFlags方法即可为命令增加对应的命令行参数,这个用法和flag库的用法十分相似;那么我们就为test增加一个-v的整数类型的命令行参数:

    package cmd
    
    import (
    	"fmt"
    
    	"github.com/spf13/cobra"
    )
    
    // testCmd represents the test command
    var testCmd = &cobra.Command{
    	Use:   "test",
    	Short: "A brief description of your command",
    	Long: `A longer description that spans multiple lines and likely contains examples
    and usage of using your command. For example:
    
    Cobra is a CLI library for Go that empowers applications.
    This application is a tool to generate the needed files
    to quickly create a Cobra application.`,
    	Run: func(cmd *cobra.Command, args []string) {
    		fmt.Println("test called")
    	},
    }
    
    func init() {
    	rootCmd.AddCommand(testCmd)
    
    	// Here you will define your flags and configuration settings.
    
    	// Cobra supports Persistent Flags which will work for this command
    	// and all subcommands, e.g.:
    	// testCmd.PersistentFlags().String("foo", "", "A help for foo")
    
    	// Cobra supports local flags which will only run when this command
    	// is called directly, e.g.:
    	testCmd.Flags().IntP("version", "v", 1, "Help message for version")
    }
    

    我们在将新的demo重新编译安装之前,先利用-h命令输出test子命令的相应信息:

    可以看到test子命令除了有Flags参数列表之外,还有Global Flags参数列表。位于Global Flags参数列表的命令行参数实际上是继承自父命令的用PersistentFlags定义的命令行参数,我们可以重新打开root.go进行查看;

    func init() {
    	cobra.OnInitialize(initConfig)
    
    	// Here you will define your flags and configuration settings.
    	// Cobra supports persistent flags, which, if defined here,
    	// will be global for your application.
    
    	rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.demo.yaml)")
    
    	// Cobra also supports local flags, which will only run
    	// when this action is called directly.
    	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
    }
    

    可以看到,我们是在根命令的init函数中定义了config命令行参数,而config参数被根命令的子命令test继承了,但是test子命令并没有继承根命令的-t参数。所以,这就是PersistentFlags方法定义的命令行参数和Flags定义的命令行参数的不同,PersistentFlags定义的命令行参数可以被子命令继承与使用,但是Flags定义的命令行参数则不行。

    在重新编译安装之后,我们在使用-h命令获得test子命令的相关信息:

    可以看到,在加上了新的命令参数之后,test子命令得到了一个新的命令行参数,-v;

    我们运行一下这个命令,为了让我们输入的参数在输出中有所反应,我们更改一下test子命令的Run函数,让它输出我们传入的参数:

    Run: func(cmd *cobra.Command, args []string) {
    		fmt.Println("test called")
    		num, err := cmd.Flags().GetInt("version")
    		if err == nil{
    			fmt.Printf("the num user input is %d\n", num);
    		}
    		
    	},
    

    在这里我们使用cmd.Flags().GetInt(“version”)来获得命令行参数的值;

    最终结果为:

    完美;


任务二
模仿 cobra.Command 编写一个 myCobra 库

要求:

  • 将带子命令的命令行处理程序的 import ("github.com/spf13/cobra") 改为 import (corbra "gitee.com/yourId/yourRepo");
  • 使得命令行处理程序修改代价最小,即可正常运行;
  • 仅允许使用的第三方库 flag "github.com/spf13/pflag";
  • 可以参考、甚至复制原来的代码;
  • 必须实现简化版的 type Command struct 定义和方法;
  • 不一定完全兼容 github.com/spf13/cobra;
  • 可支持简单带子命令的命令行程序开发;

阶段一:让最简单的根命令跑起来;

​ 为了让我们的实现能够一步步进行,我们将command函数的实现分步骤进行。并将原来的cmd目录下的子命令的test.go文件删去,只保留根命令的root.go文件,并将root.go中cobra包改为我们自行定义的cobra包,更改后的root.go文件如下:

package cmd

import (
	"fmt"
	corbra "user/cobra"
	"os"
)

var cfgFile string

// rootCmd represents the base command when called without any subcommands
var rootCmd = &corbra.Command{
	Use:   "demo",
	Short: "A brief description of your application",
	Long: `A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:

Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
	// Uncomment the following line if your bare application
	// has an action associated with it:
	Run: func(cmd *corbra.Command, args []string) {
		fmt.Println("this is a test to run")
	 },
}

// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
	if err := rootCmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

在这里我们主要进行了以下改动:

  • 删除现在不必要的vipper等包,并将原来的cobra包改为我们自定义的corbra包:"user/cobra";
  • 删除了root.go中现在不需要的init与initConfig函数;
  • 将run与Execute函数中的cobra改为corbra,使用我们的corbra;

为了让根命令能够跑起来,我们的command.go文件的内容如下:

package cobra

import (
	"fmt"
)

// Command .
type Command struct {
	// Use is the one-line usage message.
	Use string
	// Short is the short description shown in the 'help' output.
	Short string
	// Long is the long message shown in the 'help <this-command>' output.
	Long string
	// commands is the list of commands supported by this program;
	commands []*Command
	// parent is a parent command for this command.
	parent *Command
	// args is a string list store all arg from command;
	args []string
	
	// Run: Typically the actual work function. Most commands will only implement this.
	Run func(cmd *Command, args []string)
}

// Execute .
func (c *Command) Execute() (err error) {
	fmt.Println("Execute run")
	err = c.execute()
	return nil
}

func (c *Command) execute()(err error){
	c.Run(c, c.args)
	return nil
}
  • Command结构体:在corbra中的Command结构体目前只包含了cobra的Command结构体的一小部分最基础的内容,后面将视情况加入其它部分。
  • Execute函数:作为对外接口的函数,这里的Execute函数只需要调用内部的execute函数即可,我们利用fmt输出这个Execute被调用的信息;
  • execute函数:实际调用Command结构体中的run函数,完成用户定义的函数,在这里应当会调用rootCmd的Run函数;

我们完成以上修改之后,对demo程序重新进行编译安装,并调用根命令,输出以下结果:

可以看到,根命令被正确调用;

阶段二:为根命令增加子命令;

​ 现在来到阶段二,在实现了根命令使用的情况下,我们希望能够给与根命令一个子命令,那么我们需要对command.go函数进行修改,得到以下内容:

package cobra

import (
	"os"
	"fmt"
	"strings"
)

// Command .
type Command struct {
	// Use is the one-line usage message.
	Use string
	// Short is the short description shown in the 'help' output.
	Short string
	// Long is the long message shown in the 'help <this-command>' output.
	Long string
	// commands is the list of commands supported by this program;
	commands []*Command
	// parent is a parent command for this command.
	parent *Command
	// args is a string list store all arg from command;
	args []string
	
	// Run: Typically the actual work function. Most commands will only implement this.
	Run func(cmd *Command, args []string)
}

// Execute .
func (c *Command) Execute() (err error) {
	fmt.Printf("Execute run %s\n", c.Name())
	if c.parent == nil{
		c.args = os.Args[1:]
	} else {
		c.args = c.parent.args[1:]
	}
	tcommands := stripFlags(c.args, c)
	if len(tcommands) == 0{
		err = c.execute()
		return err
	}
	//fmt.Printf("something wrong with %v\n", tcommands)
	for _, v := range c.commands{
		if tcommands[0] == v.Name(){
			err = v.Execute()
		}
	}
	return err
}

func (c *Command) execute()(err error){
	c.Run(c, c.args)
	return nil
}

// 从命令行参数中读取下一个子命令并返回该子命令的名称;
func stripFlags(args []string, c *Command) []string {
	if len(args) == 0 {
		return args
	}
	commands := []string{}

	for _, v := range args {
		if v != "" && !strings.HasPrefix(v, "-"){
			commands = append(commands, v)
			break
		}
	}
	return commands
}

// AddCommand 用于先命令c加入子命令;
// AddCommand is used to add a subcommand to command c
func (c *Command) AddCommand(cmds ...*Command){
	for i, x := range cmds {
		if cmds[i] == c {
			panic("Command can't be a child of itself")
		}
		cmds[i].parent = c
		c.commands = append(c.commands, x)
	}
}

// Name 函数用于返回命令的名称;
// 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
}
  • 增加AddCommand函数:使得子命令可以加入到父命令中;具体方式为,将命令加入父命令的子命令列表commands中,并将子命令的父命令指针parent指向父命令;
  • 增加Name函数:使得我们可以得到命令的名称,命令的名称即为我们初始化命令时的Use:XXX一列为命令分配的名字;
  • 增加stripFlags函数:用于从命令行参数中取出下一个子命令,当没有时返回空值;
  • 修改Execute函数:我们从根命令开始,利用os.Args得到命令行参数,并将os.Args[1:]赋予根命令的args成员变量,当为子命令时,就用父命令的args[1:]为子命令的args赋值。然后使用stripFlags函数获取下一条子命令,并执行子命令的Execute函数;如果没有下一条子命令说明到达命令末尾,我们调用当前命令的execute函数;

这样我们就实现了将子命令加入到父命令方法,并且实现了多级子命令,即根命令的子命令可以有自己的子命令。我们进行测试,为根命令root添加一个子命令test,并为test添加子命令testson。我们对demo程序进行重新编译安装,然后运行各个命令进行测试;

  • 运行demo:

  • 运行demo test:

  • 运行demo test testson

可以看到,我们的命令只运行的子命令的run函数,而没有运行父命令的run函数,说明多级子命令实现成功;但是由于Execute函数的限制,我们的命令行参数格式拥有以下限制;

格式要求:

由于我们的Execute函数对命令格式的要求,我们的命令行只能接收以下形式对子命令的调用:

根命令[子命令[子命令的子命令 ...]]

即我们要调用test子命令的时候,必须使用demo test,而不是test;调用testson的时候,必须使用demo test testson,以给出使用子命令的完整路径。

而且,我们的命令不允许在父命令和子命令之间存在其他命令行参数,只允许给与最后一个子命令命令行参数,即不允许如下情况出现:

demo -h test

但是可以使用:

demo test -h

阶段三:为命令增加参数;

那么,就到了最重要的部分了,给命令增加参数,为了给命令增加参数,我们需要对command.go作出修改,修改后的内容如下:

package cobra

import (
	...
	flag "github.com/spf13/pflag"
)

// Command .
type Command struct {
	...

	// flags is full set of flags.
	flags *flag.FlagSet

	...
}

...

func (c *Command) execute()(err error){
	c.ParseFlags(c.args)
	c.Run(c, c.args)
	return nil
}

// 从命令行参数中读取所有子命令并返回所有子命令的字符串列表;
func stripFlags(args []string, c *Command) []string {
	if len(args) == 0 {
		return args
	}
	commands := []string{}

	for _, v := range args {
		if v != "" && !strings.HasPrefix(v, "-"){
			commands = append(commands, v)
			break
		} else {
			break
		}
	}
	return commands
}

...

// Flags 用于初始化命令的flags参数
// Flags returns the complete FlagSet that applies
// to this command (local and persistent declared here and by all parents).
func (c *Command) Flags() *flag.FlagSet {
	if c.flags == nil {
		c.flags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
	}
	return c.flags
}

// ParseFlags 为命令行参数进行解析;
// ParseFlags parses persistent flag tree and local flags.
func (c *Command) ParseFlags(args []string) error {
	
	err := c.Flags().Parse(args)
	return err
}

修改的内容如下:

  • 新调用了pflag包,我们使用pflag包中的FlagSet变量及其相关函数来帮助我们进行命令行参数的解析;
  • command结构体增加了flags *flag.FlagSet用于保存各个命令行参数;
  • 增加了Flags函数用于初始化command结构体的flags变量,以及访问flags变量;
  • 增加了ParseFlags函数,在其中使用了pflag包中的FlagSet变量的类方法Parse([]string)来解析命令行参数,使用的数据则来自命令的args变量;
  • 改动了execute函数,新增对ParseFlags的调用,使得我们在运行命令的run函数之前就已经完成了对命令行参数的解析,之后Run中的用户可以使用cmd.Flags().GetXXX()函数访问解析后的命令行参数;
  • 改动了stripFlags函数,使它在分析到第一个非命令字符串的时候便停止分析;

测试:

​ 经过改动之后,我们就可以为我们的命令增加对应的命令行参数了,为了进行我们的测试,我们为test子命令增加了一个-v的命令行参数,它可以接收整数值;改动后的test.go文件如下:

package cmd

import (
	"fmt"

	corbra "user/cobra"
	//"github.com/spf13/cobra"
)

// testCmd represents the test command
var testCmd = &corbra.Command{
	Use:   "test",
	Short: "A brief description of your command",
	...
	Run: func(cmd *corbra.Command, args []string) {
		fmt.Println("test called")
		v, _:= cmd.Flags().GetInt("version")
		fmt.Printf("test called by args int %d\n", v)
	},
}

func init() {
	rootCmd.AddCommand(testCmd)
	...
	testCmd.Flags().IntP("version", "v", 1, "Help message for version")
}

我们在init中使用pflag中的FlagSet的IntP方法为test命令增加了一个命令行参数,并在test的run函数中对这个命令行参数进行了访问;结果如下:

我们使用以上三种形式对命令行参数-v进行访问,都输出了正确结果,而在默认情况下,我们输出了-v的默认值1;那么,我们为函数添加命令行参数成功;

阶段四:为命令增加默认的-h参数;

为了实现cobra中的一般功能,我们还必须为命令增加默认的-h参数,以及父命令的PersistFlag所定义的命令行参数可以被子命令访问的功能;为了实现这两个功能,我们对command.go进行如下更改:

package cobra

import (
	"os"
	"fmt"
	"strings"
	flag "github.com/spf13/pflag"
)



// Command .
type Command struct {
	...
	// helpFunc is 
	helpFunc func(c *Command, a []string)
	...
}

// Execute .
func (c *Command) Execute() (err error) {
	//fmt.Printf("Execute run %s\n", c.Name())
	c.InitDefaultHelpFlag()
	if c.parent == nil{
		c.args = os.Args[1:]
	} else {
		c.args = c.parent.args[1:]
	}
	tcommands := stripFlags(c.args, c)
	if len(tcommands) == 0{
		err = c.execute()
		return err
	}
	//fmt.Printf("something wrong with %v\n", tcommands)
	for _, v := range c.commands{
		if tcommands[0] == v.Name(){
			err = v.Execute()
		}
	}
	return err
}

func (c *Command) execute()(err error){
	c.ParseFlags(c.args)
	if v, _ := c.Flags().GetBool("help"); v == true {
		c.helpFunc(c, c.args)
		return nil
	}
	c.Run(c, c.args)
	return nil
}

...

// InitDefaultHelpFlag: 为命令c添加help命令行参数;
// InitDefaultHelpFlag adds default help flag to c.
// It is called automatically by executing the c or by calling help and usage.
// If c already has help flag, it will do nothing.
func (c *Command) InitDefaultHelpFlag() {
	// c.mergePersistentFlags()
	if c.Flags().Lookup("help") == nil {
		usage := "help for "
		if c.Name() == "" {
			usage += "this command"
		} else {
			usage += c.Name()
		}
		c.Flags().BoolP("help", "h", false, usage)
	}
	c.helpFunc = printHelp;
}

// printHelp: 用于打印帮助信息;
func printHelp(c *Command, a []string) {
	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.Flags().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.Println()
	if len(c.commands) > 0 {
		fmt.Printf("Use \"%s [command] --help\" for more information about a command.\n", c.Name())
	}
	fmt.Println()
}

改动如下:

  • command结构体中增加了一个helpFunc函数,用于定义帮助函数;
  • Execute函数增加了在刚开始的时候增加了一个对InitDefaultHelpFlag()函数的调用,为当前命令进行添加一个-h命令行参数,并为当前命令指定helpFunc函数;;
  • execute函数中增加对-h命令参数的判断,如果发现-h参数立即调用HelpFunc,打印帮助信息,并结束命令;
  • 增加InitDefaultHelpFlag函数,用于为当前命令添加-h参数,并指定helpFunc;
  • 增加printHelp函数,用于打印帮助信息;

输出示例:

以上就是本次作业的全部实现,本次作业所实现的command.go的接口与cobra中的command.go的接口相同,因此,我们在利用自己写的command.go替代cobra的时候,可以不用对函数进行太多的改动,只需要改动引用的库即可;

源码地址: https://gitee.com/wangyuwen2020/sever_count/tree/master/cobra

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值