如何在Go语言中解析嵌入式模板?

ycl3bljg  于 2023-02-01  发布在  Go
关注(0)|答案(1)|浏览(139)

我正在尝试编写一个生成代码的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"
    "path/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 contains使用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只是"巧合地"从codegenfoo目录正确解析。如果我改变目录级别,我怀疑这个示例将不再工作。
我希望通过使用embed包来使其更加健壮,这样我就可以始终引用包目录中的文件(在本例中为foo)。为此,我尝试将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没有"找到"模板文件吗?

pdsfdshx

pdsfdshx1#

要将mkopriva的注解转换为答案,我需要为ParseFS提供第二个可变参数,表示与模板文件匹配的glob模式:

tmpl, err := template.New("foo.go.tmpl").ParseFS(templateFS, "*.go.tmpl")

相关问题