github.com/cloudwego/kitex@v0.9.0/tool/internal_pkg/generator/custom_template.go (about) 1 // Copyright 2021 CloudWeGo Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package generator 16 17 import ( 18 "fmt" 19 "io/ioutil" 20 "os" 21 "path" 22 "path/filepath" 23 "strings" 24 25 "gopkg.in/yaml.v3" 26 27 "github.com/cloudwego/kitex/tool/internal_pkg/log" 28 "github.com/cloudwego/kitex/tool/internal_pkg/util" 29 ) 30 31 var DefaultDelimiters = [2]string{"{{", "}}"} 32 33 type updateType string 34 35 const ( 36 skip updateType = "skip" 37 cover updateType = "cover" 38 incrementalUpdate updateType = "append" 39 ) 40 41 type Update struct { 42 // update type: skip / cover / append. Default is skip. 43 // If `LoopMethod` is true, only Type field is effect and no append behavior. 44 Type string `yaml:"type,omitempty"` 45 // Match key in append type. If the rendered key exists in the file, the method will be skipped. 46 Key string `yaml:"key,omitempty"` 47 // Append template. Use it to render append content. 48 AppendTpl string `yaml:"append_tpl,omitempty"` 49 // Append import template. Use it to render import content to append. 50 ImportTpl []string `yaml:"import_tpl,omitempty"` 51 } 52 53 type Template struct { 54 // The generated path and its filename. For example: biz/test.go 55 // will generate test.go in biz directory. 56 Path string `yaml:"path,omitempty"` 57 // Render template content, currently only supports go template syntax 58 Body string `yaml:"body,omitempty"` 59 // define update behavior 60 UpdateBehavior *Update `yaml:"update_behavior,omitempty"` 61 // If set this field, kitex will generate file by cycle. For example: 62 // test_a/test_b/{{ .Name}}_test.go 63 LoopMethod bool `yaml:"loop_method,omitempty"` 64 // If both set this field and combine-service, kitex will generate service by cycle. 65 LoopService bool `yaml:"loop_service,omitempty"` 66 } 67 68 type customGenerator struct { 69 fs []*File 70 pkg *PackageInfo 71 basePath string 72 } 73 74 func NewCustomGenerator(pkg *PackageInfo, basePath string) *customGenerator { 75 return &customGenerator{ 76 pkg: pkg, 77 basePath: basePath, 78 } 79 } 80 81 func (c *customGenerator) loopGenerate(tpl *Template) error { 82 tmp := c.pkg.Methods 83 defer func() { 84 c.pkg.Methods = tmp 85 }() 86 m := c.pkg.AllMethods() 87 for _, method := range m { 88 c.pkg.Methods = []*MethodInfo{method} 89 pathTask := &Task{ 90 Text: tpl.Path, 91 } 92 renderPath, err := pathTask.RenderString(c.pkg) 93 if err != nil { 94 return err 95 } 96 filePath := filepath.Join(c.basePath, renderPath) 97 // update 98 if util.Exists(filePath) && updateType(tpl.UpdateBehavior.Type) == skip { 99 continue 100 } 101 task := &Task{ 102 Name: path.Base(renderPath), 103 Path: filePath, 104 Text: tpl.Body, 105 } 106 f, err := task.Render(c.pkg) 107 if err != nil { 108 return err 109 } 110 c.fs = append(c.fs, f) 111 } 112 return nil 113 } 114 115 func (c *customGenerator) commonGenerate(tpl *Template) error { 116 // Use all services including base service. 117 tmp := c.pkg.Methods 118 defer func() { 119 c.pkg.Methods = tmp 120 }() 121 c.pkg.Methods = c.pkg.AllMethods() 122 123 pathTask := &Task{ 124 Text: tpl.Path, 125 } 126 renderPath, err := pathTask.RenderString(c.pkg) 127 if err != nil { 128 return err 129 } 130 filePath := filepath.Join(c.basePath, renderPath) 131 update := util.Exists(filePath) 132 if update && updateType(tpl.UpdateBehavior.Type) == skip { 133 log.Infof("skip generate file %s", tpl.Path) 134 return nil 135 } 136 var f *File 137 if update && updateType(tpl.UpdateBehavior.Type) == incrementalUpdate { 138 cc := &commonCompleter{ 139 path: filePath, 140 pkg: c.pkg, 141 update: tpl.UpdateBehavior, 142 } 143 f, err = cc.Complete() 144 if err != nil { 145 return err 146 } 147 } else { 148 // just create dir 149 if tpl.Path[len(tpl.Path)-1] == '/' { 150 os.MkdirAll(filePath, 0o755) 151 return nil 152 } 153 154 task := &Task{ 155 Name: path.Base(tpl.Path), 156 Path: filePath, 157 Text: tpl.Body, 158 } 159 160 f, err = task.Render(c.pkg) 161 if err != nil { 162 return err 163 } 164 } 165 166 c.fs = append(c.fs, f) 167 return nil 168 } 169 170 func (g *generator) GenerateCustomPackage(pkg *PackageInfo) (fs []*File, err error) { 171 g.updatePackageInfo(pkg) 172 173 g.setImports(HandlerFileName, pkg) 174 t, err := readTemplates(g.TemplateDir) 175 if err != nil { 176 return nil, err 177 } 178 for _, tpl := range t { 179 if tpl.LoopService && g.CombineService { 180 svrInfo, cs := pkg.ServiceInfo, pkg.CombineServices 181 182 for i := range cs { 183 pkg.ServiceInfo = cs[i] 184 f, err := renderFile(pkg, g.OutputPath, tpl) 185 if err != nil { 186 return nil, err 187 } 188 fs = append(fs, f...) 189 } 190 pkg.ServiceInfo, pkg.CombineServices = svrInfo, cs 191 } else { 192 f, err := renderFile(pkg, g.OutputPath, tpl) 193 if err != nil { 194 return nil, err 195 } 196 fs = append(fs, f...) 197 } 198 } 199 return fs, nil 200 } 201 202 func renderFile(pkg *PackageInfo, outputPath string, tpl *Template) (fs []*File, err error) { 203 cg := NewCustomGenerator(pkg, outputPath) 204 // special handling Methods field 205 if tpl.LoopMethod { 206 err = cg.loopGenerate(tpl) 207 } else { 208 err = cg.commonGenerate(tpl) 209 } 210 if err == errNoNewMethod { 211 err = nil 212 } 213 return cg.fs, err 214 } 215 216 func readTemplates(dir string) ([]*Template, error) { 217 files, _ := ioutil.ReadDir(dir) 218 var ts []*Template 219 for _, f := range files { 220 // filter dir and non-yaml files 221 if f.Name() != ExtensionFilename && !f.IsDir() && (strings.HasSuffix(f.Name(), "yaml") || strings.HasSuffix(f.Name(), "yml")) { 222 p := filepath.Join(dir, f.Name()) 223 tplData, err := ioutil.ReadFile(p) 224 if err != nil { 225 return nil, fmt.Errorf("read layout config from %s failed, err: %v", p, err.Error()) 226 } 227 t := &Template{ 228 UpdateBehavior: &Update{Type: string(skip)}, 229 } 230 if err = yaml.Unmarshal(tplData, t); err != nil { 231 return nil, fmt.Errorf("%s: unmarshal layout config failed, err: %s", f.Name(), err.Error()) 232 } 233 ts = append(ts, t) 234 } 235 } 236 237 return ts, nil 238 }