Go 每日一庫之 go-ini

簡介

ini 是 Windows 上經常使用的配置文件格式。MySQL 的 Windows 版就是使用 ini 格式存儲配置的。 go-ini是 Go 語言中用於操做 ini 文件的第三方庫。mysql

本文介紹go-ini庫的使用。git

快速使用

go-ini 是第三方庫,使用前須要安裝:github

$ go get gopkg.in/ini.v1
複製代碼

也可使用 GitHub 上的倉庫:golang

$ go get github.com/go-ini/ini
複製代碼

首先,建立一個my.ini配置文件:web

app_name = awesome web

# possible values: DEBUG, INFO, WARNING, ERROR, FATAL
log_level = DEBUG

[mysql]
ip = 127.0.0.1
port = 3306
user = dj
password = 123456
database = awesome

[redis]
ip = 127.0.0.1
port = 6381
複製代碼

使用 go-ini 庫讀取:redis

package main

import (
  "fmt"
  "log"

  "gopkg.in/ini.v1"
)

func main() {
  cfg, err := ini.Load("my.ini")
  if err != nil {
    log.Fatal("Fail to read file: ", err)
  }

  fmt.Println("App Name:", cfg.Section("").Key("app_name").String())
  fmt.Println("Log Level:", cfg.Section("").Key("log_level").String())

  fmt.Println("MySQL IP:", cfg.Section("mysql").Key("ip").String())
  mysqlPort, err := cfg.Section("mysql").Key("port").Int()
  if err != nil {
    log.Fatal(err)
  }
  fmt.Println("MySQL Port:", mysqlPort)
  fmt.Println("MySQL User:", cfg.Section("mysql").Key("user").String())
  fmt.Println("MySQL Password:", cfg.Section("mysql").Key("password").String())
  fmt.Println("MySQL Database:", cfg.Section("mysql").Key("database").String())

  fmt.Println("Redis IP:", cfg.Section("redis").Key("ip").String())
  redisPort, err := cfg.Section("redis").Key("port").Int()
  if err != nil {
    log.Fatal(err)
  }
  fmt.Println("Redis Port:", redisPort)
}
複製代碼

在 ini 文件中,每一個鍵值對佔用一行,中間使用=隔開。以#開頭的內容爲註釋。ini 文件是以分區(section)組織的。 分區以[name]開始,在下一個分區前結束。全部分區前的內容屬於默認分區,如my.ini文件中的app_namelog_levelsql

使用go-ini讀取配置文件的步驟以下:json

  • 首先調用ini.Load加載文件,獲得配置對象cfg
  • 而後以分區名調用配置對象的Section方法獲得對應的分區對象section,默認分區的名字爲"",也可使用ini.DefaultSection
  • 以鍵名調用分區對象的Key方法獲得對應的配置項key對象;
  • 因爲文件中讀取出來的都是字符串,key對象需根據類型調用對應的方法返回具體類型的值使用,如上面的StringMustInt方法。

運行如下程序,獲得輸出:bash

App Name: awesome web
Log Level: DEBUG
MySQL IP: 127.0.0.1
MySQL Port: 3306
MySQL User: dj
MySQL Password: 123456
MySQL Database: awesome
Redis IP: 127.0.0.1
Redis Port: 6381
複製代碼

配置文件中存儲的都是字符串,因此類型爲字符串的配置項不會出現類型轉換失敗的,故String()方法只返回一個值。 但若是類型爲Int/Uint/Float64這些時,轉換可能失敗。因此Int()/Uint()/Float64()返回一個值和一個錯誤。微信

要留意這種不一致!若是咱們將配置中 redis 端口改爲非法的數字 x6381,那麼運行程序將報錯:

2020/01/14 22:43:13 strconv.ParseInt: parsing "x6381": invalid syntax
複製代碼

Must*便捷方法

若是每次取值都須要進行錯誤判斷,那麼代碼寫起來會很是繁瑣。爲此,go-ini也提供對應的MustType(Type 爲Init/Uint/Float64等)方法,這個方法只返回一個值。 同時它接受可變參數,若是類型沒法轉換,取參數中第一個值返回,而且該參數設置爲這個配置的值,下次調用返回這個值:

package main

import (
  "fmt"
  "log"

  "gopkg.in/ini.v1"
)

