HCL を Parse する

Go 言語で、Viper を使わず、生の [HCL](https://github.com/hashicorp/hcl) を`interface{}` ではなく、特定の構造体にパースしたいと思ったので、サンプルを書いてみました。

[Using HCL - Part 1 - Introduction](http://jen20.com/2015/09/07/using-hcl-part-1.html)を参考にしました。

# ターゲットの HCL

とても簡単ですが、こちらのHCL

```
variable "azure" {
  description = "This is Azure"
}
```

こちらは下記の `json` と等価です。やはり、HCLは１００倍読みやすい、、、

```
{
 "variable": {
    "azure": {
      "description": "This is Azure"
    }
}
```

# 構造体

上記の HCL をパースするための構造体を定義します。

```go
	type Directories struct {
		Name        string `hcl:",key"`
		Description string
	}

	type Config struct {
		Variable []Directories
	}
```

実際に実施してみると、ポイントは二つありました。`Config` が起点になります。実際の`description` は一つしかありませんが`Directories` は配列にしないとうまく Parse できません。

これは上記のブログが参考になります。

```
directory "config" {
    source_dir = "/etc/eventstore"
    dest_prefix = "escluster/config"
    exclude = []
    pre_backup_script = "before_backup.sh"
    post_backup_script = "after_backup.sh"
    pre_restore_script = "before_restore.sh"
    post_restore_script = "after_restore.sh"
}
```

の部分は下記のようなオブジェクトに Parse されます。下記のオブジェクトを見ると、`directory` は、map になっており、そのValue　の部分は、`([]*hcl.Object)` の形式になっています。ですので、この部分は、配列でないとうまくパースできないことになります。

```
  (*hcl.Object)(0x82035e840)({
   Key: (string) (len=9) "directory",
   Type: (hcl.ValueType) ValueTypeObject,
   Value: ([]*hcl.Object) (len=1 cap=1) {
    (*hcl.Object)(0x82035e810)({
     Key: (string) (len=6) "config",
     Type: (hcl.ValueType) ValueTypeObject,
     Value: ([]*hcl.Object) (len=7 cap=7) {
      (*hcl.Object)(0x82035e6c0)({
       Key: (string) (len=10) "source_dir",
       Type: (hcl.ValueType) ValueTypeString,
       Value: (string) (len=15) "/etc/eventstore",
       Next: (*hcl.Object)(<nil>)
      }),
```

もう一点は、下記の`azure` を取得する方法です。こちらも上記の生オブジェクトの結果が参考になります。上記のサンプルは、`config` という値は、Keyに格納されています。

```
variable "azure" {
  description = "This is Azure"
}
```

ですので、Nameを hcl の Key から取得します。

```go
	type Directories struct {
		Name        string `hcl:",key"`
```

# Decode の実行

Decode の実行は、`Decode` 関数で行います。

```go
	var conf Config
            :
	err = hcl.Decode(&conf, string(b))
```

第一引数に、構造体のポインタを渡して、２つ目にパースしたい文字列を渡します。

これでめでたく Decode できました。

```
$ go run main.go
Decoding...

variable "azure" {
  description = "This is Azure"
}

This is Azure
azure
```

# コード全体

```go
package main

import (
	"fmt"
	"io/ioutil"

	"github.com/hashicorp/hcl"
)

/// This sample test read/write from HCL file.
func main() {

	type Directories struct {
		Name        string `hcl:",key"`
		Description string
	}

	type Config struct {
		Variable []Directories
	}

	//	var conf map[string]map[string]string
	var conf Config
	b, err := ioutil.ReadFile("sample.hcl")
	if err != nil {
		panic(err)
	}

	fmt.Println("Decoding...\n")
	fmt.Println(string(b))
	err = hcl.Decode(&conf, string(b))
	fmt.Println(conf.Variable[0].Description)
	fmt.Println(conf.Variable[0].Name)
	if err != nil {
		panic(err)
	}
}

```

# 今回の反省と改善のヒント

こんな簡単なものに数時間かかった。考えられる原因は

* しばらく触っておらず、go の文法（構造体とポインタ、ファイルアクセス）を忘れた
* HCLのドキュメントやサンプルが見つからず、テストを読んだ
* HCL の Decode の仕様がよくわからなかった（テストは、`interface{}` に対するパースになっている)
* 上記のを理解するために、結局他人のブログで、生パースをしているコードと結果が参考になった


次回からの改善としては

* Go の基本文法は忘れたのなら、まず思い出す。よくコードを書くようにする

`hcl:", key"` の記述は、`json`のパースの時に使われるものと同じノリでで、HCLに実装されていると思われる。[参考](https://github.com/hashicorp/hcl/blob/master/decoder_test.go#L626)。この記法は、Struct の Tagという記法。
* [Tags in Golang](https://medium.com/golangspec/tags-in-golang-3e5db0b8ef3e) 

タグは、下記のようにリフレクションでLookupできる様子。

```
type T struct {
    f string `one:"1" two:"2"blank:""`
}
func main() {
    t := reflect.TypeOf(T{})
    f, _ := t.FieldByName("f")
    fmt.Println(f.Tag) // one:"1" two:"2"blank:""
    v, ok := f.Tag.Lookup("one")
```

実際のコードだと、この付近で、tagを参照している。
* [decoder.go#L588](https://github.com/hashicorp/hcl/blob/aaf0695c2fd7aa0e05362cc272941463d9a7e1ba/decoder.go#L588)

多分、このあたりのコードを読んでよくわからない文法があったら、ハローワールドを作っていって理解したら次回はこの手のコードは高速に書けそう。