1 /++ 2 $(H2 Scriptlike $(SCRIPTLIKE_VERSION)) 3 4 Extra Scriptlike-only functionality to complement $(MODULE_STD_PATH). 5 6 Copyright: Copyright (C) 2014-2015 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, osDefaultCaseSensitivity, 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 = null) 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 string toRawString() pure @safe nothrow 49 { 50 return str; 51 } 52 53 /// Compare using OS-specific case-sensitivity rules. If you want to force 54 /// case-sensitive or case-insensitive, then call filenameCmp instead. 55 int opCmp(ref const Ext other) const 56 { 57 return std.path.filenameCmp(this.str, other.str); 58 } 59 60 ///ditto 61 int opCmp(Ext other) const 62 { 63 return std.path.filenameCmp(this.str, other.str); 64 } 65 66 ///ditto 67 int opCmp(string other) const 68 { 69 return std.path.filenameCmp(this.str, other); 70 } 71 72 /// Compare using OS-specific case-sensitivity rules. If you want to force 73 /// case-sensitive or case-insensitive, then call filenameCmp instead. 74 int opEquals(ref const Ext other) const 75 { 76 return opCmp(other) == 0; 77 } 78 79 ///ditto 80 int opEquals(Ext other) const 81 { 82 return opCmp(other) == 0; 83 } 84 85 ///ditto 86 int opEquals(string other) const 87 { 88 return opCmp(other) == 0; 89 } 90 91 /// Convert to bool 92 T opCast(T)() if(is(T==bool)) 93 { 94 return !!str; 95 } 96 } 97 98 /// Represents a filesystem path. The path is always kept normalized 99 /// automatically (as performed by buildNormalizedPathFixed). 100 struct Path 101 { 102 private string str = "."; 103 104 /// Main constructor. 105 this(string path = ".") pure @safe nothrow 106 { 107 this.str = buildNormalizedPathFixed(path); 108 } 109 110 pure @trusted nothrow invariant() 111 { 112 assert(str == buildNormalizedPathFixed(str)); 113 } 114 115 /// Convert to string, quoting or escaping spaces if necessary. 116 string toString() 117 { 118 return escapeShellArg(str); 119 } 120 121 /// Returns the underlying string. Does NOT do any escaping, even if path contains spaces. 122 string toRawString() const pure @safe nothrow 123 { 124 return str; 125 } 126 127 /// Concatenates two paths, with a directory separator in between. 128 Path opBinary(string op)(Path rhs) if(op=="~") 129 { 130 Path newPath; 131 newPath.str = buildNormalizedPathFixed(this.str, rhs.str); 132 return newPath; 133 } 134 135 ///ditto 136 Path opBinary(string op)(string rhs) if(op=="~") 137 { 138 Path newPath; 139 newPath.str = buildNormalizedPathFixed(this.str, rhs); 140 return newPath; 141 } 142 143 ///ditto 144 Path opBinaryRight(string op)(string lhs) if(op=="~") 145 { 146 Path newPath; 147 newPath.str = buildNormalizedPathFixed(lhs, this.str); 148 return newPath; 149 } 150 151 /// Appends an extension to a path. Naturally, a directory separator 152 /// is NOT inserted in between. 153 Path opBinary(string op)(Ext rhs) if(op=="~") 154 { 155 Path newPath; 156 newPath.str = std.path.setExtension(this.str, rhs.str); 157 return newPath; 158 } 159 160 /// Appends a path to this one, with a directory separator in between. 161 Path opOpAssign(string op)(Path rhs) if(op=="~") 162 { 163 str = buildNormalizedPathFixed(str, rhs.str); 164 return this; 165 } 166 167 ///ditto 168 Path opOpAssign(string op)(string rhs) if(op=="~") 169 { 170 str = buildNormalizedPathFixed(str, rhs); 171 return this; 172 } 173 174 /// Appends an extension to this path. Naturally, a directory separator 175 /// is NOT inserted in between. 176 Path opOpAssign(string op)(Ext rhs) if(op=="~") 177 { 178 str = std.path.setExtension(str, rhs.str); 179 return this; 180 } 181 182 /// Compare using OS-specific case-sensitivity rules. If you want to force 183 /// case-sensitive or case-insensitive, then call filenameCmp instead. 184 int opCmp(ref const Path other) const 185 { 186 return std.path.filenameCmp(this.str, other.str); 187 } 188 189 ///ditto 190 int opCmp(Path other) const 191 { 192 return std.path.filenameCmp(this.str, other.str); 193 } 194 195 ///ditto 196 int opCmp(string other) const 197 { 198 return std.path.filenameCmp(this.str, other); 199 } 200 201 /// Compare using OS-specific case-sensitivity rules. If you want to force 202 /// case-sensitive or case-insensitive, then call filenameCmp instead. 203 int opEquals(ref const Path other) const 204 { 205 return opCmp(other) == 0; 206 } 207 208 ///ditto 209 int opEquals(Path other) const 210 { 211 return opCmp(other) == 0; 212 } 213 214 ///ditto 215 int opEquals(string other) const 216 { 217 return opCmp(other) == 0; 218 } 219 220 /// Convert to bool 221 T opCast(T)() if(is(T==bool)) 222 { 223 return !!str; 224 } 225 226 /// Returns the parent path, according to $(FULL_STD_PATH dirName). 227 @property Path up() 228 { 229 return this.dirName(); 230 } 231 232 /// Is this path equal to empty string? 233 @property bool empty() 234 { 235 return str == ""; 236 } 237 } 238 239 /// Convenience alias 240 alias extOf = extension; 241 alias stripExt = stripExtension; ///ditto 242 alias setExt = setExtension; ///ditto 243 alias defaultExt = defaultExtension; ///ditto 244 245 /// Like buildNormalizedPath, but if the result is the current directory, 246 /// this returns "." instead of "". However, if all the inputs are "", or there 247 /// are no inputs, this still returns "" just like buildNormalizedPath. 248 string buildNormalizedPathFixed(string[] paths...) 249 @trusted pure nothrow 250 { 251 if(all!`a is null`(paths)) 252 return null; 253 254 if(all!`a==""`(paths)) 255 return ""; 256 257 auto result = std.path.buildNormalizedPath(paths); 258 return result==""? "." : result; 259 } 260 261 /// Properly escape arguments containing spaces for the command shell, if necessary. 262 const(string) escapeShellArg(in string str) 263 { 264 if(str.canFind(' ')) 265 { 266 version(Windows) 267 return escapeWindowsArgument(str); 268 else version(Posix) 269 return escapeShellFileName(str); 270 else 271 static assert(0, "This platform not supported."); 272 } 273 else 274 return str; 275 }