Introduction to the

Ini is a common configuration file format on Windows. MySQL for Windows uses INI format to store configurations. Go-ini is a third-party library in the GO language for manipulating INI files.

This article introduces the use of the GO-INI library.

Quick to use

Go-ini is a third-party library that needs to be installed before use:

$ go get gopkg.in/ini.v1
Copy the code

You can also use the repository on GitHub:

$ go get github.com/go-ini/ini
Copy the code

First, create a my.ini configuration file:

app_name = awesome web

# possible values: DEBUG, INFO, WARNING, ERROR, FATALLog_level = DEBUG [mysql] IP = 127.0.0.1 port = 3306 user = DJ password = 123456 database = awesome [redis] IP = 127.0.0.1 port = 6381Copy the code

Use the Go-INI library to read:

package main

import (
  "fmt"
  "log"

  "gopkg.in/ini.v1"
)

func main(a) {
  cfg, err := ini.Load("my.ini")
  iferr ! =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()
  iferr ! =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()
  iferr ! =nil {
    log.Fatal(err)
  }
  fmt.Println("Redis Port:", redisPort)
}
Copy the code

In the INI file, each key-value pair occupies a line separated by =. Content that begins with # is a comment. Ini files are organized as sections. A partition starts with [name] and ends before the next partition. All the content before the partition belongs to the default partition, such as app_name and log_level in the my.ini file.

To read the configuration file using go-INI, perform the following steps:

  • First callini.LoadLoad the file and get the configuration objectcfg;
  • The configuration object’s is then called with the partition nameSectionMethod to obtain the corresponding partition objectsection, the default partition name is"", can also be usedini.DefaultSection;
  • Of a partition object called with a key nameKeyMethod to obtain the corresponding configuration itemkeyObject;
  • And since everything that comes out of this file is a string,keyObject is used by calling the corresponding method based on the type, as shown aboveString,MustIntMethods.

Run the following program to get output:

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
Copy the code

Configuration files store strings, so configuration items of type String do not fail casting, so String() returns only one value. However, if the type is Int/Uint/Float64, the conversion may fail. So Int()/Uint()/Float64() returns a value and an error.

Be aware of this inconsistency! If we change the redis port in the configuration to the invalid number x6381, the program will report an error:

2020/01/14 22:43:13 strconv.ParseInt: parsing "x6381": invalid syntax
Copy the code

Must*Convenient method

If you had to make a mistake every time you evaluated it, the code would be tedious to write. For this purpose, go-ini also provides the corresponding MustType (Type Init/Uint/Float64, etc.) method, which returns only one value. It also accepts mutable arguments. If the type cannot be converted, it returns the first value of the argument, and the parameter is set to the configured value. The next call returns this value:

package main

import (
  "fmt"
  "log"

  "gopkg.in/ini.v1"
)

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

  redisPort, err := cfg.Section("redis").Key("port").Int()
  iferr ! =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()
  iferr ! =nil {
    fmt.Println("after must, get redis port error:", err)
  } else {
    fmt.Println("after must, get redis port:", redisPort)
  }
}
Copy the code

Configuration file or redis port is not digital x6381 state, run the program:

before must, get redis port error: strconv.ParseInt: parsing "x6381": invalid syntax
redis Port: 6381
after must, get redis port: 6381
Copy the code

The first call to Int returns an error. After MustInt is called with 6381 as an argument, the next call to Int returns 6381 on success. MustInt source is also relatively simple:

// 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
}
Copy the code

To partition

Access to information

After loading the configuration, all partitions can be obtained through the Sections method and all partition names can be obtained through the SectionStrings() method.

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

fmt.Println("sections: ", sections)
fmt.Println("names: ", names)
Copy the code

Run output 3 partitions:

[DEFAULT mysql redis]
Copy the code

Call Section(name) to get a partition named name. If the partition does not exist, a partition is automatically created.

newSection := cfg.Section("new")

fmt.Println("new section: ", newSection)
fmt.Println("names: ", cfg.SectionStrings())
Copy the code

