From 359601bcb9f91fbac703fee7d7bb70bd6b47e70a Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Mon, 14 Jun 2021 11:30:23 +0200 Subject: [PATCH] feat: add u.ExpandPath Signed-off-by: Manfred Touron <94029+moul@users.noreply.github.com> --- README.md | 113 +++++++++++++++++++++++++++------------------------ os.go | 31 -------------- os_test.go | 17 -------- path.go | 70 +++++++++++++++++++++++++++++++ path_test.go | 65 +++++++++++++++++++++++++++++ 5 files changed, 194 insertions(+), 102 deletions(-) create mode 100644 path.go create mode 100644 path_test.go diff --git a/README.md b/README.md index 1c85514..1eaa8e8 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,12 @@ func ExecStandaloneOutputs(cmd *exec.Cmd) ([]byte, []byte, error) ExecStandaloneOutputs runs the command and returns its standard output and standard error. -func ExpandUser(path string) (string, error) +func ExpandPath(path string) (string, error) + ExpandPath performs various expansions on a given path. + + - Replaces ~/ with $HOME/. - Returns absolute path. - Expands env vars. + TODO: - Follow symlinks. + func FanIn(chans ...<-chan interface{}) <-chan interface{} FanIn merges multiple input chans events into one. @@ -100,8 +105,8 @@ func MustCaptureStdoutAndStderr() func() string MustCaptureStdoutAndStderr wraps CaptureStdoutAndStderr and panics if initialization fails. -func MustExpandUser(path string) string - MustExpandUser wraps ExpandUser and panics if initialization fails. +func MustExpandPath(path string) string + MustExpandPath wraps ExpandPath and panics if initialization fails. func MustTempFileName(dir, pattern string) string MustTempFileName wraps TempFileName and panics if initialization fails. @@ -212,57 +217,57 @@ $ go get moul.io/u ```txt benchmark iter time/iter --------- ---- --------- -BenchmarkUnzip-12 3795 281158.00 ns/op -BenchmarkUnzipBytes-12 3854 270661.00 ns/op -BenchmarkB64Encode/1-12 14475339 78.53 ns/op -BenchmarkB64Encode/1-parallel-12 95066647 11.42 ns/op -BenchmarkB64Encode/1000-12 450759 5188.00 ns/op -BenchmarkB64Encode/1000-parallel-12 1379708 870.70 ns/op -BenchmarkB64Encode/1000000-12 279 4530342.00 ns/op -BenchmarkB64Encode/1000000-parallel-12 2029 518956.00 ns/op -BenchmarkB64Decode/1000-12 403045 4057.00 ns/op -BenchmarkB64Decode/1000-parallel-12 1795483 690.40 ns/op -BenchmarkB64Decode/10000-12 30897 37342.00 ns/op -BenchmarkB64Decode/10000-parallel-12 193408 7130.00 ns/op -BenchmarkB64Decode/100000-12 5318 344592.00 ns/op -BenchmarkB64Decode/100000-parallel-12 24860 47960.00 ns/op -BenchmarkIsBinary/small-valid-12 134240438 8.63 ns/op -BenchmarkIsBinary/small-valid-parallel-12 819188630 1.35 ns/op -BenchmarkIsBinary/long-valid-12 5294448 225.10 ns/op -BenchmarkIsBinary/long-valid-parallel-12 27616174 36.47 ns/op -BenchmarkIsBinary/small-invalid-12 141702962 8.35 ns/op -BenchmarkIsBinary/small-invalid-parallel-12 746452850 1.40 ns/op -BenchmarkCommandExists/go-12 140930 8908.00 ns/op -BenchmarkCommandExists/go-parallel-12 947446 1299.00 ns/op -BenchmarkCommandExists/asddsa-12 23431 51981.00 ns/op -BenchmarkCommandExists/asddsa-parallel-12 155037 6934.00 ns/op -BenchmarkSafeExec-12 679 2368421.00 ns/op -BenchmarkCombineFuncs-12 6238905 202.40 ns/op -BenchmarkFuture-12 1468946 802.00 ns/op -BenchmarkRandomLetters/1000-12 324505 4102.00 ns/op -BenchmarkRandomLetters/1000-parallel-12 33856 35397.00 ns/op -BenchmarkRandomLetters/10000-12 29956 39849.00 ns/op -BenchmarkRandomLetters/10000-parallel-12 3334 357897.00 ns/op -BenchmarkRandomLetters/100000-12 2995 394264.00 ns/op -BenchmarkRandomLetters/100000-parallel-12 349 3509818.00 ns/op -BenchmarkUniqueStrings/slice1-12 849084 1424.00 ns/op -BenchmarkUniqueStrings/slice1-parallel-12 5389947 220.00 ns/op -BenchmarkUniqueStrings/slice2-12 9249 251324.00 ns/op -BenchmarkUniqueStrings/slice2-parallel-12 27469 43579.00 ns/op -BenchmarkUniqueInts/slice1-12 1474201 789.60 ns/op -BenchmarkUniqueInts/slice1-parallel-12 9390207 126.50 ns/op -BenchmarkUniqueInts/slice2-12 10000 154489.00 ns/op -BenchmarkUniqueInts/slice2-parallel-12 46874 25296.00 ns/op -BenchmarkUniqueInterfaces/slice1-12 932260 1724.00 ns/op -BenchmarkUniqueInterfaces/slice1-parallel-12 4381582 255.00 ns/op -BenchmarkUniqueInterfaces/slice2-12 2624 563016.00 ns/op -BenchmarkUniqueInterfaces/slice2-parallel-12 10660 111471.00 ns/op -BenchmarkShortDuration/Simple-12 13368848 93.61 ns/op -BenchmarkShortDuration/Simple-parallel-12 84225661 13.74 ns/op -BenchmarkShortDuration/Complex-12 2778331 462.80 ns/op -BenchmarkShortDuration/Complex-parallel-12 13124893 81.04 ns/op -BenchmarkBoolPtr/serial-12 1000000000 0.41 ns/op -BenchmarkBoolPtr/parallel-12 1000000000 0.30 ns/op +BenchmarkUnzip-12 3919 262998.00 ns/op +BenchmarkUnzipBytes-12 4461 249418.00 ns/op +BenchmarkB64Encode/1-12 15309999 82.28 ns/op +BenchmarkB64Encode/1-parallel-12 66409862 15.98 ns/op +BenchmarkB64Encode/1000-12 356790 4955.00 ns/op +BenchmarkB64Encode/1000-parallel-12 1349776 839.50 ns/op +BenchmarkB64Encode/1000000-12 296 4421480.00 ns/op +BenchmarkB64Encode/1000000-parallel-12 1935 562698.00 ns/op +BenchmarkB64Decode/1000-12 358122 3997.00 ns/op +BenchmarkB64Decode/1000-parallel-12 1674694 688.20 ns/op +BenchmarkB64Decode/10000-12 35721 36986.00 ns/op +BenchmarkB64Decode/10000-parallel-12 186091 5815.00 ns/op +BenchmarkB64Decode/100000-12 5961 342365.00 ns/op +BenchmarkB64Decode/100000-parallel-12 25243 46691.00 ns/op +BenchmarkIsBinary/small-valid-12 130751559 9.20 ns/op +BenchmarkIsBinary/small-valid-parallel-12 721446243 1.43 ns/op +BenchmarkIsBinary/long-valid-12 5305724 232.00 ns/op +BenchmarkIsBinary/long-valid-parallel-12 25722481 39.80 ns/op +BenchmarkIsBinary/small-invalid-12 135880948 8.99 ns/op +BenchmarkIsBinary/small-invalid-parallel-12 763902718 1.55 ns/op +BenchmarkCommandExists/go-12 111031 10957.00 ns/op +BenchmarkCommandExists/go-parallel-12 718045 1587.00 ns/op +BenchmarkCommandExists/asddsa-12 24708 57858.00 ns/op +BenchmarkCommandExists/asddsa-parallel-12 130980 8246.00 ns/op +BenchmarkSafeExec-12 690 2308535.00 ns/op +BenchmarkCombineFuncs-12 5793744 203.80 ns/op +BenchmarkFuture-12 1492561 789.80 ns/op +BenchmarkRandomLetters/1000-12 326869 4196.00 ns/op +BenchmarkRandomLetters/1000-parallel-12 37795 30892.00 ns/op +BenchmarkRandomLetters/10000-12 29838 41700.00 ns/op +BenchmarkRandomLetters/10000-parallel-12 3884 305148.00 ns/op +BenchmarkRandomLetters/100000-12 2571 400259.00 ns/op +BenchmarkRandomLetters/100000-parallel-12 386 2972860.00 ns/op +BenchmarkUniqueStrings/slice1-12 942453 1435.00 ns/op +BenchmarkUniqueStrings/slice1-parallel-12 4134234 273.10 ns/op +BenchmarkUniqueStrings/slice2-12 8259 261283.00 ns/op +BenchmarkUniqueStrings/slice2-parallel-12 21670 56012.00 ns/op +BenchmarkUniqueInts/slice1-12 1523756 843.80 ns/op +BenchmarkUniqueInts/slice1-parallel-12 7665976 133.00 ns/op +BenchmarkUniqueInts/slice2-12 10000 159122.00 ns/op +BenchmarkUniqueInts/slice2-parallel-12 61369 17542.00 ns/op +BenchmarkUniqueInterfaces/slice1-12 1000000 1743.00 ns/op +BenchmarkUniqueInterfaces/slice1-parallel-12 3386178 337.70 ns/op +BenchmarkUniqueInterfaces/slice2-12 2461 585875.00 ns/op +BenchmarkUniqueInterfaces/slice2-parallel-12 8068 131634.00 ns/op +BenchmarkShortDuration/Simple-12 14830498 92.78 ns/op +BenchmarkShortDuration/Simple-parallel-12 61034352 17.06 ns/op +BenchmarkShortDuration/Complex-12 2587477 504.30 ns/op +BenchmarkShortDuration/Complex-parallel-12 8923653 123.70 ns/op +BenchmarkBoolPtr/serial-12 1000000000 0.43 ns/op +BenchmarkBoolPtr/parallel-12 1000000000 0.33 ns/op ``` ## Contribute diff --git a/os.go b/os.go index 52f63bb..d789dd8 100644 --- a/os.go +++ b/os.go @@ -1,13 +1,11 @@ package u import ( - "errors" "fmt" "io" "io/ioutil" "os" "os/user" - "strings" ) // TempfileWithContent creates a tempfile with specified content written in it, it also seeks the file pointer so you can read it directly. @@ -48,35 +46,6 @@ func MustTempfileWithContent(content []byte) (*os.File, func()) { return f, cleanup } -func ExpandUser(path string) (string, error) { - // expand variables - path = os.ExpandEnv(path) - - // replace ~ with homedir - if len(path) > 1 && path[:2] == "~/" { - home := os.Getenv("HOME") // *nix - if home == "" { - home = os.Getenv("USERPROFILE") // windows - } - if home == "" { - return "", errors.New("user home directory not found") - } - - return strings.Replace(path, "~", home, 1), nil - } - - return path, nil -} - -// MustExpandUser wraps ExpandUser and panics if initialization fails. -func MustExpandUser(path string) string { - ret, err := ExpandUser(path) - if err != nil { - panic(err) - } - return ret -} - // PathExists checks whether a path exists or not. func PathExists(path string) bool { _, err := os.Stat(path) diff --git a/os_test.go b/os_test.go index 0a7010d..ad9ae20 100644 --- a/os_test.go +++ b/os_test.go @@ -45,23 +45,6 @@ func ExampleMustTempfileWithContent() { // CCC } -func ExampleExpandUser() { - os.Setenv("HOME", "/home/foo") // just for example - ret, err := u.ExpandUser("~/hello-world.txt") - if err != nil { - panic(err) - } - fmt.Println(ret) - // Output: /home/foo/hello-world.txt -} - -func ExampleMustExpandUser() { - os.Setenv("HOME", "/home/foo") // just for example - ret := u.MustExpandUser("~/hello-world.txt") - fmt.Println(ret) - // Output: /home/foo/hello-world.txt -} - func ExamplePathExists() { file, err := ioutil.TempFile("", "bar") if err != nil { diff --git a/path.go b/path.go new file mode 100644 index 0000000..fb487e9 --- /dev/null +++ b/path.go @@ -0,0 +1,70 @@ +package u + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" +) + +// ExpandPath performs various expansions on a given path. +// +// - Replaces ~/ with $HOME/. +// - Returns absolute path. +// - Expands env vars. +// TODO: - Follow symlinks. +func ExpandPath(path string) (string, error) { + // expand variables + path = os.ExpandEnv(path) + + // replace ~ with homedir + if len(path) > 1 && path[:2] == "~/" { + home := os.Getenv("HOME") // *nix + if home == "" { + home = os.Getenv("USERPROFILE") // windows + } + if home == "" { + return "", errors.New("user home directory not found") + } + + return strings.Replace(path, "~", home, 1), nil + } + + // compute absolute path + { + result, err := filepath.Abs(path) + if err != nil { + return "", fmt.Errorf("absolute path: %q: %w", path, err) + } + path = result + } + + // expand env vars + { + path = os.ExpandEnv(path) + } + + // eval symlinks + // TODO: do not fail if path does not exist + /* + { + result, err := filepath.EvalSymlinks(path) + if err != nil { + return "", fmt.Errorf("eval symlinks: %q: %w", path, err) + } + path = result + } + */ + + return path, nil +} + +// MustExpandPath wraps ExpandPath and panics if initialization fails. +func MustExpandPath(path string) string { + ret, err := ExpandPath(path) + if err != nil { + panic(err) + } + return ret +} diff --git a/path_test.go b/path_test.go new file mode 100644 index 0000000..a293ff5 --- /dev/null +++ b/path_test.go @@ -0,0 +1,65 @@ +package u_test + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "moul.io/u" +) + +func TestExpandPath(t *testing.T) { + os.Setenv("HOME", "/home/foo") + os.Setenv("USER", "foo") + os.Unsetenv("FOOBAR") + workdir, err := os.Getwd() + require.NoError(t, err) + + tests := []struct { + input string + expected string + shouldFails bool + }{ + {"/home/foo", "/home/foo", false}, + {"/home/foo/", "/home/foo", false}, + {"~", filepath.Join(workdir, "~"), false}, + {"~/", "/home/foo/", false}, + {"$HOME/hello", "/home/foo/hello", false}, + {"/home/$USER/hello", "/home/foo/hello", false}, + {"/tmp/$FOOBAR/hello", "/tmp/hello", false}, + } + for _, tc := range tests { + name := strings.Replace(tc.input, "/", "-", -1) + t.Run(name, func(t *testing.T) { + result, err := u.ExpandPath(tc.input) + if tc.shouldFails { + assert.Error(t, err) + assert.Empty(t, result) + } else { + assert.NoError(t, err) + assert.Equal(t, result, tc.expected) + } + }) + } +} + +func ExampleExpandPath() { + os.Setenv("HOME", "/home/foo") // just for example + ret, err := u.ExpandPath("~/hello-world.txt") + if err != nil { + panic(err) + } + fmt.Println(ret) + // Output: /home/foo/hello-world.txt +} + +func ExampleMustExpandPath() { + os.Setenv("HOME", "/home/foo") // just for example + ret := u.MustExpandPath("~/hello-world.txt") + fmt.Println(ret) + // Output: /home/foo/hello-world.txt +}