LoginSignup
5
2

More than 5 years have passed since last update.

HCL を Parse する

Last updated at Posted at 2018-04-30

Go 言語で、Viper を使わず、生の HCLinterface{} ではなく、特定の構造体にパースしたいと思ったので、サンプルを書いてみました。

Using HCL - Part 1 - Introductionを参考にしました。

ターゲットの HCL

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

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

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

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

構造体

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

    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 から取得します。

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

Decode の実行

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

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

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

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

$ go run main.go
Decoding...

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

This is Azure
azure

コード全体

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に実装されていると思われる。参考。この記法は、Struct の Tagという記法。
* Tags in Golang

タグは、下記のようにリフレクションで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

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

5
2
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
5
2