summaryrefslogtreecommitdiff
path: root/internal/fsutil/path.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/fsutil/path.go')
-rw-r--r--internal/fsutil/path.go99
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:]
+}