This commit is contained in:
cubixle
2021-06-18 21:06:02 +01:00
parent f435f65536
commit f330903f9e
13 changed files with 85 additions and 26 deletions

View File

@@ -1,2 +1,2 @@
build: build:
go build ./cmd/tuugen go build -o tuugen .

46
README.md Normal file
View File

@@ -0,0 +1,46 @@
# Tuugen
Tuugen takes a tuugen.yml and a grpc proto definition to generate common boiler plate logic. Some of the common boiler plate it generates is http routes, grpc service, data model structs and a main file with some default plumbing.
Tuugen adds an interactor package which the http and grpc transport layers use for business logic.
Feel free to pull the repo and play around with the example_project.
## Using Tuugen.
```
go install github.com/cubixle/tuugen
```
And the run `tuugen` in the root dir of your project or wherever you have your tuugen and proto file.
## Example tuugen file
```yaml
project: tuugen_test_project
import_path: github.com/cubixle/tuugen/example_project
proto_file: service.proto
grpc_file: internal/pb/service/service_grpc.pb.go
service_name: Service
data_models:
- name: User
properties:
- name: id
type: varchar
autoinc: true
- name: team_id
type: varchar
- name: name
type: varchar
- name: email
type: varchar
- name: created_at
type: timestamp
- name: updated_at
type: timestamp
- name: Team
properties:
- name: id
type: varchar
- name: name
type: varchar
```

2
app.go
View File

