1 module more.path; 2 3 import std.path; 4 5 version(unittest_path) { 6 import std.stdio; 7 import core.stdc.stdlib : alloca; 8 9 import more.common; 10 } 11 12 auto rtrimDirSeparators(inout(char)[] path) @safe pure nothrow @nogc 13 { 14 if(path.length <= 0) return path; 15 16 auto i = path.length - 1; 17 while(true) { 18 if(!isDirSeparator(path[i])) return path[0 .. i+1]; 19 if(i == 0) return path[0..0]; 20 i--; 21 } 22 } 23 24 /** 25 Returns the parent directory of the given file/directory. 26 If there is no parent directory, it will return an empty string. 27 This function will include the trailing slash only if it is the root directory. 28 Note: This function does not allocate a new string, instead it will 29 return a slice to the given path. 30 */ 31 inout(char)[] parentDir(inout(char)[] path) @safe pure nothrow @nogc 32 { 33 return path[0..parentDirLength(path)]; 34 } 35 /** 36 Returns the length of the substring that is the parent directory 37 of the given path. If there is no parent directory, it will return 0. 38 This function will include the trailing slash only if it is the root directory. 39 */ 40 size_t parentDirLength(inout(char)[] path) @safe pure nothrow @nogc 41 { 42 path = rtrimDirSeparators(path); 43 if (path.length <= 0) return 0; 44 45 // i is pointing at a nonDirSeparator 46 size_t i; 47 for(i = path.length - 1; !isDirSeparator(path[i]); i--) { 48 if(i == 0) return 0; 49 } 50 51 // i is pointing at a dirSeparator, Remove the trailing dir separators 52 while(true) { 53 if(i == 0) return 1; // Handles '/' or '\' 54 i--; 55 if(!isDirSeparator(path[i])) break; 56 } 57 58 // i+1 is pointing at a dir separator and i is pointing at a nonDirSeparator 59 if(path[i] == ':') return i+2; 60 return i+1; 61 } 62 63 version(unittest_path) unittest 64 { 65 mixin(scopedTest!("parentDir function")); 66 67 assert(parentDir(null) == null); 68 assert(parentDir("") == ""); 69 assert(parentDir("/") == ""); 70 assert(parentDir("///") == ""); 71 72 assert(parentDir("a") == ""); 73 assert(parentDir("abc") == ""); 74 assert(parentDir("/a") == "/"); 75 assert(parentDir("///a") == "/"); 76 assert(parentDir("/abc") == "/"); 77 assert(parentDir("///abc") == "/"); 78 79 assert(parentDir("dir/") == ""); 80 assert(parentDir("dir///") == ""); 81 assert(parentDir("dir/a") == "dir"); 82 assert(parentDir("dir///a") == "dir"); 83 assert(parentDir("dir/abc") == "dir"); 84 assert(parentDir("dir///abc") == "dir"); 85 86 version (Windows) { 87 assert(parentDir(`c:`) == ""); 88 89 assert(parentDir(`c:\`) == ""); 90 assert(parentDir(`c:/`) == ""); 91 92 assert(parentDir(`c:\\\`) == ""); 93 assert(parentDir(`c:///`) == ""); 94 95 assert(parentDir(`c:\a`) == `c:\`); 96 assert(parentDir(`c:/a`) == `c:/`); 97 98 assert(parentDir(`c:\\\a`) == `c:\`); 99 assert(parentDir(`c:///a`) == `c:/`); 100 101 assert(parentDir(`c:\abc`) == `c:\`); 102 assert(parentDir(`c:/abc`) == `c:/`); 103 104 assert(parentDir(`c:\\\abc`) == `c:\`); 105 assert(parentDir(`c:///abc`) == `c:/`); 106 } 107 } 108 109 struct ParentDirTraverser 110 { 111 string path; 112 @property empty() nothrow @nogc { 113 return path.length <= 0; 114 } 115 @property string front() nothrow @nogc { 116 return path; 117 } 118 @property popFront() nothrow @nogc { 119 path = parentDir(path); 120 } 121 } 122 123 version(unittest_path) unittest 124 { 125 mixin(scopedTest!("ParentDirTraverser")); 126 127 ParentDirTraverser traverser; 128 129 traverser = ParentDirTraverser(""); 130 assert(traverser.empty); 131 132 traverser = ParentDirTraverser("a"); 133 assert(!traverser.empty); 134 assert(traverser.front == "a"); 135 traverser.popFront; 136 assert(traverser.empty); 137 138 traverser = ParentDirTraverser("/a"); 139 assert(!traverser.empty); 140 assert(traverser.front == "/a"); 141 traverser.popFront; 142 assert(!traverser.empty); 143 assert(traverser.front == "/"); 144 traverser.popFront; 145 assert(traverser.empty); 146 147 traverser = ParentDirTraverser("/parent/child"); 148 assert(!traverser.empty); 149 assert(traverser.front == "/parent/child"); 150 traverser.popFront; 151 assert(!traverser.empty); 152 assert(traverser.front == "/parent"); 153 traverser.popFront; 154 assert(!traverser.empty); 155 assert(traverser.front == "/"); 156 traverser.popFront; 157 assert(traverser.empty); 158 159 traverser = ParentDirTraverser("Z:/parent/child/grandchild"); 160 assert(!traverser.empty); 161 assert(traverser.front == "Z:/parent/child/grandchild"); 162 traverser.popFront; 163 assert(!traverser.empty); 164 assert(traverser.front == "Z:/parent/child"); 165 traverser.popFront; 166 assert(!traverser.empty); 167 assert(traverser.front == "Z:/parent"); 168 traverser.popFront; 169 assert(!traverser.empty); 170 assert(traverser.front == "Z:/"); 171 traverser.popFront; 172 assert(traverser.empty); 173 } 174 175 176 char[] normalizePath(bool useSlashes)(char[] path) @safe pure nothrow @nogc 177 { 178 return path[0..normalizePathLength!(useSlashes)(path)]; 179 } 180 /** 181 Normalizes the given path using the following: 182 1. removes duplicate slashes/backslashes 183 2. removes '/./' strings 184 3. replaces 'path/..' strings with 'path' 185 4. replaces all slashes/backslashes with the dirSeparator 186 5. removed trailing slashes if not a root directory 187 This function modifies the given string 188 Returns: The length of the normalized path 189 */ 190 size_t normalizePathLength(bool useSlashes)(char[] path) @safe pure nothrow @nogc 191 { 192 enum dirSeparator = useSlashes ? '/' : '\\'; 193 enum otherDirSeparator = useSlashes ? '\\' : '/'; 194 195 // Normalize Dir Separators 196 for(auto i = 0; i < path.length; i++) { 197 auto c = path[i]; 198 if(c == otherDirSeparator) { 199 path[i] = dirSeparator; 200 } 201 } 202 203 return path.length; 204 205 // size_t i = 0; 206 207 /+ 208 while(true) { 209 auto c = path[i]; 210 211 if(c == dirSeparator) { 212 213 } 214 215 } 216 +/ 217 } 218 219 220 version(unittest_path) unittest 221 { 222 mixin(scopedTest!("normalizePath function")); 223 224 void testNormalizePath(bool useSlashes)(string testString, string expected, size_t testLine = __LINE__) 225 { 226 auto normalized = cast(char*)alloca(testString.length); 227 normalized[0..testString.length] = testString; 228 229 auto actual = normalizePath!useSlashes(normalized[0..testString.length]); 230 if(actual != expected) { 231 writefln("Expected: '%s'", expected); 232 writefln("Actual : '%s'", actual); 233 assert(0); 234 } 235 } 236 237 238 testNormalizePath!true("/", "/"); 239 testNormalizePath!true(`\`, "/"); 240 241 testNormalizePath!false("/", `\`); 242 testNormalizePath!false(`\`, `\`); 243 244 245 246 } 247 248 /+ 249 unittest 250 { 251 assert (normalizePath("") is null); 252 assert (normalizePath("foo") == "foo"); 253 254 version (Posix) 255 { 256 assert (normalizePath("/", "foo", "bar") == "/foo/bar"); 257 assert (normalizePath("foo", "bar", "baz") == "foo/bar/baz"); 258 assert (normalizePath("foo", "bar/baz") == "foo/bar/baz"); 259 assert (normalizePath("foo", "bar//baz///") == "foo/bar/baz"); 260 assert (normalizePath("/foo", "bar/baz") == "/foo/bar/baz"); 261 assert (normalizePath("/foo", "/bar/baz") == "/bar/baz"); 262 assert (normalizePath("/foo/..", "/bar/./baz") == "/bar/baz"); 263 assert (normalizePath("/foo/..", "bar/baz") == "/bar/baz"); 264 assert (normalizePath("/foo/../../", "bar/baz") == "/bar/baz"); 265 assert (normalizePath("/foo/bar", "../baz") == "/foo/baz"); 266 assert (normalizePath("/foo/bar", "../../baz") == "/baz"); 267 assert (normalizePath("/foo/bar", ".././/baz/..", "wee/") == "/foo/wee"); 268 assert (normalizePath("//foo/bar", "baz///wee") == "/foo/bar/baz/wee"); 269 static assert (normalizePath("/foo/..", "/bar/./baz") == "/bar/baz"); 270 // Examples in docs: 271 assert (normalizePath("/foo", "bar/baz/") == "/foo/bar/baz"); 272 assert (normalizePath("/foo", "/bar/..", "baz") == "/baz"); 273 assert (normalizePath("foo/./bar", "../../", "../baz") == "../baz"); 274 assert (normalizePath("/foo/./bar", "../../baz") == "/baz"); 275 } 276 else version (Windows) 277 { 278 assert (normalizePath(`\`, `foo`, `bar`) == `\foo\bar`); 279 assert (normalizePath(`foo`, `bar`, `baz`) == `foo\bar\baz`); 280 assert (normalizePath(`foo`, `bar\baz`) == `foo\bar\baz`); 281 assert (normalizePath(`foo`, `bar\\baz\\\`) == `foo\bar\baz`); 282 assert (normalizePath(`\foo`, `bar\baz`) == `\foo\bar\baz`); 283 assert (normalizePath(`\foo`, `\bar\baz`) == `\bar\baz`); 284 assert (normalizePath(`\foo\..`, `\bar\.\baz`) == `\bar\baz`); 285 assert (normalizePath(`\foo\..`, `bar\baz`) == `\bar\baz`); 286 assert (normalizePath(`\foo\..\..\`, `bar\baz`) == `\bar\baz`); 287 assert (normalizePath(`\foo\bar`, `..\baz`) == `\foo\baz`); 288 assert (normalizePath(`\foo\bar`, `../../baz`) == `\baz`); 289 assert (normalizePath(`\foo\bar`, `..\.\/baz\..`, `wee\`) == `\foo\wee`); 290 291 assert (normalizePath(`c:\`, `foo`, `bar`) == `c:\foo\bar`); 292 assert (normalizePath(`c:foo`, `bar`, `baz`) == `c:foo\bar\baz`); 293 assert (normalizePath(`c:foo`, `bar\baz`) == `c:foo\bar\baz`); 294 assert (normalizePath(`c:foo`, `bar\\baz\\\`) == `c:foo\bar\baz`); 295 assert (normalizePath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`); 296 assert (normalizePath(`c:\foo`, `\bar\baz`) == `c:\bar\baz`); 297 assert (normalizePath(`c:\foo\..`, `\bar\.\baz`) == `c:\bar\baz`); 298 assert (normalizePath(`c:\foo\..`, `bar\baz`) == `c:\bar\baz`); 299 assert (normalizePath(`c:\foo\..\..\`, `bar\baz`) == `c:\bar\baz`); 300 assert (normalizePath(`c:\foo\bar`, `..\baz`) == `c:\foo\baz`); 301 assert (normalizePath(`c:\foo\bar`, `..\..\baz`) == `c:\baz`); 302 assert (normalizePath(`c:\foo\bar`, `..\.\\baz\..`, `wee\`) == `c:\foo\wee`); 303 304 assert (normalizePath(`\\server\share`, `foo`, `bar`) == `\\server\share\foo\bar`); 305 assert (normalizePath(`\\server\share\`, `foo`, `bar`) == `\\server\share\foo\bar`); 306 assert (normalizePath(`\\server\share\foo`, `bar\baz`) == `\\server\share\foo\bar\baz`); 307 assert (normalizePath(`\\server\share\foo`, `\bar\baz`) == `\\server\share\bar\baz`); 308 assert (normalizePath(`\\server\share\foo\..`, `\bar\.\baz`) == `\\server\share\bar\baz`); 309 assert (normalizePath(`\\server\share\foo\..`, `bar\baz`) == `\\server\share\bar\baz`); 310 assert (normalizePath(`\\server\share\foo\..\..\`, `bar\baz`) == `\\server\share\bar\baz`); 311 assert (normalizePath(`\\server\share\foo\bar`, `..\baz`) == `\\server\share\foo\baz`); 312 assert (normalizePath(`\\server\share\foo\bar`, `..\..\baz`) == `\\server\share\baz`); 313 assert (normalizePath(`\\server\share\foo\bar`, `..\.\\baz\..`, `wee\`) == `\\server\share\foo\wee`); 314 315 static assert (normalizePath(`\foo\..\..\`, `bar\baz`) == `\bar\baz`); 316 317 // Examples in docs: 318 assert (normalizePath(`c:\foo`, `bar\baz\`) == `c:\foo\bar\baz`); 319 assert (normalizePath(`c:\foo`, `bar/..`) == `c:\foo`); 320 assert (normalizePath(`\\server\share\foo`, `..\bar`) == `\\server\share\bar`); 321 } 322 else static assert (0); 323 } 324 325 unittest 326 { 327 version (Posix) 328 { 329 // Trivial 330 assert (normalizePath("").empty); 331 assert (normalizePath("foo/bar") == "foo/bar"); 332 333 // Correct handling of leading slashes 334 assert (normalizePath("/") == "/"); 335 assert (normalizePath("///") == "/"); 336 assert (normalizePath("////") == "/"); 337 assert (normalizePath("/foo/bar") == "/foo/bar"); 338 assert (normalizePath("//foo/bar") == "/foo/bar"); 339 assert (normalizePath("///foo/bar") == "/foo/bar"); 340 assert (normalizePath("////foo/bar") == "/foo/bar"); 341 342 // Correct handling of single-dot symbol (current directory) 343 assert (normalizePath("/./foo") == "/foo"); 344 assert (normalizePath("/foo/./bar") == "/foo/bar"); 345 346 assert (normalizePath("./foo") == "foo"); 347 assert (normalizePath("././foo") == "foo"); 348 assert (normalizePath("foo/././bar") == "foo/bar"); 349 350 // Correct handling of double-dot symbol (previous directory) 351 assert (normalizePath("/foo/../bar") == "/bar"); 352 assert (normalizePath("/foo/../../bar") == "/bar"); 353 assert (normalizePath("/../foo") == "/foo"); 354 assert (normalizePath("/../../foo") == "/foo"); 355 assert (normalizePath("/foo/..") == "/"); 356 assert (normalizePath("/foo/../..") == "/"); 357 358 assert (normalizePath("foo/../bar") == "bar"); 359 assert (normalizePath("foo/../../bar") == "../bar"); 360 assert (normalizePath("../foo") == "../foo"); 361 assert (normalizePath("../../foo") == "../../foo"); 362 assert (normalizePath("../foo/../bar") == "../bar"); 363 assert (normalizePath(".././../foo") == "../../foo"); 364 assert (normalizePath("foo/bar/..") == "foo"); 365 assert (normalizePath("/foo/../..") == "/"); 366 367 // The ultimate path 368 assert (normalizePath("/foo/../bar//./../...///baz//") == "/.../baz"); 369 static assert (normalizePath("/foo/../bar//./../...///baz//") == "/.../baz"); 370 } 371 else version (Windows) 372 { 373 // Trivial 374 assert (normalizePath("").empty); 375 assert (normalizePath(`foo\bar`) == `foo\bar`); 376 assert (normalizePath("foo/bar") == `foo\bar`); 377 378 // Correct handling of absolute paths 379 assert (normalizePath("/") == `\`); 380 assert (normalizePath(`\`) == `\`); 381 assert (normalizePath(`\\\`) == `\`); 382 assert (normalizePath(`\\\\`) == `\`); 383 assert (normalizePath(`\foo\bar`) == `\foo\bar`); 384 assert (normalizePath(`\\foo`) == `\\foo`); 385 assert (normalizePath(`\\foo\\`) == `\\foo`); 386 assert (normalizePath(`\\foo/bar`) == `\\foo\bar`); 387 assert (normalizePath(`\\\foo\bar`) == `\foo\bar`); 388 assert (normalizePath(`\\\\foo\bar`) == `\foo\bar`); 389 assert (normalizePath(`c:\`) == `c:\`); 390 assert (normalizePath(`c:\foo\bar`) == `c:\foo\bar`); 391 assert (normalizePath(`c:\\foo\bar`) == `c:\foo\bar`); 392 393 // Correct handling of single-dot symbol (current directory) 394 assert (normalizePath(`\./foo`) == `\foo`); 395 assert (normalizePath(`\foo/.\bar`) == `\foo\bar`); 396 397 assert (normalizePath(`.\foo`) == `foo`); 398 assert (normalizePath(`./.\foo`) == `foo`); 399 assert (normalizePath(`foo\.\./bar`) == `foo\bar`); 400 401 // Correct handling of double-dot symbol (previous directory) 402 assert (normalizePath(`\foo\..\bar`) == `\bar`); 403 assert (normalizePath(`\foo\../..\bar`) == `\bar`); 404 assert (normalizePath(`\..\foo`) == `\foo`); 405 assert (normalizePath(`\..\..\foo`) == `\foo`); 406 assert (normalizePath(`\foo\..`) == `\`); 407 assert (normalizePath(`\foo\../..`) == `\`); 408 409 assert (normalizePath(`foo\..\bar`) == `bar`); 410 assert (normalizePath(`foo\..\../bar`) == `..\bar`); 411 assert (normalizePath(`..\foo`) == `..\foo`); 412 assert (normalizePath(`..\..\foo`) == `..\..\foo`); 413 assert (normalizePath(`..\foo\..\bar`) == `..\bar`); 414 assert (normalizePath(`..\.\..\foo`) == `..\..\foo`); 415 assert (normalizePath(`foo\bar\..`) == `foo`); 416 assert (normalizePath(`\foo\..\..`) == `\`); 417 assert (normalizePath(`c:\foo\..\..`) == `c:\`); 418 419 // Correct handling of non-root path with drive specifier 420 assert (normalizePath(`c:foo`) == `c:foo`); 421 assert (normalizePath(`c:..\foo\.\..\bar`) == `c:..\bar`); 422 423 // The ultimate path 424 assert (normalizePath(`c:\foo\..\bar\\.\..\...\\\baz\\`) == `c:\...\baz`); 425 static assert (normalizePath(`c:\foo\..\bar\\.\..\...\\\baz\\`) == `c:\...\baz`); 426 } 427 else static assert (false); 428 } 429 430 unittest 431 { 432 // Test for issue 7397 433 string[] ary = ["a", "b"]; 434 version (Posix) 435 { 436 assert (normalizePath(ary) == "a/b"); 437 } 438 else version (Windows) 439 { 440 assert (normalizePath(ary) == `a\b`); 441 } 442 } 443 +/ 444 445 446 447 448 449 450 451 /+ 452 453 void appendPath(char* output, const(char[]) path) 454 { 455 *output = dirSeparator[0]; 456 output[1..1+path.length] = path; 457 } 458 void buildPath(char* output, string[] segments...) 459 { 460 if (segments.empty) return null; 461 462 size_t first; 463 foreach(i, segment; segments) { 464 if(!segment.empty) { 465 first = i; 466 goto BUILD; 467 } 468 } 469 470 return; 471 472 BUILD: 473 auto firstSegment = segments[first]; 474 output[0..firstSegment.length] = firstSegment; 475 size_t pos = firstSegment.length;; 476 foreach (segment; segments[first+1..$]) { 477 if (segment.empty) continue; 478 /+ 479 if (isRooted(segment)) { 480 version (Posix) { 481 pos = 0; 482 } else version (Windows) { 483 if (isAbsolute(segment)) { 484 pos = 0; 485 } else { 486 pos = rootName(buf[0 .. pos]).length; 487 if (pos > 0 && isDirSeparator(buf[pos-1])) --pos; 488 } 489 } 490 } 491 +/ 492 if (!isDirSeparator(output[pos-1])) { 493 output[pos++] = dirSeparator[0]; 494 } 495 output[pos .. pos + segment.length] = segment[]; 496 pos += segment.length; 497 } 498 } 499 +/ 500