From d518f3b44ae3b3dd97838b583a1caef12a9f9464 Mon Sep 17 00:00:00 2001 From: Adrien CABARBAYE Date: Thu, 5 Sep 2024 23:42:04 +0100 Subject: [PATCH] :sparkles: `[filesystem]` Add a way to determine all parents of a path (#503) ### Description try to give the same functionality as [pathlib](https://github.com/python/cpython/blob/3.12/Lib/pathlib.py#L263) ### Test Coverage - [x] This change is covered by existing or additional automated tests. - [ ] Manual testing has been performed (and evidence provided) as automated testing was not feasible. - [ ] Additional tests are not required for this change (e.g. documentation update). --- changes/20240905230606.feature | 1 + utils/filesystem/filepath.go | 30 ++++++++++++++++- utils/filesystem/filepath_test.go | 56 +++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 changes/20240905230606.feature diff --git a/changes/20240905230606.feature b/changes/20240905230606.feature new file mode 100644 index 0000000000..955a13c3b2 --- /dev/null +++ b/changes/20240905230606.feature @@ -0,0 +1 @@ +:sparkles: [filesystem] Add a way to determine all parents of a path similar to python's [pathlib](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.parents) diff --git a/utils/filesystem/filepath.go b/utils/filesystem/filepath.go index b13b220cf8..84742da954 100644 --- a/utils/filesystem/filepath.go +++ b/utils/filesystem/filepath.go @@ -13,11 +13,39 @@ import ( "github.com/ARM-software/golang-utils/utils/reflection" ) -// FilepathStem returns the final path component, without its suffix. +// FilepathStem returns the final path component, without its suffix. It's similar to `stem` in python's [pathlib](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.stem) func FilepathStem(fp string) string { return strings.TrimSuffix(filepath.Base(fp), filepath.Ext(fp)) } +// FilepathParents returns a list of to the logical ancestors of the path and it's similar to `parents` in python's [pathlib](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.parents) +func FilepathParents(fp string) []string { + return FilePathParentsOnFilesystem(GetGlobalFileSystem(), fp) +} + +// FilePathParentsOnFilesystem is similar to FilepathParents but with the ability to be applied to a particular filesystem. +func FilePathParentsOnFilesystem(fs FS, fp string) (parents []string) { + cleanFp := filepath.Clean(fp) + elements := strings.Split(cleanFp, string(fs.PathSeparator())) + if len(elements) <= 1 { + return + } + path := elements[0] + if path == "" { + elements = elements[1:] + if len(elements) <= 1 { + return + } + path = elements[0] + } + parents = append(parents, path) + for i := 1; i < len(elements)-1; i++ { + path = strings.Join([]string{path, elements[i]}, string(fs.PathSeparator())) + parents = append(parents, path) + } + return +} + // FileTreeDepth returns the depth of a file in a tree starting from root func FileTreeDepth(fs FS, root, filePath string) (depth int64, err error) { if reflection.IsEmpty(filePath) { diff --git a/utils/filesystem/filepath_test.go b/utils/filesystem/filepath_test.go index 06becf2481..ae0d94ac66 100644 --- a/utils/filesystem/filepath_test.go +++ b/utils/filesystem/filepath_test.go @@ -12,6 +12,7 @@ import ( "github.com/ARM-software/golang-utils/utils/commonerrors" "github.com/ARM-software/golang-utils/utils/commonerrors/errortest" + "github.com/ARM-software/golang-utils/utils/platform" ) func TestFilepathStem(t *testing.T) { @@ -29,6 +30,61 @@ func TestFilepathStem(t *testing.T) { }) } +func TestFilepathParents(t *testing.T) { + type PathTest struct { + path string + expectedParents []string + } + tests := []PathTest{ + {}, + { + path: " ", + expectedParents: nil, + }, + { + path: "/", + expectedParents: nil, + }, + { + path: ".", + expectedParents: nil, + }, + { + path: "./", + expectedParents: nil, + }, + { + path: "./blah", + expectedParents: nil, + }, + { + path: filepath.Join("a", "great", "fake", "path", "blah"), + expectedParents: []string{"a", filepath.Join("a", "great"), filepath.Join("a", "great", "fake"), filepath.Join("a", "great", "fake", "path")}, + }, + { + path: "/foo/bar/setup.py", + expectedParents: []string{`foo`, filepath.Join(`foo`, `bar`)}, + }, + } + + if platform.IsWindows() { + tests = append(tests, PathTest{ + path: "C:/foo/bar/setup.py", + expectedParents: []string{"C:", filepath.Join(`C:`, `\foo`), filepath.Join(`C:`, `\foo`, `bar`)}, + }) + } else { + tests = append(tests, PathTest{ + path: "C:/foo/bar/setup.py", + expectedParents: []string{"C:", filepath.Join(`C:`, `foo`), filepath.Join(`C:`, `foo`, `bar`)}, + }) + } + for _, tt := range tests { + t.Run(tt.path, func(t *testing.T) { + assert.ElementsMatch(t, tt.expectedParents, FilepathParents(tt.path)) + }) + } +} + func TestFileTreeDepth(t *testing.T) { random := fmt.Sprintf("%v %v %v", faker.Name(), faker.Name(), faker.Name()) complexRandom := fmt.Sprintf("%v&#~@£*-_()^+!%v %v", faker.Name(), faker.Name(), faker.Name())