@@ -1,4 +1,4 @@
package tuugen package main
import ( import (
"embed" "embed"

2
cmd.go
View File

@@ -1,4 +1,4 @@
package tuugen package main
import "os/exec" import "os/exec"

View File

@@ -1,4 +1,4 @@
package tuugen package main
import ( import (
"path/filepath" "path/filepath"

2
db.go
View File

@@ -1,4 +1,4 @@
package tuugen package main
import ( import (
"embed" "embed"

View File

@@ -18,7 +18,7 @@ func main() {
} }
s := grpc.NewServer() s := grpc.NewServer()
pb.RegisterServiceServer(s, service.New(store)) pb.RegisterServiceServer(s, service.New())
if err := s.Serve(lis); err != nil { if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err) log.Fatalf("failed to serve: %v", err)
} }

View File

@@ -4,8 +4,6 @@ import (
"embed" "embed"
"io/ioutil" "io/ioutil"
"log" "log"
"github.com/cubixle/tuugen"
) )
//go:embed templates/* //go:embed templates/*
@@ -17,47 +15,47 @@ func main() {
if err != nil { if err != nil {
log.Fatalf("failed to find tuugen.yaml, %v", err) log.Fatalf("failed to find tuugen.yaml, %v", err)
} }
cfg, err := tuugen.YamlToConfig(d) cfg, err := YamlToConfig(d)
if err != nil { if err != nil {
log.Fatalf("failed to load config, %v", err) log.Fatalf("failed to load config, %v", err)
} }
log.Printf("Generating protos from %s\n", cfg.ProtoFile) log.Printf("Generating protos from %s\n", cfg.ProtoFile)
err = tuugen.GenerateProtos(cfg.ProtoFile) err = GenerateProtos(cfg.ProtoFile)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
log.Println("Generating service implementation") log.Println("Generating service implementation")
err = tuugen.GenerateService(FS, cfg) err = GenerateService(FS, cfg)
if err != nil { if err != nil {
log.Fatalf("failed to generate service: %v", err) log.Fatalf("failed to generate service: %v", err)
} }
log.Println("Generating interactor stubs") log.Println("Generating interactor stubs")
err = tuugen.GenerateInteractor(FS, cfg) err = GenerateInteractor(FS, cfg)
if err != nil { if err != nil {
log.Fatalf("failed to generate interactor: %v", err) log.Fatalf("failed to generate interactor: %v", err)
} }
log.Println("Generating data models") log.Println("Generating data models")
if err := tuugen.GenerateDataModels(FS, cfg); err != nil { if err := GenerateDataModels(FS, cfg); err != nil {
log.Fatalf("failed to generate data models: %v", err) log.Fatalf("failed to generate data models: %v", err)
} }
log.Println("Generate main.go") log.Println("Generate main.go")
if err := tuugen.GenerateMain(FS, cfg); err != nil { if err := GenerateMain(FS, cfg); err != nil {
log.Fatalf("failed to generate main file: %v", err) log.Fatalf("failed to generate main file: %v", err)
} }
log.Printf("Running 'go mod init %s' \n", cfg.ImportPath) log.Printf("Running 'go mod init %s' \n", cfg.ImportPath)
if err := tuugen.GoModInit(cfg.ImportPath); err != nil { if err := GoModInit(cfg.ImportPath); err != nil {
log.Fatalf("failed to run 'go mod init': %v", err) log.Fatalf("failed to run 'go mod init': %v", err)
} }
log.Println("Running clean up") log.Println("Running clean up")
if err := tuugen.GoFmt(); err != nil { if err := GoFmt(); err != nil {
log.Fatalf("failed to run gofmt: %v", err) log.Fatalf("failed to run gofmt: %v", err)
} }
if err := tuugen.GoImports(); err != nil { if err := GoImports(); err != nil {
log.Fatalf("failed to run 'goimports': %v", err) log.Fatalf("failed to run 'goimports': %v", err)
} }
log.Println("------") log.Println("------")

View File

@@ -1,4 +1,4 @@
package tuugen package main
import ( import (
"embed" "embed"
@@ -67,20 +67,24 @@ func createFileFromProto(FS embed.FS, cfg Config, templateFile, outputFile strin
if err != nil { if err != nil {
return nil return nil
} }
funcs, err := methodsFromProto(cfg) def, err := parseProto(cfg)
if err != nil { if err != nil {
return err return err
} }
if err := t.Execute(f, funcs); err != nil { // dirty check to see if we need to import the interactors.
if strings.Contains(templateFile, "service") {
def.Imports = append(def.Imports, cfg.ImportPath+"/internal/interactors")
}
if err := t.Execute(f, def); err != nil {
return err return err
} }
return f.Close() return f.Close()
} }
func methodsFromProto(cfg Config) (serviceDef, error) { func parseProto(cfg Config) (serviceDef, error) {
funcs := []serviceFuncDef{}
fset := token.NewFileSet() fset := token.NewFileSet()
f, err := parser.ParseFile(fset, cfg.GRPCFile, nil, 0) f, err := parser.ParseFile(fset, cfg.GRPCFile, nil, 0)
if err != nil { if err != nil {
@@ -90,10 +94,17 @@ func methodsFromProto(cfg Config) (serviceDef, error) {
importStrs := []string{} importStrs := []string{}
servicePath := filepath.Dir(cfg.GRPCFile) servicePath := filepath.Dir(cfg.GRPCFile)
for _, i := range f.Imports { for _, i := range f.Imports {
importStrs = append(importStrs, i.Name.Name) importStrs = append(importStrs, i.Path.Value)
} }
importStrs = append(importStrs, strings.Join([]string{cfg.ImportPath, strings.TrimLeft(servicePath, "/")}, "/"), cfg.ImportPath+"/internal/interactors") importStrs = append(importStrs, strings.Join([]string{cfg.ImportPath, strings.TrimLeft(servicePath, "/")}, "/"))
funcs := parseFuncs(cfg, f)
return serviceDef{Funcs: funcs, Imports: importStrs}, nil
}
func parseFuncs(cfg Config, f *ast.File) []serviceFuncDef {
funcs := []serviceFuncDef{}
for _, m := range f.Scope.Objects { for _, m := range f.Scope.Objects {
if m.Name != cfg.ServiceName+"Server" { if m.Name != cfg.ServiceName+"Server" {
continue continue
@@ -118,7 +129,7 @@ func methodsFromProto(cfg Config) (serviceDef, error) {
funcs = append(funcs, f) funcs = append(funcs, f)
} }
} }
return serviceDef{Funcs: funcs, Imports: importStrs}, nil return funcs
} }
func parseFieldList(fl *ast.FieldList) []string { func parseFieldList(fl *ast.FieldList) []string {

View File

@@ -18,7 +18,7 @@ func main() {
} }
s := grpc.NewServer() s := grpc.NewServer()
pb.Register{{.ServiceName}}Server(s, service.New(store)) pb.Register{{.ServiceName}}Server(s, service.New())
if err := s.Serve(lis); err != nil { if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err) log.Fatalf("failed to serve: %v", err)
} }

View File

@@ -4,6 +4,10 @@ import ({{ range .Imports }}
"{{ . }}"{{ end }} "{{ . }}"{{ end }}
) )
func New() *Service {
return &Service{}
}
type Service struct { type Service struct {
interactor *interactors.Interactor interactor *interactors.Interactor
} }