/
main.go
162 lines (132 loc) · 4.06 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
/*
Copyright 2021 The Skaffold 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 main
import (
"bytes"
"encoding/json"
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/russross/blackfriday/v2"
)
var camelSepRegex = regexp.MustCompile(`([a-z0-9])([A-Z])`)
type Doc struct {
*Definition
Definitions map[string]*Definition `json:"definitions,omitempty"`
}
type Definition struct {
Items *Definition `json:"items,omitempty"`
Properties map[string]*Definition `json:"properties,omitempty"`
Description string `json:"description,omitempty"`
HTMLDescription string `json:"x-intellij-html-description,omitempty"`
}
func main() {
if len(os.Args) < 4 {
panic(fmt.Errorf("not enough arguments"))
}
if err := generateJSON(".", os.Args[2], os.Args[3], false); err != nil {
panic(err)
}
}
func generateJSON(root, input, output string, dryRun bool) error {
buf, err := generate(filepath.Join(root, input))
if err != nil {
return fmt.Errorf("unable to generate json with comments for %s %v", input, err)
}
if !dryRun {
if err := os.WriteFile(output, buf, os.ModePerm); err != nil {
return fmt.Errorf("unable to write json %q: %w", output, err)
}
}
return nil
}
func newDefinition(name string, t ast.Expr, comment string) *Definition {
def := &Definition{}
tt, ok := t.(*ast.StructType)
if ok {
for _, field := range tt.Fields.List {
name := string(camelSepRegex.ReplaceAll([]byte(field.Names[0].Name), []byte("$1 $2")))
if def.Properties == nil {
def.Properties = make(map[string]*Definition)
}
def.Properties[name] = newDefinition(name, field.Type, field.Doc.Text())
}
}
ogName := strings.ReplaceAll(name, " ", "")
if name != "" {
if comment == "" {
panic(fmt.Sprintf("field %q needs comment (all public fields require comments)", name))
}
if !strings.HasPrefix(comment, ogName+" ") {
panic(fmt.Sprintf("comment %q should start with field name on field %s", comment, name))
}
}
description := strings.TrimSpace(strings.ReplaceAll(comment, "\n", " "))
// Remove type prefix
description = regexp.MustCompile("^"+ogName+" (\\*.*\\* )?((is (the )?)|(are (the )?)|(lists ))?").ReplaceAllString(description, "$1")
if name != "" {
if description == "" {
panic(fmt.Sprintf("no description on field %s", name))
}
if !strings.HasSuffix(description, ".") {
panic(fmt.Sprintf("description should end with a dot on field %s", name))
}
}
def.Description = description
// Convert to HTML
html := string(blackfriday.Run([]byte(description), blackfriday.WithNoExtensions()))
def.HTMLDescription = strings.TrimSpace(html)
return def
}
func generate(inputPath string) ([]byte, error) {
fset := token.NewFileSet()
node, err := parser.ParseFile(fset, inputPath, nil, parser.ParseComments)
if err != nil {
return nil, err
}
definitions := make(map[string]*Definition)
for _, i := range node.Decls {
declaration, ok := i.(*ast.GenDecl)
if !ok {
continue
}
for _, spec := range declaration.Specs {
typeSpec, ok := spec.(*ast.TypeSpec)
if !ok {
continue
}
name := typeSpec.Name.Name
definitions[name] = newDefinition(name, typeSpec.Type, declaration.Doc.Text())
}
}
doc := Doc{
Definitions: definitions,
}
return toJSON(doc)
}
// Make sure HTML description are not encoded
func toJSON(v interface{}) ([]byte, error) {
var buf bytes.Buffer
encoder := json.NewEncoder(&buf)
encoder.SetEscapeHTML(false)
encoder.SetIndent("", " ")
if err := encoder.Encode(v); err != nil {
return nil, err
}
return buf.Bytes(), nil
}