add core lib

This commit is contained in:
2026-03-01 03:04:10 +01:00
parent 9a6818ea3c
commit baa764befd
22 changed files with 2353 additions and 0 deletions

52
dotenv/dotenv.go Normal file
View File

@@ -0,0 +1,52 @@
package config
import (
"bufio"
"os"
"strings"
)
// LoadDotEnvCandidates loads KEY=VALUE pairs from the first existing file in paths.
// Existing process env vars are preserved (file values only fill missing keys).
func LoadDotEnvCandidates(paths []string) {
for _, path := range paths {
if loadDotEnvFile(path) {
return
}
}
}
func loadDotEnvFile(path string) bool {
file, err := os.Open(path)
if err != nil {
return false
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
key, value, ok := strings.Cut(line, "=")
if !ok {
continue
}
key = strings.TrimSpace(key)
value = strings.TrimSpace(value)
value = strings.Trim(value, `"'`)
if key == "" {
continue
}
if _, exists := os.LookupEnv(key); exists {
continue
}
_ = os.Setenv(key, value)
}
return true
}

76
dotenv/dotenv_test.go Normal file
View File

@@ -0,0 +1,76 @@
package config
import (
"os"
"path/filepath"
"testing"
)
func TestLoadDotEnvFileParsesAndPreservesExistingValues(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, ".env")
content := "" +
"# comment\n" +
"KEY1 = value1\n" +
"KEY2='quoted value'\n" +
"KEY3=\"double quoted\"\n" +
"INVALID_LINE\n" +
"=missing_key\n"
if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
t.Fatalf("write env file: %v", err)
}
t.Setenv("KEY1", "existing")
_ = os.Unsetenv("KEY2")
_ = os.Unsetenv("KEY3")
if ok := loadDotEnvFile(path); !ok {
t.Fatal("expected loadDotEnvFile to return true")
}
if got := os.Getenv("KEY1"); got != "existing" {
t.Fatalf("expected KEY1 to preserve existing value, got %q", got)
}
if got := os.Getenv("KEY2"); got != "quoted value" {
t.Fatalf("expected KEY2 parsed value, got %q", got)
}
if got := os.Getenv("KEY3"); got != "double quoted" {
t.Fatalf("expected KEY3 parsed value, got %q", got)
}
}
func TestLoadDotEnvCandidatesStopsAtFirstExistingFile(t *testing.T) {
dir := t.TempDir()
first := filepath.Join(dir, "first.env")
second := filepath.Join(dir, "second.env")
if err := os.WriteFile(first, []byte("KEY_A=first\nKEY_B=from_first\n"), 0o600); err != nil {
t.Fatalf("write first env file: %v", err)
}
if err := os.WriteFile(second, []byte("KEY_A=second\nKEY_C=from_second\n"), 0o600); err != nil {
t.Fatalf("write second env file: %v", err)
}
_ = os.Unsetenv("KEY_A")
_ = os.Unsetenv("KEY_B")
_ = os.Unsetenv("KEY_C")
LoadDotEnvCandidates([]string{filepath.Join(dir, "missing.env"), first, second})
if got := os.Getenv("KEY_A"); got != "first" {
t.Fatalf("expected KEY_A from first file, got %q", got)
}
if got := os.Getenv("KEY_B"); got != "from_first" {
t.Fatalf("expected KEY_B from first file, got %q", got)
}
if got := os.Getenv("KEY_C"); got != "" {
t.Fatalf("expected KEY_C to remain unset, got %q", got)
}
}
func TestLoadDotEnvFileReturnsFalseWhenMissing(t *testing.T) {
if ok := loadDotEnvFile(filepath.Join(t.TempDir(), "does-not-exist.env")); ok {
t.Fatal("expected loadDotEnvFile to return false for missing file")
}
}