package config import ( "bufio" "fmt" "os" "strings" ) // LoadDotEnv reads a dotenv file and sets each variable into the process // environment unless it is already set (real environment wins). Supported // syntax: KEY=VALUE, blank lines, full-line and unquoted inline comments, // optional `export ` prefix, single- or double-quoted values. func LoadDotEnv(path string) error { f, err := os.Open(path) if err != nil { if os.IsNotExist(err) { return nil } return fmt.Errorf("open %s: %w", path, err) } defer f.Close() sc := bufio.NewScanner(f) for n := 1; sc.Scan(); n++ { line := strings.TrimSpace(sc.Text()) if line == "" || strings.HasPrefix(line, "#") { continue } line = strings.TrimPrefix(line, "export ") key, val, ok := strings.Cut(line, "=") if !ok || strings.TrimSpace(key) == "" { return fmt.Errorf("%s:%d: not a KEY=VALUE line", path, n) } key = strings.TrimSpace(key) val = strings.TrimSpace(val) switch { case len(val) >= 2 && (val[0] == '"' || val[0] == '\'') && val[len(val)-1] == val[0]: val = val[1 : len(val)-1] default: // unquoted: strip trailing inline comment if i := strings.Index(val, " #"); i >= 0 { val = strings.TrimSpace(val[:i]) } } if _, exists := os.LookupEnv(key); !exists { os.Setenv(key, val) } } return sc.Err() }