package infra import ( "bufio" "fmt" "os" "strings" ) // DotenvLoad parses a .env file and sets environment variables via os.Setenv. // It does NOT overwrite variables that are already set in the environment. // // Parsing rules: // - Lines starting with # (after trimming) are ignored as comments. // - Blank lines are ignored. // - Lines must follow the KEY=VALUE format. // - Values may be optionally quoted with single or double quotes; quotes are stripped. // - Inline comments (# after value) are NOT stripped — the raw value after = is used. // // Returns an error if the file cannot be opened or if a line has no '=' separator. func DotenvLoad(path string) error { f, err := os.Open(path) if err != nil { return fmt.Errorf("dotenv_load: open %q: %w", path, err) } defer f.Close() scanner := bufio.NewScanner(f) lineNum := 0 for scanner.Scan() { lineNum++ line := strings.TrimSpace(scanner.Text()) // skip blank lines and comments if line == "" || strings.HasPrefix(line, "#") { continue } idx := strings.IndexByte(line, '=') if idx < 0 { return fmt.Errorf("dotenv_load: %q line %d: missing '=' in %q", path, lineNum, line) } key := strings.TrimSpace(line[:idx]) val := line[idx+1:] // strip surrounding quotes from value val = stripQuotes(val) // only set if not already present if os.Getenv(key) == "" { if err := os.Setenv(key, val); err != nil { return fmt.Errorf("dotenv_load: setenv %q: %w", key, err) } } } if err := scanner.Err(); err != nil { return fmt.Errorf("dotenv_load: scan %q: %w", path, err) } return nil } // stripQuotes removes surrounding single or double quotes from a string. func stripQuotes(s string) string { if len(s) >= 2 { if (s[0] == '"' && s[len(s)-1] == '"') || (s[0] == '\'' && s[len(s)-1] == '\'') { return s[1 : len(s)-1] } } return s }