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 }