Я работал над тем, чтобы README автоматически генерировались для некоторых программ Cobra CLI. Поначалу все шло хорошо, и я получил первый README, который сгенерировался нормально. Однако по какой-то причине вторая попытка с использованием той же функции не удалась. Итак, после нескольких попыток заставить его работать, я решил задать вопрос здесь.
Весь код можно найти здесь с помощью Makefile (makegenerate — это команда для создания файлов 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)
}
}
Есть идеи, почему шаблон будет работать в автономной программе, но не в командах кобры?
Пожалуйста, дайте мне знать, если какая-либо другая информация будет полезна. Спасибо!
Смытьбуфио.Писатель:
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
Лучшее решение — напрямую записать в bytes.Buffer:
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())
}
Спасибо @Eaten Scribner. Это работает! Я думал, что он не будет записывать данные напрямую в буфер, но, похоже, я ошибался.