blob: 5bc642bc8e9e5584546709a42afa7b192e1dcca2 [file] [log] [blame]
Zack Williamse940c7a2019-08-21 14:25:39 -07001/*
2Copyright 2015 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package runtime
18
19import (
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
32type 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
37type KubeTypes []Pair
38
39func 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
55func 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
97func 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.
117type bufferedLine struct {
118 line string
119 indentation int
120}
121
122type buffer struct {
123 lines []bufferedLine
124}
125
126func newBuffer() *buffer {
127 return &buffer{
128 lines: make([]bufferedLine, 0),
129 }
130}
131
132func (b *buffer) addLine(line string, indent int) {
133 b.lines = append(b.lines, bufferedLine{line, indent})
134}
135
136func (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
147func 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
152func 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
162func 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, ""}.
174func 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
199func 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.
234func 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}