1 // Copyright 2019 The Kubernetes Authors. 2 // SPDX-License-Identifier: Apache-2.0 3 4 package filesys 5 6 import ( 7 "os" 8 "path/filepath" 9 "strings" 10 ) 11 12 // RootedPath returns a rooted path, e.g. "/foo/bar" as 13 // opposed to "foo/bar". 14 func RootedPath(elem ...string) string { 15 return Separator + filepath.Join(elem...) 16 } 17 18 // StripTrailingSeps trims trailing filepath separators from input. 19 func StripTrailingSeps(s string) string { 20 k := len(s) 21 for k > 0 && s[k-1] == filepath.Separator { 22 k-- 23 } 24 return s[:k] 25 } 26 27 // StripLeadingSeps trims leading filepath separators from input. 28 func StripLeadingSeps(s string) string { 29 k := 0 30 for k < len(s) && s[k] == filepath.Separator { 31 k++ 32 } 33 return s[k:] 34 } 35 36 // PathSplit converts a file path to a slice of string. 37 // If the path is absolute (if the path has a leading slash), 38 // then the first entry in the result is an empty string. 39 // Desired: path == PathJoin(PathSplit(path)) 40 func PathSplit(incoming string) []string { 41 if incoming == "" { 42 return []string{} 43 } 44 dir, path := filepath.Split(incoming) 45 if dir == string(os.PathSeparator) { 46 if path == "" { 47 return []string{""} 48 } 49 return []string{"", path} 50 } 51 dir = strings.TrimSuffix(dir, string(os.PathSeparator)) 52 if dir == "" { 53 return []string{path} 54 } 55 return append(PathSplit(dir), path) 56 } 57 58 // PathJoin converts a slice of string to a file path. 59 // If the first entry is an empty string, then the returned 60 // path is absolute (it has a leading slash). 61 // Desired: path == PathJoin(PathSplit(path)) 62 func PathJoin(incoming []string) string { 63 if len(incoming) == 0 { 64 return "" 65 } 66 if incoming[0] == "" { 67 return string(os.PathSeparator) + filepath.Join(incoming[1:]...) 68 } 69 return filepath.Join(incoming...) 70 } 71 72 // InsertPathPart inserts 'part' at position 'pos' in the given filepath. 73 // The first position is 0. 74 // 75 // E.g. if part == 'PEACH' 76 // 77 // OLD : NEW : POS 78 // -------------------------------------------------------- 79 // {empty} : PEACH : irrelevant 80 // / : /PEACH : irrelevant 81 // pie : PEACH/pie : 0 (or negative) 82 // /pie : /PEACH/pie : 0 (or negative) 83 // raw : raw/PEACH : 1 (or larger) 84 // /raw : /raw/PEACH : 1 (or larger) 85 // a/nice/warm/pie : a/nice/warm/PEACH/pie : 3 86 // /a/nice/warm/pie : /a/nice/warm/PEACH/pie : 3 87 // 88 // * An empty part results in no change. 89 // 90 // - Absolute paths get their leading '/' stripped, treated like 91 // relative paths, and the leading '/' is re-added on output. 92 // The meaning of pos is intentionally the same in either absolute or 93 // relative paths; if it weren't, this function could convert absolute 94 // paths to relative paths, which is not desirable. 95 // 96 // - For robustness (liberal input, conservative output) Pos values 97 // that are too small (large) to index the split filepath result in a 98 // prefix (postfix) rather than an error. Use extreme position values 99 // to assure a prefix or postfix (e.g. 0 will always prefix, and 100 // 9999 will presumably always postfix). 101 func InsertPathPart(path string, pos int, part string) string { 102 if part == "" { 103 return path 104 } 105 parts := PathSplit(path) 106 if pos < 0 { 107 pos = 0 108 } else if pos > len(parts) { 109 pos = len(parts) 110 } 111 if len(parts) > 0 && parts[0] == "" && pos < len(parts) { 112 // An empty string at 0 indicates an absolute path, and means 113 // we must increment pos. This change means that a position 114 // specification has the same meaning in relative and absolute paths. 115 // E.g. in either the path 'a/b/c' or the path '/a/b/c', 116 // 'a' is at 0, 'b' is at 1 and 'c' is at 2, and inserting at 117 // zero means a new first field _without_ changing an absolute 118 // path to a relative path. 119 pos++ 120 } 121 result := make([]string, len(parts)+1) 122 copy(result, parts[0:pos]) 123 result[pos] = part 124 return PathJoin(append(result, parts[pos:]...)) //nolint: makezero 125 } 126 127 func IsHiddenFilePath(pattern string) bool { 128 return strings.HasPrefix(filepath.Base(pattern), ".") 129 } 130 131 // Removes paths containing hidden files/folders from a list of paths 132 func RemoveHiddenFiles(paths []string) []string { 133 if len(paths) == 0 { 134 return paths 135 } 136 var result []string 137 for _, path := range paths { 138 if !IsHiddenFilePath(path) { 139 result = append(result, path) 140 } 141 } 142 return result 143 } 144