|
17 | 17 | package main
|
18 | 18 |
|
19 | 19 | import (
|
20 |
| - "encoding/json" |
21 |
| - "errors" |
22 |
| - "go/ast" |
23 |
| - "go/parser" |
24 |
| - "go/token" |
25 |
| - "log" |
26 |
| - "os" |
27 |
| - "path/filepath" |
28 |
| - "reflect" |
29 |
| - "sort" |
30 |
| - "strings" |
31 |
| - "text/template" |
| 20 | + "github.com/devtron-labs/common-lib/fetchAllEnv" |
32 | 21 | )
|
33 | 22 |
|
34 |
| -type EnvField struct { |
35 |
| - Env string |
36 |
| - EnvType string |
37 |
| - EnvValue string |
38 |
| - EnvDescription string |
39 |
| - Example string |
40 |
| - Deprecated string |
41 |
| -} |
42 |
| - |
43 |
| -type CategoryField struct { |
44 |
| - Category string |
45 |
| - Fields []EnvField |
46 |
| -} |
47 |
| - |
48 |
| -const ( |
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" |
60 |
| -) |
61 |
| - |
62 |
| -const MarkdownTemplate = ` |
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}} |
69 |
| -{{end}}` |
70 |
| - |
71 | 23 | func main() {
|
72 |
| - WalkThroughProject() |
73 |
| - return |
74 |
| -} |
75 |
| - |
76 |
| -func WalkThroughProject() { |
77 |
| - categoryFieldsMap := make(map[string][]EnvField) |
78 |
| - uniqueKeys := make(map[string]bool) |
79 |
| - err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error { |
80 |
| - if err != nil { |
81 |
| - return err |
82 |
| - } |
83 |
| - if !info.IsDir() && strings.HasSuffix(path, ".go") { |
84 |
| - err = processGoFile(path, categoryFieldsMap, uniqueKeys) |
85 |
| - if err != nil { |
86 |
| - log.Println("error in processing go file", err) |
87 |
| - return err |
88 |
| - } |
89 |
| - } |
90 |
| - return nil |
91 |
| - }) |
92 |
| - if err != nil { |
93 |
| - return |
94 |
| - } |
95 |
| - writeToFile(categoryFieldsMap) |
96 |
| -} |
97 |
| - |
98 |
| -func processGoFile(filePath string, categoryFieldsMap map[string][]EnvField, uniqueKeys map[string]bool) error { |
99 |
| - fset := token.NewFileSet() |
100 |
| - node, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments) |
101 |
| - if err != nil { |
102 |
| - log.Println("error parsing file:", err) |
103 |
| - return err |
104 |
| - } |
105 |
| - ast.Inspect(node, func(n ast.Node) bool { |
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 |
| - } |
129 |
| - } |
130 |
| - } |
131 |
| - } |
132 |
| - } |
133 |
| - } |
134 |
| - return true |
135 |
| - }) |
136 |
| - return nil |
137 |
| -} |
138 |
| - |
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 |
| - } |
| 24 | + fetchAllEnv.FetchEnvAndWriteToFile() |
226 | 25 | }
|
0 commit comments