Skip to content

Commit

Permalink
Support --include-non-tpl and --output (#14)
Browse files Browse the repository at this point in the history
* Add --include-non-tpl

* Add --output

* Refactor
  • Loading branch information
DanielLiu1123 authored Oct 1, 2024
1 parent 1375d03 commit a00dc2c
Show file tree
Hide file tree
Showing 8 changed files with 220 additions and 95 deletions.
9 changes: 3 additions & 6 deletions cmd/handlebarsjs/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ package main

import (
"fmt"
"github.com/DanielLiu1123/gencoder/pkg/util"
"io"
"log"
"net/http"
"os"
"path/filepath"
"strings"
)

Expand Down Expand Up @@ -62,11 +62,8 @@ func genHandlebarJS() {
}

func generateFile(filePath, content string) {
if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil {
log.Fatal(err)
}
err := os.WriteFile(filePath, []byte(content), 0644)
err := util.WriteFile(filePath, []byte(content))
if err != nil {
log.Fatal(err)
log.Fatal("Error writing file:", err)
}
}
106 changes: 67 additions & 39 deletions pkg/cmd/generate/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@ import (
)

type generateOptions struct {
config string
importHelper string
commandLineProperties map[string]string
commandLineTemplates string
config string
importHelper string
includeNonTpl bool

// Override config file gencoder.yaml
Templates string
Properties map[string]string // Add properties, will override properties in config file
output string
}

func NewCmdGenerate(globalOptions *model.GlobalOptions) *cobra.Command {
Expand All @@ -45,7 +49,7 @@ func NewCmdGenerate(globalOptions *model.GlobalOptions) *cobra.Command {
`,
PreRun: func(cmd *cobra.Command, args []string) {
validateArgs(args)
opt.commandLineProperties = parseProperties(props)
opt.Properties = parseProperties(props)
if opt.importHelper != "" {
registerCustomHelpers(opt.importHelper)
}
Expand All @@ -58,7 +62,9 @@ func NewCmdGenerate(globalOptions *model.GlobalOptions) *cobra.Command {
c.Flags().StringVarP(&opt.config, "config", "f", globalOptions.Config, "Config file to use")
c.Flags().StringVarP(&opt.importHelper, "import-helper", "i", "", "Import helper JavaScript file, can be URL ([http|https]://...) or file path")
c.Flags().StringSliceVarP(&props, "properties", "p", []string{}, "Add properties, will override properties in config file, --properties=\"k1=v1\" --properties=\"k2=v2,k3=v3\"")
c.Flags().StringVarP(&opt.commandLineTemplates, "templates", "t", "", "Override templates directory, can be path or URL, e.g. https://github.com/DanielLiu1123/gencoder/tree/main/templates")
c.Flags().StringVarP(&opt.Templates, "templates", "t", "", "Override templates directory, can be path or URL, e.g. https://github.com/DanielLiu1123/gencoder/tree/main/templates")
c.Flags().BoolVarP(&opt.includeNonTpl, "include-non-tpl", "a", false, "Include non-template files in the 'templates' option")
c.Flags().StringVarP(&opt.output, "output", "o", "", "Output directory for generated files, default is the current directory")

return c
}
Expand Down Expand Up @@ -121,51 +127,76 @@ func run(_ *cobra.Command, _ []string, opt *generateOptions, _ *model.GlobalOpti
log.Fatal(err)
}

templates, err := util.LoadTemplates(cfg, opt.commandLineTemplates)
mergeCmdOptionsToConfig(cfg, opt)

files, err := util.LoadFiles(cfg)
if err != nil {
log.Fatal(err)
}

registerPartialTemplates(templates)
registerPartials(files)

renderContexts := util.CollectRenderContexts(cfg, opt.commandLineProperties)
renderContexts := util.CollectRenderContexts(cfg, opt.Properties)

if opt.includeNonTpl {
for _, f := range files {
generateForNormalFiles(cfg, f)
}
}

if len(renderContexts) > 0 {
generateForAllContexts(cfg, templates, renderContexts)
generateForAllContexts(cfg, files, renderContexts)
} else {
generateFromBoilerplate(cfg, templates, opt.commandLineProperties)
properties := mergeProperties(cfg.Properties, opt.Properties)
renderContext := &model.RenderContext{Properties: properties, Config: cfg}
for _, t := range files {
generateForTemplateFiles(cfg, t, renderContext)
}
}
}

func mergeCmdOptionsToConfig(cfg *model.Config, opt *generateOptions) {
if opt.output != "" {
cfg.Output = opt.output
}
if opt.Templates != "" {
cfg.Templates = opt.Templates
}
}

func loadConfig(opt *generateOptions) (*model.Config, error) {
cfg, err := util.ReadConfig(opt.config)
if (err != nil && !errors.Is(err, os.ErrNotExist)) || (errors.Is(err, os.ErrNotExist) && opt.commandLineTemplates == "") {
if (err != nil && !errors.Is(err, os.ErrNotExist)) || (errors.Is(err, os.ErrNotExist) && opt.Templates == "") {
return nil, err
}
return cfg, nil
}

func registerPartialTemplates(templates []*model.Tpl) {
for _, t := range templates {
if t.GeneratedFileName == "" {
handlebars.RegisterPartial(t.TemplateName, t.Source)
func registerPartials(files []*model.File) {
for _, f := range files {
if f.Type == model.FileTypePartial {
handlebars.RegisterPartial(f.Name, string(f.Content))
}
}
}

func generateForAllContexts(cfg *model.Config, templates []*model.Tpl, renderContexts []*model.RenderContext) {
func generateForAllContexts(cfg *model.Config, files []*model.File, renderContexts []*model.RenderContext) {
for _, ctx := range renderContexts {
for _, t := range templates {
generate(cfg, t, ctx)
for _, f := range files {
generateForTemplateFiles(cfg, f, ctx)
}
}
}

func generateFromBoilerplate(cfg *model.Config, templates []*model.Tpl, commandLineProperties map[string]string) {
properties := mergeProperties(cfg.Properties, commandLineProperties)
renderContext := &model.RenderContext{Properties: properties, Config: cfg}
for _, t := range templates {
generate(cfg, t, renderContext)
func generateForNormalFiles(cfg *model.Config, f *model.File) {
if f.Type != model.FileTypeNormal {
return
}

out := filepath.Join(cfg.Output, f.RelativePath)

if _, err := os.Stat(out); err != nil {
createNewFile(out, f.Content)
}
}

Expand All @@ -180,19 +211,20 @@ func mergeProperties(configProps map[string]string, cmdLineProps map[string]stri
return merged
}

func generate(cfg *model.Config, tpl *model.Tpl, ctx *model.RenderContext) {
if tpl.GeneratedFileName == "" { // partial template
func generateForTemplateFiles(cfg *model.Config, tpl *model.File, ctx *model.RenderContext) {
if tpl.Type != model.FileTypeTemplate {
return
}

context := util.ToMap(ctx)
newContent := handlebars.Render(tpl.Template, context)
fileName := getFileName(tpl.GeneratedFileName, context)
fileName := getFileName(tpl.Output, context)
out := filepath.Join(cfg.Output, fileName)

if _, err := os.Stat(fileName); err == nil {
updateExistingFile(cfg, fileName, newContent)
if _, err := os.Stat(out); err == nil {
updateExistingFile(cfg, out, newContent)
} else {
createNewFile(fileName, newContent)
createNewFile(out, []byte(newContent))
}
}

Expand All @@ -203,19 +235,15 @@ func updateExistingFile(cfg *model.Config, fileName, newContent string) {
}

realContent := replaceBlocks(cfg, string(oldContent), newContent)
writeFile(fileName, realContent)
}

func createNewFile(fileName, content string) {
dir := filepath.Dir(fileName)
if err := os.MkdirAll(dir, 0755); err != nil {
err = util.WriteFile(fileName, []byte(realContent))
if err != nil {
log.Fatal(err)
}
writeFile(fileName, content)
}

func writeFile(fileName, content string) {
if err := os.WriteFile(fileName, []byte(content), 0644); err != nil {
func createNewFile(fileName string, content []byte) {
err := util.WriteFile(fileName, content)
if err != nil {
log.Fatal(err)
}
}
Expand Down
65 changes: 65 additions & 0 deletions pkg/cmd/generate/generate_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package generate

import (
"github.com/stretchr/testify/require"
"os"
"path/filepath"
"testing"

"github.com/DanielLiu1123/gencoder/pkg/model"
Expand Down Expand Up @@ -217,3 +220,65 @@ new content 2
})
}
}

func TestNewCmdGenerate_whenUsingIncludeNonTpl_thenShouldGenerateNonTemplateFiles(t *testing.T) {

// Create template directory
tplDir, err := os.MkdirTemp("", "tpl")
require.NoError(t, err)
defer os.RemoveAll(tplDir)

_ = os.Chdir(tplDir)

// Create non-template files
createNewFile(filepath.Join(tplDir, "templates/non-template1.txt"), []byte("This is a non-template file"))
createNewFile(filepath.Join(tplDir, "templates/foo/non-template2.txt"), []byte("This is a non-template file"))
// Create partial file
createNewFile(filepath.Join(tplDir, "templates/header.txt.hbs"), []byte("This is a header"))
// Create template file
createNewFile(filepath.Join(tplDir, "templates/test1.text.hbs"), []byte(`@gencoder.generated: test1.txt
{{> header.txt.hbs}}
Hello, {{properties.name}}!`))
createNewFile(filepath.Join(tplDir, "templates/test2.text.hbs"), []byte(`@gencoder.generated: foo/test2.txt
{{> header.txt.hbs}}
Hello, {{properties.name}}!`))

// Create generated directory
generatedDir, err := os.MkdirTemp("", "generated")
require.NoError(t, err)
defer os.RemoveAll(generatedDir)

_ = os.Chdir(generatedDir)

cmd := NewCmdGenerate(&model.GlobalOptions{})

cmd.SetArgs([]string{"--templates", filepath.Join(tplDir, "templates"), "--include-non-tpl", "--properties", "name=World"})

err = cmd.Execute()
require.NoError(t, err)

// Verify non-template files are generated
_, err = os.Stat(filepath.Join(generatedDir, "non-template1.txt"))
assert.NoError(t, err)
_, err = os.Stat(filepath.Join(generatedDir, "foo/non-template2.txt")) // keep directory structure
assert.NoError(t, err)

// Verify partial files do not exist
_, err = os.Stat(filepath.Join(generatedDir, "header.txt.hbs"))
assert.Error(t, err)

// Verify template files are generated
content, err := os.ReadFile(filepath.Join(generatedDir, "test1.txt"))
assert.NoError(t, err)
assert.Equal(t, `@gencoder.generated: test1.txt
This is a header
Hello, World!`, string(content))

content, err = os.ReadFile(filepath.Join(generatedDir, "foo/test2.txt")) // keep directory structure
assert.NoError(t, err)
assert.Equal(t, `@gencoder.generated: foo/test2.txt
This is a header
Hello, World!`, string(content))
}
47 changes: 23 additions & 24 deletions pkg/cmd/init/init.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package init

import (
"github.com/DanielLiu1123/gencoder/pkg/util"
"log"
"os"
"path/filepath"
Expand All @@ -10,33 +11,40 @@ import (
)

type initOptions struct {
output string
}

func NewCmdInit(globalOptions *model.GlobalOptions) *cobra.Command {
opt := &initOptions{}

return &cobra.Command{
c := &cobra.Command{
Use: "init",
Short: "Init basic configuration for gencoder",
Example: ` # Init basic configuration for gencoder
$ gencoder init`,
$ gencoder init
# Init basic configuration in a specific directory
$ gencoder init -o myproject`,
Run: func(cmd *cobra.Command, args []string) {
run(cmd, args, opt, globalOptions)
},
}

c.Flags().StringVarP(&opt.output, "output", "o", "", "Output directory, default to current directory")

return c
}

func run(_ *cobra.Command, _ []string, _ *initOptions, _ *model.GlobalOptions) {
initGencoderYaml()
initTemplatesDir()
initTemplates()
func run(_ *cobra.Command, _ []string, opt *initOptions, _ *model.GlobalOptions) {
initGencoderYaml(opt)
initTemplates(opt)

log.Println("Init success! Please modify the gencoder.yaml and templates to fit your project needs.")
log.Println()
log.Println("Thank you for using gencoder!")
}

func initGencoderYaml() {
func initGencoderYaml(opt *initOptions) {
gencoderYaml := `templates: templates
databases:
- dsn: 'mysql://root:root@localhost:3306/testdb'
Expand All @@ -45,19 +53,10 @@ databases:
properties:
package: 'com.example'
`
writeFileIfNotExists("gencoder.yaml", []byte(gencoderYaml))
writeFileIfNotExists(filepath.Join(opt.output, "gencoder.yaml"), []byte(gencoderYaml))
}

func initTemplatesDir() {
if _, err := os.Stat("templates"); os.IsNotExist(err) {
err := os.Mkdir("templates", 0755)
if err != nil {
log.Fatal(err)
}
}
}

func initTemplates() {
func initTemplates(opt *initOptions) {
entityJava := `/**
* @gencoder.generated: src/main/java/{{_replaceAll properties.package '.' '/'}}/{{_pascalCase table.name}}.java
*/
Expand Down Expand Up @@ -92,8 +91,6 @@ public record {{_pascalCase table.name}} (
}
}
`
writeFileIfNotExists(filepath.Join("templates", "entity.java.hbs"), []byte(entityJava))

javaTypePartial := `{{~#if (_match 'varchar\(\d+\)|char|tinytext|text|mediumtext|longtext' columnType)}}String
{{~else if (_match 'bigint' columnType)}}Long
{{~else if (_match 'int|integer|mediumint' columnType)}}Integer
Expand All @@ -110,14 +107,16 @@ public record {{_pascalCase table.name}} (
{{~else if (_match 'enum.*' columnType)}}String
{{~else}}Object
{{~/if}}`
writeFileIfNotExists(filepath.Join("templates", "java_type.partial.hbs"), []byte(javaTypePartial))

writeFileIfNotExists(filepath.Join(opt.output, "templates", "entity.java.hbs"), []byte(entityJava))
writeFileIfNotExists(filepath.Join(opt.output, "templates", "java_type.partial.hbs"), []byte(javaTypePartial))
}

func writeFileIfNotExists(filename string, data []byte) {
if _, err := os.Stat(filename); os.IsNotExist(err) {
err := os.WriteFile(filename, data, 0644)
if err != nil {
log.Fatal(err)
e := util.WriteFile(filename, data)
if e != nil {
log.Fatal(e)
}
}
}
Loading

0 comments on commit a00dc2c

Please sign in to comment.