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:     &regionType,
   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  }