The SectionStrings method is called after the partition is created, and the new partition will return:

names:  [DEFAULT mysql redis new]
Copy the code

You can also manually create a new partition. If the partition already exists, an error is returned:

err := cfg.NewSection("new")
Copy the code

Father and son partition

In the configuration file, you can use the placeholder %(name)s to represent the value of the previously defined key name, where s represents the value as a string:

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

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

[package.sub]
Copy the code

When setting the value IMPORT_PATH in the default partition above, NAME and VERSION were used as defined earlier. When the value of CLONE_URL is set in the Package partition, the IMPORT_PATH defined in the default partition is used.

We can also use it in partition names. Indicates the parent-child relationship between two or more partitions. For example, the parent partition of package.sub is Package and the parent partition of package is the default partition. If a key does not exist in the child partition, it is looked up again in its parent partition until there is no parent partition:

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

fmt.Println("Clone url from package.sub:", cfg.Section("package.sub").Key("CLONE_URL").String())
Copy the code

Run the program output:

Clone url from package.sub: https://gopkg.in/ini.v1
Copy the code

There is no CLONE_URL in package.sub, which returns the value in the parent partition package.

Save the configuration

Sometimes, we need to write the generated configuration to a file. For example, when writing tools. There are two types of interfaces to save, one directly to a file and the other to write to IO.Writer:

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

cfg.WriteTo(writer)
cfg.WriteToIndent(writer, "\t")
Copy the code

Here we programmatically generate the configuration file my.ini we used previously and save it:

package main

import (
  "fmt"
  "os"

  "gopkg.in/ini.v1"
)

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

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

  mysqlSection, err := cfg.NewSection("mysql")
  iferr ! =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")
  iferr ! =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")
  iferr ! =nil {
    fmt.Println("SaveTo failed: ", err)
  }

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

  cfg.WriteTo(os.Stdout)
  fmt.Println()
  cfg.WriteToIndent(os.Stdout, "\t")}Copy the code

Ini and my-pretty. Ini files are generated by running the program, while the console outputs the contents of the files.

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 = 6381Copy the code

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 = 6381Copy the code

The Indent method indents the subpartition keys to make them look nicer.

Partition and structure field mapping

After loading the configuration file, call MapTo to assign the configuration item to the corresponding field of the structure variable.

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(a) {
  cfg, err := ini.Load("my.ini")
  iferr ! =nil {
    fmt.Println("load my.ini failed: ", err)
  }

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

  fmt.Println(c)
}
Copy the code

MapTo uses reflection internally, so the structure fields must all be exported. If the key name is different from the field name, you need to specify the corresponding key name in the structure tag. This is different from the Go standard libraries encoding/ JSON and Encoding/XML. Standard library JSON/XML parsing can map the key name app_name to the field name AppName. Maybe this is a point that the Go-Ini library can optimize?

It’s a bit cumbersome to load and map, so use ini.MapTo to merge the two steps:

err = ini.MapTo(&c, "my.ini")
Copy the code

It is also possible to map only one partition:

mysqlCfg := MySQLConfig{}
err = cfg.Section("mysql").MapTo(&mysqlCfg)
Copy the code

Configuration can also be generated from a structure:

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)
iferr ! =nil {
  fmt.Println("ReflectFrom failed: ", err)
  return
}

err = cfg.SaveTo("my-copy.ini")
iferr ! =nil {
  fmt.Println("SaveTo failed: ", err)
  return
}
Copy the code

conclusion

This article introduced you to the basic usage and some interesting features of the Go-INI library. The sample code has been uploaded to GitHub. There are many more advanced features in go-INI. The official document is very detailed, recommended to see, and there are Chinese yo ~ the author did not hear, I believe that do Go development are not strange.

reference

  1. Go – ini making warehouse
  2. Go-ini official document

I

My blog

Welcome to follow my wechat public account [GoUpUp], learn together, progress together ~

This article is published by OpenWrite!