1 /++ 2 $(H2 Scriptlike $(SCRIPTLIKE_VERSION)) 3 4 Extra Scriptlike-only functionality to complement $(MODULE_STD_PATH). 5 6 Copyright: Copyright (C) 2014-2017 Nick Sabalausky 7 License: zlib/libpng 8 Authors: Nick Sabalausky 9 +/ 10 module scriptlike.path.extras; 11 12 import std.algorithm; 13 import std.conv; 14 import std.datetime; 15 import std.file; 16 import std.process; 17 import std.range; 18 import std.stdio; 19 import std..string; 20 import std.traits; 21 import std.typecons; 22 import std.typetuple; 23 24 static import std.path; 25 import std.path : dirSeparator, pathSeparator, isDirSeparator, 26 CaseSensitive, buildPath, buildNormalizedPath; 27 28 import scriptlike.path.wrappers; 29 30 /// Represents a file extension. 31 struct Ext 32 { 33 private string str; 34 35 /// Main constructor. 36 this(string extension) pure @safe nothrow 37 { 38 this.str = extension; 39 } 40 41 /// Convert to string. 42 string toString() pure @safe nothrow 43 { 44 return str; 45 } 46 47 /// No longer needed. Use Ext.toString() instead. 48 deprecated("Use Ext.toString() instead.") 49 string toRawString() pure @safe nothrow 50 { 51 return str; 52 } 53 54 /// Compare using OS-specific case-sensitivity rules. If you want to force 55 /// case-sensitive or case-insensitive, then call filenameCmp instead. 56 int opCmp(ref const Ext other) const 57 { 58 return std.path.filenameCmp(this.str, other.str); 59 } 60 61 ///ditto 62 int opCmp(Ext other) const 63 { 64 return std.path.filenameCmp(this.str, other.str); 65 } 66 67 ///ditto 68 int opCmp(string other) const 69 { 70 return std.path.filenameCmp(this.str, other); 71 } 72 73 /// Compare using OS-specific case-sensitivity rules. If you want to force 74 /// case-sensitive or case-insensitive, then call filenameCmp instead. 75 int opEquals(ref const Ext other) const 76 { 77 return opCmp(other) == 0; 78 } 79 80 ///ditto 81 int opEquals(Ext other) const 82 { 83 return opCmp(other) == 0; 84 } 85 86 ///ditto 87 int opEquals(string other) const 88 { 89 return opCmp(other) == 0; 90 } 91 92 /// Convert to bool 93 T opCast(T)() if(is(T==bool)) 94 { 95 return !!str; 96 } 97 } 98 99 /// Represents a filesystem path. The path is always kept normalized 100 /// automatically (as performed by buildNormalizedPathFixed). 101 struct Path 102 { 103 private string str = "."; 104 105 /// Main constructor. 106 this(string path) pure @safe nothrow 107 { 108 this.str = buildNormalizedPathFixed(path); 109 } 110 111 pure @trusted nothrow invariant() 112 { 113 assert(str == buildNormalizedPathFixed(str)); 114 } 115 116 /// Convert to string, quoting or escaping spaces if necessary. 117 string toString() 118 { 119 return .escapeShellArg(str); 120 } 121 122 /// Returns the underlying string. Does NOT do any escaping, even if path contains spaces. 123 string raw() const pure @safe nothrow 124 { 125 return str; 126 } 127 128 ///ditto 129 deprecated("Use Path.raw instead.") 130 alias toRawString = raw; 131 132 /// Concatenates two paths, with a directory separator in between. 133 Path opBinary(string op)(Path rhs) if(op=="~") 134 { 135 Path newPath; 136 newPath.str = buildNormalizedPathFixed(this.str, rhs.str); 137 return newPath; 138 } 139 140 ///ditto 141 Path opBinary(string op)(string rhs) if(op=="~") 142 { 143 Path newPath; 144 newPath.str = buildNormalizedPathFixed(this.str, rhs); 145 return newPath; 146 } 147 148 ///ditto 149 Path opBinaryRight(string op)(string lhs) if(op=="~") 150 { 151 Path newPath; 152 newPath.str = buildNormalizedPathFixed(lhs, this.str); 153 return newPath; 154 } 155 156 /// Appends an extension to a path. Naturally, a directory separator 157 /// is NOT inserted in between. 158 Path opBinary(string op)(Ext rhs) if(op=="~") 159 { 160 Path newPath; 161 newPath.str = std.path.setExtension(this.str, rhs.str); 162 return newPath; 163 } 164 165 /// Appends a path to this one, with a directory separator in between. 166 Path opOpAssign(string op)(Path rhs) if(op=="~") 167 { 168 str = buildNormalizedPathFixed(str, rhs.str); 169 return this; 170 } 171 172 ///ditto 173 Path opOpAssign(string op)(string rhs) if(op=="~") 174 { 175 str = buildNormalizedPathFixed(str, rhs); 176 return this; 177 } 178 179 /// Appends an extension to this path. Naturally, a directory separator 180 /// is NOT inserted in between. 181 Path opOpAssign(string op)(Ext rhs) if(op=="~") 182 { 183 str = std.path.setExtension(str, rhs.str); 184 return this; 185 } 186 187 /// Compare using OS-specific case-sensitivity rules. If you want to force 188 /// case-sensitive or case-insensitive, then call filenameCmp instead. 189 int opCmp(ref const Path other) const 190 { 191 return std.path.filenameCmp(this.str, other.str); 192 } 193 194 ///ditto 195 int opCmp(Path other) const 196 { 197 return std.path.filenameCmp(this.str, other.str); 198 } 199 200 ///ditto 201 int opCmp(string other) const 202 { 203 return std.path.filenameCmp(this.str, other); 204 } 205 206 /// Compare using OS-specific case-sensitivity rules. If you want to force 207 /// case-sensitive or case-insensitive, then call filenameCmp instead. 208 int opEquals(ref const Path other) const 209 { 210 return opCmp(other) == 0; 211 } 212 213 ///ditto 214 int opEquals(Path other) const 215 { 216 return opCmp(other) == 0; 217 } 218 219 ///ditto 220 int opEquals(string other) const 221 { 222 return opCmp(other) == 0; 223 } 224 225 /// Convert to bool 226 T opCast(T)() if(is(T==bool)) 227 { 228 return !!str; 229 } 230 231 /// Returns the parent path, according to $(FULL_STD_PATH dirName). 232 @property Path up() 233 { 234 return this.dirName(); 235 } 236 237 /// Is this path equal to empty string? 238 @property bool empty() 239 { 240 return str == ""; 241 } 242 } 243 244 /// Convenience alias 245 alias extOf = extension; 246 alias stripExt = stripExtension; ///ditto 247 alias setExt = setExtension; ///ditto 248 alias defaultExt = defaultExtension; ///ditto 249 250 /// Like buildNormalizedPath, but if the result is the current directory, 251 /// this returns "." instead of "". However, if all the inputs are "", or there 252 /// are no inputs, this still returns "" just like buildNormalizedPath. 253 /// 254 /// Also, unlike buildNormalizedPath, this converts back/forward slashes to 255 /// native on BOTH Windows and Posix, not just on Windows. 256 string buildNormalizedPathFixed(string[] paths...) 257 @trusted pure nothrow 258 { 259 if(all!`a is null`(paths)) 260 return null; 261 262 if(all!`a==""`(paths)) 263 return ""; 264 265 auto result = std.path.buildNormalizedPath(paths); 266 267 version(Posix) result = result.replace(`\`, `/`); 268 else version(Windows) { /+ do nothing +/ } 269 else static assert(0); 270 271 return result==""? "." : result; 272 } 273 274 /// Properly escape arguments containing spaces for the command shell, if necessary. 275 /// 276 /// Although Path doesn't stricktly need this (since Path.toString automatically 277 /// calls this anyway), an overload of escapeShellArg which accepts a Path is 278 /// provided for the sake of generic code. 279 const(string) escapeShellArg(in string str) 280 { 281 if(str.canFind(' ')) 282 { 283 version(Windows) 284 return escapeWindowsArgument(str); 285 else version(Posix) 286 return escapeShellFileName(str); 287 else 288 static assert(0, "This platform not supported."); 289 } 290 else 291 return str; 292 } 293 294 ///ditto 295 string escapeShellArg(Path path) 296 { 297 return path.toString(); 298 } 299