Irohabook
0
1101

Golangでテンプレートを読みこむ:ParseFilesやParseGlobよりもParseを使う。そうすれば異なるディレクトリにある同じファイル名のテンプレートを読みこむ

テンプレートディレクトリに a と b があり、そのどちらにも index.html があるとする。

a/index.html
b/index.html

このとき Go の ParseFiles は b/index.html のみを処理する。つまり ParseFiles を使ってテンプレートを読みこむとき、同じ名前のファイルは扱えない。

When parsing multiple files with the same name in different directories, the last one mentioned will be the one that results.
Go 公式より

階層を分けて、しかも同じファイル名を使いまわしたいときは工夫が必要になる。

ここからは個人的な想像で、公式ページにもとづくものではない。おそらく Go は、テンプレートのパスからファイル名に相当する部分(上では index.html にあたる)を、内部で用いるテンプレート名にしている。ディレクトリはすべて除去している。だから最後に読んだテンプレートだけが残る。

Go が内部で使うテンプレート名は define で設定したものも含まれる。

{{define "abc"}}
{{end}}

とすると abc というテンプレートが作られ、これは abc という名前で管理される。

つまりすべてのテンプレートファイルに define でテンプレート名を指定すれば、同名の衝突問題を解決できるかもしれない。検証していない。おそらく推奨されないやり方だ。

ParseFiles は使わずに Parse を使う

Golang: Parse all templates in directory and subdirectories?

の記事が問題を解決している。このベストアンサー以外のアンサーに同じファイル名が衝突する問題が指摘されている。こういう新しい言語では、重要な指摘にスターがつかない&目立たないことが非常によくある。結論は次のようになる。

func ParseTemplates() *template.Template {
    funcMap := template.FuncMap{
        "markdown": func(s string) string {
            return Markdown(s)
        },
    }
    tpl := template.New("")
    err := filepath.Walk("./templates", func(path string, info os.FileInfo, e1 error) error {
        if !info.IsDir() && strings.HasSuffix(path, ".html") {
            fmt.Println(path)
            if e1 != nil {
                fmt.Println(e1)
                return e1
            }
            b, e2 := ioutil.ReadFile(path)
            if e2 != nil {
                fmt.Println(e2)
                return e2
            }
            filename := strings.Replace(path, "templates/", "", -1)
            fmt.Println(filename)
            t := tpl.New(filename).Funcs(funcMap)
            t, e2 = t.Parse(string(b))
            if e2 != nil {
                fmt.Println(e2)
                return e2
            }
        }
        return nil
    })
    if err != nil {
        panic(err)
    }
    return tpl
}

これは不完全だが、テンプレートを再帰的に読みこむ関数として基本的なところはおさえてある。まずは filepath.Walk で再帰的にファイルを読みこみ、パス名をそのままテンプレート名にする。ポイントは

t := tpl.New(filename).Funcs(funcMap)

である。なにも指定しないとパスでなく、ファイル名がテンプレート名になることがそもそも元凶だった。それは ParseFiles の欠陥だが、Parse を使うことでこれを回避できる。

次の記事

HTTP