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