Remove BPA from Makefile
[icn.git] / cmd / bpa-operator / vendor / k8s.io / apimachinery / pkg / runtime / swagger_doc_generator.go
1 /*
2 Copyright 2015 The Kubernetes Authors.
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 */
16
17 package runtime
18
19 import (
20         "bytes"
21         "fmt"
22         "go/ast"
23         "go/doc"
24         "go/parser"
25         "go/token"
26         "io"
27         "reflect"
28         "strings"
29 )
30
31 // Pair of strings. We keed the name of fields and the doc
32 type Pair struct {
33         Name, Doc string
34 }
35
36 // KubeTypes is an array to represent all available types in a parsed file. [0] is for the type itself
37 type KubeTypes []Pair
38
39 func astFrom(filePath string) *doc.Package {
40         fset := token.NewFileSet()
41         m := make(map[string]*ast.File)
42
43         f, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments)
44         if err != nil {
45                 fmt.Println(err)
46                 return nil
47         }
48
49         m[filePath] = f
50         apkg, _ := ast.NewPackage(fset, m, nil, nil)
51
52         return doc.New(apkg, "", 0)
53 }
54
55 func fmtRawDoc(rawDoc string) string {
56         var buffer bytes.Buffer
57         delPrevChar := func() {
58                 if buffer.Len() > 0 {
59                         buffer.Truncate(buffer.Len() - 1) // Delete the last " " or "\n"
60                 }
61         }
62
63         // Ignore all lines after ---
64         rawDoc = strings.Split(rawDoc, "---")[0]
65
66         for _, line := range strings.Split(rawDoc, "\n") {
67                 line = strings.TrimRight(line, " ")
68                 leading := strings.TrimLeft(line, " ")
69                 switch {
70                 case len(line) == 0: // Keep paragraphs
71                         delPrevChar()
72                         buffer.WriteString("\n\n")
73                 case strings.HasPrefix(leading, "TODO"): // Ignore one line TODOs
74                 case strings.HasPrefix(leading, "+"): // Ignore instructions to the generators
75                 default:
76                         if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") {
77                                 delPrevChar()
78                                 line = "\n" + line + "\n" // Replace it with newline. This is useful when we have a line with: "Example:\n\tJSON-someting..."
79                         } else {
80                                 line += " "
81                         }
82                         buffer.WriteString(line)
83                 }
84         }
85
86         postDoc := strings.TrimRight(buffer.String(), "\n")
87         postDoc = strings.Replace(postDoc, "\\\"", "\"", -1) // replace user's \" to "
88         postDoc = strings.Replace(postDoc, "\"", "\\\"", -1) // Escape "
89         postDoc = strings.Replace(postDoc, "\n", "\\n", -1)
90         postDoc = strings.Replace(postDoc, "\t", "\\t", -1)
91
92         return postDoc
93 }
94
95 // fieldName returns the name of the field as it should appear in JSON format
96 // "-" indicates that this field is not part of the JSON representation
97 func fieldName(field *ast.Field) string {
98         jsonTag := ""
99         if field.Tag != nil {
100                 jsonTag = reflect.StructTag(field.Tag.Value[1 : len(field.Tag.Value)-1]).Get("json") // Delete first and last quotation
101                 if strings.Contains(jsonTag, "inline") {
102                         return "-"
103                 }
104         }
105
106         jsonTag = strings.Split(jsonTag, ",")[0] // This can return "-"
107         if jsonTag == "" {
108                 if field.Names != nil {
109                         return field.Names[0].Name
110                 }
111                 return field.Type.(*ast.Ident).Name
112         }
113         return jsonTag
114 }
115
116 // A buffer of lines that will be written.
117 type bufferedLine struct {
118         line        string
119         indentation int
120 }
121
122 type buffer struct {
123         lines []bufferedLine
124 }
125
126 func newBuffer() *buffer {
127         return &buffer{
128                 lines: make([]bufferedLine, 0),
129         }
130 }
131
132 func (b *buffer) addLine(line string, indent int) {
133         b.lines = append(b.lines, bufferedLine{line, indent})
134 }
135
136 func (b *buffer) flushLines(w io.Writer) error {
137         for _, line := range b.lines {
138                 indentation := strings.Repeat("\t", line.indentation)
139                 fullLine := fmt.Sprintf("%s%s", indentation, line.line)
140                 if _, err := io.WriteString(w, fullLine); err != nil {
141                         return err
142                 }
143         }
144         return nil
145 }
146
147 func writeFuncHeader(b *buffer, structName string, indent int) {
148         s := fmt.Sprintf("var map_%s = map[string]string {\n", structName)
149         b.addLine(s, indent)
150 }
151
152 func writeFuncFooter(b *buffer, structName string, indent int) {
153         b.addLine("}\n", indent) // Closes the map definition
154
155         s := fmt.Sprintf("func (%s) SwaggerDoc() map[string]string {\n", structName)
156         b.addLine(s, indent)
157         s = fmt.Sprintf("return map_%s\n", structName)
158         b.addLine(s, indent+1)
159         b.addLine("}\n", indent) // Closes the function definition
160 }
161
162 func writeMapBody(b *buffer, kubeType []Pair, indent int) {
163         format := "\"%s\": \"%s\",\n"
164         for _, pair := range kubeType {
165                 s := fmt.Sprintf(format, pair.Name, pair.Doc)
166                 b.addLine(s, indent+2)
167         }
168 }
169
170 // ParseDocumentationFrom gets all types' documentation and returns them as an
171 // array. Each type is again represented as an array (we have to use arrays as we
172 // need to be sure for the order of the fields). This function returns fields and
173 // struct definitions that have no documentation as {name, ""}.
174 func ParseDocumentationFrom(src string) []KubeTypes {
175         var docForTypes []KubeTypes
176
177         pkg := astFrom(src)
178
179         for _, kubType := range pkg.Types {
180                 if structType, ok := kubType.Decl.Specs[0].(*ast.TypeSpec).Type.(*ast.StructType); ok {
181                         var ks KubeTypes
182                         ks = append(ks, Pair{kubType.Name, fmtRawDoc(kubType.Doc)})
183
184                         for _, field := range structType.Fields.List {
185                                 if n := fieldName(field); n != "-" {
186                                         fieldDoc := fmtRawDoc(field.Doc.Text())
187                                         ks = append(ks, Pair{n, fieldDoc})
188                                 }
189                         }
190                         docForTypes = append(docForTypes, ks)
191                 }
192         }
193
194         return docForTypes
195 }
196
197 // WriteSwaggerDocFunc writes a declaration of a function as a string. This function is used in
198 // Swagger as a documentation source for structs and theirs fields
199 func WriteSwaggerDocFunc(kubeTypes []KubeTypes, w io.Writer) error {
200         for _, kubeType := range kubeTypes {
201                 structName := kubeType[0].Name
202                 kubeType[0].Name = ""
203
204                 // Ignore empty documentation
205                 docfulTypes := make(KubeTypes, 0, len(kubeType))
206                 for _, pair := range kubeType {
207                         if pair.Doc != "" {
208                                 docfulTypes = append(docfulTypes, pair)
209                         }
210                 }
211
212                 if len(docfulTypes) == 0 {
213                         continue // If no fields and the struct have documentation, skip the function definition
214                 }
215
216                 indent := 0
217                 buffer := newBuffer()
218
219                 writeFuncHeader(buffer, structName, indent)
220                 writeMapBody(buffer, docfulTypes, indent)
221                 writeFuncFooter(buffer, structName, indent)
222                 buffer.addLine("\n", 0)
223
224                 if err := buffer.flushLines(w); err != nil {
225                         return err
226                 }
227         }
228
229         return nil
230 }
231
232 // VerifySwaggerDocsExist writes in a io.Writer a list of structs and fields that
233 // are missing of documentation.
234 func VerifySwaggerDocsExist(kubeTypes []KubeTypes, w io.Writer) (int, error) {
235         missingDocs := 0
236         buffer := newBuffer()
237
238         for _, kubeType := range kubeTypes {
239                 structName := kubeType[0].Name
240                 if kubeType[0].Doc == "" {
241                         format := "Missing documentation for the struct itself: %s\n"
242                         s := fmt.Sprintf(format, structName)
243                         buffer.addLine(s, 0)
244                         missingDocs++
245                 }
246                 kubeType = kubeType[1:] // Skip struct definition
247
248                 for _, pair := range kubeType { // Iterate only the fields
249                         if pair.Doc == "" {
250                                 format := "In struct: %s, field documentation is missing: %s\n"
251                                 s := fmt.Sprintf(format, structName, pair.Name)
252                                 buffer.addLine(s, 0)
253                                 missingDocs++
254                         }
255                 }
256         }
257
258         if err := buffer.flushLines(w); err != nil {
259                 return -1, err
260         }
261         return missingDocs, nil
262 }