diff --git a/lib/os/os.go b/lib/os/os.go index 53999775..291074e7 100644 --- a/lib/os/os.go +++ b/lib/os/os.go @@ -13,6 +13,7 @@ import ( "log" "os" "path/filepath" + "strings" "github.com/shuLhan/share/lib/ascii" ) @@ -200,6 +201,55 @@ func IsFileExist(parent, relpath string) bool { return true } +// PathFold replace the path "in" with tilde "~" if its prefix match with +// user's home directory from [os.UserHomeDir]. +func PathFold(in string) (out string, err error) { + var ( + logp = `PathFold` + + userHomeDir string + ) + + in = filepath.Clean(in) + + userHomeDir, err = os.UserHomeDir() + if err != nil { + return ``, fmt.Errorf(`%s: %s: %w`, logp, in, err) + } + if strings.HasPrefix(in, userHomeDir) { + out = filepath.Join(`~`, in[len(userHomeDir):]) + } else { + out = in + } + return out, nil +} + +// PathUnfold expand the tilde "~/" prefix into user's home directory using +// [os.UserHomeDir] and environment variables using [os.ExpandEnv] inside +// the string path "in". +func PathUnfold(in string) (out string, err error) { + var ( + logp = `PathUnfold` + + userHomeDir string + ) + + if strings.HasPrefix(in, `~/`) { + userHomeDir, err = os.UserHomeDir() + if err != nil { + return ``, fmt.Errorf(`%s: %s: %w`, logp, in, err) + } + out = filepath.Join(userHomeDir, in[2:]) + } else { + out = in + } + + out = os.ExpandEnv(out) + out = filepath.Clean(out) + + return out, nil +} + // RmdirEmptyAll remove directory in path if it's empty until one of the // parent is not empty. func RmdirEmptyAll(path string) error { diff --git a/lib/os/os_unix_test.go b/lib/os/os_unix_test.go new file mode 100644 index 00000000..124e8943 --- /dev/null +++ b/lib/os/os_unix_test.go @@ -0,0 +1,104 @@ +// Copyright 2023, Shulhan . All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package os + +import ( + "os" + "path/filepath" + "testing" + + "github.com/shuLhan/share/lib/test" +) + +func TestPathFold(t *testing.T) { + type testCase struct { + path string + expPath string + } + + var ( + userHomeDir string + err error + ) + userHomeDir, err = os.UserHomeDir() + if err != nil { + t.Fatal(err) + } + + var listTestCase = []testCase{{ + path: filepath.Join(userHomeDir, `tmp`), + expPath: `~/tmp`, + }, { + // Unclean path. + path: `//` + userHomeDir + `///tmp`, + expPath: `~/tmp`, + }} + + var ( + c testCase + gotPath string + ) + for _, c = range listTestCase { + gotPath, err = PathFold(c.path) + if err != nil { + t.Fatal(err) + } + test.Assert(t, c.path, c.expPath, gotPath) + } +} + +func TestPathUnfold(t *testing.T) { + type testCase struct { + path string + expPath string + } + + var ( + username = os.Getenv(`USER`) + + workDir string + userHomeDir string + err error + ) + + userHomeDir, err = os.UserHomeDir() + if err != nil { + t.Fatal(err) + } + + workDir, err = os.Getwd() + if err != nil { + t.Fatal(err) + } + + var listTestCase = []testCase{{ + path: `~/tmp`, + expPath: filepath.Join(userHomeDir, `tmp`), + }, { + path: `/home/user/~/tmp`, + expPath: `/home/user/~/tmp`, + }, { + path: `$HOME/tmp`, + expPath: filepath.Join(userHomeDir, `tmp`), + }, { + path: `/tmp/$USER/adir`, + expPath: filepath.Join(`/`, `tmp`, username, `adir`), + }, { + path: `~/$PWD/adir`, + expPath: filepath.Join(userHomeDir, workDir, `adir`), + }} + + var ( + c testCase + gotPath string + ) + for _, c = range listTestCase { + gotPath, err = PathUnfold(c.path) + if err != nil { + t.Fatal(err) + } + test.Assert(t, c.path, c.expPath, gotPath) + } +}