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