17
17
package main
18
18
19
19
import (
20
+ "errors"
20
21
"go/ast"
21
22
"go/parser"
22
23
"go/token"
@@ -31,13 +32,23 @@ import (
31
32
32
33
type EnvField struct {
33
34
Env string
35
+ EnvType string
34
36
EnvValue string
35
37
EnvDescription string
36
38
EnvPossibleValues string
37
39
EnvDeprecated string
38
40
}
39
41
42
+ type CategoryField struct {
43
+ Category string
44
+ Fields []EnvField
45
+ }
46
+
40
47
const (
48
+ categoryCommentStructPrefix = "CATEGORY="
49
+ defaultCategory = "DEVTRON"
50
+ deprecatedDefaultValue = "false"
51
+
41
52
envFieldTypeTag = "env"
42
53
envDefaultFieldTypeTag = "envDefault"
43
54
envDescriptionFieldTypeTag = "envDescription"
@@ -47,44 +58,28 @@ const (
47
58
)
48
59
49
60
const MarkdownTemplate = `
50
- ## Devtron Environment Variables
61
+ {{range . }}
62
+ ## {{ .Category }} Related Environment Variables
51
63
| Key | Value | Description | Possible values | Deprecated |
52
64
|-------|--------------|-------------------|-----------------------|------------------|
53
- {{range .}} | {{ .Env }} | {{ .EnvValue }} | {{ .EnvDescription }} | {{ .EnvPossibleValues }} | {{ .EnvDeprecated }} |
65
+ {{range .Fields }} | {{ .Env }} | {{ .EnvValue }} | {{ .EnvDescription }} | {{ .EnvPossibleValues }} | {{ .EnvDeprecated }} |
66
+ {{end}}
54
67
{{end}}`
55
68
56
- func writeToFile (allFields []EnvField ) {
57
- sort .Slice (allFields , func (i , j int ) bool {
58
- return allFields [i ].Env < allFields [j ].Env
59
- })
60
-
61
- file , err := os .Create (MARKDOWN_FILENAME )
62
- if err != nil {
63
- panic (err )
64
- }
65
- defer file .Close ()
66
-
67
- tmpl , err := template .New ("markdown" ).Parse (MarkdownTemplate )
68
- if err != nil {
69
- panic (err )
70
- }
71
-
72
- err = tmpl .Execute (file , allFields )
73
- if err != nil {
74
- panic (err )
75
- }
69
+ func main () {
70
+ WalkThroughProject ()
71
+ return
76
72
}
77
73
78
74
func WalkThroughProject () {
79
- var allFields [] EnvField
75
+ categoryFieldsMap := make ( map [ string ][] EnvField )
80
76
uniqueKeys := make (map [string ]bool )
81
-
82
77
err := filepath .Walk ("." , func (path string , info os.FileInfo , err error ) error {
83
78
if err != nil {
84
79
return err
85
80
}
86
81
if ! info .IsDir () && strings .HasSuffix (path , ".go" ) {
87
- err = processGoFile (path , & allFields , & uniqueKeys )
82
+ err = processGoFile (path , categoryFieldsMap , uniqueKeys )
88
83
if err != nil {
89
84
log .Println ("error in processing go file" , err )
90
85
return err
@@ -95,14 +90,53 @@ func WalkThroughProject() {
95
90
if err != nil {
96
91
return
97
92
}
98
- writeToFile (allFields )
93
+ writeToFile (categoryFieldsMap )
99
94
}
100
95
101
- func convertTagToStructTag (tag string ) reflect.StructTag {
102
- return reflect .StructTag (strings .Split (tag , "`" )[1 ])
96
+ func processGoFile (filePath string , categoryFieldsMap map [string ][]EnvField , uniqueKeys map [string ]bool ) error {
97
+ fset := token .NewFileSet ()
98
+ node , err := parser .ParseFile (fset , filePath , nil , parser .ParseComments )
99
+ if err != nil {
100
+ log .Println ("error parsing file:" , err )
101
+ return err
102
+ }
103
+ ast .Inspect (node , func (n ast.Node ) bool {
104
+ if genDecl , ok := n .(* ast.GenDecl ); ok {
105
+ // checking if type declaration, one of [func, map, struct, array, channel, interface]
106
+ if genDecl .Tok == token .TYPE {
107
+ for _ , spec := range genDecl .Specs {
108
+ if typeSpec , ok := spec .(* ast.TypeSpec ); ok {
109
+ // only checking struct type declarations
110
+ if structType , ok2 := typeSpec .Type .(* ast.StructType ); ok2 {
111
+ allFields := make ([]EnvField , 0 , len (structType .Fields .List ))
112
+ for _ , field := range structType .Fields .List {
113
+ if field .Tag != nil {
114
+ envField := getEnvKeyAndValue (field )
115
+ envKey := envField .Env
116
+ if len (envKey ) == 0 || uniqueKeys [envKey ] {
117
+ continue
118
+ }
119
+ allFields = append (allFields , envField )
120
+ uniqueKeys [envKey ] = true
121
+ }
122
+ }
123
+ if len (allFields ) > 0 {
124
+ category := getCategoryForAStruct (genDecl )
125
+ categoryFieldsMap [category ] = append (categoryFieldsMap [category ], allFields ... )
126
+ }
127
+ }
128
+ }
129
+ }
130
+ }
131
+ }
132
+ return true
133
+ })
134
+ return nil
103
135
}
104
136
105
- func getEnvKeyAndValue (tag reflect.StructTag ) EnvField {
137
+ func getEnvKeyAndValue (field * ast.Field ) EnvField {
138
+ tag := reflect .StructTag (strings .Trim (field .Tag .Value , "`" )) // remove surrounding backticks
139
+
106
140
envKey := addReadmeTableDelimiterEscapeChar (tag .Get (envFieldTypeTag ))
107
141
envValue := addReadmeTableDelimiterEscapeChar (tag .Get (envDefaultFieldTypeTag ))
108
142
envDescription := addReadmeTableDelimiterEscapeChar (tag .Get (envDescriptionFieldTypeTag ))
@@ -112,51 +146,70 @@ func getEnvKeyAndValue(tag reflect.StructTag) EnvField {
112
146
if value , ok := os .LookupEnv (envKey ); ok {
113
147
envValue = value
114
148
}
115
- return EnvField {
149
+ env := EnvField {
116
150
Env : envKey ,
117
151
EnvValue : envValue ,
118
152
EnvDescription : envDescription ,
119
153
EnvPossibleValues : envPossibleValues ,
120
154
EnvDeprecated : envDeprecated ,
121
155
}
156
+ if indent , ok := field .Type .(* ast.Ident ); ok && indent != nil {
157
+ env .EnvType = indent .Name
158
+ }
159
+ if len (envDeprecated ) == 0 {
160
+ env .EnvDeprecated = deprecatedDefaultValue
161
+ }
162
+ return env
163
+ }
164
+
165
+ func getCategoryForAStruct (genDecl * ast.GenDecl ) string {
166
+ category := defaultCategory
167
+ if genDecl .Doc != nil {
168
+ commentTexts := strings .Split (genDecl .Doc .Text (), "\n " )
169
+ for _ , comment := range commentTexts {
170
+ commentText := strings .TrimPrefix (strings .ReplaceAll (comment , " " , "" ), "//" ) // this can happen if comment group is in /* */
171
+ if strings .HasPrefix (commentText , categoryCommentStructPrefix ) {
172
+ categories := strings .Split (strings .TrimPrefix (commentText , categoryCommentStructPrefix ), "," )
173
+ if len (categories ) > 0 && len (categories [0 ]) > 0 { //only supporting one category as of now
174
+ category = categories [0 ] //overriding category
175
+ break
176
+ }
177
+ }
178
+ }
179
+ }
180
+ return category
122
181
}
123
182
124
183
func addReadmeTableDelimiterEscapeChar (s string ) string {
125
184
return strings .ReplaceAll (s , "|" , `\|` )
126
185
}
127
186
128
- func processGoFile (filePath string , allFields * []EnvField , uniqueKeys * map [string ]bool ) error {
129
- fset := token .NewFileSet ()
130
- node , err := parser .ParseFile (fset , filePath , nil , parser .ParseComments )
131
- if err != nil {
132
- log .Println ("error parsing file:" , err )
133
- return err
187
+ func writeToFile (categoryFieldsMap map [string ][]EnvField ) {
188
+ cfs := make ([]CategoryField , 0 , len (categoryFieldsMap ))
189
+ for category , allFields := range categoryFieldsMap {
190
+ sort .Slice (allFields , func (i , j int ) bool {
191
+ return allFields [i ].Env < allFields [j ].Env
192
+ })
193
+
194
+ cfs = append (cfs , CategoryField {
195
+ Category : category ,
196
+ Fields : allFields ,
197
+ })
134
198
}
135
-
136
- ast .Inspect (node , func (n ast.Node ) bool {
137
- switch x := n .(type ) {
138
- case * ast.TypeSpec :
139
- if structType , ok := x .Type .(* ast.StructType ); ok {
140
- for _ , field := range structType .Fields .List {
141
- if field .Tag != nil {
142
- strippedTags := convertTagToStructTag (field .Tag .Value )
143
- envField := getEnvKeyAndValue (strippedTags )
144
- envKey := envField .Env
145
- if len (envKey ) == 0 || (* uniqueKeys )[envKey ] {
146
- continue
147
- }
148
- * allFields = append (* allFields , envField )
149
- (* uniqueKeys )[envKey ] = true
150
- }
151
- }
152
- }
153
- }
154
- return true
199
+ sort .Slice (cfs , func (i , j int ) bool {
200
+ return cfs [i ].Category < cfs [j ].Category
155
201
})
156
- return nil
157
- }
158
-
159
- func main () {
160
- WalkThroughProject ()
161
- return
202
+ file , err := os .Create (MARKDOWN_FILENAME )
203
+ if err != nil && ! errors .Is (err , os .ErrExist ) {
204
+ panic (err )
205
+ }
206
+ defer file .Close ()
207
+ tmpl , err := template .New ("markdown" ).Parse (MarkdownTemplate )
208
+ if err != nil {
209
+ panic (err )
210
+ }
211
+ err = tmpl .Execute (file , cfs )
212
+ if err != nil {
213
+ panic (err )
214
+ }
162
215
}
0 commit comments