diff --git a/internal/arcgen/lang/go/generate.go b/internal/arcgen/lang/go/generate.go index 24b6220..e5ed1a3 100644 --- a/internal/arcgen/lang/go/generate.go +++ b/internal/arcgen/lang/go/generate.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "os" + "path/filepath" "strings" errorz "github.com/kunitsucom/util.go/errors" @@ -27,15 +28,17 @@ func Generate(ctx context.Context, src string) error { return nil } +const rw_r__r__ = 0o644 //nolint:revive,stylecheck // rw-r--r-- + +//nolint:cyclop,funlen,gocognit func generate(arcSrcSetSlice ARCSourceSetSlice) error { - for _, arcSrcSet := range arcSrcSetSlice { - const rw_r__r__ = 0o644 //nolint:revive,stylecheck // rw-r--r-- + genFileExt := fmt.Sprintf(".%s.gen%s", config.GoColumnTag(), fileExt) + for _, arcSrcSet := range arcSrcSetSlice { // closure for defer if err := func() error { filePathWithoutExt := strings.TrimSuffix(arcSrcSet.Filename, fileExt) - newExt := fmt.Sprintf(".%s.gen%s", config.GoColumnTag(), fileExt) - filename := filePathWithoutExt + newExt + filename := filePathWithoutExt + genFileExt f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, rw_r__r__) if err != nil { return errorz.Errorf("os.OpenFile: %w", err) @@ -51,6 +54,52 @@ func generate(arcSrcSetSlice ARCSourceSetSlice) error { } } + if config.GenerateGoCRUDPackage() { + crudFileExt := ".crud" + genFileExt + + if err := func() error { + filename := filepath.Join(config.GoCRUDPackagePath(), "common"+crudFileExt) + f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, rw_r__r__) + if err != nil { + return errorz.Errorf("os.OpenFile: %w", err) + } + defer f.Close() + + if err := fprintCRUDCommon(f, bytes.NewBuffer(nil), arcSrcSetSlice); err != nil { + return errorz.Errorf("sprint: %w", err) + } + + return nil + }(); err != nil { + return errorz.Errorf("f: %w", err) + } + + for _, arcSrcSet := range arcSrcSetSlice { + // closure for defer + if err := func() error { + filePathWithoutExt := strings.TrimSuffix(filepath.Base(arcSrcSet.Filename), fileExt) + filename := filepath.Join(config.GoCRUDPackagePath(), filePathWithoutExt+crudFileExt) + f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, rw_r__r__) + if err != nil { + return errorz.Errorf("os.OpenFile: %w", err) + } + defer f.Close() + f.Name() + + if err := fprintCRUD( + f, + bytes.NewBuffer(nil), + arcSrcSet, + ); err != nil { + return errorz.Errorf("sprint: %w", err) + } + return nil + }(); err != nil { + return errorz.Errorf("f: %w", err) + } + } + } + return nil } @@ -58,3 +107,8 @@ type buffer = interface { io.Writer fmt.Stringer } + +type osFile = interface { + io.Writer + Name() string +} diff --git a/internal/arcgen/lang/go/generate_columns.go b/internal/arcgen/lang/go/generate_columns.go index 306797f..bd94485 100644 --- a/internal/arcgen/lang/go/generate_columns.go +++ b/internal/arcgen/lang/go/generate_columns.go @@ -14,7 +14,7 @@ import ( "github.com/kunitsucom/arcgen/pkg/errors" ) -func fprintColumns(osFile io.Writer, buf buffer, arcSrcSet *ARCSourceSet) error { +func fprintColumns(osFile osFile, buf buffer, arcSrcSet *ARCSourceSet) error { content, err := generateColumnsFileContent(buf, arcSrcSet) if err != nil { return errorz.Errorf("generateColumnsFile: %w", err) @@ -44,7 +44,7 @@ func generateColumnsFileContent(buf buffer, arcSrcSet *ARCSourceSet) (string, er for _, arcSrc := range arcSrcSet.ARCSourceSlice { structName := arcSrc.extractStructName() tableName := arcSrc.extractTableNameFromCommentGroup() - fieldNames, columnNames := arcSrc.extractFieldNamesAndColumnNames() + columnInfos := arcSrc.extractFieldNamesAndColumnNames() appendAST( astFile, @@ -54,8 +54,7 @@ func generateColumnsFileContent(buf buffer, arcSrcSet *ARCSourceSet) (string, er config.GoMethodNameTable(), config.GoMethodNameColumns(), config.GoMethodPrefixColumn(), - fieldNames, - columnNames, + columnInfos, ) } @@ -64,7 +63,7 @@ func generateColumnsFileContent(buf buffer, arcSrcSet *ARCSourceSet) (string, er } // add header comment - content := arcSrcSet.generateGoFileHeader() + buf.String() + content := arcSrcSet.generateGoFileHeader() + "\n" + buf.String() // add blank line between methods content = strings.ReplaceAll(content, "\n}\nfunc ", "\n}\n\nfunc ") @@ -72,10 +71,10 @@ func generateColumnsFileContent(buf buffer, arcSrcSet *ARCSourceSet) (string, er return content, nil } -func appendAST(file *ast.File, structName string, sliceTypeSuffix string, tableName string, methodNameTable string, methodNameColumns string, methodPrefixColumn string, fieldNames, columnNames []string) { +func appendAST(file *ast.File, structName string, sliceTypeSuffix string, tableName string, methodNameTable string, methodNameColumns string, methodPrefixColumn string, tableInfo *TableInfo) { file.Decls = append(file.Decls, generateASTTableMethods(structName, sliceTypeSuffix, tableName, methodNameTable)...) - file.Decls = append(file.Decls, generateASTColumnMethods(structName, sliceTypeSuffix, methodNameColumns, methodPrefixColumn, fieldNames, columnNames)...) + file.Decls = append(file.Decls, generateASTColumnMethods(structName, sliceTypeSuffix, methodNameColumns, methodPrefixColumn, tableInfo)...) return //nolint:gosimple } @@ -207,15 +206,15 @@ func generateASTTableMethods(structName string, sliceTypeSuffix string, tableNam } //nolint:funlen -func generateASTColumnMethods(structName string, sliceTypeSuffix string, methodNameColumns string, prefixColumn string, fieldNames, columnNames []string) []ast.Decl { +func generateASTColumnMethods(structName string, sliceTypeSuffix string, methodNameColumns string, prefixColumn string, tableInfo *TableInfo) []ast.Decl { decls := make([]ast.Decl, 0) // all column names method elts := make([]ast.Expr, 0) - for _, columnName := range columnNames { + for _, c := range tableInfo.Columns { elts = append(elts, &ast.BasicLit{ Kind: token.STRING, - Value: strconv.Quote(columnName), + Value: strconv.Quote(c.ColumnName), }) } decls = append(decls, @@ -274,7 +273,7 @@ func generateASTColumnMethods(structName string, sliceTypeSuffix string, methodN ) // each column name methods - for i := range columnNames { + for i := range tableInfo.Columns { decls = append(decls, // func (s *StructName) Column1() string { // return "column1" @@ -297,7 +296,7 @@ func generateASTColumnMethods(structName string, sliceTypeSuffix string, methodN }, }, Name: &ast.Ident{ - Name: prefixColumn + fieldNames[i], + Name: prefixColumn + tableInfo.Columns[i].FieldName, }, Type: &ast.FuncType{ Params: &ast.FieldList{}, @@ -316,7 +315,7 @@ func generateASTColumnMethods(structName string, sliceTypeSuffix string, methodN &ast.ReturnStmt{ Results: []ast.Expr{ &ast.Ident{ - Name: strconv.Quote(columnNames[i]), + Name: strconv.Quote(tableInfo.Columns[i].ColumnName), }, }, }, @@ -381,7 +380,7 @@ func generateASTColumnMethods(structName string, sliceTypeSuffix string, methodN ) // each column name methods - for i := range columnNames { + for i := range tableInfo.Columns { decls = append(decls, // func (s StructNameSlice) Column1() string { // return "column1" @@ -402,7 +401,7 @@ func generateASTColumnMethods(structName string, sliceTypeSuffix string, methodN }, }, Name: &ast.Ident{ - Name: prefixColumn + fieldNames[i], + Name: prefixColumn + tableInfo.Columns[i].FieldName, }, Type: &ast.FuncType{ Params: &ast.FieldList{}, @@ -421,7 +420,7 @@ func generateASTColumnMethods(structName string, sliceTypeSuffix string, methodN &ast.ReturnStmt{ Results: []ast.Expr{ &ast.Ident{ - Name: strconv.Quote(columnNames[i]), + Name: strconv.Quote(tableInfo.Columns[i].ColumnName), }, }, }, diff --git a/internal/arcgen/lang/go/generate_columns_test.go b/internal/arcgen/lang/go/generate_columns_test.go index c8e926e..527641d 100644 --- a/internal/arcgen/lang/go/generate_columns_test.go +++ b/internal/arcgen/lang/go/generate_columns_test.go @@ -20,6 +20,7 @@ var _ buffer = (*testBuffer)(nil) type testBuffer struct { WriteFunc func(p []byte) (n int, err error) StringFunc func() string + NameFunc func() string } func (w *testBuffer) Write(p []byte) (n int, err error) { @@ -30,6 +31,10 @@ func (w *testBuffer) String() string { return w.StringFunc() } +func (w *testBuffer) Name() string { + return w.NameFunc() +} + //nolint:paralleltest func Test_generate(t *testing.T) { t.Run("failure,os.OpenFile", func(t *testing.T) { diff --git a/internal/arcgen/lang/go/generate_crud.go b/internal/arcgen/lang/go/generate_crud.go new file mode 100644 index 0000000..cb4ad7e --- /dev/null +++ b/internal/arcgen/lang/go/generate_crud.go @@ -0,0 +1,93 @@ +package arcgengo + +import ( + "go/ast" + "go/printer" + "go/token" + "io" + "path/filepath" + "strconv" + "strings" + + errorz "github.com/kunitsucom/util.go/errors" + + "github.com/kunitsucom/arcgen/internal/arcgen/lang/util" + "github.com/kunitsucom/arcgen/internal/config" + "github.com/kunitsucom/arcgen/pkg/errors" +) + +func fprintCRUD(osFile osFile, buf buffer, arcSrcSet *ARCSourceSet) error { + content, err := generateCRUDFileContent(buf, arcSrcSet) + if err != nil { + return errorz.Errorf("generateCRUDFileContent: %w", err) + } + + // write to file + if _, err := io.WriteString(osFile, content); err != nil { + return errorz.Errorf("io.WriteString: %w", err) + } + + return nil +} + +//nolint:funlen +func generateCRUDFileContent(buf buffer, arcSrcSet *ARCSourceSet) (string, error) { + if arcSrcSet == nil || arcSrcSet.PackageName == "" { + return "", errors.ErrInvalidSourceSet + } + astFile := &ast.File{ + // package + Name: &ast.Ident{ + Name: config.GoCRUDPackageName(), + }, + // methods + Decls: []ast.Decl{}, + } + + structPackagePath, err := util.GetPackagePath(filepath.Dir(arcSrcSet.Filename)) + if err != nil { + return "", errorz.Errorf("GetPackagePath: %w", err) + } + + // import + astFile.Decls = append(astFile.Decls, + // import ( + // "context" + // "fmt" + // + // dao "path/to/your/dao" + // ) + &ast.GenDecl{ + Tok: token.IMPORT, + Specs: []ast.Spec{ + &ast.ImportSpec{ + Path: &ast.BasicLit{Kind: token.STRING, Value: strconv.Quote("context")}, + }, + &ast.ImportSpec{ + Path: &ast.BasicLit{Kind: token.STRING, Value: strconv.Quote("fmt")}, + }, + &ast.ImportSpec{ + Name: &ast.Ident{Name: "dao"}, + Path: &ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(structPackagePath)}, + }, + }, + }, + ) + + generateCREATEContent(astFile, arcSrcSet) + generateREADContent(astFile, arcSrcSet) + generateUPDATEContent(astFile, arcSrcSet) + generateDELETEContent(astFile, arcSrcSet) + + if err := printer.Fprint(buf, token.NewFileSet(), astFile); err != nil { + return "", errorz.Errorf("printer.Fprint: %w", err) + } + + // add header comment + content := arcSrcSet.generateGoFileHeader() + buf.String() + + // add blank line between methods + content = strings.ReplaceAll(content, "\n}\nfunc ", "\n}\n\nfunc ") + + return content, nil +} diff --git a/internal/arcgen/lang/go/generate_crud_common.go b/internal/arcgen/lang/go/generate_crud_common.go new file mode 100644 index 0000000..af24d04 --- /dev/null +++ b/internal/arcgen/lang/go/generate_crud_common.go @@ -0,0 +1,171 @@ +package arcgengo + +import ( + "go/ast" + "go/printer" + "go/token" + "io" + "strconv" + "strings" + + errorz "github.com/kunitsucom/util.go/errors" + + "github.com/kunitsucom/arcgen/internal/config" +) + +func fprintCRUDCommon(osFile osFile, buf buffer, arcSrcSetSlice ARCSourceSetSlice) error { + content, err := generateCRUDCommonFileContent(buf, arcSrcSetSlice) + if err != nil { + return errorz.Errorf("generateCRUDCommonFileContent: %w", err) + } + + // write to file + if _, err := io.WriteString(osFile, content); err != nil { + return errorz.Errorf("io.WriteString: %w", err) + } + + return nil +} + +//nolint:funlen +func generateCRUDCommonFileContent(buf buffer, _ ARCSourceSetSlice) (string, error) { + astFile := &ast.File{ + // package + Name: &ast.Ident{ + Name: config.GoCRUDPackageName(), + }, + // methods + Decls: []ast.Decl{}, + } + + // // Since all directories are the same from arcSrcSetSlice[0].Filename to arcSrcSetSlice[len(-1)].Filename, + // // get the package path from arcSrcSetSlice[0].Filename. + // dir := filepath.Dir(arcSrcSetSlice[0].Filename) + // structPackagePath, err := util.GetPackagePath(dir) + // if err != nil { + // return "", errorz.Errorf("GetPackagePath: %w", err) + // } + + astFile.Decls = append(astFile.Decls, + // import ( + // "context" + // "database/sql" + // + // dao "path/to/your/dao" + // ) + &ast.GenDecl{ + Tok: token.IMPORT, + Specs: []ast.Spec{ + &ast.ImportSpec{ + Path: &ast.BasicLit{Kind: token.STRING, Value: strconv.Quote("context")}, + }, + &ast.ImportSpec{ + Path: &ast.BasicLit{Kind: token.STRING, Value: strconv.Quote("database/sql")}, + }, + // &ast.ImportSpec{ + // Name: &ast.Ident{Name: "dao"}, + // Path: &ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(structPackagePath)}, + // }, + }, + }, + ) + + // type sqlContext interface { + // QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) + // QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row + // ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) + // } + astFile.Decls = append(astFile.Decls, + &ast.GenDecl{ + Tok: token.TYPE, + Specs: []ast.Spec{ + &ast.TypeSpec{ + Name: &ast.Ident{Name: "sqlContext"}, + Type: &ast.InterfaceType{ + Methods: &ast.FieldList{ + List: []*ast.Field{ + { + Names: []*ast.Ident{{Name: "QueryContext"}}, + Type: &ast.FuncType{ + Params: &ast.FieldList{List: []*ast.Field{ + {Names: []*ast.Ident{{Name: "ctx"}}, Type: &ast.Ident{Name: "context.Context"}}, + {Names: []*ast.Ident{{Name: "query"}}, Type: &ast.Ident{Name: "string"}}, + {Names: []*ast.Ident{{Name: "args"}}, Type: &ast.Ellipsis{Elt: &ast.Ident{Name: "interface{}"}}}, + }}, + Results: &ast.FieldList{List: []*ast.Field{ + {Type: &ast.StarExpr{X: &ast.SelectorExpr{X: &ast.Ident{Name: "sql"}, Sel: &ast.Ident{Name: "Rows"}}}}, + {Type: &ast.Ident{Name: "error"}}, + }}, + }, + }, + { + Names: []*ast.Ident{{Name: "QueryRowContext"}}, + Type: &ast.FuncType{ + Params: &ast.FieldList{List: []*ast.Field{ + {Names: []*ast.Ident{{Name: "ctx"}}, Type: &ast.Ident{Name: "context.Context"}}, + {Names: []*ast.Ident{{Name: "query"}}, Type: &ast.Ident{Name: "string"}}, + {Names: []*ast.Ident{{Name: "args"}}, Type: &ast.Ellipsis{Elt: &ast.Ident{Name: "interface{}"}}}, + }}, + Results: &ast.FieldList{List: []*ast.Field{ + {Type: &ast.StarExpr{X: &ast.SelectorExpr{X: &ast.Ident{Name: "sql"}, Sel: &ast.Ident{Name: "Row"}}}}, + }}, + }, + }, + { + Names: []*ast.Ident{{Name: "ExecContext"}}, + Type: &ast.FuncType{ + Params: &ast.FieldList{List: []*ast.Field{ + {Names: []*ast.Ident{{Name: "ctx"}}, Type: &ast.Ident{Name: "context.Context"}}, + {Names: []*ast.Ident{{Name: "query"}}, Type: &ast.Ident{Name: "string"}}, + {Names: []*ast.Ident{{Name: "args"}}, Type: &ast.Ellipsis{Elt: &ast.Ident{Name: "interface{}"}}}, + }}, + Results: &ast.FieldList{List: []*ast.Field{ + {Type: &ast.SelectorExpr{X: &ast.Ident{Name: "sql"}, Sel: &ast.Ident{Name: "Result"}}}, + {Type: &ast.Ident{Name: "error"}}, + }}, + }, + }, + }, + }, + }, + }, + }, + }, + ) + + // type Queryer struct {} + astFile.Decls = append(astFile.Decls, + &ast.GenDecl{ + Tok: token.TYPE, + Specs: []ast.Spec{ + &ast.TypeSpec{ + Name: &ast.Ident{Name: "Queryer"}, + Type: &ast.StructType{Fields: &ast.FieldList{}}, + }, + }, + }, + ) + + // func NewQueryer() *Query { + // return &Queryer{} + // } + astFile.Decls = append(astFile.Decls, + &ast.FuncDecl{ + Name: &ast.Ident{Name: "NewQueryer"}, + Type: &ast.FuncType{Results: &ast.FieldList{List: []*ast.Field{{Type: &ast.StarExpr{X: &ast.Ident{Name: "Queryer"}}}}}}, + Body: &ast.BlockStmt{List: []ast.Stmt{&ast.ReturnStmt{Results: []ast.Expr{&ast.UnaryExpr{Op: token.AND, X: &ast.Ident{Name: "Queryer{}"}}}}}}, + }, + ) + + if err := printer.Fprint(buf, token.NewFileSet(), astFile); err != nil { + return "", errorz.Errorf("printer.Fprint: %w", err) + } + + // add header comment + content := generateGoFileHeader() + buf.String() + + // add blank line between methods + content = strings.ReplaceAll(content, "\n}\nfunc ", "\n}\n\nfunc ") + + return content, nil +} diff --git a/internal/arcgen/lang/go/generate_crud_create.go b/internal/arcgen/lang/go/generate_crud_create.go new file mode 100644 index 0000000..07aa70a --- /dev/null +++ b/internal/arcgen/lang/go/generate_crud_create.go @@ -0,0 +1,100 @@ +package arcgengo + +import ( + "go/ast" + "go/token" + "strconv" + "strings" +) + +//nolint:funlen +func generateCREATEContent(astFile *ast.File, arcSrcSet *ARCSourceSet) { + for _, arcSrc := range arcSrcSet.ARCSourceSlice { + structName := arcSrc.extractStructName() + tableName := arcSrc.extractTableNameFromCommentGroup() + tableInfo := arcSrc.extractFieldNamesAndColumnNames() + columnNames := tableInfo.ColumnNames() + + // const Create{StructName}Query = `INSERT INTO {table_name} ({column_name1}, {column_name2}) VALUES (?, ?)` + // + // func (q *query) Create{StructName}(ctx context.Context, queryer sqlContext, s *{Struct}) error { + // if _, err := queryer.ExecContext(ctx, Create{StructName}Query, s.{ColumnName1}, s.{ColumnName2}); err != nil { + // return fmt.Errorf("q.queryer.ExecContext: %w", err) + // } + // return nil + // } + funcName := "Create" + structName + queryName := funcName + "Query" + astFile.Decls = append(astFile.Decls, + &ast.GenDecl{ + Tok: token.CONST, + Specs: []ast.Spec{ + &ast.ValueSpec{ + Names: []*ast.Ident{{Name: queryName}}, + Values: []ast.Expr{&ast.BasicLit{ + Kind: token.STRING, + Value: "`INSERT INTO " + tableName + " (" + strings.Join(columnNames, ", ") + ") VALUES (?" + strings.Repeat(", ?", len(columnNames)-1) + ")`", + }}, + }, + }, + }, + &ast.FuncDecl{ + Recv: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{{Name: "q"}}, Type: &ast.StarExpr{X: &ast.Ident{Name: "Queryer"}}}}}, + Name: &ast.Ident{Name: funcName}, + Type: &ast.FuncType{ + Params: &ast.FieldList{List: []*ast.Field{ + {Names: []*ast.Ident{{Name: "ctx"}}, Type: &ast.Ident{Name: "context.Context"}}, + {Names: []*ast.Ident{{Name: "sqlCtx"}}, Type: &ast.Ident{Name: "sqlContext"}}, + {Names: []*ast.Ident{{Name: "s"}}, Type: &ast.StarExpr{X: &ast.Ident{Name: "dao." + structName}}}, + }}, + Results: &ast.FieldList{List: []*ast.Field{ + {Type: &ast.Ident{Name: "error"}}, + }}, + }, + Body: &ast.BlockStmt{ + List: []ast.Stmt{ + &ast.IfStmt{ + // if _, err := queryer.ExecContext(ctx, Create{StructName}Query, s.{ColumnName1}, s.{ColumnName2}); err != nil { + Init: &ast.AssignStmt{ + Lhs: []ast.Expr{&ast.Ident{Name: "_"}, &ast.Ident{Name: "err"}}, + Tok: token.DEFINE, + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{Name: "sqlCtx"}, + Sel: &ast.Ident{Name: "ExecContext"}, + }, + Args: append( + []ast.Expr{ + &ast.Ident{Name: "ctx"}, + &ast.Ident{Name: queryName}, + }, + func() []ast.Expr { + var args []ast.Expr + for _, c := range tableInfo.Columns { + args = append(args, &ast.SelectorExpr{X: &ast.Ident{Name: "s"}, Sel: &ast.Ident{Name: c.FieldName}}) + } + return args + }()...), + }}, + }, + // err != nil { + Cond: &ast.BinaryExpr{X: &ast.Ident{Name: "err"}, Op: token.NEQ, Y: &ast.Ident{Name: "nil"}}, + Body: &ast.BlockStmt{List: []ast.Stmt{ + // return fmt.Errorf("queryer.ExecContext: %w", err) + &ast.ReturnStmt{Results: []ast.Expr{&ast.CallExpr{ + Fun: &ast.SelectorExpr{X: &ast.Ident{Name: "fmt"}, Sel: &ast.Ident{Name: "Errorf"}}, + Args: []ast.Expr{&ast.Ident{Name: strconv.Quote("queryer.ExecContext: %w")}, &ast.Ident{Name: "err"}}, + }}}, + }}, + }, + &ast.ReturnStmt{ + Results: []ast.Expr{ + &ast.Ident{Name: "nil"}, + }, + }, + }, + }, + }, + ) + } +} diff --git a/internal/arcgen/lang/go/generate_crud_delete.go b/internal/arcgen/lang/go/generate_crud_delete.go new file mode 100644 index 0000000..c1803ee --- /dev/null +++ b/internal/arcgen/lang/go/generate_crud_delete.go @@ -0,0 +1,115 @@ +package arcgengo + +import ( + "go/ast" + "go/token" + "strconv" + "strings" +) + +//nolint:funlen +func generateDELETEContent(astFile *ast.File, arcSrcSet *ARCSourceSet) { + for _, arcSrc := range arcSrcSet.ARCSourceSlice { + structName := arcSrc.extractStructName() + tableName := arcSrc.extractTableNameFromCommentGroup() + tableInfo := arcSrc.extractFieldNamesAndColumnNames() + + // const Delete{StructName}Query = `DELETE FROM {table_name} WHERE {pk1} = ? [AND {pk2} = ?]` + // + // func (q *query) Delete{StructName}(ctx context.Context, queryer sqlContext, pk1 pk1type [, pk2 pk2type]) error { + // if _, err := queryer.ExecContext(ctx, Delete{StructName}Query, pk1 [, pk2]); err != nil { + // return fmt.Errorf("q.queryer.ExecContext: %w", err) + // } + // return nil + // } + funcName := "Delete" + structName + "ByPK" + queryName := funcName + "Query" + pkColumns := tableInfo.PrimaryKeys() + pkColumnNames := func() (pkColumnNames []string) { + for _, c := range pkColumns { + pkColumnNames = append(pkColumnNames, c.ColumnName) + } + return pkColumnNames + }() + astFile.Decls = append(astFile.Decls, + &ast.GenDecl{ + Tok: token.CONST, + Specs: []ast.Spec{ + &ast.ValueSpec{ + Names: []*ast.Ident{{Name: queryName}}, + Values: []ast.Expr{&ast.BasicLit{ + Kind: token.STRING, + Value: "`DELETE FROM " + tableName + " WHERE " + strings.Join(pkColumnNames, " = ? AND ") + " = ?`", + }}, + }, + }, + }, + &ast.FuncDecl{ + Recv: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{{Name: "q"}}, Type: &ast.StarExpr{X: &ast.Ident{Name: "Queryer"}}}}}, + Name: &ast.Ident{Name: funcName}, + Type: &ast.FuncType{ + Params: &ast.FieldList{List: append( + []*ast.Field{ + {Names: []*ast.Ident{{Name: "ctx"}}, Type: &ast.Ident{Name: "context.Context"}}, + {Names: []*ast.Ident{{Name: "sqlCtx"}}, Type: &ast.Ident{Name: "sqlContext"}}, + }, + func() []*ast.Field { + var fields []*ast.Field + for _, c := range pkColumns { + fields = append(fields, &ast.Field{Names: []*ast.Ident{{Name: c.ColumnName}}, Type: &ast.Ident{Name: c.FieldType}}) + } + return fields + }()..., + )}, + Results: &ast.FieldList{List: []*ast.Field{ + {Type: &ast.Ident{Name: "error"}}, + }}, + }, + Body: &ast.BlockStmt{ + List: []ast.Stmt{ + &ast.IfStmt{ + // if _, err := queryer.ExecContext(ctx, Delete{StructName}Query, pk1 [, pk2]); err != nil { + Init: &ast.AssignStmt{ + Lhs: []ast.Expr{&ast.Ident{Name: "_"}, &ast.Ident{Name: "err"}}, + Tok: token.DEFINE, + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{Name: "sqlCtx"}, + Sel: &ast.Ident{Name: "ExecContext"}, + }, + Args: append( + []ast.Expr{ + &ast.Ident{Name: "ctx"}, + &ast.Ident{Name: queryName}, + }, + func() []ast.Expr { + var args []ast.Expr + for _, c := range pkColumns { + args = append(args, &ast.Ident{Name: c.ColumnName}) + } + return args + }()..., + ), + }}, + }, + // err != nil { + Cond: &ast.BinaryExpr{X: &ast.Ident{Name: "err"}, Op: token.NEQ, Y: &ast.Ident{Name: "nil"}}, + Body: &ast.BlockStmt{List: []ast.Stmt{ + // return fmt.Errorf("queryer.ExecContext: %w", err) + &ast.ReturnStmt{Results: []ast.Expr{&ast.CallExpr{ + Fun: &ast.SelectorExpr{X: &ast.Ident{Name: "fmt"}, Sel: &ast.Ident{Name: "Errorf"}}, + Args: []ast.Expr{&ast.Ident{Name: strconv.Quote("queryer.ExecContext: %w")}, &ast.Ident{Name: "err"}}, + }}}, + }}, + }, + &ast.ReturnStmt{ + Results: []ast.Expr{ + &ast.Ident{Name: "nil"}, + }, + }, + }, + }, + }, + ) + } +} diff --git a/internal/arcgen/lang/go/generate_crud_read.go b/internal/arcgen/lang/go/generate_crud_read.go new file mode 100644 index 0000000..c753c27 --- /dev/null +++ b/internal/arcgen/lang/go/generate_crud_read.go @@ -0,0 +1,560 @@ +package arcgengo + +import ( + "go/ast" + "go/token" + "strconv" + "strings" + + "github.com/kunitsucom/arcgen/internal/config" +) + +//nolint:cyclop,funlen,gocognit,maintidx,unparam +func generateREADContent(astFile *ast.File, arcSrcSet *ARCSourceSet) { + for _, arcSrc := range arcSrcSet.ARCSourceSlice { + structName := arcSrc.extractStructName() + tableName := arcSrc.extractTableNameFromCommentGroup() + tableInfo := arcSrc.extractFieldNamesAndColumnNames() + columnNames := tableInfo.ColumnNames() + pks := tableInfo.PrimaryKeys() + + // const Find{StructName}ByPKQuery = `SELECT {column_name1}, {column_name2} FROM {table_name} WHERE {pk1} = ? [AND ...]` + // + // func (q *query) Find{StructName}ByPK(ctx context.Context, queryer sqlContext, pk1 pk1type, ...) ({Struct}, error) { + // row := queryer.QueryRowContext(ctx, Find{StructName}Query, pk1, ...) + // var s {Struct} + // if err := row.Scan( + // &s.{ColumnName1}, + // &i.{ColumnName2}, + // ) err != nil { + // return nil, fmt.Errorf("row.Scan: %w", err) + // } + // return &s, nil + // } + byPKFuncName := "Find" + structName + "ByPK" + byPKQueryName := byPKFuncName + "Query" + astFile.Decls = append(astFile.Decls, + &ast.GenDecl{ + Tok: token.CONST, + Specs: []ast.Spec{ + &ast.ValueSpec{ + Names: []*ast.Ident{{Name: byPKQueryName}}, + Values: []ast.Expr{&ast.BasicLit{ + Kind: token.STRING, + Value: "`SELECT " + strings.Join(columnNames, ", ") + " FROM " + tableName + " WHERE " + func() string { + var where []string + for _, pk := range pks { + where = append(where, pk.ColumnName+" = ?") + } + return strings.Join(where, " AND ") + }() + "`", + }}, + }, + }, + }, + &ast.FuncDecl{ + Name: &ast.Ident{Name: byPKFuncName}, + Recv: &ast.FieldList{List: []*ast.Field{{ + Names: []*ast.Ident{{Name: "q"}}, + Type: &ast.StarExpr{X: &ast.Ident{Name: "Queryer"}}, + }}}, + Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: append([]*ast.Field{ + { + Names: []*ast.Ident{{Name: "ctx"}}, + Type: &ast.Ident{Name: "context.Context"}, + }, + { + Names: []*ast.Ident{{Name: "sqlCtx"}}, + Type: &ast.Ident{Name: "sqlContext"}, + }, + }, + func() []*ast.Field { + fields := make([]*ast.Field, 0) + for _, pk := range pks { + fields = append(fields, &ast.Field{ + Names: []*ast.Ident{{Name: pk.ColumnName}}, + Type: &ast.Ident{Name: pk.FieldType}, + }) + } + return fields + }()...), + }, + Results: &ast.FieldList{List: []*ast.Field{ + {Type: &ast.StarExpr{X: &ast.Ident{Name: "dao." + structName}}}, + {Type: &ast.Ident{Name: "error"}}, + }}, + }, + Body: &ast.BlockStmt{ + // row, err := queryer.QueryRowContext(ctx, Find{StructName}Query, pk1, ...) + List: []ast.Stmt{ + &ast.AssignStmt{ + Lhs: []ast.Expr{&ast.Ident{Name: "row"}}, + Tok: token.DEFINE, + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: &ast.SelectorExpr{X: &ast.Ident{Name: "sqlCtx"}, Sel: &ast.Ident{Name: "QueryRowContext"}}, + Args: append( + []ast.Expr{ + &ast.Ident{Name: "ctx"}, + &ast.Ident{Name: byPKQueryName}, + }, + func() []ast.Expr { + var args []ast.Expr + for _, pk := range pks { + args = append(args, &ast.Ident{Name: pk.ColumnName}) + } + return args + }()...), + }}, + }, + // var s {Struct} + &ast.DeclStmt{Decl: &ast.GenDecl{ + Tok: token.VAR, + Specs: []ast.Spec{&ast.ValueSpec{ + Names: []*ast.Ident{{Name: "s"}}, + Type: &ast.Ident{Name: "dao." + structName}, + }}, + }}, + // if err := row.Scan(&s.{ColumnName1}, &s.{ColumnName2}); err != nil { + &ast.IfStmt{ + Init: &ast.AssignStmt{ + Lhs: []ast.Expr{&ast.Ident{Name: "err"}}, + Tok: token.DEFINE, + // row.Scan(&s.{ColumnName1}, &s.{ColumnName2}) + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: &ast.SelectorExpr{X: &ast.Ident{Name: "row"}, Sel: &ast.Ident{Name: "Scan"}}, + Args: func() []ast.Expr { + var args []ast.Expr + for _, c := range tableInfo.Columns { + args = append(args, &ast.UnaryExpr{ + Op: token.AND, + X: &ast.SelectorExpr{ + X: &ast.Ident{Name: "s"}, + Sel: &ast.Ident{Name: c.FieldName}, + }, + }) + } + return args + }(), + }}, + }, + Cond: &ast.BinaryExpr{X: &ast.Ident{Name: "err"}, Op: token.NEQ, Y: &ast.Ident{Name: "nil"}}, + Body: &ast.BlockStmt{List: []ast.Stmt{ + // return fmt.Errorf("row.Scan: %w", err) + &ast.ReturnStmt{Results: []ast.Expr{ + &ast.Ident{Name: "nil"}, + &ast.CallExpr{ + Fun: &ast.SelectorExpr{X: &ast.Ident{Name: "fmt"}, Sel: &ast.Ident{Name: "Errorf"}}, + Args: []ast.Expr{&ast.Ident{Name: strconv.Quote("row.Scan: %w")}, &ast.Ident{Name: "err"}}, + }, + }}, + }}, + }, + // return &s, nil + &ast.ReturnStmt{Results: []ast.Expr{&ast.UnaryExpr{Op: token.AND, X: &ast.Ident{Name: "s"}}, &ast.Ident{Name: "nil"}}}, + }, + }, + }, + ) + + hasOneColumnsByTag := tableInfo.HasOneTagColumnsByTag() + for _, hasOneTag := range tableInfo.HasOneTags { + // const Find{StructName}By{FieldName}Query = `SELECT {column_name1}, {column_name2} FROM {table_name} WHERE {column} = ? [AND ...]` + // + // func (q *Queryer) Find{StructName}ByColumn1[AndColumn2](ctx context.Context, queryer sqlContext, {ColumnName} {ColumnType} [, {Column2Name} {Column2Type}]) ({Struct}Slice, error) { + // row := queryer.QueryRowContext(ctx, Find{StructName}Query, {ColumnName}, {Column2Name}) + // var s {Struct} + // if err := row.Scan( + // &s.{ColumnName1}, + // &i.{ColumnName2}, + // ) err != nil { + // return nil, fmt.Errorf("row.Scan: %w", err) + // } + // return &s, nil + // } + byHasOneTagFuncName := "Find" + structName + "By" + hasOneTag + byHasOneTagQueryName := byHasOneTagFuncName + "Query" + hasOneColumns := hasOneColumnsByTag[hasOneTag] + astFile.Decls = append(astFile.Decls, + &ast.GenDecl{ + Tok: token.CONST, + Specs: []ast.Spec{ + &ast.ValueSpec{ + Names: []*ast.Ident{{Name: byHasOneTagQueryName}}, + Values: []ast.Expr{&ast.BasicLit{ + Kind: token.STRING, + Value: "`SELECT " + strings.Join(columnNames, ", ") + " FROM " + tableName + " WHERE " + func() string { + var where []string + for _, hasOneColumn := range hasOneColumns { + where = append(where, hasOneColumn.ColumnName+" = ?") + } + return strings.Join(where, " AND ") + }() + "`", + }}, + }, + }, + }, + &ast.FuncDecl{ + Name: &ast.Ident{Name: byHasOneTagFuncName}, + Recv: &ast.FieldList{List: []*ast.Field{{ + Names: []*ast.Ident{{Name: "q"}}, + Type: &ast.StarExpr{X: &ast.Ident{Name: "Queryer"}}, + }}}, + Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: append([]*ast.Field{ + { + Names: []*ast.Ident{{Name: "ctx"}}, + Type: &ast.Ident{Name: "context.Context"}, + }, + { + Names: []*ast.Ident{{Name: "sqlCtx"}}, + Type: &ast.Ident{Name: "sqlContext"}, + }, + }, + func() []*ast.Field { + fields := make([]*ast.Field, 0) + for _, hasOneColumn := range hasOneColumns { + fields = append(fields, &ast.Field{ + Names: []*ast.Ident{{Name: hasOneColumn.ColumnName}}, + Type: &ast.Ident{Name: hasOneColumn.FieldType}, + }) + } + return fields + }()...), + }, + Results: &ast.FieldList{List: []*ast.Field{ + {Type: &ast.StarExpr{X: &ast.Ident{Name: "dao." + structName}}}, + {Type: &ast.Ident{Name: "error"}}, + }}, + }, + Body: &ast.BlockStmt{ + // row, err := queryer.QueryRowContext(ctx, Find{StructName}Query, column1, ...) + List: []ast.Stmt{ + &ast.AssignStmt{ + Lhs: []ast.Expr{&ast.Ident{Name: "row"}}, + Tok: token.DEFINE, + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: &ast.SelectorExpr{X: &ast.Ident{Name: "sqlCtx"}, Sel: &ast.Ident{Name: "QueryRowContext"}}, + Args: append( + []ast.Expr{ + &ast.Ident{Name: "ctx"}, + &ast.Ident{Name: byHasOneTagQueryName}, + }, + func() []ast.Expr { + var args []ast.Expr + for _, c := range hasOneColumns { + args = append(args, &ast.Ident{Name: c.ColumnName}) + } + return args + }()...), + }}, + }, + // var s {Struct} + &ast.DeclStmt{Decl: &ast.GenDecl{ + Tok: token.VAR, + Specs: []ast.Spec{&ast.ValueSpec{ + Names: []*ast.Ident{{Name: "s"}}, + Type: &ast.Ident{Name: "dao." + structName}, + }}, + }}, + // if err := row.Scan(&s.{ColumnName1}, &s.{ColumnName2}); err != nil { + &ast.IfStmt{ + Init: &ast.AssignStmt{ + Lhs: []ast.Expr{&ast.Ident{Name: "err"}}, + Tok: token.DEFINE, + // row.Scan(&s.{ColumnName1}, &s.{ColumnName2}) + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: &ast.SelectorExpr{X: &ast.Ident{Name: "row"}, Sel: &ast.Ident{Name: "Scan"}}, + Args: func() []ast.Expr { + var args []ast.Expr + for _, c := range tableInfo.Columns { + args = append(args, &ast.UnaryExpr{ + Op: token.AND, + X: &ast.SelectorExpr{ + X: &ast.Ident{Name: "s"}, + Sel: &ast.Ident{Name: c.FieldName}, + }, + }) + } + return args + }(), + }}, + }, + Cond: &ast.BinaryExpr{X: &ast.Ident{Name: "err"}, Op: token.NEQ, Y: &ast.Ident{Name: "nil"}}, + Body: &ast.BlockStmt{List: []ast.Stmt{ + // return fmt.Errorf("row.Scan: %w", err) + &ast.ReturnStmt{Results: []ast.Expr{ + &ast.Ident{Name: "nil"}, + &ast.CallExpr{ + Fun: &ast.SelectorExpr{X: &ast.Ident{Name: "fmt"}, Sel: &ast.Ident{Name: "Errorf"}}, + Args: []ast.Expr{&ast.Ident{Name: strconv.Quote("row.Scan: %w")}, &ast.Ident{Name: "err"}}, + }, + }}, + }}, + }, + // return &s, nil + &ast.ReturnStmt{Results: []ast.Expr{&ast.UnaryExpr{Op: token.AND, X: &ast.Ident{Name: "s"}}, &ast.Ident{Name: "nil"}}}, + }, + }, + }, + ) + } + + hasManyColumnsByTag := tableInfo.HasManyTagColumnsByTag() + for _, hasManyTag := range tableInfo.HasManyTags { + // const List{StructName}By{FieldName}Query = `SELECT {column_name1}, {column_name2} FROM {table_name} WHERE {pk1} = ? [AND ...]` + // + // func (q *query) List{StructName}ByColumn1[AndColumn2](ctx context.Context, queryer sqlContext, {ColumnName} {ColumnType} [, {Column2Name} {Column2Type}]) ({Struct}Slice, error) { + // rows, err := queryer.QueryContext(ctx, List{StructName}Query, {ColumnName}, {Column2Name}) + // if err != nil { + // return nil, fmt.Errorf("queryer.QueryContext: %w", err) + // } + // var ss {Struct}Slice + // for rows.Next() { + // var s {Struct} + // if err := rows.Scan( + // &i.{ColumnName1}, + // &i.{ColumnName2}, + // ); err != nil { + // return nil, fmt.Errorf("rows.Scan: %w", err) + // } + // s = append(s, &i) + // } + // if err := rows.Close(); err != nil { + // return nil, fmt.Errorf("rows.Close: %w", err) + // } + // if err := rows.Err(); err != nil { + // return nil, fmt.Errorf("rows.Err: %w", err) + // } + // return ss, nil + // } + structSliceType := "[]*dao." + structName + if sliceSuffix := config.GoSliceTypeSuffix(); sliceSuffix != "" { + structSliceType = "dao." + structName + sliceSuffix + } + byHasOneTagFuncName := "List" + structName + "By" + hasManyTag + byHasOneTagQueryName := byHasOneTagFuncName + "Query" + hasManyColumns := hasManyColumnsByTag[hasManyTag] + astFile.Decls = append(astFile.Decls, + &ast.GenDecl{ + Tok: token.CONST, + Specs: []ast.Spec{ + &ast.ValueSpec{ + Names: []*ast.Ident{{Name: byHasOneTagQueryName}}, + Values: []ast.Expr{&ast.BasicLit{ + Kind: token.STRING, + Value: "`SELECT " + strings.Join(columnNames, ", ") + " FROM " + tableName + " WHERE " + func() string { + var where []string + for _, c := range hasManyColumns { + where = append(where, c.ColumnName+" = ?") + } + return strings.Join(where, " AND ") + }() + "`", + }}, + }, + }, + }, + &ast.FuncDecl{ + Name: &ast.Ident{Name: byHasOneTagFuncName}, + Recv: &ast.FieldList{List: []*ast.Field{{ + Names: []*ast.Ident{{Name: "q"}}, + Type: &ast.StarExpr{X: &ast.Ident{Name: "Queryer"}}, + }}}, + Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: append( + []*ast.Field{ + {Names: []*ast.Ident{{Name: "ctx"}}, Type: &ast.Ident{Name: "context.Context"}}, + {Names: []*ast.Ident{{Name: "sqlCtx"}}, Type: &ast.Ident{Name: "sqlContext"}}, + }, + func() []*ast.Field { + fields := make([]*ast.Field, 0) + for _, c := range hasManyColumns { + fields = append(fields, &ast.Field{ + Names: []*ast.Ident{{Name: c.ColumnName}}, + Type: &ast.Ident{Name: c.FieldType}, + }) + } + return fields + }()..., + ), + }, + Results: &ast.FieldList{List: []*ast.Field{ + {Type: &ast.Ident{Name: structSliceType}}, + {Type: &ast.Ident{Name: "error"}}, + }}, + }, + Body: &ast.BlockStmt{ + List: []ast.Stmt{ + &ast.AssignStmt{ + Lhs: []ast.Expr{&ast.Ident{Name: "rows"}, &ast.Ident{Name: "err"}}, + Tok: token.DEFINE, + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: &ast.SelectorExpr{X: &ast.Ident{Name: "sqlCtx"}, Sel: &ast.Ident{Name: "QueryContext"}}, + Args: append( + []ast.Expr{ + &ast.Ident{Name: "ctx"}, + &ast.Ident{Name: byHasOneTagQueryName}, + }, + func() []ast.Expr { + var args []ast.Expr + for _, c := range hasManyColumns { + args = append(args, &ast.Ident{Name: c.ColumnName}) + } + return args + }()..., + ), + }}, + }, + // if err != nil { + // return nil, fmt.Errorf("queryer.QueryContext: %w", err) + // } + &ast.IfStmt{ + Cond: &ast.BinaryExpr{X: &ast.Ident{Name: "err"}, Op: token.NEQ, Y: &ast.Ident{Name: "nil"}}, + Body: &ast.BlockStmt{List: []ast.Stmt{ + &ast.ReturnStmt{Results: []ast.Expr{ + &ast.Ident{Name: "nil"}, + &ast.CallExpr{ + Fun: &ast.SelectorExpr{X: &ast.Ident{Name: "fmt"}, Sel: &ast.Ident{Name: "Errorf"}}, + Args: []ast.Expr{&ast.BasicLit{Kind: token.STRING, Value: strconv.Quote("queryer.QueryContext: %w")}, &ast.Ident{Name: "err"}}, + }, + }}, + }}, + }, + // var ss {Struct}Slice + &ast.DeclStmt{Decl: &ast.GenDecl{ + Tok: token.VAR, + Specs: []ast.Spec{&ast.ValueSpec{ + Names: []*ast.Ident{{Name: "ss"}}, + Type: &ast.Ident{Name: structSliceType}, + }}, + }}, + // for rows.Next() { + &ast.ForStmt{ + Cond: &ast.CallExpr{ + Fun: &ast.SelectorExpr{X: &ast.Ident{Name: "rows"}, Sel: &ast.Ident{Name: "Next"}}, + }, + Body: &ast.BlockStmt{List: []ast.Stmt{ + // var s {Struct} + &ast.DeclStmt{Decl: &ast.GenDecl{ + Tok: token.VAR, + Specs: []ast.Spec{&ast.ValueSpec{ + Names: []*ast.Ident{{Name: "s"}}, + Type: &ast.Ident{Name: "dao." + structName}, + }}, + }}, + // if err := rows.Scan(&s.{ColumnName1}, &s.{ColumnName2}); err != nil { + &ast.IfStmt{ + Init: &ast.AssignStmt{ + Lhs: []ast.Expr{&ast.Ident{Name: "err"}}, + Tok: token.DEFINE, + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: &ast.SelectorExpr{X: &ast.Ident{Name: "rows"}, Sel: &ast.Ident{Name: "Scan"}}, + Args: func() []ast.Expr { + var args []ast.Expr + for _, c := range tableInfo.Columns { + args = append(args, &ast.UnaryExpr{ + Op: token.AND, + X: &ast.SelectorExpr{ + X: &ast.Ident{Name: "s"}, + Sel: &ast.Ident{Name: c.FieldName}, + }, + }) + } + return args + }(), + }}, + }, + Cond: &ast.BinaryExpr{X: &ast.Ident{Name: "err"}, Op: token.NEQ, Y: &ast.Ident{Name: "nil"}}, + Body: &ast.BlockStmt{List: []ast.Stmt{ + // return nil, fmt.Errorf("rows.Scan: %w", err) + &ast.ReturnStmt{ + Results: []ast.Expr{ + &ast.Ident{Name: "nil"}, + &ast.CallExpr{ + Fun: &ast.SelectorExpr{X: &ast.Ident{Name: "fmt"}, Sel: &ast.Ident{Name: "Errorf"}}, + Args: []ast.Expr{&ast.BasicLit{Kind: token.STRING, Value: strconv.Quote("rows.Scan: %w")}, &ast.Ident{Name: "err"}}, + }, + }, + }, + }}, + }, + // ss = append(ss, &s) + &ast.AssignStmt{ + Lhs: []ast.Expr{&ast.Ident{Name: "ss"}}, + Tok: token.ASSIGN, + Rhs: []ast.Expr{ + &ast.CallExpr{ + Fun: &ast.Ident{Name: "append"}, + Args: []ast.Expr{ + &ast.Ident{Name: "ss"}, + &ast.UnaryExpr{Op: token.AND, X: &ast.Ident{Name: "s"}}, + }, + }, + }, + }, + }}, + }, + // if err := rows.Close(); err != nil { + &ast.IfStmt{ + Init: &ast.AssignStmt{ + // err := rows.Close() + Lhs: []ast.Expr{&ast.Ident{Name: "err"}}, + Tok: token.DEFINE, + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: &ast.SelectorExpr{X: &ast.Ident{Name: "rows"}, Sel: &ast.Ident{Name: "Close"}}, + }}, + }, + Cond: &ast.BinaryExpr{X: &ast.Ident{Name: "err"}, Op: token.NEQ, Y: &ast.Ident{Name: "nil"}}, + Body: &ast.BlockStmt{ + List: []ast.Stmt{ + // return nil, fmt.Errorf("rows.Close: %w", err) + &ast.ReturnStmt{ + Results: []ast.Expr{ + &ast.Ident{Name: "nil"}, + &ast.CallExpr{ + Fun: &ast.SelectorExpr{X: &ast.Ident{Name: "fmt"}, Sel: &ast.Ident{Name: "Errorf"}}, + Args: []ast.Expr{&ast.BasicLit{Kind: token.STRING, Value: strconv.Quote("rows.Close: %w")}, &ast.Ident{Name: "err"}}, + }, + }, + }, + }, + }, + }, + // if err := rows.Err(); err != nil { + &ast.IfStmt{ + Init: &ast.AssignStmt{ + // err := rows.Err() + Lhs: []ast.Expr{&ast.Ident{Name: "err"}}, + Tok: token.DEFINE, + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: &ast.SelectorExpr{X: &ast.Ident{Name: "rows"}, Sel: &ast.Ident{Name: "Err"}}, + }}, + }, + Cond: &ast.BinaryExpr{X: &ast.Ident{Name: "err"}, Op: token.NEQ, Y: &ast.Ident{Name: "nil"}}, + Body: &ast.BlockStmt{ + List: []ast.Stmt{ + // return nil, fmt.Errorf("rows.Err: %w", err) + &ast.ReturnStmt{ + Results: []ast.Expr{ + &ast.Ident{Name: "nil"}, + &ast.CallExpr{ + Fun: &ast.SelectorExpr{X: &ast.Ident{Name: "fmt"}, Sel: &ast.Ident{Name: "Errorf"}}, + Args: []ast.Expr{&ast.BasicLit{Kind: token.STRING, Value: strconv.Quote("rows.Err: %w")}, &ast.Ident{Name: "err"}}, + }, + }, + }, + }, + }, + }, + // return ss, nil + &ast.ReturnStmt{Results: []ast.Expr{&ast.Ident{Name: "ss"}, &ast.Ident{Name: "nil"}}}, + }, + }, + }, + ) + } + } +} diff --git a/internal/arcgen/lang/go/generate_crud_update.go b/internal/arcgen/lang/go/generate_crud_update.go new file mode 100644 index 0000000..26a9cf4 --- /dev/null +++ b/internal/arcgen/lang/go/generate_crud_update.go @@ -0,0 +1,122 @@ +package arcgengo + +import ( + "go/ast" + "go/token" + "strconv" + "strings" +) + +//nolint:funlen +func generateUPDATEContent(astFile *ast.File, arcSrcSet *ARCSourceSet) { + for _, arcSrc := range arcSrcSet.ARCSourceSlice { + structName := arcSrc.extractStructName() + tableName := arcSrc.extractTableNameFromCommentGroup() + tableInfo := arcSrc.extractFieldNamesAndColumnNames() + + // const Update{StructName}Query = `UPDATE {table_name} SET ({column_name1}, {column_name2}) = (?, ?) WHERE {pk1} = ? [AND {pk2} = ?]` + // + // func (q *query) Update{StructName}(ctx context.Context, queryer sqlContext, s *{Struct}) error { + // if _, err := queryer.ExecContext(ctx, Update{StructName}Query, s.{ColumnName1}, s.{ColumnName2}, s.{PK1} [, s.{PK2}]); err != nil { + // return fmt.Errorf("q.queryer.ExecContext: %w", err) + // } + // return nil + // } + funcName := "Update" + structName + queryName := funcName + "Query" + pkColumns := tableInfo.PrimaryKeys() + pkColumnNames := func() (pkColumnNames []string) { + for _, c := range pkColumns { + pkColumnNames = append(pkColumnNames, c.ColumnName) + } + return pkColumnNames + }() + nonPKColumns := tableInfo.NonPrimaryKeys() + nonPKColumnNames := func() (nonPKColumnNames []string) { + for _, c := range nonPKColumns { + nonPKColumnNames = append(nonPKColumnNames, c.ColumnName) + } + return nonPKColumnNames + }() + astFile.Decls = append(astFile.Decls, + &ast.GenDecl{ + Tok: token.CONST, + Specs: []ast.Spec{ + &ast.ValueSpec{ + Names: []*ast.Ident{{Name: queryName}}, + Values: []ast.Expr{&ast.BasicLit{ + Kind: token.STRING, + Value: "`UPDATE " + tableName + " SET (" + strings.Join(nonPKColumnNames, ", ") + ") = (?" + strings.Repeat(", ?", len(nonPKColumns)-1) + ") WHERE " + strings.Join(pkColumnNames, " = ? AND ") + " = ?`", + }}, + }, + }, + }, + &ast.FuncDecl{ + Recv: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{{Name: "q"}}, Type: &ast.StarExpr{X: &ast.Ident{Name: "Queryer"}}}}}, + Name: &ast.Ident{Name: funcName}, + Type: &ast.FuncType{ + Params: &ast.FieldList{List: []*ast.Field{ + {Names: []*ast.Ident{{Name: "ctx"}}, Type: &ast.Ident{Name: "context.Context"}}, + {Names: []*ast.Ident{{Name: "sqlCtx"}}, Type: &ast.Ident{Name: "sqlContext"}}, + {Names: []*ast.Ident{{Name: "s"}}, Type: &ast.StarExpr{X: &ast.Ident{Name: "dao." + structName}}}, + }}, + Results: &ast.FieldList{List: []*ast.Field{ + {Type: &ast.Ident{Name: "error"}}, + }}, + }, + Body: &ast.BlockStmt{ + List: []ast.Stmt{ + &ast.IfStmt{ + // if _, err := queryer.ExecContext(ctx, Update{StructName}Query, s.{ColumnName1}, s.{ColumnName2}, s.{PK1} [, s.{PK2}]); err != nil { + Init: &ast.AssignStmt{ + Lhs: []ast.Expr{&ast.Ident{Name: "_"}, &ast.Ident{Name: "err"}}, + Tok: token.DEFINE, + Rhs: []ast.Expr{&ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{Name: "sqlCtx"}, + Sel: &ast.Ident{Name: "ExecContext"}, + }, + Args: append( + append( + []ast.Expr{ + &ast.Ident{Name: "ctx"}, + &ast.Ident{Name: queryName}, + }, + func() []ast.Expr { + var args []ast.Expr + for _, c := range nonPKColumns { + args = append(args, &ast.SelectorExpr{X: &ast.Ident{Name: "s"}, Sel: &ast.Ident{Name: c.FieldName}}) + } + return args + }()...), + func() []ast.Expr { + var args []ast.Expr + for _, c := range pkColumns { + args = append(args, &ast.SelectorExpr{X: &ast.Ident{Name: "s"}, Sel: &ast.Ident{Name: c.FieldName}}) + } + return args + }()..., + ), + }}, + }, + // err != nil { + Cond: &ast.BinaryExpr{X: &ast.Ident{Name: "err"}, Op: token.NEQ, Y: &ast.Ident{Name: "nil"}}, + Body: &ast.BlockStmt{List: []ast.Stmt{ + // return fmt.Errorf("queryer.ExecContext: %w", err) + &ast.ReturnStmt{Results: []ast.Expr{&ast.CallExpr{ + Fun: &ast.SelectorExpr{X: &ast.Ident{Name: "fmt"}, Sel: &ast.Ident{Name: "Errorf"}}, + Args: []ast.Expr{&ast.Ident{Name: strconv.Quote("queryer.ExecContext: %w")}, &ast.Ident{Name: "err"}}, + }}}, + }}, + }, + &ast.ReturnStmt{ + Results: []ast.Expr{ + &ast.Ident{Name: "nil"}, + }, + }, + }, + }, + }, + ) + } +} diff --git a/internal/arcgen/lang/go/generate_test.go b/internal/arcgen/lang/go/generate_test.go index 598d367..fa5297d 100644 --- a/internal/arcgen/lang/go/generate_test.go +++ b/internal/arcgen/lang/go/generate_test.go @@ -55,14 +55,14 @@ func TestGenerate(t *testing.T) { } }) - t.Run("failure,no.errsource", func(t *testing.T) { + t.Run("success,skipErrInDir", func(t *testing.T) { ctx := contexts.WithOSArgs(context.Background(), []string{ "arcgen", "--go-column-tag=dbtest", "--go-method-name-table=GetTableName", "--go-method-name-columns=GetColumnNames", "--go-method-prefix-column=GetColumnName_", - "tests/no.errsource", + "tests", }) backup := fileExt @@ -71,9 +71,9 @@ func TestGenerate(t *testing.T) { _, remainingArgs, err := config.Load(ctx) require.NoError(t, err) - fileExt = ".source" + fileExt = ".errsource" for _, src := range remainingArgs { - require.ErrorContains(t, Generate(ctx, src), "expected 'package', found 'EOF'") + require.NoError(t, Generate(ctx, src)) } }) @@ -84,7 +84,7 @@ func TestGenerate(t *testing.T) { "--go-method-name-table=GetTableName", "--go-method-name-columns=GetColumnNames", "--go-method-prefix-column=GetColumnName_", - "tests", + "tests/no.errsource", }) backup := fileExt @@ -93,9 +93,9 @@ func TestGenerate(t *testing.T) { _, remainingArgs, err := config.Load(ctx) require.NoError(t, err) - fileExt = ".errsource" + fileExt = ".source" for _, src := range remainingArgs { - require.ErrorContains(t, Generate(ctx, src), "expected 'package', found 'EOF'") + require.ErrorContains(t, Generate(ctx, src), "expected 'package', found ") } }) diff --git a/internal/arcgen/lang/go/parse.go b/internal/arcgen/lang/go/parse.go index 86f69ee..15c30d0 100644 --- a/internal/arcgen/lang/go/parse.go +++ b/internal/arcgen/lang/go/parse.go @@ -26,12 +26,12 @@ func parse(ctx context.Context, src string) (ARCSourceSetSlice, error) { } if info.IsDir() { - arcSrcSets := make(ARCSourceSetSlice, 0) - if err := filepath.WalkDir(sourceAbs, walkDirFn(ctx, &arcSrcSets)); err != nil { + arcSrcSetSlice := make(ARCSourceSetSlice, 0) + if err := filepath.WalkDir(sourceAbs, walkDirFn(ctx, &arcSrcSetSlice)); err != nil { return nil, errorz.Errorf("filepath.WalkDir: %w", err) } - return arcSrcSets, nil + return arcSrcSetSlice, nil } arcSrcSet, err := parseFile(ctx, sourceAbs) @@ -45,7 +45,7 @@ func parse(ctx context.Context, src string) (ARCSourceSetSlice, error) { //nolint:gochecknoglobals var fileExt = ".go" -func walkDirFn(ctx context.Context, arcSrcSets *ARCSourceSetSlice) func(path string, d os.DirEntry, err error) error { +func walkDirFn(ctx context.Context, arcSrcSetSlice *ARCSourceSetSlice) func(path string, d os.DirEntry, err error) error { return func(path string, d os.DirEntry, err error) error { if err != nil { return err //nolint:wrapcheck @@ -58,13 +58,15 @@ func walkDirFn(ctx context.Context, arcSrcSets *ARCSourceSetSlice) func(path str arcSrcSet, err := parseFile(ctx, path) if err != nil { if errors.Is(err, apperr.ErrGoColumnTagAnnotationNotFoundInSource) { - logs.Debug.Printf("SKIP: parseFile: file=%s: %v", path, err) + logs.Debug.Printf("SKIP: parseFile: %v", err) return nil } - return errorz.Errorf("parseFile: file=%s: %v", path, err) + + logs.Info.Printf("SKIP NON-GO FILE: parseFile: %v", err) + return nil } - *arcSrcSets = append(*arcSrcSets, arcSrcSet) + *arcSrcSetSlice = append(*arcSrcSetSlice, arcSrcSet) return nil } @@ -74,12 +76,13 @@ func parseFile(ctx context.Context, filename string) (*ARCSourceSet, error) { fset := token.NewFileSet() rootNode, err := parser.ParseFile(fset, filename, nil, parser.ParseComments) if err != nil { + // MEMO: parser.ParseFile err contains file path, so no need to log it return nil, errorz.Errorf("parser.ParseFile: %w", err) } arcSrcSet, err := extractSource(ctx, fset, rootNode) if err != nil { - return nil, errorz.Errorf("extractSource: %w", err) + return nil, errorz.Errorf("extractSource: filename=%s: %w", filename, err) } dumpSource(fset, arcSrcSet) diff --git a/internal/arcgen/lang/go/source.go b/internal/arcgen/lang/go/source.go index 913ad52..332f6a2 100644 --- a/internal/arcgen/lang/go/source.go +++ b/internal/arcgen/lang/go/source.go @@ -6,6 +6,7 @@ import ( "go/token" "reflect" "regexp" + "slices" "strings" "sync" @@ -73,29 +74,159 @@ func (a *ARCSource) extractTableNameFromCommentGroup() string { return "" } -func (a *ARCSource) extractFieldNamesAndColumnNames() ([]string, []string) { - fieldNames, columnNames := make([]string, 0, len(a.StructType.Fields.List)), make([]string, 0, len(a.StructType.Fields.List)) +type TableInfo struct { + HasOneTags []string + HasManyTags []string + Columns []*ColumnInfo +} + +func (t *TableInfo) ColumnNames() []string { + columnNames := make([]string, len(t.Columns)) + for i := range t.Columns { + columnNames[i] = t.Columns[i].ColumnName + } + return columnNames +} + +func (t *TableInfo) PrimaryKeys() []*ColumnInfo { + pks := make([]*ColumnInfo, 0, len(t.Columns)) + for _, column := range t.Columns { + if column.PK { + pks = append(pks, column) + } + } + return pks +} + +func (t *TableInfo) NonPrimaryKeys() []*ColumnInfo { + nonPks := make([]*ColumnInfo, 0, len(t.Columns)) + for _, column := range t.Columns { + if !column.PK { + nonPks = append(nonPks, column) + } + } + return nonPks +} + +func (t *TableInfo) HasOneTagColumnsByTag() map[string][]*ColumnInfo { + columns := make(map[string][]*ColumnInfo) + for _, hasOneTagInTable := range t.HasOneTags { + columns[hasOneTagInTable] = make([]*ColumnInfo, 0, len(t.Columns)) + for _, column := range t.Columns { + for _, hasOneTag := range column.HasOneTags { + if hasOneTagInTable == hasOneTag { + columns[hasOneTag] = append(columns[hasOneTag], column) + } + } + } + } + + return columns +} + +func (t *TableInfo) HasManyTagColumnsByTag() map[string][]*ColumnInfo { + columns := make(map[string][]*ColumnInfo) + for _, hasManyTagInTable := range t.HasManyTags { + columns[hasManyTagInTable] = make([]*ColumnInfo, 0, len(t.Columns)) + for _, column := range t.Columns { + for _, hasManyTag := range column.HasManyTags { + if hasManyTagInTable == hasManyTag { + columns[hasManyTag] = append(columns[hasManyTag], column) + } + } + } + } + + return columns +} + +type ColumnInfo struct { + FieldName string + FieldType string + ColumnName string + PK bool + HasOneTags []string + HasManyTags []string +} + +func fieldName(x ast.Expr) *ast.Ident { + switch t := x.(type) { + case *ast.Ident: + return t + case *ast.SelectorExpr: + if _, ok := t.X.(*ast.Ident); ok { + return t.Sel + } + case *ast.StarExpr: + return fieldName(t.X) + } + return nil +} + +//nolint:cyclop +func (a *ARCSource) extractFieldNamesAndColumnNames() *TableInfo { + tableInfo := &TableInfo{ + Columns: make([]*ColumnInfo, 0, len(a.StructType.Fields.List)), + } for _, field := range a.StructType.Fields.List { if field.Tag != nil { tag := reflect.StructTag(strings.Trim(field.Tag.Value, "`")) + // db tag switch columnName := tag.Get(config.GoColumnTag()); columnName { case "", "-": logs.Trace.Printf("SKIP: %s: field.Names=%s, columnName=%q", a.Source.String(), field.Names, columnName) // noop default: logs.Trace.Printf("%s: field.Names=%s, columnName=%q", a.Source.String(), field.Names, columnName) - fieldNames, columnNames = append(fieldNames, field.Names[0].Name), append(columnNames, columnName) + columnInfo := &ColumnInfo{ + FieldName: field.Names[0].Name, + FieldType: fieldName(field.Type).String(), + ColumnName: columnName, + } + // pk tag + switch pk := tag.Get(config.GoPKTag()); pk { + case "", "-": + logs.Trace.Printf("SKIP: %s: field.Names=%s, pk=%q", a.Source.String(), field.Names, pk) + // noop + default: + logs.Trace.Printf("%s: field.Names=%s, pk=%q", a.Source.String(), field.Names, pk) + columnInfo.PK = true + } + // hasOne tag + for _, hasOneTag := range strings.Split(tag.Get(config.GoHasOneTag()), ",") { + if hasOneTag != "" { + logs.Trace.Printf("%s: field.Names=%s, hasOneTag=%q", a.Source.String(), field.Names, hasOneTag) + tableInfo.HasOneTags = append(tableInfo.HasOneTags, hasOneTag) + columnInfo.HasOneTags = append(columnInfo.HasOneTags, hasOneTag) + } + } + // hasMany tag + for _, hasManyTag := range strings.Split(tag.Get(config.GoHasManyTag()), ",") { + if hasManyTag != "" { + logs.Trace.Printf("%s: field.Names=%s, hasManyTag=%q", a.Source.String(), field.Names, hasManyTag) + tableInfo.HasManyTags = append(tableInfo.HasManyTags, hasManyTag) + columnInfo.HasManyTags = append(columnInfo.HasManyTags, hasManyTag) + } + } + + tableInfo.Columns = append(tableInfo.Columns, columnInfo) } } } - return fieldNames, columnNames + slices.Sort(tableInfo.HasOneTags) + tableInfo.HasOneTags = slices.Compact(tableInfo.HasOneTags) + + return tableInfo } func (ss *ARCSourceSet) generateGoFileHeader() string { + return generateGoFileHeader() + + "// source: " + filepathz.Short(ss.Source.Filename) + "\n" +} + +func generateGoFileHeader() string { return "" + "// Code generated by arcgen. DO NOT EDIT." + "\n" + - "//" + "\n" + - "// source: " + filepathz.Short(ss.Source.Filename) + "\n" + - "\n" + "//" + "\n" } diff --git a/internal/config/config.go b/internal/config/config.go index 9d4f350..b88380a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -22,9 +22,14 @@ type config struct { Language string `json:"language"` // Golang GoColumnTag string `json:"go_column_tag"` + GoCRUDPackagePath string `json:"go_crud_package_path"` + GoCRUDPackageName string `json:"go_crud_package_name"` + GoHasManyTag string `json:"go_has_many_tag"` + GoHasOneTag string `json:"go_has_one_tag"` GoMethodNameTable string `json:"go_method_name_table"` GoMethodNameColumns string `json:"go_method_name_columns"` GoMethodPrefixColumn string `json:"go_method_prefix_column"` + GoPKTag string `json:"go_pk_tag"` GoSliceTypeSuffix string `json:"go_slice_type_suffix"` } @@ -81,6 +86,18 @@ const ( _OptionGoColumnTag = "go-column-tag" _EnvKeyGoColumnTag = "ARCGEN_GO_COLUMN_TAG" + _OptionGoCRUDPackagePath = "go-crud-package-path" + _EnvKeyGoCRUDPackagePath = "ARCGEN_GO_CRUD_PACKAGE_PATH" + + _OptionGoCRUDPackageName = "go-crud-package-name" + _EnvKeyGoCRUDPackageName = "ARCGEN_GO_CRUD_PACKAGE_NAME" + + _OptionGoHasManyTag = "go-has-many-tag" + _EnvKeyGoHasManyTag = "ARCGEN_GO_HAS_MANY_TAG" + + _OptionGoHasOneTag = "go-has-one-tag" + _EnvKeyGoHasOneTag = "ARCGEN_GO_HAS_ONE_TAG" + _OptionGoMethodNameTable = "go-method-name-table" _EnvKeyGoMethodNameTable = "ARCGEN_GO_METHOD_NAME_TABLE" @@ -90,6 +107,9 @@ const ( _OptionGoMethodPrefixColumn = "go-method-prefix-column" _EnvKeyGoMethodPrefixColumn = "ARCGEN_GO_METHOD_PREFIX_COLUMN" + _OptionGoPKTag = "go-pk-tag" + _EnvKeyGoPKTag = "ARCGEN_GO_PK_TAG" + _OptionGoSliceTypeSuffix = "go-slice-type-suffix" _EnvKeyGoSliceTypeSuffix = "ARCGEN_GO_SLICE_TYPE_SUFFIX" ) @@ -109,53 +129,70 @@ func load(ctx context.Context) (cfg *config, remainingArgs []string, err error) Default: cliz.Default(false), }, &cliz.BoolOption{ - Name: _OptionTrace, - Environment: _EnvKeyTrace, + Name: _OptionTrace, Environment: _EnvKeyTrace, Description: "trace mode enabled", Default: cliz.Default(false), }, &cliz.BoolOption{ - Name: _OptionDebug, - Environment: _EnvKeyDebug, + Name: _OptionDebug, Environment: _EnvKeyDebug, Description: "debug mode", Default: cliz.Default(false), }, &cliz.StringOption{ - Name: _OptionLanguage, - Environment: _EnvKeyLanguage, + Name: _OptionLanguage, Environment: _EnvKeyLanguage, Description: "programming language to generate DDL", Default: cliz.Default("go"), }, // Golang &cliz.StringOption{ - Name: _OptionGoColumnTag, - Environment: _EnvKeyGoColumnTag, + Name: _OptionGoColumnTag, Environment: _EnvKeyGoColumnTag, Description: "column annotation key for Go struct tag", Default: cliz.Default("db"), }, &cliz.StringOption{ - Name: _OptionGoMethodNameTable, - Environment: _EnvKeyGoMethodNameTable, + Name: _OptionGoCRUDPackagePath, Environment: _EnvKeyGoCRUDPackagePath, + Description: "package path for CRUD", + Default: cliz.Default(""), + }, + &cliz.StringOption{ + Name: _OptionGoCRUDPackageName, Environment: _EnvKeyGoCRUDPackageName, + Description: "package name for CRUD", + Default: cliz.Default(""), + }, + &cliz.StringOption{ + Name: _OptionGoHasManyTag, Environment: _EnvKeyGoHasManyTag, + Description: "\"hasMany\" annotation key for Go struct tag", + Default: cliz.Default("hasMany"), + }, + &cliz.StringOption{ + Name: _OptionGoHasOneTag, Environment: _EnvKeyGoHasOneTag, + Description: "\"hasOne\" annotation key for Go struct tag", + Default: cliz.Default("hasOne"), + }, + &cliz.StringOption{ + Name: _OptionGoMethodNameTable, Environment: _EnvKeyGoMethodNameTable, Description: "method name for table", Default: cliz.Default("TableName"), }, &cliz.StringOption{ - Name: _OptionGoMethodNameColumns, - Environment: _EnvKeyGoMethodNameColumns, + Name: _OptionGoMethodNameColumns, Environment: _EnvKeyGoMethodNameColumns, Description: "method name for columns", Default: cliz.Default("ColumnNames"), }, &cliz.StringOption{ - Name: _OptionGoMethodPrefixColumn, - Environment: _EnvKeyGoMethodPrefixColumn, + Name: _OptionGoPKTag, Environment: _EnvKeyGoPKTag, + Description: "primary key annotation key for Go struct tag", + Default: cliz.Default("pk"), + }, + &cliz.StringOption{ + Name: _OptionGoMethodPrefixColumn, Environment: _EnvKeyGoMethodPrefixColumn, Description: "method prefix for column name", Default: cliz.Default("ColumnName_"), }, &cliz.StringOption{ - Name: _OptionGoSliceTypeSuffix, - Environment: _EnvKeyGoSliceTypeSuffix, + Name: _OptionGoSliceTypeSuffix, Environment: _EnvKeyGoSliceTypeSuffix, Description: "suffix for slice type", - Default: cliz.Default(""), + Default: cliz.Default("Slice"), }, }, } @@ -172,9 +209,14 @@ func load(ctx context.Context) (cfg *config, remainingArgs []string, err error) Language: loadLanguage(ctx, cmd), // Golang GoColumnTag: loadGoColumnTag(ctx, cmd), + GoCRUDPackagePath: loadGoCRUDPackagePath(ctx, cmd), + GoCRUDPackageName: loadGoCRUDPackageName(ctx, cmd), + GoHasManyTag: loadGoHasManyTag(ctx, cmd), + GoHasOneTag: loadGoHasOneTag(ctx, cmd), GoMethodNameTable: loadGoMethodNameTable(ctx, cmd), GoMethodNameColumns: loadGoMethodNameColumns(ctx, cmd), GoMethodPrefixColumn: loadGoMethodPrefixColumn(ctx, cmd), + GoPKTag: loadGoPKTag(ctx, cmd), GoSliceTypeSuffix: loadGoSliceTypeSuffix(ctx, cmd), } diff --git a/internal/config/go_crud_package_name.go b/internal/config/go_crud_package_name.go new file mode 100644 index 0000000..ad9752e --- /dev/null +++ b/internal/config/go_crud_package_name.go @@ -0,0 +1,24 @@ +package config + +import ( + "context" + "path/filepath" + + cliz "github.com/kunitsucom/util.go/exp/cli" +) + +func loadGoCRUDPackageName(_ context.Context, cmd *cliz.Command) string { + v, _ := cmd.GetOptionString(_OptionGoCRUDPackageName) + return v +} + +func GoCRUDPackageName() string { + globalConfigMu.Lock() + defer globalConfigMu.Unlock() + + if globalConfig.GoCRUDPackageName == "" { + globalConfig.GoCRUDPackageName = filepath.Base(globalConfig.GoCRUDPackagePath) + } + + return globalConfig.GoCRUDPackageName +} diff --git a/internal/config/go_crud_package_path.go b/internal/config/go_crud_package_path.go new file mode 100644 index 0000000..3e1adc7 --- /dev/null +++ b/internal/config/go_crud_package_path.go @@ -0,0 +1,22 @@ +package config + +import ( + "context" + + cliz "github.com/kunitsucom/util.go/exp/cli" +) + +func loadGoCRUDPackagePath(_ context.Context, cmd *cliz.Command) string { + v, _ := cmd.GetOptionString(_OptionGoCRUDPackagePath) + return v +} + +func GoCRUDPackagePath() string { + globalConfigMu.RLock() + defer globalConfigMu.RUnlock() + return globalConfig.GoCRUDPackagePath +} + +func GenerateGoCRUDPackage() bool { + return GoCRUDPackagePath() != "" +} diff --git a/internal/config/go_has_many_tag.go b/internal/config/go_has_many_tag.go new file mode 100644 index 0000000..e3f5888 --- /dev/null +++ b/internal/config/go_has_many_tag.go @@ -0,0 +1,18 @@ +package config + +import ( + "context" + + cliz "github.com/kunitsucom/util.go/exp/cli" +) + +func loadGoHasManyTag(_ context.Context, cmd *cliz.Command) string { + v, _ := cmd.GetOptionString(_OptionGoHasManyTag) + return v +} + +func GoHasManyTag() string { + globalConfigMu.RLock() + defer globalConfigMu.RUnlock() + return globalConfig.GoHasManyTag +} diff --git a/internal/config/go_has_one_tag.go b/internal/config/go_has_one_tag.go new file mode 100644 index 0000000..9a361df --- /dev/null +++ b/internal/config/go_has_one_tag.go @@ -0,0 +1,18 @@ +package config + +import ( + "context" + + cliz "github.com/kunitsucom/util.go/exp/cli" +) + +func loadGoHasOneTag(_ context.Context, cmd *cliz.Command) string { + v, _ := cmd.GetOptionString(_OptionGoHasOneTag) + return v +} + +func GoHasOneTag() string { + globalConfigMu.RLock() + defer globalConfigMu.RUnlock() + return globalConfig.GoHasOneTag +} diff --git a/internal/config/go_pk_tag.go b/internal/config/go_pk_tag.go new file mode 100644 index 0000000..b5853bf --- /dev/null +++ b/internal/config/go_pk_tag.go @@ -0,0 +1,18 @@ +package config + +import ( + "context" + + cliz "github.com/kunitsucom/util.go/exp/cli" +) + +func loadGoPKTag(_ context.Context, cmd *cliz.Command) string { + v, _ := cmd.GetOptionString(_OptionGoPKTag) + return v +} + +func GoPKTag() string { + globalConfigMu.RLock() + defer globalConfigMu.RUnlock() + return globalConfig.GoPKTag +}