textmachine/backend/cmd/tmctl/dotenv.go

58 lines
1.8 KiB
Go

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)
}
}
}