From c1f56f102f9bcd280fd8a67d6c87e415a63b7b21 Mon Sep 17 00:00:00 2001 From: acabarbaye Date: Thu, 5 Sep 2024 23:07:00 +0100 Subject: [PATCH 1/3] ":sparkles: `[filesystem]` Add a way to determine all parents of a path --- changes/20240905230606.feature | 1 + utils/filesystem/filepath.go | 23 ++++++++++++++++++++++- utils/filesystem/filepath_test.go | 27 +++++++++++++++++++++++++++ 3 files changed, 50 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..561b9dbb8d 100644 --- a/utils/filesystem/filepath.go +++ b/utils/filesystem/filepath.go @@ -13,11 +13,32 @@ 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] + 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..466645bb0a 100644 --- a/utils/filesystem/filepath_test.go +++ b/utils/filesystem/filepath_test.go @@ -29,6 +29,33 @@ func TestFilepathStem(t *testing.T) { }) } +func TestFilepathParents(t *testing.T) { + + tests := []struct { + path string + expectedParents []string + }{ + {}, + { + path: " ", + 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: "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()) From b75fa48fd6cb136caacb99879c2de2ef6aba4a36 Mon Sep 17 00:00:00 2001 From: acabarbaye Date: Thu, 5 Sep 2024 23:12:52 +0100 Subject: [PATCH 2/3] go mod tidy --- utils/go.mod | 2 +- utils/go.sum | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/utils/go.mod b/utils/go.mod index 81548009bc..a77e61fc4e 100644 --- a/utils/go.mod +++ b/utils/go.mod @@ -20,6 +20,7 @@ require ( github.com/go-ozzo/ozzo-validation/v4 v4.3.0 github.com/gofrs/uuid v4.4.0+incompatible github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f + github.com/golang/mock v1.6.0 github.com/hashicorp/go-cleanhttp v0.5.2 github.com/hashicorp/go-hclog v1.6.3 github.com/hashicorp/go-retryablehttp v0.7.7 @@ -40,7 +41,6 @@ require ( github.com/zailic/slogr v0.0.2-alpha go.uber.org/atomic v1.11.0 go.uber.org/goleak v1.3.0 - go.uber.org/mock v0.4.0 go.uber.org/zap v1.27.0 golang.org/x/crypto v0.26.0 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 diff --git a/utils/go.sum b/utils/go.sum index 6ba42e8ce0..bbb5668eb7 100644 --- a/utils/go.sum +++ b/utils/go.sum @@ -86,6 +86,8 @@ github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW github.com/golang/glog v0.0.0-20210429001901-424d2337a529/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -233,6 +235,7 @@ github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+F github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= @@ -247,6 +250,7 @@ go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -256,6 +260,7 @@ golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= @@ -266,6 +271,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -278,6 +284,7 @@ golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= @@ -298,8 +305,10 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210601080250-7ecdf8ef093b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -337,11 +346,13 @@ golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= From 2e2c012945f37ec6479d34eda1972000c6107c29 Mon Sep 17 00:00:00 2001 From: acabarbaye Date: Thu, 5 Sep 2024 23:28:23 +0100 Subject: [PATCH 3/3] more testing --- utils/filesystem/filepath.go | 7 ++++++ utils/filesystem/filepath_test.go | 37 +++++++++++++++++++++++++++---- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/utils/filesystem/filepath.go b/utils/filesystem/filepath.go index 561b9dbb8d..84742da954 100644 --- a/utils/filesystem/filepath.go +++ b/utils/filesystem/filepath.go @@ -31,6 +31,13 @@ func FilePathParentsOnFilesystem(fs FS, fp string) (parents []string) { 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())) diff --git a/utils/filesystem/filepath_test.go b/utils/filesystem/filepath_test.go index 466645bb0a..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) { @@ -30,24 +31,52 @@ func TestFilepathStem(t *testing.T) { } func TestFilepathParents(t *testing.T) { - - tests := []struct { + 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) {