mirror of
				https://github.com/kubernetes/client-go.git
				synced 2025-11-03 23:40:44 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			263 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			263 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
Copyright 2015 The Kubernetes Authors.
 | 
						|
 | 
						|
Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
you may not use this file except in compliance with the License.
 | 
						|
You may obtain a copy of the License at
 | 
						|
 | 
						|
    http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 | 
						|
Unless required by applicable law or agreed to in writing, software
 | 
						|
distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
See the License for the specific language governing permissions and
 | 
						|
limitations under the License.
 | 
						|
*/
 | 
						|
 | 
						|
package runtime
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"fmt"
 | 
						|
	"go/ast"
 | 
						|
	"go/doc"
 | 
						|
	"go/parser"
 | 
						|
	"go/token"
 | 
						|
	"io"
 | 
						|
	"reflect"
 | 
						|
	"strings"
 | 
						|
)
 | 
						|
 | 
						|
// Pair of strings. We keed the name of fields and the doc
 | 
						|
type Pair struct {
 | 
						|
	Name, Doc string
 | 
						|
}
 | 
						|
 | 
						|
// KubeTypes is an array to represent all available types in a parsed file. [0] is for the type itself
 | 
						|
type KubeTypes []Pair
 | 
						|
 | 
						|
func astFrom(filePath string) *doc.Package {
 | 
						|
	fset := token.NewFileSet()
 | 
						|
	m := make(map[string]*ast.File)
 | 
						|
 | 
						|
	f, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments)
 | 
						|
	if err != nil {
 | 
						|
		fmt.Println(err)
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	m[filePath] = f
 | 
						|
	apkg, _ := ast.NewPackage(fset, m, nil, nil)
 | 
						|
 | 
						|
	return doc.New(apkg, "", 0)
 | 
						|
}
 | 
						|
 | 
						|
