diff --git a/cmd/create.go b/cmd/create.go index 59ca986..efd8379 100644 --- a/cmd/create.go +++ b/cmd/create.go @@ -1,6 +1,8 @@ package cmd import ( + "context" + "github.com/gchiesa/ska/internal/templateprovider" "github.com/gchiesa/ska/pkg/skaffolder" ) @@ -11,9 +13,10 @@ type CreateCmd struct { NonInteractive bool `arg:"-n,--non-interactive" help:"Run in non-interactive mode"` } -func (c *CreateCmd) Execute() error { +func (c *CreateCmd) Execute(ctx context.Context) error { options := &skaffolder.SkaOptions{ NonInteractive: c.NonInteractive, + Engine: ctx.Value(contextEngineKey("engine")).(templateprovider.TemplateType), } ska := skaffolder.NewSkaCreate( c.TemplateURI, diff --git a/cmd/entrypoint.go b/cmd/entrypoint.go index bd2ec5a..327474f 100644 --- a/cmd/entrypoint.go +++ b/cmd/entrypoint.go @@ -1,11 +1,13 @@ package cmd import ( + "context" "fmt" "github.com/alexflint/go-arg" "github.com/apex/log" "github.com/apex/log/handlers/cli" "github.com/apex/log/handlers/json" + "github.com/gchiesa/ska/internal/templateprovider" "os" ) @@ -19,25 +21,10 @@ type arguments struct { UpdateCmd *UpdateCmd `arg:"subcommand:update"` Debug bool `arg:"-d"` JSONOutput bool `arg:"-j,--json" help:"Enable JSON output for logging"` + Engine string `arg:"--engine" default:"sprig" help:"Template engine to use (sprig or jinja)"` } -func (arguments) Version() string { - return fmt.Sprintf("version: %s\n", commandVersion) -} - -func (arguments) Description() string { - return fmt.Sprintf(` -%s is a tool for scaffolding your directories based on blueprint templates available locally or removely on GitHub and GitLab. -`, programName) -} - -func (arguments) Epilogue() string { - return fmt.Sprintf(` -For more information check the repository on %s. - -Made with love by https://github.com/gchiesa. -`, githubRepo) -} +type contextEngineKey string func Execute(version string) error { commandVersion = version @@ -54,13 +41,19 @@ func Execute(version string) error { log.SetHandler(json.New(os.Stderr)) } + if args.Engine != "sprig" && args.Engine != "jinja" { + log.Fatalf("invalid template engine: %s", args.Engine) + } + + ctx := context.TODO() + ctx = context.WithValue(ctx, contextEngineKey("engine"), templateprovider.GetTypeFromString(args.Engine)) switch { case args.CreateCmd != nil: - if err := args.CreateCmd.Execute(); err != nil { + if err := args.CreateCmd.Execute(ctx); err != nil { log.Fatalf("error executing create command: %v", err) } case args.UpdateCmd != nil: - if err := args.UpdateCmd.Execute(); err != nil { + if err := args.UpdateCmd.Execute(ctx); err != nil { log.Fatalf("error executing update command: %v", err) } default: @@ -69,3 +62,21 @@ func Execute(version string) error { return nil } + +func (arguments) Version() string { + return fmt.Sprintf("version: %s\n", commandVersion) +} + +func (arguments) Description() string { + return fmt.Sprintf(` +%s is a tool for scaffolding your directories based on blueprint templates available locally or removely on GitHub and GitLab. +`, programName) +} + +func (arguments) Epilogue() string { + return fmt.Sprintf(` +For more information check the repository on %s. + +Made with love by https://github.com/gchiesa. +`, githubRepo) +} diff --git a/cmd/update.go b/cmd/update.go index 9428781..11ab521 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -1,6 +1,8 @@ package cmd import ( + "context" + "github.com/gchiesa/ska/internal/templateprovider" "github.com/gchiesa/ska/pkg/skaffolder" ) @@ -10,9 +12,10 @@ type UpdateCmd struct { NonInteractive bool `arg:"-n,--non-interactive" help:"Run in non-interactive mode"` } -func (c *UpdateCmd) Execute() error { +func (c *UpdateCmd) Execute(ctx context.Context) error { options := &skaffolder.SkaOptions{ NonInteractive: c.NonInteractive, + Engine: ctx.Value(contextEngineKey("engine")).(templateprovider.TemplateType), } ska := skaffolder.NewSkaUpdate( c.FolderPath, diff --git a/go.mod b/go.mod index c2baec4..ac8802b 100644 --- a/go.mod +++ b/go.mod @@ -11,12 +11,14 @@ require ( github.com/charmbracelet/bubbletea v0.26.4 github.com/charmbracelet/lipgloss v0.12.1 github.com/daixiang0/gci v0.13.4 + github.com/flosch/pongo2/v6 v6.0.0 github.com/go-critic/go-critic v0.11.3 github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 github.com/golangci/golangci-lint v1.58.1 github.com/gotesttools/gotestfmt/v2 v2.5.0 github.com/huandu/xstrings v1.5.0 github.com/otiai10/copy v1.14.0 + github.com/palantir/stacktrace v0.0.0-20161112013806-78658fd2d177 github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 github.com/stretchr/testify v1.9.0 golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 @@ -147,12 +149,10 @@ require ( github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/termenv v0.15.2 // indirect github.com/nakabonne/nestif v0.3.1 // indirect - github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/nishanths/exhaustive v0.12.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect github.com/nunnatsa/ginkgolinter v0.16.2 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/palantir/stacktrace v0.0.0-20161112013806-78658fd2d177 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pkg/errors v0.9.1 // indirect @@ -225,7 +225,6 @@ require ( golang.org/x/sys v0.21.0 // indirect golang.org/x/text v0.16.0 // indirect google.golang.org/protobuf v1.33.0 // indirect - gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/src-d/go-billy.v4 v4.3.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/go.sum b/go.sum index 519d20e..430bf8b 100644 --- a/go.sum +++ b/go.sum @@ -184,6 +184,8 @@ github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4 github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/firefart/nonamedreturns v1.0.5 h1:tM+Me2ZaXs8tfdDw3X6DOX++wMCOqzYUho6tUTYIdRA= github.com/firefart/nonamedreturns v1.0.5/go.mod h1:gHJjDqhGM4WyPt639SOZs+G89Ko7QKH5R5BhnO6xJhw= +github.com/flosch/pongo2/v6 v6.0.0 h1:lsGru8IAzHgIAw6H2m4PCyleO58I40ow6apih0WprMU= +github.com/flosch/pongo2/v6 v6.0.0/go.mod h1:CuDpFm47R0uGGE7z13/tTlt1Y6zdxvr2RLT5LJhsHEU= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= @@ -463,8 +465,6 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= @@ -1050,8 +1050,8 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= diff --git a/internal/processor/filetreeprocessor.go b/internal/processor/filetreeprocessor.go index 6901781..8374b90 100644 --- a/internal/processor/filetreeprocessor.go +++ b/internal/processor/filetreeprocessor.go @@ -2,7 +2,7 @@ package processor import ( "github.com/apex/log" - "github.com/gchiesa/ska/internal/templateservice" + "github.com/gchiesa/ska/internal/templateprovider" "os" ) @@ -15,7 +15,7 @@ func NewFileTreeProcessor(sourcePath, destinationPathRoot string, options ...fun sourcePath: sourcePath, destinationPathRoot: destinationPathRoot, workingDir: "", - templateService: templateservice.NewSprigTemplate("default"), + template: nil, log: logCtx, } // configure options @@ -72,15 +72,9 @@ func (tp *FileTreeProcessor) Render(withVariables map[string]interface{}) error return nil } -func WithTemplateService(templateService *templateservice.SprigTemplate) func(tp *FileTreeProcessor) { +func WithTemplateService(ts templateprovider.TemplateService) func(tp *FileTreeProcessor) { return func(tp *FileTreeProcessor) { - tp.templateService = templateService - } -} - -func WithErrorOnMissingKey(errorOnMissingKey bool) func(tp *FileTreeProcessor) { - return func(tp *FileTreeProcessor) { - tp.templateService.WithErrorOnMissingKey(errorOnMissingKey) + tp.template = ts } } diff --git a/internal/processor/internal.go b/internal/processor/internal.go index fed792f..f6a76b5 100644 --- a/internal/processor/internal.go +++ b/internal/processor/internal.go @@ -47,14 +47,14 @@ func (tp *FileTreeProcessor) buildStagingFileTree(withVariables map[string]inter } // create a template from the file name as it was a template - if err := tp.templateService.FromString(sRelPath); err != nil { + if err := tp.template.FromString(sRelPath); err != nil { return err } // render the template buff := bytes.NewBufferString("") - if err := tp.templateService.Execute(buff, withVariables); err != nil { - if tp.templateService.IsMissingKeyError(err) { + if err := tp.template.Execute(buff, withVariables); err != nil { + if tp.template.IsMissingKeyError(err) { logger.WithFields(log.Fields{"path": sRelPath}).Errorf("missing variable while rendering file path: %s", sRelPath) } return err @@ -158,14 +158,14 @@ func (tp *FileTreeProcessor) renderStagingFileTree(withVariables map[string]inte return nil } - if err := tp.templateService.FromFile(absPath); err != nil { + if err := tp.template.FromFile(absPath); err != nil { return err } // render the template buff := bytes.NewBufferString("") - if err := tp.templateService.Execute(buff, withVariables); err != nil { - if tp.templateService.IsMissingKeyError(err) { + if err := tp.template.Execute(buff, withVariables); err != nil { + if tp.template.IsMissingKeyError(err) { logger.WithFields(log.Fields{"path": relPath}).Errorf("missing variable while rendering file: %s", relPath) } return err diff --git a/internal/processor/type.go b/internal/processor/type.go index 8a58bb9..8c2734b 100644 --- a/internal/processor/type.go +++ b/internal/processor/type.go @@ -3,7 +3,7 @@ package processor import ( "github.com/apex/log" "github.com/gchiesa/ska/internal/multipart" - "github.com/gchiesa/ska/internal/templateservice" + "github.com/gchiesa/ska/internal/templateprovider" ) type FileTreeProcessor struct { @@ -13,6 +13,6 @@ type FileTreeProcessor struct { destinationIgnorePaths []string workingDir string multiparts []*multipart.Multipart - templateService *templateservice.SprigTemplate + template templateprovider.TemplateService log *log.Entry } diff --git a/internal/templateprovider/jinjatemplate.go b/internal/templateprovider/jinjatemplate.go new file mode 100644 index 0000000..8a84fe9 --- /dev/null +++ b/internal/templateprovider/jinjatemplate.go @@ -0,0 +1,61 @@ +package templateprovider + +import ( + "github.com/flosch/pongo2/v6" + "github.com/palantir/stacktrace" + "io" +) + +type JinjaTemplate struct { + templateContent string + variables map[string]interface{} + pongo2Template *pongo2.Template +} + +func NewJinjaTemplate(_ string) *JinjaTemplate { + return &JinjaTemplate{} +} + +func (t *JinjaTemplate) FromString(templateContent string) error { + t.templateContent = templateContent + tpl, err := pongo2.FromString(t.templateContent) + if err != nil { + return stacktrace.Propagate(err, "failed to parse template") + } + t.pongo2Template = tpl + return nil +} + +func (t *JinjaTemplate) FromFile(templateFilePath string) error { + tpl, err := pongo2.FromFile(templateFilePath) + if err != nil { + return err + } + t.pongo2Template = tpl + return nil +} + +func (t *JinjaTemplate) Execute(fp io.Writer, withVariables map[string]interface{}) error { + t.variables = withVariables + var context = make(pongo2.Context) + for k, v := range t.variables { + context[k] = v + } + renderedContent, err := t.pongo2Template.Execute(context) + if err != nil { + return err + } + + _, err = fp.Write([]byte(renderedContent)) + if err != nil { + return err + } + return nil +} + +func (t *JinjaTemplate) WithErrorOnMissingKey(_ bool) { +} + +func (t *JinjaTemplate) IsMissingKeyError(err error) bool { + return err.Error() == "TokenError" +} diff --git a/internal/templateservice/template.go b/internal/templateprovider/sprigtemplate.go similarity index 93% rename from internal/templateservice/template.go rename to internal/templateprovider/sprigtemplate.go index 3417c34..223593f 100644 --- a/internal/templateservice/template.go +++ b/internal/templateprovider/sprigtemplate.go @@ -1,4 +1,4 @@ -package templateservice +package templateprovider import ( sprig "github.com/go-task/slim-sprig" @@ -24,13 +24,12 @@ func NewSprigTemplate(name string) *SprigTemplate { } } -func (t *SprigTemplate) WithErrorOnMissingKey(state bool) *SprigTemplate { +func (t *SprigTemplate) WithErrorOnMissingKey(state bool) { if state { t.textTemplate.Option("missingkey=error") } else { t.textTemplate.Option("missingkey=default") } - return t } func (t *SprigTemplate) FromString(templateContent string) error { diff --git a/internal/templateprovider/templateservice.go b/internal/templateprovider/templateservice.go new file mode 100644 index 0000000..0ae1e51 --- /dev/null +++ b/internal/templateprovider/templateservice.go @@ -0,0 +1,23 @@ +package templateprovider + +func ByType(templateType TemplateType, name string) TemplateService { + var ts TemplateService + switch templateType { + case SprigTemplateType: + ts = NewSprigTemplate(name) + case JinjaTemplateType: + ts = NewJinjaTemplate(name) + } + return ts +} + +func GetTypeFromString(templateType string) TemplateType { + switch templateType { + case "sprig": + return SprigTemplateType + case "jinja": + return JinjaTemplateType + default: + return -1 + } +} diff --git a/internal/templateprovider/type.go b/internal/templateprovider/type.go new file mode 100644 index 0000000..78497e1 --- /dev/null +++ b/internal/templateprovider/type.go @@ -0,0 +1,24 @@ +package templateprovider + +import ( + "io" +) + +type TemplateService interface { + // FromFile Load template from file + FromFile(path string) error + // FromString Load template from string + FromString(templateContent string) error + // Execute Execute the template + Execute(fp io.Writer, withVariables map[string]interface{}) error + // WithErrorOnMissingKey Set error on missing key + WithErrorOnMissingKey(key bool) + IsMissingKeyError(err error) bool +} + +type TemplateType int + +const ( + SprigTemplateType TemplateType = iota + JinjaTemplateType +) diff --git a/internal/templateservice/type.go b/internal/templateservice/type.go deleted file mode 100644 index b7ce9fc..0000000 --- a/internal/templateservice/type.go +++ /dev/null @@ -1,11 +0,0 @@ -package templateservice - -import ( - "io" -) - -type TemplateService interface { - FromFile(path string) error - FromString(templateContent string) error - Execute(fp io.Writer, withVariables map[string]interface{}) error -} diff --git a/pkg/skaffolder/create.go b/pkg/skaffolder/create.go index 1b6c78d..18349d0 100644 --- a/pkg/skaffolder/create.go +++ b/pkg/skaffolder/create.go @@ -6,6 +6,7 @@ import ( "github.com/gchiesa/ska/internal/configuration" "github.com/gchiesa/ska/internal/contentprovider" "github.com/gchiesa/ska/internal/processor" + "github.com/gchiesa/ska/internal/templateprovider" "github.com/gchiesa/ska/internal/tui" ) @@ -19,6 +20,7 @@ type SkaCreate struct { type SkaOptions struct { NonInteractive bool + Engine templateprovider.TemplateType // jinja or sprig } func NewSkaCreate(templateURI, destinationPath string, variables map[string]string, options SkaOptions) *SkaCreate { @@ -58,8 +60,19 @@ func (s *SkaCreate) Create() error { return err } + // template engine + var templateService templateprovider.TemplateService + switch s.Options.Engine { + case templateprovider.SprigTemplateType: + templateService = templateprovider.NewSprigTemplate(s.TemplateURI) + case templateprovider.JinjaTemplateType: + templateService = templateprovider.NewJinjaTemplate(s.TemplateURI) + default: + return fmt.Errorf("unknown template engine") + } + fileTreeProcessor := processor.NewFileTreeProcessor(blueprintProvider.WorkingDir(), s.DestinationPath, - processor.WithErrorOnMissingKey(true), + processor.WithTemplateService(templateService), processor.WithSourceIgnorePaths(upstreamConfig.GetIgnorePaths()), processor.WithDestinationIgnorePaths(localConfig.GetIgnorePaths())) diff --git a/pkg/skaffolder/update.go b/pkg/skaffolder/update.go index 12c63c9..bbf10b9 100644 --- a/pkg/skaffolder/update.go +++ b/pkg/skaffolder/update.go @@ -6,6 +6,7 @@ import ( "github.com/gchiesa/ska/internal/configuration" "github.com/gchiesa/ska/internal/contentprovider" "github.com/gchiesa/ska/internal/processor" + "github.com/gchiesa/ska/internal/templateprovider" "github.com/gchiesa/ska/internal/tui" ) @@ -56,8 +57,19 @@ func (s *SkaUpdate) Update() error { return err } + // template engine + var templateService templateprovider.TemplateService + switch s.Options.Engine { + case templateprovider.SprigTemplateType: + templateService = templateprovider.NewSprigTemplate(s.BaseURI) + case templateprovider.JinjaTemplateType: + templateService = templateprovider.NewJinjaTemplate(s.BaseURI) + default: + return fmt.Errorf("unknown template engine") + } + fileTreeProcessor := processor.NewFileTreeProcessor(blueprintProvider.WorkingDir(), s.BaseURI, - processor.WithErrorOnMissingKey(true), + processor.WithTemplateService(templateService), processor.WithSourceIgnorePaths(upstreamConfig.GetIgnorePaths()), processor.WithDestinationIgnorePaths(localConfig.GetIgnorePaths()))