github.com/mailru/activerecord@v1.12.2/internal/pkg/generator/generator.go (about)

     1  package generator
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	_ "embed"
     7  	"fmt"
     8  	"io"
     9  	"log"
    10  	"regexp"
    11  	"strconv"
    12  	"strings"
    13  	"text/template"
    14  
    15  	"github.com/mailru/activerecord/pkg/iproto/util/text"
    16  	"github.com/pkg/errors"
    17  	"golang.org/x/tools/imports"
    18  
    19  	"github.com/mailru/activerecord/internal/pkg/arerror"
    20  	"github.com/mailru/activerecord/internal/pkg/ds"
    21  )
    22  
    23  const disclaimer string = `// Code generated by argen. DO NOT EDIT.
    24  // This code was generated from a template.
    25  //
    26  // Manual changes to this file may cause unexpected behavior in your application.
    27  // Manual changes to this file will be overwritten if the code is regenerated.
    28  //
    29  // Generate info: {{ .AppInfo }}
    30  `
    31  
    32  type PkgData struct {
    33  	ARPkg            string
    34  	ARPkgTitle       string
    35  	FieldList        []ds.FieldDeclaration
    36  	FieldMap         map[string]int
    37  	FieldObject      map[string]ds.FieldObject
    38  	LinkedObject     map[string]ds.RecordPackage
    39  	ProcInFieldList  []ds.ProcFieldDeclaration
    40  	ProcOutFieldList []ds.ProcFieldDeclaration
    41  	Server           ds.ServerDeclaration
    42  	Container        ds.NamespaceDeclaration
    43  	Indexes          []ds.IndexDeclaration
    44  	Serializers      map[string]ds.SerializerDeclaration
    45  	Mutators         map[string]ds.MutatorDeclaration
    46  	Imports          []ds.ImportDeclaration
    47  	Triggers         map[string]ds.TriggerDeclaration
    48  	Flags            map[string]ds.FlagDeclaration
    49  	AppInfo          string
    50  }
    51  
    52  func NewPkgData(appInfo string, cl ds.RecordPackage) PkgData {
    53  	return PkgData{
    54  		ARPkg:            cl.Namespace.PackageName,
    55  		ARPkgTitle:       cl.Namespace.PublicName,
    56  		Indexes:          cl.Indexes,
    57  		FieldList:        cl.Fields,
    58  		FieldMap:         cl.FieldsMap,
    59  		ProcInFieldList:  cl.ProcInFields,
    60  		ProcOutFieldList: cl.ProcOutFields.List(),
    61  		FieldObject:      cl.FieldsObjectMap,
    62  		Server:           cl.Server,
    63  		Container:        cl.Namespace,
    64  		Serializers:      cl.SerializerMap,
    65  		Mutators:         cl.MutatorMap,
    66  		Imports:          cl.Imports,
    67  		Triggers:         cl.TriggerMap,
    68  		Flags:            cl.FlagMap,
    69  		AppInfo:          appInfo,
    70  	}
    71  }
    72  
    73  const TemplateName = `ARPkgTemplate`
    74  
    75  type GenerateFile struct {
    76  	Data    []byte
    77  	Name    string
    78  	Dir     string
    79  	Backend string
    80  }
    81  
    82  type MetaData struct {
    83  	Namespaces []*ds.RecordPackage
    84  	AppInfo    string
    85  }
    86  
    87  //nolint:revive
    88  //go:embed tmpl/meta.tmpl
    89  var MetaTmpl string
    90  
    91  func GenerateMeta(params MetaData) ([]GenerateFile, *arerror.ErrGeneratorFile) {
    92  	metaWriter := bytes.Buffer{}
    93  	metaFile := bufio.NewWriter(&metaWriter)
    94  
    95  	if err := GenerateByTmpl(metaFile, params, "meta", MetaTmpl); err != nil {
    96  		return nil, &arerror.ErrGeneratorFile{Name: "repository.go", Backend: "meta", Filename: "repository.go", Err: err}
    97  	}
    98  
    99  	metaFile.Flush()
   100  
   101  	genRes := GenerateFile{
   102  		Dir:     "",
   103  		Name:    "repository.go",
   104  		Backend: "meta",
   105  	}
   106  
   107  	genData := metaWriter.Bytes()
   108  
   109  	var err error
   110  
   111  	genRes.Data, err = imports.Process("", genData, nil)
   112  	if err != nil {
   113  		return nil, &arerror.ErrGeneratorFile{Name: "repository.go", Backend: "meta", Filename: genRes.Name, Err: ErrorLine(err, string(genData))}
   114  	}
   115  
   116  	return []GenerateFile{genRes}, nil
   117  }
   118  
   119  func GenerateByTmpl(dstFile io.Writer, params any, name, tmpl string, tmplFuncs ...template.FuncMap) *arerror.ErrGeneratorPhases {
   120  	template := template.New(TemplateName).Funcs(funcs)
   121  	for _, f := range tmplFuncs {
   122  		template = template.Funcs(f)
   123  	}
   124  
   125  	templatePackage, err := template.Parse(disclaimer + tmpl)
   126  	if err != nil {
   127  		tmplLines, errgetline := getTmplErrorLine(strings.SplitAfter(disclaimer+tmpl, "\n"), err.Error())
   128  		if errgetline != nil {
   129  			tmplLines = errgetline.Error()
   130  		}
   131  
   132  		return &arerror.ErrGeneratorPhases{Backend: name, Phase: "parse", TmplLines: tmplLines, Err: err}
   133  	}
   134  
   135  	err = templatePackage.Execute(dstFile, params)
   136  	if err != nil {
   137  		tmplLines, errgetline := getTmplErrorLine(strings.SplitAfter(disclaimer+tmpl, "\n"), err.Error())
   138  		if errgetline != nil {
   139  			tmplLines = errgetline.Error()
   140  		}
   141  
   142  		return &arerror.ErrGeneratorPhases{Backend: name, Phase: "execute", TmplLines: tmplLines, Err: err}
   143  	}
   144  
   145  	return nil
   146  }
   147  
   148  func Generate(appInfo string, cl ds.RecordPackage, linkObject map[string]ds.RecordPackage) (ret []GenerateFile, err error) {
   149  	for _, backend := range cl.Backends {
   150  		var generated map[string]bytes.Buffer
   151  
   152  		switch backend {
   153  		case "tarantool15":
   154  			fallthrough
   155  		case "octopus":
   156  			params := NewPkgData(appInfo, cl)
   157  			params.LinkedObject = linkObject
   158  
   159  			log.Printf("Generate package (%v)", cl)
   160  
   161  			var err *arerror.ErrGeneratorPhases
   162  
   163  			generated, err = GenerateOctopus(params)
   164  			if err != nil {
   165  				err.Name = cl.Namespace.PublicName
   166  				return nil, err
   167  			}
   168  		case "tarantool16":
   169  			fallthrough
   170  		case "tarantool2":
   171  			params := NewPkgData(appInfo, cl)
   172  			params.LinkedObject = linkObject
   173  
   174  			log.Printf("Generate tarantool package (%v)", cl)
   175  
   176  			var err *arerror.ErrGeneratorPhases
   177  
   178  			generated, err = GenerateTarantool(params)
   179  			if err != nil {
   180  				err.Name = cl.Namespace.PublicName
   181  				return nil, err
   182  			}
   183  		case "postgres":
   184  			return nil, &arerror.ErrGeneratorFile{Name: cl.Namespace.PublicName, Backend: backend, Err: arerror.ErrGeneratorBackendNotImplemented}
   185  		default:
   186  			return nil, &arerror.ErrGeneratorFile{Name: cl.Namespace.PublicName, Backend: backend, Err: arerror.ErrGeneratorBackendUnknown}
   187  		}
   188  
   189  		for name, data := range generated {
   190  			genRes := GenerateFile{
   191  				Dir:     cl.Namespace.PackageName,
   192  				Name:    name + ".go",
   193  				Backend: backend,
   194  			}
   195  
   196  			genData := data.Bytes()
   197  
   198  			genRes.Data, err = imports.Process("", genData, nil)
   199  			if err != nil {
   200  				return nil, &arerror.ErrGeneratorFile{Name: cl.Namespace.PublicName, Backend: backend, Filename: genRes.Name, Err: ErrorLine(err, string(genData))}
   201  			}
   202  
   203  			ret = append(ret, genRes)
   204  		}
   205  	}
   206  
   207  	return ret, nil
   208  }
   209  
   210  var errImportsRx = regexp.MustCompile(`^(\d+):(\d+):`)
   211  
   212  func ErrorLine(errIn error, genData string) error {
   213  	findErr := errImportsRx.FindStringSubmatch(errIn.Error())
   214  	if len(findErr) == 3 {
   215  		lineNum, err := strconv.Atoi(findErr[1])
   216  		if err != nil {
   217  			return errors.Wrap(errIn, "can't unparse error line num")
   218  		}
   219  
   220  		lines := strings.Split(genData, "\n")
   221  
   222  		if len(lines) < lineNum {
   223  			return errors.Wrap(errIn, fmt.Sprintf("line num %d not found (total %d)", lineNum, len(lines)))
   224  		}
   225  
   226  		line := lines[lineNum-1]
   227  
   228  		byteNum, err := strconv.Atoi(findErr[2])
   229  		if err != nil {
   230  			return errors.Wrap(errIn, "can't unparse error byte num in line: "+line)
   231  		}
   232  
   233  		if len(line) < byteNum {
   234  			return errors.Wrap(errIn, "byte num not found in line: "+line)
   235  		}
   236  
   237  		return errors.Wrap(errIn, "\n"+strings.Trim(lines[lineNum-2], "\t")+"\n"+strings.Trim(line, "\t")+"\n"+strings.Repeat(" ", byteNum-1)+"^^^^^"+"\n"+strings.Trim(lines[lineNum], "\t"))
   238  	}
   239  
   240  	return errors.Wrap(errIn, "cant parse error message")
   241  }
   242  
   243  func GenerateFixture(appInfo string, cl ds.RecordPackage, pkg string, pkgFixture string) ([]GenerateFile, error) {
   244  	var ret []GenerateFile
   245  
   246  	params := FixturePkgData{
   247  		FixturePkg:       pkgFixture,
   248  		ARPkg:            pkg,
   249  		ARPkgTitle:       cl.Namespace.PublicName,
   250  		FieldList:        cl.Fields,
   251  		FieldMap:         cl.FieldsMap,
   252  		FieldObject:      cl.FieldsObjectMap,
   253  		ProcInFieldList:  cl.ProcInFields,
   254  		ProcOutFieldList: cl.ProcOutFields.List(),
   255  		Container:        cl.Namespace,
   256  		Indexes:          cl.Indexes,
   257  		Serializers:      cl.SerializerMap,
   258  		Mutators:         cl.MutatorMap,
   259  		Imports:          cl.Imports,
   260  		AppInfo:          appInfo,
   261  	}
   262  
   263  	log.Printf("Generate package (%v)", cl)
   264  
   265  	for _, backend := range cl.Backends {
   266  		var generated map[string]bytes.Buffer
   267  
   268  		switch backend {
   269  		case "tarantool15":
   270  			fallthrough
   271  		case "octopus":
   272  
   273  			var err *arerror.ErrGeneratorPhases
   274  
   275  			generated, err = GenerateOctopusFixtureStore(params)
   276  			if err != nil {
   277  				err.Name = cl.Namespace.PublicName
   278  				return nil, err
   279  			}
   280  		case "tarantool16":
   281  			fallthrough
   282  		case "tarantool2":
   283  
   284  			var err *arerror.ErrGeneratorPhases
   285  
   286  			generated, err = GenerateTarantoolFixtureStore(params)
   287  			if err != nil {
   288  				err.Name = cl.Namespace.PublicName
   289  				return nil, err
   290  			}
   291  		case "postgres":
   292  			return nil, &arerror.ErrGeneratorFile{Name: cl.Namespace.PublicName, Backend: backend, Err: arerror.ErrGeneratorBackendNotImplemented}
   293  		default:
   294  			return nil, &arerror.ErrGeneratorFile{Name: cl.Namespace.PublicName, Backend: backend, Err: arerror.ErrGeneratorBackendUnknown}
   295  		}
   296  
   297  		for _, data := range generated {
   298  			genRes := GenerateFile{
   299  				Dir:  pkgFixture,
   300  				Name: cl.Namespace.PackageName + "_gen.go",
   301  			}
   302  
   303  			genData := data.Bytes()
   304  
   305  			dataImp, err := imports.Process("", genData, nil)
   306  			if err != nil {
   307  				return nil, &arerror.ErrGeneratorFile{Name: cl.Namespace.PublicName, Backend: "fixture", Filename: genRes.Name, Err: ErrorLine(err, string(genData))}
   308  			}
   309  
   310  			genRes.Data = dataImp
   311  			ret = append(ret, genRes)
   312  		}
   313  	}
   314  
   315  	return ret, nil
   316  }
   317  
   318  var funcs = template.FuncMap{
   319  	"snakeCase": text.ToSnakeCase,
   320  	"split":     strings.Split,
   321  }