github.com/timstclair/heapster@v0.20.0-alpha1/Godeps/_workspace/src/k8s.io/kubernetes/pkg/runtime/swagger_doc_generator.go (about)

     1  /*
     2  Copyright 2015 The Kubernetes Authors All rights reserved.
     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  
    69  		if line == "" { // Keep paragraphs
    70  			delPrevChar()
    71  			buffer.WriteString("\n\n")
    72  		} else if !strings.HasPrefix(strings.TrimLeft(line, " "), "TODO") { // Ignore one line TODOs
    73  			if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") {
    74  				delPrevChar()
    75  				line = "\n" + line + "\n" // Replace it with newline. This is useful when we have a line with: "Example:\n\tJSON-someting..."
    76  			} else {
    77  				line += " "
    78  			}
    79  			buffer.WriteString(line)
    80  		}
    81  	}
    82  
    83  	postDoc := strings.TrimRight(buffer.String(), "\n")
    84  	postDoc = strings.Replace(postDoc, "\\\"", "\"", -1) // replace user's \" to "
    85  	postDoc = strings.Replace(postDoc, "\"", "\\\"", -1) // Escape "
    86  	postDoc = strings.Replace(postDoc, "\n", "\\n", -1)
    87  	postDoc = strings.Replace(postDoc, "\t", "\\t", -1)
    88  
    89  	return postDoc
    90  }
    91  
    92  // fieldName returns the name of the field as it should appear in JSON format
    93  // "-" indicates that this field is not part of the JSON representation
    94  func fieldName(field *ast.Field) string {
    95  	jsonTag := ""
    96  	if field.Tag != nil {
    97  		jsonTag = reflect.StructTag(field.Tag.Value[1 : len(field.Tag.Value)-1]).Get("json") // Delete first and last quotation
    98  		if strings.Contains(jsonTag, "inline") {
    99  			return "-"
   100  		}
   101  	}
   102  
   103  	jsonTag = strings.Split(jsonTag, ",")[0] // This can return "-"
   104  	if jsonTag == "" {
   105  		if field.Names != nil {
   106  			return field.Names[0].Name
   107  		}
   108  		return field.Type.(*ast.Ident).Name
   109  	}
   110  	return jsonTag
   111  }
   112  
   113  func writeFuncHeader(b *buffer, structName string, indent int) {
   114  	s := fmt.Sprintf("var map_%s = map[string]string {\n", structName)
   115  	b.addLine(s, indent)
   116  }
   117  
   118  func writeFuncFooter(b *buffer, structName string, indent int) {
   119  	b.addLine("}\n", indent) // Closes the map definition
   120  
   121  	s := fmt.Sprintf("func (%s) SwaggerDoc() map[string]string {\n", structName)
   122  	b.addLine(s, indent)
   123  	s = fmt.Sprintf("return map_%s\n", structName)
   124  	b.addLine(s, indent+1)
   125  	b.addLine("}\n", indent) // Closes the function definition
   126  }
   127  
   128  func writeMapBody(b *buffer, kubeType []Pair, indent int) {
   129  	format := "\"%s\": \"%s\",\n"
   130  	for _, pair := range kubeType {
   131  		s := fmt.Sprintf(format, pair.Name, pair.Doc)
   132  		b.addLine(s, indent+2)
   133  	}
   134  }
   135  
   136  // ParseDocumentationFrom gets all types' documentation and returns them as an
   137  // array. Each type is again represented as an array (we have to use arrays as we
   138  // need to be sure for the order of the fields). This function returns fields and
   139  // struct definitions that have no documentation as {name, ""}.
   140  func ParseDocumentationFrom(src string) []KubeTypes {
   141  	var docForTypes []KubeTypes
   142  
   143  	pkg := astFrom(src)
   144  
   145  	for _, kubType := range pkg.Types {
   146  		if structType, ok := kubType.Decl.Specs[0].(*ast.TypeSpec).Type.(*ast.StructType); ok {
   147  			var ks KubeTypes
   148  			ks = append(ks, Pair{kubType.Name, fmtRawDoc(kubType.Doc)})
   149  
   150  			for _, field := range structType.Fields.List {
   151  				if n := fieldName(field); n != "-" {
   152  					fieldDoc := fmtRawDoc(field.Doc.Text())
   153  					ks = append(ks, Pair{n, fieldDoc})
   154  				}
   155  			}
   156  			docForTypes = append(docForTypes, ks)
   157  		}
   158  	}
   159  
   160  	return docForTypes
   161  }
   162  
   163  // WriteSwaggerDocFunc writes a declaration of a function as a string. This function is used in
   164  // Swagger as a documentation source for structs and theirs fields
   165  func WriteSwaggerDocFunc(kubeTypes []KubeTypes, w io.Writer) error {
   166  	for _, kubeType := range kubeTypes {
   167  		structName := kubeType[0].Name
   168  		kubeType[0].Name = ""
   169  
   170  		// Ignore empty documentation
   171  		docfulTypes := make(KubeTypes, 0, len(kubeType))
   172  		for _, pair := range kubeType {
   173  			if pair.Doc != "" {
   174  				docfulTypes = append(docfulTypes, pair)
   175  			}
   176  		}
   177  
   178  		if len(docfulTypes) == 0 {
   179  			continue // If no fields and the struct have documentation, skip the function definition
   180  		}
   181  
   182  		indent := 0
   183  		buffer := newBuffer()
   184  
   185  		writeFuncHeader(buffer, structName, indent)
   186  		writeMapBody(buffer, docfulTypes, indent)
   187  		writeFuncFooter(buffer, structName, indent)
   188  		buffer.addLine("\n", 0)
   189  
   190  		if err := buffer.flushLines(w); err != nil {
   191  			return err
   192  		}
   193  	}
   194  
   195  	return nil
   196  }
   197  
   198  // VerifySwaggerDocsExist writes in a io.Writer a list of structs and fields that
   199  // are missing of documentation.
   200  func VerifySwaggerDocsExist(kubeTypes []KubeTypes, w io.Writer) (int, error) {
   201  	missingDocs := 0
   202  	buffer := newBuffer()
   203  
   204  	for _, kubeType := range kubeTypes {
   205  		structName := kubeType[0].Name
   206  		if kubeType[0].Doc == "" {
   207  			format := "Missing documentation for the struct itself: %s\n"
   208  			s := fmt.Sprintf(format, structName)
   209  			buffer.addLine(s, 0)
   210  			missingDocs++
   211  		}
   212  		kubeType = kubeType[1:] // Skip struct definition
   213  
   214  		for _, pair := range kubeType { // Iterate only the fields
   215  			if pair.Doc == "" {
   216  				format := "In struct: %s, field documentation is missing: %s\n"
   217  				s := fmt.Sprintf(format, structName, pair.Name)
   218  				buffer.addLine(s, 0)
   219  				missingDocs++
   220  			}
   221  		}
   222  	}
   223  
   224  	if err := buffer.flushLines(w); err != nil {
   225  		return -1, err
   226  	}
   227  	return missingDocs, nil
   228  }