func main() {
  cfg, err := ini.Load("my.ini")
  if err != nil {
    log.Fatal("Fail to read file: ", err)
  }

  redisPort, err := cfg.Section("redis").Key("port").Int()
  if err != nil {
    fmt.Println("before must, get redis port error:", err)
  } else {
    fmt.Println("before must, get redis port:", redisPort)
  }

  fmt.Println("redis Port:", cfg.Section("redis").Key("port").MustInt(6381))

  redisPort, err = cfg.Section("redis").Key("port").Int()
  if err != nil {
    fmt.Println("after must, get redis port error:", err)
  } else {
    fmt.Println("after must, get redis port:", redisPort)
  }
}
複製代碼

配置文件仍是 redis 端口爲非數字 x6381 時的狀態,運行程序:

before must, get redis port error: strconv.ParseInt: parsing "x6381": invalid syntax
redis Port: 6381
after must, get redis port: 6381
複製代碼

咱們看到第一次調用Int返回錯誤,以 6381 爲參數調用MustInt以後,再次調用Int,成功返回 6381。MustInt源碼也比較簡單:

// gopkg.in/ini.v1/key.go
func (k *Key) MustInt(defaultVal ...int) int {
  val, err := k.Int()
  if len(defaultVal) > 0 && err != nil {
    k.value = strconv.FormatInt(int64(defaultVal[0]), 10)
    return defaultVal[0]
  }
  return val
}
複製代碼

分區操做

獲取信息

在加載配置以後,能夠經過Sections方法獲取全部分區,SectionStrings()方法獲取全部分區名。

sections := cfg.Sections()
names := cfg.SectionStrings()

fmt.Println("sections: ", sections)
fmt.Println("names: ", names)
複製代碼

運行輸出 3 個分區:

[DEFAULT mysql redis]
複製代碼

調用Section(name)獲取名爲name的分區,若是該分區不存在,則自動建立一個分區返回:

newSection := cfg.Section("new")

fmt.Println("new section: ", newSection)
fmt.Println("names: ", cfg.SectionStrings())
複製代碼

建立以後調用SectionStrings方法,新分區也會返回:

names:  [DEFAULT mysql redis new]
複製代碼

也能夠手動建立一個新分區,若是分區已存在,則返回錯誤:

err := cfg.NewSection("new")
複製代碼

父子分區

在配置文件中,可使用佔位符%(name)s表示用以前已定義的鍵name的值來替換,這裏的s表示值爲字符串類型:

NAME = ini
VERSION = v1
IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s

[package]
CLONE_URL = https://%(IMPORT_PATH)s

[package.sub]
複製代碼

上面在默認分區中設置IMPORT_PATH的值時,使用了前面定義的NAMEVERSION。 在package分區中設置CLONE_URL的值時,使用了默認分區中定義的IMPORT_PATH

咱們還能夠在分區名中使用.表示兩個或多個分區之間的父子關係,例如package.sub的父分區爲packagepackage的父分區爲默認分區。 若是某個鍵在子分區中不存在,則會在它的父分區中再次查找,直到沒有父分區爲止:

cfg, err := ini.Load("parent_child.ini")
if err != nil {
  fmt.Println("Fail to read file: ", err)
  return
}

fmt.Println("Clone url from package.sub:", cfg.Section("package.sub").Key("CLONE_URL").String())
複製代碼

運行程序輸出:

Clone url from package.sub: https://gopkg.in/ini.v1
複製代碼

子分區中package.sub中沒有鍵CLONE_URL,返回了父分區package中的值。

保存配置

有時候,咱們須要將生成的配置寫到文件中。例如在寫工具的時候。保存有兩種類型的接口,一種直接保存到文件,另外一種寫入到io.Writer中:

err = cfg.SaveTo("my.ini")
err = cfg.SaveToIndent("my.ini", "\t")

cfg.WriteTo(writer)
cfg.WriteToIndent(writer, "\t")
複製代碼

下面咱們經過程序生成前面使用的配置文件my.ini並保存:

package main

import (
  "fmt"
  "os"

  "gopkg.in/ini.v1"
)

