This is an automated email from the ASF dual-hosted git repository.

klesh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git


The following commit(s) were added to refs/heads/main by this push:
     new d0ca0fcb add generator for create-plugin (#2352)
d0ca0fcb is described below

commit d0ca0fcb6ee467e008526cf47ffd6a17b9cfd748
Author: likyh <[email protected]>
AuthorDate: Mon Jun 27 11:11:44 2022 +0800

    add generator for create-plugin (#2352)
    
    * add generator for create-plugin
    
    * add document
    
    * append
    
    * delete gif in repo
    
    * fix lint
    
    Co-authored-by: linyh <[email protected]>
    Co-authored-by: Klesh Wong <[email protected]>
---
 generator/README.md                                |  27 +++++
 generator/cmd/create_collector.go                  | 128 +++++++++++++++++++
 generator/cmd/create_extractor.go                  | 126 +++++++++++++++++++
 generator/cmd/create_plugin.go                     | 135 +++++++++++++++++++++
 .../task_data.go-template => cmd/generator_doc.go} |  25 ++--
 generator/cmd/root.go                              |  77 ++++++++++++
 generator/docs/generator.md                        |  21 ++++
 generator/docs/generator_completion.md             |  32 +++++
 generator/docs/generator_completion_bash.md        |  51 ++++++++
 generator/docs/generator_completion_fish.md        |  42 +++++++
 generator/docs/generator_completion_powershell.md  |  39 ++++++
 generator/docs/generator_completion_zsh.md         |  53 ++++++++
 generator/docs/generator_create-collector.md       |  31 +++++
 generator/docs/generator_create-extractor.md       |  31 +++++
 generator/docs/generator_create-plugin.md          |  31 +++++
 generator/docs/generator_generator-doc.md          |  26 ++++
 .../plugin/tasks/task_data.go-template => main.go} |  23 ++--
 generator/template/plugin/plugin_main.go-template  |  10 +-
 ...ate => plugin_main_with_api_client.go-template} |  14 +--
 .../template/plugin/tasks/api_client.go-template   |  10 +-
 .../template/plugin/tasks/extractor.go-template    |   1 -
 .../template/plugin/tasks/task_data.go-template    |   6 -
 ...plate => task_data_with_api_client.go-template} |   3 +-
 generator/util/template.go                         | 102 ++++++++++++++++
 go.mod                                             |   7 +-
 go.sum                                             |  12 ++
 26 files changed, 1002 insertions(+), 61 deletions(-)

