查看原文
其他

构建Go命令行程序工具链

李德 GoCN 2022-09-09

偷懒的故事

今天的推荐需要从一个偷懒的故事说起···

话说不久前,需要输出一个按发布时间排序的酷Go推荐的历史文章列表,类似于这样:

于是乎,想着从GoCN上一篇一篇copy文章的标题吧,5篇下来,手眼已经不协调了,此时此刻才想起自己貌似是个码农,此情此景那必须coding一段,让代码来输出这个列表。

思路很简单,看一下GoCN文章列表的API,再看下鉴权方式(看header是通过cookie),然后就可以coding了,通过API拉取文章数据,提取需要的字段再组装成MD输出就完事了!

当准备一个main.go搞定的时候,本着对coding的敬畏,简单的思考了一下程序的可读性、拓展性,决定还是构建一个比较完整的命令行程序,如下:

主角登场

那么要实现上图的命令行提示,程序参数解析等,不借助于第三方包当然也能实现。但,前人种好的大树为哈不乘凉呢,在业界对于命令行工具的开发已经有比较成熟的实现,下面清楚本文的主角:

  • cobra:应用命令行框架
  • pflag:命令行参数解析
  • viper:配置文件解析
下面结合实际项目来看如何使用这三位大哥来构建Go命令行程序。

先简单看下项目结构:

.
├── cmd
│   ├── pull.go
│   └── root.go
├── config.yaml
├── file
│   └── cool-go.md
├── go.mod
├── go.sum
├── gocn-cli
└── main.go

然后引入上面所推荐的三个库:

go get -u github.com/spf13/cobra@latest

go get -u github.com/spf13/viper

go get -u github.com/spf13/pflag

Coding

package cmd

import (
    "fmt"
    "github.com/spf13/cobra"
    "github.com/spf13/viper"
    "os"
)

var (
    cfgFile string
)

func init() {
    cobra.OnInitialize(initConfig)
    
    // 解析配置文件参数
    rootCmd.PersistentFlags().StringVar(&cfgFile, "config""""config file (default is ./config.yaml)")
}

// initConfig viper加载配置文件
func initConfig() {
    if cfgFile != "" {
        viper.SetConfigFile(cfgFile)
    } else {
        viper.AddConfigPath(".")
        viper.SetConfigName("config")
    }
    
    if err := viper.ReadInConfig(); err != nil {
        fmt.Println("Can't read config file:", err)
        os.Exit(1)
    }
}

// 创建全局rootCmd
var rootCmd = &cobra.Command{
    Use:   "gocn-cli",
    Short: "gocn-cli is a command line tool for gocn.com",
    Long:  "gocn-cli is a command line tool for gocn.com",
    Run: func(cmd *cobra.Command, args []string) {
        
    },
}

// Execute 执行命令 main调用
func Execute() {
    if err := rootCmd.Execute(); err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
}

package cmd

import (
 "github.com/spf13/cobra"
 "github.com/spf13/viper"
 "os"
)

var (
 moduleName string
)

func init() {
    // 给rootCmd增加新的命令pullCmd
 rootCmd.AddCommand(pullCmd)

 pullCmd.Flags().StringVarP(&moduleName, "module""m""""module name")
}

var pullCmd = &cobra.Command{
 Use:   "pull gocn data",
 Short: "pull gocn data",
 Run: func(cmd *cobra.Command, args []string) {
  pullHandler()
 },
}

// pullHandler pullCmd 对应的处理函数
func pullHandler() {
 if moduleName == "" {
  fmt.Println("module name is required")
  os.Exit(1)
 }

 switch moduleName {
 case "cool-go":
  pullCoolGo()

 default:
  fmt.Println("module name is invalid")
  os.Exit(1)
 }
}

func pullCoolGo() {
 // TODO Write Your Code
}

结语

以上简单介绍了如何使用cobra、viper、pflag,当然它们的用法还有很多,这里只做个简单的入门。
可见,通过简单的代就能构建出比较好的命令行程序。

参考

  • https://github.com/spf13/cobra
  • https://github.com/spf13/viper
  • https://github.com/spf13/pflag


往期推荐



等等,怎么使用 SetMemoryLimit?


第八届 GopherChina 大会蓄势待发!


这不会又是一个Go的BUG吧?

想要了解Go更多内容,欢迎扫描下方👇 关注 公众号,回复关键词 [实战群]  ,就有机会进群和我们进行交流

分享、在看与点赞,至少我要拥有一个叭~

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存