147 lines
3.7 KiB
Go
147 lines
3.7 KiB
Go
package ezconf
|
|
|
|
import (
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// ParseConfigFile parses a Go source file and extracts ENV comments from struct fields
|
|
func ParseConfigFile(filename string) ([]EnvVar, error) {
|
|
content, err := os.ReadFile(filename)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to read file")
|
|
}
|
|
|
|
fset := token.NewFileSet()
|
|
file, err := parser.ParseFile(fset, filename, content, parser.ParseComments)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to parse file")
|
|
}
|
|
|
|
envVars := make([]EnvVar, 0)
|
|
|
|
// Walk through the AST
|
|
ast.Inspect(file, func(n ast.Node) bool {
|
|
// Look for struct type declarations
|
|
typeSpec, ok := n.(*ast.TypeSpec)
|
|
if !ok {
|
|
return true
|
|
}
|
|
|
|
structType, ok := typeSpec.Type.(*ast.StructType)
|
|
if !ok {
|
|
return true
|
|
}
|
|
|
|
// Iterate through struct fields
|
|
for _, field := range structType.Fields.List {
|
|
var comment string
|
|
|
|
// Try to get from doc comment (comment before field)
|
|
if field.Doc != nil && len(field.Doc.List) > 0 {
|
|
comment = field.Doc.List[0].Text
|
|
comment = strings.TrimPrefix(comment, "//")
|
|
comment = strings.TrimSpace(comment)
|
|
}
|
|
|
|
// Try to get from inline comment (comment after field)
|
|
if comment == "" && field.Comment != nil && len(field.Comment.List) > 0 {
|
|
comment = field.Comment.List[0].Text
|
|
comment = strings.TrimPrefix(comment, "//")
|
|
comment = strings.TrimSpace(comment)
|
|
}
|
|
|
|
// Parse ENV comment
|
|
if strings.HasPrefix(comment, "ENV ") {
|
|
envVar, err := parseEnvComment(comment)
|
|
if err == nil {
|
|
envVars = append(envVars, *envVar)
|
|
}
|
|
}
|
|
}
|
|
|
|
return true
|
|
})
|
|
|
|
return envVars, nil
|
|
}
|
|
|
|
// ParseConfigPackage parses all Go files in a package directory and extracts ENV comments
|
|
func ParseConfigPackage(packagePath string) ([]EnvVar, error) {
|
|
// Find all .go files in the package
|
|
files, err := filepath.Glob(filepath.Join(packagePath, "*.go"))
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to glob package files")
|
|
}
|
|
|
|
allEnvVars := make([]EnvVar, 0)
|
|
|
|
for _, file := range files {
|
|
// Skip test files
|
|
if strings.HasSuffix(file, "_test.go") {
|
|
continue
|
|
}
|
|
|
|
envVars, err := ParseConfigFile(file)
|
|
if err != nil {
|
|
// Log error but continue with other files
|
|
continue
|
|
}
|
|
|
|
allEnvVars = append(allEnvVars, envVars...)
|
|
}
|
|
|
|
return allEnvVars, nil
|
|
}
|
|
|
|
// parseEnvComment parses a field comment to extract environment variable information.
|
|
// Expected format: ENV ENV_NAME: Description (required <condition>) (default: <value>)
|
|
func parseEnvComment(comment string) (*EnvVar, error) {
|
|
// Check if comment starts with ENV
|
|
if !strings.HasPrefix(comment, "ENV ") {
|
|
return nil, errors.New("comment does not start with 'ENV '")
|
|
}
|
|
|
|
// Remove "ENV " prefix
|
|
comment = strings.TrimPrefix(comment, "ENV ")
|
|
|
|
// Extract env var name (everything before the first colon)
|
|
colonIdx := strings.Index(comment, ":")
|
|
if colonIdx == -1 {
|
|
return nil, errors.New("missing colon separator")
|
|
}
|
|
|
|
envVar := &EnvVar{
|
|
Name: strings.TrimSpace(comment[:colonIdx]),
|
|
}
|
|
|
|
// Extract description and optional parts
|
|
remainder := strings.TrimSpace(comment[colonIdx+1:])
|
|
|
|
// Check for (required ...) pattern
|
|
requiredPattern := regexp.MustCompile(`\(required[^)]*\)`)
|
|
if requiredPattern.MatchString(remainder) {
|
|
envVar.Required = true
|
|
remainder = requiredPattern.ReplaceAllString(remainder, "")
|
|
}
|
|
|
|
// Check for (default: ...) pattern
|
|
defaultPattern := regexp.MustCompile(`\(default:\s*([^)]*)\)`)
|
|
if matches := defaultPattern.FindStringSubmatch(remainder); len(matches) > 1 {
|
|
envVar.Default = strings.TrimSpace(matches[1])
|
|
remainder = defaultPattern.ReplaceAllString(remainder, "")
|
|
}
|
|
|
|
// What remains is the description
|
|
envVar.Description = strings.TrimSpace(remainder)
|
|
|
|
return envVar, nil
|
|
}
|