Ruby
Go
golang
TOML

【個人メモ】設定ファイルフォーマットにはTOMLがいいのかも

More than 1 year has passed since last update.


TOMLとは

https://github.com/toml-lang/toml

Tom's Obvious, Minimal Language. の略らしい。

ぐぐってみたら、日本語のエントリがでてきた。

TOMLノススメ

より一部引用。

TOMLとは

https://github.com/mojombo/toml

Tom's Obvious, Minimal Language (TOML) とはgithubの中の人が提案している設定ファイルのためのミニ言語で、以下のような特徴があります。

人間が読み書きしやすい
標準的なデータ型が利用できる
曖昧さが発生する余地が極力排除されている
パーサをかくのが簡単

とのこと。知らなかった。


Golangでサクッとかく設定ファイルとしてのjson

jsonで設定を書いて、Unmarshalして、構造体に突っ込む。

encoding/json パッケージを使えば良いのでお手軽だ。 


config.json

{

"server": {
"host": "localhost",
"port": "3306",
"slave": [
{"weight": 1, "ip": "10.0.0.1"},
{"weight": 5, "ip": "10.0.0.2"},
{"weight": 3, "ip": "10.0.0.3"},
{"weight": 2, "ip": "10.0.0.4"}
]
},
"db": {
"user": "root",
"pass": "pass"
}
}

以上のようなjsonであれば、以下のようにかけば

Unmarshalし、構造体に突っ込むことができる。


json.go

package main

import (
"encoding/json"
"fmt"
"io/ioutil"
)

type Config struct {
Server ServerConfig `json:"server"`
Db DbConfig `json:"db"`
}

type ServerConfig struct {
Host string `json:"host"`
Port string `json:"port"`
Slave []SlaveServer `json:"slave"`
}

type DbConfig struct {
User string `json:"user"`
Pass string `json:"pass"`
}

type SlaveServer struct {
Weight int `json:"weight"`
Ip string `json:"ip"`
}

func main() {
file, err := ioutil.ReadFile("config.json")
if err != nil {
panic(err)
}
var config Config
json.Unmarshal(file, &config)
fmt.Printf("Host is :%s\n", config.Server.Host)
fmt.Printf("Port is :%s\n", config.Server.Port)
for k, v := range config.Server.Slave {
fmt.Printf("Slave %d\n", k)
fmt.Printf(" weight is %d\n", v.Weight)
fmt.Printf(" ip is %s\n", v.Ip)
}
fmt.Printf("DB Username is :%s\n", config.Db.User)
fmt.Printf("DB Password is :%s\n", config.Db.Pass)
}


実行結果は以下となる

> go run json.go 

Host is :localhost
Port is :3306
Slave 0
weight is 1
ip is 10.0.0.1
Slave 1
weight is 5
ip is 10.0.0.2
Slave 2
weight is 3
ip is 10.0.0.3
Slave 3
weight is 2
ip is 10.0.0.4
DB Username is :root
DB Password is :pass

きちんとアンシリアライズできている。

けど、ネストしてるとちょっとわかりづらい。


TOMLで書いてみると...


TOMLファイルを用意する

ということで、TOMLで先ほどのjsonと同等のものを書いてみる。

[server]

host = "localhost"
port = "3306"

[[server.slave]]
weight = 1
ip = "10.0.0.1"

[[server.slave]]
weight = 5
ip = "10.0.0.2"

[[server.slave]]
weight = 3
ip = "10.0.0.3"

[[server.slave]]
weight = 2
ip = "10.0.0.4"

[db]
user = "root"
pass = "pass"


デコードをするには

TOMLを構造体にしたい場合は toml.Decode 関数を利用する。

goのパッケージはgithub.com/BurntSushi/tomlを使う。

go get github.com/BurntSushi/toml をすること


toml.go

package main

import (
"fmt"
"github.com/BurntSushi/toml"
)

type Config struct {
Server ServerConfig
Db DbConfig
}

type ServerConfig struct {
Host string `toml:"host"`
Port string `toml:"port"`
Slave []SlaveServer `toml:"slave"`
}

type DbConfig struct {
User string `toml:"user"`
Pass string `toml:"pass"`
}

type SlaveServer struct {
Weight int `toml:"weight"`
Ip string `toml:"ip"`
}

func main() {
var config Config
_, err := toml.DecodeFile("config.tml", &config)
if err != nil {
panic(err)
}
fmt.Printf("Host is :%s\n", config.Server.Host)
fmt.Printf("Port is :%s\n", config.Server.Port)
for k, v := range config.Server.Slave {
fmt.Printf("Slave %d\n", k)
fmt.Printf(" weight is %d\n", v.Weight)
fmt.Printf(" ip is %s\n", v.Ip)
}
fmt.Printf("DB Username is :%s\n", config.Db.User)
fmt.Printf("DB Password is :%s\n", config.Db.Pass)
}



