parser.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  1. // Copyright 2014 beego Author. All Rights Reserved.
  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. package beego
  15. import (
  16. "encoding/json"
  17. "errors"
  18. "fmt"
  19. "go/ast"
  20. "go/parser"
  21. "go/token"
  22. "io/ioutil"
  23. "os"
  24. "path/filepath"
  25. "regexp"
  26. "sort"
  27. "strconv"
  28. "strings"
  29. "unicode"
  30. "github.com/cnlh/nps/vender/github.com/astaxie/beego/context/param"
  31. "github.com/cnlh/nps/vender/github.com/astaxie/beego/logs"
  32. "github.com/cnlh/nps/vender/github.com/astaxie/beego/utils"
  33. )
  34. var globalRouterTemplate = `package routers
  35. import (
  36. "github.com/cnlh/nps/vender/github.com/astaxie/beego"
  37. "github.com/cnlh/nps/vender/github.com/astaxie/beego/context/param"{{.globalimport}}
  38. )
  39. func init() {
  40. {{.globalinfo}}
  41. }
  42. `
  43. var (
  44. lastupdateFilename = "lastupdate.tmp"
  45. commentFilename string
  46. pkgLastupdate map[string]int64
  47. genInfoList map[string][]ControllerComments
  48. routerHooks = map[string]int{
  49. "beego.BeforeStatic": BeforeStatic,
  50. "beego.BeforeRouter": BeforeRouter,
  51. "beego.BeforeExec": BeforeExec,
  52. "beego.AfterExec": AfterExec,
  53. "beego.FinishRouter": FinishRouter,
  54. }
  55. routerHooksMapping = map[int]string{
  56. BeforeStatic: "beego.BeforeStatic",
  57. BeforeRouter: "beego.BeforeRouter",
  58. BeforeExec: "beego.BeforeExec",
  59. AfterExec: "beego.AfterExec",
  60. FinishRouter: "beego.FinishRouter",
  61. }
  62. )
  63. const commentPrefix = "commentsRouter_"
  64. func init() {
  65. pkgLastupdate = make(map[string]int64)
  66. }
  67. func parserPkg(pkgRealpath, pkgpath string) error {
  68. rep := strings.NewReplacer("\\", "_", "/", "_", ".", "_")
  69. commentFilename, _ = filepath.Rel(AppPath, pkgRealpath)
  70. commentFilename = commentPrefix + rep.Replace(commentFilename) + ".go"
  71. if !compareFile(pkgRealpath) {
  72. logs.Info(pkgRealpath + " no changed")
  73. return nil
  74. }
  75. genInfoList = make(map[string][]ControllerComments)
  76. fileSet := token.NewFileSet()
  77. astPkgs, err := parser.ParseDir(fileSet, pkgRealpath, func(info os.FileInfo) bool {
  78. name := info.Name()
  79. return !info.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go")
  80. }, parser.ParseComments)
  81. if err != nil {
  82. return err
  83. }
  84. for _, pkg := range astPkgs {
  85. for _, fl := range pkg.Files {
  86. for _, d := range fl.Decls {
  87. switch specDecl := d.(type) {
  88. case *ast.FuncDecl:
  89. if specDecl.Recv != nil {
  90. exp, ok := specDecl.Recv.List[0].Type.(*ast.StarExpr) // Check that the type is correct first beforing throwing to parser
  91. if ok {
  92. parserComments(specDecl, fmt.Sprint(exp.X), pkgpath)
  93. }
  94. }
  95. }
  96. }
  97. }
  98. }
  99. genRouterCode(pkgRealpath)
  100. savetoFile(pkgRealpath)
  101. return nil
  102. }
  103. type parsedComment struct {
  104. routerPath string
  105. methods []string
  106. params map[string]parsedParam
  107. filters []parsedFilter
  108. imports []parsedImport
  109. }
  110. type parsedImport struct {
  111. importPath string
  112. importAlias string
  113. }
  114. type parsedFilter struct {
  115. pattern string
  116. pos int
  117. filter string
  118. params []bool
  119. }
  120. type parsedParam struct {
  121. name string
  122. datatype string
  123. location string
  124. defValue string
  125. required bool
  126. }
  127. func parserComments(f *ast.FuncDecl, controllerName, pkgpath string) error {
  128. if f.Doc != nil {
  129. parsedComments, err := parseComment(f.Doc.List)
  130. if err != nil {
  131. return err
  132. }
  133. for _, parsedComment := range parsedComments {
  134. if parsedComment.routerPath != "" {
  135. key := pkgpath + ":" + controllerName
  136. cc := ControllerComments{}
  137. cc.Method = f.Name.String()
  138. cc.Router = parsedComment.routerPath
  139. cc.AllowHTTPMethods = parsedComment.methods
  140. cc.MethodParams = buildMethodParams(f.Type.Params.List, parsedComment)
  141. cc.FilterComments = buildFilters(parsedComment.filters)
  142. cc.ImportComments = buildImports(parsedComment.imports)
  143. genInfoList[key] = append(genInfoList[key], cc)
  144. }
  145. }
  146. }
  147. return nil
  148. }
  149. func buildImports(pis []parsedImport) []*ControllerImportComments {
  150. var importComments []*ControllerImportComments
  151. for _, pi := range pis {
  152. importComments = append(importComments, &ControllerImportComments{
  153. ImportPath: pi.importPath,
  154. ImportAlias: pi.importAlias,
  155. })
  156. }
  157. return importComments
  158. }
  159. func buildFilters(pfs []parsedFilter) []*ControllerFilterComments {
  160. var filterComments []*ControllerFilterComments
  161. for _, pf := range pfs {
  162. var (
  163. returnOnOutput bool
  164. resetParams bool
  165. )
  166. if len(pf.params) >= 1 {
  167. returnOnOutput = pf.params[0]
  168. }
  169. if len(pf.params) >= 2 {
  170. resetParams = pf.params[1]
  171. }
  172. filterComments = append(filterComments, &ControllerFilterComments{
  173. Filter: pf.filter,
  174. Pattern: pf.pattern,
  175. Pos: pf.pos,
  176. ReturnOnOutput: returnOnOutput,
  177. ResetParams: resetParams,
  178. })
  179. }
  180. return filterComments
  181. }
  182. func buildMethodParams(funcParams []*ast.Field, pc *parsedComment) []*param.MethodParam {
  183. result := make([]*param.MethodParam, 0, len(funcParams))
  184. for _, fparam := range funcParams {
  185. for _, pName := range fparam.Names {
  186. methodParam := buildMethodParam(fparam, pName.Name, pc)
  187. result = append(result, methodParam)
  188. }
  189. }
  190. return result
  191. }
  192. func buildMethodParam(fparam *ast.Field, name string, pc *parsedComment) *param.MethodParam {
  193. options := []param.MethodParamOption{}
  194. if cparam, ok := pc.params[name]; ok {
  195. //Build param from comment info
  196. name = cparam.name
  197. if cparam.required {
  198. options = append(options, param.IsRequired)
  199. }
  200. switch cparam.location {
  201. case "body":
  202. options = append(options, param.InBody)
  203. case "header":
  204. options = append(options, param.InHeader)
  205. case "path":
  206. options = append(options, param.InPath)
  207. }
  208. if cparam.defValue != "" {
  209. options = append(options, param.Default(cparam.defValue))
  210. }
  211. } else {
  212. if paramInPath(name, pc.routerPath) {
  213. options = append(options, param.InPath)
  214. }
  215. }
  216. return param.New(name, options...)
  217. }
  218. func paramInPath(name, route string) bool {
  219. return strings.HasSuffix(route, ":"+name) ||
  220. strings.Contains(route, ":"+name+"/")
  221. }
  222. var routeRegex = regexp.MustCompile(`@router\s+(\S+)(?:\s+\[(\S+)\])?`)
  223. func parseComment(lines []*ast.Comment) (pcs []*parsedComment, err error) {
  224. pcs = []*parsedComment{}
  225. params := map[string]parsedParam{}
  226. filters := []parsedFilter{}
  227. imports := []parsedImport{}
  228. for _, c := range lines {
  229. t := strings.TrimSpace(strings.TrimLeft(c.Text, "//"))
  230. if strings.HasPrefix(t, "@Param") {
  231. pv := getparams(strings.TrimSpace(strings.TrimLeft(t, "@Param")))
  232. if len(pv) < 4 {
  233. logs.Error("Invalid @Param format. Needs at least 4 parameters")
  234. }
  235. p := parsedParam{}
  236. names := strings.SplitN(pv[0], "=>", 2)
  237. p.name = names[0]
  238. funcParamName := p.name
  239. if len(names) > 1 {
  240. funcParamName = names[1]
  241. }
  242. p.location = pv[1]
  243. p.datatype = pv[2]
  244. switch len(pv) {
  245. case 5:
  246. p.required, _ = strconv.ParseBool(pv[3])
  247. case 6:
  248. p.defValue = pv[3]
  249. p.required, _ = strconv.ParseBool(pv[4])
  250. }
  251. params[funcParamName] = p
  252. }
  253. }
  254. for _, c := range lines {
  255. t := strings.TrimSpace(strings.TrimLeft(c.Text, "//"))
  256. if strings.HasPrefix(t, "@Import") {
  257. iv := getparams(strings.TrimSpace(strings.TrimLeft(t, "@Import")))
  258. if len(iv) == 0 || len(iv) > 2 {
  259. logs.Error("Invalid @Import format. Only accepts 1 or 2 parameters")
  260. continue
  261. }
  262. p := parsedImport{}
  263. p.importPath = iv[0]
  264. if len(iv) == 2 {
  265. p.importAlias = iv[1]
  266. }
  267. imports = append(imports, p)
  268. }
  269. }
  270. filterLoop:
  271. for _, c := range lines {
  272. t := strings.TrimSpace(strings.TrimLeft(c.Text, "//"))
  273. if strings.HasPrefix(t, "@Filter") {
  274. fv := getparams(strings.TrimSpace(strings.TrimLeft(t, "@Filter")))
  275. if len(fv) < 3 {
  276. logs.Error("Invalid @Filter format. Needs at least 3 parameters")
  277. continue filterLoop
  278. }
  279. p := parsedFilter{}
  280. p.pattern = fv[0]
  281. posName := fv[1]
  282. if pos, exists := routerHooks[posName]; exists {
  283. p.pos = pos
  284. } else {
  285. logs.Error("Invalid @Filter pos: ", posName)
  286. continue filterLoop
  287. }
  288. p.filter = fv[2]
  289. fvParams := fv[3:]
  290. for _, fvParam := range fvParams {
  291. switch fvParam {
  292. case "true":
  293. p.params = append(p.params, true)
  294. case "false":
  295. p.params = append(p.params, false)
  296. default:
  297. logs.Error("Invalid @Filter param: ", fvParam)
  298. continue filterLoop
  299. }
  300. }
  301. filters = append(filters, p)
  302. }
  303. }
  304. for _, c := range lines {
  305. var pc = &parsedComment{}
  306. pc.params = params
  307. pc.filters = filters
  308. pc.imports = imports
  309. t := strings.TrimSpace(strings.TrimLeft(c.Text, "//"))
  310. if strings.HasPrefix(t, "@router") {
  311. t := strings.TrimSpace(strings.TrimLeft(c.Text, "//"))
  312. matches := routeRegex.FindStringSubmatch(t)
  313. if len(matches) == 3 {
  314. pc.routerPath = matches[1]
  315. methods := matches[2]
  316. if methods == "" {
  317. pc.methods = []string{"get"}
  318. //pc.hasGet = true
  319. } else {
  320. pc.methods = strings.Split(methods, ",")
  321. //pc.hasGet = strings.Contains(methods, "get")
  322. }
  323. pcs = append(pcs, pc)
  324. } else {
  325. return nil, errors.New("Router information is missing")
  326. }
  327. }
  328. }
  329. return
  330. }
  331. // direct copy from bee\g_docs.go
  332. // analysis params return []string
  333. // @Param query form string true "The email for login"
  334. // [query form string true "The email for login"]
  335. func getparams(str string) []string {
  336. var s []rune
  337. var j int
  338. var start bool
  339. var r []string
  340. var quoted int8
  341. for _, c := range str {
  342. if unicode.IsSpace(c) && quoted == 0 {
  343. if !start {
  344. continue
  345. } else {
  346. start = false
  347. j++
  348. r = append(r, string(s))
  349. s = make([]rune, 0)
  350. continue
  351. }
  352. }
  353. start = true
  354. if c == '"' {
  355. quoted ^= 1
  356. continue
  357. }
  358. s = append(s, c)
  359. }
  360. if len(s) > 0 {
  361. r = append(r, string(s))
  362. }
  363. return r
  364. }
  365. func genRouterCode(pkgRealpath string) {
  366. os.Mkdir(getRouterDir(pkgRealpath), 0755)
  367. logs.Info("generate router from comments")
  368. var (
  369. globalinfo string
  370. globalimport string
  371. sortKey []string
  372. )
  373. for k := range genInfoList {
  374. sortKey = append(sortKey, k)
  375. }
  376. sort.Strings(sortKey)
  377. for _, k := range sortKey {
  378. cList := genInfoList[k]
  379. sort.Sort(ControllerCommentsSlice(cList))
  380. for _, c := range cList {
  381. allmethod := "nil"
  382. if len(c.AllowHTTPMethods) > 0 {
  383. allmethod = "[]string{"
  384. for _, m := range c.AllowHTTPMethods {
  385. allmethod += `"` + m + `",`
  386. }
  387. allmethod = strings.TrimRight(allmethod, ",") + "}"
  388. }
  389. params := "nil"
  390. if len(c.Params) > 0 {
  391. params = "[]map[string]string{"
  392. for _, p := range c.Params {
  393. for k, v := range p {
  394. params = params + `map[string]string{` + k + `:"` + v + `"},`
  395. }
  396. }
  397. params = strings.TrimRight(params, ",") + "}"
  398. }
  399. methodParams := "param.Make("
  400. if len(c.MethodParams) > 0 {
  401. lines := make([]string, 0, len(c.MethodParams))
  402. for _, m := range c.MethodParams {
  403. lines = append(lines, fmt.Sprint(m))
  404. }
  405. methodParams += "\n " +
  406. strings.Join(lines, ",\n ") +
  407. ",\n "
  408. }
  409. methodParams += ")"
  410. imports := ""
  411. if len(c.ImportComments) > 0 {
  412. for _, i := range c.ImportComments {
  413. if i.ImportAlias != "" {
  414. imports += fmt.Sprintf(`
  415. %s "%s"`, i.ImportAlias, i.ImportPath)
  416. } else {
  417. imports += fmt.Sprintf(`
  418. "%s"`, i.ImportPath)
  419. }
  420. }
  421. }
  422. filters := ""
  423. if len(c.FilterComments) > 0 {
  424. for _, f := range c.FilterComments {
  425. filters += fmt.Sprintf(` &beego.ControllerFilter{
  426. Pattern: "%s",
  427. Pos: %s,
  428. Filter: %s,
  429. ReturnOnOutput: %v,
  430. ResetParams: %v,
  431. },`, f.Pattern, routerHooksMapping[f.Pos], f.Filter, f.ReturnOnOutput, f.ResetParams)
  432. }
  433. }
  434. if filters == "" {
  435. filters = "nil"
  436. } else {
  437. filters = fmt.Sprintf(`[]*beego.ControllerFilter{
  438. %s
  439. }`, filters)
  440. }
  441. globalimport = imports
  442. globalinfo = globalinfo + `
  443. beego.GlobalControllerRouter["` + k + `"] = append(beego.GlobalControllerRouter["` + k + `"],
  444. beego.ControllerComments{
  445. Method: "` + strings.TrimSpace(c.Method) + `",
  446. ` + "Router: `" + c.Router + "`" + `,
  447. AllowHTTPMethods: ` + allmethod + `,
  448. MethodParams: ` + methodParams + `,
  449. Filters: ` + filters + `,
  450. Params: ` + params + `})
  451. `
  452. }
  453. }
  454. if globalinfo != "" {
  455. f, err := os.Create(filepath.Join(getRouterDir(pkgRealpath), commentFilename))
  456. if err != nil {
  457. panic(err)
  458. }
  459. defer f.Close()
  460. content := strings.Replace(globalRouterTemplate, "{{.globalinfo}}", globalinfo, -1)
  461. content = strings.Replace(content, "{{.globalimport}}", globalimport, -1)
  462. f.WriteString(content)
  463. }
  464. }
  465. func compareFile(pkgRealpath string) bool {
  466. if !utils.FileExists(filepath.Join(getRouterDir(pkgRealpath), commentFilename)) {
  467. return true
  468. }
  469. if utils.FileExists(lastupdateFilename) {
  470. content, err := ioutil.ReadFile(lastupdateFilename)
  471. if err != nil {
  472. return true
  473. }
  474. json.Unmarshal(content, &pkgLastupdate)
  475. lastupdate, err := getpathTime(pkgRealpath)
  476. if err != nil {
  477. return true
  478. }
  479. if v, ok := pkgLastupdate[pkgRealpath]; ok {
  480. if lastupdate <= v {
  481. return false
  482. }
  483. }
  484. }
  485. return true
  486. }
  487. func savetoFile(pkgRealpath string) {
  488. lastupdate, err := getpathTime(pkgRealpath)
  489. if err != nil {
  490. return
  491. }
  492. pkgLastupdate[pkgRealpath] = lastupdate
  493. d, err := json.Marshal(pkgLastupdate)
  494. if err != nil {
  495. return
  496. }
  497. ioutil.WriteFile(lastupdateFilename, d, os.ModePerm)
  498. }
  499. func getpathTime(pkgRealpath string) (lastupdate int64, err error) {
  500. fl, err := ioutil.ReadDir(pkgRealpath)
  501. if err != nil {
  502. return lastupdate, err
  503. }
  504. for _, f := range fl {
  505. if lastupdate < f.ModTime().UnixNano() {
  506. lastupdate = f.ModTime().UnixNano()
  507. }
  508. }
  509. return lastupdate, nil
  510. }
  511. func getRouterDir(pkgRealpath string) string {
  512. dir := filepath.Dir(pkgRealpath)
  513. for {
  514. d := filepath.Join(dir, "routers")
  515. if utils.FileExists(d) {
  516. return d
  517. }
  518. if r, _ := filepath.Rel(dir, AppPath); r == "." {
  519. return d
  520. }
  521. // Parent dir.
  522. dir = filepath.Dir(dir)
  523. }
  524. }