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

# TOMLとは

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

**Tom's Obvious, Minimal Language.** の略らしい。
ぐぐってみたら、日本語のエントリがでてきた。

[TOMLノススメ](http://mojavy.com/blog/2013/04/26/toml/)

より一部引用。

```
TOMLとは
https://github.com/mojombo/toml

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

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

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

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

jsonで設定を書いて、Unmarshalして、構造体に突っ込む。
[encoding/json](http://golang.org/pkg/encoding/json/) パッケージを使えば良いのでお手軽だ。　

```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し、構造体に突っ込むことができる。

```go: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と同等のものを書いてみる。

```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.Decode``` 関数を利用する。
goのパッケージは[github.com/BurntSushi/toml](https://github.com/BurntSushi/toml)を使う。

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

```go: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ファイルとするならば、こんな形。

```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化するとこうなる。

```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](https://github.com/toml-lang/toml#array-of-tables)を参考。

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

この構造をTOMLに落とすとき、どう書いていいかわからない、
記法なれてないしなーというときは
```toml.Encode``` 関数を利用すれば良い。

```go: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ファイル化される。便利だ。

```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](https://github.com/toml-lang/toml/blob/master/README.md#implementations)

Rubyもあるし、Pythonもある。

## Rubyの場合

Rubyの場合は[toml-rb](https://github.com/eMancu/toml-rb)を使うと良いと思う。

### セットアップ

```ruby:Gemfile
source 'https://rubygems.org/'

gem 'toml-rb'
```

```
> bundle ins --path vendor/bundle
```

### デコード

- コード

```ruby: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)
```

- 出力結果

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

### エンコード

- コード

```ruby:encode.rb
require 'toml'

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

puts TOML.dump(obj)
```

- 出力結果

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

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

定数を書いたファイルを管理するとか
YAMLファイルを使うとかあるのだけど、
複数言語向けのパーサが揃っていて(有志が書いてる)、
かつphp.iniとかmy.confのような
configファイル形式ライクにかけるので、
TOMLは良いんじゃないかなと思った。

YAMLとか縦に長くなるし、
セクションの区切りも厳しいしなァ...。