Goを書いてると、設定ファイルにはHCL使ってみるか、という気分になったりすることがある。 普通に考えるとgithub.com/hashicorp/hclが正解なわけだけど、
を見つけてしまったので使い方のメモ。まだまだexperimentalとはいえ、Terraformに使われてたりするし、まあそんな派手に変更はないのでは?と思っている。
簡単な例
こういうファイルを読みたいとき
num = 1 chr = "aaa" list = ["x", "y", "z"]
コードはこんな感じになる。要はhclparse.ParseHCLFile()
でファイルをパースすると、
hcl.Body
というのが中間表現?を含んだオブジェクト(hcl.File
)が返される。
それをgohcl.DecodeBody()
で構造体に変換する、という流れ。
各操作は、hcl.Diagnostics
オブジェクトを返すので、それをhasErrors()
でエラーがあるかチェックしたり、その場では何もせずappend()
していって最後にまとめてエラー処理、ということもできる。
package main import ( "fmt" "github.com/hashicorp/hcl2/gohcl" "github.com/hashicorp/hcl2/hclparse" "log" ) type foo struct { Num int `hcl:"num"` Chr string `hcl:"chr"` List []string `hcl:"list"` } func main() { parser := hclparse.NewParser() f, parseDiags := parser.ParseHCLFile("test.hcl") if parseDiags.HasErrors() { log.Fatal(parseDiags.Error()) } var fooInstance foo decodeDiags := gohcl.DecodeBody(f.Body, nil, &fooInstance) if decodeDiags.HasErrors() { log.Fatal(decodeDiags.Error()) } fmt.Printf("%#v", fooInstance) }
もうちょっと複雑な例
よく見るHCLだとこんな感じの指定をする。
revision = 1 instance "x" { cpu 1 mem 512 } instance "y" { cpu 3 mem 1024 }
これをパースするコードは以下のようになる。,
の後にblock
とかlabel
を指定しているところに注目。
package main import ( "fmt" "github.com/hashicorp/hcl2/gohcl" "github.com/hashicorp/hcl2/hclparse" "log" ) type instance struct { Name string `hcl:"name,label"` CPU int `hcl:"cpu"` Memory int `hcl:"mem"` } type foo struct { Revision int `hcl:"revision"` Instance []instance `hcl:"instance,block"` } func main() { parser := hclparse.NewParser() f, parseDiags := parser.ParseHCLFile("test.hcl") if parseDiags.HasErrors() { log.Fatal(parseDiags.Error()) } var fooInstance foo decodeDiags := gohcl.DecodeBody(f.Body, nil, &fooInstance) if decodeDiags.HasErrors() { log.Fatal(decodeDiags.Error()) } fmt.Printf("%#v", fooInstance) }
ちなみに、これをラベルをキーにしてmap[string]SomeClass
に勝手に変換したりしてくれないかな?と思ったけど、ラベルは複数あったりするのでそこの対応関係は自動ではやってくれないか...
感想
使用例がうまく見つけられなくて戸惑ってたけど、軽く使う分には雰囲気でも使えそうなので便利そう。 あと、ぜんぜん理解してないけどpartial decodingとか興味深そう。