package main import ( "bufio" "io" "os" "strings" ) // dotenv.go: reads backend/.env (gitignored) into the environment. The parser is split // out as a pure function (package №4): the «matched quotes» and «do not override what // is already set» rules are review findings that used to live only in comments; now they // are pinned by unit tests (a regression that «simplified to strings.Trim» would corrupt // API key values and surface as inexplicable 401s in the middle of an acceptance run). // dotenvPair is one KEY=VALUE line of a .env file. type dotenvPair struct{ K, V string } // parseDotEnv reads KEY=VALUE lines: skips empty ones/comments/those without «=», // trims key and value, strips only MATCHED enclosing quotes — a Trim over a character // set would bite off a legitimate quote at the end of the value (review finding). func parseDotEnv(r io.Reader) []dotenvPair { var out []dotenvPair sc := bufio.NewScanner(r) for sc.Scan() { line := strings.TrimSpace(sc.Text()) if line == "" || strings.HasPrefix(line, "#") { continue } k, v, ok := strings.Cut(line, "=") if !ok { continue } k, v = strings.TrimSpace(k), strings.TrimSpace(v) if n := len(v); n >= 2 && (v[0] == '"' || v[0] == '\'') && v[n-1] == v[0] { v = v[1 : n-1] } out = append(out, dotenvPair{K: k, V: v}) } return out } // loadDotEnv reads KEY=VALUE lines into the environment without overriding // already-set variables. Keys come from backend/.env (gitignored); secrets never // end up in configs/the repo. Note: a variable set to an EMPTY string is treated // as unset and will be overridden (current behavior, frozen). func loadDotEnv(path string) { f, err := os.Open(path) if err != nil { return } defer f.Close() for _, p := range parseDotEnv(f) { if os.Getenv(p.K) == "" { os.Setenv(p.K, p.V) } } }