github.com/morlay/goqcloud@v0.0.0-20181123023149-b00e0b0b9afc/generator/api_doc.go (about) 1 package generator 2 3 import ( 4 "net/url" 5 "regexp" 6 "strings" 7 "sync" 8 9 "github.com/PuerkitoBio/goquery" 10 "github.com/davecgh/go-spew/spew" 11 "github.com/go-courier/codegen" 12 "github.com/sirupsen/logrus" 13 ) 14 15 type APIDocEntries struct { 16 ContentsURL string 17 DataTypesURL string 18 } 19 20 func NewAPIDoc(apiDocEntries *APIDocEntries) *APIDoc { 21 return &APIDoc{ 22 APIDocEntries: apiDocEntries, 23 APIs: map[string]*API{}, 24 } 25 } 26 27 type APIDoc struct { 28 *APIDocEntries 29 Service string 30 APIs map[string]*API 31 DataTypes map[string]*TypeSchema 32 } 33 34 func (doc *APIDoc) Scan() { 35 doc.scanAPIContents() 36 doc.scanAPIDetails() 37 doc.scanDataTypes() 38 } 39 40 func (doc *APIDoc) addDataType(s *TypeSchema) { 41 if doc.DataTypes == nil { 42 doc.DataTypes = map[string]*TypeSchema{} 43 } 44 doc.DataTypes[s.Name] = s 45 } 46 47 func (doc *APIDoc) scanDataTypes() { 48 d, err := goquery.NewDocument(doc.DataTypesURL) 49 if err != nil { 50 logrus.Panicf("%s", err) 51 } 52 53 d.Find("#docArticleContent > h2[id]").Each(func(i int, h2 *goquery.Selection) { 54 ts := &TypeSchema{} 55 ts.Name = h2.Text() 56 57 doc.addDataType(ts) 58 59 p := h2.Next() 60 ts.Desc = p.Text() 61 62 p.Next().Find("tbody tr").Each(func(i int, tr *goquery.Selection) { 63 cols := tr.Find("td") 64 65 propType := &TypeSchema{} 66 67 col := cols.Eq(1) 68 69 if col.Find("a").Length() != 0 { 70 propType.Name = col.Find("a").First().Text() 71 } else { 72 propType.Type = IndirectType(col.Text()) 73 } 74 75 if IsArrayType(col.Text()) { 76 propType = &TypeSchema{ 77 Items: propType, 78 } 79 } 80 81 propType.Desc = cols.Eq(3).Text() 82 propType.Required = cols.Eq(2).Text() != "否" 83 84 ts.AddProp(cols.Eq(0).Text(), propType) 85 }) 86 }) 87 } 88 89 func (doc *APIDoc) scanAPIContents() { 90 d, err := goquery.NewDocument(doc.ContentsURL) 91 if err != nil { 92 logrus.Panicf("%s", err) 93 } 94 95 originURL, _ := url.Parse(doc.ContentsURL) 96 97 d.Find("#docArticleContent tbody tr").Each(func(i int, s *goquery.Selection) { 98 cols := s.Find("td") 99 100 name := cols.Eq(0).Text() 101 docURL, _ := cols.Find("a").Eq(0).Attr("href") 102 103 u, _ := url.Parse(docURL) 104 u = originURL.ResolveReference(u) 105 106 desc := cols.Eq(1).Text() 107 108 doc.APIs[name] = NewAPI(&doc.Service, name, desc, u.String()) 109 }) 110 } 111 112 func (doc *APIDoc) scanAPIDetails() { 113 wg := sync.WaitGroup{} 114 wg.Add(len(doc.APIs)) 115 116 jobs := make(chan func()) 117 118 for i := 0; i < 10; i++ { 119 go func() { 120 for runJob := range jobs { 121 runJob() 122 wg.Add(-1) 123 } 124 }() 125 } 126 127 for name := range doc.APIs { 128 api := doc.APIs[name] 129 130 if doc.Service == "" { 131 api.ScanServiceName() 132 } 133 134 jobs <- func() { 135 api.Scan() 136 } 137 } 138 139 wg.Wait() 140 close(jobs) 141 } 142 143 func (doc *APIDoc) WriteAPIs() { 144 for name := range doc.APIs { 145 api := doc.APIs[name] 146 pkgName := codegen.LowerSnakeCase(doc.Service) 147 f := codegen.NewFile(pkgName, "./clients/"+pkgName+"/"+codegen.LowerSnakeCase(api.Name)+".go") 148 149 api.Write(f) 150 151 f.WriteFile() 152 logrus.Infof("Generated %s.%s", doc.Service, api.Name) 153 } 154 } 155 156 func (doc *APIDoc) WriteDataTypes() { 157 pkgName := codegen.LowerSnakeCase(doc.Service) 158 f := codegen.NewFile(pkgName, "./clients/"+pkgName+"/data_types.go") 159 160 for name := range doc.DataTypes { 161 dataType := doc.DataTypes[name] 162 dataType.Write(f) 163 } 164 165 _, err := f.WriteFile() 166 if err != nil { 167 panic(err) 168 } 169 logrus.Infof("Generated data types of %s", doc.Service) 170 } 171 172 func (doc *APIDoc) SyncAndWriteFiles() { 173 doc.WriteDataTypes() 174 doc.WriteAPIs() 175 } 176 177 func NewAPI(service *string, name string, desc string, docUrl string) *API { 178 regionType := BasicTypeString 179 180 return &API{ 181 Service: service, 182 Name: name, 183 Desc: desc, 184 DocURL: docUrl, 185 Parameters: &TypeSchema{ 186 Name: codegen.UpperCamelCase(name + "Request"), 187 Desc: desc + "\n" + docUrl, 188 Tag: "name", 189 Props: map[string]*TypeSchema{ 190 "Region": &TypeSchema{ 191 Type: ®ionType, 192 Desc: "区域", 193 Required: true, 194 }, 195 }, 196 }, 197 Response: &TypeSchema{ 198 Name: codegen.UpperCamelCase(name + "Response"), 199 AllOf: []*TypeSchema{ 200 &TypeSchema{ 201 ImportPath: "github.com/morlay/goqcloud", 202 Name: "TencentCloudBaseResponse", 203 Required: true, 204 }, 205 }, 206 Props: map[string]*TypeSchema{}, 207 }, 208 } 209 } 210 211 type API struct { 212 Service *string 213 Name string 214 Version string 215 Desc string 216 DocURL string 217 Parameters *TypeSchema 218 Response *TypeSchema 219 } 220 221 func (api *API) Write(file *codegen.File) { 222 api.Parameters.Write(file) 223 api.WriteInvoke(file) 224 api.Response.Write(file) 225 } 226 227 func (api *API) WriteInvoke(file *codegen.File) { 228 229 spew.Dump(file.Use("github.com/morlay/goqcloud", "Client")) 230 231 file.WriteBlock( 232 codegen.Func( 233 codegen.Var(codegen.Type(file.Use("github.com/morlay/goqcloud", "Client")), "client"), 234 ).Return( 235 codegen.Var(codegen.Star(codegen.Type(api.Response.Name))), 236 codegen.Var(codegen.Error), 237 ).MethodOf( 238 codegen.Var(codegen.Star(codegen.Type(api.Parameters.Name)), "req"), 239 ).Named("Invoke").Do( 240 codegen.Define(codegen.Id("resp")).By(file.Expr("&?{}", codegen.Type(api.Response.Name))), 241 codegen.Define(codegen.Id("err")).By( 242 codegen.Sel( 243 codegen.Id("client"), 244 codegen.Call("Request", file.Val(*api.Service), file.Val(api.Name), file.Val(api.Version)), 245 codegen.Call("Do", codegen.Id("req"), codegen.Id("resp")), 246 ), 247 ), 248 codegen.Return(codegen.Id("resp"), codegen.Id("err")), 249 ), 250 ) 251 } 252 253 func (api *API) AddParameter(name string, tpe *TypeSchema) { 254 name = strings.TrimSuffix(name, ".N") 255 api.Parameters.AddProp(name, tpe) 256 } 257 258 func (api *API) AddResponseProp(name string, tpe *TypeSchema) { 259 api.Response.AddProp(name, tpe) 260 } 261 262 var reVersion = regexp.MustCompile("[0-9]{4}-[0-9]{2}-[0-9]{2}") 263 var reTencentCloudHostname = regexp.MustCompile("([a-z]+)\\.tencentcloudapi\\.com") 264 265 func (api *API) ScanServiceName() { 266 d, err := goquery.NewDocument(api.DocURL) 267 if err != nil { 268 logrus.Panicf("%s", err) 269 } 270 271 service := reTencentCloudHostname.FindStringSubmatch(d.Find("#docArticleContent h2").First().Next().Text())[1] 272 *api.Service = service 273 } 274 275 func (api *API) Scan() { 276 d, err := goquery.NewDocument(api.DocURL) 277 if err != nil { 278 logrus.Panicf("%s", err) 279 } 280 281 tables := make([][]*goquery.Selection, 3) 282 tableIndex := 0 283 284 logrus.Infof("Scan api %s.%s from `%s`", *api.Service, api.Name, api.DocURL) 285 286 d.Find("#docArticleContent h2").Siblings().Each(func(i int, s *goquery.Selection) { 287 if s.Is("h2") { 288 if strings.HasPrefix(s.Text(), "2") { 289 tables[tableIndex] = make([]*goquery.Selection, 0) 290 } 291 if strings.HasPrefix(s.Text(), "3") { 292 tableIndex = 1 293 tables[tableIndex] = make([]*goquery.Selection, 0) 294 } 295 if strings.HasPrefix(s.Text(), "4") { 296 tableIndex = 2 297 tables[tableIndex] = make([]*goquery.Selection, 0) 298 } 299 } 300 301 if s.Is("table") { 302 tables[tableIndex] = append(tables[tableIndex], s) 303 } 304 }) 305 306 for _, table := range tables[0] { 307 table.Find("tbody tr").Each(func(i int, s *goquery.Selection) { 308 cols := s.Find("td") 309 310 paramType := &TypeSchema{} 311 312 tpeCol := cols.Eq(2) 313 314 if tpeCol.Find("a").Length() != 0 { 315 paramType.Name = tpeCol.Find("a").First().Text() 316 } else { 317 paramType.Type = IndirectType(tpeCol.Text()) 318 } 319 320 if IsArrayType(tpeCol.Text()) { 321 paramType = &TypeSchema{ 322 Items: paramType, 323 } 324 } 325 326 paramType.Desc = cols.Eq(3).Text() 327 paramType.Required = cols.Eq(1).Text() != "否" 328 329 name := cols.Eq(0).Text() 330 331 switch name { 332 case "Action": 333 case "Version": 334 api.Version = reVersion.FindString(paramType.Desc) 335 default: 336 api.AddParameter(cols.Eq(0).Text(), paramType) 337 } 338 }) 339 } 340 341 for _, table := range tables[1] { 342 table.Find("tbody tr").Each(func(i int, s *goquery.Selection) { 343 cols := s.Find("td") 344 345 respPropType := &TypeSchema{} 346 347 tpeCol := cols.Eq(1) 348 349 if tpeCol.Find("a").Length() != 0 { 350 respPropType.Name = tpeCol.Find("a").First().Text() 351 } else { 352 respPropType.Type = IndirectType(tpeCol.Text()) 353 } 354 355 if IsArrayType(tpeCol.Text()) { 356 respPropType = &TypeSchema{ 357 Items: respPropType, 358 } 359 } 360 361 respPropType.Desc = cols.Eq(2).Text() 362 respPropType.Required = true 363 364 name := cols.Eq(0).Text() 365 366 switch name { 367 case "RequestId": 368 default: 369 api.AddResponseProp(name, respPropType) 370 } 371 372 }) 373 } 374 }