2 Copyright 2015 The Kubernetes Authors.
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
8 http://www.apache.org/licenses/LICENSE-2.0
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.
31 // Pair of strings. We keed the name of fields and the doc
36 // KubeTypes is an array to represent all available types in a parsed file. [0] is for the type itself
39 func astFrom(filePath string) *doc.Package {
40 fset := token.NewFileSet()
41 m := make(map[string]*ast.File)
43 f, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments)
50 apkg, _ := ast.NewPackage(fset, m, nil, nil)
52 return doc.New(apkg, "", 0)
55 func fmtRawDoc(rawDoc string) string {
56 var buffer bytes.Buffer
57 delPrevChar := func() {
59 buffer.Truncate(buffer.Len() - 1) // Delete the last " " or "\n"
63 // Ignore all lines after ---
64 rawDoc = strings.Split(rawDoc, "---")[0]
66 for _, line := range strings.Split(rawDoc, "\n") {
67 line = strings.TrimRight(line, " ")
68 leading := strings.TrimLeft(line, " ")
70 case len(line) == 0: // Keep paragraphs
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
76 if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") {
78 line = "\n" + line + "\n" // Replace it with newline. This is useful when we have a line with: "Example:\n\tJSON-someting..."
82 buffer.WriteString(line)
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)
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 {
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") {
106 jsonTag = strings.Split(jsonTag, ",")[0] // This can return "-"
108 if field.Names != nil {
109 return field.Names[0].Name
111 return field.Type.(*ast.Ident).Name
116 // A buffer of lines that will be written.
117 type bufferedLine struct {
126 func newBuffer() *buffer {
128 lines: make([]bufferedLine, 0),
132 func (b *buffer) addLine(line string, indent int) {
133 b.lines = append(b.lines, bufferedLine{line, indent})
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 {
147 func writeFuncHeader(b *buffer, structName string, indent int) {
148 s := fmt.Sprintf("var map_%s = map[string]string {\n", structName)
152 func writeFuncFooter(b *buffer, structName string, indent int) {
153 b.addLine("}\n", indent) // Closes the map definition
155 s := fmt.Sprintf("func (%s) SwaggerDoc() map[string]string {\n", structName)
157 s = fmt.Sprintf("return map_%s\n", structName)
158 b.addLine(s, indent+1)
159 b.addLine("}\n", indent) // Closes the function definition
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)
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
179 for _, kubType := range pkg.Types {
180 if structType, ok := kubType.Decl.Specs[0].(*ast.TypeSpec).Type.(*ast.StructType); ok {
182 ks = append(ks, Pair{kubType.Name, fmtRawDoc(kubType.Doc)})
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})
190 docForTypes = append(docForTypes, ks)
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 = ""
204 // Ignore empty documentation
205 docfulTypes := make(KubeTypes, 0, len(kubeType))
206 for _, pair := range kubeType {
208 docfulTypes = append(docfulTypes, pair)
212 if len(docfulTypes) == 0 {
213 continue // If no fields and the struct have documentation, skip the function definition
217 buffer := newBuffer()
219 writeFuncHeader(buffer, structName, indent)
220 writeMapBody(buffer, docfulTypes, indent)
221 writeFuncFooter(buffer, structName, indent)
222 buffer.addLine("\n", 0)
224 if err := buffer.flushLines(w); err != nil {
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) {
236 buffer := newBuffer()
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)
246 kubeType = kubeType[1:] // Skip struct definition
248 for _, pair := range kubeType { // Iterate only the fields
250 format := "In struct: %s, field documentation is missing: %s\n"
251 s := fmt.Sprintf(format, structName, pair.Name)
258 if err := buffer.flushLines(w); err != nil {
261 return missingDocs, nil