mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-02 07:25:26 +08:00
138 lines
2.8 KiB
Go
138 lines
2.8 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// LoadDotEnv loads environment variables from .env in the current working
|
|
// directory without overriding variables that are already set.
|
|
func LoadDotEnv() error {
|
|
return loadDotEnvFromPath(filepath.Join(BaseDir(), ".env"))
|
|
}
|
|
|
|
func loadDotEnvFromPath(path string) error {
|
|
content, err := os.ReadFile(path)
|
|
if err != nil {
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
lines := strings.Split(strings.ReplaceAll(string(content), "\r\n", "\n"), "\n")
|
|
for i, rawLine := range lines {
|
|
line := strings.TrimSpace(rawLine)
|
|
if i == 0 {
|
|
line = strings.TrimPrefix(line, "\ufeff")
|
|
}
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
if strings.HasPrefix(line, "export ") {
|
|
line = strings.TrimSpace(strings.TrimPrefix(line, "export "))
|
|
}
|
|
|
|
key, value, ok := strings.Cut(line, "=")
|
|
if !ok {
|
|
return fmt.Errorf("%s:%d invalid env assignment", path, i+1)
|
|
}
|
|
key = strings.TrimSpace(key)
|
|
if key == "" {
|
|
return fmt.Errorf("%s:%d empty env key", path, i+1)
|
|
}
|
|
if _, exists := os.LookupEnv(key); exists {
|
|
continue
|
|
}
|
|
if err := os.Setenv(key, normalizeDotEnvValue(trimDotEnvValue(strings.TrimSpace(value)))); err != nil {
|
|
return fmt.Errorf("%s:%d set env %q: %w", path, i+1, key, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Preserve quoted values, but drop Compose-style inline comments from unquoted values.
|
|
func trimDotEnvValue(raw string) string {
|
|
if raw == "" {
|
|
return raw
|
|
}
|
|
|
|
switch raw[0] {
|
|
case '"':
|
|
if trimmed, ok := trimQuotedDotEnvValue(raw, '"'); ok {
|
|
return trimmed
|
|
}
|
|
case '\'':
|
|
if trimmed, ok := trimQuotedDotEnvValue(raw, '\''); ok {
|
|
return trimmed
|
|
}
|
|
default:
|
|
if idx := inlineDotEnvCommentStart(raw); idx >= 0 {
|
|
return strings.TrimSpace(raw[:idx])
|
|
}
|
|
}
|
|
|
|
return raw
|
|
}
|
|
|
|
func trimQuotedDotEnvValue(raw string, quote byte) (string, bool) {
|
|
escaped := false
|
|
for i := 1; i < len(raw); i++ {
|
|
ch := raw[i]
|
|
if quote == '"' && escaped {
|
|
escaped = false
|
|
continue
|
|
}
|
|
if quote == '"' && ch == '\\' {
|
|
escaped = true
|
|
continue
|
|
}
|
|
if ch == quote {
|
|
return strings.TrimSpace(raw[:i+1]), true
|
|
}
|
|
}
|
|
return raw, false
|
|
}
|
|
|
|
func inlineDotEnvCommentStart(raw string) int {
|
|
for i := 1; i < len(raw); i++ {
|
|
if raw[i] == '#' && isDotEnvCommentSpacer(raw[i-1]) {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func isDotEnvCommentSpacer(b byte) bool {
|
|
return b == ' ' || b == '\t'
|
|
}
|
|
|
|
func normalizeDotEnvValue(raw string) string {
|
|
if len(raw) < 2 {
|
|
return raw
|
|
}
|
|
first := raw[0]
|
|
last := raw[len(raw)-1]
|
|
if (first != '"' || last != '"') && (first != '\'' || last != '\'') {
|
|
return raw
|
|
}
|
|
|
|
raw = raw[1 : len(raw)-1]
|
|
if first == '\'' {
|
|
return raw
|
|
}
|
|
|
|
replacer := strings.NewReplacer(
|
|
`\\`, `\`,
|
|
`\n`, "\n",
|
|
`\r`, "\r",
|
|
`\t`, "\t",
|
|
`\"`, `"`,
|
|
)
|
|
return replacer.Replace(raw)
|
|
}
|