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  }