代码之家  ›  专栏  ›  技术社区  ›  Peter Kaufman

Golang模板为空,但未抛出错误

  •  1
  • Peter Kaufman  · 技术社区  · 11 月前

    我一直在努力让README为我自动生成一些Cobra CLI程序。起初,事情进展顺利,我得到了第一个生成良好的README。然而,由于某种原因,使用相同功能的第二次尝试失败了。因此,在几次尝试让它发挥作用后,我决定在这里发布一个问题。

    所有代码都可以找到 here 使用Makefile(make generate是生成README文件的命令)。由于某种原因,未生成的模板将显示为空字符串。

    生成的README和一个空字符串的README使用相同的代码。以下是共享代码:

    package cmdhandler
    
    import (
        "bufio"
        "bytes"
        "errors"
        "fmt"
        "strings"
        "text/template"
    
        "github.com/MakeNowJust/heredoc"
        "github.com/davecgh/go-spew/spew"
        cmdtomd "github.com/pjkaufman/go-go-gadgets/pkg/cmd-to-md"
        filehandler "github.com/pjkaufman/go-go-gadgets/pkg/file-handler"
        "github.com/pjkaufman/go-go-gadgets/pkg/logger"
        "github.com/spf13/cobra"
    )
    
    var generationDir string
    
    const GenerationDirArgEmpty = "generation-dir must have a non-whitespace value"
    
    type TmplData struct {
        CommandStrings string
        Todos          []string
        Description    string
        Title          string
        CustomValues   map[string]any
    }
    
    func AddGenerateCmd(rootCmd *cobra.Command, title, description string, todos []string, getCustomValues func(string) (map[string]any, error)) {
        var generateCmd = &cobra.Command{
            Use:   "generate",
            Short: "Generates the readme file for the program",
            Example: heredoc.Doc(`
                ` + rootCmd.Use + ` generate -d ./` + rootCmd.Use + `
                will look for a file called README.md.tmpl and if it is found generate a readme based
                on that file and the file command info.
            `),
            Run: func(cmd *cobra.Command, args []string) {
                err := ValidateGenerateFlags(generationDir)
                if err != nil {
                    logger.WriteError(err.Error())
                }
    
                err = filehandler.FolderMustExist(generationDir, "generation-dir")
                if err != nil {
                    logger.WriteError(err.Error())
                }
    
                tmpl, err := template.ParseFiles(filehandler.JoinPath(generationDir, "README.md.tmpl"))
                if err != nil {
                    logger.WriteError(err.Error())
                }
    
                var customValues = make(map[string]any)
                if getCustomValues != nil {
                    customValues, err = getCustomValues(generationDir)
    
                    if err != nil {
                        logger.WriteError(err.Error())
                    }
                }
    
                var b bytes.Buffer
                writer := bufio.NewWriter(&b)
    
                err = tmpl.Execute(writer, TmplData{
                    CommandStrings: cmdtomd.RootToMd(rootCmd),
                    Todos:          todos,
                    Description:    description,
                    Title:          title,
                    CustomValues:   customValues,
                })
                if err != nil {
                    logger.WriteError(err.Error())
                }
    
                spew.Dump(TmplData{
                    CommandStrings: cmdtomd.RootToMd(rootCmd),
                    Todos:          todos,
                    Description:    description,
                    Title:          title,
                    CustomValues:   customValues,
                })
    
                err = filehandler.WriteFileContents(filehandler.JoinPath(generationDir, "README.md"), b.String())
                if err != nil {
                    logger.WriteError(err.Error())
                }
            },
        }
    
        rootCmd.AddCommand(generateCmd)
    
        generateCmd.Flags().StringVarP(&generationDir, "generation-dir", "g", "", "the path to the base folder of the "+rootCmd.Use+" program source code")
        err := generateCmd.MarkFlagRequired("generation-dir")
        if err != nil {
            logger.WriteError(fmt.Sprintf(`failed to mark flag "generation-dir" as required on generate command: %v`, err))
        }
    
        // keep from showing up in the output of the command generation
        generateCmd.Hidden = true
    }
    
    func ValidateGenerateFlags(generationDir string) error {
        if strings.TrimSpace(generationDir) == "" {
            return errors.New(GenerationDirArgEmpty)
        }
    
        return nil
    }
    

    使用此逻辑但无法正常工作的代码:

    //go:build generate
    
    package cmd
    
    import (
        cmdhandler "github.com/pjkaufman/go-go-gadgets/pkg/cmd-handler"
    )
    
    const (
        title       = "Jpeg and Png Processor"
        description = `This is meant to be a replacement for my usage of imgp.
    
    Currently I use imgp for the following things:
    - image resizing
    - exif data removal
    - image quality setting
    
    Given how this works, I find it easier to just go ahead and do a simple program in Go to see how things stack up and not be so reliant on Python. This also helps me learn some more about imaging processing as well. So a win-win in my book.`
    )
    
    func init() {
        cmdhandler.AddGenerateCmd(rootCmd, title, description, []string{
            "Resize png test",
        }, nil)
    }
    

    传入的模板是:

    <!-- This file is generated from  https://github.com/pjkaufman/go-go-gadgets/jp-proc/README.md.tmpl. Please make any necessary changes there. -->
    
    # {{ .Title }}
    
    {{ .Description }}
    
    ## How does this program compare with imgp?
    
    | Operation | Original Size | New Size (imgp) | New Size (imgp with optimize flag) | New Size (jp-proc) |
    | --------- | ------------- | --------------- | ---------------------------------- | ------------------ |
    | Resize jpeg to 800x600 and remove exif data | 3.4M | 57KB | 56KB | 68KB |
    | Resize jpeg to 800x600 and remove exif data and set quality to 40 | 3.4M | 32KB | 28KB | 37KB |
    {{- if .Todos }}
    
    ## TODOs
    
    {{- range .Todos }}
    - {{ . }}
    {{- end}}
    {{- end}}
    
    ## Commands
    
    {{ .CommandStrings }}
    
    

    我使用相同的模板制作了一个示例程序,它确实可以工作,但由于某种原因,我无法使其他设置工作。以下是正在工作的独立程序:

    package main
    
    import (
        "log"
        "os"
        "text/template"
    )
    
    const format = `# {{ .Title }}
    
    {{ .Description }}
    
    ## How does this program compare with imgp?
    
    | Operation | Original Size | New Size (imgp) | New Size (imgp with optimize flag) | New Size (jp-proc) |
    | --------- | ------------- | --------------- | ---------------------------------- | ------------------ |
    | Resize jpeg to 800x600 and remove exif data | 3.4M | 57KB | 56KB | 68KB |
    | Resize jpeg to 800x600 and remove exif data and set quality to 40 | 3.4M | 32KB | 28KB | 37KB |
    {{- if .Todos }}
    
    ## TODOs
    
    {{- range .Todos }}
    - {{ . }}
    {{- end}}
    {{- end}}
    
    ## Commands
    
    {{ .CommandStrings }}
    `
    
    type TmplData struct {
        CommandStrings string
        Todos          []string
        Description    string
        Title          string
        CustomValues   map[string]any
    }
    
    func main() {
        test := template.New("testTemp")
    
        tmpl, err := test.Parse(format)
        if err != nil {
            log.Fatal(err)
        }
    
        err = tmpl.ExecuteTemplate(os.Stdout, "testTemp", TmplData{
            Title:       "Test Title",
            Description: "This is a test template",
            Todos: []string{
                "TODO 1",
                "TODO 2",
            },
            CommandStrings: "Some command string text here...",
        })
        if err != nil {
            log.Fatal(err)
        }
    }
    

    你知道为什么模板可以在独立程序中工作,但不能在cobra命令中工作吗?

    如果有任何其他信息有帮助,请告诉我。谢谢

    1 回复  |  直到 11 月前
        1
  •  1
  •   Eaton Scribner    11 月前

    Flush bufio。作家

    var b bytes.Buffer
    writer := bufio.NewWriter(&b)
    
    err = tmpl.Execute(writer, TmplData{
        CommandStrings: cmdtomd.RootToMd(rootCmd),
        Todos:          todos,
        Description:    description,
        Title:          title,
        CustomValues:   customValues,
    })
    if err != nil {
        logger.WriteError(err.Error())
    }
    writer.Flush() // <--- add this line
    

    更好的解决方案是写入字节。直接缓冲:

    var b bytes.Buffer 
    err = tmpl.Execute(&b, TmplData{ // <--- write to buffer directly
        CommandStrings: cmdtomd.RootToMd(rootCmd),
        Todos:          todos,
        Description:    description,
        Title:          title,
        CustomValues:   customValues,
    })
    if err != nil {
        logger.WriteError(err.Error())
    }