2014-06-19 13:09:17 +00:00
|
|
|
// Copyright 2013 bee authors
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
|
|
|
// not use this file except in compliance with the License. You may obtain
|
|
|
|
// a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
|
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
|
|
// License for the specific language governing permissions and limitations
|
|
|
|
// under the License.
|
|
|
|
|
2014-06-18 04:19:03 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"go/ast"
|
|
|
|
"go/parser"
|
|
|
|
"go/token"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"path/filepath"
|
2014-06-23 14:00:57 +00:00
|
|
|
"reflect"
|
2014-06-21 13:27:12 +00:00
|
|
|
"regexp"
|
2014-09-04 14:35:30 +00:00
|
|
|
"runtime"
|
2014-06-18 04:19:03 +00:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
2014-06-20 11:47:03 +00:00
|
|
|
"unicode"
|
2014-06-18 04:19:03 +00:00
|
|
|
|
|
|
|
"github.com/astaxie/beego/swagger"
|
|
|
|
"github.com/astaxie/beego/utils"
|
|
|
|
)
|
|
|
|
|
|
|
|
var globalDocsTemplate = `package docs
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/astaxie/beego"
|
|
|
|
"github.com/astaxie/beego/swagger"
|
|
|
|
)
|
2014-11-19 18:14:04 +00:00
|
|
|
|
2014-11-19 17:17:43 +00:00
|
|
|
const (
|
|
|
|
Rootinfo string = {{.rootinfo}}
|
|
|
|
Subapi string = {{.subapi}}
|
2014-11-19 18:14:04 +00:00
|
|
|
BasePath string= "{{.version}}"
|
2014-11-19 17:17:43 +00:00
|
|
|
)
|
2014-06-18 04:19:03 +00:00
|
|
|
|
|
|
|
var rootapi swagger.ResourceListing
|
2015-09-22 12:53:43 +00:00
|
|
|
var apilist map[string]*swagger.APIDeclaration
|
2014-06-18 04:19:03 +00:00
|
|
|
|
|
|
|
func init() {
|
2015-07-01 15:55:13 +00:00
|
|
|
if beego.EnableDocs {
|
|
|
|
err := json.Unmarshal([]byte(Rootinfo), &rootapi)
|
|
|
|
if err != nil {
|
|
|
|
beego.Error(err)
|
|
|
|
}
|
|
|
|
err = json.Unmarshal([]byte(Subapi), &apilist)
|
|
|
|
if err != nil {
|
|
|
|
beego.Error(err)
|
|
|
|
}
|
2015-09-22 12:53:43 +00:00
|
|
|
beego.GlobalDocAPI["Root"] = rootapi
|
2015-07-01 15:55:13 +00:00
|
|
|
for k, v := range apilist {
|
2015-09-22 12:53:43 +00:00
|
|
|
for i, a := range v.APIs {
|
2015-07-01 15:55:13 +00:00
|
|
|
a.Path = urlReplace(k + a.Path)
|
2015-09-22 12:53:43 +00:00
|
|
|
v.APIs[i] = a
|
2015-07-01 15:55:13 +00:00
|
|
|
}
|
|
|
|
v.BasePath = BasePath
|
2015-09-22 12:53:43 +00:00
|
|
|
beego.GlobalDocAPI[strings.Trim(k, "/")] = v
|
2014-06-18 04:19:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-23 15:16:06 +00:00
|
|
|
|
2014-06-18 04:19:03 +00:00
|
|
|
func urlReplace(src string) string {
|
|
|
|
pt := strings.Split(src, "/")
|
|
|
|
for i, p := range pt {
|
|
|
|
if len(p) > 0 {
|
|
|
|
if p[0] == ':' {
|
|
|
|
pt[i] = "{" + p[1:] + "}"
|
2014-06-23 15:16:06 +00:00
|
|
|
} else if p[0] == '?' && p[1] == ':' {
|
2014-06-23 15:10:17 +00:00
|
|
|
pt[i] = "{" + p[2:] + "}"
|
2014-06-18 04:19:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return strings.Join(pt, "/")
|
|
|
|
}
|
|
|
|
`
|
|
|
|
|
2014-06-20 11:47:03 +00:00
|
|
|
const (
|
|
|
|
ajson = "application/json"
|
|
|
|
axml = "application/xml"
|
|
|
|
aplain = "text/plain"
|
|
|
|
ahtml = "text/html"
|
|
|
|
)
|
|
|
|
|
2014-06-18 04:19:03 +00:00
|
|
|
var pkgCache map[string]bool //pkg:controller:function:comments comments: key:value
|
|
|
|
var controllerComments map[string]string
|
|
|
|
var importlist map[string]string
|
2015-09-22 12:53:43 +00:00
|
|
|
var apilist map[string]*swagger.APIDeclaration
|
|
|
|
var controllerList map[string][]swagger.API
|
2014-07-07 10:06:02 +00:00
|
|
|
var modelsList map[string]map[string]swagger.Model
|
2014-06-18 04:19:03 +00:00
|
|
|
var rootapi swagger.ResourceListing
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
pkgCache = make(map[string]bool)
|
|
|
|
controllerComments = make(map[string]string)
|
|
|
|
importlist = make(map[string]string)
|
2015-09-22 12:53:43 +00:00
|
|
|
apilist = make(map[string]*swagger.APIDeclaration)
|
|
|
|
controllerList = make(map[string][]swagger.API)
|
2014-07-07 10:06:02 +00:00
|
|
|
modelsList = make(map[string]map[string]swagger.Model)
|
2014-06-18 04:19:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func generateDocs(curpath string) {
|
|
|
|
fset := token.NewFileSet()
|
|
|
|
|
|
|
|
f, err := parser.ParseFile(fset, path.Join(curpath, "routers", "router.go"), nil, parser.ParseComments)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
ColorLog("[ERRO] parse router.go error\n")
|
|
|
|
os.Exit(2)
|
|
|
|
}
|
|
|
|
|
|
|
|
rootapi.Infos = swagger.Infomation{}
|
|
|
|
rootapi.SwaggerVersion = swagger.SwaggerVersion
|
|
|
|
//analysis API comments
|
|
|
|
if f.Comments != nil {
|
|
|
|
for _, c := range f.Comments {
|
|
|
|
for _, s := range strings.Split(c.Text(), "\n") {
|
|
|
|
if strings.HasPrefix(s, "@APIVersion") {
|
2015-09-22 12:53:43 +00:00
|
|
|
rootapi.APIVersion = strings.TrimSpace(s[len("@APIVersion"):])
|
2014-06-18 04:19:03 +00:00
|
|
|
} else if strings.HasPrefix(s, "@Title") {
|
2014-06-20 11:47:03 +00:00
|
|
|
rootapi.Infos.Title = strings.TrimSpace(s[len("@Title"):])
|
2014-06-18 04:19:03 +00:00
|
|
|
} else if strings.HasPrefix(s, "@Description") {
|
2014-06-20 11:47:03 +00:00
|
|
|
rootapi.Infos.Description = strings.TrimSpace(s[len("@Description"):])
|
2014-06-18 04:19:03 +00:00
|
|
|
} else if strings.HasPrefix(s, "@TermsOfServiceUrl") {
|
2015-09-22 12:53:43 +00:00
|
|
|
rootapi.Infos.TermsOfServiceURL = strings.TrimSpace(s[len("@TermsOfServiceUrl"):])
|
2014-06-18 04:19:03 +00:00
|
|
|
} else if strings.HasPrefix(s, "@Contact") {
|
2014-06-20 11:47:03 +00:00
|
|
|
rootapi.Infos.Contact = strings.TrimSpace(s[len("@Contact"):])
|
2014-06-18 04:19:03 +00:00
|
|
|
} else if strings.HasPrefix(s, "@License") {
|
2014-06-20 11:47:03 +00:00
|
|
|
rootapi.Infos.License = strings.TrimSpace(s[len("@License"):])
|
2014-06-18 04:19:03 +00:00
|
|
|
} else if strings.HasPrefix(s, "@LicenseUrl") {
|
2015-09-22 12:53:43 +00:00
|
|
|
rootapi.Infos.LicenseURL = strings.TrimSpace(s[len("@LicenseUrl"):])
|
2014-06-18 04:19:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, im := range f.Imports {
|
2015-05-14 07:56:19 +00:00
|
|
|
localName := ""
|
|
|
|
if im.Name != nil {
|
|
|
|
localName = im.Name.Name
|
|
|
|
}
|
|
|
|
analisyscontrollerPkg(localName, im.Path.Value)
|
2014-06-18 04:19:03 +00:00
|
|
|
}
|
|
|
|
for _, d := range f.Decls {
|
|
|
|
switch specDecl := d.(type) {
|
|
|
|
case *ast.FuncDecl:
|
|
|
|
for _, l := range specDecl.Body.List {
|
|
|
|
switch smtp := l.(type) {
|
|
|
|
case *ast.AssignStmt:
|
|
|
|
for _, l := range smtp.Rhs {
|
2014-08-01 15:40:48 +00:00
|
|
|
if v, ok := l.(*ast.CallExpr); ok {
|
|
|
|
f, params := analisysNewNamespace(v)
|
|
|
|
globalDocsTemplate = strings.Replace(globalDocsTemplate, "{{.version}}", f, -1)
|
|
|
|
for _, p := range params {
|
|
|
|
switch pp := p.(type) {
|
|
|
|
case *ast.CallExpr:
|
|
|
|
if selname := pp.Fun.(*ast.SelectorExpr).Sel.String(); selname == "NSNamespace" {
|
|
|
|
s, params := analisysNewNamespace(pp)
|
2015-09-22 12:53:43 +00:00
|
|
|
subapi := swagger.APIRef{Path: s}
|
2014-08-01 15:40:48 +00:00
|
|
|
controllerName := ""
|
|
|
|
for _, sp := range params {
|
|
|
|
switch pp := sp.(type) {
|
|
|
|
case *ast.CallExpr:
|
|
|
|
if pp.Fun.(*ast.SelectorExpr).Sel.String() == "NSInclude" {
|
|
|
|
controllerName = analisysNSInclude(s, pp)
|
|
|
|
}
|
2014-06-18 04:19:03 +00:00
|
|
|
}
|
|
|
|
}
|
2014-08-01 15:40:48 +00:00
|
|
|
if v, ok := controllerComments[controllerName]; ok {
|
|
|
|
subapi.Description = v
|
|
|
|
}
|
2015-09-22 12:53:43 +00:00
|
|
|
rootapi.APIs = append(rootapi.APIs, subapi)
|
2014-08-01 15:40:48 +00:00
|
|
|
} else if selname == "NSInclude" {
|
|
|
|
analisysNSInclude(f, pp)
|
2014-06-18 04:19:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-08-01 15:40:48 +00:00
|
|
|
|
2014-06-18 04:19:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
apiinfo, err := json.Marshal(rootapi)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
subapi, err := json.Marshal(apilist)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
os.Mkdir(path.Join(curpath, "docs"), 0755)
|
|
|
|
fd, err := os.Create(path.Join(curpath, "docs", "docs.go"))
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
defer fd.Close()
|
|
|
|
a := strings.Replace(globalDocsTemplate, "{{.rootinfo}}", "`"+string(apiinfo)+"`", -1)
|
|
|
|
a = strings.Replace(a, "{{.subapi}}", "`"+string(subapi)+"`", -1)
|
|
|
|
fd.WriteString(a)
|
|
|
|
}
|
|
|
|
|
|
|
|
func analisysNewNamespace(ce *ast.CallExpr) (first string, others []ast.Expr) {
|
|
|
|
for i, p := range ce.Args {
|
|
|
|
if i == 0 {
|
|
|
|
switch pp := p.(type) {
|
|
|
|
case *ast.BasicLit:
|
|
|
|
first = strings.Trim(pp.Value, `"`)
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
others = append(others, p)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func analisysNSInclude(baseurl string, ce *ast.CallExpr) string {
|
|
|
|
cname := ""
|
2015-09-22 12:53:43 +00:00
|
|
|
a := &swagger.APIDeclaration{}
|
|
|
|
a.APIVersion = rootapi.APIVersion
|
2014-06-18 04:19:03 +00:00
|
|
|
a.SwaggerVersion = swagger.SwaggerVersion
|
|
|
|
a.ResourcePath = baseurl
|
|
|
|
a.Produces = []string{"application/json", "application/xml", "text/plain", "text/html"}
|
2015-09-22 12:53:43 +00:00
|
|
|
a.APIs = make([]swagger.API, 0)
|
2014-06-19 08:40:55 +00:00
|
|
|
a.Models = make(map[string]swagger.Model)
|
2014-06-18 04:19:03 +00:00
|
|
|
for _, p := range ce.Args {
|
|
|
|
x := p.(*ast.UnaryExpr).X.(*ast.CompositeLit).Type.(*ast.SelectorExpr)
|
|
|
|
if v, ok := importlist[fmt.Sprint(x.X)]; ok {
|
|
|
|
cname = v + x.Sel.Name
|
|
|
|
}
|
|
|
|
if apis, ok := controllerList[cname]; ok {
|
2015-09-22 12:53:43 +00:00
|
|
|
if len(a.APIs) > 0 {
|
|
|
|
a.APIs = append(a.APIs, apis...)
|
2014-06-18 04:19:03 +00:00
|
|
|
} else {
|
2015-09-22 12:53:43 +00:00
|
|
|
a.APIs = apis
|
2014-06-18 04:19:03 +00:00
|
|
|
}
|
|
|
|
}
|
2014-06-19 08:40:55 +00:00
|
|
|
if models, ok := modelsList[cname]; ok {
|
|
|
|
for _, m := range models {
|
2015-09-22 12:53:43 +00:00
|
|
|
a.Models[m.ID] = m
|
2014-06-19 08:40:55 +00:00
|
|
|
}
|
|
|
|
}
|
2014-06-18 04:19:03 +00:00
|
|
|
}
|
|
|
|
apilist[baseurl] = a
|
|
|
|
return cname
|
|
|
|
}
|
|
|
|
|
2015-05-14 07:56:19 +00:00
|
|
|
func analisyscontrollerPkg(localName, pkgpath string) {
|
2014-06-18 04:19:03 +00:00
|
|
|
pkgpath = strings.Trim(pkgpath, "\"")
|
2014-09-04 14:35:30 +00:00
|
|
|
if isSystemPackage(pkgpath) {
|
|
|
|
return
|
|
|
|
}
|
2015-05-14 07:56:19 +00:00
|
|
|
if localName != "" {
|
|
|
|
importlist[localName] = pkgpath
|
|
|
|
} else {
|
|
|
|
pps := strings.Split(pkgpath, "/")
|
|
|
|
importlist[pps[len(pps)-1]] = pkgpath
|
|
|
|
}
|
2014-06-18 04:19:03 +00:00
|
|
|
if pkgpath == "github.com/astaxie/beego" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
gopath := os.Getenv("GOPATH")
|
|
|
|
if gopath == "" {
|
|
|
|
panic("please set gopath")
|
|
|
|
}
|
|
|
|
pkgRealpath := ""
|
|
|
|
|
|
|
|
wgopath := filepath.SplitList(gopath)
|
|
|
|
for _, wg := range wgopath {
|
|
|
|
wg, _ = filepath.EvalSymlinks(filepath.Join(wg, "src", pkgpath))
|
|
|
|
if utils.FileExists(wg) {
|
|
|
|
pkgRealpath = wg
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if pkgRealpath != "" {
|
|
|
|
if _, ok := pkgCache[pkgpath]; ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ColorLog("[ERRO] the %s pkg not exist in gopath\n", pkgpath)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
fileSet := token.NewFileSet()
|
|
|
|
astPkgs, err := parser.ParseDir(fileSet, pkgRealpath, func(info os.FileInfo) bool {
|
|
|
|
name := info.Name()
|
|
|
|
return !info.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go")
|
|
|
|
}, parser.ParseComments)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
ColorLog("[ERRO] the %s pkg parser.ParseDir error\n", pkgpath)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
for _, pkg := range astPkgs {
|
|
|
|
for _, fl := range pkg.Files {
|
|
|
|
for _, d := range fl.Decls {
|
|
|
|
switch specDecl := d.(type) {
|
|
|
|
case *ast.FuncDecl:
|
|
|
|
if specDecl.Recv != nil && len(specDecl.Recv.List) > 0 {
|
|
|
|
if t, ok := specDecl.Recv.List[0].Type.(*ast.StarExpr); ok {
|
|
|
|
parserComments(specDecl.Doc, specDecl.Name.String(), fmt.Sprint(t.X), pkgpath)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case *ast.GenDecl:
|
|
|
|
if specDecl.Tok.String() == "type" {
|
|
|
|
for _, s := range specDecl.Specs {
|
|
|
|
switch tp := s.(*ast.TypeSpec).Type.(type) {
|
|
|
|
case *ast.StructType:
|
|
|
|
_ = tp.Struct
|
|
|
|
controllerComments[pkgpath+s.(*ast.TypeSpec).Name.String()] = specDecl.Doc.Text()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-04 14:35:30 +00:00
|
|
|
func isSystemPackage(pkgpath string) bool {
|
|
|
|
goroot := runtime.GOROOT()
|
|
|
|
if goroot == "" {
|
|
|
|
panic("goroot is empty, do you install Go right?")
|
|
|
|
}
|
|
|
|
wg, _ := filepath.EvalSymlinks(filepath.Join(goroot, "src", "pkg", pkgpath))
|
|
|
|
if utils.FileExists(wg) {
|
|
|
|
return true
|
|
|
|
}
|
2015-01-03 13:17:59 +00:00
|
|
|
|
|
|
|
//TODO(zh):support go1.4
|
|
|
|
wg, _ = filepath.EvalSymlinks(filepath.Join(goroot, "src", pkgpath))
|
|
|
|
if utils.FileExists(wg) {
|
|
|
|
return true
|
|
|
|
}
|
2014-12-15 07:13:53 +00:00
|
|
|
|
2014-09-04 14:35:30 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2014-06-18 04:19:03 +00:00
|
|
|
// parse the func comments
|
|
|
|
func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpath string) error {
|
2015-09-22 12:53:43 +00:00
|
|
|
innerapi := swagger.API{}
|
2014-06-18 04:19:03 +00:00
|
|
|
opts := swagger.Operation{}
|
|
|
|
if comments != nil && comments.List != nil {
|
|
|
|
for _, c := range comments.List {
|
|
|
|
t := strings.TrimSpace(strings.TrimLeft(c.Text, "//"))
|
|
|
|
if strings.HasPrefix(t, "@router") {
|
2014-06-20 11:49:43 +00:00
|
|
|
elements := strings.TrimSpace(t[len("@router"):])
|
2014-06-18 04:19:03 +00:00
|
|
|
e1 := strings.SplitN(elements, " ", 2)
|
|
|
|
if len(e1) < 1 {
|
|
|
|
return errors.New("you should has router infomation")
|
|
|
|
}
|
|
|
|
innerapi.Path = e1[0]
|
|
|
|
if len(e1) == 2 && e1[1] != "" {
|
|
|
|
e1 = strings.SplitN(e1[1], " ", 2)
|
2015-09-22 12:53:43 +00:00
|
|
|
opts.HTTPMethod = strings.ToUpper(strings.Trim(e1[0], "[]"))
|
2014-06-18 04:19:03 +00:00
|
|
|
} else {
|
2015-09-22 12:53:43 +00:00
|
|
|
opts.HTTPMethod = "GET"
|
2014-06-18 04:19:03 +00:00
|
|
|
}
|
|
|
|
} else if strings.HasPrefix(t, "@Title") {
|
2014-06-20 11:47:03 +00:00
|
|
|
opts.Nickname = strings.TrimSpace(t[len("@Title"):])
|
2014-06-18 04:19:03 +00:00
|
|
|
} else if strings.HasPrefix(t, "@Description") {
|
2014-06-20 11:47:03 +00:00
|
|
|
opts.Summary = strings.TrimSpace(t[len("@Description"):])
|
2014-06-18 04:19:03 +00:00
|
|
|
} else if strings.HasPrefix(t, "@Success") {
|
2014-06-20 11:47:03 +00:00
|
|
|
ss := strings.TrimSpace(t[len("@Success"):])
|
2014-06-18 04:19:03 +00:00
|
|
|
rs := swagger.ResponseMessage{}
|
2014-06-20 11:47:03 +00:00
|
|
|
st := make([]string, 3)
|
|
|
|
j := 0
|
|
|
|
var tmp []rune
|
|
|
|
start := false
|
|
|
|
|
|
|
|
for i, c := range ss {
|
|
|
|
if unicode.IsSpace(c) {
|
|
|
|
if !start && j < 2 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if j == 0 || j == 1 {
|
|
|
|
st[j] = string(tmp)
|
|
|
|
tmp = make([]rune, 0)
|
|
|
|
j += 1
|
|
|
|
start = false
|
|
|
|
continue
|
|
|
|
} else {
|
|
|
|
st[j] = strings.TrimSpace(ss[i+1:])
|
|
|
|
break
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
start = true
|
|
|
|
tmp = append(tmp, c)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(tmp) > 0 && st[2] == "" {
|
|
|
|
st[2] = strings.TrimSpace(string(tmp))
|
|
|
|
}
|
2014-06-19 08:40:55 +00:00
|
|
|
rs.Message = st[2]
|
|
|
|
if st[1] == "{object}" {
|
2014-06-20 11:47:03 +00:00
|
|
|
if st[2] == "" {
|
|
|
|
panic(controllerName + " " + funcName + " has no object")
|
|
|
|
}
|
2014-06-23 13:25:12 +00:00
|
|
|
cmpath, m, mod, realTypes := getModel(st[2])
|
2014-06-24 04:06:45 +00:00
|
|
|
//ll := strings.Split(st[2], ".")
|
|
|
|
//opts.Type = ll[len(ll)-1]
|
2014-06-19 08:40:55 +00:00
|
|
|
rs.ResponseModel = m
|
2014-06-21 13:27:12 +00:00
|
|
|
if _, ok := modelsList[pkgpath+controllerName]; !ok {
|
2014-07-07 10:06:02 +00:00
|
|
|
modelsList[pkgpath+controllerName] = make(map[string]swagger.Model, 0)
|
2014-06-19 08:40:55 +00:00
|
|
|
}
|
2014-08-01 08:04:09 +00:00
|
|
|
modelsList[pkgpath+controllerName][st[2]] = mod
|
2014-06-23 13:25:12 +00:00
|
|
|
appendModels(cmpath, pkgpath, controllerName, realTypes)
|
2014-06-19 08:40:55 +00:00
|
|
|
}
|
|
|
|
|
2014-06-18 04:19:03 +00:00
|
|
|
rs.Code, _ = strconv.Atoi(st[0])
|
|
|
|
opts.ResponseMessages = append(opts.ResponseMessages, rs)
|
|
|
|
} else if strings.HasPrefix(t, "@Param") {
|
|
|
|
para := swagger.Parameter{}
|
2014-06-18 10:50:08 +00:00
|
|
|
p := getparams(strings.TrimSpace(t[len("@Param "):]))
|
2014-06-20 11:47:03 +00:00
|
|
|
if len(p) < 4 {
|
|
|
|
panic(controllerName + "_" + funcName + "'s comments @Param at least should has 4 params")
|
|
|
|
}
|
2014-06-18 04:19:03 +00:00
|
|
|
para.Name = p[0]
|
|
|
|
para.ParamType = p[1]
|
2014-06-24 04:06:45 +00:00
|
|
|
pp := strings.Split(p[2], ".")
|
|
|
|
para.DataType = pp[len(pp)-1]
|
2014-06-18 04:19:03 +00:00
|
|
|
if len(p) > 4 {
|
|
|
|
para.Required, _ = strconv.ParseBool(p[3])
|
|
|
|
para.Description = p[4]
|
|
|
|
} else {
|
|
|
|
para.Description = p[3]
|
|
|
|
}
|
|
|
|
opts.Parameters = append(opts.Parameters, para)
|
|
|
|
} else if strings.HasPrefix(t, "@Failure") {
|
|
|
|
rs := swagger.ResponseMessage{}
|
2014-06-20 11:47:03 +00:00
|
|
|
st := strings.TrimSpace(t[len("@Failure"):])
|
2014-06-18 04:19:03 +00:00
|
|
|
var cd []rune
|
2014-06-20 11:47:03 +00:00
|
|
|
var start bool
|
2014-06-18 04:19:03 +00:00
|
|
|
for i, s := range st {
|
2014-06-20 11:47:03 +00:00
|
|
|
if unicode.IsSpace(s) {
|
|
|
|
if start {
|
|
|
|
rs.Message = strings.TrimSpace(st[i+1:])
|
|
|
|
break
|
|
|
|
} else {
|
|
|
|
continue
|
|
|
|
}
|
2014-06-18 04:19:03 +00:00
|
|
|
}
|
2014-06-20 11:47:03 +00:00
|
|
|
start = true
|
2014-06-18 04:19:03 +00:00
|
|
|
cd = append(cd, s)
|
|
|
|
}
|
|
|
|
rs.Code, _ = strconv.Atoi(string(cd))
|
|
|
|
opts.ResponseMessages = append(opts.ResponseMessages, rs)
|
|
|
|
} else if strings.HasPrefix(t, "@Type") {
|
2014-06-20 11:47:03 +00:00
|
|
|
opts.Type = strings.TrimSpace(t[len("@Type"):])
|
|
|
|
} else if strings.HasPrefix(t, "@Accept") {
|
|
|
|
accepts := strings.Split(strings.TrimSpace(strings.TrimSpace(t[len("@Accept"):])), ",")
|
|
|
|
for _, a := range accepts {
|
|
|
|
switch a {
|
|
|
|
case "json":
|
|
|
|
opts.Consumes = append(opts.Consumes, ajson)
|
|
|
|
opts.Produces = append(opts.Produces, ajson)
|
|
|
|
case "xml":
|
|
|
|
opts.Consumes = append(opts.Consumes, axml)
|
|
|
|
opts.Produces = append(opts.Produces, axml)
|
|
|
|
case "plain":
|
|
|
|
opts.Consumes = append(opts.Consumes, aplain)
|
|
|
|
opts.Produces = append(opts.Produces, aplain)
|
|
|
|
case "html":
|
|
|
|
opts.Consumes = append(opts.Consumes, ahtml)
|
|
|
|
opts.Produces = append(opts.Produces, ahtml)
|
|
|
|
}
|
|
|
|
}
|
2014-06-18 04:19:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
innerapi.Operations = append(innerapi.Operations, opts)
|
|
|
|
if innerapi.Path != "" {
|
|
|
|
if _, ok := controllerList[pkgpath+controllerName]; ok {
|
|
|
|
controllerList[pkgpath+controllerName] = append(controllerList[pkgpath+controllerName], innerapi)
|
|
|
|
} else {
|
2015-09-22 12:53:43 +00:00
|
|
|
controllerList[pkgpath+controllerName] = make([]swagger.API, 1)
|
2014-06-18 04:19:03 +00:00
|
|
|
controllerList[pkgpath+controllerName][0] = innerapi
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// analisys params return []string
|
|
|
|
// @Param query form string true "The email for login"
|
|
|
|
// [query form string true "The email for login"]
|
|
|
|
func getparams(str string) []string {
|
2014-06-20 11:47:03 +00:00
|
|
|
var s []rune
|
2014-06-18 04:19:03 +00:00
|
|
|
var j int
|
|
|
|
var start bool
|
|
|
|
var r []string
|
2014-06-20 11:47:03 +00:00
|
|
|
for i, c := range []rune(str) {
|
|
|
|
if unicode.IsSpace(c) {
|
2014-06-18 04:19:03 +00:00
|
|
|
if !start {
|
|
|
|
continue
|
|
|
|
} else {
|
|
|
|
if j == 3 {
|
|
|
|
r = append(r, string(s))
|
2014-06-20 11:47:03 +00:00
|
|
|
r = append(r, strings.TrimSpace((str[i+1:])))
|
2014-06-18 04:19:03 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
start = false
|
|
|
|
j++
|
|
|
|
r = append(r, string(s))
|
2014-06-20 11:47:03 +00:00
|
|
|
s = make([]rune, 0)
|
2014-06-18 04:19:03 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
start = true
|
|
|
|
s = append(s, c)
|
|
|
|
}
|
|
|
|
return r
|
|
|
|
}
|
2014-06-19 08:40:55 +00:00
|
|
|
|
2014-06-23 13:25:12 +00:00
|
|
|
func getModel(str string) (pkgpath, objectname string, m swagger.Model, realTypes []string) {
|
2014-06-19 08:40:55 +00:00
|
|
|
strs := strings.Split(str, ".")
|
|
|
|
objectname = strs[len(strs)-1]
|
2014-06-23 13:25:12 +00:00
|
|
|
pkgpath = strings.Join(strs[:len(strs)-1], "/")
|
2014-06-19 08:40:55 +00:00
|
|
|
curpath, _ := os.Getwd()
|
|
|
|
pkgRealpath := path.Join(curpath, pkgpath)
|
|
|
|
fileSet := token.NewFileSet()
|
|
|
|
astPkgs, err := parser.ParseDir(fileSet, pkgRealpath, func(info os.FileInfo) bool {
|
|
|
|
name := info.Name()
|
|
|
|
return !info.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go")
|
|
|
|
}, parser.ParseComments)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
ColorLog("[ERRO] the model %s parser.ParseDir error\n", str)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, pkg := range astPkgs {
|
|
|
|
for _, fl := range pkg.Files {
|
|
|
|
for k, d := range fl.Scope.Objects {
|
|
|
|
if d.Kind == ast.Typ {
|
|
|
|
if k != objectname {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
ts, ok := d.Decl.(*ast.TypeSpec)
|
|
|
|
if !ok {
|
|
|
|
ColorLog("Unknown type without TypeSec: %v", d)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
st, ok := ts.Type.(*ast.StructType)
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
2015-09-22 12:53:43 +00:00
|
|
|
m.ID = k
|
2014-06-19 08:40:55 +00:00
|
|
|
if st.Fields.List != nil {
|
|
|
|
m.Properties = make(map[string]swagger.ModelProperty)
|
|
|
|
for _, field := range st.Fields.List {
|
2014-06-23 13:25:12 +00:00
|
|
|
isSlice, realType := typeAnalyser(field)
|
2014-06-21 13:27:12 +00:00
|
|
|
realTypes = append(realTypes, realType)
|
2014-06-19 08:40:55 +00:00
|
|
|
mp := swagger.ModelProperty{}
|
2014-06-21 13:27:12 +00:00
|
|
|
// add type slice
|
|
|
|
if isSlice {
|
2014-09-04 16:09:35 +00:00
|
|
|
if isBasicType(realType) {
|
|
|
|
mp.Type = "[]" + realType
|
|
|
|
} else {
|
|
|
|
mp.Type = "array"
|
|
|
|
mp.Items = make(map[string]string)
|
|
|
|
mp.Items["$ref"] = realType
|
|
|
|
}
|
2014-06-21 13:27:12 +00:00
|
|
|
} else {
|
|
|
|
mp.Type = realType
|
|
|
|
}
|
2014-10-22 14:45:26 +00:00
|
|
|
|
|
|
|
// dont add property if anonymous field
|
|
|
|
if field.Names != nil {
|
|
|
|
|
|
|
|
// set property name as field name
|
|
|
|
var name = field.Names[0].Name
|
|
|
|
|
|
|
|
// if no tag skip tag processing
|
|
|
|
if field.Tag == nil {
|
|
|
|
m.Properties[name] = mp
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
var tagValues []string
|
2014-06-23 14:21:13 +00:00
|
|
|
stag := reflect.StructTag(strings.Trim(field.Tag.Value, "`"))
|
2014-10-22 14:45:26 +00:00
|
|
|
tag := stag.Get("json")
|
|
|
|
|
|
|
|
if tag != "" {
|
|
|
|
tagValues = strings.Split(tag, ",")
|
2014-06-21 13:27:12 +00:00
|
|
|
}
|
2014-10-22 14:45:26 +00:00
|
|
|
|
|
|
|
// dont add property if json tag first value is "-"
|
|
|
|
if len(tagValues) == 0 || tagValues[0] != "-" {
|
|
|
|
|
|
|
|
// set property name to the left most json tag value only if is not omitempty
|
|
|
|
if len(tagValues) > 0 && tagValues[0] != "omitempty" {
|
|
|
|
name = tagValues[0]
|
2014-06-23 14:21:13 +00:00
|
|
|
}
|
2014-10-22 14:45:26 +00:00
|
|
|
|
|
|
|
if thrifttag := stag.Get("thrift"); thrifttag != "" {
|
|
|
|
ts := strings.Split(thrifttag, ",")
|
|
|
|
if ts[0] != "" {
|
|
|
|
name = ts[0]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if required := stag.Get("required"); required != "" {
|
|
|
|
m.Required = append(m.Required, name)
|
|
|
|
}
|
|
|
|
if desc := stag.Get("description"); desc != "" {
|
|
|
|
mp.Description = desc
|
|
|
|
}
|
|
|
|
|
|
|
|
m.Properties[name] = mp
|
2014-06-23 14:21:13 +00:00
|
|
|
}
|
2015-01-03 13:17:59 +00:00
|
|
|
if ignore := stag.Get("ignore"); ignore != "" {
|
|
|
|
continue
|
|
|
|
}
|
2014-06-20 04:03:42 +00:00
|
|
|
}
|
2014-06-19 08:40:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-09-22 12:53:43 +00:00
|
|
|
if m.ID == "" {
|
2014-06-19 08:40:55 +00:00
|
|
|
ColorLog("can't find the object: %v", str)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
2014-06-21 13:27:12 +00:00
|
|
|
|
2014-06-23 13:25:12 +00:00
|
|
|
func typeAnalyser(f *ast.Field) (isSlice bool, realType string) {
|
|
|
|
if arr, ok := f.Type.(*ast.ArrayType); ok {
|
2014-06-23 13:38:19 +00:00
|
|
|
if isBasicType(fmt.Sprint(arr.Elt)) {
|
2014-06-23 14:00:57 +00:00
|
|
|
return false, fmt.Sprintf("[]%v", arr.Elt)
|
2014-06-23 13:25:12 +00:00
|
|
|
}
|
2014-06-23 14:00:57 +00:00
|
|
|
if mp, ok := arr.Elt.(*ast.MapType); ok {
|
|
|
|
return false, fmt.Sprintf("map[%v][%v]", mp.Key, mp.Value)
|
2014-06-23 13:25:12 +00:00
|
|
|
}
|
|
|
|
if star, ok := arr.Elt.(*ast.StarExpr); ok {
|
|
|
|
return true, fmt.Sprint(star.X)
|
|
|
|
} else {
|
|
|
|
return true, fmt.Sprint(arr.Elt)
|
|
|
|
}
|
|
|
|
} else {
|
2014-06-24 04:06:45 +00:00
|
|
|
switch t := f.Type.(type) {
|
|
|
|
case *ast.StarExpr:
|
|
|
|
return false, fmt.Sprint(t.X)
|
|
|
|
}
|
2014-06-23 13:38:19 +00:00
|
|
|
return false, fmt.Sprint(f.Type)
|
2014-06-21 13:27:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func isBasicType(Type string) bool {
|
|
|
|
for _, v := range basicTypes {
|
|
|
|
if v == Type {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// refer to builtin.go
|
|
|
|
var basicTypes = []string{
|
2014-06-24 04:06:45 +00:00
|
|
|
"bool",
|
2014-06-21 13:27:12 +00:00
|
|
|
"uint", "uint8", "uint16", "uint32", "uint64",
|
|
|
|
"int", "int8", "int16", "int32", "int64",
|
|
|
|
"float32", "float64",
|
|
|
|
"string",
|
2014-06-24 04:06:45 +00:00
|
|
|
"complex64", "complex128",
|
|
|
|
"byte", "rune", "uintptr",
|
2014-06-21 13:27:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// regexp get json tag
|
|
|
|
func grepJsonTag(tag string) string {
|
|
|
|
r, _ := regexp.Compile(`json:"([^"]*)"`)
|
|
|
|
matches := r.FindAllStringSubmatch(tag, -1)
|
|
|
|
if len(matches) > 0 {
|
|
|
|
return matches[0][1]
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// append models
|
2014-06-23 13:25:12 +00:00
|
|
|
func appendModels(cmpath, pkgpath, controllerName string, realTypes []string) {
|
2014-08-01 08:04:09 +00:00
|
|
|
var p string
|
|
|
|
if cmpath != "" {
|
|
|
|
p = strings.Join(strings.Split(cmpath, "/"), ".") + "."
|
|
|
|
} else {
|
|
|
|
p = ""
|
|
|
|
}
|
2014-06-21 13:27:12 +00:00
|
|
|
for _, realType := range realTypes {
|
2014-06-24 04:06:45 +00:00
|
|
|
if realType != "" && !isBasicType(strings.TrimLeft(realType, "[]")) &&
|
|
|
|
!strings.HasPrefix(realType, "map") && !strings.HasPrefix(realType, "&") {
|
2014-08-01 08:04:09 +00:00
|
|
|
if _, ok := modelsList[pkgpath+controllerName][p+realType]; ok {
|
|
|
|
continue
|
2014-06-23 13:25:12 +00:00
|
|
|
}
|
2014-08-08 16:55:55 +00:00
|
|
|
//fmt.Printf(pkgpath + ":" + controllerName + ":" + cmpath + ":" + realType + "\n")
|
2014-08-01 08:04:09 +00:00
|
|
|
_, _, mod, newRealTypes := getModel(p + realType)
|
2014-07-07 10:06:02 +00:00
|
|
|
modelsList[pkgpath+controllerName][p+realType] = mod
|
2014-06-23 13:25:12 +00:00
|
|
|
appendModels(cmpath, pkgpath, controllerName, newRealTypes)
|
2014-06-21 13:27:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|