128 lines
3.3 KiB
Go
128 lines
3.3 KiB
Go
package sugar
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
// EnsureDir joins the given path segments and creates the directory
|
|
// (including all parent directories) if it does not already exist.
|
|
// Returns the resolved path or an error if creation fails.
|
|
func EnsureDir(dirPath ...string) (string, error) {
|
|
outPath := filepath.Join(dirPath...)
|
|
if outPath == "" {
|
|
return "", fmt.Errorf("path must not be empty")
|
|
}
|
|
|
|
if err := os.MkdirAll(outPath, os.FileMode(0775)); err != nil {
|
|
return "", fmt.Errorf("failed to ensure directory %q: %w", outPath, err)
|
|
}
|
|
|
|
return outPath, nil
|
|
}
|
|
|
|
// ExecDir returns the directory of the running binary.
|
|
// Works correctly even with symlinks and -trimpath builds.
|
|
func ExecDir() (string, error) {
|
|
exe, err := os.Executable()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
// EvalSymlinks is critical — os.Executable can return a symlink path
|
|
resolved, err := filepath.EvalSymlinks(exe)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return filepath.Dir(resolved), nil
|
|
}
|
|
|
|
func WriteDataToTmpFile(content, fp string) (int, string, error) {
|
|
dir, err := EnsureDir(os.TempDir(), "mdb")
|
|
if err != nil {
|
|
slog.Error("EnsureDir", "err", err.Error())
|
|
return 0, dir, err
|
|
}
|
|
|
|
fp = filepath.Join(dir, fp)
|
|
|
|
f, err := os.Create(fp)
|
|
if err != nil {
|
|
slog.Error("CreateFile", "e", err)
|
|
return 0, fp, err
|
|
}
|
|
|
|
filesize, err := f.WriteString(content)
|
|
if err != nil {
|
|
slog.Error("WriteString", "e", err)
|
|
return 0, fp, err
|
|
}
|
|
|
|
err = f.Close()
|
|
if err != nil {
|
|
slog.Error("CloseFile", "e", err)
|
|
return 0, fp, err
|
|
}
|
|
|
|
return filesize, fp, nil
|
|
}
|
|
|
|
// ConfigPaths returns candidate directories for config lookup.
|
|
// Priority: XDG_CONFIG_HOME → binary dir → CWD → /etc/appname
|
|
func ConfigPaths(appName string) []string {
|
|
var candidates []string
|
|
|
|
// 1. Explicit env override (highest priority)
|
|
if envPath := os.Getenv("APP_CONFIG_DIR"); envPath != "" {
|
|
candidates = append(candidates, envPath)
|
|
}
|
|
|
|
// 2. Relative to binary (best for production deployments)
|
|
if exe, err := os.Executable(); err == nil {
|
|
if resolved, err := filepath.EvalSymlinks(exe); err == nil {
|
|
candidates = append(candidates, filepath.Dir(resolved))
|
|
}
|
|
}
|
|
|
|
// 3. CWD (convenient for `go run` and development)
|
|
if cwd, err := os.Getwd(); err == nil {
|
|
candidates = append(candidates, cwd)
|
|
}
|
|
|
|
// 4. XDG standard (Linux/Unix)
|
|
if xdg := os.Getenv("XDG_CONFIG_HOME"); xdg != "" {
|
|
candidates = append(candidates, filepath.Join(xdg, appName))
|
|
} else if home, err := os.UserHomeDir(); err == nil {
|
|
candidates = append(candidates, filepath.Join(home, ".config", appName))
|
|
}
|
|
|
|
// 5. System-wide fallback
|
|
candidates = append(candidates, filepath.Join("/etc", appName))
|
|
|
|
return candidates
|
|
}
|
|
|
|
// FindConfig searches all candidate dirs for the given filename.
|
|
func FindConfig(appName, filename string) (string, error) {
|
|
for _, dir := range ConfigPaths(appName) {
|
|
path := filepath.Join(dir, filename)
|
|
if _, err := os.Stat(path); err == nil {
|
|
return path, nil
|
|
}
|
|
}
|
|
return "", fmt.Errorf("config %q not found in any candidate directory", filename)
|
|
}
|
|
|
|
// DebugPath Debug helper — remove before production
|
|
func DebugPath(path string) {
|
|
info, err := os.Stat(path)
|
|
log.Printf("path=%q exists=%v err=%v mode=%v", path, err == nil, err, func() string {
|
|
if info != nil {
|
|
return info.Mode().String()
|
|
}
|
|
return "n/a"
|
|
}())
|
|
}
|