出力

出力は、以下となる。

> go run toml.go

Host is :localhost
Port is :3306
Slave 0
weight is 1
ip is 10.0.0.1
Slave 1
weight is 5
ip is 10.0.0.2
Slave 2
weight is 3
ip is 10.0.0.3
Slave 3
weight is 2
ip is 10.0.0.4
DB Username is :root
DB Password is :pass


[[server.slave]] とは何か。

[[slave]]として、列挙すると

Hashの集合(Array)として扱える。

JSONファイルとするならば、こんな形。

{

"slave": [
{ "weight": 3, "ip": "10.0.0.1" },
{ "weight": 10, "ip": "10.0.0.2" },
{ "weight": 1, "ip": "10.0.0.3" }
]
}

このjsonをTOML化するとこうなる。

[[slave]]

weight = 3
ip = "10.0.0.1"

[[slave]]
weight = 10
ip = "10.0.0.2"

[[slave]]
weight = 1
ip = "10.0.0.3"

Array of Tablesを参考。


既存の構造体をTOMLへエンコードするとどうなるか

この構造をTOMLに落とすとき、どう書いていいかわからない、

記法なれてないしなーというときは

toml.Encode 関数を利用すれば良い。


encode_toml.go

package main

import (
"bytes"
"fmt"
"github.com/BurntSushi/toml"
)

type Config struct {
Server ServerConfig
Db DbConfig
}

type ServerConfig struct {
Host string `toml:"host"`
Port string `toml:"port"`
Slave []SlaveServer `toml:"slave"`
}

type DbConfig struct {
User string `toml:"user"`
Pass string `toml:"pass"`
}

type SlaveServer struct {
Weight int `toml:"weight"`
Ip string `toml:"ip"`
}

func main() {
var config Config

var dbConfig DbConfig
dbConfig.User = "root"
dbConfig.Pass = "pass"

var serverConfig ServerConfig
serverConfig.Host = "localhost"
serverConfig.Port = "3306"

serverConfig.Slave = append(serverConfig.Slave, SlaveServer{Weight: 1, Ip: "10.0.0.1"})
serverConfig.Slave = append(serverConfig.Slave, SlaveServer{Weight: 5, Ip: "10.0.0.2"})
serverConfig.Slave = append(serverConfig.Slave, SlaveServer{Weight: 3, Ip: "10.0.0.3"})
serverConfig.Slave = append(serverConfig.Slave, SlaveServer{Weight: 2, Ip: "10.0.0.4"})

config.Db = dbConfig
config.Server = serverConfig

var buffer bytes.Buffer
encoder := toml.NewEncoder(&buffer)
err := encoder.Encode(config)
if err != nil {
panic(err)
}
fmt.Printf("%v\n", buffer.String())
}



出力結果

以下のようにTOMLファイル化される。便利だ。

[Server]

host = "localhost"
port = "3306"

[[Server.slave]]
weight = 1
ip = "10.0.0.1"

[[Server.slave]]
weight = 5
ip = "10.0.0.2"

[[Server.slave]]
weight = 3
ip = "10.0.0.3"

[[Server.slave]]
weight = 2
ip = "10.0.0.4"

[Db]
user = "root"
pass = "pass"


TOMLのパーサの各言語対応

TOML リポジトリのREADME.mdに書いてあるのだけど、

ある程度の言語はサポートされている。

TOMLのREADME.md

Rubyもあるし、Pythonもある。


Rubyの場合

Rubyの場合はtoml-rbを使うと良いと思う。


セットアップ


Gemfile

source 'https://rubygems.org/'

gem 'toml-rb'


> bundle ins --path vendor/bundle


デコード


  • コード


decode.rb

require 'pp'

require 'toml'

doc = <<-EOF
[server]
host = "localhost"
port = "8080"

[application]
environment = "production"
region = "us-west-1"
EOF

pp TOML.parse(doc)



  • 出力結果

{"server"=>{"host"=>"localhost", "port"=>"8080"},

"application"=>{"environment"=>"production", "region"=>"us-west-1"}}


エンコード


  • コード


encode.rb

require 'toml'

obj = {
"server" => {
"host"=>"localhost",
"port"=>"8080"
},
"application" => {
"environment"=>"production",
"region"=>"us-west-1"
}
}

puts TOML.dump(obj)



  • 出力結果

[application]

environment = "production"
region = "us-west-1"
[server]
host = "localhost"
port = "8080"


設定ファイルのフォーマットの悩み

定数を書いたファイルを管理するとか

YAMLファイルを使うとかあるのだけど、

複数言語向けのパーサが揃っていて(有志が書いてる)、

かつphp.iniとかmy.confのような

configファイル形式ライクにかけるので、

TOMLは良いんじゃないかなと思った。

YAMLとか縦に長くなるし、

セクションの区切りも厳しいしなァ...。