🧞♂️
【Golang】テンプレートからEnumコード生成してみた(text/template)
少し探した感じでは見つけられなかったので,GolangでEnumコードを生成するコードジェネレーターを自分で書いた時の記録になります.
text/template
の具体例として参考にしていただければ幸いです.
何を作ったか
複数種の(単純な)Enumを大量生成するにあたって必要な情報が書かれたjsonファイルを入力とし,goのEnumコードを出力生成するコードジェネレーターを作成しました.
入力(jsonファイルenums.json
)
[
{
"type": "CITY",
"package": "types",
"data": [
{
"id": "TYO",
"name": "Tokyo"
},
{
"id": "NYC",
"name": "New York"
},
{
"id": "LDN",
"name": "London"
}
]
},
{
"type": "COUNTRY",
"package": "enums",
"data": [
{
"id": "JPN",
"name": "Japan"
},
{
"id": "US",
"name": "United States"
},
{
"id": "UK",
"name": "United Kingdom"
}
]
}
]
生成されたEnum(types/city.go
)
// Code generated by go generate; DO NOT EDIT.
package types
type CITY int
const (
TYO CITY = iota
NYC CITY = iota
LDN CITY = iota
)
func (e CITY) String() string {
switch e {
case TYO:
return "Tokyo"
case NYC:
return "New York"
case LDN:
return "London"
default:
return "Unknown CITY"
}
}
生成されたEnum(enums/country.go
)
// Code generated by go generate; DO NOT EDIT.
package enums
type COUNTRY int
const (
JPN COUNTRY = iota
US COUNTRY = iota
UK COUNTRY = iota
)
func (e COUNTRY) String() string {
switch e {
case JPN:
return "Japan"
case US:
return "United States"
case UK:
return "United Kingdom"
default:
return "Unknown COUNTRY"
}
}
TL;DR
package main
//go:generate go run main.go
import (
_ "embed"
"encoding/json"
"fmt"
"os"
"strings"
"text/template"
)
const (
ENUM_FILE_NAME = "enum.json"
)
type Enum struct {
Id string `json:"id"`
Name string `json:"name"`
}
type Enums struct {
Type string `json:"type"`
Package string `json:"package"`
Data []Enum `json:"data"`
}
func main() {
var enums []Enums
enumBytes, _ := os.ReadFile(ENUM_FILE_NAME)
if err := json.Unmarshal(enumBytes, &enums); err != nil {
panic(err)
}
fmt.Println(enums)
for _, e := range enums {
os.Mkdir(e.Package, 0755)
f, err := os.Create("./" + e.Package + "/" + strings.ToLower(e.Type) + ".go")
if err != nil {
panic(err)
}
defer f.Close()
err = enumTemplate.Execute(f, e)
if err != nil {
panic(err)
}
}
}
var enumTemplate = template.Must(template.New("").Parse(`// Code generated by go generate; DO NOT EDIT.
package {{.Package}}
{{ $type := .Type }}
type {{$type}} int
{{ $data := .Data }}
const (
{{- range $d := $data }}
{{ $d.Id }} {{ $type }} = iota
{{- end }}
)
func (e {{ $type }}) String() string {
switch e {
{{- range $i, $d := .Data }}
case {{ $d.Id }}:
return {{ printf "%q" $d.Name }}
{{- end }}
default:
return "Unknown {{ $type }}"
}
}
`))
使い方
$ go generate
[{CITY types [{TYO Tokyo} {NYC New York} {LDN London}]} {COUNTRY enums [{JPN Japan} {US United States} {UK United Kingdom}]}]
実行後,それぞれのpackage配下にファイルが生成されます.
個人的なハマりポイントとしては,テンプレートにおいて {{range}}{{end}}
内では {{.Type}}
を呼び出すとエラーになるので,一回 {{ $type := .Type }}
で変数置換する必要があるという点でした.
なお,Go 1.16 から採用されたgo:embed
ディレクティブを使うと,1バイナリになったコードジェネレーターが作れるので,実行時にjsonファイルがなくてもコード生成できるようになります.
まとめ
今回のこの単純な具体例を通して,text/template
でコード自動生成へ興味を持っていただけたなら幸いです.
また,間違いがありましたら指摘くださると助かります.
利用したコードは下記に格納してあります
Discussion