github.com/d0sbit/gocode@v0.0.0-20211001144653-a968ce917518/cmd/gocode_sqlcrud/main.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "embed" 6 "encoding/json" 7 "flag" 8 "fmt" 9 "io/fs" 10 "log" 11 "os" 12 "path" 13 "path/filepath" 14 "sort" 15 "strings" 16 "text/template" 17 "time" 18 19 "github.com/psanford/memfs" 20 21 "github.com/d0sbit/gocode/srcedit" 22 "github.com/d0sbit/gocode/srcedit/diff" 23 "github.com/d0sbit/gocode/srcedit/model" 24 ) 25 26 //go:embed sqlcrud.tmpl 27 var defaultTmplFS embed.FS 28 29 func main() { 30 os.Exit(maine( 31 flag.NewFlagSet(os.Args[0], flag.PanicOnError), 32 os.Args[1:])) 33 } 34 35 // maine is broken out so it can be tested separately 36 func maine(flagSet *flag.FlagSet, args []string) int { 37 38 typeF := flagSet.String("type", "", "Type name of Go struct with fields corresponding to the MongoDB document") 39 fileF := flagSet.String("file", "", "Filename for the main type into which to add store code") 40 testFileF := flagSet.String("test-file", "", "Test filename into which to add code, defaults to file with _test.go suffix") 41 storeFileF := flagSet.String("store-file", "store.go", "Filename for the Store type") 42 storeTestFileF := flagSet.String("store-test-file", "store_test.go", "Filename for the Store type tests") 43 packageF := flagSet.String("package", "", "Package directory within module to analyze/edit") 44 migrationsPackageF := flagSet.String("migrations-package", "", "Package directory to use for migrations, will default to ../migrations resolved against the package directory") 45 dryRunF := flagSet.String("dry-run", "off", "Do not apply changes, only output diff of what would change. Value specifies format, 'term' for terminal pretty text, 'html' for HTML, or 'off' to disable.") 46 noGofmtF := flagSet.Bool("no-gofmt", false, "Do not gofmt the output") 47 jsonF := flagSet.Bool("json", false, "Write output as JSON") 48 vF := flagSet.Bool("v", false, "Verbose output") 49 // allF := flagSet.Bool("all", false, "Generate all methods") 50 51 flagSet.Parse(args) 52 53 fileArgList := flagSet.Args() 54 if len(fileArgList) > 1 { 55 log.Fatalf("you cannot specify more than one file (%d found)", len(fileArgList)) 56 } 57 58 // FIXME: for now if they provide an arg we just infer the various other params and overwrite them, 59 // this should be cleaned up at some point so it is consistent with gocode_handlercrud and whatever else 60 if len(fileArgList) == 1 { 61 fileArg := fileArgList[0] 62 63 rootFS, modDir, packagePath, modPath, err := srcedit.FindOSWdModuleDir(filepath.Dir(fileArg)) 64 if err != nil { 65 log.Fatalf("error finding module directory: %v", err) 66 } 67 // if *vF { 68 // log.Printf("file arg step: rootFS=%v; modDir=%q, packagePath=%q, modPath=%q", rootFS, modDir, packagePath, modPath) 69 // } 70 inFS, err := fs.Sub(rootFS, modDir) 71 if err != nil { 72 log.Fatalf("fs.Sub error while construct input fs: %v", err) 73 } 74 75 // we won't be writing here so don't bother with outFS 76 storePkg := srcedit.NewPackage(inFS, inFS, modPath, packagePath) 77 78 // now that we have all of this stuff hacked in here, 79 // we can look for the type based on the file name 80 typeSearch := strings.TrimSuffix(filepath.Base(fileArg), ".go") 81 typeInfo, err := storePkg.FindTypeLoose(typeSearch) 82 if err != nil { 83 log.Fatalf("failed to find type for %q: %v", typeSearch, err) 84 } 85 86 // ast.Print(typeInfo.FileSet, typeInfo.GenDecl) 87 *typeF = typeInfo.Name() 88 89 *fileF = filepath.Base(fileArg) 90 91 // testFileF can just be inferred below 92 // storeFileF and storeTestFileF are fine as-is too 93 94 // packageF needs to come from FindOSWdModuleDir 95 *packageF = packagePath 96 97 } 98 99 typeName := *typeF 100 if typeName == "" { 101 log.Fatalf("-type is required") 102 } 103 104 typeFilename := *fileF 105 if typeFilename == "" { 106 typeFilename = srcedit.LowerForType(typeName+"Store", "-") + ".go" 107 } 108 109 if *testFileF == "" { 110 *testFileF = strings.TrimSuffix(typeFilename, ".go") + "_test.go" 111 } 112 113 rootFS, modDir, packagePath, modPath, err := srcedit.FindOSWdModuleDir(*packageF) 114 if err != nil { 115 log.Fatalf("error finding module directory: %v", err) 116 } 117 if *vF { 118 log.Printf("rootFS=%v; modDir=%q, packagePath=%q, modPath=%q", rootFS, modDir, packagePath, modPath) 119 } 120 121 migrationsPackagePath := *migrationsPackageF 122 if migrationsPackagePath == "" { 123 migrationsPackagePath = strings.TrimPrefix(path.Join(packagePath, "../migrations"), "/") 124 } 125 126 // set up file systems 127 inFS, err := fs.Sub(rootFS, modDir) 128 if err != nil { 129 log.Fatalf("fs.Sub error while construct input fs: %v", err) 130 } 131 132 // output is either same as input or memory for dry-run 133 var outFS fs.FS 134 var dryRunFS *memfs.FS 135 if *dryRunF == "off" { 136 outFS = inFS 137 if migrationsPackagePath != "" { 138 mda, ok := outFS.(srcedit.MkdirAller) 139 if ok { 140 err := mda.MkdirAll(migrationsPackagePath, 0755) 141 if err != nil { 142 log.Fatalf("mda.MkdirAll for %q: %v", migrationsPackagePath, err) 143 } 144 } 145 //outFS.MkdirAll(migrationsPackagePath, 0755) 146 } 147 } else { 148 dryRunFS = memfs.New() 149 if packagePath != "" { 150 dryRunFS.MkdirAll(packagePath, 0755) 151 } 152 if migrationsPackagePath != "" { 153 // log.Printf("creating migrationsPackagePath %q", migrationsPackagePath) 154 err := dryRunFS.MkdirAll(migrationsPackagePath, 0755) 155 // log.Printf("migrations MkdirAll returned: %v", err) 156 _ = err 157 } 158 outFS = dryRunFS 159 } 160 161 // load the package with srcedit 162 pkg := srcedit.NewPackage(inFS, outFS, modPath, packagePath) 163 164 // load the migrations package 165 if *vF { 166 log.Printf("NewPackage for migrations: inFS=%#v, outFS=%#v, modPath=%#v, migrationsPackagePath=%#v", 167 inFS, outFS, modPath, migrationsPackagePath) 168 } 169 migrationsPkg := srcedit.NewPackage(inFS, outFS, modPath, migrationsPackagePath) 170 171 // get the definition for the specified type 172 typeInfo, err := pkg.FindType(typeName) 173 if err != nil { 174 log.Fatalf("failed to find type %q: %v", typeName, err) 175 } 176 177 // populate Struct 178 // s := Struct{ 179 // pkgImportedName: "", // TOOD: figure out what to do with prefixed types (not in the same package) 180 // name: typeName, 181 // typeInfo: typeInfo, 182 // } 183 // s.fields, err = s.makeFields() 184 // if err != nil { 185 // log.Fatalf("failed to extract field info from type %q: %v", typeName, err) 186 // } 187 188 s, err := model.NewStruct(typeInfo, "") 189 if err != nil { 190 log.Fatalf("failed to find type %q: %v", typeName, err) 191 } 192 193 // execute template 194 data := struct { 195 Struct *model.Struct 196 MigrationsImportPath string 197 }{ 198 Struct: s, 199 MigrationsImportPath: modPath + "/" + migrationsPackagePath, 200 } 201 tmpl, err := template.New("_main_").Funcs(funcMap).ParseFS(defaultTmplFS, "sqlcrud.tmpl") 202 if err != nil { 203 log.Fatalf("template parse error: %v", err) 204 } 205 206 fmtt := &srcedit.GofmtTransform{} 207 var trs []srcedit.Transform 208 209 { 210 fn := "sqlutil.go" 211 fmtt.FilenameList = append(fmtt.FilenameList, fn) 212 trList, err := tmplToTransforms(fn, data, tmpl, "SQLUtil") 213 if err != nil { 214 log.Fatal(err) 215 } 216 trs = append(trs, trList...) 217 } 218 219 { 220 fn := *storeFileF 221 fmtt.FilenameList = append(fmtt.FilenameList, fn) 222 trList, err := tmplToTransforms(fn, data, tmpl, "Store", "StoreMethods") 223 if err != nil { 224 log.Fatal(err) 225 } 226 trs = append(trs, trList...) 227 } 228 229 { 230 fn := *storeTestFileF 231 fmtt.FilenameList = append(fmtt.FilenameList, fn) 232 trList, err := tmplToTransforms(fn, data, tmpl, "TestStore") 233 if err != nil { 234 log.Fatal(err) 235 } 236 trs = append(trs, trList...) 237 } 238 239 { 240 fn := typeFilename 241 fmtt.FilenameList = append(fmtt.FilenameList, fn) 242 trList, err := tmplToTransforms(fn, data, tmpl, 243 "TYPEStore", 244 "TYPEStoreMethods", 245 // FIXME: filter which things go here base on flags 246 "TYPEInsert", 247 "TYPEDelete", 248 "TYPEUpdate", 249 "TYPESelectByID", 250 "TYPESelect", 251 "TYPESelectCursor", 252 "TYPECount", 253 ) 254 if err != nil { 255 log.Fatal(err) 256 } 257 trs = append(trs, trList...) 258 } 259 260 { 261 // TODO: -no-test flag 262 fn := *testFileF 263 fmtt.FilenameList = append(fmtt.FilenameList, fn) 264 trList, err := tmplToTransforms(fn, data, tmpl, 265 "TestTYPE", 266 ) 267 if err != nil { 268 log.Fatal(err) 269 } 270 trs = append(trs, trList...) 271 } 272 273 dd := &srcedit.DedupImportsTransform{ 274 FilenameList: fmtt.FilenameList, 275 } 276 trs = append(trs, dd) 277 278 if !*noGofmtF { 279 trs = append(trs, fmtt) 280 } 281 282 err = pkg.ApplyTransforms(trs...) 283 if err != nil { 284 log.Fatalf("apply transform error: %v", err) 285 } 286 287 // TODO: option to skip migrations stuff? 288 // do migrations package separately 289 { 290 fmtt := &srcedit.GofmtTransform{} 291 var trs []srcedit.Transform 292 293 fn := "migrations.go" 294 fmtt.FilenameList = append(fmtt.FilenameList, fn) 295 trList, err := tmplToTransforms(fn, data, tmpl, "Migrations") 296 if err != nil { 297 log.Fatal(err) 298 } 299 trs = append(trs, trList...) 300 301 dd := &srcedit.DedupImportsTransform{ 302 FilenameList: fmtt.FilenameList, 303 } 304 trs = append(trs, dd) 305 306 if !*noGofmtF { 307 trs = append(trs, fmtt) 308 } 309 310 err = migrationsPkg.ApplyTransforms(trs...) 311 if err != nil { 312 log.Fatalf("apply transform for migrations error: %v", err) 313 } 314 315 // mpf, err := inFS.Open(migrationsPackagePath) 316 317 needSampleMigration := true 318 err = fs.WalkDir(inFS, migrationsPackagePath, fs.WalkDirFunc(func(path string, d fs.DirEntry, err error) error { 319 if err != nil { 320 return err 321 } 322 if path == migrationsPackagePath { 323 return nil 324 } 325 if d.IsDir() { // only scan the immediate directory 326 return fs.SkipDir 327 } 328 if strings.HasSuffix(path, ".sql") || strings.HasSuffix(path, ".SQL") { 329 needSampleMigration = false 330 } 331 return nil 332 })) 333 if err != nil && !os.IsNotExist(err) { 334 log.Fatalf("error walking migrations dir %q: %v", migrationsPackagePath, err) 335 } 336 337 if needSampleMigration { 338 b := []byte(` 339 -- +goose Up 340 341 -- +goose Down 342 343 `) 344 fname := time.Now().UTC().Format("20060102150405") + "_sample.sql" 345 fpath := path.Join(migrationsPackagePath, fname) 346 err := outFS.(srcedit.FileWriter).WriteFile(fpath, b, 0644) 347 if err != nil { 348 log.Fatalf("error creating example migration file %q: %v", fpath, err) 349 } 350 } 351 352 } 353 354 if *dryRunF != "off" { 355 diffMap, err := diff.Run(inFS, outFS, ".", *dryRunF) 356 if err != nil { 357 log.Fatalf("error running diff: %v", err) 358 } 359 if *jsonF { 360 enc := json.NewEncoder(os.Stdout) 361 enc.Encode(map[string]interface{}{ 362 "diff": diffMap, 363 }) 364 } else { 365 klist := make([]string, 0, len(diffMap)) 366 for k := range diffMap { 367 klist = append(klist, k) 368 } 369 sort.Strings(klist) 370 for _, k := range klist { 371 fmt.Printf("### %s\n", k) 372 fmt.Println(diffMap[k]) 373 } 374 } 375 } 376 377 return 0 378 } 379 380 func tmplToTransforms(fileName string, data interface{}, tmpl *template.Template, tmplName ...string) ([]srcedit.Transform, error) { 381 382 var ret []srcedit.Transform 383 384 for _, tName := range tmplName { 385 var buf bytes.Buffer 386 err := tmpl.ExecuteTemplate(&buf, tName, data) 387 if err != nil { 388 return ret, fmt.Errorf("%q template exec error: %v", tName, err) 389 } 390 391 trList, err := srcedit.ParseTransforms(fileName, buf.String()) 392 if err != nil { 393 return ret, fmt.Errorf("%q transform parse error: %v", tName, err) 394 } 395 ret = append(ret, trList...) 396 397 } 398 399 return ret, nil 400 401 } 402 403 var funcMap = template.FuncMap(map[string]interface{}{ 404 "LowerForType": srcedit.LowerForType, 405 })