Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[GoCon2022 Spring] Go言語でコードジェネレーターを作ろう / let's make a code generator by golang

riita10069
April 24, 2022
65

[GoCon2022 Spring] Go言語でコードジェネレーターを作ろう / let's make a code generator by golang

Go Conference 2022 Spring

Go言語のコードジェネレーターを作ってみよう
https://gocon.jp/2022spring/ja/speakers/riita10069/

動画:
後に追加

riita10069

April 24, 2022
Tweet

More Decks by riita10069

Transcript

  1. Ryot a Y a m a d a (Pݴޠͷ ίʔυδΣωϨʔλʔΛ

    ࡞ͬͯΈΑ͏ The Go gopher was designed by Renée French. Illustrations by tottie
  2. e := &entity.User { Name: name , NameKana: name_kana ,

    Email: email , Age: age , Address: address , Birthday: birthday , PhoneNumber: phone_number , LastLogin: last_login , is_premium: is_premium , … } ͜ΕΛ࡞Δͷ͕ ΊΜͲ͍͘͞ʂ
  3. !"" cm d !"" domai n # !"" entit y

    # %"" repositor y !"" infr a # !"" mode l # %"" repositor y %"" usecase ͜Ε΋࡞Δͷ͕ ΊΜͲ͍͘͞ʂ
  4. ϢʔβʔͷCRUD͢ΔΤϯυϙΠϯτ΄͍ͥ͠ message User { string name = 1 ; string

    name_kana = 2 ; int64 age = 3 ; string email = 4 ; … } ͜Ε͚ͩ࡞Ε͹ ׬੒ͳͷͩ ϊʔίʔυͷੈք
  5. ίʔυੜ੒ͷσϝϦοτ • ίʔυδΣωϨʔλʔ͕ؒҧ͍ͬͯΔͱࢮ͵ • ख࡞ۀͰશͯͷ΋ͷΛ௚͢ͷ͸େม • ઃܭͷมߋ͸Ͱ͖ͳ͍ • DDDΛ࠾༻͍ͨ͠ʁNoSQLΛ࢖͍͍ͨʁrocheͰ͸ແཧ •

    ࿮૊Έ͔Β֎ΕΔ࣌͸ࣗಈੜ੒͞ΕͨίʔυΛফ͢ • Web APIͰɺentity, Req, DBͷεΩʔϚ͸ࣅͨΑ͏ͳ΋ͷʹͳΔͱ ͍͏ԾઆΛஔ͍͍ͯΔ
  6. https://github.com/dave/jennifer#readme ੜ੒͍ͨ͠ίʔυΛ؆୯ʹੜ੒͢Δ package mai n import ( "fmt " .

    "github.com/dave/jennifer/jen " ) func main() { f := NewFile("main" ) f.Func().Id("main").Params().Block ( Qual("fmt", "Println").Call(Lit("Hello, world")) , ) fmt.Printf("%#v", f ) } package mai n import "fmt " func main() { fmt.Println("Hello, world" ) }
  7. https://github.com/riita10069/roche/blob/master/pkg/rochectl/gen-sca ff old/gen-usecase.go ϝιουͷ࡞੒ usecaseFile.Func().Params(Id("u").Id(name + "Usecase")).Id("GetByID").Params(Id("id").Int64()).Params(Id("*entity." + name), Error()).Block

    ( List(Id("e"), Err()).Op(":=") . Id("u").Dot(name+ "Repo").Dot("GetByID").Call(Id("id")) , If ( Err().Op("!=").Nil() , ).Block ( Return(Id("nil"), Err()) , ) , Return(Id("e"), Err()) , ) func (u EnterpriseUsecase) GetByID(id int64) (*entity.Enterprise, error) { e, err := u.EnterpriseRepo.GetByID(id ) if err != nil { return nil, er r } return e, er r }
  8. https://github.com/riita10069/roche/blob/master/pkg/rochectl/gen-sca ff old/gen-usecase.go ϝιουͷ࡞੒ usecaseFile.Func().Params(Id("u").Id(name + "Usecase")).Id("GetByID").Params(Id("id").Int64()).Params(Id("*entity." + name), Error()).Block

    ( List(Id("e"), Err()).Op(":=") . Id("u").Dot(name+ "Repo").Dot("GetByID").Call(Id("id")) , If ( Err().Op("!=").Nil() , ).Block ( Return(Id("nil"), Err()) , ) , Return(Id("e"), Err()) , ) func (u EnterpriseUsecase) GetByID(id int64) (*entity.Enterprise, error) { e, err := u.EnterpriseRepo.GetByID(id ) if err != nil { return nil, er r } return e, er r }
  9. https://github.com/riita10069/roche/blob/master/pkg/rochectl/gen-sca ff old/gen-usecase.go ϝιουͷ࡞੒ usecaseFile.Func().Params(Id("u").Id(name + "Usecase")).Id("GetByID").Params(Id("id").Int64()).Params(Id("*entity." + name), Error()).Block

    ( List(Id("e"), Err()).Op(":=") . Id("u").Dot(name+ "Repo").Dot("GetByID").Call(Id("id")) , If ( Err().Op("!=").Nil() , ).Block ( Return(Id("nil"), Err()) , ) , Return(Id("e"), Err()) , ) func (u EnterpriseUsecase) GetByID(id int64) (*entity.Enterprise, error) { e, err := u.EnterpriseRepo.GetByID(id ) if err != nil { return nil, er r } return e, er r } Id() & ม਺໊ɺؔ਺໊ͳͲ .Ͱܨ͛ΔͱεϖʔεͰܨ͕Δ Params()&Ҿ਺ɺؙׅހɺෳ਺ͷҾ਺͕౉ͤΔ Block()&ϒϩοΫɺ೾ׅހ Dot()&. Call() & Ͱؙׅހ ͦͷଞɺ༧໿ޠ͸ؔ਺͕ଘࡏ͢Δɻ
  10. https://github.com/riita10069/roche/blob/master/pkg/rochectl/gen-sca ff old/gen-model.go Structͷ࡞੒ var codes []jen.Cod e var code

    *jen.Statemen t for _, field := range structAst.Fields.List { for _, nameIdent := range field.Names { code := jen.Id(nameIdent.Name ) // ཁૉͷܕ໊ switch field.Type.(type) { // ผύοέʔδͷܕΛར༻͍ͯ͠Δ৔߹ case *ast.SelectorExpr : selectorExpr, _ := field.Type.(*ast.SelectorExpr ) xIdent, _ := selectorExpr.X.(*ast.Ident ) if xIdent.Name == "protoimpl" { continu e } code.Id(xIdent.Name + "." + selectorExpr.Sel.Name ) // ૊ΈࠐΈ·ͨ͸ύοέʔδ಺ͷܕΛར༻͍ͯ͠Δ৔߹ case *ast.Ident : ident, _ := field.Type.(*ast.Ident ) code.Id(ident.Name ) case *ast.StarExpr : code.Id(nameIdent.Name ) } codes = append(codes, code ) } } f.Type().Id(name).Struct(codes...) type User struct { Id int6 4 Name strin g Age int6 4 }
  11. File & string func JenFileToString(f *jen.File) { buf := &bytes.Buffer{

    } err := f.Render(buf ) if err != nil { fmt.Println(err.Error() ) os.Exit(1 ) } return buf.Strin g } https://github.com/riita10069/roche/blob/master/pkg/rochectl/ fi le/wrirte- fi le.go ҙਤͨ͠ίʔυ͕ੜ੒Ͱ͖Δ͔ͷϢχοτςετ ࣮ࡍʹϑΝΠϧʹจࣈྻΛॻ͖ࠐΉ
  12. String & ϑΝΠϧॻ͖ࠐΈ func makeDirectory (filename string) { paths :=

    strings.Split(filename, "/" ) paths = paths[:len(paths)-1 ] filepath := strings.Join(paths, "/" ) if _, err := os.Stat(filepath); os.IsNotExist(err) { defaultUmask := syscall.Umask(0 ) os.MkdirAll(filepath, 0755 ) os.Chmod(filepath, 0755 ) syscall.Umask(defaultUmask ) } fmt.Println("Execute Make Directory" ) (" ✔ ", filepath ) } func CreateAndWrite(content string, filename string) error { if strings.Contains(filename, "/") { makeDirectory(filename ) } err := ioutil.WriteFile(filename, []byte(content), 0664 ) if err != nil { return errors.New("cannot write" + filename ) } fmt.Println("Execute code generate to following file" ) fmt.Println(" ✔ ", filename ) return ni l } https://github.com/riita10069/roche/blob/master/pkg/rochectl/ fi le/wrirte- fi le.go
  13. AST(ந৅ߏจ໦)ͷ࡞੒ func getAst(filePath string) f *ast.File { fset := token.NewFileSet(

    ) f, err := parser.ParseFile(fset, filePath, nil, parser.Mode(0) ) if err != nil { fmt.Println("cannot parse file, filepath is ", filePath ) fmt.Println(err.Error() ) return ni l } return f } https://github.com/riita10069/roche/blob/master/pkg/rochectl/ast/ fi nd-struct.go
  14. ͋ΔϑΝΠϧ͔ΒstructͷཁૉΛऔಘ func getAstHash(f *ast.File, structASTMap map[string]*ast.StructType) { for _, decl

    := range f.Decls { d, ok := decl.(*ast.GenDecl ) if !ok || d.Tok != token.TYPE { continu e } for _, spec := range d.Specs { s, ok := spec.(*ast.TypeSpec ) if !ok { continu e } t, ok := s.Type.(*ast.StructType ) if !ok { continu e } structASTMap[s.Name.Name] = t } } } https://github.com/riita10069/roche/blob/master/pkg/rochectl/ast/ fi nd-struct.go func GetPropertyByStructAst(structAst *ast.StructType) ([]string, []string) { var properties []strin g var propertiesType []strin g for _, field := range structAst.Fields.List { for _, nameIdent := range field.Names { switch field.Type.(type) { case *ast.SelectorExpr : selectorExpr, _ := field.Type.(*ast.SelectorExpr ) xIdent, _ := selectorExpr.X.(*ast.Ident ) if xIdent.Name == "protoimpl" { continu e } properties = append(properties, util.SnakeToLowerCamel(nameIdent.Name) ) propertiesType = append(propertiesType, xIdent.Name ) case *ast.Ident : ident, _ := field.Type.(*ast.Ident ) properties = append(properties, nameIdent.Name ) propertiesType = append(propertiesType, ident.Name ) } } } return properties, propertiesTyp e }