1
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
32 type Pair struct {
33 Name, Doc string
34 }
35
36
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)
60 }
61 }
62
63
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:
71 delPrevChar()
72 buffer.WriteString("\n\n")
73 case strings.HasPrefix(leading, "TODO"):
74 case strings.HasPrefix(leading, "+"):
75 default:
76 if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") {
77 delPrevChar()
78 line = "\n" + line + "\n"
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)
88 postDoc = strings.Replace(postDoc, "\"", "\\\"", -1)
89 postDoc = strings.Replace(postDoc, "\n", "\\n", -1)
90 postDoc = strings.Replace(postDoc, "\t", "\\t", -1)
91
92 return postDoc
93 }
94
95
96
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")
101 if strings.Contains(jsonTag, "inline") {
102 return "-"
103 }
104 }
105
106 jsonTag = strings.Split(jsonTag, ",")[0]
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
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)
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)
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
171
172
173
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
198
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
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
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
233
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:]
247
248 for _, pair := range kubeType {
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 }
263
View as plain text