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