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