@@ -2,19 +2,22 @@ package arcgengo
22
33import (
44 "go/ast"
5+ "go/parser"
56 "go/printer"
67 "go/token"
78 "io"
9+ "path/filepath"
810 "strconv"
911 "strings"
1012
1113 errorz "github.com/kunitsucom/util.go/errors"
1214
15+ "github.com/kunitsucom/arcgen/internal/arcgen/lang/util"
1316 "github.com/kunitsucom/arcgen/internal/config"
1417)
1518
16- func fprintCRUDCommon (osFile osFile , buf buffer , arcSrcSetSlice ARCSourceSetSlice ) error {
17- content , err := generateCRUDCommonFileContent (buf , arcSrcSetSlice )
19+ func fprintCRUDCommon (osFile osFile , buf buffer , arcSrcSetSlice ARCSourceSetSlice , crudFiles [] string ) error {
20+ content , err := generateCRUDCommonFileContent (buf , arcSrcSetSlice , crudFiles )
1821 if err != nil {
1922 return errorz .Errorf ("generateCRUDCommonFileContent: %w" , err )
2023 }
@@ -27,8 +30,13 @@ func fprintCRUDCommon(osFile osFile, buf buffer, arcSrcSetSlice ARCSourceSetSlic
2730 return nil
2831}
2932
30- //nolint:funlen
31- func generateCRUDCommonFileContent (buf buffer , _ ARCSourceSetSlice ) (string , error ) {
33+ const (
34+ sqlQueryerContextVarName = "sqlContext"
35+ sqlQueryerContextTypeName = "sqlQueryerContext"
36+ )
37+
38+ //nolint:cyclop,funlen,gocognit,maintidx
39+ func generateCRUDCommonFileContent (buf buffer , arcSrcSetSlice ARCSourceSetSlice , crudFiles []string ) (string , error ) {
3240 astFile := & ast.File {
3341 // package
3442 Name : & ast.Ident {
@@ -38,18 +46,19 @@ func generateCRUDCommonFileContent(buf buffer, _ ARCSourceSetSlice) (string, err
3846 Decls : []ast.Decl {},
3947 }
4048
41- // // Since all directories are the same from arcSrcSetSlice[0].Filename to arcSrcSetSlice[len(-1)].Filename,
42- // // get the package path from arcSrcSetSlice[0].Filename.
43- // dir := filepath.Dir(arcSrcSetSlice[0].Filename)
44- // structPackagePath, err := util.GetPackagePath(dir)
45- // if err != nil {
46- // return "", errorz.Errorf("GetPackagePath: %w", err)
47- // }
49+ // Since all directories are the same from arcSrcSetSlice[0].Filename to arcSrcSetSlice[len(-1)].Filename,
50+ // get the package path from arcSrcSetSlice[0].Filename.
51+ dir := filepath .Dir (arcSrcSetSlice [0 ].Filename )
52+ structPackagePath , err := util .GetPackagePath (dir )
53+ if err != nil {
54+ return "" , errorz .Errorf ("GetPackagePath: %w" , err )
55+ }
4856
4957 astFile .Decls = append (astFile .Decls ,
5058 // import (
5159 // "context"
5260 // "database/sql"
61+ // "log/slog"
5362 //
5463 // dao "path/to/your/dao"
5564 // )
@@ -62,15 +71,18 @@ func generateCRUDCommonFileContent(buf buffer, _ ARCSourceSetSlice) (string, err
6271 & ast.ImportSpec {
6372 Path : & ast.BasicLit {Kind : token .STRING , Value : strconv .Quote ("database/sql" )},
6473 },
65- // &ast.ImportSpec{
66- // Name: &ast.Ident{Name: "dao"},
67- // Path: &ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(structPackagePath)},
68- // },
74+ & ast.ImportSpec {
75+ Path : & ast.BasicLit {Kind : token .STRING , Value : strconv .Quote ("log/slog" )},
76+ },
77+ & ast.ImportSpec {
78+ Name : & ast.Ident {Name : "dao" },
79+ Path : & ast.BasicLit {Kind : token .STRING , Value : strconv .Quote (structPackagePath )},
80+ },
6981 },
7082 },
7183 )
7284
73- // type sqlContext interface {
85+ // type sqlQueryerContext interface {
7486 // QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
7587 // QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
7688 // ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
@@ -80,7 +92,8 @@ func generateCRUDCommonFileContent(buf buffer, _ ARCSourceSetSlice) (string, err
8092 Tok : token .TYPE ,
8193 Specs : []ast.Spec {
8294 & ast.TypeSpec {
83- Name : & ast.Ident {Name : "sqlContext" },
95+ // Assign: token.Pos(1),
96+ Name : & ast.Ident {Name : sqlQueryerContextTypeName },
8497 Type : & ast.InterfaceType {
8598 Methods : & ast.FieldList {
8699 List : []* ast.Field {
@@ -133,27 +146,138 @@ func generateCRUDCommonFileContent(buf buffer, _ ARCSourceSetSlice) (string, err
133146 },
134147 )
135148
136- // type Queryer struct {}
149+ // type _CRUD struct {
150+ // }
137151 astFile .Decls = append (astFile .Decls ,
138152 & ast.GenDecl {
139153 Tok : token .TYPE ,
140154 Specs : []ast.Spec {
141155 & ast.TypeSpec {
142- Name : & ast.Ident {Name : "Queryer" },
156+ Name : & ast.Ident {Name : config . GoCRUDTypeNameUnexported () },
143157 Type : & ast.StructType {Fields : & ast.FieldList {}},
144158 },
145159 },
146160 },
147161 )
148162
149- // func NewQueryer() *Query {
150- // return &Queryer{}
151- // }
163+ // func LoggerFromContext(ctx context.Context) *slog.Logger {
164+ // if ctx == nil {
165+ // return slog.Default()
166+ // }
167+ // if logger, ok := ctx.Value((*slog.Logger)(nil)).(*slog.Logger); ok {
168+ // return logger
169+ // }
170+ // return slog.Default()
171+ // }
172+ astFile .Decls = append (astFile .Decls ,
173+ & ast.FuncDecl {
174+ Name : & ast.Ident {Name : "LoggerFromContext" },
175+ Type : & ast.FuncType {Params : & ast.FieldList {List : []* ast.Field {{Names : []* ast.Ident {{Name : "ctx" }}, Type : & ast.Ident {Name : "context.Context" }}}}, Results : & ast.FieldList {List : []* ast.Field {{Type : & ast.StarExpr {X : & ast.SelectorExpr {X : & ast.Ident {Name : "slog" }, Sel : & ast.Ident {Name : "Logger" }}}}}}},
176+ Body : & ast.BlockStmt {
177+ List : []ast.Stmt {
178+ & ast.IfStmt {
179+ Cond : & ast.BinaryExpr {X : & ast.Ident {Name : "ctx" }, Op : token .EQL , Y : & ast.Ident {Name : "nil" }},
180+ Body : & ast.BlockStmt {List : []ast.Stmt {
181+ & ast.ReturnStmt {Results : []ast.Expr {& ast.CallExpr {Fun : & ast.SelectorExpr {X : & ast.Ident {Name : "slog" }, Sel : & ast.Ident {Name : "Default" }}}}},
182+ }},
183+ },
184+ & ast.IfStmt {
185+ // if logger, ok := ctx.Value((*slog.Logger)(nil)).(*slog.Logger); ok {
186+ Init : & ast.AssignStmt {
187+ Lhs : []ast.Expr {& ast.Ident {Name : "logger" }, & ast.Ident {Name : "ok" }},
188+ Tok : token .DEFINE ,
189+ Rhs : []ast.Expr {
190+ & ast.TypeAssertExpr {
191+ X : & ast.CallExpr {
192+ Fun : & ast.Ident {Name : "ctx.Value" },
193+ Args : []ast.Expr {& ast.CallExpr {Fun : & ast.ParenExpr {X : & ast.StarExpr {X : & ast.SelectorExpr {X : & ast.Ident {Name : "slog" }, Sel : & ast.Ident {Name : "Logger" }}}}, Args : []ast.Expr {& ast.Ident {Name : "nil" }}}},
194+ },
195+ Type : & ast.StarExpr {X : & ast.SelectorExpr {X : & ast.Ident {Name : "slog" }, Sel : & ast.Ident {Name : "Logger" }}},
196+ },
197+ },
198+ },
199+ Cond : & ast.Ident {Name : "ok" },
200+ Body : & ast.BlockStmt {List : []ast.Stmt {& ast.ReturnStmt {Results : []ast.Expr {& ast.Ident {Name : "logger" }}}}},
201+ },
202+ & ast.ReturnStmt {Results : []ast.Expr {& ast.CallExpr {Fun : & ast.SelectorExpr {X : & ast.Ident {Name : "slog" }, Sel : & ast.Ident {Name : "Default" }}}}},
203+ },
204+ },
205+ },
206+ )
207+
208+ // func LoggerWithContext(ctx context.Context, logger *slog.Logger) context.Context {
209+ // return context.WithValue(ctx, (*slog.Logger)(nil), logger)
210+ // }
211+ astFile .Decls = append (astFile .Decls ,
212+ & ast.FuncDecl {
213+ Name : & ast.Ident {Name : "LoggerWithContext" },
214+ Type : & ast.FuncType {Params : & ast.FieldList {List : []* ast.Field {{Names : []* ast.Ident {{Name : "ctx" }}, Type : & ast.Ident {Name : "context.Context" }}, {Names : []* ast.Ident {{Name : "logger" }}, Type : & ast.StarExpr {X : & ast.SelectorExpr {X : & ast.Ident {Name : "slog" }, Sel : & ast.Ident {Name : "Logger" }}}}}}, Results : & ast.FieldList {List : []* ast.Field {{Type : & ast.Ident {Name : "context.Context" }}}}},
215+ Body : & ast.BlockStmt {
216+ List : []ast.Stmt {
217+ & ast.ReturnStmt {Results : []ast.Expr {& ast.CallExpr {Fun : & ast.SelectorExpr {X : & ast.Ident {Name : "context" }, Sel : & ast.Ident {Name : "WithValue" }}, Args : []ast.Expr {& ast.Ident {Name : "ctx" }, & ast.CallExpr {Fun : & ast.ParenExpr {X : & ast.StarExpr {X : & ast.SelectorExpr {X : & ast.Ident {Name : "slog" }, Sel : & ast.Ident {Name : "Logger" }}}}, Args : []ast.Expr {& ast.Ident {Name : "nil" }}}, & ast.Ident {Name : "logger" }}}}},
218+ },
219+ },
220+ },
221+ )
222+
223+ // type CRUD interface {
224+ // Create{StructName}(ctx context.Context, sqlQueryer sqlQueryerContext, s *{Struct}) error
225+ // ...
226+ // }
227+ methods := make ([]* ast.Field , 0 )
228+ fset := token .NewFileSet ()
229+ for _ , crudFile := range crudFiles {
230+ rootNode , err := parser .ParseFile (fset , crudFile , nil , parser .ParseComments )
231+ if err != nil {
232+ // MEMO: parser.ParseFile err contains file path, so no need to log it
233+ return "" , errorz .Errorf ("parser.ParseFile: %w" , err )
234+ }
235+
236+ // MEMO: Inspect is used to get the method declaration from the file
237+ ast .Inspect (rootNode , func (node ast.Node ) bool {
238+ switch n := node .(type ) {
239+ case * ast.FuncDecl :
240+ //nolint:nestif
241+ if n .Recv != nil && len (n .Recv .List ) > 0 {
242+ if t , ok := n .Recv .List [0 ].Type .(* ast.StarExpr ); ok {
243+ if ident , ok := t .X .(* ast.Ident ); ok {
244+ if ident .Name == config .GoCRUDTypeNameUnexported () {
245+ methods = append (methods , & ast.Field {
246+ Names : []* ast.Ident {{Name : n .Name .Name }},
247+ Type : n .Type ,
248+ })
249+ }
250+ }
251+ }
252+ }
253+ default :
254+ // noop
255+ }
256+ return true
257+ })
258+ }
259+ astFile .Decls = append (astFile .Decls ,
260+ & ast.GenDecl {
261+ Tok : token .TYPE ,
262+ Specs : []ast.Spec {
263+ & ast.TypeSpec {
264+ Name : & ast.Ident {Name : config .GoCRUDTypeName ()},
265+ Type : & ast.InterfaceType {
266+ Methods : & ast.FieldList {List : methods },
267+ },
268+ },
269+ },
270+ },
271+ )
272+
273+ // func NewCRUD() CRUD {
274+ // return &_CRUD{}
275+ // }
152276 astFile .Decls = append (astFile .Decls ,
153277 & ast.FuncDecl {
154- Name : & ast.Ident {Name : "NewQueryer" },
155- Type : & ast.FuncType {Results : & ast.FieldList {List : []* ast.Field {{Type : & ast.StarExpr { X : & ast. Ident {Name : "Queryer" } }}}}},
156- Body : & ast.BlockStmt {List : []ast.Stmt {& ast.ReturnStmt {Results : []ast.Expr {& ast.UnaryExpr {Op : token .AND , X : & ast.Ident {Name : "Queryer {}" }}}}}},
278+ Name : & ast.Ident {Name : "New" + config . GoCRUDTypeName () },
279+ Type : & ast.FuncType {Results : & ast.FieldList {List : []* ast.Field {{Type : & ast.Ident {Name : config . GoCRUDTypeName () }}}}},
280+ Body : & ast.BlockStmt {List : []ast.Stmt {& ast.ReturnStmt {Results : []ast.Expr {& ast.UnaryExpr {Op : token .AND , X : & ast.Ident {Name : config . GoCRUDTypeNameUnexported () + " {}" }}}}}},
157281 },
158282 )
159283
0 commit comments