github.com/d0sbit/gocode@v0.0.0-20211001144653-a968ce917518/cmd/gocode_handlercrud/main.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "embed" 6 "flag" 7 "fmt" 8 "io/fs" 9 "log" 10 "os" 11 "path" 12 "path/filepath" 13 "sort" 14 "strings" 15 "text/template" 16 17 "github.com/d0sbit/gocode/config" 18 "github.com/d0sbit/gocode/srcedit" 19 "github.com/d0sbit/gocode/srcedit/diff" 20 "github.com/d0sbit/gocode/srcedit/model" 21 "github.com/psanford/memfs" 22 "github.com/pterm/pterm" 23 ) 24 25 //go:embed handlercrud.tmpl 26 var defaultTmplFS embed.FS 27 28 func main() { 29 os.Exit(maine( 30 flag.NewFlagSet(os.Args[0], flag.PanicOnError), 31 os.Args[1:])) 32 } 33 34 // maine is broken out so it can be tested separately 35 func maine(flagSet *flag.FlagSet, args []string) int { 36 37 // typeF := flagSet.String("type", "", "Type name of Go struct with fields corresponding to the MongoDB document") 38 // fileF := flagSet.String("file", "", "Filename for the main type into which to add store code") 39 // testFileF := flagSet.String("test-file", "", "Test filename into which to add code, defaults to file with _test.go suffix") 40 // storeFileF := flagSet.String("store-file", "store.go", "Filename for the Store type") 41 // storeTestFileF := flagSet.String("store-test-file", "store_test.go", "Filename for the Store type tests") 42 // packageF := flagSet.String("package", "", "Package directory within module to analyze/edit") 43 // migrationsPackageF := flagSet.String("migrations-package", "", "Package directory to use for migrations, will default to ../migrations resolved against the package directory") 44 dryRunF := flagSet.Bool("dry-run", false, "Do not apply changes, only output diff of what would change.") 45 noGofmtF := flagSet.Bool("no-gofmt", false, "Do not gofmt the output") 46 // jsonF := flagSet.Bool("json", false, "Write output as JSON") 47 vF := flagSet.Bool("v", false, "Verbose output") 48 // allF := flagSet.Bool("all", false, "Generate all methods") 49 50 flagSet.Parse(args) 51 52 pterm.Info.Println("Hello!") 53 54 _ = vF 55 56 fileArgs := flagSet.Args() 57 if len(fileArgs) != 1 { 58 log.Fatalf("you must provide exactly one file name (found %d instead)", len(fileArgs)) 59 } 60 fileArg := fileArgs[0] 61 62 // absFileArg, err := filepath.Abs(fileArg) 63 // if err != nil { 64 // log.Fatal(err) 65 // } 66 67 // fileNamePart := filepath.Base(absFileArg) 68 fileNamePart := filepath.Base(fileArg) 69 70 rootFS, modDir, wdPackagePath, modPath, err := srcedit.FindOSWdModuleDir(filepath.Dir(fileArg)) 71 if err != nil { 72 log.Fatalf("error finding module directory: %v", err) 73 } 74 // _, _, _, _ = rootFS, modDir, wdPackagePath, modPath 75 // log.Printf("FindOSWdModuleDir(%q) returned rootFS=%q, modDir=%q, wdPackagePath=%q, modPath=%q", 76 // absFileArg, rootFS, modDir, wdPackagePath, modPath) 77 if *vF { 78 log.Printf("FindOSWdModuleDir(%q) returned rootFS=%q, modDir=%q, wdPackagePath=%q, modPath=%q", 79 fileArg, rootFS, modDir, wdPackagePath, modPath) 80 } 81 82 // set up file systems 83 inFS, err := fs.Sub(rootFS, modDir) 84 if err != nil { 85 log.Fatalf("fs.Sub error while construct input fs: %v", err) 86 } 87 88 // output is either same as input or memory for dry-run 89 var outFS fs.FS 90 var dryRunFS *memfs.FS 91 if !*dryRunF { 92 outFS = inFS 93 // if migrationsPackagePath != "" { 94 // mda, ok := outFS.(srcedit.MkdirAller) 95 // if ok { 96 // err := mda.MkdirAll(migrationsPackagePath, 0755) 97 // if err != nil { 98 // log.Fatalf("mda.MkdirAll for %q: %v", migrationsPackagePath, err) 99 // } 100 // } 101 // //outFS.MkdirAll(migrationsPackagePath, 0755) 102 // } 103 } else { 104 dryRunFS = memfs.New() 105 // if packagePath != "" { 106 // dryRunFS.MkdirAll(packagePath, 0755) 107 // } 108 // if migrationsPackagePath != "" { 109 // // log.Printf("creating migrationsPackagePath %q", migrationsPackagePath) 110 // err := dryRunFS.MkdirAll(migrationsPackagePath, 0755) 111 // // log.Printf("migrations MkdirAll returned: %v", err) 112 // _ = err 113 // } 114 outFS = dryRunFS 115 } 116 117 // FIXME: how does config work with dry run? (it probably should be part of the dry-run output) 118 // which means the dry run FS stuff should move up here 119 120 // load config 121 // moduleFS, err := fs.Sub(rootFS, modDir) 122 // if err != nil { 123 // log.Fatal(err) 124 // } 125 config, err := config.LoadFS(inFS, true) 126 if err != nil { 127 log.Fatalf("config.LoadFS failed: %v", err) 128 } 129 // TODO: we should distinguish between the directory with stores and the directory with types at some later point, 130 // but for now we assume these are always the same 131 storeDir := config.GetString("store_dir", "store") 132 133 handlersDir := config.GetString("handlers_dir", "handlers") 134 135 // TODO: make functions that: 136 // 1. verify a given path matches the specified suffix (i.e. "is this in the handlers folder") 137 // 2. given that we are in one folder, then translate to another (i.e. "verify we are in the handlers folder and give me the path for the store folder") 138 139 if !srcedit.DirHasSuffix(wdPackagePath, handlersDir) { 140 log.Fatalf("%q is not in the handlers_dir %q", wdPackagePath, handlersDir) 141 } 142 143 // make sure the handlers dir exists in the output 144 outFSMDA, ok := outFS.(srcedit.MkdirAller) 145 if ok { 146 err := outFSMDA.MkdirAll(wdPackagePath, 0755) 147 if err != nil { 148 log.Fatalf("MkdirAll for %q: %v", wdPackagePath, err) 149 } 150 } 151 152 storePkgPath, err := srcedit.DirResolveTo(wdPackagePath, handlersDir, storeDir) 153 if err != nil { 154 log.Fatal(err) 155 } 156 if *vF { 157 log.Printf("storePkgPath: %s", storePkgPath) 158 } 159 160 // NVM: if there's no store then there's no types so don't bother 161 // check if storeDir exists, if not then prompt before mkdir 162 // Store directory %q does not exist, press Ctrl+C to abort, enter to create it, or enter a new directory name to use that instead: 163 // save to config if they entered anything 164 165 // that handles: type package... maybe check for "store" and/or prompt 166 167 // parse the store/type package 168 storePkg := srcedit.NewPackage(inFS, outFS, modPath, storePkgPath) 169 170 // and handlers package while we're at it 171 handlersPkg := srcedit.NewPackage(inFS, outFS, modPath, wdPackagePath) 172 _ = handlersPkg 173 174 // look at the file name and check for a matching type 175 typeSearch := strings.TrimSuffix(fileNamePart, ".go") 176 typeInfo, err := storePkg.FindTypeLoose(typeSearch) 177 if err != nil { 178 log.Fatalf("failed to find type for %q: %v", typeSearch, err) 179 } 180 _ = typeInfo 181 182 // file is the file 183 // test file is formed by just adding _test.go 184 // handler package is folder 185 // then store in config file (use https://github.com/BurntSushi/toml for now and figure out comment preservation later) 186 187 // that should be what we need to 188 // parse the template 189 // emit everything 190 191 s, err := model.NewStruct(typeInfo, "") 192 if err != nil { 193 log.Fatalf("NewStruct failed: %v", err) 194 } 195 196 // execute template 197 data := struct { 198 Struct *model.Struct 199 StoreImportPath string 200 }{ 201 Struct: s, 202 StoreImportPath: path.Join(modPath, storePkgPath), 203 } 204 tmpl, err := template.New("_main_").Funcs(funcMap).ParseFS(defaultTmplFS, "handlercrud.tmpl") 205 if err != nil { 206 log.Fatalf("template parse error: %v", err) 207 } 208 209 fmtt := &srcedit.GofmtTransform{} 210 var trs []srcedit.Transform 211 212 { 213 fn := "handlerutil.go" 214 fmtt.FilenameList = append(fmtt.FilenameList, fn) 215 trList, err := tmplToTransforms(fn, data, tmpl, "HandlerUtil") 216 if err != nil { 217 log.Fatalf("tmplToTransforms for %q error: %v", fn, err) 218 } 219 trs = append(trs, trList...) 220 } 221 222 { 223 // fn := filepath.Join(wdPackagePath, fileNamePart) 224 fn := fileNamePart 225 fmtt.FilenameList = append(fmtt.FilenameList, fn) 226 trList, err := tmplToTransforms(fn, data, tmpl, "Handler", "HandlerMethods") 227 if err != nil { 228 log.Fatalf("tmplToTransforms for %q error: %v", fn, err) 229 } 230 trs = append(trs, trList...) 231 } 232 233 { 234 // fn := filepath.Join(wdPackagePath, strings.TrimSuffix(fileNamePart, ".go")+"_test.go") 235 fn := strings.TrimSuffix(fileNamePart, ".go") + "_test.go" 236 fmtt.FilenameList = append(fmtt.FilenameList, fn) 237 trList, err := tmplToTransforms(fn, data, tmpl, "TestHandler") 238 if err != nil { 239 log.Fatalf("tmplToTransforms for %q error: %v", fn, err) 240 } 241 trs = append(trs, trList...) 242 } 243 244 dd := &srcedit.DedupImportsTransform{ 245 FilenameList: fmtt.FilenameList, 246 } 247 trs = append(trs, dd) 248 249 if !*noGofmtF { 250 trs = append(trs, fmtt) 251 } 252 253 err = handlersPkg.ApplyTransforms(trs...) 254 if err != nil { 255 log.Fatalf("apply transform error: %v", err) 256 } 257 258 if *dryRunF { 259 diffMap, err := diff.Run(inFS, outFS, ".", "term") 260 if err != nil { 261 log.Fatalf("error running diff: %v", err) 262 } 263 // if *jsonF { 264 // enc := json.NewEncoder(os.Stdout) 265 // enc.Encode(map[string]interface{}{ 266 // "diff": diffMap, 267 // }) 268 // } else { 269 klist := make([]string, 0, len(diffMap)) 270 for k := range diffMap { 271 klist = append(klist, k) 272 } 273 sort.Strings(klist) 274 for _, k := range klist { 275 fmt.Printf("### %s\n", k) 276 fmt.Println(diffMap[k]) 277 } 278 // } 279 } 280 281 // ---- 282 283 // _ = storeTestFileF 284 285 // typeName := *typeF 286 // if typeName == "" { 287 // log.Fatalf("-type is required") 288 // } 289 290 // typeFilename := *fileF 291 // if typeFilename == "" { 292 // typeFilename = srcedit.LowerForType(typeName+"Store", "-") + ".go" 293 // } 294 295 // if *testFileF == "" { 296 // *testFileF = strings.TrimSuffix(typeFilename, ".go") + "_test.go" 297 // } 298 299 // rootFS, modDir, packagePath, modPath, err := srcedit.FindOSWdModuleDir(*packageF) 300 // if err != nil { 301 // log.Fatalf("error finding module directory: %v", err) 302 // } 303 // if *vF { 304 // log.Printf("rootFS=%v; modDir=%q, packagePath=%q, modPath=%q", rootFS, modDir, packagePath, modPath) 305 // } 306 307 // migrationsPackagePath := *migrationsPackageF 308 // if migrationsPackagePath == "" { 309 // migrationsPackagePath = strings.TrimPrefix(path.Join(packagePath, "../migrations"), "/") 310 // } 311 312 // // set up file systems 313 // inFS, err := fs.Sub(rootFS, modDir) 314 // if err != nil { 315 // log.Fatalf("fs.Sub error while construct input fs: %v", err) 316 // } 317 318 // // output is either same as input or memory for dry-run 319 // var outFS fs.FS 320 // var dryRunFS *memfs.FS 321 // if *dryRunF == "off" { 322 // outFS = inFS 323 // if migrationsPackagePath != "" { 324 // mda, ok := outFS.(srcedit.MkdirAller) 325 // if ok { 326 // err := mda.MkdirAll(migrationsPackagePath, 0755) 327 // if err != nil { 328 // log.Fatalf("mda.MkdirAll for %q: %v", migrationsPackagePath, err) 329 // } 330 // } 331 // //outFS.MkdirAll(migrationsPackagePath, 0755) 332 // } 333 // } else { 334 // dryRunFS = memfs.New() 335 // if packagePath != "" { 336 // dryRunFS.MkdirAll(packagePath, 0755) 337 // } 338 // if migrationsPackagePath != "" { 339 // // log.Printf("creating migrationsPackagePath %q", migrationsPackagePath) 340 // err := dryRunFS.MkdirAll(migrationsPackagePath, 0755) 341 // // log.Printf("migrations MkdirAll returned: %v", err) 342 // _ = err 343 // } 344 // outFS = dryRunFS 345 // } 346 347 // // load the package with srcedit 348 // pkg := srcedit.NewPackage(inFS, outFS, modPath, packagePath) 349 350 // // load the migrations package 351 // log.Printf("NewPackage for migrations: inFS=%#v, outFS=%#v, modPath=%#v, migrationsPackagePath=%#v", 352 // inFS, outFS, modPath, migrationsPackagePath) 353 // migrationsPkg := srcedit.NewPackage(inFS, outFS, modPath, migrationsPackagePath) 354 355 // // get the definition for the specified type 356 // typeInfo, err := pkg.FindType(typeName) 357 // if err != nil { 358 // log.Fatalf("failed to find type %q: %v", typeName, err) 359 // } 360 361 // // populate Struct 362 // // s := Struct{ 363 // // pkgImportedName: "", // TOOD: figure out what to do with prefixed types (not in the same package) 364 // // name: typeName, 365 // // typeInfo: typeInfo, 366 // // } 367 // // s.fields, err = s.makeFields() 368 // // if err != nil { 369 // // log.Fatalf("failed to extract field info from type %q: %v", typeName, err) 370 // // } 371 372 // s, err := model.NewStruct(typeInfo, "") 373 // if err != nil { 374 // log.Fatalf("failed to find type %q: %v", typeName, err) 375 // } 376 377 // // execute template 378 // data := struct { 379 // Struct *model.Struct 380 // MigrationsImportPath string 381 // }{ 382 // Struct: s, 383 // MigrationsImportPath: modPath + "/" + migrationsPackagePath, 384 // } 385 // tmpl, err := template.New("_main_").Funcs(funcMap).ParseFS(defaultTmplFS, "sqlcrud.tmpl") 386 // if err != nil { 387 // log.Fatalf("template parse error: %v", err) 388 // } 389 390 // fmtt := &srcedit.GofmtTransform{} 391 // var trs []srcedit.Transform 392 393 // { 394 // fn := "sqlutil.go" 395 // fmtt.FilenameList = append(fmtt.FilenameList, fn) 396 // trList, err := tmplToTransforms(fn, data, tmpl, "SQLUtil") 397 // if err != nil { 398 // log.Fatal(err) 399 // } 400 // trs = append(trs, trList...) 401 // } 402 403 // { 404 // fn := *storeFileF 405 // fmtt.FilenameList = append(fmtt.FilenameList, fn) 406 // trList, err := tmplToTransforms(fn, data, tmpl, "Store", "StoreMethods") 407 // if err != nil { 408 // log.Fatal(err) 409 // } 410 // trs = append(trs, trList...) 411 // } 412 413 // { 414 // fn := *storeTestFileF 415 // fmtt.FilenameList = append(fmtt.FilenameList, fn) 416 // trList, err := tmplToTransforms(fn, data, tmpl, "TestStore") 417 // if err != nil { 418 // log.Fatal(err) 419 // } 420 // trs = append(trs, trList...) 421 // } 422 423 // { 424 // fn := typeFilename 425 // fmtt.FilenameList = append(fmtt.FilenameList, fn) 426 // trList, err := tmplToTransforms(fn, data, tmpl, 427 // "TYPEStore", 428 // "TYPEStoreMethods", 429 // // FIXME: filter which things go here base on flags 430 // "TYPEInsert", 431 // "TYPEDelete", 432 // "TYPEUpdate", 433 // "TYPESelectByID", 434 // "TYPESelect", 435 // "TYPESelectCursor", 436 // "TYPECount", 437 // ) 438 // if err != nil { 439 // log.Fatal(err) 440 // } 441 // trs = append(trs, trList...) 442 // } 443 444 // { 445 // // TODO: -no-test flag 446 // fn := *testFileF 447 // fmtt.FilenameList = append(fmtt.FilenameList, fn) 448 // trList, err := tmplToTransforms(fn, data, tmpl, 449 // "TestTYPE", 450 // ) 451 // if err != nil { 452 // log.Fatal(err) 453 // } 454 // trs = append(trs, trList...) 455 // } 456 457 // dd := &srcedit.DedupImportsTransform{ 458 // FilenameList: fmtt.FilenameList, 459 // } 460 // trs = append(trs, dd) 461 462 // if !*noGofmtF { 463 // trs = append(trs, fmtt) 464 // } 465 466 // err = pkg.ApplyTransforms(trs...) 467 // if err != nil { 468 // log.Fatalf("apply transform error: %v", err) 469 // } 470 471 // // TODO: option to skip migrations stuff? 472 // // do migrations package separately 473 // { 474 // fmtt := &srcedit.GofmtTransform{} 475 // var trs []srcedit.Transform 476 477 // fn := "migrations.go" 478 // fmtt.FilenameList = append(fmtt.FilenameList, fn) 479 // trList, err := tmplToTransforms(fn, data, tmpl, "Migrations") 480 // if err != nil { 481 // log.Fatal(err) 482 // } 483 // trs = append(trs, trList...) 484 485 // dd := &srcedit.DedupImportsTransform{ 486 // FilenameList: fmtt.FilenameList, 487 // } 488 // trs = append(trs, dd) 489 490 // if !*noGofmtF { 491 // trs = append(trs, fmtt) 492 // } 493 494 // err = migrationsPkg.ApplyTransforms(trs...) 495 // if err != nil { 496 // log.Fatalf("apply transform for migrations error: %v", err) 497 // } 498 499 // // mpf, err := inFS.Open(migrationsPackagePath) 500 501 // needSampleMigration := true 502 // err = fs.WalkDir(inFS, migrationsPackagePath, fs.WalkDirFunc(func(path string, d fs.DirEntry, err error) error { 503 // if err != nil { 504 // return err 505 // } 506 // if path == migrationsPackagePath { 507 // return nil 508 // } 509 // if d.IsDir() { // only scan the immediate directory 510 // return fs.SkipDir 511 // } 512 // if strings.HasSuffix(path, ".sql") || strings.HasSuffix(path, ".SQL") { 513 // needSampleMigration = false 514 // } 515 // return nil 516 // })) 517 // if err != nil && !os.IsNotExist(err) { 518 // log.Fatalf("error walking migrations dir %q: %v", migrationsPackagePath, err) 519 // } 520 521 // if needSampleMigration { 522 // b := []byte(` 523 // -- +goose Up 524 525 // -- +goose Down 526 527 // `) 528 // fname := time.Now().UTC().Format("20060102150405") + "_sample.sql" 529 // fpath := path.Join(migrationsPackagePath, fname) 530 // err := outFS.(srcedit.FileWriter).WriteFile(fpath, b, 0644) 531 // if err != nil { 532 // log.Fatalf("error creating example migration file %q: %v", fpath, err) 533 // } 534 // } 535 536 // } 537 538 // if *dryRunF != "off" { 539 // diffMap, err := diff.Run(inFS, outFS, ".", *dryRunF) 540 // if err != nil { 541 // log.Fatalf("error running diff: %v", err) 542 // } 543 // if *jsonF { 544 // enc := json.NewEncoder(os.Stdout) 545 // enc.Encode(map[string]interface{}{ 546 // "diff": diffMap, 547 // }) 548 // } else { 549 // klist := make([]string, 0, len(diffMap)) 550 // for k := range diffMap { 551 // klist = append(klist, k) 552 // } 553 // sort.Strings(klist) 554 // for _, k := range klist { 555 // fmt.Printf("### %s\n", k) 556 // fmt.Println(diffMap[k]) 557 // } 558 // } 559 // } 560 561 return 0 562 } 563 564 func tmplToTransforms(fileName string, data interface{}, tmpl *template.Template, tmplName ...string) ([]srcedit.Transform, error) { 565 566 var ret []srcedit.Transform 567 568 for _, tName := range tmplName { 569 var buf bytes.Buffer 570 err := tmpl.ExecuteTemplate(&buf, tName, data) 571 if err != nil { 572 return ret, fmt.Errorf("%q template exec error: %v", tName, err) 573 } 574 575 trList, err := srcedit.ParseTransforms(fileName, buf.String()) 576 if err != nil { 577 return ret, fmt.Errorf("%q transform parse error: %v", tName, err) 578 } 579 ret = append(ret, trList...) 580 581 } 582 583 return ret, nil 584 585 } 586 587 var funcMap = template.FuncMap(map[string]interface{}{ 588 "LowerForType": srcedit.LowerForType, 589 })