diff options
Diffstat (limited to 'internal/fsutil/path.go')
| -rw-r--r-- | internal/fsutil/path.go | 99 |
1 files changed, 99 insertions, 0 deletions
diff --git a/internal/fsutil/path.go b/internal/fsutil/path.go new file mode 100644 index 0000000..c872a5b --- /dev/null +++ b/internal/fsutil/path.go @@ -0,0 +1,99 @@ +package fsutil + +import ( + "errors" + "fmt" + "io/fs" + "os" + "os/user" + "path/filepath" + "strings" +) + +// ResolveRootPath expands and validates the supplied root path. When the +// caller did not specify a value, defaultValue is used and created on demand. +func ResolveRootPath(input, defaultValue string) (string, error) { + value, isDefault := normalizeRootInput(input, defaultValue) + expanded, err := expandPath(value) + if err != nil { + return "", fmt.Errorf("cannot expand root path %q: %w", value, err) + } + abs, err := filepath.Abs(expanded) + if err != nil { + return "", fmt.Errorf("cannot resolve root path %q: %w", expanded, err) + } + info, err := ensureRootExists(abs, isDefault) + if err != nil { + return "", err + } + if !info.IsDir() && !info.Mode().IsRegular() { + return "", fmt.Errorf("root path %q is not a file or directory", abs) + } + return abs, nil +} + +func normalizeRootInput(input, defaultValue string) (value string, isDefault bool) { + trimmed := strings.TrimSpace(input) + if trimmed == "" { + return defaultValue, true + } + return trimmed, false +} + +func ensureRootExists(path string, allowCreate bool) (fs.FileInfo, error) { + info, err := os.Stat(path) + if err == nil { + return info, nil + } + if !errors.Is(err, fs.ErrNotExist) { + return nil, fmt.Errorf("cannot access root path %q: %w", path, err) + } + if !allowCreate { + return nil, fmt.Errorf("root path does not exist: %s", path) + } + if mkErr := os.MkdirAll(path, 0o755); mkErr != nil { + return nil, fmt.Errorf("cannot create default directory %q: %w", path, mkErr) + } + info, err = os.Stat(path) + if err != nil { + return nil, fmt.Errorf("cannot stat default directory %q: %w", path, err) + } + return info, nil +} + +func expandPath(p string) (string, error) { + if p == "" || p[0] != '~' { + return p, nil + } + if len(p) == 1 { + home, err := os.UserHomeDir() + if err != nil { + return "", err + } + return home, nil + } + if p[1] == '/' { + home, err := os.UserHomeDir() + if err != nil { + return "", err + } + return filepath.Join(home, p[2:]), nil + } + username, rest := splitUserPath(p) + usr, err := user.Lookup(username) + if err != nil { + return "", err + } + if rest == "" { + return usr.HomeDir, nil + } + return filepath.Join(usr.HomeDir, rest), nil +} + +func splitUserPath(p string) (string, string) { + sep := strings.IndexRune(p, '/') + if sep == -1 { + return p[1:], "" + } + return p[1:sep], p[sep:] +} |
