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";