1 /**
2  * ae.utils.path
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <vladimir@thecybershadow.net>
12  */
13 
14 module ae.utils.path;
15 
16 import std.algorithm.searching;
17 import std.path;
18 
19 /// Modify a path under oldBase to a new path with the same subpath under newBase.
20 /// E.g.: `/foo/bar`.rebasePath(`/foo`, `/quux`) == `/quux/bar`
21 string rebasePath(string path, string oldBase, string newBase)
22 {
23 	return buildPath(newBase, path.absolutePath.relativePath(oldBase.absolutePath));
24 }
25 
26 /// Like Pascal's IncludeTrailingPathDelimiter
27 string includeTrailingPathSeparator(string path)
28 {
29 	if (path.length && !path[$-1].isDirSeparator())
30 		path ~= dirSeparator;
31 	return path;
32 }
33 
34 /// Like Pascal's ExcludeTrailingPathDelimiter
35 string excludeTrailingPathSeparator(string path)
36 {
37 	if (path.length && path[$-1].isDirSeparator())
38 		path = path[0..$-1];
39 	return path;
40 }
41 
42 /// Like startsWith, but pathStartsWith("/foo/barbara", "/foo/bar") is false.
43 bool pathStartsWith(in char[] path, in char[] prefix)
44 {
45 	// Special cases to accommodate relativePath(path, path) results
46 	if (prefix == "" || prefix == ".")
47 		return true;
48 
49 	return path.startsWith(prefix) &&
50 		(path.length == prefix.length || isDirSeparator(path[prefix.length]));
51 }
52 
53 unittest
54 {
55 	assert( "/foo/bar"    .pathStartsWith("/foo/bar"));
56 	assert( "/foo/bar/baz".pathStartsWith("/foo/bar"));
57 	assert(!"/foo/barbara".pathStartsWith("/foo/bar"));
58 	assert( "/foo/bar"    .pathStartsWith(""));
59 	assert( "/foo/bar"    .pathStartsWith("."));
60 }
61 
62 // ************************************************************************
63 
64 import std.process : environment;
65 import std.string : split;
66 
67 @property string[] pathDirs()
68 {
69 	return environment["PATH"].split(pathSeparator);
70 }
71 
72 bool haveExecutable(string name)
73 {
74 	return findExecutable(name, pathDirs) !is null;
75 }
76 
77 /// Find an executable with the given name
78 /// (no extension) in the given directories.
79 /// Returns null if not found.
80 string findExecutable(string name, string[] dirs)
81 {
82 	import std.file : exists;
83 
84 	version (Windows)
85 		enum executableSuffixes = [".exe", ".bat", ".cmd"];
86 	else
87 		enum executableSuffixes = [""];
88 
89 	foreach (dir; dirs)
90 		foreach (suffix; executableSuffixes)
91 		{
92 			auto fn = buildPath(dir, name) ~ suffix;
93 			if (fn.exists)
94 				return fn;
95 		}
96 
97 	return null;
98 }
99 
100 
101 // ************************************************************************
102 
103 /// The file name for the null device
104 /// (which discards all writes).
105 version (Windows)
106 	enum nullFileName = "nul";
107 else
108 	enum nullFileName = "/dev/null";