From afee95933a55b5f3335f3c89434c120a229f8fa3 Mon Sep 17 00:00:00 2001 From: Freeman Liu Date: Sun, 22 Sep 2024 02:15:08 +0800 Subject: [PATCH] Rename templatesDir to templates, support templates from command line (#7) --- pkg/cmd/generate/generate.go | 21 ++++++++-- pkg/cmd/init/init.go | 2 +- pkg/cmd/introspect/introspect.go | 5 ++- pkg/model/config.go | 8 ++-- pkg/util/util.go | 71 +++++++++++++++++++++++++++----- 5 files changed, 88 insertions(+), 19 deletions(-) diff --git a/pkg/cmd/generate/generate.go b/pkg/cmd/generate/generate.go index 25e33d1..723783c 100644 --- a/pkg/cmd/generate/generate.go +++ b/pkg/cmd/generate/generate.go @@ -1,6 +1,7 @@ package generate import ( + "errors" "github.com/DanielLiu1123/gencoder/pkg/handlebars" "github.com/DanielLiu1123/gencoder/pkg/jsruntime" "github.com/DanielLiu1123/gencoder/pkg/model" @@ -16,8 +17,9 @@ import ( type generateOptions struct { config string - commandLineProperties map[string]string // properties passed from command line importHelper string + commandLineProperties map[string]string // properties passed from command line + commandLineTemplates string // templates passed from command line } func NewCmdGenerate(globalOptions *model.GlobalOptions) *cobra.Command { @@ -38,6 +40,9 @@ func NewCmdGenerate(globalOptions *model.GlobalOptions) *cobra.Command { # Generate code with custom import helper JavaScript file $ gencoder generate -f myconfig.yaml --import-helper helpers.js + + # Generate boilerplate code from URL with custom properties + $ gencoder generate --templates "https://github.com/DanielLiu1123/gencoder/tree/main/templates" --properties="package=com.example,author=Freeman" `, PreRun: func(cmd *cobra.Command, args []string) { if len(args) > 0 { @@ -65,6 +70,7 @@ 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 or URL") return c } @@ -98,9 +104,18 @@ func registerCustomHelpers(location string) { func run(_ *cobra.Command, _ []string, opt *generateOptions, _ *model.GlobalOptions) { - cfg := util.ReadConfig(opt.config) + cfg, err := util.ReadConfig(opt.config) + isNotExist := errors.Is(err, os.ErrNotExist) + if isNotExist { + // if is not found, try to read from command line templates + if opt.commandLineTemplates == "" { + log.Fatal(err) + } + } else { + log.Fatal(err) + } - templates, err := util.LoadTemplates(cfg) + templates, err := util.LoadTemplates(cfg, opt.commandLineTemplates) if err != nil { log.Fatal(err) } diff --git a/pkg/cmd/init/init.go b/pkg/cmd/init/init.go index b3be9ec..0f4638f 100644 --- a/pkg/cmd/init/init.go +++ b/pkg/cmd/init/init.go @@ -32,7 +32,7 @@ func NewCmdInit(globalOptions *model.GlobalOptions) *cobra.Command { func run(_ *cobra.Command, _ []string, _ *initOptions, _ *model.GlobalOptions) { // init gencoder.yaml - gencoderYaml := `templatesDir: templates + gencoderYaml := `templates: templates databases: - dsn: 'mysql://root:root@localhost:3306/testdb' tables: diff --git a/pkg/cmd/introspect/introspect.go b/pkg/cmd/introspect/introspect.go index 4246345..77d47c6 100644 --- a/pkg/cmd/introspect/introspect.go +++ b/pkg/cmd/introspect/introspect.go @@ -53,7 +53,10 @@ func NewCmdIntrospect(globalOptions *model.GlobalOptions) *cobra.Command { func run(_ *cobra.Command, _ []string, opt *introspectOptions, _ *model.GlobalOptions) { renderContextsFunc := sync.OnceValue(func() []*model.RenderContext { - cfg := util.ReadConfig(opt.config) + cfg, err := util.ReadConfig(opt.config) + if err != nil { + log.Fatalf("failed to read config: %v", err) + } return util.CollectRenderContexts(cfg, nil) }) diff --git a/pkg/model/config.go b/pkg/model/config.go index 6b91e75..2a3754a 100644 --- a/pkg/model/config.go +++ b/pkg/model/config.go @@ -1,7 +1,7 @@ package model type Config struct { - TemplatesDir string `json:"templatesDir" yaml:"templatesDir"` + Templates string `json:"templates" yaml:"templates"` // Can be a directory or URL OutputMarker string `json:"outputMarker" yaml:"outputMarker"` BlockMarker BlockMarker `json:"blockMarker" yaml:"blockMarker"` Databases []*DatabaseConfig `json:"databases" yaml:"databases"` @@ -28,11 +28,11 @@ type BlockMarker struct { End string `json:"end" yaml:"end"` } -func (c Config) GetTemplatesDir() string { - if c.TemplatesDir == "" { +func (c Config) GetTemplates() string { + if c.Templates == "" { return "templates" } - return c.TemplatesDir + return c.Templates } func (c Config) GetOutputMarker() string { diff --git a/pkg/util/util.go b/pkg/util/util.go index d3311a8..3518efd 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -4,6 +4,7 @@ import ( "bufio" "context" "database/sql" + "fmt" "github.com/DanielLiu1123/gencoder/pkg/db" "github.com/DanielLiu1123/gencoder/pkg/handlebars" "github.com/DanielLiu1123/gencoder/pkg/model" @@ -12,32 +13,82 @@ import ( "io/fs" "log" "os" + "os/exec" "path/filepath" + "regexp" "strings" "sync" ) // ReadConfig reads the configuration file from the given path -func ReadConfig(configPath string) *model.Config { +func ReadConfig(configPath string) (*model.Config, error) { + var cfg model.Config file, err := os.ReadFile(configPath) if err != nil { - log.Fatal(err) + return &cfg, err } - - var cfg model.Config err = yaml.Unmarshal(file, &cfg) if err != nil { - log.Fatal(err) + return &cfg, err } - - return &cfg + return &cfg, nil } -// LoadTemplates loads all templates from the given configuration -func LoadTemplates(cfg *model.Config) ([]*model.Tpl, error) { +// LoadTemplates loads all templates from the given configuration and command line templates +func LoadTemplates(cfg *model.Config, commandLineTemplates string) ([]*model.Tpl, error) { + + template := cfg.GetTemplates() + if commandLineTemplates != "" { + template = commandLineTemplates + } + var templates []*model.Tpl - err := filepath.WalkDir(cfg.GetTemplatesDir(), func(path string, d fs.DirEntry, err error) error { + // If url is provided, download templates + isGitUrl, err := regexp.Match(`^.*(github\.com)/.*`, []byte(template)) + if err != nil { + return nil, err + } + + if isGitUrl { + // Parse GitHub URL to extract the repo and branch (if provided) + re := regexp.MustCompile(`github\.com/([^/]+)/([^/]+)(/tree/([^/]+)/(.*))?`) + matches := re.FindStringSubmatch(template) + if matches == nil { + return nil, fmt.Errorf("invalid GitHub URL format") + } + + owner := matches[1] + repo := matches[2] + branch := "main" + if matches[4] != "" { + branch = matches[4] + } + dirInRepo := matches[5] + + tmpDir, err := os.MkdirTemp("", "templates") + if err != nil { + return nil, fmt.Errorf("failed to create temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + cloneUrl := fmt.Sprintf("https://github.com/%s/%s.git", owner, repo) + cmd := exec.Command("git", "clone", "--branch", branch, "--depth", "1", cloneUrl, tmpDir) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return nil, fmt.Errorf("failed to clone repository: %v", err) + } + + // If a directory within the repo is specified, update the path + if dirInRepo != "" { + template = filepath.Join(tmpDir, dirInRepo) + } else { + template = tmpDir + } + } + + err = filepath.WalkDir(template, func(path string, d fs.DirEntry, err error) error { if err != nil { return err }