/std/path.d

http://github.com/jcd/phobos · D · 2926 lines · 1889 code · 298 blank · 739 comment · 728 complexity · c3e22674ee5c1e4ccc9289b6acb496e0 MD5 · raw file

Large files are truncated click here to view the full file

  1. // Written in the D programming language.
  2. /** This module is used to manipulate _path strings.
  3. All functions, with the exception of $(LREF expandTilde) (and in some
  4. cases $(LREF absolutePath) and $(LREF relativePath)), are pure
  5. string manipulation functions; they don't depend on any state outside
  6. the program, nor do they perform any actual file system actions.
  7. This has the consequence that the module does not make any distinction
  8. between a _path that points to a directory and a _path that points to a
  9. file, and it does not know whether or not the object pointed to by the
  10. _path actually exists in the file system.
  11. To differentiate between these cases, use $(XREF file,isDir) and
  12. $(XREF file,exists).
  13. Note that on Windows, both the backslash ($(D `\`)) and the slash ($(D `/`))
  14. are in principle valid directory separators. This module treats them
  15. both on equal footing, but in cases where a $(I new) separator is
  16. added, a backslash will be used. Furthermore, the $(LREF buildNormalizedPath)
  17. function will replace all slashes with backslashes on that platform.
  18. In general, the functions in this module assume that the input paths
  19. are well-formed. (That is, they should not contain invalid characters,
  20. they should follow the file system's _path format, etc.) The result
  21. of calling a function on an ill-formed _path is undefined. When there
  22. is a chance that a _path or a file name is invalid (for instance, when it
  23. has been input by the user), it may sometimes be desirable to use the
  24. $(LREF isValidFilename) and $(LREF isValidPath) functions to check
  25. this.
  26. Most functions do not perform any memory allocations, and if a string is
  27. returned, it is usually a slice of an input string. If a function
  28. allocates, this is explicitly mentioned in the documentation.
  29. Authors:
  30. Lars Tandle Kyllingstad,
  31. $(WEB digitalmars.com, Walter Bright),
  32. Grzegorz Adam Hankiewicz,
  33. Thomas Kühne,
  34. $(WEB erdani.org, Andrei Alexandrescu)
  35. Copyright:
  36. Copyright (c) 2000–2011, the authors. All rights reserved.
  37. License:
  38. $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0)
  39. Source:
  40. $(PHOBOSSRC std/_path.d)
  41. Macros:
  42. WIKI = Phobos/StdPath
  43. */
  44. module std.path;
  45. import std.algorithm;
  46. import std.array;
  47. import std.conv;
  48. import std.file: getcwd;
  49. import std.range;
  50. import std.string;
  51. import std.traits;
  52. version(Posix)
  53. {
  54. import core.exception;
  55. import core.stdc.errno;
  56. import core.sys.posix.pwd;
  57. import core.sys.posix.stdlib;
  58. private import core.exception : onOutOfMemoryError;
  59. }
  60. /** String used to separate directory names in a path. Under
  61. POSIX this is a slash, under Windows a backslash.
  62. */
  63. version(Posix) enum string dirSeparator = "/";
  64. else version(Windows) enum string dirSeparator = "\\";
  65. else static assert (0, "unsupported platform");
  66. /** Path separator string. A colon under POSIX, a semicolon
  67. under Windows.
  68. */
  69. version(Posix) enum string pathSeparator = ":";
  70. else version(Windows) enum string pathSeparator = ";";
  71. else static assert (0, "unsupported platform");
  72. /** Determines whether the given character is a directory separator.
  73. On Windows, this includes both $(D `\`) and $(D `/`).
  74. On POSIX, it's just $(D `/`).
  75. */
  76. bool isDirSeparator(dchar c) @safe pure nothrow
  77. {
  78. if (c == '/') return true;
  79. version(Windows) if (c == '\\') return true;
  80. return false;
  81. }
  82. /* Determines whether the given character is a drive separator.
  83. On Windows, this is true if c is the ':' character that separates
  84. the drive letter from the rest of the path. On POSIX, this always
  85. returns false.
  86. */
  87. private bool isDriveSeparator(dchar c) @safe pure nothrow
  88. {
  89. version(Windows) return c == ':';
  90. else return false;
  91. }
  92. /* Combines the isDirSeparator and isDriveSeparator tests. */
  93. version(Windows) private bool isSeparator(dchar c) @safe pure nothrow
  94. {
  95. return isDirSeparator(c) || isDriveSeparator(c);
  96. }
  97. version(Posix) private alias isDirSeparator isSeparator;
  98. /* Helper function that determines the position of the last
  99. drive/directory separator in a string. Returns -1 if none
  100. is found.
  101. */
  102. private ptrdiff_t lastSeparator(C)(in C[] path) @safe pure nothrow
  103. if (isSomeChar!C)
  104. {
  105. auto i = (cast(ptrdiff_t) path.length) - 1;
  106. while (i >= 0 && !isSeparator(path[i])) --i;
  107. return i;
  108. }
  109. version (Windows)
  110. {
  111. private bool isUNC(C)(in C[] path) @safe pure nothrow if (isSomeChar!C)
  112. {
  113. return path.length >= 3 && isDirSeparator(path[0]) && isDirSeparator(path[1])
  114. && !isDirSeparator(path[2]);
  115. }
  116. private ptrdiff_t uncRootLength(C)(in C[] path) @safe pure nothrow if (isSomeChar!C)
  117. in { assert (isUNC(path)); }
  118. body
  119. {
  120. ptrdiff_t i = 3;
  121. while (i < path.length && !isDirSeparator(path[i])) ++i;
  122. if (i < path.length)
  123. {
  124. auto j = i;
  125. do { ++j; } while (j < path.length && isDirSeparator(path[j]));
  126. if (j < path.length)
  127. {
  128. do { ++j; } while (j < path.length && !isDirSeparator(path[j]));
  129. i = j;
  130. }
  131. }
  132. return i;
  133. }
  134. private bool hasDrive(C)(in C[] path) @safe pure nothrow if (isSomeChar!C)
  135. {
  136. return path.length >= 2 && isDriveSeparator(path[1]);
  137. }
  138. private bool isDriveRoot(C)(in C[] path) @safe pure nothrow if (isSomeChar!C)
  139. {
  140. return path.length >= 3 && isDriveSeparator(path[1])
  141. && isDirSeparator(path[2]);
  142. }
  143. }
  144. /* Helper functions that strip leading/trailing slashes and backslashes
  145. from a path.
  146. */
  147. private inout(C)[] ltrimDirSeparators(C)(inout(C)[] path) @safe pure nothrow
  148. if (isSomeChar!C)
  149. {
  150. int i = 0;
  151. while (i < path.length && isDirSeparator(path[i])) ++i;
  152. return path[i .. $];
  153. }
  154. private inout(C)[] rtrimDirSeparators(C)(inout(C)[] path) @safe pure nothrow
  155. if (isSomeChar!C)
  156. {
  157. auto i = (cast(ptrdiff_t) path.length) - 1;
  158. while (i >= 0 && isDirSeparator(path[i])) --i;
  159. return path[0 .. i+1];
  160. }
  161. private inout(C)[] trimDirSeparators(C)(inout(C)[] path) @safe pure nothrow
  162. if (isSomeChar!C)
  163. {
  164. return ltrimDirSeparators(rtrimDirSeparators(path));
  165. }
  166. /** This $(D enum) is used as a template argument to functions which
  167. compare file names, and determines whether the comparison is
  168. case sensitive or not.
  169. */
  170. enum CaseSensitive : bool
  171. {
  172. /// File names are case insensitive
  173. no = false,
  174. /// File names are case sensitive
  175. yes = true,
  176. /** The default (or most common) setting for the current platform.
  177. That is, $(D no) on Windows and Mac OS X, and $(D yes) on all
  178. POSIX systems except OS X (Linux, *BSD, etc.).
  179. */
  180. osDefault = osDefaultCaseSensitivity
  181. }
  182. version (Windows) private enum osDefaultCaseSensitivity = false;
  183. else version (OSX) private enum osDefaultCaseSensitivity = false;
  184. else version (Posix) private enum osDefaultCaseSensitivity = true;
  185. else static assert (0);
  186. /** Returns the name of a file, without any leading directory
  187. and with an optional suffix chopped off.
  188. If $(D suffix) is specified, it will be compared to $(D path)
  189. using $(D filenameCmp!cs),
  190. where $(D cs) is an optional template parameter determining whether
  191. the comparison is case sensitive or not. See the
  192. $(LREF filenameCmp) documentation for details.
  193. Examples:
  194. ---
  195. assert (baseName("dir/file.ext") == "file.ext");
  196. assert (baseName("dir/file.ext", ".ext") == "file");
  197. assert (baseName("dir/file.ext", ".xyz") == "file.ext");
  198. assert (baseName("dir/filename", "name") == "file");
  199. assert (baseName("dir/subdir/") == "subdir");
  200. version (Windows)
  201. {
  202. assert (baseName(`d:file.ext`) == "file.ext");
  203. assert (baseName(`d:\dir\file.ext`) == "file.ext");
  204. }
  205. ---
  206. Note:
  207. This function $(I only) strips away the specified suffix, which
  208. doesn't necessarily have to represent an extension. If you want
  209. to remove the extension from a path, regardless of what the extension
  210. is, use $(LREF stripExtension).
  211. If you want the filename without leading directories and without
  212. an extension, combine the functions like this:
  213. ---
  214. assert (baseName(stripExtension("dir/file.ext")) == "file");
  215. ---
  216. Standards:
  217. This function complies with
  218. $(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/basename.html,
  219. the POSIX requirements for the 'basename' shell utility)
  220. (with suitable adaptations for Windows paths).
  221. */
  222. inout(C)[] baseName(C)(inout(C)[] path)
  223. @trusted pure //TODO: nothrow (BUG 5700)
  224. if (isSomeChar!C)
  225. {
  226. auto p1 = stripDrive(path);
  227. if (p1.empty)
  228. {
  229. version (Windows) if (isUNC(path))
  230. {
  231. return cast(typeof(return)) dirSeparator.dup;
  232. }
  233. return null;
  234. }
  235. auto p2 = rtrimDirSeparators(p1);
  236. if (p2.empty) return p1[0 .. 1];
  237. return p2[lastSeparator(p2)+1 .. $];
  238. }
  239. /// ditto
  240. inout(C)[] baseName(CaseSensitive cs = CaseSensitive.osDefault, C, C1)
  241. (inout(C)[] path, in C1[] suffix)
  242. @safe pure //TODO: nothrow (because of filenameCmp())
  243. if (isSomeChar!C && isSomeChar!C1)
  244. {
  245. auto p = baseName(path);
  246. if (p.length > suffix.length
  247. && filenameCmp!cs(p[$-suffix.length .. $], suffix) == 0)
  248. {
  249. return p[0 .. $-suffix.length];
  250. }
  251. else return p;
  252. }
  253. unittest
  254. {
  255. assert (baseName("").empty);
  256. assert (baseName("file.ext"w) == "file.ext");
  257. assert (baseName("file.ext"d, ".ext") == "file");
  258. assert (baseName("file", "file"w.dup) == "file");
  259. assert (baseName("dir/file.ext"d.dup) == "file.ext");
  260. assert (baseName("dir/file.ext", ".ext"d) == "file");
  261. assert (baseName("dir/file"w, "file"d) == "file");
  262. assert (baseName("dir///subdir////") == "subdir");
  263. assert (baseName("dir/subdir.ext/", ".ext") == "subdir");
  264. assert (baseName("dir/subdir/".dup, "subdir") == "subdir");
  265. assert (baseName("/"w.dup) == "/");
  266. assert (baseName("//"d.dup) == "/");
  267. assert (baseName("///") == "/");
  268. assert (baseName!(CaseSensitive.yes)("file.ext", ".EXT") == "file.ext");
  269. assert (baseName!(CaseSensitive.no)("file.ext", ".EXT") == "file");
  270. version (Windows)
  271. {
  272. assert (baseName(`dir\file.ext`) == `file.ext`);
  273. assert (baseName(`dir\file.ext`, `.ext`) == `file`);
  274. assert (baseName(`dir\file`, `file`) == `file`);
  275. assert (baseName(`d:file.ext`) == `file.ext`);
  276. assert (baseName(`d:file.ext`, `.ext`) == `file`);
  277. assert (baseName(`d:file`, `file`) == `file`);
  278. assert (baseName(`dir\\subdir\\\`) == `subdir`);
  279. assert (baseName(`dir\subdir.ext\`, `.ext`) == `subdir`);
  280. assert (baseName(`dir\subdir\`, `subdir`) == `subdir`);
  281. assert (baseName(`\`) == `\`);
  282. assert (baseName(`\\`) == `\`);
  283. assert (baseName(`\\\`) == `\`);
  284. assert (baseName(`d:\`) == `\`);
  285. assert (baseName(`d:`).empty);
  286. assert (baseName(`\\server\share\file`) == `file`);
  287. assert (baseName(`\\server\share\`) == `\`);
  288. assert (baseName(`\\server\share`) == `\`);
  289. }
  290. assert (baseName(stripExtension("dir/file.ext")) == "file");
  291. static assert (baseName("dir/file.ext") == "file.ext");
  292. static assert (baseName("dir/file.ext", ".ext") == "file");
  293. }
  294. /** Returns the directory part of a path. On Windows, this
  295. includes the drive letter if present.
  296. This function performs a memory allocation if and only if $(D path)
  297. is mutable and does not have a directory (in which case a new mutable
  298. string is needed to hold the returned current-directory symbol,
  299. $(D ".")).
  300. Examples:
  301. ---
  302. assert (dirName("file") == ".");
  303. assert (dirName("dir/file") == "dir");
  304. assert (dirName("/file") == "/");
  305. assert (dirName("dir/subdir/") == "dir");
  306. version (Windows)
  307. {
  308. assert (dirName("d:file") == "d:");
  309. assert (dirName(`d:\dir\file`) == `d:\dir`);
  310. assert (dirName(`d:\file`) == `d:\`);
  311. assert (dirName(`dir\subdir\`) == `dir`);
  312. }
  313. ---
  314. Standards:
  315. This function complies with
  316. $(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/dirname.html,
  317. the POSIX requirements for the 'dirname' shell utility)
  318. (with suitable adaptations for Windows paths).
  319. */
  320. C[] dirName(C)(C[] path)
  321. //TODO: @safe (BUG 6169) pure nothrow (because of to())
  322. if (isSomeChar!C)
  323. {
  324. if (path.empty) return to!(typeof(return))(".");
  325. auto p = rtrimDirSeparators(path);
  326. if (p.empty) return path[0 .. 1];
  327. version (Windows)
  328. {
  329. if (isUNC(p) && uncRootLength(p) == p.length)
  330. return p;
  331. if (p.length == 2 && isDriveSeparator(p[1]) && path.length > 2)
  332. return path[0 .. 3];
  333. }
  334. auto i = lastSeparator(p);
  335. if (i == -1) return to!(typeof(return))(".");
  336. if (i == 0) return p[0 .. 1];
  337. version (Windows)
  338. {
  339. // If the directory part is either d: or d:\, don't
  340. // chop off the last symbol.
  341. if (isDriveSeparator(p[i]) || isDriveSeparator(p[i-1]))
  342. return p[0 .. i+1];
  343. }
  344. // Remove any remaining trailing (back)slashes.
  345. return rtrimDirSeparators(p[0 .. i]);
  346. }
  347. unittest
  348. {
  349. assert (dirName("") == ".");
  350. assert (dirName("file"w) == ".");
  351. assert (dirName("dir/"d) == ".");
  352. assert (dirName("dir///") == ".");
  353. assert (dirName("dir/file"w.dup) == "dir");
  354. assert (dirName("dir///file"d.dup) == "dir");
  355. assert (dirName("dir/subdir/") == "dir");
  356. assert (dirName("/dir/file"w) == "/dir");
  357. assert (dirName("/file"d) == "/");
  358. assert (dirName("/") == "/");
  359. assert (dirName("///") == "/");
  360. version (Windows)
  361. {
  362. assert (dirName(`dir\`) == `.`);
  363. assert (dirName(`dir\\\`) == `.`);
  364. assert (dirName(`dir\file`) == `dir`);
  365. assert (dirName(`dir\\\file`) == `dir`);
  366. assert (dirName(`dir\subdir\`) == `dir`);
  367. assert (dirName(`\dir\file`) == `\dir`);
  368. assert (dirName(`\file`) == `\`);
  369. assert (dirName(`\`) == `\`);
  370. assert (dirName(`\\\`) == `\`);
  371. assert (dirName(`d:`) == `d:`);
  372. assert (dirName(`d:file`) == `d:`);
  373. assert (dirName(`d:\`) == `d:\`);
  374. assert (dirName(`d:\file`) == `d:\`);
  375. assert (dirName(`d:\dir\file`) == `d:\dir`);
  376. assert (dirName(`\\server\share\dir\file`) == `\\server\share\dir`);
  377. assert (dirName(`\\server\share\file`) == `\\server\share`);
  378. assert (dirName(`\\server\share\`) == `\\server\share`);
  379. assert (dirName(`\\server\share`) == `\\server\share`);
  380. }
  381. static assert (dirName("dir/file") == "dir");
  382. }
  383. /** Returns the root directory of the specified path, or $(D null) if the
  384. path is not rooted.
  385. Examples:
  386. ---
  387. assert (rootName("foo") is null);
  388. assert (rootName("/foo") == "/");
  389. version (Windows)
  390. {
  391. assert (rootName(`\foo`) == `\`);
  392. assert (rootName(`c:\foo`) == `c:\`);
  393. assert (rootName(`\\server\share\foo`) == `\\server\share`);
  394. }
  395. ---
  396. */
  397. inout(C)[] rootName(C)(inout(C)[] path) @safe pure nothrow if (isSomeChar!C)
  398. {
  399. if (path.empty) return null;
  400. version (Posix)
  401. {
  402. if (isDirSeparator(path[0])) return path[0 .. 1];
  403. }
  404. else version (Windows)
  405. {
  406. if (isDirSeparator(path[0]))
  407. {
  408. if (isUNC(path)) return path[0 .. uncRootLength(path)];
  409. else return path[0 .. 1];
  410. }
  411. else if (path.length >= 3 && isDriveSeparator(path[1]) &&
  412. isDirSeparator(path[2]))
  413. {
  414. return path[0 .. 3];
  415. }
  416. }
  417. else static assert (0, "unsupported platform");
  418. assert (!isRooted(path));
  419. return null;
  420. }
  421. unittest
  422. {
  423. assert (rootName("") is null);
  424. assert (rootName("foo") is null);
  425. assert (rootName("/") == "/");
  426. assert (rootName("/foo/bar") == "/");
  427. version (Windows)
  428. {
  429. assert (rootName("d:foo") is null);
  430. assert (rootName(`d:\foo`) == `d:\`);
  431. assert (rootName(`\\server\share\foo`) == `\\server\share`);
  432. assert (rootName(`\\server\share`) == `\\server\share`);
  433. }
  434. }
  435. /** Returns the drive of a path, or $(D null) if the drive
  436. is not specified. In the case of UNC paths, the network share
  437. is returned.
  438. Always returns $(D null) on POSIX.
  439. Examples:
  440. ---
  441. version (Windows)
  442. {
  443. assert (driveName(`d:\file`) == "d:");
  444. assert (driveName(`\\server\share\file`) == `\\server\share`);
  445. assert (driveName(`dir\file`).empty);
  446. }
  447. ---
  448. */
  449. inout(C)[] driveName(C)(inout(C)[] path) @safe pure nothrow
  450. if (isSomeChar!C)
  451. {
  452. version (Windows)
  453. {
  454. if (hasDrive(path))
  455. return path[0 .. 2];
  456. else if (isUNC(path))
  457. return path[0 .. uncRootLength(path)];
  458. }
  459. return null;
  460. }
  461. unittest
  462. {
  463. version (Posix) assert (driveName("c:/foo").empty);
  464. version (Windows)
  465. {
  466. assert (driveName(`dir\file`).empty);
  467. assert (driveName(`d:file`) == "d:");
  468. assert (driveName(`d:\file`) == "d:");
  469. assert (driveName("d:") == "d:");
  470. assert (driveName(`\\server\share\file`) == `\\server\share`);
  471. assert (driveName(`\\server\share\`) == `\\server\share`);
  472. assert (driveName(`\\server\share`) == `\\server\share`);
  473. static assert (driveName(`d:\file`) == "d:");
  474. }
  475. }
  476. /** Strips the drive from a Windows path. On POSIX, the path is returned
  477. unaltered.
  478. Example:
  479. ---
  480. version (Windows)
  481. {
  482. assert (stripDrive(`d:\dir\file`) == `\dir\file`);
  483. assert (stripDrive(`\\server\share\dir\file`) == `\dir\file`);
  484. }
  485. ---
  486. */
  487. inout(C)[] stripDrive(C)(inout(C)[] path) @safe pure nothrow if (isSomeChar!C)
  488. {
  489. version(Windows)
  490. {
  491. if (hasDrive(path)) return path[2 .. $];
  492. else if (isUNC(path)) return path[uncRootLength(path) .. $];
  493. }
  494. return path;
  495. }
  496. unittest
  497. {
  498. version(Windows)
  499. {
  500. assert (stripDrive(`d:\dir\file`) == `\dir\file`);
  501. assert (stripDrive(`\\server\share\dir\file`) == `\dir\file`);
  502. static assert (stripDrive(`d:\dir\file`) == `\dir\file`);
  503. }
  504. version(Posix)
  505. {
  506. assert (stripDrive(`d:\dir\file`) == `d:\dir\file`);
  507. }
  508. }
  509. /* Helper function that returns the position of the filename/extension
  510. separator dot in path. If not found, returns -1.
  511. */
  512. private ptrdiff_t extSeparatorPos(C)(in C[] path) @safe pure nothrow
  513. if (isSomeChar!C)
  514. {
  515. auto i = (cast(ptrdiff_t) path.length) - 1;
  516. while (i >= 0 && !isSeparator(path[i]))
  517. {
  518. if (path[i] == '.' && i > 0 && !isSeparator(path[i-1])) return i;
  519. --i;
  520. }
  521. return -1;
  522. }
  523. /** Returns the _extension part of a file name, including the dot.
  524. If there is no _extension, $(D null) is returned.
  525. Examples:
  526. ---
  527. assert (extension("file").empty);
  528. assert (extension("file.ext") == ".ext");
  529. assert (extension("file.ext1.ext2") == ".ext2");
  530. assert (extension("file.") == ".");
  531. assert (extension(".file").empty);
  532. assert (extension(".file.ext") == ".ext");
  533. ---
  534. */
  535. inout(C)[] extension(C)(inout(C)[] path) @safe pure nothrow if (isSomeChar!C)
  536. {
  537. auto i = extSeparatorPos(path);
  538. if (i == -1) return null;
  539. else return path[i .. $];
  540. }
  541. unittest
  542. {
  543. assert (extension("file").empty);
  544. assert (extension("file.") == ".");
  545. assert (extension("file.ext"w) == ".ext");
  546. assert (extension("file.ext1.ext2"d) == ".ext2");
  547. assert (extension(".foo".dup).empty);
  548. assert (extension(".foo.ext"w.dup) == ".ext");
  549. assert (extension("dir/file"d.dup).empty);
  550. assert (extension("dir/file.") == ".");
  551. assert (extension("dir/file.ext") == ".ext");
  552. assert (extension("dir/file.ext1.ext2"w) == ".ext2");
  553. assert (extension("dir/.foo"d).empty);
  554. assert (extension("dir/.foo.ext".dup) == ".ext");
  555. version(Windows)
  556. {
  557. assert (extension(`dir\file`).empty);
  558. assert (extension(`dir\file.`) == ".");
  559. assert (extension(`dir\file.ext`) == `.ext`);
  560. assert (extension(`dir\file.ext1.ext2`) == `.ext2`);
  561. assert (extension(`dir\.foo`).empty);
  562. assert (extension(`dir\.foo.ext`) == `.ext`);
  563. assert (extension(`d:file`).empty);
  564. assert (extension(`d:file.`) == ".");
  565. assert (extension(`d:file.ext`) == `.ext`);
  566. assert (extension(`d:file.ext1.ext2`) == `.ext2`);
  567. assert (extension(`d:.foo`).empty);
  568. assert (extension(`d:.foo.ext`) == `.ext`);
  569. }
  570. static assert (extension("file").empty);
  571. static assert (extension("file.ext") == ".ext");
  572. }
  573. /** Returns the path with the extension stripped off.
  574. Examples:
  575. ---
  576. assert (stripExtension("file") == "file");
  577. assert (stripExtension("file.ext") == "file");
  578. assert (stripExtension("file.ext1.ext2") == "file.ext1");
  579. assert (stripExtension("file.") == "file");
  580. assert (stripExtension(".file") == ".file");
  581. assert (stripExtension(".file.ext") == ".file");
  582. assert (stripExtension("dir/file.ext") == "dir/file");
  583. ---
  584. */
  585. inout(C)[] stripExtension(C)(inout(C)[] path) @safe pure nothrow
  586. if (isSomeChar!C)
  587. {
  588. auto i = extSeparatorPos(path);
  589. if (i == -1) return path;
  590. else return path[0 .. i];
  591. }
  592. unittest
  593. {
  594. assert (stripExtension("file") == "file");
  595. assert (stripExtension("file.ext"w) == "file");
  596. assert (stripExtension("file.ext1.ext2"d) == "file.ext1");
  597. assert (stripExtension(".foo".dup) == ".foo");
  598. assert (stripExtension(".foo.ext"w.dup) == ".foo");
  599. assert (stripExtension("dir/file"d.dup) == "dir/file");
  600. assert (stripExtension("dir/file.ext") == "dir/file");
  601. assert (stripExtension("dir/file.ext1.ext2"w) == "dir/file.ext1");
  602. assert (stripExtension("dir/.foo"d) == "dir/.foo");
  603. assert (stripExtension("dir/.foo.ext".dup) == "dir/.foo");
  604. version(Windows)
  605. {
  606. assert (stripExtension("dir\\file") == "dir\\file");
  607. assert (stripExtension("dir\\file.ext") == "dir\\file");
  608. assert (stripExtension("dir\\file.ext1.ext2") == "dir\\file.ext1");
  609. assert (stripExtension("dir\\.foo") == "dir\\.foo");
  610. assert (stripExtension("dir\\.foo.ext") == "dir\\.foo");
  611. assert (stripExtension("d:file") == "d:file");
  612. assert (stripExtension("d:file.ext") == "d:file");
  613. assert (stripExtension("d:file.ext1.ext2") == "d:file.ext1");
  614. assert (stripExtension("d:.foo") == "d:.foo");
  615. assert (stripExtension("d:.foo.ext") == "d:.foo");
  616. }
  617. static assert (stripExtension("file") == "file");
  618. static assert (stripExtension("file.ext"w) == "file");
  619. }
  620. /** Returns a string containing the _path given by $(D path), but where
  621. the extension has been set to $(D ext).
  622. If the filename already has an extension, it is replaced. If not, the
  623. extension is simply appended to the filename. Including a leading dot
  624. in $(D ext) is optional.
  625. If the extension is empty, this function is equivalent to
  626. $(LREF stripExtension).
  627. This function normally allocates a new string (the possible exception
  628. being the case when path is immutable and doesn't already have an
  629. extension).
  630. Examples:
  631. ---
  632. assert (setExtension("file", "ext") == "file.ext");
  633. assert (setExtension("file", ".ext") == "file.ext");
  634. assert (setExtension("file.old", "") == "file");
  635. assert (setExtension("file.old", "new") == "file.new");
  636. assert (setExtension("file.old", ".new") == "file.new");
  637. ---
  638. */
  639. immutable(Unqual!C1)[] setExtension(C1, C2)(in C1[] path, in C2[] ext)
  640. @trusted pure nothrow
  641. if (isSomeChar!C1 && !is(C1 == immutable) && is(Unqual!C1 == Unqual!C2))
  642. {
  643. if (ext.length > 0 && ext[0] != '.')
  644. return cast(typeof(return))(stripExtension(path)~'.'~ext);
  645. else
  646. return cast(typeof(return))(stripExtension(path)~ext);
  647. }
  648. ///ditto
  649. immutable(C1)[] setExtension(C1, C2)(immutable(C1)[] path, const(C2)[] ext)
  650. @trusted pure nothrow
  651. if (isSomeChar!C1 && is(Unqual!C1 == Unqual!C2))
  652. {
  653. if (ext.length == 0)
  654. return stripExtension(path);
  655. // Optimised for the case where path is immutable and has no extension
  656. if (ext.length > 0 && ext[0] == '.') ext = ext[1 .. $];
  657. auto i = extSeparatorPos(path);
  658. if (i == -1)
  659. {
  660. path ~= '.';
  661. path ~= ext;
  662. return path;
  663. }
  664. else if (i == path.length - 1)
  665. {
  666. path ~= ext;
  667. return path;
  668. }
  669. else
  670. {
  671. return cast(typeof(return))(path[0 .. i+1] ~ ext);
  672. }
  673. }
  674. unittest
  675. {
  676. assert (setExtension("file", "ext") == "file.ext");
  677. assert (setExtension("file"w, ".ext"w) == "file.ext");
  678. assert (setExtension("file."d, "ext"d) == "file.ext");
  679. assert (setExtension("file.", ".ext") == "file.ext");
  680. assert (setExtension("file.old"w, "new"w) == "file.new");
  681. assert (setExtension("file.old"d, ".new"d) == "file.new");
  682. assert (setExtension("file"w.dup, "ext"w) == "file.ext");
  683. assert (setExtension("file"w.dup, ".ext"w) == "file.ext");
  684. assert (setExtension("file."w, "ext"w.dup) == "file.ext");
  685. assert (setExtension("file."w, ".ext"w.dup) == "file.ext");
  686. assert (setExtension("file.old"d.dup, "new"d) == "file.new");
  687. assert (setExtension("file.old"d.dup, ".new"d) == "file.new");
  688. static assert (setExtension("file", "ext") == "file.ext");
  689. static assert (setExtension("file.old", "new") == "file.new");
  690. static assert (setExtension("file"w.dup, "ext"w) == "file.ext");
  691. static assert (setExtension("file.old"d.dup, "new"d) == "file.new");
  692. // Issue 10601
  693. assert (setExtension("file", "") == "file");
  694. assert (setExtension("file.ext", "") == "file");
  695. }
  696. /** Returns the _path given by $(D path), with the extension given by
  697. $(D ext) appended if the path doesn't already have one.
  698. Including the dot in the extension is optional.
  699. This function always allocates a new string, except in the case when
  700. path is immutable and already has an extension.
  701. Examples:
  702. ---
  703. assert (defaultExtension("file", "ext") == "file.ext");
  704. assert (defaultExtension("file", ".ext") == "file.ext");
  705. assert (defaultExtension("file.", "ext") == "file.");
  706. assert (defaultExtension("file.old", "new") == "file.old");
  707. assert (defaultExtension("file.old", ".new") == "file.old");
  708. ---
  709. */
  710. immutable(Unqual!C1)[] defaultExtension(C1, C2)(in C1[] path, in C2[] ext)
  711. @trusted pure // TODO: nothrow (because of to())
  712. if (isSomeChar!C1 && is(Unqual!C1 == Unqual!C2))
  713. {
  714. auto i = extSeparatorPos(path);
  715. if (i == -1)
  716. {
  717. if (ext.length > 0 && ext[0] == '.')
  718. return cast(typeof(return))(path~ext);
  719. else
  720. return cast(typeof(return))(path~'.'~ext);
  721. }
  722. else return to!(typeof(return))(path);
  723. }
  724. unittest
  725. {
  726. assert (defaultExtension("file", "ext") == "file.ext");
  727. assert (defaultExtension("file", ".ext") == "file.ext");
  728. assert (defaultExtension("file.", "ext") == "file.");
  729. assert (defaultExtension("file.old", "new") == "file.old");
  730. assert (defaultExtension("file.old", ".new") == "file.old");
  731. assert (defaultExtension("file"w.dup, "ext"w) == "file.ext");
  732. assert (defaultExtension("file.old"d.dup, "new"d) == "file.old");
  733. static assert (defaultExtension("file", "ext") == "file.ext");
  734. static assert (defaultExtension("file.old", "new") == "file.old");
  735. static assert (defaultExtension("file"w.dup, "ext"w) == "file.ext");
  736. static assert (defaultExtension("file.old"d.dup, "new"d) == "file.old");
  737. }
  738. /** Combines one or more path segments.
  739. This function takes a set of path segments, given as an input
  740. range of string elements or as a set of string arguments,
  741. and concatenates them with each other. Directory separators
  742. are inserted between segments if necessary. If any of the
  743. path segments are absolute (as defined by $(LREF isAbsolute)), the
  744. preceding segments will be dropped.
  745. On Windows, if one of the path segments are rooted, but not absolute
  746. (e.g. $(D `\foo`)), all preceding path segments down to the previous
  747. root will be dropped. (See below for an example.)
  748. This function always allocates memory to hold the resulting path.
  749. The variadic overload is guaranteed to only perform a single
  750. allocation, as is the range version if $(D paths) is a forward
  751. range.
  752. */
  753. immutable(ElementEncodingType!(ElementType!Range))[]
  754. buildPath(Range)(Range segments)
  755. if (isInputRange!Range && isSomeString!(ElementType!Range))
  756. {
  757. if (segments.empty) return null;
  758. // If this is a forward range, we can pre-calculate a maximum length.
  759. static if (isForwardRange!Range)
  760. {
  761. auto segments2 = segments.save;
  762. size_t precalc = 0;
  763. foreach (segment; segments2) precalc += segment.length + 1;
  764. }
  765. // Otherwise, just venture a guess and resize later if necessary.
  766. else size_t precalc = 255;
  767. auto buf = new Unqual!(ElementEncodingType!(ElementType!Range))[](precalc);
  768. size_t pos = 0;
  769. foreach (segment; segments)
  770. {
  771. if (segment.empty) continue;
  772. static if (!isForwardRange!Range)
  773. {
  774. immutable neededLength = pos + segment.length + 1;
  775. if (buf.length < neededLength)
  776. buf.length = reserve(buf, neededLength + buf.length/2);
  777. }
  778. if (pos > 0)
  779. {
  780. if (isRooted(segment))
  781. {
  782. version (Posix)
  783. {
  784. pos = 0;
  785. }
  786. else version (Windows)
  787. {
  788. if (isAbsolute(segment))
  789. pos = 0;
  790. else
  791. {
  792. pos = rootName(buf[0 .. pos]).length;
  793. if (pos > 0 && isDirSeparator(buf[pos-1])) --pos;
  794. }
  795. }
  796. }
  797. else if (!isDirSeparator(buf[pos-1]))
  798. buf[pos++] = dirSeparator[0];
  799. }
  800. buf[pos .. pos + segment.length] = segment[];
  801. pos += segment.length;
  802. }
  803. static U trustedCast(U, V)(V v) @trusted pure nothrow { return cast(U) v; }
  804. return trustedCast!(typeof(return))(buf[0 .. pos]);
  805. }
  806. /// ditto
  807. immutable(C)[] buildPath(C)(const(C[])[] paths...)
  808. @safe pure nothrow
  809. if (isSomeChar!C)
  810. {
  811. return buildPath!(typeof(paths))(paths);
  812. }
  813. ///
  814. unittest
  815. {
  816. version (Posix)
  817. {
  818. assert (buildPath("foo", "bar", "baz") == "foo/bar/baz");
  819. assert (buildPath("/foo/", "bar/baz") == "/foo/bar/baz");
  820. assert (buildPath("/foo", "/bar") == "/bar");
  821. }
  822. version (Windows)
  823. {
  824. assert (buildPath("foo", "bar", "baz") == `foo\bar\baz`);
  825. assert (buildPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`);
  826. assert (buildPath("foo", `d:\bar`) == `d:\bar`);
  827. assert (buildPath("foo", `\bar`) == `\bar`);
  828. assert (buildPath(`c:\foo`, `\bar`) == `c:\bar`);
  829. }
  830. }
  831. unittest // non-documented
  832. {
  833. // ir() wraps an array in a plain (i.e. non-forward) input range, so that
  834. // we can test both code paths
  835. InputRange!(C[]) ir(C)(C[][] p...) { return inputRangeObject(p); }
  836. version (Posix)
  837. {
  838. assert (buildPath("foo") == "foo");
  839. assert (buildPath("/foo/") == "/foo/");
  840. assert (buildPath("foo", "bar") == "foo/bar");
  841. assert (buildPath("foo", "bar", "baz") == "foo/bar/baz");
  842. assert (buildPath("foo/".dup, "bar") == "foo/bar");
  843. assert (buildPath("foo///", "bar".dup) == "foo///bar");
  844. assert (buildPath("/foo"w, "bar"w) == "/foo/bar");
  845. assert (buildPath("foo"w.dup, "/bar"w) == "/bar");
  846. assert (buildPath("foo"w, "bar/"w.dup) == "foo/bar/");
  847. assert (buildPath("/"d, "foo"d) == "/foo");
  848. assert (buildPath(""d.dup, "foo"d) == "foo");
  849. assert (buildPath("foo"d, ""d.dup) == "foo");
  850. assert (buildPath("foo", "bar".dup, "baz") == "foo/bar/baz");
  851. assert (buildPath("foo"w, "/bar"w, "baz"w.dup) == "/bar/baz");
  852. static assert (buildPath("foo", "bar", "baz") == "foo/bar/baz");
  853. static assert (buildPath("foo", "/bar", "baz") == "/bar/baz");
  854. // The following are mostly duplicates of the above, except that the
  855. // range version does not accept mixed constness.
  856. assert (buildPath(ir("foo")) == "foo");
  857. assert (buildPath(ir("/foo/")) == "/foo/");
  858. assert (buildPath(ir("foo", "bar")) == "foo/bar");
  859. assert (buildPath(ir("foo", "bar", "baz")) == "foo/bar/baz");
  860. assert (buildPath(ir("foo/".dup, "bar".dup)) == "foo/bar");
  861. assert (buildPath(ir("foo///".dup, "bar".dup)) == "foo///bar");
  862. assert (buildPath(ir("/foo"w, "bar"w)) == "/foo/bar");
  863. assert (buildPath(ir("foo"w.dup, "/bar"w.dup)) == "/bar");
  864. assert (buildPath(ir("foo"w.dup, "bar/"w.dup)) == "foo/bar/");
  865. assert (buildPath(ir("/"d, "foo"d)) == "/foo");
  866. assert (buildPath(ir(""d.dup, "foo"d.dup)) == "foo");
  867. assert (buildPath(ir("foo"d, ""d)) == "foo");
  868. assert (buildPath(ir("foo", "bar", "baz")) == "foo/bar/baz");
  869. assert (buildPath(ir("foo"w.dup, "/bar"w.dup, "baz"w.dup)) == "/bar/baz");
  870. }
  871. version (Windows)
  872. {
  873. assert (buildPath("foo") == "foo");
  874. assert (buildPath(`\foo/`) == `\foo/`);
  875. assert (buildPath("foo", "bar", "baz") == `foo\bar\baz`);
  876. assert (buildPath("foo", `\bar`) == `\bar`);
  877. assert (buildPath(`c:\foo`, "bar") == `c:\foo\bar`);
  878. assert (buildPath("foo"w, `d:\bar`w.dup) == `d:\bar`);
  879. assert (buildPath(`c:\foo\bar`, `\baz`) == `c:\baz`);
  880. assert (buildPath(`\\foo\bar\baz`d, `foo`d, `\bar`d) == `\\foo\bar\bar`d);
  881. static assert (buildPath("foo", "bar", "baz") == `foo\bar\baz`);
  882. static assert (buildPath("foo", `c:\bar`, "baz") == `c:\bar\baz`);
  883. assert (buildPath(ir("foo")) == "foo");
  884. assert (buildPath(ir(`\foo/`)) == `\foo/`);
  885. assert (buildPath(ir("foo", "bar", "baz")) == `foo\bar\baz`);
  886. assert (buildPath(ir("foo", `\bar`)) == `\bar`);
  887. assert (buildPath(ir(`c:\foo`, "bar")) == `c:\foo\bar`);
  888. assert (buildPath(ir("foo"w.dup, `d:\bar`w.dup)) == `d:\bar`);
  889. assert (buildPath(ir(`c:\foo\bar`, `\baz`)) == `c:\baz`);
  890. assert (buildPath(ir(`\\foo\bar\baz`d, `foo`d, `\bar`d)) == `\\foo\bar\bar`d);
  891. }
  892. // Test that allocation works as it should.
  893. auto manyShort = "aaa".repeat(1000).array();
  894. auto manyShortCombined = join(manyShort, dirSeparator);
  895. assert (buildPath(manyShort) == manyShortCombined);
  896. assert (buildPath(ir(manyShort)) == manyShortCombined);
  897. auto fewLong = 'b'.repeat(500).array().repeat(10).array();
  898. auto fewLongCombined = join(fewLong, dirSeparator);
  899. assert (buildPath(fewLong) == fewLongCombined);
  900. assert (buildPath(ir(fewLong)) == fewLongCombined);
  901. }
  902. unittest
  903. {
  904. // Test for issue 7397
  905. string[] ary = ["a", "b"];
  906. version (Posix)
  907. {
  908. assert (buildPath(ary) == "a/b");
  909. }
  910. else version (Windows)
  911. {
  912. assert (buildPath(ary) == `a\b`);
  913. }
  914. }
  915. /** Performs the same task as $(LREF buildPath),
  916. while at the same time resolving current/parent directory
  917. symbols ($(D ".") and $(D "..")) and removing superfluous
  918. directory separators.
  919. On Windows, slashes are replaced with backslashes.
  920. Note that this function does not resolve symbolic links.
  921. This function always allocates memory to hold the resulting path.
  922. Examples:
  923. ---
  924. version (Posix)
  925. {
  926. assert (buildNormalizedPath("/foo/./bar/..//baz/") == "/foo/baz");
  927. assert (buildNormalizedPath("../foo/.") == "../foo");
  928. assert (buildNormalizedPath("/foo", "bar/baz/") == "/foo/bar/baz");
  929. assert (buildNormalizedPath("/foo", "/bar/..", "baz") == "/baz");
  930. assert (buildNormalizedPath("foo/./bar", "../../", "../baz") == "../baz");
  931. assert (buildNormalizedPath("/foo/./bar", "../../baz") == "/baz");
  932. }
  933. version (Windows)
  934. {
  935. assert (buildNormalizedPath(`c:\foo\.\bar/..\\baz\`) == `c:\foo\baz`);
  936. assert (buildNormalizedPath(`..\foo\.`) == `..\foo`);
  937. assert (buildNormalizedPath(`c:\foo`, `bar\baz\`) == `c:\foo\bar\baz`);
  938. assert (buildNormalizedPath(`c:\foo`, `bar/..`) == `c:\foo`);
  939. assert (buildNormalizedPath(`\\server\share\foo`, `..\bar`) == `\\server\share\bar`);
  940. }
  941. ---
  942. */
  943. immutable(C)[] buildNormalizedPath(C)(const(C[])[] paths...)
  944. @trusted pure nothrow
  945. if (isSomeChar!C)
  946. {
  947. import std.c.stdlib;
  948. auto paths2 = new const(C)[][](paths.length);
  949. //(cast(const(C)[]*)alloca((const(C)[]).sizeof * paths.length))[0 .. paths.length];
  950. // Check whether the resulting path will be absolute or rooted,
  951. // calculate its maximum length, and discard segments we won't use.
  952. typeof(paths[0][0])[] rootElement;
  953. int numPaths = 0;
  954. bool seenAbsolute;
  955. size_t segmentLengthSum = 0;
  956. foreach (i; 0 .. paths.length)
  957. {
  958. auto p = paths[i];
  959. if (p.empty) continue;
  960. else if (isRooted(p))
  961. {
  962. immutable thisIsAbsolute = isAbsolute(p);
  963. if (thisIsAbsolute || !seenAbsolute)
  964. {
  965. if (thisIsAbsolute) seenAbsolute = true;
  966. rootElement = rootName(p);
  967. paths2[0] = p[rootElement.length .. $];
  968. numPaths = 1;
  969. segmentLengthSum = paths2[0].length;
  970. }
  971. else
  972. {
  973. paths2[0] = p;
  974. numPaths = 1;
  975. segmentLengthSum = p.length;
  976. }
  977. }
  978. else
  979. {
  980. paths2[numPaths++] = p;
  981. segmentLengthSum += p.length;
  982. }
  983. }
  984. if (rootElement.length + segmentLengthSum == 0) return null;
  985. paths2 = paths2[0 .. numPaths];
  986. immutable rooted = !rootElement.empty;
  987. assert (rooted || !seenAbsolute); // absolute => rooted
  988. // Allocate memory for the resulting path, including room for
  989. // extra dir separators
  990. auto fullPath = new C[rootElement.length + segmentLengthSum + paths2.length];
  991. // Copy the root element into fullPath, and let relPart be
  992. // the remaining slice.
  993. typeof(fullPath) relPart;
  994. if (rooted)
  995. {
  996. // For Windows, we also need to perform normalization on
  997. // the root element.
  998. version (Posix)
  999. {
  1000. fullPath[0 .. rootElement.length] = rootElement[];
  1001. }
  1002. else version (Windows)
  1003. {
  1004. foreach (i, c; rootElement)
  1005. {
  1006. if (isDirSeparator(c))
  1007. {
  1008. static assert (dirSeparator.length == 1);
  1009. fullPath[i] = dirSeparator[0];
  1010. }
  1011. else fullPath[i] = c;
  1012. }
  1013. }
  1014. else static assert (0);
  1015. // If the root element doesn't end with a dir separator,
  1016. // we add one.
  1017. if (!isDirSeparator(rootElement[$-1]))
  1018. {
  1019. static assert (dirSeparator.length == 1);
  1020. fullPath[rootElement.length] = dirSeparator[0];
  1021. relPart = fullPath[rootElement.length + 1 .. $];
  1022. }
  1023. else
  1024. {
  1025. relPart = fullPath[rootElement.length .. $];
  1026. }
  1027. }
  1028. else relPart = fullPath;
  1029. // Now, we have ensured that all segments in path are relative to the
  1030. // root we found earlier.
  1031. bool hasParents = rooted;
  1032. ptrdiff_t i;
  1033. foreach (path; paths2)
  1034. {
  1035. path = trimDirSeparators(path);
  1036. enum Prev { nonSpecial, dirSep, dot, doubleDot }
  1037. Prev prev = Prev.dirSep;
  1038. foreach (j; 0 .. path.length+1)
  1039. {
  1040. // Fake a dir separator between path segments
  1041. immutable c = (j == path.length ? dirSeparator[0] : path[j]);
  1042. if (isDirSeparator(c))
  1043. {
  1044. final switch (prev)
  1045. {
  1046. case Prev.doubleDot:
  1047. if (hasParents)
  1048. {
  1049. while (i > 0 && !isDirSeparator(relPart[i-1])) --i;
  1050. if (i > 0) --i; // skip the dir separator
  1051. while (i > 0 && !isDirSeparator(relPart[i-1])) --i;
  1052. if (i == 0) hasParents = rooted;
  1053. }
  1054. else
  1055. {
  1056. relPart[i++] = '.';
  1057. relPart[i++] = '.';
  1058. static assert (dirSeparator.length == 1);
  1059. relPart[i++] = dirSeparator[0];
  1060. }
  1061. break;
  1062. case Prev.dot:
  1063. while (i > 0 && !isDirSeparator(relPart[i-1])) --i;
  1064. break;
  1065. case Prev.nonSpecial:
  1066. static assert (dirSeparator.length == 1);
  1067. relPart[i++] = dirSeparator[0];
  1068. hasParents = true;
  1069. break;
  1070. case Prev.dirSep:
  1071. break;
  1072. }
  1073. prev = Prev.dirSep;
  1074. }
  1075. else if (c == '.')
  1076. {
  1077. final switch (prev)
  1078. {
  1079. case Prev.dirSep:
  1080. prev = Prev.dot;
  1081. break;
  1082. case Prev.dot:
  1083. prev = Prev.doubleDot;
  1084. break;
  1085. case Prev.doubleDot:
  1086. prev = Prev.nonSpecial;
  1087. relPart[i .. i+3] = "...";
  1088. i += 3;
  1089. break;
  1090. case Prev.nonSpecial:
  1091. relPart[i] = '.';
  1092. ++i;
  1093. break;
  1094. }
  1095. }
  1096. else
  1097. {
  1098. final switch (prev)
  1099. {
  1100. case Prev.doubleDot:
  1101. relPart[i] = '.';
  1102. ++i;
  1103. goto case;
  1104. case Prev.dot:
  1105. relPart[i] = '.';
  1106. ++i;
  1107. break;
  1108. case Prev.dirSep: break;
  1109. case Prev.nonSpecial: break;
  1110. }
  1111. relPart[i] = c;
  1112. ++i;
  1113. prev = Prev.nonSpecial;
  1114. }
  1115. }
  1116. }
  1117. // Return path, including root element and excluding the
  1118. // final dir separator.
  1119. immutable len = (relPart.ptr - fullPath.ptr) + (i > 0 ? i - 1 : 0);
  1120. fullPath = fullPath[0 .. len];
  1121. version (Windows)
  1122. {
  1123. // On Windows, if the path is on the form `\\server\share`,
  1124. // with no further segments, normalization will have turned it
  1125. // into `\\server\share\`. If so, we need to remove the final
  1126. // backslash.
  1127. if (isUNC(fullPath) && uncRootLength(fullPath) == fullPath.length - 1)
  1128. fullPath = fullPath[0 .. $-1];
  1129. }
  1130. return cast(typeof(return)) fullPath;
  1131. }
  1132. unittest
  1133. {
  1134. assert (buildNormalizedPath("") is null);
  1135. assert (buildNormalizedPath("foo") == "foo");
  1136. version (Posix)
  1137. {
  1138. assert (buildNormalizedPath("/", "foo", "bar") == "/foo/bar");
  1139. assert (buildNormalizedPath("foo", "bar", "baz") == "foo/bar/baz");
  1140. assert (buildNormalizedPath("foo", "bar/baz") == "foo/bar/baz");
  1141. assert (buildNormalizedPath("foo", "bar//baz///") == "foo/bar/baz");
  1142. assert (buildNormalizedPath("/foo", "bar/baz") == "/foo/bar/baz");
  1143. assert (buildNormalizedPath("/foo", "/bar/baz") == "/bar/baz");
  1144. assert (buildNormalizedPath("/foo/..", "/bar/./baz") == "/bar/baz");
  1145. assert (buildNormalizedPath("/foo/..", "bar/baz") == "/bar/baz");
  1146. assert (buildNormalizedPath("/foo/../../", "bar/baz") == "/bar/baz");
  1147. assert (buildNormalizedPath("/foo/bar", "../baz") == "/foo/baz");
  1148. assert (buildNormalizedPath("/foo/bar", "../../baz") == "/baz");
  1149. assert (buildNormalizedPath("/foo/bar", ".././/baz/..", "wee/") == "/foo/wee");
  1150. assert (buildNormalizedPath("//foo/bar", "baz///wee") == "/foo/bar/baz/wee");
  1151. static assert (buildNormalizedPath("/foo/..", "/bar/./baz") == "/bar/baz");
  1152. // Examples in docs:
  1153. assert (buildNormalizedPath("/foo", "bar/baz/") == "/foo/bar/baz");
  1154. assert (buildNormalizedPath("/foo", "/bar/..", "baz") == "/baz");
  1155. assert (buildNormalizedPath("foo/./bar", "../../", "../baz") == "../baz");
  1156. assert (buildNormalizedPath("/foo/./bar", "../../baz") == "/baz");
  1157. }
  1158. else version (Windows)
  1159. {
  1160. assert (buildNormalizedPath(`\`, `foo`, `bar`) == `\foo\bar`);
  1161. assert (buildNormalizedPath(`foo`, `bar`, `baz`) == `foo\bar\baz`);
  1162. assert (buildNormalizedPath(`foo`, `bar\baz`) == `foo\bar\baz`);
  1163. assert (buildNormalizedPath(`foo`, `bar\\baz\\\`) == `foo\bar\baz`);
  1164. assert (buildNormalizedPath(`\foo`, `bar\baz`) == `\foo\bar\baz`);
  1165. assert (buildNormalizedPath(`\foo`, `\bar\baz`) == `\bar\baz`);
  1166. assert (buildNormalizedPath(`\foo\..`, `\bar\.\baz`) == `\bar\baz`);
  1167. assert (buildNormalizedPath(`\foo\..`, `bar\baz`) == `\bar\baz`);
  1168. assert (buildNormalizedPath(`\foo\..\..\`, `bar\baz`) == `\bar\baz`);
  1169. assert (buildNormalizedPath(`\foo\bar`, `..\baz`) == `\foo\baz`);
  1170. assert (buildNormalizedPath(`\foo\bar`, `../../baz`) == `\baz`);
  1171. assert (buildNormalizedPath(`\foo\bar`, `..\.\/baz\..`, `wee\`) == `\foo\wee`);
  1172. assert (buildNormalizedPath(`c:\`, `foo`, `bar`) == `c:\foo\bar`);
  1173. assert (buildNormalizedPath(`c:foo`, `bar`, `baz`) == `c:foo\bar\baz`);
  1174. assert (buildNormalizedPath(`c:foo`, `bar\baz`) == `c:foo\bar\baz`);
  1175. assert (buildNormalizedPath(`c:foo`, `bar\\baz\\\`) == `c:foo\bar\baz`);
  1176. assert (buildNormalizedPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`);
  1177. assert (buildNormalizedPath(`c:\foo`, `\bar\baz`) == `c:\bar\baz`);
  1178. assert (buildNormalizedPath(`c:\foo\..`, `\bar\.\baz`) == `c:\bar\baz`);
  1179. assert (buildNormalizedPath(`c:\foo\..`, `bar\baz`) == `c:\bar\baz`);
  1180. assert (buildNormalizedPath(`c:\foo\..\..\`, `bar\baz`) == `c:\bar\baz`);
  1181. assert (buildNormalizedPath(`c:\foo\bar`, `..\baz`) == `c:\foo\baz`);
  1182. assert (buildNormalizedPath(`c:\foo\bar`, `..\..\baz`) == `c:\baz`);
  1183. assert (buildNormalizedPath(`c:\foo\bar`, `..\.\\baz\..`, `wee\`) == `c:\foo\wee`);
  1184. assert (buildNormalizedPath(`\\server\share`, `foo`, `bar`) == `\\server\share\foo\bar`);
  1185. assert (buildNormalizedPath(`\\server\share\`, `foo`, `bar`) == `\\server\share\foo\bar`);
  1186. assert (buildNormalizedPath(`\\server\share\foo`, `bar\baz`) == `\\server\share\foo\bar\baz`);
  1187. assert (buildNormalizedPath(`\\server\share\foo`, `\bar\baz`) == `\\server\share\bar\baz`);
  1188. assert (buildNormalizedPath(`\\server\share\foo\..`, `\bar\.\baz`) == `\\server\share\bar\baz`);
  1189. assert (buildNormalizedPath(`\\server\share\foo\..`, `bar\baz`) == `\\server\share\bar\baz`);
  1190. assert (buildNormalizedPath(`\\server\share\foo\..\..\`, `bar\baz`) == `\\server\share\bar\baz`);
  1191. assert (buildNormalizedPath(`\\server\share\foo\bar`, `..\baz`) == `\\server\share\foo\baz`);
  1192. assert (buildNormalizedPath(`\\server\share\foo\bar`, `..\..\baz`) == `\\server\share\baz`);
  1193. assert (buildNormalizedPath(`\\server\share\foo\bar`, `..\.\\baz\..`, `wee\`) == `\\server\share\foo\wee`);
  1194. static assert (buildNormalizedPath(`\foo\..\..\`, `bar\baz`) == `\bar\baz`);
  1195. // Examples in docs:
  1196. assert (buildNormalizedPath(`c:\foo`, `bar\baz\`) == `c:\foo\bar\baz`);
  1197. assert (buildNormalizedPath(`c:\foo`, `bar/..`) == `c:\foo`);
  1198. assert (buildNormalizedPath(`\\server\share\foo`, `..\bar`) == `\\server\share\bar`);
  1199. }
  1200. else static assert (0);
  1201. }
  1202. unittest
  1203. {
  1204. version (Posix)
  1205. {
  1206. // Trivial
  1207. assert (buildNormalizedPath("").empty);
  1208. assert (buildNormalizedPath("foo/bar") == "foo/bar");
  1209. // Correct handling of leading slashes
  1210. assert (buildNormalizedPath("/") == "/");
  1211. assert (buildNormalizedPath("///") == "/");
  1212. assert (buildNormalizedPath("////") == "/");
  1213. assert (buildNormalizedPath("/foo/bar") == "/foo/bar");
  1214. assert (buildNormalizedPath("//foo/bar") == "/foo/bar");
  1215. assert (buildNormalizedPath("///foo/bar") == "/foo/bar");
  1216. assert (buildNormalizedPath("////foo/bar") == "/foo/bar");
  1217. // Correct handling of single-dot symbol (current directory)
  1218. assert (buildNormalizedPath("/./foo") == "/foo");
  1219. assert (buildNormalizedPath("/foo/./bar") == "/foo/bar");
  1220. assert (buildNormalizedPath("./foo") == "foo");
  1221. assert (buildNormalizedPath("././foo") == "foo");
  1222. assert (buildNormalizedPath("foo/././bar") == "foo/bar");
  1223. // Correct handling of double-dot symbol (previous directory)
  1224. assert (buildNormalizedPath("/foo/../bar") == "/bar");
  1225. assert (buildNormalizedPath("/foo/../../bar") == "/bar");
  1226. assert (buildNormalizedPath("/../foo") == "/foo");
  1227. assert (buildNormalizedPath("/../../foo") == "/foo");
  1228. assert (buildNormalizedPath("/foo/..") == "/");
  1229. assert (buildNormalizedPath("/foo/../..") == "/");
  1230. assert (buildNormalizedPath("foo/../bar") == "bar");
  1231. assert (buildNormalizedPath("foo/../../bar") == "../bar");
  1232. assert (buildNormalizedPath("../foo") == "../foo");
  1233. assert (buildNormalizedPa