LoginSignup
279
268

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-08-16

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とか縦に長くなるし、
セクションの区切りも厳しいしなァ...。

279
268
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
279
268