diff --git a/cmd/create_project.go b/cmd/create_project.go index 9e3f9ac..bc9db94 100644 --- a/cmd/create_project.go +++ b/cmd/create_project.go @@ -19,6 +19,12 @@ var newProjectCmd = &cobra.Command{ Run: createProject, } +func setupFlagsForNewProject(cmd *cobra.Command) { + cmd.Flags().StringP("mod", "m", "", "module name") + cmd.Flags().StringP("dir", "d", "", "target directory") + cmd.Flags().StringP("version", "v", "", "version support: Default: 1.20") +} + func createProject(cmd *cobra.Command, args []string) { var projectName string var projectModuleName string @@ -79,7 +85,7 @@ func createProject(cmd *cobra.Command, args []string) { return } if projectModuleName == "" { - color.Redln("Error: module name is required") + color.Redln("Error: golang module name is required") return } diff --git a/cmd/generate_fx_module.go b/cmd/generate_fx_module.go new file mode 100644 index 0000000..ec119c3 --- /dev/null +++ b/cmd/generate_fx_module.go @@ -0,0 +1,23 @@ +package cmd + +import ( + "github.com/mukezhz/geng/pkg/utility" + "github.com/spf13/cobra" +) + +var generateFxCmd = &cobra.Command{ + Use: "fx", + Short: "Generate the fx configuration file for the project", + Long: ` +Generate a module by reading comments from the source code. +Example: + geng fx + + `, + Args: cobra.MaximumNArgs(2), + Run: generateFx, +} + +func generateFx(_ *cobra.Command, _ []string) { + utility.GenerateFxModule() +} diff --git a/cmd/new_module.go b/cmd/new_module.go index 7c4057d..cc43917 100644 --- a/cmd/new_module.go +++ b/cmd/new_module.go @@ -7,19 +7,33 @@ import ( "github.com/gookit/color" "github.com/mukezhz/geng/pkg/constant" + "github.com/mukezhz/geng/pkg/model" "github.com/mukezhz/geng/pkg/terminal" "github.com/mukezhz/geng/pkg/utility" "github.com/spf13/cobra" ) var newModuleCmd = &cobra.Command{ - Use: "gen module [name]", + Use: "gen mod [name]", Short: "Create a new domain", - Args: cobra.MaximumNArgs(2), - Run: createModule, + Long: ` +Create a new module|service|middleware in the project. +Example: + geng gen mod [name] + geng gen srv [name] + geng gen mid [name] + +Default: + geng gen -> geng gen mod + `, + Args: cobra.MaximumNArgs(2), + Run: generate, } -func createModule(_ *cobra.Command, args []string) { +func generate(_ *cobra.Command, args []string) { + if len(args) == 0 { + args = append(args, "module") + } projectModule, err := utility.GetModuleNameFromGoModFile() if err != nil { fmt.Println("Error finding Module name from go.mod:", err) @@ -35,6 +49,12 @@ func createModule(_ *cobra.Command, args []string) { fmt.Println("Error finding Git root:", err) return } + // Define the directory structure + generateModule(projectPath, args, projectModule) + +} + +func generateModule(projectPath string, args []string, projectModule model.GoMod) { mainModulePath := filepath.Join(projectPath, "domain", "module.go") var moduleName string if len(args) == 1 { @@ -50,7 +70,7 @@ func createModule(_ *cobra.Command, args []string) { } if q.Input.Exited() { color.Redln("exited without completing...") - return + } } } else { @@ -62,11 +82,10 @@ func createModule(_ *cobra.Command, args []string) { } data := utility.GetModuleDataFromModuleName(moduleName, projectModule.Module, projectModule.GoVersion) - // Define the directory structure targetRoot := filepath.Join(".", "domain", data.PackageName) templatePath := filepath.Join(".", "templates", "wesionary", "module") - err = utility.GenerateFiles(templatesFS, templatePath, targetRoot, data) + err := utility.GenerateFiles(templatesFS, templatePath, targetRoot, data) if err != nil { color.Redln("Error: generate file", err) return @@ -76,4 +95,5 @@ func createModule(_ *cobra.Command, args []string) { utility.WriteContentToPath(mainModulePath, updatedCode) utility.PrintColorizeModuleDetail(data) + } diff --git a/cmd/root.go b/cmd/root.go index 4b778ba..5737dd1 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -7,16 +7,10 @@ import ( ) var ( - // Used for flags. - cfgFile string - userLicense string - rootCmd = &cobra.Command{ - Use: "cobra-cli", + Use: "geng", Short: "A generator for Cobra based Applications", - Long: `Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, + Long: `geng is a CLI library for Go that empowers applications.`, } templatesFS embed.FS ) diff --git a/cmd/run_project.go b/cmd/run_project.go index d17537f..524bbd2 100644 --- a/cmd/run_project.go +++ b/cmd/run_project.go @@ -22,12 +22,11 @@ func runProject(_ *cobra.Command, args []string) { } func init() { - newProjectCmd.Flags().StringP("mod", "m", "", "features name") - newProjectCmd.Flags().StringP("dir", "d", "", "target directory") - newProjectCmd.Flags().StringP("version", "v", "", "version support") + setupFlagsForNewProject(newProjectCmd) rootCmd.AddCommand(newModuleCmd) rootCmd.AddCommand(newProjectCmd) rootCmd.AddCommand(runProjectCmd) rootCmd.AddCommand(addInfrastructureCmd) rootCmd.AddCommand(addServiceCmd) + rootCmd.AddCommand(generateFxCmd) } diff --git a/pkg/utility/fx_generator.go b/pkg/utility/fx_generator.go new file mode 100644 index 0000000..4a07821 --- /dev/null +++ b/pkg/utility/fx_generator.go @@ -0,0 +1,138 @@ +package utility + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "os" + "path/filepath" + "strings" + "text/template" + + "golang.org/x/text/cases" + "golang.org/x/text/language" +) + +func GenerateFxModule() { + fset := token.NewFileSet() + root := "./" + moduleMap := make(map[string][]string) + + err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + if !strings.HasSuffix(info.Name(), ".go") { + return nil + } + + file, err := parser.ParseFile(fset, path, nil, parser.ParseComments) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to parse file %s: %v\n", path, err) + return err + } + + processedFile := processFile(file) + if len(processedFile) != 0 { + moduleMap[filepath.Dir(path)] = append(moduleMap[filepath.Dir(path)], processedFile...) + } + + return nil + }) + generateFile(moduleMap) + + if err != nil { + fmt.Fprintf(os.Stderr, "Error walking through the directory: %v\n", err) + os.Exit(1) + } +} + +func processFile(file *ast.File) []string { + providers := make([]string, 0) + ast.Inspect(file, func(n ast.Node) bool { + fn, ok := n.(*ast.FuncDecl) + if !ok || fn.Doc == nil { + return true + } + + var provide bool + var asInterface string + + for _, comment := range fn.Doc.List { + if strings.Contains(comment.Text, "@fxProvide") { + provide = true + } + if strings.HasPrefix(comment.Text, "// @fxAs ") { + parts := strings.SplitN(comment.Text, " ", 3) + if len(parts) > 2 { + asInterface = strings.TrimSpace(parts[2]) + } + } + } + + if provide { + if asInterface != "" { + providers = append(providers, fmt.Sprintf("fx.Provide(fx.Annotate(%s, fx.As(new(%s)))),", fn.Name.Name, asInterface)) + } else { + providers = append(providers, fmt.Sprintf("fx.Provide(%s),", fn.Name.Name)) + } + } + return true + }) + return providers +} + +type Module struct { + PackageName string + ModuleName string + Name string + Providers []string +} + +const templateText = `package {{.PackageName}} + +import "go.uber.org/fx" + +// This file is generated by geng. DO NOT EDIT. +// File generated at: {{.Timestamp}} +// File will be overwritten when running geng again. + +var {{.ModuleName}} = fx.Module("{{.Name}}", +{{- range .Providers}} + {{.}} +{{- end}} +) +` + +func generateFile(dependencies map[string][]string) { + tmpl, err := template.New("module").Parse(templateText) + if err != nil { + panic(err) + } + + for k, v := range dependencies { + module := Module{ + PackageName: filepath.Base(k), + Name: filepath.Base(k), + ModuleName: cases.Title(language.English).String(filepath.Base(k)), + Providers: v, + } + filePath := filepath.Join(k, "module.go") + file, err := os.Create(filePath) + if err != nil { + panic(err) + } + defer file.Close() + + err = tmpl.Execute(file, module) + if err != nil { + panic(err) + } + } + count := len(dependencies) + fmt.Printf("Generated %d module files\n", count) +}