From c84503fe283ed02368db0dbc236f2f0a866ff422 Mon Sep 17 00:00:00 2001 From: Mukesh Kumar Chaudhary Date: Sun, 14 Apr 2024 10:18:57 +0545 Subject: [PATCH 1/2] feat: add feature to generate the fx config by reading the comment --- cmd/generate_fx_module.go | 23 +++++++ cmd/run_project.go | 1 + pkg/utility/fx_generator.go | 134 ++++++++++++++++++++++++++++++++++++ 3 files changed, 158 insertions(+) create mode 100644 cmd/generate_fx_module.go create mode 100644 pkg/utility/fx_generator.go 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/run_project.go b/cmd/run_project.go index e5a88cf..524bbd2 100644 --- a/cmd/run_project.go +++ b/cmd/run_project.go @@ -28,4 +28,5 @@ func init() { 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..87c20e5 --- /dev/null +++ b/pkg/utility/fx_generator.go @@ -0,0 +1,134 @@ +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. + +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) + } + } +} From 9f84b1581879c86f0b95c5d9cc6665495b3f309c Mon Sep 17 00:00:00 2001 From: Mukesh Kumar Chaudhary Date: Sun, 14 Apr 2024 10:27:04 +0545 Subject: [PATCH 2/2] refactor: add message after code run --- pkg/utility/fx_generator.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/utility/fx_generator.go b/pkg/utility/fx_generator.go index 87c20e5..4a07821 100644 --- a/pkg/utility/fx_generator.go +++ b/pkg/utility/fx_generator.go @@ -98,6 +98,8 @@ 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}} @@ -131,4 +133,6 @@ func generateFile(dependencies map[string][]string) { panic(err) } } + count := len(dependencies) + fmt.Printf("Generated %d module files\n", count) }