1 // Copyright (c) 2017 Matthew Brennan Jones <matthew.brennan.jones@gmail.com> 2 // Boost Software License - Version 1.0 3 // Search file systems with glob patterns using the D programming language 4 // https://github.com/workhorsy/d-glob 5 6 /++ 7 Search file systems with glob patterns using the D programming language 8 9 See Glob $(LINK https://en.wikipedia.org/wiki/Glob_(programming)) 10 11 Home page: 12 $(LINK https://github.com/workhorsy/d-glob) 13 14 Version: 0.2.0 15 16 License: 17 Boost Software License - Version 1.0 18 19 Examples: 20 ---- 21 import std.stdio : stdout; 22 import glob : glob; 23 24 foreach (entry ; glob("/usr/*/python*")) { 25 stdout.writefln("%s", entry); 26 } 27 28 // outputs 29 /* 30 /usr/bin/python2 31 /usr/bin/python2.7 32 /usr/bin/python3 33 /usr/bin/python3.5 34 /usr/lib/python2.7 35 /usr/lib/python3 36 /usr/lib/python3.5 37 */ 38 ---- 39 +/ 40 41 // https://en.wikipedia.org/wiki/Glob_%28programming%29 42 43 module glob; 44 45 46 import std.stdio : stdout; 47 48 /++ 49 Return all the paths that match the pattern 50 Params: 51 path_name = The path with paterns to match. 52 +/ 53 string[] glob(string path_name) { 54 import std.algorithm : map, filter; 55 import std.array : array; 56 import std.string : split, startsWith; 57 import std.file : getcwd; 58 59 // Break the path into a stack separated by / 60 string cwd = getcwd(); 61 bool is_relative_path = ! path_name.startsWith("/"); 62 string[] patterns = path_name.split("/").filter!(n => n != "").array(); 63 string[] paths = (is_relative_path ? [cwd] : ["/"]); 64 // stdout.writefln("path_name: \"%s\"", path_name); 65 // stdout.writefln("patterns: %s", patterns); 66 67 // For each pattern get the directory entries that match the pattern 68 while (patterns.length > 0) { 69 // Pop the next pattern off the stack 70 string pattern = patterns[0]; 71 patterns = patterns[1 .. $]; 72 73 // Get the matches 74 paths = getMatches(paths, pattern); 75 // stdout.writefln(" paths: %s", paths); 76 } 77 78 // Convert from an absolute path to a relative one, if path_name is relative 79 if (is_relative_path) { 80 size_t len = cwd.length + 1; 81 paths = paths.map!(n => n[len .. $]).array(); 82 } 83 84 return paths; 85 } 86 87 /// 88 unittest { 89 import std.stdio : stdout; 90 import glob : glob; 91 92 string[] entries = glob("/usr/*/python*"); 93 94 // entries would contain: 95 /* 96 /usr/bin/python2 97 /usr/bin/python2.7 98 /usr/bin/python3 99 /usr/bin/python3.5 100 /usr/lib/python2.7 101 /usr/lib/python3 102 /usr/lib/python3.5 103 */ 104 105 entries = glob("/usr/bin/python?"); 106 107 // entries would contain: 108 /* 109 /usr/bin/python2 110 /usr/bin/python3 111 */ 112 } 113 114 private string[] getMatches(string[] path_candidates, string pattern) { 115 import std.path : baseName, globMatch; 116 117 string[] matches; 118 119 // Iterate through all the entries in the paths 120 // and return the ones that match the pattern 121 foreach (path ; path_candidates) { 122 // stdout.writefln(" searching \"%s\" for \"%s\"", path, pattern); 123 foreach (entry ; getEntries(path)) { 124 if (globMatch(baseName(entry), pattern)) { 125 // stdout.writefln(" match: \"%s\"", entry); 126 matches ~= entry; 127 } 128 } 129 } 130 131 return matches; 132 } 133 134 // Returns the name of all the shallow entries in a directory 135 private string[] getEntries(string path_name) { 136 import std.file : dirEntries, SpanMode, FileException; 137 import std.algorithm : map, sort; 138 import std.array : array, replace; 139 140 string[] entries; 141 try { 142 entries = dirEntries(path_name, SpanMode.shallow).map!(n => n.name).array(); 143 } catch (FileException) { 144 } 145 146 // Convert Windows file paths to posix format 147 version (Windows) { 148 entries = entries.map!(n => n.replace("\\", "/")).array(); 149 } 150 151 entries.sort!("a < b"); 152 return entries; 153 } 154 155