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