Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #27 from adrg/windows-known-folders
Add Known Folders support on Windows
- Loading branch information
Showing
24 changed files
with
922 additions
and
348 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package pathutil | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
) | ||
|
||
// Unique eliminates the duplicate paths from the provided slice and returns | ||
// the result. The items in the output slice are in the order in which they | ||
// occur in the input slice. If a `home` location is provided, the paths are | ||
// expanded using the `ExpandHome` function. | ||
func Unique(paths []string, home string) []string { | ||
var ( | ||
uniq []string | ||
registry = map[string]struct{}{} | ||
) | ||
|
||
for _, p := range paths { | ||
p = ExpandHome(p, home) | ||
if p != "" && filepath.IsAbs(p) { | ||
if _, ok := registry[p]; ok { | ||
continue | ||
} | ||
|
||
registry[p] = struct{}{} | ||
uniq = append(uniq, p) | ||
} | ||
} | ||
|
||
return uniq | ||
} | ||
|
||
// Create returns a suitable location relative to which the file with the | ||
// specified `name` can be written. The first path from the provided `paths` | ||
// slice which is successfully created (or already exists) is used as a base | ||
// path for the file. The `name` parameter should contain the name of the file | ||
// which is going to be written in the location returned by this function, but | ||
// it can also contain a set of parent directories, which will be created | ||
// relative to the selected parent path. | ||
func Create(name string, paths []string) (string, error) { | ||
var searchedPaths []string | ||
for _, p := range paths { | ||
p = filepath.Join(p, name) | ||
|
||
dir := filepath.Dir(p) | ||
if Exists(dir) { | ||
return p, nil | ||
} | ||
if err := os.MkdirAll(dir, os.ModeDir|0700); err == nil { | ||
return p, nil | ||
} | ||
|
||
searchedPaths = append(searchedPaths, dir) | ||
} | ||
|
||
return "", fmt.Errorf("could not create any of the following paths: %s", | ||
strings.Join(searchedPaths, ", ")) | ||
} | ||
|
||
// Search searches for the file with the specified `name` in the provided | ||
// slice of `paths`. The `name` parameter must contain the name of the file, | ||
// but it can also contain a set of parent directories. | ||
func Search(name string, paths []string) (string, error) { | ||
var searchedPaths []string | ||
for _, p := range paths { | ||
p = filepath.Join(p, name) | ||
if Exists(p) { | ||
return p, nil | ||
} | ||
|
||
searchedPaths = append(searchedPaths, filepath.Dir(p)) | ||
} | ||
|
||
return "", fmt.Errorf("could not locate `%s` in any of the following paths: %s", | ||
filepath.Base(name), strings.Join(searchedPaths, ", ")) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package pathutil | ||
|
||
import ( | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
) | ||
|
||
// Exists returns true if the specified path exists. | ||
func Exists(path string) bool { | ||
_, err := os.Stat(path) | ||
return err == nil || os.IsExist(err) | ||
} | ||
|
||
// ExpandHome substitutes `~` and `$home` at the start of the specified | ||
// `path` using the provided `home` location. | ||
func ExpandHome(path, home string) string { | ||
if path == "" || home == "" { | ||
return path | ||
} | ||
if path[0] == '~' { | ||
return filepath.Join(home, path[1:]) | ||
} | ||
if strings.HasPrefix(path, "$home") { | ||
return filepath.Join(home, path[5:]) | ||
} | ||
|
||
return path | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
//go:build plan9 | ||
// +build plan9 | ||
|
||
package pathutil_test | ||
|
||
import ( | ||
"path/filepath" | ||
"testing" | ||
|
||
"github.com/adrg/xdg/internal/pathutil" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestExpandHome(t *testing.T) { | ||
home := "/home/test" | ||
|
||
require.Equal(t, home, pathutil.ExpandHome("~", home)) | ||
require.Equal(t, home, pathutil.ExpandHome("$home", home)) | ||
require.Equal(t, filepath.Join(home, "appname"), pathutil.ExpandHome("~/appname", home)) | ||
require.Equal(t, filepath.Join(home, "appname"), pathutil.ExpandHome("$home/appname", home)) | ||
|
||
require.Equal(t, "", pathutil.ExpandHome("", home)) | ||
require.Equal(t, home, pathutil.ExpandHome(home, "")) | ||
require.Equal(t, "", pathutil.ExpandHome("", "")) | ||
|
||
require.Equal(t, home, pathutil.ExpandHome(home, home)) | ||
require.Equal(t, "/", pathutil.ExpandHome("~", "/")) | ||
require.Equal(t, "/", pathutil.ExpandHome("$home", "/")) | ||
require.Equal(t, "/usr/bin", pathutil.ExpandHome("~/bin", "/usr")) | ||
require.Equal(t, "/usr/bin", pathutil.ExpandHome("$home/bin", "/usr")) | ||
} | ||
|
||
func TestUnique(t *testing.T) { | ||
input := []string{ | ||
"", | ||
"/home", | ||
"/home/test", | ||
"a", | ||
"~/appname", | ||
"$home/appname", | ||
"a", | ||
"/home", | ||
} | ||
|
||
expected := []string{ | ||
"/home", | ||
"/home/test", | ||
"/home/test/appname", | ||
} | ||
|
||
require.EqualValues(t, expected, pathutil.Unique(input, "/home/test")) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package pathutil_test | ||
|
||
import ( | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
|
||
"github.com/adrg/xdg/internal/pathutil" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestExists(t *testing.T) { | ||
tempDir := os.TempDir() | ||
|
||
// Test regular file. | ||
pathFile := filepath.Join(tempDir, "regular") | ||
f, err := os.Create(pathFile) | ||
require.NoError(t, err) | ||
require.NoError(t, f.Close()) | ||
require.True(t, pathutil.Exists(pathFile)) | ||
|
||
// Test symlink. | ||
pathSymlink := filepath.Join(tempDir, "symlink") | ||
require.NoError(t, os.Symlink(pathFile, pathSymlink)) | ||
require.True(t, pathutil.Exists(pathSymlink)) | ||
|
||
// Test non-existent file. | ||
require.NoError(t, os.Remove(pathFile)) | ||
require.False(t, pathutil.Exists(pathFile)) | ||
require.False(t, pathutil.Exists(pathSymlink)) | ||
require.NoError(t, os.Remove(pathSymlink)) | ||
require.False(t, pathutil.Exists(pathSymlink)) | ||
} | ||
|
||
func TestCreate(t *testing.T) { | ||
tempDir := os.TempDir() | ||
|
||
// Test path selection order. | ||
p, err := pathutil.Create("test", []string{tempDir, "\000a"}) | ||
require.NoError(t, err) | ||
require.Equal(t, filepath.Join(tempDir, "test"), p) | ||
|
||
p, err = pathutil.Create("test", []string{"\000a", tempDir}) | ||
require.NoError(t, err) | ||
require.Equal(t, filepath.Join(tempDir, "test"), p) | ||
|
||
// Test relative parent directories. | ||
expected := filepath.Join(tempDir, "appname", "config", "test") | ||
p, err = pathutil.Create(filepath.Join("appname", "config", "test"), []string{"\000a", tempDir}) | ||
require.NoError(t, err) | ||
require.Equal(t, expected, p) | ||
require.NoError(t, os.RemoveAll(filepath.Dir(expected))) | ||
|
||
expected = filepath.Join(tempDir, "appname", "test") | ||
p, err = pathutil.Create(filepath.Join("appname", "test"), []string{"\000a", tempDir}) | ||
require.NoError(t, err) | ||
require.Equal(t, expected, p) | ||
require.NoError(t, os.RemoveAll(filepath.Dir(expected))) | ||
|
||
// Test invalid paths. | ||
_, err = pathutil.Create(filepath.Join("appname", "test"), []string{"\000a"}) | ||
require.Error(t, err) | ||
|
||
_, err = pathutil.Create("test", []string{filepath.Join(tempDir, "\000a")}) | ||
require.Error(t, err) | ||
} | ||
|
||
func TestSearch(t *testing.T) { | ||
tempDir := os.TempDir() | ||
|
||
// Test file not found. | ||
_, err := pathutil.Search("test", []string{tempDir, filepath.Join(tempDir, "appname")}) | ||
require.Error(t, err) | ||
|
||
// Test file found. | ||
expected := filepath.Join(tempDir, "test") | ||
f, err := os.Create(expected) | ||
require.NoError(t, err) | ||
require.NoError(t, f.Close()) | ||
|
||
p, err := pathutil.Search("test", []string{tempDir}) | ||
require.NoError(t, err) | ||
require.Equal(t, expected, p) | ||
|
||
p, err = pathutil.Search("test", []string{filepath.Join(tempDir, "appname"), tempDir}) | ||
require.NoError(t, err) | ||
require.Equal(t, expected, p) | ||
|
||
require.NoError(t, os.Remove(expected)) | ||
|
||
// Test relative parent directories. | ||
expected = filepath.Join(tempDir, "appname", "test") | ||
_, err = pathutil.Create(filepath.Join("appname", "test"), []string{tempDir}) | ||
require.NoError(t, err) | ||
f, err = os.Create(expected) | ||
require.NoError(t, err) | ||
require.NoError(t, f.Close()) | ||
|
||
p, err = pathutil.Search(filepath.Join("appname", "test"), []string{tempDir}) | ||
require.NoError(t, err) | ||
require.Equal(t, expected, p) | ||
|
||
p, err = pathutil.Search("test", []string{tempDir, filepath.Join(tempDir, "appname")}) | ||
require.NoError(t, err) | ||
require.Equal(t, expected, p) | ||
|
||
require.NoError(t, os.RemoveAll(filepath.Dir(expected))) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
//go:build aix || darwin || dragonfly || freebsd || (js && wasm) || nacl || linux || netbsd || openbsd || solaris | ||
// +build aix darwin dragonfly freebsd js,wasm nacl linux netbsd openbsd solaris | ||
|
||
package pathutil | ||
|
||
import ( | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
) | ||
|
||
// Exists returns true if the specified path exists. | ||
func Exists(path string) bool { | ||
_, err := os.Stat(path) | ||
return err == nil || os.IsExist(err) | ||
} | ||
|
||
// ExpandHome substitutes `~` and `$HOME` at the start of the specified | ||
// `path` using the provided `home` location. | ||
func ExpandHome(path, home string) string { | ||
if path == "" || home == "" { | ||
return path | ||
} | ||
if path[0] == '~' { | ||
return filepath.Join(home, path[1:]) | ||
} | ||
if strings.HasPrefix(path, "$HOME") { | ||
return filepath.Join(home, path[5:]) | ||
} | ||
|
||
return path | ||
} |
Oops, something went wrong.