github.com/alimy/mir/v4@v4.1.0/internal/generator/generator.go (about) 1 // Copyright 2020 Michael Li <alimy@gility.net>. All rights reserved. 2 // Use of this source code is governed by Apache License 2.0 that 3 // can be found in the LICENSE file. 4 5 package generator 6 7 import ( 8 "errors" 9 "os" 10 "path" 11 "path/filepath" 12 "reflect" 13 "sync" 14 "text/template" 15 16 "github.com/alimy/mir/v4/core" 17 "github.com/alimy/mir/v4/internal/naming" 18 "github.com/alimy/mir/v4/internal/utils" 19 ) 20 21 func init() { 22 core.RegisterGenerators( 23 &mirGenerator{name: core.GeneratorGin}, 24 &mirGenerator{name: core.GeneratorChi}, 25 &mirGenerator{name: core.GeneratorMux}, 26 &mirGenerator{name: core.GeneratorHertz}, 27 &mirGenerator{name: core.GeneratorEcho}, 28 &mirGenerator{name: core.GeneratorIris}, 29 &mirGenerator{name: core.GeneratorFiber}, 30 &mirGenerator{name: core.GeneratorMacaron}, 31 &mirGenerator{name: core.GeneratorHttpRouter}, 32 ) 33 } 34 35 type mirGenerator struct { 36 sinkPath string 37 name string 38 isCleanup bool 39 } 40 41 type mirWriter struct { 42 ns naming.NamingStrategy 43 tmpl *template.Template 44 } 45 46 func (w *mirWriter) Write(dirPath string, iface *core.IfaceDescriptor) error { 47 fileName := w.ns.Naming(iface.TypeName) + ".go" 48 filePath := filepath.Join(dirPath, fileName) 49 file, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) 50 if err == nil { 51 defer func() { 52 _ = file.Close() 53 }() 54 if err = w.tmpl.Execute(file, iface); err == nil { 55 core.Logus("generated iface: %s.%s to file: %s", iface.PkgName, iface.TypeName, filePath) 56 } 57 } 58 return err 59 } 60 61 // Name name of generator 62 func (g *mirGenerator) Name() string { 63 return g.name 64 } 65 66 // Init init generator 67 func (g *mirGenerator) Init(opts *core.GeneratorOpts) (err error) { 68 if opts == nil { 69 return errors.New("init opts is nil") 70 } 71 g.isCleanup = opts.Cleanup 72 g.sinkPath, err = evalSinkPath(opts.SinkPath) 73 return 74 } 75 76 // Generate serial generate interface code 77 func (g *mirGenerator) Generate(ds core.Descriptors) error { 78 // cleanup out first if need 79 g.cleanup() 80 return generate(g.name, g.sinkPath, ds) 81 } 82 83 // GenerateContext concurrent generate interface code 84 func (g *mirGenerator) GenerateContext(ctx core.MirCtx) { 85 tmpl, err := templateFrom(g.name) 86 if err != nil { 87 ctx.Cancel(err) 88 return 89 } 90 apiPath := filepath.Join(g.sinkPath, "api") 91 ifaceSource, _ := ctx.Pipe() 92 onceSet := utils.NewOnceSet(func(path string) error { 93 return os.MkdirAll(path, 0755) 94 }) 95 96 // cleanup out first if need 97 g.cleanup() 98 99 var t *template.Template 100 wg := &sync.WaitGroup{} 101 ns := naming.NewSnakeNamingStrategy() 102 inOutsMap := make(map[string]utils.Set) 103 for iface := range ifaceSource { 104 dirPath := filepath.Join(apiPath, iface.Group) 105 if err = onceSet.Add(dirPath); err != nil { 106 goto FuckErr 107 } 108 if t, err = tmpl.Clone(); err != nil { 109 goto FuckErr 110 } 111 112 // setup inOuts for IfaceDescriptor 113 filter, exist := inOutsMap[iface.Group] 114 if !exist { 115 filter = utils.NewStrSet() 116 inOutsMap[iface.Group] = filter 117 } 118 var inouts []reflect.Type 119 for _, typ := range iface.AllInOuts() { 120 if typ.PkgPath() == iface.PkgPath { 121 if err := filter.Add(typ.Name()); err == nil { 122 inouts = append(inouts, typ) 123 } 124 } else { 125 inouts = append(inouts, typ) 126 } 127 } 128 iface.SetInnerInOuts(inouts) 129 130 writer := &mirWriter{tmpl: t, ns: ns} 131 wg.Add(1) 132 go func(ctx core.MirCtx, wg *sync.WaitGroup, writer *mirWriter, iface *core.IfaceDescriptor) { 133 defer wg.Done() 134 135 if err := writer.Write(dirPath, iface); err != nil { 136 ctx.Cancel(err) 137 } 138 }(ctx, wg, writer, iface) 139 } 140 wg.Wait() 141 142 ctx.GeneratorDone() 143 return 144 145 FuckErr: 146 ctx.Cancel(err) 147 } 148 149 // Clone return a copy of Generator 150 func (g *mirGenerator) Clone() core.Generator { 151 return &mirGenerator{ 152 name: g.name, 153 sinkPath: g.sinkPath, 154 } 155 } 156 157 func (g *mirGenerator) cleanup() { 158 if g.isCleanup { 159 apiPath := path.Join(g.sinkPath, "api") 160 core.Logus("cleanup out: %s", apiPath) 161 if err := os.RemoveAll(apiPath); err != nil { 162 core.Logus("want cleanup out first but failure: %s.do it later by yourself.", err) 163 } 164 } 165 } 166 167 func generate(generatorName string, sinkPath string, ds core.Descriptors) error { 168 var dirPath string 169 170 tmpl, err := templateFrom(generatorName) 171 if err != nil { 172 return err 173 } 174 writer := &mirWriter{tmpl: tmpl, ns: naming.NewSnakeNamingStrategy()} 175 apiPath := filepath.Join(sinkPath, "api") 176 177 FuckErr: 178 for key, ifaces := range ds { 179 group := ds.GroupFrom(key) 180 dirPath = filepath.Join(apiPath, group) 181 if err = os.MkdirAll(dirPath, 0755); err != nil { 182 break 183 } 184 filter := utils.NewStrSet() 185 hadDecribeCoreInterface := false 186 for _, iface := range ifaces.SortedIfaces() { 187 var inouts []reflect.Type 188 for _, typ := range iface.AllInOuts() { 189 if typ.PkgPath() == iface.PkgPath { 190 if err := filter.Add(typ.Name()); err == nil { 191 inouts = append(inouts, typ) 192 } 193 } else { 194 inouts = append(inouts, typ) 195 } 196 } 197 iface.SetInnerInOuts(inouts) 198 if !hadDecribeCoreInterface { 199 hadDecribeCoreInterface = true 200 iface.SetDeclareCoreInterface(true) 201 } 202 if err = writer.Write(dirPath, iface); err != nil { 203 break FuckErr 204 } 205 } 206 } 207 208 return err 209 } 210 211 func evalSinkPath(path string) (string, error) { 212 sp, err := filepath.EvalSymlinks(path) 213 if err != nil { 214 if os.IsNotExist(err) { 215 if !filepath.IsAbs(path) { 216 if sp, err = os.Getwd(); err == nil { 217 sp = filepath.Join(sp, path) 218 } 219 } else { 220 sp, err = path, nil 221 } 222 } 223 } 224 return sp, err 225 }