diff --git a/generator/README.md b/generator/README.md
new file mode 100644
index 00000000..b129f940
--- /dev/null
+++ b/generator/README.md
@@ -0,0 +1,27 @@
+# Apache DevLake Cli Tool -- Code Generator
+
+## How to use?
+
+Just run by `go run`:
+```bash
+go run generator/main.go [command]
+```
+
+Help also integrate in it:
+```bash
+go run generator/main.go help
+```
+
+Usage Gif:
+![usage](https://user-images.githubusercontent.com/3294100/175464884-1dce09b0-fade-4c26-9a1b-b535d9651bc1.gif)
+
+## Plugin Related
+
+* [create-collector](./docs/generator_create-collector.md)      - Create a new 
collector
+* [create-extractor](./docs/generator_create-extractor.md)      - Create a new 
extractor
+* [create-plugin](./docs/generator_create-plugin.md)    - Create a new plugin
+
+## Others
+
+* [completion](./docs/generator_completion.md)  - Generate the autocompletion 
script for the specified shell
+* [global options](./docs/generator.md)
diff --git a/generator/cmd/create_collector.go 
b/generator/cmd/create_collector.go
new file mode 100644
index 00000000..79744028
--- /dev/null
+++ b/generator/cmd/create_collector.go
@@ -0,0 +1,128 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package cmd
+
+import (
+       "errors"
+       "fmt"
+       "github.com/apache/incubator-devlake/generator/util"
+       "github.com/manifoldco/promptui"
+       "github.com/spf13/cobra"
+       "github.com/stoewer/go-strcase"
+       "os"
+       "path/filepath"
+       "regexp"
+       "strings"
+)
+
+func init() {
+       rootCmd.AddCommand(createCollectorCmd)
+}
+
+func collectorNameNotExistValidateHoc(pluginName string) func(input string) 
error {
+       collectorNameValidate := func(input string) error {
+               if input == `` {
+                       return errors.New("please input which data would you 
will collect (snake_format)")
+               }
+               snakeNameReg := regexp.MustCompile(`^[A-Za-z][A-Za-z0-9_]*$`)
+               if !snakeNameReg.MatchString(input) {
+                       return errors.New("collector name invalid (start with 
a-z and consist with a-z0-9_)")
+               }
+               _, err := os.Stat(filepath.Join(`plugins`, pluginName, `tasks`, 
input+`_collector.go`))
+               if os.IsNotExist(err) {
+                       return nil
+               }
+               if err != nil {
+                       return err
+               }
+               return errors.New("collector exists")
+       }
+       return collectorNameValidate
+}
+
+func collectorNameExistValidateHoc(pluginName string) func(input string) error 
{
+       collectorNameValidate := func(input string) error {
+               if input == `` {
+                       return errors.New("please input which data would you 
will collect (snake_format)")
+               }
+               _, err := os.Stat(filepath.Join(`plugins`, pluginName, `tasks`, 
input+`_collector.go`))
+               return err
+       }
+       return collectorNameValidate
+}
+
+var createCollectorCmd = &cobra.Command{
+       Use:   "create-collector [plugin_name] [collector_name]",
+       Short: "Create a new collector",
+       Long: `Create a new collector
+Type in what the name of collector is, then generator will create a new 
collector in plugins/$plugin_name/tasks/$collector_name for you`,
+       Run: func(cmd *cobra.Command, args []string) {
+               var pluginName string
+               var collectorName string
+               var err error
+
+               // try to get plugin name and collector name
+               if len(args) > 0 {
+                       pluginName = args[0]
+               }
+               prompt := promptui.Prompt{
+                       Label:    "plugin_name",
+                       Validate: pluginNameExistValidate,
+                       Default:  pluginName,
+               }
+               pluginName, err = prompt.Run()
+               cobra.CheckErr(err)
+               pluginName = strings.ToLower(pluginName)
+
+               if len(args) > 1 {
+                       collectorName = args[1]
+               }
+               prompt = promptui.Prompt{
+                       Label:    "collector_data_name",
+                       Validate: collectorNameNotExistValidateHoc(pluginName),
+                       Default:  collectorName,
+               }
+               collectorName, err = prompt.Run()
+               cobra.CheckErr(err)
+               collectorName = strings.ToLower(collectorName)
+
+               // read template
+               templates := map[string]string{
+                       collectorName + `_collector.go`: 
util.ReadTemplate("generator/template/plugin/tasks/api_collector.go-template"),
+               }
+
+               // create vars
+               values := map[string]string{}
+               util.GenerateAllFormatVar(values, `plugin_name`, pluginName)
+               util.GenerateAllFormatVar(values, `collector_data_name`, 
collectorName)
+               collectorDataNameUpperCamel := 
strcase.UpperCamelCase(collectorName)
+               values = util.DetectExistVars(templates, values)
+               println(`vars in template:`, fmt.Sprint(values))
+
+               // write template
+               util.ReplaceVarInTemplates(templates, values)
+               util.WriteTemplates(filepath.Join(`plugins`, pluginName, 
`tasks`), templates)
+               if modifyExistCode {
+                       util.ReplaceVarInFile(
+                               filepath.Join(`plugins`, pluginName, 
`plugin_main.go`),
+                               regexp.MustCompile(`(return 
+\[]core\.SubTaskMeta ?\{ ?\n?)((\s*[\w.]+,\n)*)(\s*})`),
+                               fmt.Sprintf("$1$2\t\ttasks.Collect%sMeta,\n$4", 
collectorDataNameUpperCamel),
+                       )
+               }
+       },
+}
diff --git a/generator/cmd/create_extractor.go 
b/generator/cmd/create_extractor.go
new file mode 100644
index 00000000..b63ac6a6
--- /dev/null
+++ b/generator/cmd/create_extractor.go
@@ -0,0 +1,126 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package cmd
+
+import (
+       "errors"
+       "fmt"
+       "github.com/apache/incubator-devlake/generator/util"
+       "github.com/manifoldco/promptui"
+       "github.com/spf13/cobra"
+       "github.com/stoewer/go-strcase"
+       "os"
+       "path/filepath"
+       "regexp"
+       "strings"
+)
+
+func init() {
+       rootCmd.AddCommand(createExtractorCmd)
+}
+
+func extractorNameNotExistValidateHoc(pluginName string) func(input string) 
error {
+       extractorNameValidate := func(input string) error {
+               if input == `` {
+                       return errors.New("please input which data would you 
will extract (snake_format)")
+               }
+               snakeNameReg := regexp.MustCompile(`^[A-Za-z][A-Za-z0-9_]*$`)
+               if !snakeNameReg.MatchString(input) {
+                       return errors.New("extractor name invalid (start with 
a-z and consist with a-z0-9_)")
+               }
+               _, err := os.Stat(filepath.Join(`plugins`, pluginName, `tasks`, 
input+`_extractor.go`))
+               if os.IsNotExist(err) {
+                       return nil
+               }
+               if err != nil {
+                       return err
+               }
+               return errors.New("extractor exists")
+       }
+       return extractorNameValidate
+}
+
+var createExtractorCmd = &cobra.Command{
+       Use:   "create-extractor [plugin_name] [extractor_name]",
+       Short: "Create a new extractor",
+       Long: `Create a new extractor
+Type in what the name of extractor is, then generator will create a new 
extractor in plugins/$plugin_name/tasks/$extractor_name for you`,
+       Run: func(cmd *cobra.Command, args []string) {
+               var pluginName string
+               var extractorName string
+               var err error
+
+               // try to get plugin name and extractor name
+               if len(args) > 0 {
+                       pluginName = args[0]
+               }
+               prompt := promptui.Prompt{
+                       Label:    "plugin_name",
+                       Validate: pluginNameExistValidate,
+                       Default:  pluginName,
+               }
+               pluginName, err = prompt.Run()
+               cobra.CheckErr(err)
+               pluginName = strings.ToLower(pluginName)
+
+               prompt = promptui.Prompt{
+                       Label:    "collector_name",
+                       Validate: collectorNameExistValidateHoc(pluginName),
+               }
+               collectorName, err := prompt.Run()
+               cobra.CheckErr(err)
+               collectorName = strings.ToLower(collectorName)
+
+               if len(args) > 1 {
+                       extractorName = args[1]
+               }
+               prompt = promptui.Prompt{
+                       Label:    "extractor_name",
+                       Validate: extractorNameNotExistValidateHoc(pluginName),
+                       Default:  extractorName,
+               }
+               extractorName, err = prompt.Run()
+               cobra.CheckErr(err)
+               extractorName = strings.ToLower(extractorName)
+
+               // read template
+               templates := map[string]string{
+                       extractorName + `_extractor.go`: 
util.ReadTemplate("generator/template/plugin/tasks/extractor.go-template"),
+               }
+
+               // create vars
+               values := map[string]string{}
+               util.GenerateAllFormatVar(values, `plugin_name`, pluginName)
+               util.GenerateAllFormatVar(values, `collector_data_name`, 
collectorName)
+               util.GenerateAllFormatVar(values, `extractor_data_name`, 
extractorName)
+               extractorDataNameUpperCamel := 
strcase.UpperCamelCase(extractorName)
+               values = util.DetectExistVars(templates, values)
+               println(`vars in template:`, fmt.Sprint(values))
+
+               // write template
+               util.ReplaceVarInTemplates(templates, values)
+               util.WriteTemplates(filepath.Join(`plugins`, pluginName, 
`tasks`), templates)
+               if modifyExistCode {
+                       util.ReplaceVarInFile(
+                               filepath.Join(`plugins`, pluginName, 
`plugin_main.go`),
+                               regexp.MustCompile(`(return 
+\[]core\.SubTaskMeta ?\{ ?\n?)((\s*[\w.]+,\n)*)(\s*})`),
+                               fmt.Sprintf("$1$2\t\ttasks.Extract%sMeta,\n$4", 
extractorDataNameUpperCamel),
+                       )
+               }
+       },
+}
diff --git a/generator/cmd/create_plugin.go b/generator/cmd/create_plugin.go
new file mode 100644
index 00000000..026cea13
--- /dev/null
+++ b/generator/cmd/create_plugin.go
@@ -0,0 +1,135 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package cmd
+
+import (
+       "errors"
+       "fmt"
+       "github.com/apache/incubator-devlake/generator/util"
+       "github.com/manifoldco/promptui"
+       "github.com/spf13/cobra"
+       "os"
+       "regexp"
+       "strings"
+)
+
+func init() {
+       rootCmd.AddCommand(createPluginCmd)
+}
+
+func pluginNameNotExistValidate(input string) error {
+       if input == `` {
+               return errors.New("plugin name requite")
+       }
+       snakeNameReg := regexp.MustCompile(`^[A-Za-z][A-Za-z0-9_]*$`)
+       if !snakeNameReg.MatchString(input) {
+               return errors.New("plugin name invalid (start with a-z and 
consist with a-z0-9_)")
+       }
+       _, err := os.Stat(`plugins/` + input)
+       if os.IsNotExist(err) {
+               return nil
+       }
+       if err != nil {
+               return err
+       }
+       return errors.New("plugin exists")
+}
+
+func pluginNameExistValidate(input string) error {
+       if input == `` {
+               return errors.New("plugin name requite")
+       }
+       _, err := os.Stat(`plugins/` + input)
+       return err
+}
+
+var createPluginCmd = &cobra.Command{
+       Use:   "create-plugin [plugin_name]",
+       Short: "Create a new plugin",
+       Long: `Create a new plugin
+Type in what the name of plugin is, then generator will create a new plugin in 
plugins/$plugin_name for you`,
+       Run: func(cmd *cobra.Command, args []string) {
+               var pluginName string
+
+               // try to get plugin name
+               if len(args) > 0 {
+                       pluginName = args[0]
+               }
+               err := pluginNameNotExistValidate(pluginName)
+               if err != nil {
+                       prompt := promptui.Prompt{
+                               Label:    "plugin_name",
+                               Validate: pluginNameNotExistValidate,
+                               Default:  pluginName,
+                       }
+                       pluginName, err = prompt.Run()
+                       cobra.CheckErr(err)
+                       pluginName = strings.ToLower(pluginName)
+               }
+
+               prompt := promptui.Select{
+                       Label: "with_api_client (is this plugin will request 
HTTP APIs?)",
+                       Items: []string{"Yes", "No"},
+               }
+               _, withApiClient, err := prompt.Run()
+               cobra.CheckErr(err)
+
+               values := map[string]string{}
+               templates := map[string]string{}
+               if withApiClient == `Yes` {
+                       prompt := promptui.Prompt{
+                               Label:   "Endpoint (which host to request)",
+                               Default: `https://open.example.cn/api/v1`,
+                               Validate: func(input string) error {
+                                       if input == `` {
+                                               return errors.New("endpoint 
requite")
+                                       }
+                                       if !strings.HasPrefix(input, `http`) {
+                                               return errors.New("endpoint 
should start with http")
+                                       }
+                                       return nil
+                               },
+                       }
+                       endpoint, err := prompt.Run()
+                       cobra.CheckErr(err)
+
+                       // read template
+                       templates = map[string]string{
+                               `plugin_main.go`:      
util.ReadTemplate("generator/template/plugin/plugin_main_with_api_client.go-template"),
+                               `tasks/api_client.go`: 
util.ReadTemplate("generator/template/plugin/tasks/api_client.go-template"),
+                               `tasks/task_data.go`:  
util.ReadTemplate("generator/template/plugin/tasks/task_data_with_api_client.go-template"),
+                       }
+                       util.GenerateAllFormatVar(values, `plugin_name`, 
pluginName)
+                       values[`Endpoint`] = endpoint
+               } else if withApiClient == `No` {
+                       // read template
+                       templates = map[string]string{
+                               `plugin_main.go`:     
util.ReadTemplate("generator/template/plugin/plugin_main.go-template"),
+                               `tasks/task_data.go`: 
util.ReadTemplate("generator/template/plugin/tasks/task_data.go-template"),
+                       }
+                       util.GenerateAllFormatVar(values, `plugin_name`, 
pluginName)
+               }
+
+               values = util.DetectExistVars(templates, values)
+               println(`vars in template:`, fmt.Sprint(values))
+
+               // write template
+               util.ReplaceVarInTemplates(templates, values)
+               util.WriteTemplates(`plugins/`+pluginName, templates)
+       },
+}
diff --git a/generator/template/plugin/tasks/task_data.go-template 
b/generator/cmd/generator_doc.go
similarity index 59%
copy from generator/template/plugin/tasks/task_data.go-template
copy to generator/cmd/generator_doc.go
index 8a58ae4c..6a4cded6 100644
--- a/generator/template/plugin/tasks/task_data.go-template
+++ b/generator/cmd/generator_doc.go
@@ -15,24 +15,21 @@ See the License for the specific language governing 
permissions and
 limitations under the License.
 */
 
-package tasks
+package cmd
 
 import (
-       "github.com/apache/incubator-devlake/plugins/helper"
+       "github.com/spf13/cobra"
+       "github.com/spf13/cobra/doc"
 )
 
-type {{ .PluginName }}ApiParams struct {
+func init() {
+       rootCmd.AddCommand(GeneratorDocCmd)
 }
 
-type {{ .PluginName }}Options struct {
-       // TODO add some custom options here if necessary
-       // options means some custom params required by plugin running.
-       // Such As How many rows do your want
-       // You can use it in sub tasks and you need pass it in main.go and 
pipelines.
-       Tasks []string `json:"tasks,omitempty"`
-}
-
-type {{ .PluginName }}TaskData struct {
-       Options   *{{ .PluginName }}Options
-       // ApiClient *helper.ApiAsyncClient
+var GeneratorDocCmd = &cobra.Command{
+       Use:   "generator-doc",
+       Short: "generate document for generator",
+       RunE: func(cmd *cobra.Command, args []string) error {
+               return doc.GenMarkdownTree(rootCmd, "generator/docs")
+       },
 }
diff --git a/generator/cmd/root.go b/generator/cmd/root.go
new file mode 100644
index 00000000..4a1316c5
--- /dev/null
+++ b/generator/cmd/root.go
@@ -0,0 +1,77 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package cmd
+
+import (
+       "errors"
+       "fmt"
+       "os"
+
+       "github.com/spf13/cobra"
+       "github.com/spf13/viper"
+)
+
+var (
+       // Used for flags.
+       cfgFile         string
+       modifyExistCode bool
+
+       rootCmd = &cobra.Command{
+               Use:   `generator [command]`,
+               Short: "Apache DevLake Cli Tool -- Code Generator",
+       }
+)
+
+// Execute executes the root command.
+func Execute() error {
+       return rootCmd.Execute()
+}
+
+func init() {
+       cobra.OnInitialize(initConfig)
+
+       rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config 
file (default is PROJECT/.env)")
+       rootCmd.PersistentFlags().BoolVar(&modifyExistCode, "modifyExistCode", 
true, "allow generator modify exist code")
+}
+
+func initConfig() {
+       if cfgFile != "" {
+               // Use config file from the flag.
+               viper.SetConfigFile(cfgFile)
+       } else {
+               viper.AddConfigPath("..")
+               viper.AddConfigPath(".")
+               viper.SetConfigType("env")
+               viper.SetConfigName(".env")
+       }
+
+       viper.AutomaticEnv()
+
+       err := viper.ReadInConfig()
+
+       notFound := &viper.ConfigFileNotFoundError{}
+       switch {
+       case err != nil && !errors.As(err, notFound):
+               cobra.CheckErr(err)
+       case err != nil && errors.As(err, notFound):
+               // The config file is optional, we shouldn't exit when the 
config is not found
+               break
+       default:
+               fmt.Fprintln(os.Stderr, "Using config file:", 
viper.ConfigFileUsed())
+       }
+}
diff --git a/generator/docs/generator.md b/generator/docs/generator.md
new file mode 100644
index 00000000..003fd86f
--- /dev/null
+++ b/generator/docs/generator.md
@@ -0,0 +1,21 @@
+## generator
+
+Apache DevLake Cli Tool -- Code Generator
+
+### Options
+
+```
+      --config string     config file (default is PROJECT/.env)
+  -h, --help              help for generator
+      --modifyExistCode   allow generator modify exist code (default true)
+```
+
+### SEE ALSO
+
+* [generator completion](generator_completion.md)       - Generate the 
autocompletion script for the specified shell
+* [generator create-collector](generator_create-collector.md)   - Create a new 
collector
+* [generator create-extractor](generator_create-extractor.md)   - Create a new 
extractor
+* [generator create-plugin](generator_create-plugin.md)         - Create a new 
plugin
+* [generator generator-doc](generator_generator-doc.md)         - generate 
document for generator
+
+###### Auto generated by spf13/cobra on 24-Jun-2022
diff --git a/generator/docs/generator_completion.md 
b/generator/docs/generator_completion.md
new file mode 100644
index 00000000..c0f14d04
--- /dev/null
+++ b/generator/docs/generator_completion.md
@@ -0,0 +1,32 @@
+## generator completion
+
+Generate the autocompletion script for the specified shell
+
+### Synopsis
+
+Generate the autocompletion script for generator for the specified shell.
+See each sub-command's help for details on how to use the generated script.
+
+
+### Options
+
+```
+  -h, --help   help for completion
+```
+
+### Options inherited from parent commands
+
+```
+      --config string     config file (default is PROJECT/.env)
+      --modifyExistCode   allow generator modify exist code (default true)
+```
+
+### SEE ALSO
+
+* [generator](generator.md)     - Apache DevLake Cli Tool -- Code Generator
+* [generator completion bash](generator_completion_bash.md)     - Generate the 
autocompletion script for bash
+* [generator completion fish](generator_completion_fish.md)     - Generate the 
autocompletion script for fish
+* [generator completion powershell](generator_completion_powershell.md)        
 - Generate the autocompletion script for powershell
+* [generator completion zsh](generator_completion_zsh.md)       - Generate the 
autocompletion script for zsh
+
+###### Auto generated by spf13/cobra on 24-Jun-2022
diff --git a/generator/docs/generator_completion_bash.md 
b/generator/docs/generator_completion_bash.md
new file mode 100644
index 00000000..99f70263
--- /dev/null
+++ b/generator/docs/generator_completion_bash.md
@@ -0,0 +1,51 @@
+## generator completion bash
+
+Generate the autocompletion script for bash
+
+### Synopsis
+
+Generate the autocompletion script for the bash shell.
+
+This script depends on the 'bash-completion' package.
+If it is not installed already, you can install it via your OS's package 
manager.
+
+To load completions in your current shell session:
+
+       source <(generator completion bash)
+
+To load completions for every new session, execute once:
+
+#### Linux:
+
+       generator completion bash > /etc/bash_completion.d/generator
+
+#### macOS:
+
+       generator completion bash > $(brew 
--prefix)/etc/bash_completion.d/generator
+
+You will need to start a new shell for this setup to take effect.
+
+
+```
+generator completion bash
+```
+
+### Options
+
+```
+  -h, --help              help for bash
+      --no-descriptions   disable completion descriptions
+```
+
+### Options inherited from parent commands
+
+```
+      --config string     config file (default is PROJECT/.env)
+      --modifyExistCode   allow generator modify exist code (default true)
+```
+
+### SEE ALSO
+
+* [generator completion](generator_completion.md)       - Generate the 
autocompletion script for the specified shell
+
+###### Auto generated by spf13/cobra on 24-Jun-2022
diff --git a/generator/docs/generator_completion_fish.md 
b/generator/docs/generator_completion_fish.md
new file mode 100644
index 00000000..d55e5b16
--- /dev/null
+++ b/generator/docs/generator_completion_fish.md
@@ -0,0 +1,42 @@
+## generator completion fish
+
+Generate the autocompletion script for fish
+
+### Synopsis
+
+Generate the autocompletion script for the fish shell.
+
+To load completions in your current shell session:
+
+       generator completion fish | source
+
+To load completions for every new session, execute once:
+
+       generator completion fish > ~/.config/fish/completions/generator.fish
+
+You will need to start a new shell for this setup to take effect.
+
+
+```
+generator completion fish [flags]
+```
+
+### Options
+
+```
+  -h, --help              help for fish
+      --no-descriptions   disable completion descriptions
+```
+
+### Options inherited from parent commands
+
+```
+      --config string     config file (default is PROJECT/.env)
+      --modifyExistCode   allow generator modify exist code (default true)
+```
+
+### SEE ALSO
+
+* [generator completion](generator_completion.md)       - Generate the 
autocompletion script for the specified shell
+
+###### Auto generated by spf13/cobra on 24-Jun-2022
diff --git a/generator/docs/generator_completion_powershell.md 
b/generator/docs/generator_completion_powershell.md
new file mode 100644
index 00000000..b3106331
--- /dev/null
+++ b/generator/docs/generator_completion_powershell.md
@@ -0,0 +1,39 @@
+## generator completion powershell
+
+Generate the autocompletion script for powershell
+
+### Synopsis
+
+Generate the autocompletion script for powershell.
+
+To load completions in your current shell session:
+
+       generator completion powershell | Out-String | Invoke-Expression
+
+To load completions for every new session, add the output of the above command
+to your powershell profile.
+
+
+```
+generator completion powershell [flags]
+```
+
+### Options
+
+```
+  -h, --help              help for powershell
+      --no-descriptions   disable completion descriptions
+```
+
+### Options inherited from parent commands
+
+```
+      --config string     config file (default is PROJECT/.env)
+      --modifyExistCode   allow generator modify exist code (default true)
+```
+
+### SEE ALSO
+
+* [generator completion](generator_completion.md)       - Generate the 
autocompletion script for the specified shell
+
+###### Auto generated by spf13/cobra on 24-Jun-2022
diff --git a/generator/docs/generator_completion_zsh.md 
b/generator/docs/generator_completion_zsh.md
new file mode 100644
index 00000000..82dbe67d
--- /dev/null
+++ b/generator/docs/generator_completion_zsh.md
@@ -0,0 +1,53 @@
+## generator completion zsh
+
+Generate the autocompletion script for zsh
+
+### Synopsis
+
+Generate the autocompletion script for the zsh shell.
+
+If shell completion is not already enabled in your environment you will need
+to enable it.  You can execute the following once:
+
+       echo "autoload -U compinit; compinit" >> ~/.zshrc
+
+To load completions in your current shell session:
+
+       source <(generator completion zsh); compdef _generator generator
+
+To load completions for every new session, execute once:
+
+#### Linux:
+
+       generator completion zsh > "${fpath[1]}/_generator"
+
+#### macOS:
+
+       generator completion zsh > $(brew 
--prefix)/share/zsh/site-functions/_generator
+
+You will need to start a new shell for this setup to take effect.
+
+
+```
+generator completion zsh [flags]
+```
+
+### Options
+
+```
+  -h, --help              help for zsh
+      --no-descriptions   disable completion descriptions
+```
+
+### Options inherited from parent commands
+
+```
+      --config string     config file (default is PROJECT/.env)
+      --modifyExistCode   allow generator modify exist code (default true)
+```
+
+### SEE ALSO
+
+* [generator completion](generator_completion.md)       - Generate the 
autocompletion script for the specified shell
+
+###### Auto generated by spf13/cobra on 24-Jun-2022
diff --git a/generator/docs/generator_create-collector.md 
b/generator/docs/generator_create-collector.md
new file mode 100644
index 00000000..6e90cf55
--- /dev/null
+++ b/generator/docs/generator_create-collector.md
@@ -0,0 +1,31 @@
+## generator create-collector
+
+Create a new collector
+
+### Synopsis
+
+Create a new collector
+Type in what the name of collector is, then generator will create a new 
collector in plugins/$plugin_name/tasks/$collector_name for you
+
+```
+generator create-collector [plugin_name] [collector_name] [flags]
+```
+
+### Options
+
+```
+  -h, --help   help for create-collector
+```
+
+### Options inherited from parent commands
+
+```
+      --config string     config file (default is PROJECT/.env)
+      --modifyExistCode   allow generator modify exist code (default true)
+```
+
+### SEE ALSO
+
+* [generator](generator.md)     - Apache DevLake Cli Tool -- Code Generator
+
+###### Auto generated by spf13/cobra on 24-Jun-2022
diff --git a/generator/docs/generator_create-extractor.md 
b/generator/docs/generator_create-extractor.md
new file mode 100644
index 00000000..fe8a906b
--- /dev/null
+++ b/generator/docs/generator_create-extractor.md
@@ -0,0 +1,31 @@
+## generator create-extractor
+
+Create a new extractor
+
+### Synopsis
+
+Create a new extractor
+Type in what the name of extractor is, then generator will create a new 
extractor in plugins/$plugin_name/tasks/$extractor_name for you
+
+```
+generator create-extractor [plugin_name] [extractor_name] [flags]
+```
+
+### Options
+
+```
+  -h, --help   help for create-extractor
+```
+
+### Options inherited from parent commands
+
+```
+      --config string     config file (default is PROJECT/.env)
+      --modifyExistCode   allow generator modify exist code (default true)
+```
+
+### SEE ALSO
+
+* [generator](generator.md)     - Apache DevLake Cli Tool -- Code Generator
+
+###### Auto generated by spf13/cobra on 24-Jun-2022
diff --git a/generator/docs/generator_create-plugin.md 
b/generator/docs/generator_create-plugin.md
new file mode 100644
index 00000000..8a83b062
--- /dev/null
+++ b/generator/docs/generator_create-plugin.md
@@ -0,0 +1,31 @@
+## generator create-plugin
+
+Create a new plugin
+
+### Synopsis
+
+Create a new plugin
+Type in what the name of plugin is, then generator will create a new plugin in 
plugins/$plugin_name for you
+
+```
+generator create-plugin [plugin_name] [flags]
+```
+
+### Options
+
+```
+  -h, --help   help for create-plugin
+```
+
+### Options inherited from parent commands
+
+```
+      --config string     config file (default is PROJECT/.env)
+      --modifyExistCode   allow generator modify exist code (default true)
+```
+
+### SEE ALSO
+
+* [generator](generator.md)     - Apache DevLake Cli Tool -- Code Generator
+
+###### Auto generated by spf13/cobra on 24-Jun-2022
diff --git a/generator/docs/generator_generator-doc.md 
b/generator/docs/generator_generator-doc.md
new file mode 100644
index 00000000..02689b43
--- /dev/null
+++ b/generator/docs/generator_generator-doc.md
@@ -0,0 +1,26 @@
+## generator generator-doc
+
+generate document for generator
+
+```
+generator generator-doc [flags]
+```
+
+### Options
+
+```
+  -h, --help   help for generator-doc
+```
+
+### Options inherited from parent commands
+
+```
+      --config string     config file (default is PROJECT/.env)
+      --modifyExistCode   allow generator modify exist code (default true)
+```
+
+### SEE ALSO
+
+* [generator](generator.md)     - Apache DevLake Cli Tool -- Code Generator
+
+###### Auto generated by spf13/cobra on 24-Jun-2022
diff --git a/generator/template/plugin/tasks/task_data.go-template 
b/generator/main.go
similarity index 58%
copy from generator/template/plugin/tasks/task_data.go-template
copy to generator/main.go
index 8a58ae4c..0515a4be 100644
--- a/generator/template/plugin/tasks/task_data.go-template
+++ b/generator/main.go
@@ -15,24 +15,15 @@ See the License for the specific language governing 
permissions and
 limitations under the License.
 */
 
-package tasks
+package main
 
 import (
-       "github.com/apache/incubator-devlake/plugins/helper"
+       "github.com/apache/incubator-devlake/generator/cmd"
+       "os"
 )
 
-type {{ .PluginName }}ApiParams struct {
-}
-
-type {{ .PluginName }}Options struct {
-       // TODO add some custom options here if necessary
-       // options means some custom params required by plugin running.
-       // Such As How many rows do your want
-       // You can use it in sub tasks and you need pass it in main.go and 
pipelines.
-       Tasks []string `json:"tasks,omitempty"`
-}
-
-type {{ .PluginName }}TaskData struct {
-       Options   *{{ .PluginName }}Options
-       // ApiClient *helper.ApiAsyncClient
+func main() {
+       if err := cmd.Execute(); err != nil {
+               os.Exit(1)
+       }
 }
diff --git a/generator/template/plugin/plugin_main.go-template 
b/generator/template/plugin/plugin_main.go-template
index f6ab681a..a2ec81db 100644
--- a/generator/template/plugin/plugin_main.go-template
+++ b/generator/template/plugin/plugin_main.go-template
@@ -51,8 +51,8 @@ func (plugin {{ .PluginName }}) Init(config *viper.Viper, 
logger core.Logger, db
 }
 
 func (plugin {{ .PluginName }}) SubTaskMetas() []core.SubTaskMeta {
+       // TODO add your sub task here
        return []core.SubTaskMeta{
-               // TODO add your sub task here
        }
 }
 
@@ -63,16 +63,8 @@ func (plugin {{ .PluginName }}) PrepareTaskData(taskCtx 
core.TaskContext, option
                return nil, err
        }
 
-       // apiClient, err := tasks.New{{ .PluginName }}ApiClient(taskCtx)
-       // if err != nil {
-       //      return nil, err
-       // }
-
        return &tasks.{{ .PluginName }}TaskData{
                Options: &op,
-               // TODO you can init and stash some handler to deal data at all 
subtasks, Such as apiClient as below.
-               // NOTES: In task_data.go/TaskData should declare `ApiClient`
-        // ApiClient: apiClient,
        }, nil
 }
 
diff --git a/generator/template/plugin/plugin_main.go-template 
b/generator/template/plugin/plugin_main_with_api_client.go-template
similarity index 89%
copy from generator/template/plugin/plugin_main.go-template
copy to generator/template/plugin/plugin_main_with_api_client.go-template
index f6ab681a..1ab8333d 100644
--- a/generator/template/plugin/plugin_main.go-template
+++ b/generator/template/plugin/plugin_main_with_api_client.go-template
@@ -51,8 +51,8 @@ func (plugin {{ .PluginName }}) Init(config *viper.Viper, 
logger core.Logger, db
 }
 
 func (plugin {{ .PluginName }}) SubTaskMetas() []core.SubTaskMeta {
+       // TODO add your sub task here
        return []core.SubTaskMeta{
-               // TODO add your sub task here
        }
 }
 
@@ -63,16 +63,14 @@ func (plugin {{ .PluginName }}) PrepareTaskData(taskCtx 
core.TaskContext, option
                return nil, err
        }
 
-       // apiClient, err := tasks.New{{ .PluginName }}ApiClient(taskCtx)
-       // if err != nil {
-       //      return nil, err
-       // }
+       apiClient, err := tasks.New{{ .PluginName }}ApiClient(taskCtx)
+       if err != nil {
+               return nil, err
+       }
 
        return &tasks.{{ .PluginName }}TaskData{
                Options: &op,
-               // TODO you can init and stash some handler to deal data at all 
subtasks, Such as apiClient as below.
-               // NOTES: In task_data.go/TaskData should declare `ApiClient`
-        // ApiClient: apiClient,
+        ApiClient: apiClient,
        }, nil
 }
 
diff --git a/generator/template/plugin/tasks/api_client.go-template 
b/generator/template/plugin/tasks/api_client.go-template
index b4387c14..a79e1312 100644
--- a/generator/template/plugin/tasks/api_client.go-template
+++ b/generator/template/plugin/tasks/api_client.go-template
@@ -26,7 +26,7 @@ import (
 )
 
 // TODO add what host would want to requist
-const ENDPOINT = "https://open.example.cn/api/v1";
+const ENDPOINT = "{{ .Endpoint }}"
 
 func New{{ .PluginName }}ApiClient(taskCtx core.TaskContext) 
(*helper.ApiAsyncClient, error) {
        // load and process configuration
@@ -46,9 +46,11 @@ func New{{ .PluginName }}ApiClient(taskCtx core.TaskContext) 
(*helper.ApiAsyncCl
                return nil, err
        }
        // set token
-       apiClient.SetHeaders(map[string]string{
-               "Authorization": fmt.Sprintf("Bearer %v", token),
-       })
+       if token != "" {
+               apiClient.SetHeaders(map[string]string{
+                       "Authorization": fmt.Sprintf("Bearer %v", token),
+               })
+       }
 
        // TODO add some check after request if necessary
        // apiClient.SetAfterFunction(func(res *http.Response) error {
diff --git a/generator/template/plugin/tasks/extractor.go-template 
b/generator/template/plugin/tasks/extractor.go-template
index eceb661c..7a41a59d 100644
--- a/generator/template/plugin/tasks/extractor.go-template
+++ b/generator/template/plugin/tasks/extractor.go-template
@@ -18,7 +18,6 @@ limitations under the License.
 package tasks
 
 import (
-       "encoding/json"
        "github.com/apache/incubator-devlake/plugins/core"
        "github.com/apache/incubator-devlake/plugins/helper"
 )
diff --git a/generator/template/plugin/tasks/task_data.go-template 
b/generator/template/plugin/tasks/task_data.go-template
index 8a58ae4c..50fdbd05 100644
--- a/generator/template/plugin/tasks/task_data.go-template
+++ b/generator/template/plugin/tasks/task_data.go-template
@@ -17,10 +17,6 @@ limitations under the License.
 
 package tasks
 
-import (
-       "github.com/apache/incubator-devlake/plugins/helper"
-)
-
 type {{ .PluginName }}ApiParams struct {
 }
 
@@ -29,10 +25,8 @@ type {{ .PluginName }}Options struct {
        // options means some custom params required by plugin running.
        // Such As How many rows do your want
        // You can use it in sub tasks and you need pass it in main.go and 
pipelines.
-       Tasks []string `json:"tasks,omitempty"`
 }
 
 type {{ .PluginName }}TaskData struct {
        Options   *{{ .PluginName }}Options
-       // ApiClient *helper.ApiAsyncClient
 }
diff --git a/generator/template/plugin/tasks/task_data.go-template 
b/generator/template/plugin/tasks/task_data_with_api_client.go-template
similarity index 94%
copy from generator/template/plugin/tasks/task_data.go-template
copy to generator/template/plugin/tasks/task_data_with_api_client.go-template
index 8a58ae4c..5dec7665 100644
--- a/generator/template/plugin/tasks/task_data.go-template
+++ b/generator/template/plugin/tasks/task_data_with_api_client.go-template
@@ -29,10 +29,9 @@ type {{ .PluginName }}Options struct {
        // options means some custom params required by plugin running.
        // Such As How many rows do your want
        // You can use it in sub tasks and you need pass it in main.go and 
pipelines.
-       Tasks []string `json:"tasks,omitempty"`
 }
 
 type {{ .PluginName }}TaskData struct {
        Options   *{{ .PluginName }}Options
-       // ApiClient *helper.ApiAsyncClient
+       ApiClient *helper.ApiAsyncClient
 }
diff --git a/generator/util/template.go b/generator/util/template.go
new file mode 100644
index 00000000..7d4c83bf
--- /dev/null
+++ b/generator/util/template.go
@@ -0,0 +1,102 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package util
+
+import (
+       "fmt"
+       "github.com/spf13/cobra"
+       "github.com/stoewer/go-strcase"
+       "io/ioutil"
+       "os"
+       "path/filepath"
+       "regexp"
+       "strings"
+)
+
+// GenerateAllFormatVar fill all format var into values
+func GenerateAllFormatVar(values map[string]string, baseVarName, baseValue 
string) {
+       values[strcase.LowerCamelCase(baseVarName)] = 
strcase.LowerCamelCase(baseValue)
+       values[strcase.UpperCamelCase(baseVarName)] = 
strcase.UpperCamelCase(baseValue)
+       values[strcase.SnakeCase(baseVarName)] = strcase.SnakeCase(baseValue)
+       values[strcase.UpperSnakeCase(baseVarName)] = 
strcase.UpperSnakeCase(baseValue)
+       values[strcase.KebabCase(baseVarName)] = strcase.KebabCase(baseValue)
+       values[strcase.UpperKebabCase(baseVarName)] = 
strcase.UpperKebabCase(baseValue)
+}
+
+// ReadTemplate read a file to string
+func ReadTemplate(templateFile string) string {
+       f, err := ioutil.ReadFile(templateFile)
+       cobra.CheckErr(err)
+       return string(f)
+}
+
+// WriteTemplates write some strings to files
+func WriteTemplates(path string, templates map[string]string) {
+       err := os.MkdirAll(path, 0777)
+       cobra.CheckErr(err)
+       for name, template := range templates {
+               err := os.MkdirAll(filepath.Dir(filepath.Join(path, name)), 
0777)
+               cobra.CheckErr(err)
+               err = ioutil.WriteFile(filepath.Join(path, name), 
[]byte(template), 0777)
+               cobra.CheckErr(err)
+               println(filepath.Join(path, name), ` generated`)
+       }
+}
+
+// ReplaceVarInFile replacte var into file without reading
+func ReplaceVarInFile(filename string, reg *regexp.Regexp, new string) {
+       f, err := ioutil.ReadFile(filename)
+       cobra.CheckErr(err)
+       f = reg.ReplaceAll(f, []byte(new))
+
+       err = ioutil.WriteFile(filename, f, 0777)
+       cobra.CheckErr(err)
+       println(filename, ` updated`)
+}
+
+// DetectExistVars filter the used vars in templates
+func DetectExistVars(templates map[string]string, values map[string]string) 
(newValues map[string]string) {
+       newValues = map[string]string{}
+       for varName, value := range values {
+               for _, template := range templates {
+                       if strings.Contains(template, varName) {
+                               newValues[varName] = value
+                               break
+                       }
+               }
+       }
+       return newValues
+}
+
+// ReplaceVarInTemplates replace var with templates into templates
+func ReplaceVarInTemplates(templates map[string]string, valueMap 
map[string]string) {
+       for i, template := range templates {
+               templates[i] = ReplaceVars(template, valueMap)
+       }
+}
+
+func ReplaceVars(s string, valueMap map[string]string) string {
+       for varName, value := range valueMap {
+               s = ReplaceVar(s, varName, value)
+       }
+       return s
+}
+
+func ReplaceVar(s, varName, value string) string {
+       return strings.ReplaceAll(s, fmt.Sprintf(`{{ .%s }}`, varName), value)
+}
diff --git a/go.mod b/go.mod
index 00d0b4ce..38ce2129 100644
--- a/go.mod
+++ b/go.mod
@@ -15,7 +15,7 @@ require (
        github.com/robfig/cron/v3 v3.0.0
        github.com/sirupsen/logrus v1.8.1
        github.com/spf13/afero v1.6.0
-       github.com/spf13/cobra v1.2.1
+       github.com/spf13/cobra v1.5.0
        github.com/spf13/viper v1.8.1
        github.com/stretchr/testify v1.7.0
        github.com/swaggo/gin-swagger v1.4.3
@@ -37,6 +37,8 @@ require (
        github.com/PuerkitoBio/purell v1.1.1 // indirect
        github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // 
indirect
        github.com/acomagu/bufpipe v1.0.3 // indirect
+       github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // 
indirect
+       github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
        github.com/davecgh/go-spew v1.1.1 // indirect
        github.com/denisenkom/go-mssqldb v0.10.0 // indirect
        github.com/emirpasic/gods v1.12.0 // indirect
@@ -79,6 +81,7 @@ require (
        github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // 
indirect
        github.com/leodido/go-urn v1.2.1 // indirect
        github.com/mailru/easyjson v0.7.6 // indirect
+       github.com/manifoldco/promptui v0.9.0 // indirect
        github.com/mattn/go-colorable v0.1.6 // indirect
        github.com/mattn/go-isatty v0.0.13 // indirect
        github.com/mattn/go-sqlite3 v1.14.6 // indirect
@@ -92,10 +95,12 @@ require (
        github.com/pelletier/go-toml v1.9.3 // indirect
        github.com/pmezard/go-difflib v1.0.0 // indirect
        github.com/robfig/cron v1.2.0 // indirect
+       github.com/russross/blackfriday/v2 v2.1.0 // indirect
        github.com/sergi/go-diff v1.1.0 // indirect
        github.com/spf13/cast v1.4.1 // indirect
        github.com/spf13/jwalterweatherman v1.1.0 // indirect
        github.com/spf13/pflag v1.0.6-0.20200504143853-81378bbcd8a1 // indirect
+       github.com/stoewer/go-strcase v1.2.0 // indirect
        github.com/stretchr/objx v0.3.0 // indirect
        github.com/subosito/gotenv v1.2.0 // indirect
        github.com/ugorji/go/codec v1.2.6 // indirect
diff --git a/go.sum b/go.sum
index b9e4de6d..217f9ee8 100644
--- a/go.sum
+++ b/go.sum
@@ -71,6 +71,7 @@ github.com/bketelsen/crypt v0.0.4/go.mod 
h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqO
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod 
h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod 
h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/chzyer/logex v1.1.10/go.mod 
h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e 
h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod 
h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod 
h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
 github.com/client9/misspell v0.3.4/go.mod 
h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
@@ -89,6 +90,8 @@ github.com/coreos/go-systemd 
v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7
 github.com/coreos/go-systemd/v22 v22.3.2/go.mod 
h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod 
h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod 
h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/cpuguy83/go-md2man/v2 v2.0.2 
h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
+github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod 
h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/creack/pty v1.1.7/go.mod 
h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
 github.com/creack/pty v1.1.9/go.mod 
h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -408,6 +411,8 @@ github.com/mailru/easyjson 
v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN
 github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod 
h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 github.com/mailru/easyjson v0.7.6 
h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
 github.com/mailru/easyjson v0.7.6/go.mod 
h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/manifoldco/promptui v0.9.0 
h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
+github.com/manifoldco/promptui v0.9.0/go.mod 
h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
 github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
 github.com/matryer/is v1.2.0/go.mod 
h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
 github.com/mattn/go-colorable v0.0.9/go.mod 
h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
@@ -493,6 +498,8 @@ github.com/rs/xid v1.2.1/go.mod 
h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
 github.com/rs/zerolog v1.13.0/go.mod 
h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
 github.com/rs/zerolog v1.15.0/go.mod 
h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod 
h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/russross/blackfriday/v2 v2.1.0 
h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod 
h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod 
h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/satori/go.uuid v1.2.0/go.mod 
h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/sclevine/agouti v3.0.0+incompatible/go.mod 
h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
@@ -520,6 +527,8 @@ github.com/spf13/cast v1.4.1 
h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
 github.com/spf13/cast v1.4.1/go.mod 
h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw=
 github.com/spf13/cobra v1.2.1/go.mod 
h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
+github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
+github.com/spf13/cobra v1.5.0/go.mod 
h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
 github.com/spf13/jwalterweatherman v1.1.0 
h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
 github.com/spf13/jwalterweatherman v1.1.0/go.mod 
h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
 github.com/spf13/pflag v1.0.5/go.mod 
h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
@@ -527,6 +536,8 @@ github.com/spf13/pflag v1.0.6-0.20200504143853-81378bbcd8a1 
h1:zrNp7OPtn2fjeNHI9
 github.com/spf13/pflag v1.0.6-0.20200504143853-81378bbcd8a1/go.mod 
h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44=
 github.com/spf13/viper v1.8.1/go.mod 
h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
+github.com/stoewer/go-strcase v1.2.0 
h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU=
+github.com/stoewer/go-strcase v1.2.0/go.mod 
h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
 github.com/stretchr/objx v0.1.0/go.mod 
h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod 
h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.2.0/go.mod 
h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
@@ -724,6 +735,7 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod 
h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod 
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod 
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod 
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod 
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod 
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod 
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

Reply via email to