1717package main
1818
1919import (
20+ "encoding/json"
21+ "errors"
2022 "go/ast"
2123 "go/parser"
2224 "go/token"
@@ -31,56 +33,55 @@ import (
3133
3234type EnvField struct {
3335 Env string
36+ EnvType string
3437 EnvValue string
3538 EnvDescription string
39+ Example string
40+ Deprecated string
41+ }
42+
43+ type CategoryField struct {
44+ Category string
45+ Fields []EnvField
3646}
3747
3848const (
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"
4360)
4461
4562const 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}}
5069{{end}}`
5170
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
7274}
7375
7476func WalkThroughProject () {
75- var allFields [] EnvField
77+ categoryFieldsMap := make ( map [ string ][] EnvField )
7678 uniqueKeys := make (map [string ]bool )
77-
7879 err := filepath .Walk ("." , func (path string , info os.FileInfo , err error ) error {
7980 if err != nil {
8081 return err
8182 }
8283 if ! info .IsDir () && strings .HasSuffix (path , ".go" ) {
83- err = processGoFile (path , & allFields , & uniqueKeys )
84+ err = processGoFile (path , categoryFieldsMap , uniqueKeys )
8485 if err != nil {
8586 log .Println ("error in processing go file" , err )
8687 return err
@@ -91,49 +92,41 @@ func WalkThroughProject() {
9192 if err != nil {
9293 return
9394 }
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 )
11096}
11197
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 {
11399 fset := token .NewFileSet ()
114100 node , err := parser .ParseFile (fset , filePath , nil , parser .ParseComments )
115101 if err != nil {
116102 log .Println ("error parsing file:" , err )
117103 return err
118104 }
119-
120105 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+ }
130129 }
131- * allFields = append (* allFields , EnvField {
132- Env : envKey ,
133- EnvValue : envValue ,
134- EnvDescription : envDescription ,
135- })
136- (* uniqueKeys )[envKey ] = true
137130 }
138131 }
139132 }
@@ -143,7 +136,91 @@ func processGoFile(filePath string, allFields *[]EnvField, uniqueKeys *map[strin
143136 return nil
144137}
145138
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+ }
149226}
0 commit comments