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  })