func fmtRawDoc(rawDoc string) string {
 | 
						|
	var buffer bytes.Buffer
 | 
						|
	delPrevChar := func() {
 | 
						|
		if buffer.Len() > 0 {
 | 
						|
			buffer.Truncate(buffer.Len() - 1) // Delete the last " " or "\n"
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Ignore all lines after ---
 | 
						|
	rawDoc = strings.Split(rawDoc, "---")[0]
 | 
						|
 | 
						|
	for _, line := range strings.Split(rawDoc, "\n") {
 | 
						|
		line = strings.TrimRight(line, " ")
 | 
						|
		leading := strings.TrimLeft(line, " ")
 | 
						|
		switch {
 | 
						|
		case len(line) == 0: // Keep paragraphs
 | 
						|
			delPrevChar()
 | 
						|
			buffer.WriteString("\n\n")
 | 
						|
		case strings.HasPrefix(leading, "TODO"): // Ignore one line TODOs
 | 
						|
		case strings.HasPrefix(leading, "+"): // Ignore instructions to go2idl
 | 
						|
		default:
 | 
						|
			if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") {
 | 
						|
				delPrevChar()
 | 
						|
				line = "\n" + line + "\n" // Replace it with newline. This is useful when we have a line with: "Example:\n\tJSON-someting..."
 | 
						|
			} else {
 | 
						|
				line += " "
 | 
						|
			}
 | 
						|
			buffer.WriteString(line)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	postDoc := strings.TrimRight(buffer.String(), "\n")
 | 
						|
	postDoc = strings.Replace(postDoc, "\\\"", "\"", -1) // replace user's \" to "
 | 
						|
	postDoc = strings.Replace(postDoc, "\"", "\\\"", -1) // Escape "
 | 
						|
	postDoc = strings.Replace(postDoc, "\n", "\\n", -1)
 | 
						|
	postDoc = strings.Replace(postDoc, "\t", "\\t", -1)
 | 
						|
 | 
						|
	return postDoc
 | 
						|
}
 | 
						|
 | 
						|
// fieldName returns the name of the field as it should appear in JSON format
 | 
						|
// "-" indicates that this field is not part of the JSON representation
 | 
						|
func fieldName(field *ast.Field) string {
 | 
						|
	jsonTag := ""
 | 
						|
	if field.Tag != nil {
 | 
						|
		jsonTag = reflect.StructTag(field.Tag.Value[1 : len(field.Tag.Value)-1]).Get("json") // Delete first and last quotation
 | 
						|
		if strings.Contains(jsonTag, "inline") {
 | 
						|
			return "-"
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	jsonTag = strings.Split(jsonTag, ",")[0] // This can return "-"
 | 
						|
	if jsonTag == "" {
 | 
						|
		if field.Names != nil {
 | 
						|
			return field.Names[0].Name
 | 
						|
		}
 | 
						|
		return field.Type.(*ast.Ident).Name
 | 
						|
	}
 | 
						|
	return jsonTag
 | 
						|
}
 | 
						|
 | 
						|
// A buffer of lines that will be written.
 | 
						|
type bufferedLine struct {
 | 
						|
	line        string
 | 
						|
	indentation int
 | 
						|
}
 | 
						|
 | 
						|
type buffer struct {
 | 
						|
	lines []bufferedLine
 | 
						|
}
 | 
						|
 | 
						|
func newBuffer() *buffer {
 | 
						|
	return &buffer{
 | 
						|
		lines: make([]bufferedLine, 0),
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (b *buffer) addLine(line string, indent int) {
 | 
						|
	b.lines = append(b.lines, bufferedLine{line, indent})
 | 
						|
}
 | 
						|
 | 
						|
func (b *buffer) flushLines(w io.Writer) error {
 | 
						|
	for _, line := range b.lines {
 | 
						|
		indentation := strings.Repeat("\t", line.indentation)
 | 
						|
		fullLine := fmt.Sprintf("%s%s", indentation, line.line)
 | 
						|
		if _, err := io.WriteString(w, fullLine); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func writeFuncHeader(b *buffer, structName string, indent int) {
 | 
						|
	s := fmt.Sprintf("var map_%s = map[string]string {\n", structName)
 | 
						|
	b.addLine(s, indent)
 | 
						|
}
 | 
						|
 | 
						|
func writeFuncFooter(b *buffer, structName string, indent int) {
 | 
						|
	b.addLine("}\n", indent) // Closes the map definition
 | 
						|
 | 
						|
	s := fmt.Sprintf("func (%s) SwaggerDoc() map[string]string {\n", structName)
 | 
						|
	b.addLine(s, indent)
 | 
						|
	s = fmt.Sprintf("return map_%s\n", structName)
 | 
						|
	b.addLine(s, indent+1)
 | 
						|
	b.addLine("}\n", indent) // Closes the function definition
 | 
						|
}
 | 
						|
 | 
						|
func writeMapBody(b *buffer, kubeType []Pair, indent int) {
 | 
						|
	format := "\"%s\": \"%s\",\n"
 | 
						|
	for _, pair := range kubeType {
 | 
						|
		s := fmt.Sprintf(format, pair.Name, pair.Doc)
 | 
						|
		b.addLine(s, indent+2)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// ParseDocumentationFrom gets all types' documentation and returns them as an
 | 
						|
// array. Each type is again represented as an array (we have to use arrays as we
 | 
						|
// need to be sure for the order of the fields). This function returns fields and
 | 
						|
// struct definitions that have no documentation as {name, ""}.
 | 
						|
func ParseDocumentationFrom(src string) []KubeTypes {
 | 
						|
	var docForTypes []KubeTypes
 | 
						|
 | 
						|
	pkg := astFrom(src)
 | 
						|
 | 
						|
	for _, kubType := range pkg.Types {
 | 
						|
		if structType, ok := kubType.Decl.Specs[0].(*ast.TypeSpec).Type.(*ast.StructType); ok {
 | 
						|
			var ks KubeTypes
 | 
						|
			ks = append(ks, Pair{kubType.Name, fmtRawDoc(kubType.Doc)})
 | 
						|
 | 
						|
			for _, field := range structType.Fields.List {
 | 
						|
				if n := fieldName(field); n != "-" {
 | 
						|
					fieldDoc := fmtRawDoc(field.Doc.Text())
 | 
						|
					ks = append(ks, Pair{n, fieldDoc})
 | 
						|
				}
 | 
						|
			}
 | 
						|
			docForTypes = append(docForTypes, ks)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return docForTypes
 | 
						|
}
 | 
						|
 | 
						|
// WriteSwaggerDocFunc writes a declaration of a function as a string. This function is used in
 | 
						|
// Swagger as a documentation source for structs and theirs fields
 | 
						|
func WriteSwaggerDocFunc(kubeTypes []KubeTypes, w io.Writer) error {
 | 
						|
	for _, kubeType := range kubeTypes {
 | 
						|
		structName := kubeType[0].Name
 | 
						|
		kubeType[0].Name = ""
 | 
						|
 | 
						|
		// Ignore empty documentation
 | 
						|
		docfulTypes := make(KubeTypes, 0, len(kubeType))
 | 
						|
		for _, pair := range kubeType {
 | 
						|
			if pair.Doc != "" {
 | 
						|
				docfulTypes = append(docfulTypes, pair)
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if len(docfulTypes) == 0 {
 | 
						|
			continue // If no fields and the struct have documentation, skip the function definition
 | 
						|
		}
 | 
						|
 | 
						|
		indent := 0
 | 
						|
		buffer := newBuffer()
 | 
						|
 | 
						|
		writeFuncHeader(buffer, structName, indent)
 | 
						|
		writeMapBody(buffer, docfulTypes, indent)
 | 
						|
		writeFuncFooter(buffer, structName, indent)
 | 
						|
		buffer.addLine("\n", 0)
 | 
						|
 | 
						|
		if err := buffer.flushLines(w); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// VerifySwaggerDocsExist writes in a io.Writer a list of structs and fields that
 | 
						|
// are missing of documentation.
 | 
						|
func VerifySwaggerDocsExist(kubeTypes []KubeTypes, w io.Writer) (int, error) {
 | 
						|
	missingDocs := 0
 | 
						|
	buffer := newBuffer()
 | 
						|
 | 
						|
	for _, kubeType := range kubeTypes {
 | 
						|
		structName := kubeType[0].Name
 | 
						|
		if kubeType[0].Doc == "" {
 | 
						|
			format := "Missing documentation for the struct itself: %s\n"
 | 
						|
			s := fmt.Sprintf(format, structName)
 | 
						|
			buffer.addLine(s, 0)
 | 
						|
			missingDocs++
 | 
						|
		}
 | 
						|
		kubeType = kubeType[1:] // Skip struct definition
 | 
						|
 | 
						|
		for _, pair := range kubeType { // Iterate only the fields
 | 
						|
			if pair.Doc == "" {
 | 
						|
				format := "In struct: %s, field documentation is missing: %s\n"
 | 
						|
				s := fmt.Sprintf(format, structName, pair.Name)
 | 
						|
				buffer.addLine(s, 0)
 | 
						|
				missingDocs++
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if err := buffer.flushLines(w); err != nil {
 | 
						|
		return -1, err
 | 
						|
	}
 | 
						|
	return missingDocs, nil
 | 
						|
}
 |