func main() {
  cfg := ini.Empty()

  defaultSection := cfg.Section("")
  defaultSection.NewKey("app_name", "awesome web")
  defaultSection.NewKey("log_level", "DEBUG")

  mysqlSection, err := cfg.NewSection("mysql")
  if err != nil {
    fmt.Println("new mysql section failed:", err)
    return
  }
  mysqlSection.NewKey("ip", "127.0.0.1")
  mysqlSection.NewKey("port", "3306")
  mysqlSection.NewKey("user", "root")
  mysqlSection.NewKey("password", "123456")
  mysqlSection.NewKey("database", "awesome")

  redisSection, err := cfg.NewSection("redis")
  if err != nil {
    fmt.Println("new redis section failed:", err)
    return
  }
  redisSection.NewKey("ip", "127.0.0.1")
  redisSection.NewKey("port", "6381")

  err = cfg.SaveTo("my.ini")
  if err != nil {
    fmt.Println("SaveTo failed: ", err)
  }

  err = cfg.SaveToIndent("my-pretty.ini", "\t")
  if err != nil {
    fmt.Println("SaveToIndent failed: ", err)
  }

  cfg.WriteTo(os.Stdout)
  fmt.Println()
  cfg.WriteToIndent(os.Stdout, "\t")
}
複製代碼

運行程序,生成兩個文件my.inimy-pretty.ini,同時控制檯輸出文件內容。

my.ini

app_name  = awesome web
log_level = DEBUG

[mysql]
ip       = 127.0.0.1
port     = 3306
user     = root
password = 123456
database = awesome

[redis]
ip   = 127.0.0.1
port = 6381
複製代碼

my-pretty.ini

app_name  = awesome web
log_level = DEBUG

[mysql]
	ip       = 127.0.0.1
	port     = 3306
	user     = root
	password = 123456
	database = awesome

[redis]
	ip   = 127.0.0.1
	port = 6381
複製代碼

*Indent方法會對子分區下的鍵增長縮進,看起來美觀一點。

分區與結構體字段映射

定義結構變量,加載完配置文件後,調用MapTo將配置項賦值到結構變量的對應字段中。

package main

import (
  "fmt"

  "gopkg.in/ini.v1"
)

type Config struct {
  AppName   string `ini:"app_name"`
  LogLevel  string `ini:"log_level"`

  MySQL     MySQLConfig `ini:"mysql"`
  Redis     RedisConfig `ini:"redis"`
}

type MySQLConfig struct {
  IP        string `ini:"ip"`
  Port      int `ini:"port"`
  User      string `ini:"user"`
  Password  string `ini:"password"`
  Database  string `ini:"database"`
}

type RedisConfig struct {
  IP      string `ini:"ip"`
  Port    int `ini:"port"`
}

func main() {
  cfg, err := ini.Load("my.ini")
  if err != nil {
    fmt.Println("load my.ini failed: ", err)
  }

  c := Config{}
  cfg.MapTo(&c)

  fmt.Println(c)
}
複製代碼

MapTo內部使用了反射,因此結構體字段必須都是導出的。若是鍵名與字段名不相同,那麼須要在結構標籤中指定對應的鍵名。 這一點與 Go 標準庫encoding/jsonencoding/xml不一樣。標準庫json/xml解析時能夠將鍵名app_name對應到字段名AppName。 或許這是go-ini庫能夠優化的點?

先加載,再映射有點繁瑣,直接使用ini.MapTo將兩步合併:

err = ini.MapTo(&c, "my.ini")
複製代碼

也能夠只映射一個分區:

mysqlCfg := MySQLConfig{}
err = cfg.Section("mysql").MapTo(&mysqlCfg)
複製代碼

還能夠經過結構體生成配置:

cfg := ini.Empty()

c := Config {
  AppName: 	"awesome web",
  LogLevel: 	"DEBUG",
  MySQL: MySQLConfig {
    IP: 	"127.0.0.1",
    Port:	3306,
    User:	"root",
    Password:"123456",
    Database:"awesome",
  },
  Redis: RedisConfig {
    IP:		"127.0.0.1",
    Port:	6381,
  },
}

err := ini.ReflectFrom(cfg, &c)
if err != nil {
  fmt.Println("ReflectFrom failed: ", err)
  return
}

err = cfg.SaveTo("my-copy.ini")
if err != nil {
  fmt.Println("SaveTo failed: ", err)
  return
}
複製代碼

總結

本文介紹了go-ini庫的基本用法和一些有趣的特性。示例代碼已上傳GitHub。 其實go-ini還有不少高級特性。官方文檔很是詳細,推薦去看,並且有中文喲~ 做者無聞,相信作 Go 開發的都不陌生。

參考

  1. go-ini GitHub 倉庫
  2. go-ini 官方文檔

個人博客

歡迎關注個人微信公衆號【GoUpUp】,共同窗習,一塊兒進步~

本文由博客一文多發平臺 OpenWrite 發佈!

相關文章
相關標籤/搜索