github.com/d0sbit/gocode@v0.0.0-20211001144653-a968ce917518/cmd/gocode_mongocrud/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 "sort" 13 "strings" 14 "text/template" 15 16 "github.com/psanford/memfs" 17 18 "github.com/d0sbit/gocode/srcedit" 19 "github.com/d0sbit/gocode/srcedit/diff" 20 "github.com/d0sbit/gocode/srcedit/model" 21 ) 22 23 //go:embed mongocrud.tmpl 24 var defaultTmplFS embed.FS 25 26 func main() { 27 os.Exit(maine( 28 flag.NewFlagSet(os.Args[0], flag.PanicOnError), 29 os.Args[1:])) 30 } 31 32 // maine is broken out so it can be tested separately 33 func maine(flagSet *flag.FlagSet, args []string) int { 34 35 // gocode mongocrud -type Workspace -file workspace.go -package ./mstore -create -read -update -delete -search -all 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 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.") 44 noGofmtF := flagSet.Bool("no-gofmt", false, "Do not gofmt the output") 45 jsonF := flagSet.Bool("json", false, "Write output as JSON") 46 vF := flagSet.Bool("v", false, "Verbose output") 47 // allF := flagSet.Bool("all", false, "Generate all methods") 48 49 // TODO: 50 // - -dry-run 51 // - codeflag package with Usage() that dumps the flags as JSON 52 // - example command lines 53 54 flagSet.Parse(args) 55 56 typeName := *typeF 57 if typeName == "" { 58 log.Fatalf("-type is required") 59 } 60 61 typeFilename := *fileF 62 if typeFilename == "" { 63 typeFilename = srcedit.LowerForType(typeName+"Store", "-") + ".go" 64 } 65 66 if *testFileF == "" { 67 *testFileF = strings.TrimSuffix(typeFilename, ".go") + "_test.go" 68 } 69 70 // NOTES: review these later and compare to what we have! 71 72 // flags 73 // document the vital set somewhere, and maybe move to separate shared package 74 75 // read in toml config 76 77 // report any missing flags (or other errors) via JSON 78 // what about optional flags? 79 80 // values read from toml config should be reported but not as errors 81 82 // example command lines? 83 84 // analyze package glean structure 85 86 // for values like the package of where the crud stuff goes, this stuff should probably be 87 // written to the toml file so it doesn't have to be specified each time 88 89 // read and execute templates (built-in, or from project or other dir) 90 91 // write output files, and report on what happened via json 92 93 // -------------------------------------- 94 95 // find go.mod 96 rootFS, modDir, packagePath, modPath, err := srcedit.FindOSWdModuleDir(*packageF) 97 if err != nil { 98 log.Fatalf("error finding module directory: %v", err) 99 } 100 if *vF { 101 log.Printf("rootFS=%v; modDir=%q, packagePath=%q, modPath=%q", rootFS, modDir, packagePath, modPath) 102 } 103 // rootFS is root of filesystem, e.g. corresponding to "/" or `C:\` 104 // modDir is the directory of where to find go.mod, e.g. "projects/somepjt" 105 // modPath is the logical import path as declared in go.mod, e.g. "github.com/example/somepjt" 106 107 // // convert package into a path relative to go.mod 108 // packagePath := *packageF 109 // if strings.HasPrefix(packagePath, ".") { 110 // // FIXME: we should make this work - need to resolve the path and then 111 // // make it relative to module directory 112 // log.Fatalf("relative package path %q not supported (yet)", packagePath) 113 // } 114 // if packagePath == "" { 115 // log.Fatal("-package is required") 116 // } 117 118 // log.Printf("packagePath (subdir): %s", packagePath) 119 120 // set up file systems 121 inFS, err := fs.Sub(rootFS, modDir) 122 if err != nil { 123 log.Fatalf("fs.Sub error while construct input fs: %v", err) 124 } 125 126 // output is either same as input or memory for dry-run 127 var outFS fs.FS 128 var dryRunFS *memfs.FS 129 if *dryRunF == "off" { 130 outFS = inFS 131 } else { 132 dryRunFS = memfs.New() 133 if packagePath != "" { 134 dryRunFS.MkdirAll(packagePath, 0755) 135 } 136 outFS = dryRunFS 137 } 138 139 // load the package with srcedit 140 pkg := srcedit.NewPackage(inFS, outFS, modPath, packagePath) 141 142 // get the definition for the specified type 143 typeInfo, err := pkg.FindType(typeName) 144 if err != nil { 145 log.Fatalf("failed to find type %q: %v", typeName, err) 146 } 147 // log.Printf("typeInfo=%v", typeInfo) 148 149 // populate Struct 150 // s := Struct{ 151 // pkgImportedName: "", 152 // name: typeName, 153 // typeInfo: typeInfo, 154 // } 155 // s.fields, err = s.makeFields() 156 // if err != nil { 157 // log.Fatalf("failed to extract field info from type %q: %v", typeName, err) 158 // } 159 160 // TOOD: figure out what to do with prefixed types (not in the same package) 161 s, err := model.NewStruct(typeInfo, "") 162 if err != nil { 163 log.Fatalf("failed to find type %q: %v", typeName, err) 164 } 165 // TODO: add back in check for bson field on pks 166 // for _, pkf := range pkFields { 167 // if pkf.TagFirst("bson") == "" { 168 // // if len(pkf.bsonTagParts) < 1 || pkf.bsonTagParts[0] == "" { 169 // return nil, fmt.Errorf("primary key field %q on type %q does not have a `bson` struct tag (or the name is empty)", pkf.name, s.name) 170 // } 171 // } 172 173 // execute template 174 data := struct { 175 Struct *model.Struct 176 }{ 177 Struct: s, 178 } 179 // TODO: check for and load module-specific template first 180 tmpl, err := template.New("_main_").ParseFS(defaultTmplFS, "mongocrud.tmpl") 181 if err != nil { 182 log.Fatalf("template parse error: %v", err) 183 } 184 185 fmtt := &srcedit.GofmtTransform{} 186 var trs []srcedit.Transform 187 188 { 189 fn := "mongoutil.go" 190 fmtt.FilenameList = append(fmtt.FilenameList, fn) 191 trList, err := tmplToTransforms(fn, data, tmpl, "MongoUtil") 192 if err != nil { 193 log.Fatal(err) 194 } 195 trs = append(trs, trList...) 196 } 197 198 { 199 fn := *storeFileF 200 fmtt.FilenameList = append(fmtt.FilenameList, fn) 201 trList, err := tmplToTransforms(fn, data, tmpl, "Store", "StoreMethods") 202 if err != nil { 203 log.Fatal(err) 204 } 205 trs = append(trs, trList...) 206 } 207 208 { 209 fn := *storeTestFileF 210 fmtt.FilenameList = append(fmtt.FilenameList, fn) 211 trList, err := tmplToTransforms(fn, data, tmpl, "TestStore") 212 if err != nil { 213 log.Fatal(err) 214 } 215 trs = append(trs, trList...) 216 } 217 218 { 219 fn := typeFilename 220 fmtt.FilenameList = append(fmtt.FilenameList, fn) 221 trList, err := tmplToTransforms(fn, data, tmpl, 222 "TYPEStore", 223 "TYPEStoreMethods", 224 // FIJXME: filter which things go here base on flags 225 "TYPEInsert", 226 "TYPEDelete", 227 "TYPEUpdate", 228 "TYPESelectByID", 229 "TYPESelect", 230 "TYPESelectCursor", 231 "TYPECount", 232 ) 233 if err != nil { 234 log.Fatal(err) 235 } 236 trs = append(trs, trList...) 237 } 238 239 { 240 // TODO: -no-test flag 241 fn := *testFileF 242 fmtt.FilenameList = append(fmtt.FilenameList, fn) 243 trList, err := tmplToTransforms(fn, data, tmpl, 244 "TestTYPE", 245 ) 246 if err != nil { 247 log.Fatal(err) 248 } 249 trs = append(trs, trList...) 250 } 251 252 dd := &srcedit.DedupImportsTransform{ 253 FilenameList: fmtt.FilenameList, 254 } 255 trs = append(trs, dd) 256 257 if !*noGofmtF { 258 trs = append(trs, fmtt) 259 } 260 261 err = pkg.ApplyTransforms(trs...) 262 if err != nil { 263 log.Fatalf("apply transform error: %v", err) 264 } 265 266 if *dryRunF != "off" { 267 diffMap, err := diff.Run(inFS, outFS, ".", *dryRunF) 268 if err != nil { 269 log.Fatalf("error running diff: %v", err) 270 } 271 if *jsonF { 272 enc := json.NewEncoder(os.Stdout) 273 enc.Encode(map[string]interface{}{ 274 "diff": diffMap, 275 }) 276 } else { 277 klist := make([]string, 0, len(diffMap)) 278 for k := range diffMap { 279 klist = append(klist, k) 280 } 281 sort.Strings(klist) 282 for _, k := range klist { 283 fmt.Printf("### %s\n", k) 284 fmt.Println(diffMap[k]) 285 } 286 } 287 } 288 289 return 0 290 } 291 292 func tmplToTransforms(fileName string, data interface{}, tmpl *template.Template, tmplName ...string) ([]srcedit.Transform, error) { 293 294 var ret []srcedit.Transform 295 296 for _, tName := range tmplName { 297 var buf bytes.Buffer 298 err := tmpl.ExecuteTemplate(&buf, tName, data) 299 if err != nil { 300 return ret, fmt.Errorf("%q template exec error: %v", tName, err) 301 } 302 303 trList, err := srcedit.ParseTransforms(fileName, buf.String()) 304 if err != nil { 305 return ret, fmt.Errorf("%q transform parse error: %v", tName, err) 306 } 307 ret = append(ret, trList...) 308 309 } 310 311 return ret, nil 312 313 }