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  }