github.com/spotmaxtech/k8s-apimachinery-v0260@v0.0.1/pkg/runtime/swagger_doc_generator.go (about) 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 }