You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

classifier.go 4.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. // +build !go1.11
  2. // Copyright 2015 go-swagger maintainers
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. package scan
  16. import (
  17. "fmt"
  18. "go/ast"
  19. "log"
  20. "regexp"
  21. "golang.org/x/tools/go/loader"
  22. )
  23. type packageFilter struct {
  24. Name string
  25. }
  26. func (pf *packageFilter) Matches(path string) bool {
  27. matched, err := regexp.MatchString(pf.Name, path)
  28. if err != nil {
  29. log.Fatal(err)
  30. }
  31. return matched
  32. }
  33. type packageFilters []packageFilter
  34. func (pf packageFilters) HasFilters() bool {
  35. return len(pf) > 0
  36. }
  37. func (pf packageFilters) Matches(path string) bool {
  38. for _, mod := range pf {
  39. if mod.Matches(path) {
  40. return true
  41. }
  42. }
  43. return false
  44. }
  45. type classifiedProgram struct {
  46. Meta []*ast.File
  47. Models []*ast.File
  48. Routes []*ast.File
  49. Operations []*ast.File
  50. Parameters []*ast.File
  51. Responses []*ast.File
  52. }
  53. // programClassifier classifies the files of a program into buckets
  54. // for processing by a swagger spec generator. This buckets files in
  55. // 3 groups: Meta, Models and Operations.
  56. //
  57. // Each of these buckets is then processed with an appropriate parsing strategy
  58. //
  59. // When there are Include or Exclude filters provide they are used to limit the
  60. // candidates prior to parsing.
  61. // The include filters take precedence over the excludes. So when something appears
  62. // in both filters it will be included.
  63. type programClassifier struct {
  64. Includes packageFilters
  65. Excludes packageFilters
  66. }
  67. func (pc *programClassifier) Classify(prog *loader.Program) (*classifiedProgram, error) {
  68. var cp classifiedProgram
  69. for pkg, pkgInfo := range prog.AllPackages {
  70. if Debug {
  71. log.Printf("analyzing: %s\n", pkg.Path())
  72. }
  73. if pc.Includes.HasFilters() {
  74. if !pc.Includes.Matches(pkg.Path()) {
  75. continue
  76. }
  77. } else if pc.Excludes.HasFilters() {
  78. if pc.Excludes.Matches(pkg.Path()) {
  79. continue
  80. }
  81. }
  82. for _, file := range pkgInfo.Files {
  83. var ro, op, mt, pm, rs, mm bool // only add a particular file once
  84. for _, comments := range file.Comments {
  85. var seenStruct string
  86. for _, cline := range comments.List {
  87. if cline != nil {
  88. matches := rxSwaggerAnnotation.FindStringSubmatch(cline.Text)
  89. if len(matches) > 1 {
  90. switch matches[1] {
  91. case "route":
  92. if !ro {
  93. cp.Routes = append(cp.Routes, file)
  94. ro = true
  95. }
  96. case "operation":
  97. if !op {
  98. cp.Operations = append(cp.Operations, file)
  99. op = true
  100. }
  101. case "model":
  102. if !mm {
  103. cp.Models = append(cp.Models, file)
  104. mm = true
  105. }
  106. if seenStruct == "" || seenStruct == matches[1] {
  107. seenStruct = matches[1]
  108. } else {
  109. return nil, fmt.Errorf("classifier: already annotated as %s, can't also be %q", seenStruct, matches[1])
  110. }
  111. case "meta":
  112. if !mt {
  113. cp.Meta = append(cp.Meta, file)
  114. mt = true
  115. }
  116. case "parameters":
  117. if !pm {
  118. cp.Parameters = append(cp.Parameters, file)
  119. pm = true
  120. }
  121. if seenStruct == "" || seenStruct == matches[1] {
  122. seenStruct = matches[1]
  123. } else {
  124. return nil, fmt.Errorf("classifier: already annotated as %s, can't also be %q", seenStruct, matches[1])
  125. }
  126. case "response":
  127. if !rs {
  128. cp.Responses = append(cp.Responses, file)
  129. rs = true
  130. }
  131. if seenStruct == "" || seenStruct == matches[1] {
  132. seenStruct = matches[1]
  133. } else {
  134. return nil, fmt.Errorf("classifier: already annotated as %s, can't also be %q", seenStruct, matches[1])
  135. }
  136. case "strfmt", "name", "discriminated", "file", "enum", "default", "alias", "type":
  137. // TODO: perhaps collect these and pass along to avoid lookups later on
  138. case "allOf":
  139. case "ignore":
  140. default:
  141. return nil, fmt.Errorf("classifier: unknown swagger annotation %q", matches[1])
  142. }
  143. }
  144. }
  145. }
  146. }
  147. }
  148. }
  149. return &cp, nil
  150. }