From 08292530132d99a9a1d361ca5ba7b0986d5a8826 Mon Sep 17 00:00:00 2001
From: Liao PengFei <136953902+hdbdn77@users.noreply.github.com>
Date: Fri, 24 Nov 2023 15:30:39 +0800
Subject: [PATCH] Quickly start developing curves based on cobra

Signed-off-by: Liao PengFei <136953902+hdbdn77@users.noreply.github.com>
---
 cli_cn.md | 258 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 cli_en.md | 258 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 516 insertions(+)
 create mode 100644 cli_cn.md
 create mode 100644 cli_en.md

diff --git a/cli_cn.md b/cli_cn.md
new file mode 100644
index 000000000..52d4cbeb4
--- /dev/null
+++ b/cli_cn.md
@@ -0,0 +1,258 @@
+# Curveadm CLI 开发
+
+## Cobra库
+
+Curveadm CLI是基于[Cobra](https://github.com/spf13/cobra)库(一个用于创建CLI命令行程序的Go语言库)开发的。
+
+### Cobra的基本使用
+
+使用Cobra创建一个根命令(在命令行打印`root command`):
+```
+package main
+
+import (
+  "fmt"
+  "os"
+  "github.com/spf13/cobra"
+)
+
+var rootCmd = &cobra.Command{
+  Use:   "hugo",
+  Short: "Hugo is a very fast static site generator",
+  Long: `A Fast and Flexible Static Site Generator built with
+           love by spf13 and friends in Go.
+           Complete documentation is available at https://gohugo.io/documentation/`,
+  Run: func(cmd *cobra.Command, args []string) {
+    fmt.Println("root command")
+  },
+}
+
+func Execute() {
+  if err := rootCmd.Execute(); err != nil {
+    fmt.Fprintln(os.Stderr, err)
+    os.Exit(1)
+  }
+}
+
+func main() {
+  rootCmd.Execute()
+}
+```
+- Use字段设置命令的名字
+- Short字段设置简短描述
+- Long字段设置详细描述
+- Run字段设置执行该命令时的函数
+
+更多`Command`对象字段及用法详见[Cobra库--command.go](https://github.com/spf13/cobra/blob/main/command.go)。
+
+### flag使用
+
+Cobra支持自定义参数的解析:
+```
+cmd.PersistentFlags().BoolP("help", "h", false, "Print usage")
+cmd.Flags().BoolVarP(&options.debug, "debug", "d", false, "Print debug information")
+```
+PersistentFlags()用于全局flag,可以被当前命令及其子命令使用。
+
+Flags()则用于定义本地flag,仅用于当前命令。
+
+更多`flag`函数及用法详见[Cobra库--command.go](https://github.com/spf13/cobra/blob/main/command.go)和[pflag库](https://github.com/spf13/pflag)。
+
+### hook函数
+
+cobra的Command对象支持自定义hook函数(PreRun和PostRun字段),在`run`命令执行前后运行hook函数。如下所示:
+```
+cmd := &cobra.Command{
+  Use:   "root [sub]",
+  Short: "My root command",
+  PersistentPreRun: func(cmd *cobra.Command, args []string) {
+      fmt.Printf("Inside rootCmd PersistentPreRun with args: %v\n", args)
+    },
+    PreRun: func(cmd *cobra.Command, args []string) {
+      fmt.Printf("Inside rootCmd PreRun with args: %v\n", args)
+    },
+    Run: func(cmd *cobra.Command, args []string) {
+      fmt.Printf("Inside rootCmd Run with args: %v\n", args)
+    },
+    PostRun: func(cmd *cobra.Command, args []string) {
+      fmt.Printf("Inside rootCmd PostRun with args: %v\n", args)
+    },
+    PersistentPostRun: func(cmd *cobra.Command, args []string) {
+      fmt.Printf("Inside rootCmd PersistentPostRun with args: %v\n", args)
+    },
+}
+```
+hook函数会按(`PersistentPreRun`、`PreRun`、`Run`、`PostRun`、`PersistentPostRun`)的顺序依次执行。注意:如果子命令没有设置`Persistent*Run`,则会自动继承父命令的函数定义。
+
+### 子命令
+cobra允许嵌套命令,通过当前命令对象的`AddCommand`函数。如下所示:
+```
+rootCmd.AddCommand(versionCmd)
+```
+推荐的层级命令嵌套结构如下:
+```
+├── cmd
+│   ├── root.go
+│   └── sub1
+│       ├── sub1.go
+│       └── sub2
+│           ├── leafA.go
+│           └── leafB.go
+└── main.go
+```
+将 leafA.go 和 leafB.go 中定义的命令添加到 sub2 命令中。
+
+将 sub2.go 中定义的命令添加到 sub1 命令中。
+
+将 sub1.go 中定义的命令添加到 root 命令中。
+
+最终的命令调用的主体结构如下:
+```
+root options
+root sub1 options
+root sub1 sub2 options
+root sub1 sub2 leafA options
+root sub1 sub2 leafB options
+```
+
+
+## curveadm cli项目结构
+```
+cli
+├── cli
+│   ├── cli.go
+│   └── version.go
+├── command
+│   ├── audit.go
+│   ├── clean.go
+│   ├── client
+│   ├── cluster
+│   ...
+└── curveadm.go
+```
+cli 文件夹的`cli.go`定义了`curveadm`对象及相关方法,贯穿所有curveadm cli的命令开发。
+```
+type CurveAdm struct {
+  rootDir      string
+  dataDir      string
+  ...
+}
+func NewCurveAdm() (*CurveAdm, error) {
+  curveadm := &CurveAdm{
+    rootDir:      rootDir,
+    ...
+  }
+  ...
+  return curveadm, nil
+}
+```
+
+command 目录中存放各层级命令实现。
+```
+├── audit.go
+├── client
+│   ├── cmd.go
+│   ├── enter.go
+│   └── unmap.go
+├── cluster
+│   ├── add.go
+│   ├── cmd.go
+├── cmd.go
+├── deploy.go
+```
+在curveadm cli中,每层的根命令都在`cmd.go`定义。根命令只负责注册子命令以及提供帮助信息,并不参与实际工作操作。
+```
+cli\command\cmd.go
+
+func addSubCommands(cmd *cobra.Command, curveadm *cli.CurveAdm) {
+  cmd.AddCommand(
+    client.NewClientCommand(curveadm),         // curveadm client
+    ...
+  )
+}
+func NewCurveAdmCommand(curveadm *cli.CurveAdm) *cobra.Command {
+  ...
+  cmd := &cobra.Command{
+    Use:     "curveadm [OPTIONS] COMMAND [ARGS...]",
+    ...
+  }
+  ...
+  addSubCommands(cmd, curveadm)
+  return cmd
+}
+################################################################
+cli\command\client\cmd.go
+
+func NewClientCommand(curveadm *cli.CurveAdm) *cobra.Command {
+  cmd := &cobra.Command{
+    Use:   "client",
+    ...
+  }
+  cmd.AddCommand(
+    NewMapCommand(curveadm),
+    ...
+  )
+  ...
+}
+################################################################
+cli\command\client\enter.go
+
+func NewEnterCommand(curveadm *cli.CurveAdm) *cobra.Command {
+  ...
+  cmd := &cobra.Command{
+    Use:   "enter ID",
+    ...
+  }
+  ...
+}
+```
+最终enter命令的调用结构如下:
+```
+curveadm client enter ID
+```
+
+curveadm.go 定义了`curveadm` 根命令的执行函数同时执行相关审计工作。
+```
+func Execute() {
+  curveadm, err := cli.NewCurveAdm()
+  ...
+  id := curveadm.PreAudit(time.Now(), os.Args[1:])
+  cmd := command.NewCurveAdmCommand(curveadm)
+  err = cmd.Execute()
+  curveadm.PostAudit(id, err)
+  if err != nil {
+    os.Exit(1)
+  }
+}
+```
+
+`curveadm` 主程序的入口则是在[curveadm文件夹下](https://github.com/opencurve/curveadm/tree/develop/cmd/curveadm),可以在该目录下执行`curveadm`的运行及编译
+```
+func main() {
+  cli.Execute()
+}
+```
+
+### curveadm 通用工具
+对于curveadm cli的命令开发,curveadm 提供了通用工具,如:
+
+- cliutil.NoArgs:用于判断命令是否不包含参数
+
+- cliutil.ShowHelp:用于在命令运行时展示帮助信息
+
+在[curveadm/internal目录下](https://github.com/opencurve/curveadm/tree/develop/internal)。如下所示:
+```
+import (
+  cliutil "github.com/opencurve/curveadm/internal/utils"
+  ...
+)
+
+cmd := &cobra.Command{
+  Use:   "client",
+  Args:  cliutil.NoArgs,
+  RunE:  cliutil.ShowHelp(curveadm.Err()),
+}
+```
+`cliutil.NoArgs`指明`curveadm client`命令不包含任何参数(子命令除外);`cliutil.ShowHelp`函数在直接运行`curveadm client`命令时展示定义的帮助选项。
+
+更多通用命令及用法请参考[internal文件夹](https://github.com/opencurve/curveadm/tree/develop/internal)。
\ No newline at end of file
diff --git a/cli_en.md b/cli_en.md
new file mode 100644
index 000000000..bd0201366
--- /dev/null
+++ b/cli_en.md
@@ -0,0 +1,258 @@
+# Curveadm CLI Development
+
+## Cobra library
+
+Curveadm CLI is developed based on the [Cobra](https://github.com/spf13/cobra) library, a Go language library for creating CLI command line programs.
+
+### Basic use of Cobra
+
+Create a root command using Cobra (print `root command` on the command line):
+```
+package main
+
+import (
+   "fmt"
+   "os"
+   "github.com/spf13/cobra"
+)
+
+var rootCmd = &cobra.Command{
+   Use: "hugo",
+   Short: "Hugo is a very fast static site generator",
+   Long: `A Fast and Flexible Static Site Generator built with
+            love by spf13 and friends in Go.
+            Complete documentation is available at https://gohugo.io/documentation/`,
+   Run: func(cmd *cobra.Command, args []string) {
+     fmt.Println("root command")
+   },
+}
+
+func Execute() {
+   if err := rootCmd.Execute(); err != nil {
+     fmt.Fprintln(os.Stderr, err)
+     os.Exit(1)
+   }
+}
+
+func main() {
+   rootCmd.Execute()
+}
+```
+- Use field sets the name of the command
+- Short field sets a short description
+- Long field setting detailed description
+- The Run field sets the function when executing the command
+
+For more details on `Command` object fields and usage, see [Cobra library--command.go](https://github.com/spf13/cobra/blob/main/command.go).
+
+### flag usage
+
+Cobra supports parsing of custom parameters:
+```
+cmd.PersistentFlags().BoolP("help", "h", false, "Print usage")
+cmd.Flags().BoolVarP(&options.debug, "debug", "d", false, "Print debug information")
+```
+PersistentFlags() is used for global flags and can be used by the current command and its subcommands.
+
+Flags() is used to define local flags, only for the current command.
+
+For more details on the `flag` function and usage, see [Cobra library--command.go](https://github.com/spf13/cobra/blob/main/command.go) and [pflag library](https:// github.com/spf13/pflag).
+
+### hook function
+
+Cobra's Command object supports custom hook functions (PreRun and PostRun fields), and the hook function is run before and after the `run` command is executed. As follows:
+```
+cmd := &cobra.Command{
+   Use: "root [sub]",
+   Short: "My root command",
+   PersistentPreRun: func(cmd *cobra.Command, args []string) {
+       fmt.Printf("Inside rootCmd PersistentPreRun with args: %v\n", args)
+     },
+     PreRun: func(cmd *cobra.Command, args []string) {
+       fmt.Printf("Inside rootCmd PreRun with args: %v\n", args)
+     },
+     Run: func(cmd *cobra.Command, args []string) {
+       fmt.Printf("Inside rootCmd Run with args: %v\n", args)
+     },
+     PostRun: func(cmd *cobra.Command, args []string) {
+       fmt.Printf("Inside rootCmd PostRun with args: %v\n", args)
+     },
+     PersistentPostRun: func(cmd *cobra.Command, args []string) {
+       fmt.Printf("Inside rootCmd PersistentPostRun with args: %v\n", args)
+     },
+}
+```
+The hook function will be executed in the order of (`PersistentPreRun`, `PreRun`, `Run`, `PostRun`, `PersistentPostRun`). Note: If the subcommand does not set `Persistent*Run`, it will automatically inherit the function definition of the parent command.
+
+### Subcommands
+cobra allows nested commands, through the `AddCommand` function of the current command object. As follows:
+```
+rootCmd.AddCommand(versionCmd)
+```
+The recommended hierarchical command nesting structure is as follows:
+```
+├── cmd
+│ ├── root.go
+│ └── sub1
+│ ├── sub1.go
+│ └── sub2
+│ ├── leafA.go
+│ └── leafB.go
+└── main.go
+```
+Add the commands defined in leafA.go and leafB.go to the sub2 command.
+
+Add the commands defined in sub2.go to the sub1 command.
+
+Add the commands defined in sub1.go to the root command.
+
+The main structure of the final command call is as follows:
+```
+root options
+root sub1 options
+root sub1 sub2 options
+root sub1 sub2 leafA options
+root sub1 sub2 leafB options
+```
+
+
+## curveadm cli project structure
+```
+cli
+├── cli
+│ ├── cli.go
+│ └── version.go
+├── command
+│  ├── audit.go
+│ ├── clean.go
+│ ├── client
+│ ├── cluster
+│ ...
+└── curveadm.go
+```
+The `cli.go` in the cli folder defines the `curveadm` object and related methods, which run through all curveadm cli command development.
+```
+type CurveAdm struct {
+   rootDir string
+   dataDir string
+   ...
+}
+func NewCurveAdm() (*CurveAdm, error) {
+   curveadm := &CurveAdm{
+     rootDir: rootDir,
+     ...
+   }
+   ...
+   return curveadm, nil
+}
+```
+
+The command directory stores command implementations at each level.
+```
+├── audit.go
+├── client
+│ ├── cmd.go
+│ ├── enter.go
+│ └── unmap.go
+├── cluster
+│ ├── add.go
+│ ├── cmd.go
+├── cmd.go
+├── deploy.go
+```
+In curveadm cli, the root command of each layer is defined in `cmd.go`. The root command is only responsible for registering subcommands and providing help information, and does not participate in actual work operations.
+```
+cli\command\cmd.go
+
+func addSubCommands(cmd *cobra.Command, curveadm *cli.CurveAdm) {
+   cmd.AddCommand(
+     client.NewClientCommand(curveadm), // curveadm client
+     ...
+   )
+}
+func NewCurveAdmCommand(curveadm *cli.CurveAdm) *cobra.Command {
+   ...
+   cmd := &cobra.Command{
+     Use: "curveadm [OPTIONS] COMMAND [ARGS...]",
+     ...
+   }
+   ...
+   addSubCommands(cmd, curveadm)
+   return cmd
+}
+################################################ ##############
+cli\command\client\cmd.go
+
+func NewClientCommand(curveadm *cli.CurveAdm) *cobra.Command {
+   cmd := &cobra.Command{
+     Use: "client",
+     ...
+   }
+   cmd.AddCommand(
+     NewMapCommand(curveadm),
+     ...
+   )
+   ...
+}
+################################################ ##############
+cli\command\client\enter.go
+
+func NewEnterCommand(curveadm *cli.CurveAdm) *cobra.Command {
+   ...
+   cmd := &cobra.Command{
+     Use: "enter ID",
+     ...
+   }
+   ...
+}
+```
+The final call structure of the enter command is as follows:
+```
+curveadm client enter ID
+```
+
+curveadm.go defines the execution function of the `curveadm` root command and performs related audit work.
+```
+func Execute() {
+   curveadm, err := cli.NewCurveAdm()
+   ...
+   id := curveadm.PreAudit(time.Now(), os.Args[1:])
+   cmd := command.NewCurveAdmCommand(curveadm)
+   err = cmd.Execute()
+   curveadm.PostAudit(id, err)
+   if err != nil {
+     os.Exit(1)
+   }
+}
+```
+
+The entrance to the `curveadm` main program is under the [curveadm folder](https://github.com/opencurve/curveadm/tree/develop/cmd/curveadm). You can execute the operation and execution of `curveadm` in this directory. compile
+```
+func main() {
+   cli.Execute()
+}
+```
+
+### curveadm general tools
+For command development of curveadm cli, curveadm provides general tools, such as:
+
+- cliutil.NoArgs: used to determine whether the command does not contain parameters
+
+- cliutil.ShowHelp: used to display help information when the command is run
+
+In the [curveadm/internal directory](https://github.com/opencurve/curveadm/tree/develop/internal). As follows:
+```
+import (
+   cliutil "github.com/opencurve/curveadm/internal/utils"
+   ...
+)
+
+cmd := &cobra.Command{
+   Use: "client",
+   Args: cliutil.NoArgs,
+   RunE: cliutil.ShowHelp(curveadm.Err()),
+}
+```
+`cliutil.NoArgs` specifies that the `curveadm client` command does not contain any arguments (except subcommands); the `cliutil.ShowHelp` function displays the defined help options when running the `curveadm client` command directly.
+
+For more common commands and usage, please refer to [internal folder](https://github.com/opencurve/curveadm/tree/develop/internal).
\ No newline at end of file