diff --git a/cmd/gen.go b/cmd/gen.go index 8ad9cbe..56da1ea 100644 --- a/cmd/gen.go +++ b/cmd/gen.go @@ -22,6 +22,8 @@ import ( "github.com/spf13/cobra" "path/filepath" "sigs.k8s.io/controller-tools/pkg/genall" + "strconv" + "time" ) func NewGenerate(ctx *Context) *cobra.Command { @@ -40,6 +42,9 @@ func NewGenerate(ctx *Context) *cobra.Command { } func NewGenerateGo(ctx *Context) *cobra.Command { + var headerFile string + var year string + genFuncGo := &cobra.Command{ Use: "go [internalPkgPath] [internalPkgName]", Short: "Generate the Go code to integrate with Tekton", @@ -67,14 +72,14 @@ The code generated for your main package will be written in zz_generated.tektask var genFunc, genInternal genall.Generator - genFuncPtr, err := gengo.NewGoFunc(ctx.Logger) + genFuncPtr, err := gengo.NewGoFunc(ctx.Logger, headerFile, year) if err != nil { return err } genFunc = genFuncPtr - genInternalPtr, err := gengo.NewGoInternal(ctx.Logger, outputPkgName) + genInternalPtr, err := gengo.NewGoInternal(ctx.Logger, outputPkgName, headerFile, year) if err != nil { return err } @@ -107,6 +112,8 @@ The code generated for your main package will be written in zz_generated.tektask }, } + genFuncGo.Flags().StringVarP(&headerFile, "headerfile", "b", "", "Path to a boilerplate header file to put at the top of any generated go code. TIPS: ' YEAR' will be replaced with the current year!") + genFuncGo.Flags().StringVar(&year, "year", strconv.Itoa(time.Now().Year()), "Which year should be written in the headerfile") return genFuncGo } diff --git a/internal/gengo/gen_func.go b/internal/gengo/gen_func.go index 90b760d..ad92fad 100644 --- a/internal/gengo/gen_func.go +++ b/internal/gengo/gen_func.go @@ -19,45 +19,61 @@ package gengo import ( "bytes" "fmt" - ttmarkers "github.com/Raskyld/go-tektasker/pkg/markers" "go/ast" "log/slog" + "strings" + "text/template" + + ttmarkers "github.com/Raskyld/go-tektasker/pkg/markers" "sigs.k8s.io/controller-tools/pkg/genall" "sigs.k8s.io/controller-tools/pkg/markers" - "text/template" ) const FuncName = "func" -const FuncTpl = `{{template "%s" .Header}} +const FuncTpl = `{{template "%s" .GoHeaderArgs}} -{{- range $tplName, $args := .Templates}} +{{- range $tplName, $args := .TemplatesArgs}} {{- range $args}} {{CallTemplate $tplName .}} {{- end}} {{- end}} ` +// TaskGoFuncGenerator is the generator in charge of generating Go files for tasks +// ATM, it generates both the internal Tektasker packages and makes user types implement +// Interface awaited by Helper functions type TaskGoFuncGenerator struct { Logger *slog.Logger Template *template.Template + + // HeaderFile path to add at the top of every generated go file + HeaderFile string + + // Year to use for header copyright notice + Year string } // PerTemplateArgs maps each registered template to a mapping of param or result name to template args // templateName -> Param/Result Name -> Param/ResultArgs -// We then will execute each template for each registered param or name with -// the associated args type PerTemplateArgs map[string]map[string]interface{} type FuncArgs struct { - Header GoHeaderArgs - Templates PerTemplateArgs + // GoHeaderArgs is the list of arguments we will pass to the GoHeader template invoked on every generated + // go file + GoHeaderArgs GoHeaderArgs + + // TemplatesArgs is a list of args to pass to the registered templates that is indexed by template, + // then by param or result name + TemplatesArgs PerTemplateArgs } -func NewGoFunc(logger *slog.Logger) (*TaskGoFuncGenerator, error) { +func NewGoFunc(logger *slog.Logger, headerFile, year string) (*TaskGoFuncGenerator, error) { g := &TaskGoFuncGenerator{ - Logger: logger.With("generator", "goFunc"), - Template: &template.Template{}, + Logger: logger.With("generator", "goFunc"), + Template: &template.Template{}, + HeaderFile: headerFile, + Year: year, } // NB(raskyld): this hack is needed to call template @@ -86,6 +102,7 @@ func NewGoFunc(logger *slog.Logger) (*TaskGoFuncGenerator, error) { return g, nil } +// RegisterTemplate with a unique name which can then latter be used by the generator func (g *TaskGoFuncGenerator) RegisterTemplate(name string, tpl string) *TaskGoFuncGenerator { nt, err := g.Template.New(name).Parse(tpl) if err != nil { @@ -101,6 +118,17 @@ func (*TaskGoFuncGenerator) RegisterMarkers(into *markers.Registry) error { } func (g *TaskGoFuncGenerator) Generate(ctx *genall.GenerationContext) error { + var headerText string + + if g.HeaderFile != "" { + buf, err := ctx.ReadFile(g.HeaderFile) + if err != nil { + return err + } + + headerText = strings.ReplaceAll(string(buf), " YEAR", " "+g.Year) + } + for _, pkg := range ctx.Roots { logger := g.Logger.With("pkg", pkg.Name) logger.Debug("starting collecting") @@ -118,6 +146,7 @@ func (g *TaskGoFuncGenerator) Generate(ctx *genall.GenerationContext) error { continue } + // Prepare the per-template per-param/result args mappings perTemplateArgs := PerTemplateArgs(make(map[string]map[string]interface{})) for _, t := range g.Template.Templates() { // NB(raskyld): we need to exclude the main template as @@ -151,18 +180,17 @@ func (g *TaskGoFuncGenerator) Generate(ctx *genall.GenerationContext) error { // TODO(raskyld): // add a way to skip creating the Unmarshal method so our // users can create a custom unmarshaler for their complex types - - mapToAdd := ParamFuncUnmarshalJSONName + funcTemplateToUse := ParamFuncUnmarshalJSONName // NB(raskyld): this is for ease of use when we have a type made of string // otherwise, we just expect the value to be valid JSON if ident, ok := info.RawSpec.Type.(*ast.Ident); ok { if ident.Name == "string" { - mapToAdd = ParamFuncUnmarshalSimpleName + funcTemplateToUse = ParamFuncUnmarshalSimpleName } } - perTemplateArgs[mapToAdd][param.Name] = ParamFuncArgs{ + perTemplateArgs[funcTemplateToUse][param.Name] = ParamFuncArgs{ ParamName: param.Name, ParamType: info.Name, } @@ -186,23 +214,26 @@ func (g *TaskGoFuncGenerator) Generate(ctx *genall.GenerationContext) error { ResultType: info.Name, } - mapToAdd := ResultFuncMarshalJSONName + funcTemplateToUse := ResultFuncMarshalJSONName // NB(raskyld): this is for ease of use when we have a type made of string // otherwise, we just expect the value to be valid JSON if ident, ok := info.RawSpec.Type.(*ast.Ident); ok { if ident.Name == "string" { - mapToAdd = ResultFuncMarshalSimpleName + funcTemplateToUse = ResultFuncMarshalSimpleName } } - perTemplateArgs[mapToAdd][result.Name] = ResultFuncArgs{ + perTemplateArgs[funcTemplateToUse][result.Name] = ResultFuncArgs{ ResultName: result.Name, ResultType: info.Name, } } } }) + if err != nil { + return err + } output, err := ctx.OutputRule.Open(pkg, "zz_generated.tektasker.go") if err != nil { @@ -215,12 +246,12 @@ func (g *TaskGoFuncGenerator) Generate(ctx *genall.GenerationContext) error { } err = g.Template.ExecuteTemplate(output, FuncName, FuncArgs{ - Header: GoHeaderArgs{ + GoHeaderArgs: GoHeaderArgs{ PkgName: pkg.Name, - Header: "// generated code", + Header: headerText, ImportPaths: importPaths, }, - Templates: perTemplateArgs, + TemplatesArgs: perTemplateArgs, }) if err != nil { diff --git a/internal/gengo/gen_internal.go b/internal/gengo/gen_internal.go index be2c7d1..05c44fb 100644 --- a/internal/gengo/gen_internal.go +++ b/internal/gengo/gen_internal.go @@ -22,22 +22,25 @@ import ( "log/slog" "sigs.k8s.io/controller-tools/pkg/genall" "sigs.k8s.io/controller-tools/pkg/markers" + "strings" "text/template" ) type TaskGoInternalGenerator struct { - Logger *slog.Logger - - Template *template.Template - + Logger *slog.Logger + Template *template.Template PackageName string + HeaderFile string + Year string } -func NewGoInternal(logger *slog.Logger, pkgName string) (*TaskGoInternalGenerator, error) { +func NewGoInternal(logger *slog.Logger, pkgName, headerFile, year string) (*TaskGoInternalGenerator, error) { g := &TaskGoInternalGenerator{ Logger: logger.With("generator", "goInternal"), Template: &template.Template{}, PackageName: pkgName, + HeaderFile: headerFile, + Year: year, } g.RegisterTemplate(GoHeaderName, GoHeaderTpl). @@ -63,10 +66,20 @@ func (*TaskGoInternalGenerator) RegisterMarkers(into *markers.Registry) error { func (g *TaskGoInternalGenerator) Generate(ctx *genall.GenerationContext) error { var headerBytes bytes.Buffer + var headerText string + + if g.HeaderFile != "" { + buf, err := ctx.ReadFile(g.HeaderFile) + if err != nil { + return err + } + + headerText = strings.ReplaceAll(string(buf), " YEAR", " "+g.Year) + } headerArgs := GoHeaderArgs{ PkgName: g.PackageName, - Header: "// generated code", + Header: headerText, ImportPaths: []string{ "errors", "fmt", diff --git a/internal/gengo/go_header.go b/internal/gengo/go_header.go index b60a37f..7b00aa7 100644 --- a/internal/gengo/go_header.go +++ b/internal/gengo/go_header.go @@ -18,7 +18,9 @@ package gengo const GoHeaderName = "go.header" -const GoHeaderTpl = `{{.Header}} +const GoHeaderTpl = `{{- with .Header -}} +{{.}} +{{end -}} package {{.PkgName}}