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