package app import ( "bufio" "errors" "fmt" "os" "strings" ) // LoadDotEnv loads simple KEY=VALUE entries without replacing variables that // are already present in the process environment. A missing file is allowed. func LoadDotEnv(path string) error { file, err := os.Open(path) if errors.Is(err, os.ErrNotExist) { return nil } if err != nil { return fmt.Errorf("open dotenv file: %w", err) } defer file.Close() scanner := bufio.NewScanner(file) for lineNumber := 1; scanner.Scan(); lineNumber++ { line := strings.TrimSpace(scanner.Text()) if line == "" || strings.HasPrefix(line, "#") { continue } line = strings.TrimSpace(strings.TrimPrefix(line, "export ")) key, value, found := strings.Cut(line, "=") key = strings.TrimSpace(key) if !found || !validEnvironmentKey(key) { return fmt.Errorf("%s:%d: invalid environment assignment", path, lineNumber) } value = strings.TrimSpace(value) if len(value) >= 2 && ((value[0] == '\'' && value[len(value)-1] == '\'') || (value[0] == '"' && value[len(value)-1] == '"')) { value = value[1 : len(value)-1] } if _, exists := os.LookupEnv(key); !exists { if err := os.Setenv(key, value); err != nil { return fmt.Errorf("%s:%d: set environment: %w", path, lineNumber, err) } } } if err := scanner.Err(); err != nil { return fmt.Errorf("read dotenv file: %w", err) } return nil } func validEnvironmentKey(key string) bool { if key == "" || !isEnvironmentKeyStart(key[0]) { return false } for i := 1; i < len(key); i++ { if !isEnvironmentKeyStart(key[i]) && (key[i] < '0' || key[i] > '9') { return false } } return true } func isEnvironmentKeyStart(char byte) bool { return char == '_' || char >= 'A' && char <= 'Z' || char >= 'a' && char <= 'z' }