hashicorp/hcl2でHCLファイルをパースする

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とか興味深そう。

参考リンク