1 /** 2 * Utilities for interacting with the file system. 3 */ 4 module dshutils.fileutils; 5 6 import std.file; 7 8 /** 9 * Removes a file or directory if it exists. 10 * Params: 11 * file = The file or directory to remove. 12 * recursive = Whether to recursively remove subdirectories. 13 * Returns: True if the file or directory was removed, or false otherwise. 14 */ 15 public bool removeIfExists(string file, bool recursive = true) { 16 if (!exists(file)) return false; 17 if (isFile(file)) { 18 remove(file); 19 } else if (isDir(file)) { 20 if (recursive) { 21 rmdirRecurse(file); 22 } else { 23 rmdir(file); 24 } 25 } 26 return true; 27 } 28 29 /** 30 * Removes a set of files or directories, if they exist. 31 * Returns: True if any of the given files were removed. 32 */ 33 public bool removeAnyIfExists(string[] files...) { 34 bool any = false; 35 foreach (file; files) { 36 bool result = removeIfExists(file); 37 any = any || result; 38 } 39 return any; 40 } 41 42 /** 43 * Checks if a directory is empty. 44 * Params: 45 * dir = The directory to check. 46 * Returns: True if the given directory exists, is a directory, and is empty. 47 */ 48 public bool isDirEmpty(string dir) { 49 import std.range : empty; 50 import std.array : array; 51 if (!exists(dir) || !isDir(dir)) return false; 52 return empty(dirEntries(dir, SpanMode.shallow).array); 53 } 54 55 unittest { 56 assert(!isDirEmpty("source")); 57 assert(!isDirEmpty("source/dsh.d")); 58 assert(!isDirEmpty("source/some-non-existant-file.blah")); 59 auto d = ".test_empty_dir"; 60 d.mkdir; 61 scope(exit) d.rmdir; 62 assert(isDirEmpty(d)); 63 } 64 65 /** 66 * Gets the user's home directory. 67 * Returns: The user's home directory, or null if it could not be determined. 68 */ 69 public string getHomeDir() { 70 import std.process : environment; 71 version (linux) { 72 return environment.get("HOME"); 73 } 74 version (Windows) { 75 string drive = environment.get("HOMEDRIVE"); 76 string path = environment.get("HOMEPATH"); 77 if (drive is null || path is null) return null; 78 return drive ~ path; 79 } 80 } 81 82 /** 83 * Walks through all the entries in a directory, and applies the given visitor 84 * function to all entries. 85 * Params: 86 * dir = The directory to walk through. 87 * visitor = A visitor delegate function to apply to all entries discovered. 88 * recursive = Whether to recursively walk through subdirectories. 89 * maxDepth = How deep to search recursively. -1 indicates infinite recursion. 90 */ 91 public void walkDir(string dir, void delegate(DirEntry entry) visitor, bool recursive = true, int maxDepth = -1) { 92 if (!exists(dir) || !isDir(dir)) return; 93 foreach (DirEntry entry; dirEntries(dir, SpanMode.shallow)) { 94 visitor(entry); 95 if (recursive && entry.isDir && (maxDepth > 0 || maxDepth == -1)) { 96 walkDir(entry.name, visitor, recursive, maxDepth - 1); 97 } 98 } 99 } 100 101 unittest { 102 ulong totalSize = 0; 103 uint filesVisited = 0; 104 void addSize(DirEntry entry) { 105 totalSize += entry.size; 106 filesVisited++; 107 } 108 walkDir(".", &addSize); 109 assert(totalSize > 0); 110 assert(filesVisited > 10); 111 } 112 113 /** 114 * Walks through all the entries in a directory, and applies the given visitor 115 * function to all entries for which a given filter function returns true. 116 * Params: 117 * dir = The directory to walk through. 118 * visitor = A visitor delegate function to apply to all entries discovered. 119 * filter = A filter delegate that determines if an entry should be visited. 120 * recursive = Whether to recursively walk through subdirectories. 121 * maxDepth = How deep to search recursively. -1 indicates infinite recursion. 122 */ 123 public void walkDirFiltered( 124 string dir, 125 void delegate(DirEntry entry) visitor, 126 bool delegate(DirEntry entry) filter, 127 bool recursive = true, 128 int maxDepth = -1 129 ) { 130 walkDir(dir, delegate(DirEntry entry) { 131 if (filter(entry)) visitor(entry); 132 }, recursive, maxDepth); 133 } 134 135 /** 136 * Walks through all files in a directory. 137 * Params: 138 * dir = The directory to walk through. 139 * visitor = A visitor delegate function to apply to all entries discovered. 140 * recursive = Whether to recursively walk through subdirectories. 141 * maxDepth = How deep to search recursively. -1 indicates infinite recursion. 142 */ 143 public void walkDirFiles( 144 string dir, 145 void delegate(DirEntry entry) visitor, 146 bool recursive = true, 147 int maxDepth = -1 148 ) { 149 walkDirFiltered(dir, visitor, (entry) {return entry.isFile;}, recursive, maxDepth); 150 } 151 152 /** 153 * Finds matching files in a directory. 154 * Params: 155 * dir = The directory to search in. 156 * pattern = A regex pattern to match against each filename. 157 * recursive = Whether to recursively search subdirectories. 158 * maxDepth = How deep to search recursively. -1 indicates infinite recursion. 159 * Returns: A list of matching filenames. 160 */ 161 public string[] findFiles(string dir, string pattern, bool recursive = true, int maxDepth = -1) { 162 import std.regex : matchFirst, Captures; 163 import std.path : baseName; 164 string[] matches = []; 165 walkDirFiles(dir, (entry) { 166 string filename = baseName(entry.name); 167 Captures!string c = matchFirst(filename, pattern); 168 if (!c.empty && c.hit.length == filename.length) matches ~= entry.name; 169 }, recursive, maxDepth); 170 return matches; 171 } 172 173 unittest { 174 assert(findFiles("source", ".*\\.d", true, 0) == ["source/dsh.d"]); 175 assert(findFiles(".", "dub.*", false).length == 2); 176 } 177 178 /** 179 * Finds all files in a directory that end with the given extension text. 180 * Params: 181 * dir = The directory to search in. 182 * extension = The extension for matching files, such as ".txt" or ".d" 183 * recursive = Whether to recursively search subdirectories. 184 * maxDepth = How deep to search recursively. -1 indicates infinite recursion. 185 * Returns: The list of matching files. 186 */ 187 public string[] findFilesByExtension(string dir, string extension, bool recursive = true, int maxDepth = -1) { 188 return findFiles(dir, ".*" ~ extension, recursive, maxDepth); 189 } 190 191 unittest { 192 assert(findFilesByExtension(".", "json").length == 2); 193 } 194 195 /** 196 * Tries to find a single file matching the given pattern. 197 * Params: 198 * dir = The directory to search in. 199 * pattern = A regex pattern to match against each filename. 200 * recursive = Whether to recursively search subdirectories. 201 * maxDepth = How deep to search recursively. -1 indicates infinite recursion. 202 * Returns: The single matching file that was found, or null if no match was 203 * found, or if multiple matches were found. 204 */ 205 public string findFile(string dir, string pattern, bool recursive = true, int maxDepth = -1) { 206 auto matches = findFiles(dir, pattern, recursive, maxDepth); 207 if (matches.length != 1) return null; 208 return matches[0]; 209 } 210 211 unittest { 212 assert(findFile(".", "dub\\.json") !is null); 213 assert(findFile(".", ".*\\.d") is null); 214 } 215 216 /** 217 * Copies all files from the given source directory, to the given destination 218 * directory. Will create the destination directory if it doesn't exist yet. 219 * Overwrites any files that already exist in the destination directory. 220 * Params: 221 * sourceDir = The source directory to copy from. 222 * destDir = The destination directory to copy to. 223 */ 224 public void copyDir(string sourceDir, string destDir) { 225 if (!isDir(sourceDir)) return; 226 if (exists(destDir) && !isDir(destDir)) return; 227 if (!exists(destDir)) mkdirRecurse(destDir); 228 import std.path : buildPath, baseName; 229 foreach (DirEntry entry; dirEntries(sourceDir, SpanMode.shallow)) { 230 string destPath = buildPath(destDir, baseName(entry.name)); 231 if (entry.isDir) { 232 copyDir(entry.name, destPath); 233 } else if (entry.isFile) { 234 copy(entry.name, destPath); 235 } 236 } 237 } 238 239 unittest { 240 copyDir("source", "source2"); 241 assert(exists("source2") && isDir("source2")); 242 assert(exists("source2/dsh.d")); 243 rmdirRecurse("source2"); 244 }