You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go中如何解析嵌入模板?为何ParseFS无法找到模板文件?

问题描述

我正在编写一个生成代码的Go程序,想要结合embed包与ParseFS解析模板,确保代码能在仓库任意目录运行。

现有基于ParseFiles的可用实现,目录结构如下:

.
├── codegen
│	└── main.go
├── foo
│	├── foo.go
│	├── foo.go.tmpl
│	└── foo_test.go
├── gen
│	└── foo.go
└── go.mod

foo.go的代码生成逻辑如下:

package foo

import (
    "bytes"
    "fmt"
    "html/template"
    "filepath"
)

const templateFile = "../foo/foo.go.tmpl"

type GeneratedType struct {
    Name         string
    StringFields []string
}

func GenerateCode() ([]byte, error) {
    tmpl, err := template.New(filepath.Base(templateFile)).ParseFiles(templateFile)
    if err != nil {
        return nil, fmt.Errorf("parse template: %v", err)
    }

    var buf bytes.Buffer
    if err := tmpl.Execute(&buf, GeneratedType{
        Name:         "Foo",
        StringFields: []string{"Bar"},
    }); err != nil {
        return nil, fmt.Errorf("execute template: %v", err)
    }

    return buf.Bytes(), nil
}

模板文件foo.go.tmpl内容:

package foo

type {{.Name}} struct {
    {{- range .StringFields}}
    {{.}} string
    {{- end}}
}

单元测试foo_test.go:

package foo

import (
    "go/format"
    "testing"
)

func TestGenerateCode(t *testing.T) {
    code, err := GenerateCode()
    if err != nil {
        t.Errorf("generate code: %v", err)
    }

    if _, err := format.Source(code); err != nil {
        t.Errorf("format source code: %v", err)
    }
}

codegen/main.go通过go generate运行,调用GenerateCode在gen目录生成代码:

//go:generate go run github.com/khpeek/codegen-example/codegen
package main

import (
    "errors"
    "log"
    "os"

    "github.com/khpeek/codegen-example/foo"
)

func main() {
    code, err := foo.GenerateCode()
    if err != nil {
        log.Fatalf("generate code: %v", err)
    }

    if err := os.Mkdir("../gen", 0700); err != nil && !errors.Is(err, os.ErrExist) {
        log.Fatalf("create directory for generated code: %v", err)
    }

    if err := os.WriteFile("../gen/foo.go", code, 0644); err != nil {
        log.Fatalf("write file: %v", err)
    }
}

该实现可以正常运行go generate ./...和go test ./...,但模板路径"../foo/foo.go.tmpl"依赖目录层级,鲁棒性差。尝试改用embed包优化foo.go:

package foo

import (
    "bytes"
    "embed"
    "fmt"
    "text/template"
)

//go:embed foo.go.tmpl
var templateFS embed.FS

type GeneratedType struct {
    Name         string
    StringFields []string
}

func GenerateCode() ([]byte, error) {
    tmpl, err := template.New("foo.go.tmpl").ParseFS(templateFS)
    if err != nil {
        return nil, fmt.Errorf("parse template: %v", err)
    }

    var buf bytes.Buffer
    if err := tmpl.Execute(&buf, GeneratedType{
        Name:         "Foo",
        StringFields: []string{"Bar"},
    }); err != nil {
        return nil, fmt.Errorf("execute template: %v", err)
    }

    return buf.Bytes(), nil
}

但此时运行生成或测试会报错:"no files name in call to ParseFiles",具体错误如下:

> go generate ./...
2023/01/31 09:06:21 generate code: parse template: template: no files named in call to ParseFiles
exit status 1
codegen/main.go:1: running "go": exit status 1
> go test ./...
?       github.com/khpeek/codegen-example/codegen   [no test files]
--- FAIL: TestGenerateCode (0.00s)
    foo_test.go:11: generate code: parse template: template: no files named in call to ParseFiles
FAIL
FAIL    github.com/khpeek/codegen-example/foo   0.137s
?       github.com/khpeek/codegen-example/gen   [no test files]
FAIL

请问为何ParseFS无法找到模板文件?


问题原因与解决方法

错误原因

template.ParseFS函数的签名是func ParseFS(fsys fs.FS, patterns ...string) (*Template, error),它需要传入要解析的文件路径模式作为第二个及以后的参数。当前代码只传了嵌入文件系统templateFS,没有指定要读取的模板文件名,导致函数找不到目标文件,从而抛出no files named in call to ParseFiles错误。

修正后的代码

修改foo.go中的GenerateCode函数,在ParseFS中添加模板文件名参数:

package foo

import (
    "bytes"
    "embed"
    "fmt"
    "text/template"
)

//go:embed foo.go.tmpl
var templateFS embed.FS

type GeneratedType struct {
    Name         string
    StringFields []string
}

func GenerateCode() ([]byte, error) {
    // 传入模板文件名作为ParseFS的第二个参数
    tmpl, err := template.New("foo.go.tmpl").ParseFS(templateFS, "foo.go.tmpl")
    if err != nil {
        return nil, fmt.Errorf("parse template: %v", err)
    }

    var buf bytes.Buffer
    if err := tmpl.Execute(&buf, GeneratedType{
        Name:         "Foo",
        StringFields: []string{"Bar"},
    }); err != nil {
        return nil, fmt.Errorf("execute template: %v", err)
    }

    return buf.Bytes(), nil
}

补充说明

  • 因为//go:embed foo.go.tmpl已经将模板文件嵌入到templateFS中,所以直接传入文件名"foo.go.tmpl"即可,不需要额外路径。
  • 修改后代码不再依赖外部目录结构,在仓库任意目录下运行go generate ./...或go test ./...都能正常工作。

内容的提问来源于stack exchange,提问作者Kurt Peek

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 02:25:48