package ezconf import ( "os" "github.com/pkg/errors" ) // EnvVar represents a single environment variable with its metadata type EnvVar struct { Name string // The environment variable name (e.g., "LOG_LEVEL") Description string // Description of what this variable does Required bool // Whether this variable is required Default string // Default value if not set CurrentValue string // Current value from environment (empty if not set) Group string // Group name for organizing variables (e.g., "Database", "Logging") } // ConfigLoader manages configuration loading from multiple sources type ConfigLoader struct { configFuncs map[string]ConfigFunc // Map of config names to ConfigFromEnv functions packagePaths []string // Paths to packages to parse for ENV comments groupNames map[string]string // Map of package paths to group names extraEnvVars []EnvVar // Additional environment variables to track envVars []EnvVar // All extracted environment variables configs map[string]interface{} // Loaded configurations } // ConfigFunc is a function that loads configuration from environment variables type ConfigFunc func() (interface{}, error) // New creates a new ConfigLoader func New() *ConfigLoader { return &ConfigLoader{ configFuncs: make(map[string]ConfigFunc), packagePaths: make([]string, 0), groupNames: make(map[string]string), extraEnvVars: make([]EnvVar, 0), envVars: make([]EnvVar, 0), configs: make(map[string]interface{}), } } // AddConfigFunc adds a ConfigFromEnv function to be called during loading. // The name parameter is used as a key to retrieve the loaded config later. func (cl *ConfigLoader) AddConfigFunc(name string, fn ConfigFunc) error { if fn == nil { return errors.New("config function cannot be nil") } if name == "" { return errors.New("config name cannot be empty") } cl.configFuncs[name] = fn return nil } // AddPackagePath adds a package directory path to parse for ENV comments func (cl *ConfigLoader) AddPackagePath(path string) error { if path == "" { return errors.New("package path cannot be empty") } // Check if path exists if _, err := os.Stat(path); os.IsNotExist(err) { return errors.Errorf("package path does not exist: %s", path) } cl.packagePaths = append(cl.packagePaths, path) return nil } // AddEnvVar adds an additional environment variable to track func (cl *ConfigLoader) AddEnvVar(envVar EnvVar) { cl.extraEnvVars = append(cl.extraEnvVars, envVar) } // Load loads all configurations and extracts environment variables func (cl *ConfigLoader) Load() error { // Parse packages for ENV comments for _, pkgPath := range cl.packagePaths { envVars, err := ParseConfigPackage(pkgPath) if err != nil { return errors.Wrapf(err, "failed to parse package: %s", pkgPath) } // Set group name for these variables from stored mapping groupName := cl.groupNames[pkgPath] if groupName == "" { groupName = "Other" } for i := range envVars { envVars[i].Group = groupName } cl.envVars = append(cl.envVars, envVars...) } // Add extra env vars cl.envVars = append(cl.envVars, cl.extraEnvVars...) // Populate current values from environment for i := range cl.envVars { cl.envVars[i].CurrentValue = os.Getenv(cl.envVars[i].Name) } // Load configurations for name, fn := range cl.configFuncs { cfg, err := fn() if err != nil { return errors.Wrapf(err, "failed to load config: %s", name) } cl.configs[name] = cfg } return nil } // GetConfig returns a loaded configuration by name func (cl *ConfigLoader) GetConfig(name string) (interface{}, bool) { cfg, ok := cl.configs[name] return cfg, ok } // GetAllConfigs returns all loaded configurations func (cl *ConfigLoader) GetAllConfigs() map[string]interface{} { return cl.configs } // GetEnvVars returns all extracted environment variables func (cl *ConfigLoader) GetEnvVars() []EnvVar { return cl.envVars }