github.com/IBM/fsgo@v0.0.0-20220920202152-e16fd2119d49/util.go (about) 1 // Copyright 2022 IBM Inc. All rights reserved 2 // Copyright © 2014 Steve Francia <spf@spf13.com> 3 // 4 // SPDX-License-Identifier: Apache2.0 5 package fsgo 6 7 import ( 8 "bytes" 9 "fmt" 10 "io" 11 "os" 12 "path/filepath" 13 "strings" 14 "unicode" 15 16 "golang.org/x/text/runes" 17 "golang.org/x/text/transform" 18 "golang.org/x/text/unicode/norm" 19 ) 20 21 // Filepath separator defined by os.Separator. 22 const FilePathSeparator = string(filepath.Separator) 23 24 // Takes a reader and a path and writes the content 25 func (a FsGo) WriteReader(path string, r io.Reader) (err error) { 26 return WriteReader(a.Fs, path, r) 27 } 28 29 func WriteReader(fs Fs, path string, r io.Reader) (err error) { 30 dir, _ := filepath.Split(path) 31 ospath := filepath.FromSlash(dir) 32 33 if ospath != "" { 34 err = fs.MkdirAll(ospath, 0777) // rwx, rw, r 35 if err != nil { 36 if err != os.ErrExist { 37 return err 38 } 39 } 40 } 41 42 file, err := fs.Create(path) 43 if err != nil { 44 return 45 } 46 defer file.Close() 47 48 _, err = io.Copy(file, r) 49 return 50 } 51 52 // Same as WriteReader but checks to see if file/directory already exists. 53 func (a FsGo) SafeWriteReader(path string, r io.Reader) (err error) { 54 return SafeWriteReader(a.Fs, path, r) 55 } 56 57 func SafeWriteReader(fs Fs, path string, r io.Reader) (err error) { 58 dir, _ := filepath.Split(path) 59 ospath := filepath.FromSlash(dir) 60 61 if ospath != "" { 62 err = fs.MkdirAll(ospath, 0777) // rwx, rw, r 63 if err != nil { 64 return 65 } 66 } 67 68 exists, err := Exists(fs, path) 69 if err != nil { 70 return 71 } 72 if exists { 73 return fmt.Errorf("%v already exists", path) 74 } 75 76 file, err := fs.Create(path) 77 if err != nil { 78 return 79 } 80 defer file.Close() 81 82 _, err = io.Copy(file, r) 83 return 84 } 85 86 func (a FsGo) GetTempDir(subPath string) string { 87 return GetTempDir(a.Fs, subPath) 88 } 89 90 // GetTempDir returns the default temp directory with trailing slash 91 // if subPath is not empty then it will be created recursively with mode 777 rwx rwx rwx 92 func GetTempDir(fs Fs, subPath string) string { 93 addSlash := func(p string) string { 94 if FilePathSeparator != p[len(p)-1:] { 95 p = p + FilePathSeparator 96 } 97 return p 98 } 99 dir := addSlash(os.TempDir()) 100 101 if subPath != "" { 102 // preserve windows backslash :-( 103 if FilePathSeparator == "\\" { 104 subPath = strings.Replace(subPath, "\\", "____", -1) 105 } 106 dir = dir + UnicodeSanitize((subPath)) 107 if FilePathSeparator == "\\" { 108 dir = strings.Replace(dir, "____", "\\", -1) 109 } 110 111 if exists, _ := Exists(fs, dir); exists { 112 return addSlash(dir) 113 } 114 115 err := fs.MkdirAll(dir, 0777) 116 if err != nil { 117 panic(err) 118 } 119 dir = addSlash(dir) 120 } 121 return dir 122 } 123 124 // Rewrite string to remove non-standard path characters 125 func UnicodeSanitize(s string) string { 126 source := []rune(s) 127 target := make([]rune, 0, len(source)) 128 129 for _, r := range source { 130 if unicode.IsLetter(r) || 131 unicode.IsDigit(r) || 132 unicode.IsMark(r) || 133 r == '.' || 134 r == '/' || 135 r == '\\' || 136 r == '_' || 137 r == '-' || 138 r == '%' || 139 r == ' ' || 140 r == '#' { 141 target = append(target, r) 142 } 143 } 144 145 return string(target) 146 } 147 148 // Transform characters with accents into plain forms. 149 func NeuterAccents(s string) string { 150 t := transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC) 151 result, _, _ := transform.String(t, string(s)) 152 153 return result 154 } 155 156 func (a FsGo) FileContainsBytes(filename string, subslice []byte) (bool, error) { 157 return FileContainsBytes(a.Fs, filename, subslice) 158 } 159 160 // Check if a file contains a specified byte slice. 161 func FileContainsBytes(fs Fs, filename string, subslice []byte) (bool, error) { 162 f, err := fs.Open(filename) 163 if err != nil { 164 return false, err 165 } 166 defer f.Close() 167 168 return readerContainsAny(f, subslice), nil 169 } 170 171 func (a FsGo) FileContainsAnyBytes(filename string, subslices [][]byte) (bool, error) { 172 return FileContainsAnyBytes(a.Fs, filename, subslices) 173 } 174 175 // Check if a file contains any of the specified byte slices. 176 func FileContainsAnyBytes(fs Fs, filename string, subslices [][]byte) (bool, error) { 177 f, err := fs.Open(filename) 178 if err != nil { 179 return false, err 180 } 181 defer f.Close() 182 183 return readerContainsAny(f, subslices...), nil 184 } 185 186 // readerContains reports whether any of the subslices is within r. 187 func readerContainsAny(r io.Reader, subslices ...[]byte) bool { 188 189 if r == nil || len(subslices) == 0 { 190 return false 191 } 192 193 largestSlice := 0 194 195 for _, sl := range subslices { 196 if len(sl) > largestSlice { 197 largestSlice = len(sl) 198 } 199 } 200 201 if largestSlice == 0 { 202 return false 203 } 204 205 bufflen := largestSlice * 4 206 halflen := bufflen / 2 207 buff := make([]byte, bufflen) 208 var err error 209 var n, i int 210 211 for { 212 i++ 213 if i == 1 { 214 n, err = io.ReadAtLeast(r, buff[:halflen], halflen) 215 } else { 216 if i != 2 { 217 // shift left to catch overlapping matches 218 copy(buff[:], buff[halflen:]) 219 } 220 n, err = io.ReadAtLeast(r, buff[halflen:], halflen) 221 } 222 223 if n > 0 { 224 for _, sl := range subslices { 225 if bytes.Contains(buff, sl) { 226 return true 227 } 228 } 229 } 230 231 if err != nil { 232 break 233 } 234 } 235 return false 236 } 237 238 func (a FsGo) DirExists(path string) (bool, error) { 239 return DirExists(a.Fs, path) 240 } 241 242 // DirExists checks if a path exists and is a directory. 243 func DirExists(fs Fs, path string) (bool, error) { 244 fi, err := fs.Stat(path) 245 if err == nil && fi.IsDir() { 246 return true, nil 247 } 248 if os.IsNotExist(err) { 249 return false, nil 250 } 251 return false, err 252 } 253 254 func (a FsGo) IsDir(path string) (bool, error) { 255 return IsDir(a.Fs, path) 256 } 257 258 // IsDir checks if a given path is a directory. 259 func IsDir(fs Fs, path string) (bool, error) { 260 fi, err := fs.Stat(path) 261 if err != nil { 262 return false, err 263 } 264 return fi.IsDir(), nil 265 } 266 267 func (a FsGo) IsEmpty(path string) (bool, error) { 268 return IsEmpty(a.Fs, path) 269 } 270 271 // IsEmpty checks if a given file or directory is empty. 272 func IsEmpty(fs Fs, path string) (bool, error) { 273 if b, _ := Exists(fs, path); !b { 274 return false, fmt.Errorf("%q path does not exist", path) 275 } 276 fi, err := fs.Stat(path) 277 if err != nil { 278 return false, err 279 } 280 if fi.IsDir() { 281 f, err := fs.Open(path) 282 if err != nil { 283 return false, err 284 } 285 defer f.Close() 286 list, err := f.Readdir(-1) 287 if err != nil { 288 return false, err 289 } 290 return len(list) == 0, nil 291 } 292 return fi.Size() == 0, nil 293 } 294 295 func (a FsGo) Exists(path string) (bool, error) { 296 return Exists(a.Fs, path) 297 } 298 299 // Check if a file or directory exists. 300 func Exists(fs Fs, path string) (bool, error) { 301 _, err := fs.Stat(path) 302 if err == nil { 303 return true, nil 304 } 305 if os.IsNotExist(err) { 306 return false, nil 307 } 308 return false, err 309 } 310 311 func FullBaseFsPath(basePathFs *BasePathFs, relativePath string) string { 312 combinedPath := filepath.Join(basePathFs.path, relativePath) 313 if parent, ok := basePathFs.source.(*BasePathFs); ok { 314 return FullBaseFsPath(parent, combinedPath) 315 } 316 317 return combinedPath 318 }