comparison lphobos/std/path.d @ 473:373489eeaf90

Applied downs' lphobos update
author Tomas Lindquist Olsen <tomas.l.olsen@gmail.com>
date Mon, 04 Aug 2008 19:28:49 +0200
parents
children 88e23f8c2354
comparison
equal deleted inserted replaced
472:15c804b6ce77 473:373489eeaf90
1
2 /**
3 * Macros:
4 * WIKI = Phobos/StdPath
5 * Copyright:
6 * Placed into public domain.
7 * www.digitalmars.com
8 *
9 * Grzegorz Adam Hankiewicz added some documentation.
10 *
11 * This module is used to parse file names. All the operations
12 * work only on strings; they don't perform any input/output
13 * operations. This means that if a path contains a directory name
14 * with a dot, functions like getExt() will work with it just as
15 * if it was a file. To differentiate these cases,
16 * use the std.file module first (i.e. std.file.isDir()).
17 */
18
19 /* NOTE: This file has been patched from the original DMD distribution to
20 work with the GDC compiler.
21
22 Modified by David Friedman, March 2006
23 */
24
25 module std.path;
26
27 //debug=path; // uncomment to turn on debugging printf's
28 //private import std.stdio;
29
30 import std.string;
31
32 version(Unix)
33 {
34 import std.c.stdlib;
35 version(linux) import std.c.linux.linux;
36 else import std.c.unix.unix;
37 import std.outofmemory;
38 /*extern(C) struct passwd
39 {
40 char* pw_name, pw_passwd; uint pw_uid, pw_gid; char* pw_gecos, pw_dir, pw_shell;
41 }*/
42 }
43
44 version(Windows)
45 {
46
47 /** String used to separate directory names in a path. Under
48 * Windows this is a backslash, under Linux a slash. */
49 const char[1] sep = "\\";
50 /** Alternate version of sep[] used in Windows (a slash). Under
51 * Linux this is empty. */
52 const char[1] altsep = "/";
53 /** Path separator string. A semi colon under Windows, a colon
54 * under Linux. */
55 const char[1] pathsep = ";";
56 /** String used to separate lines, \r\n under Windows and \n
57 * under Linux. */
58 const char[2] linesep = "\r\n"; /// String used to separate lines.
59 const char[1] curdir = "."; /// String representing the current directory.
60 const char[2] pardir = ".."; /// String representing the parent directory.
61 }
62 else version(Unix)
63 {
64 /** String used to separate directory names in a path. Under
65 * Windows this is a backslash, under Linux a slash. */
66 const char[1] sep = "/";
67 /** Alternate version of sep[] used in Windows (a slash). Under
68 * Linux this is empty. */
69 const char[0] altsep;
70 /** Path separator string. A semi colon under Windows, a colon
71 * under Linux. */
72 const char[1] pathsep = ":";
73 /** String used to separate lines, \r\n under Windows and \n
74 * under Linux. */
75 const char[1] linesep = "\n";
76 const char[1] curdir = "."; /// String representing the current directory.
77 const char[2] pardir = ".."; /// String representing the parent directory.
78 }
79 else
80 {
81 static assert(0);
82 }
83
84 /*****************************
85 * Compare file names.
86 * Returns:
87 * <table border=1 cellpadding=4 cellspacing=0>
88 * <tr> <td> &lt; 0 <td> filename1 &lt; filename2
89 * <tr> <td> = 0 <td> filename1 == filename2
90 * <tr> <td> &gt; 0 <td> filename1 &gt; filename2
91 * </table>
92 */
93
94 version (Windows) alias std.string.icmp fcmp;
95
96 version (Unix) alias std.string.cmp fcmp;
97
98 /**************************
99 * Extracts the extension from a filename or path.
100 *
101 * This function will search fullname from the end until the
102 * first dot, path separator or first character of fullname is
103 * reached. Under Windows, the drive letter separator (<i>colon</i>)
104 * also terminates the search.
105 *
106 * Returns: If a dot was found, characters to its right are
107 * returned. If a path separator was found, or fullname didn't
108 * contain any dots or path separators, returns null.
109 *
110 * Throws: Nothing.
111 *
112 * Examples:
113 * -----
114 * version(Win32)
115 * {
116 * getExt(r"d:\path\foo.bat") // "bat"
117 * getExt(r"d:\path.two\bar") // null
118 * }
119 * version(linux)
120 * {
121 * getExt(r"/home/user.name/bar.") // ""
122 * getExt(r"d:\\path.two\\bar") // "two\\bar"
123 * getExt(r"/home/user/.resource") // "resource"
124 * }
125 * -----
126 */
127
128 char[] getExt(char[] fullname)
129 {
130 size_t i;
131
132 i = fullname.length;
133 while (i > 0)
134 {
135 if (fullname[i - 1] == '.')
136 return fullname[i .. fullname.length];
137 i--;
138 version(Win32)
139 {
140 if (fullname[i] == ':' || fullname[i] == '\\')
141 break;
142 }
143 else version(Unix)
144 {
145 if (fullname[i] == '/')
146 break;
147 }
148 }
149 return null;
150 }
151
152 unittest
153 {
154 debug(path) printf("path.getExt.unittest\n");
155 int i;
156 char[] result;
157
158 version (Win32)
159 result = getExt("d:\\path\\foo.bat");
160 version (Unix)
161 result = getExt("/path/foo.bat");
162 i = cmp(result, "bat");
163 assert(i == 0);
164
165 version (Win32)
166 result = getExt("d:\\path\\foo.");
167 version (Unix)
168 result = getExt("d/path/foo.");
169 i = cmp(result, "");
170 assert(i == 0);
171
172 version (Win32)
173 result = getExt("d:\\path\\foo");
174 version (Unix)
175 result = getExt("d/path/foo");
176 i = cmp(result, "");
177 assert(i == 0);
178
179 version (Win32)
180 result = getExt("d:\\path.bar\\foo");
181 version (Unix)
182 result = getExt("/path.bar/foo");
183
184 i = cmp(result, "");
185 assert(i == 0);
186
187 result = getExt("foo");
188 i = cmp(result, "");
189 assert(i == 0);
190 }
191
192 /**************************
193 * Returns the extensionless version of a filename or path.
194 *
195 * This function will search fullname from the end until the
196 * first dot, path separator or first character of fullname is
197 * reached. Under Windows, the drive letter separator (<i>colon</i>)
198 * also terminates the search.
199 *
200 * Returns: If a dot was found, characters to its left are
201 * returned. If a path separator was found, or fullname didn't
202 * contain any dots or path separators, returns null.
203 *
204 * Throws: Nothing.
205 *
206 * Examples:
207 * -----
208 * version(Win32)
209 * {
210 * getName(r"d:\path\foo.bat") => "d:\path\foo"
211 * getName(r"d:\path.two\bar") => null
212 * }
213 * version(linux)
214 * {
215 * getName("/home/user.name/bar.") => "/home/user.name/bar"
216 * getName(r"d:\path.two\bar") => "d:\path"
217 * getName("/home/user/.resource") => "/home/user/"
218 * }
219 * -----
220 */
221
222 char[] getName(char[] fullname)
223 {
224 size_t i;
225
226 i = fullname.length;
227 while (i > 0)
228 {
229 if (fullname[i - 1] == '.')
230 return fullname[0 .. i - 1];
231 i--;
232 version(Win32)
233 {
234 if (fullname[i] == ':' || fullname[i] == '\\')
235 break;
236 }
237 version(Unix)
238 {
239 if (fullname[i] == '/')
240 break;
241 }
242 }
243 return null;
244 }
245
246 unittest
247 {
248 debug(path) printf("path.getName.unittest\n");
249 int i;
250 char[] result;
251
252 result = getName("foo.bar");
253 i = cmp(result, "foo");
254 assert(i == 0);
255
256 result = getName("d:\\path.two\\bar");
257 version (Win32)
258 i = cmp(result, null);
259 version (linux)
260 i = cmp(result, "d:\\path");
261 assert(i == 0);
262 }
263
264 /**************************
265 * Extracts the base name of a path.
266 *
267 * This function will search fullname from the end until the
268 * first path separator or first character of fullname is
269 * reached. Under Windows, the drive letter separator (<i>colon</i>)
270 * also terminates the search.
271 *
272 * Returns: If a path separator was found, all the characters to its
273 * right are returned. Otherwise, fullname is returned.
274 *
275 * Throws: Nothing.
276 *
277 * Examples:
278 * -----
279 * version(Win32)
280 * {
281 * getBaseName(r"d:\path\foo.bat") => "foo.bat"
282 * }
283 * version(linux)
284 * {
285 * getBaseName("/home/user.name/bar.") => "bar."
286 * }
287 * -----
288 */
289
290 char[] getBaseName(char[] fullname)
291 out (result)
292 {
293 assert(result.length <= fullname.length);
294 }
295 body
296 {
297 size_t i;
298
299 for (i = fullname.length; i > 0; i--)
300 {
301 version(Win32)
302 {
303 if (fullname[i - 1] == ':' || fullname[i - 1] == '\\')
304 break;
305 }
306 else version(Unix)
307 {
308 if (fullname[i - 1] == '/')
309 break;
310 }
311 }
312 return fullname[i .. fullname.length];
313 }
314
315 unittest
316 {
317 debug(path) printf("path.getBaseName.unittest\n");
318 int i;
319 char[] result;
320
321 version (Windows)
322 result = getBaseName("d:\\path\\foo.bat");
323 version (Unix)
324 result = getBaseName("/path/foo.bat");
325 //printf("result = '%.*s'\n", result);
326 i = cmp(result, "foo.bat");
327 assert(i == 0);
328
329 version (Windows)
330 result = getBaseName("a\\b");
331 version (Unix)
332 result = getBaseName("a/b");
333 i = cmp(result, "b");
334 assert(i == 0);
335 }
336
337
338 /**************************
339 * Extracts the directory part of a path.
340 *
341 * This function will search fullname from the end until the
342 * first path separator or first character of fullname is
343 * reached. Under Windows, the drive letter separator (<i>colon</i>)
344 * also terminates the search.
345 *
346 * Returns: If a path separator was found, all the characters to its
347 * left are returned. Otherwise, fullname is returned.
348 *
349 * Under Windows, the found path separator will be included in the
350 * returned string if it is preceeded by a colon.
351 *
352 * Throws: Nothing.
353 *
354 * Examples:
355 * -----
356 * version(Win32)
357 * {
358 * getDirName(r"d:\path\foo.bat") => "d:\path"
359 * getDirName(getDirName(r"d:\path\foo.bat")) => "d:\"
360 * }
361 * version(linux)
362 * {
363 * getDirName("/home/user") => "/home"
364 * getDirName(getDirName("/home/user")) => ""
365 * }
366 * -----
367 */
368
369 char[] getDirName(char[] fullname)
370 out (result)
371 {
372 assert(result.length <= fullname.length);
373 }
374 body
375 {
376 size_t i;
377
378 for (i = fullname.length; i > 0; i--)
379 {
380 version(Win32)
381 {
382 if (fullname[i - 1] == ':')
383 break;
384 if (fullname[i - 1] == '\\')
385 { i--;
386 break;
387 }
388 }
389 else version(Unix)
390 {
391 if (fullname[i - 1] == '/')
392 { i--;
393 break;
394 }
395 }
396 }
397 return fullname[0 .. i];
398 }
399
400
401 /********************************
402 * Extracts the drive letter of a path.
403 *
404 * This function will search fullname for a colon from the beginning.
405 *
406 * Returns: If a colon is found, all the characters to its left
407 * plus the colon are returned. Otherwise, null is returned.
408 *
409 * Under Linux, this function always returns null immediately.
410 *
411 * Throws: Nothing.
412 *
413 * Examples:
414 * -----
415 * getDrive(r"d:\path\foo.bat") => "d:"
416 * -----
417 */
418
419 char[] getDrive(char[] fullname)
420 out (result)
421 {
422 assert(result.length <= fullname.length);
423 }
424 body
425 {
426 version(Win32)
427 {
428 size_t i;
429
430 for (i = 0; i < fullname.length; i++)
431 {
432 if (fullname[i] == ':')
433 return fullname[0 .. i + 1];
434 }
435 return null;
436 }
437 else version(Unix)
438 {
439 return null;
440 }
441 }
442
443 /****************************
444 * Appends a default extension to a filename.
445 *
446 * This function first searches filename for an extension and
447 * appends ext if there is none. ext should not have any leading
448 * dots, one will be inserted between filename and ext if filename
449 * doesn't already end with one.
450 *
451 * Returns: filename if it contains an extension, otherwise filename
452 * + ext.
453 *
454 * Throws: Nothing.
455 *
456 * Examples:
457 * -----
458 * defaultExt("foo.txt", "raw") => "foo.txt"
459 * defaultExt("foo.", "raw") => "foo.raw"
460 * defaultExt("bar", "raw") => "bar.raw"
461 * -----
462 */
463
464 char[] defaultExt(char[] filename, char[] ext)
465 {
466 char[] existing;
467
468 existing = getExt(filename);
469 if (existing.length == 0)
470 {
471 // Check for filename ending in '.'
472 if (filename.length && filename[filename.length - 1] == '.')
473 filename ~= ext;
474 else
475 filename = filename ~ "." ~ ext;
476 }
477 return filename;
478 }
479
480
481 /****************************
482 * Adds or replaces an extension to a filename.
483 *
484 * This function first searches filename for an extension and
485 * replaces it with ext if found. If there is no extension, ext
486 * will be appended. ext should not have any leading dots, one will
487 * be inserted between filename and ext if filename doesn't already
488 * end with one.
489 *
490 * Returns: filename + ext if filename is extensionless. Otherwise
491 * strips filename's extension off, appends ext and returns the
492 * result.
493 *
494 * Throws: Nothing.
495 *
496 * Examples:
497 * -----
498 * addExt("foo.txt", "raw") => "foo.raw"
499 * addExt("foo.", "raw") => "foo.raw"
500 * addExt("bar", "raw") => "bar.raw"
501 * -----
502 */
503
504 char[] addExt(char[] filename, char[] ext)
505 {
506 char[] existing;
507
508 existing = getExt(filename);
509 if (existing.length == 0)
510 {
511 // Check for filename ending in '.'
512 if (filename.length && filename[filename.length - 1] == '.')
513 filename ~= ext;
514 else
515 filename = filename ~ "." ~ ext;
516 }
517 else
518 {
519 filename = filename[0 .. filename.length - existing.length] ~ ext;
520 }
521 return filename;
522 }
523
524
525 /*************************************
526 * Checks if path is absolute.
527 *
528 * Returns: non-zero if the path starts from the root directory (Linux) or
529 * drive letter and root directory (Windows),
530 * zero otherwise.
531 *
532 * Throws: Nothing.
533 *
534 * Examples:
535 * -----
536 * version(Win32)
537 * {
538 * isabs(r"relative\path") => 0
539 * isabs(r"\relative\path") => 0
540 * isabs(r"d:\absolute") => 1
541 * }
542 * version(linux)
543 * {
544 * isabs("/home/user") => 1
545 * isabs("foo") => 0
546 * }
547 * -----
548 */
549
550 int isabs(char[] path)
551 {
552 char[] d = getDrive(path);
553
554 version (Windows)
555 {
556 return d.length && d.length < path.length && path[d.length] == sep[0];
557 }
558 else
559 return d.length < path.length && path[d.length] == sep[0];
560 }
561
562 unittest
563 {
564 debug(path) printf("path.isabs.unittest\n");
565
566 version (Windows)
567 {
568 assert(isabs(r"relative\path") == 0);
569 assert(isabs(r"\relative\path") == 0);
570 assert(isabs(r"d:\absolute") == 1);
571 }
572 version (linux)
573 {
574 assert(isabs("/home/user") == 1);
575 assert(isabs("foo") == 0);
576 }
577 }
578
579 /*************************************
580 * Joins two path components.
581 *
582 * If p1 doesn't have a trailing path separator, one will be appended
583 * to it before concatting p2.
584 *
585 * Returns: p1 ~ p2. However, if p2 is an absolute path, only p2
586 * will be returned.
587 *
588 * Throws: Nothing.
589 *
590 * Examples:
591 * -----
592 * version(Win32)
593 * {
594 * join(r"c:\foo", "bar") => "c:\foo\bar"
595 * join("foo", r"d:\bar") => "d:\bar"
596 * }
597 * version(linux)
598 * {
599 * join("/foo/", "bar") => "/foo/bar"
600 * join("/foo", "/bar") => "/bar"
601 * }
602 * -----
603 */
604
605 char[] join(char[] p1, char[] p2)
606 {
607 if (!p2.length)
608 return p1;
609 if (!p1.length)
610 return p2;
611
612 char[] p;
613 char[] d1;
614
615 version(Win32)
616 {
617 if (getDrive(p2))
618 {
619 p = p2;
620 }
621 else
622 {
623 d1 = getDrive(p1);
624 if (p1.length == d1.length)
625 {
626 p = p1 ~ p2;
627 }
628 else if (p2[0] == '\\')
629 {
630 if (d1.length == 0)
631 p = p2;
632 else if (p1[p1.length - 1] == '\\')
633 p = p1 ~ p2[1 .. p2.length];
634 else
635 p = p1 ~ p2;
636 }
637 else if (p1[p1.length - 1] == '\\')
638 {
639 p = p1 ~ p2;
640 }
641 else
642 {
643 p = p1 ~ sep ~ p2;
644 }
645 }
646 }
647 else version(Unix)
648 {
649 if (p2[0] == sep[0])
650 {
651 p = p2;
652 }
653 else if (p1[p1.length - 1] == sep[0])
654 {
655 p = p1 ~ p2;
656 }
657 else
658 {
659 p = p1 ~ sep ~ p2;
660 }
661 }
662 return p;
663 }
664
665 unittest
666 {
667 debug(path) printf("path.join.unittest\n");
668
669 char[] p;
670 int i;
671
672 p = join("foo", "bar");
673 version (Win32)
674 i = cmp(p, "foo\\bar");
675 version (Unix)
676 i = cmp(p, "foo/bar");
677 assert(i == 0);
678
679 version (Win32)
680 { p = join("foo\\", "bar");
681 i = cmp(p, "foo\\bar");
682 }
683 version (Unix)
684 { p = join("foo/", "bar");
685 i = cmp(p, "foo/bar");
686 }
687 assert(i == 0);
688
689 version (Win32)
690 { p = join("foo", "\\bar");
691 i = cmp(p, "\\bar");
692 }
693 version (Unix)
694 { p = join("foo", "/bar");
695 i = cmp(p, "/bar");
696 }
697 assert(i == 0);
698
699 version (Win32)
700 { p = join("foo\\", "\\bar");
701 i = cmp(p, "\\bar");
702 }
703 version (Unix)
704 { p = join("foo/", "/bar");
705 i = cmp(p, "/bar");
706 }
707 assert(i == 0);
708
709 version(Win32)
710 {
711 p = join("d:", "bar");
712 i = cmp(p, "d:bar");
713 assert(i == 0);
714
715 p = join("d:\\", "bar");
716 i = cmp(p, "d:\\bar");
717 assert(i == 0);
718
719 p = join("d:\\", "\\bar");
720 i = cmp(p, "d:\\bar");
721 assert(i == 0);
722
723 p = join("d:\\foo", "bar");
724 i = cmp(p, "d:\\foo\\bar");
725 assert(i == 0);
726
727 p = join("d:", "\\bar");
728 i = cmp(p, "d:\\bar");
729 assert(i == 0);
730
731 p = join("foo", "d:");
732 i = cmp(p, "d:");
733 assert(i == 0);
734
735 p = join("foo", "d:\\");
736 i = cmp(p, "d:\\");
737 assert(i == 0);
738
739 p = join("foo", "d:\\bar");
740 i = cmp(p, "d:\\bar");
741 assert(i == 0);
742 }
743 }
744
745
746 /*********************************
747 * Matches filename characters.
748 *
749 * Under Windows, the comparison is done ignoring case. Under Linux
750 * an exact match is performed.
751 *
752 * Returns: non zero if c1 matches c2, zero otherwise.
753 *
754 * Throws: Nothing.
755 *
756 * Examples:
757 * -----
758 * version(Win32)
759 * {
760 * fncharmatch('a', 'b') => 0
761 * fncharmatch('A', 'a') => 1
762 * }
763 * version(linux)
764 * {
765 * fncharmatch('a', 'b') => 0
766 * fncharmatch('A', 'a') => 0
767 * }
768 * -----
769 */
770
771 int fncharmatch(dchar c1, dchar c2)
772 {
773 version (Win32)
774 {
775 if (c1 != c2)
776 {
777 if ('A' <= c1 && c1 <= 'Z')
778 c1 += cast(char)'a' - 'A';
779 if ('A' <= c2 && c2 <= 'Z')
780 c2 += cast(char)'a' - 'A';
781 return c1 == c2;
782 }
783 return true;
784 }
785 else version (Unix)
786 {
787 return c1 == c2;
788 }
789 /* this is filesystem-dependent, figure out the filesystem?
790 else version (GNU)
791 {
792 // %% figure out filesystem?
793 return c1 == c2;
794 }
795 */
796 }
797
798 /************************************
799 * Matches a pattern against a filename.
800 *
801 * Some characters of pattern have special a meaning (they are
802 * <i>meta-characters</i>) and <b>can't</b> be escaped. These are:
803 * <p><table>
804 * <tr><td><b>*</b></td>
805 * <td>Matches 0 or more instances of any character.</td></tr>
806 * <tr><td><b>?</b></td>
807 * <td>Matches exactly one instances of any character.</td></tr>
808 * <tr><td><b>[</b><i>chars</i><b>]</b></td>
809 * <td>Matches one instance of any character that appears
810 * between the brackets.</td></tr>
811 * <tr><td><b>[!</b><i>chars</i><b>]</b></td>
812 * <td>Matches one instance of any character that does not appear
813 * between the brackets after the exclamation mark.</td></tr>
814 * </table><p>
815 * Internally individual character comparisons are done calling
816 * fncharmatch(), so its rules apply here too. Note that path
817 * separators and dots don't stop a meta-character from matching
818 * further portions of the filename.
819 *
820 * Returns: non zero if pattern matches filename, zero otherwise.
821 *
822 * See_Also: fncharmatch().
823 *
824 * Throws: Nothing.
825 *
826 * Examples:
827 * -----
828 * version(Win32)
829 * {
830 * fnmatch("foo.bar", "*") => 1
831 * fnmatch(r"foo/foo\bar", "f*b*r") => 1
832 * fnmatch("foo.bar", "f?bar") => 0
833 * fnmatch("Goo.bar", "[fg]???bar") => 1
834 * fnmatch(r"d:\foo\bar", "d*foo?bar") => 1
835 * }
836 * version(linux)
837 * {
838 * fnmatch("Go*.bar", "[fg]???bar") => 0
839 * fnmatch("/foo*home/bar", "?foo*bar") => 1
840 * fnmatch("foobar", "foo?bar") => 1
841 * }
842 * -----
843 */
844
845 int fnmatch(char[] filename, char[] pattern)
846 in
847 {
848 // Verify that pattern[] is valid
849 size_t i;
850 int inbracket = false;
851
852 for (i = 0; i < pattern.length; i++)
853 {
854 switch (pattern[i])
855 {
856 case '[':
857 assert(!inbracket);
858 inbracket = true;
859 break;
860
861 case ']':
862 assert(inbracket);
863 inbracket = false;
864 break;
865
866 default:
867 break;
868 }
869 }
870 }
871 body
872 {
873 size_t pi;
874 size_t ni;
875 char pc;
876 char nc;
877 size_t j;
878 int not;
879 int anymatch;
880
881 ni = 0;
882 for (pi = 0; pi < pattern.length; pi++)
883 {
884 pc = pattern[pi];
885 switch (pc)
886 {
887 case '*':
888 if (pi + 1 == pattern.length)
889 goto match;
890 for (j = ni; j < filename.length; j++)
891 {
892 if (fnmatch(filename[j .. filename.length], pattern[pi + 1 .. pattern.length]))
893 goto match;
894 }
895 goto nomatch;
896
897 case '?':
898 if (ni == filename.length)
899 goto nomatch;
900 ni++;
901 break;
902
903 case '[':
904 if (ni == filename.length)
905 goto nomatch;
906 nc = filename[ni];
907 ni++;
908 not = 0;
909 pi++;
910 if (pattern[pi] == '!')
911 { not = 1;
912 pi++;
913 }
914 anymatch = 0;
915 while (1)
916 {
917 pc = pattern[pi];
918 if (pc == ']')
919 break;
920 if (!anymatch && fncharmatch(nc, pc))
921 anymatch = 1;
922 pi++;
923 }
924 if (!(anymatch ^ not))
925 goto nomatch;
926 break;
927
928 default:
929 if (ni == filename.length)
930 goto nomatch;
931 nc = filename[ni];
932 if (!fncharmatch(pc, nc))
933 goto nomatch;
934 ni++;
935 break;
936 }
937 }
938 if (ni < filename.length)
939 goto nomatch;
940
941 match:
942 return true;
943
944 nomatch:
945 return false;
946 }
947
948 unittest
949 {
950 debug(path) printf("path.fnmatch.unittest\n");
951
952 version (Win32)
953 assert(fnmatch("foo", "Foo"));
954 version (Unix)
955 assert(!fnmatch("foo", "Foo"));
956 assert(fnmatch("foo", "*"));
957 assert(fnmatch("foo.bar", "*"));
958 assert(fnmatch("foo.bar", "*.*"));
959 assert(fnmatch("foo.bar", "foo*"));
960 assert(fnmatch("foo.bar", "f*bar"));
961 assert(fnmatch("foo.bar", "f*b*r"));
962 assert(fnmatch("foo.bar", "f???bar"));
963 assert(fnmatch("foo.bar", "[fg]???bar"));
964 assert(fnmatch("foo.bar", "[!gh]*bar"));
965
966 assert(!fnmatch("foo", "bar"));
967 assert(!fnmatch("foo", "*.*"));
968 assert(!fnmatch("foo.bar", "f*baz"));
969 assert(!fnmatch("foo.bar", "f*b*x"));
970 assert(!fnmatch("foo.bar", "[gh]???bar"));
971 assert(!fnmatch("foo.bar", "[!fg]*bar"));
972 assert(!fnmatch("foo.bar", "[fg]???baz"));
973 }
974
975 /**
976 * Performs tilde expansion in paths.
977 *
978 * There are two ways of using tilde expansion in a path. One
979 * involves using the tilde alone or followed by a path separator. In
980 * this case, the tilde will be expanded with the value of the
981 * environment variable <i>HOME</i>. The second way is putting
982 * a username after the tilde (i.e. <tt>~john/Mail</tt>). Here,
983 * the username will be searched for in the user database
984 * (i.e. <tt>/etc/passwd</tt> on Unix systems) and will expand to
985 * whatever path is stored there. The username is considered the
986 * string after the tilde ending at the first instance of a path
987 * separator.
988 *
989 * Note that using the <i>~user</i> syntax may give different
990 * values from just <i>~</i> if the environment variable doesn't
991 * match the value stored in the user database.
992 *
993 * When the environment variable version is used, the path won't
994 * be modified if the environment variable doesn't exist or it
995 * is empty. When the database version is used, the path won't be
996 * modified if the user doesn't exist in the database or there is
997 * not enough memory to perform the query.
998 *
999 * Returns: inputPath with the tilde expanded, or just inputPath
1000 * if it could not be expanded.
1001 * For Windows, expandTilde() merely returns its argument inputPath.
1002 *
1003 * Throws: std.outofmemory.OutOfMemoryException if there is not enough
1004 * memory to perform
1005 * the database lookup for the <i>~user</i> syntax.
1006 *
1007 * Examples:
1008 * -----
1009 * import std.path;
1010 *
1011 * void process_file(char[] filename)
1012 * {
1013 * char[] path = expandTilde(filename);
1014 * ...
1015 * }
1016 * -----
1017 *
1018 * -----
1019 * import std.path;
1020 *
1021 * const char[] RESOURCE_DIR_TEMPLATE = "~/.applicationrc";
1022 * char[] RESOURCE_DIR; // This gets expanded in main().
1023 *
1024 * int main(char[][] args)
1025 * {
1026 * RESOURCE_DIR = expandTilde(RESOURCE_DIR_TEMPLATE);
1027 * ...
1028 * }
1029 * -----
1030 * Version: Available since v0.143.
1031 * Authors: Grzegorz Adam Hankiewicz, Thomas Kühne.
1032 */
1033
1034 char[] expandTilde(char[] inputPath)
1035 {
1036 version(Unix)
1037 {
1038 static assert(sep.length == 1);
1039
1040 // Return early if there is no tilde in path.
1041 if (inputPath.length < 1 || inputPath[0] != '~')
1042 return inputPath;
1043
1044 if (inputPath.length == 1 || inputPath[1] == sep[0])
1045 return expandFromEnvironment(inputPath);
1046 else
1047 return expandFromDatabase(inputPath);
1048 }
1049 else version(Windows)
1050 {
1051 // Put here real windows implementation.
1052 return inputPath;
1053 }
1054 else
1055 {
1056 static assert(0); // Guard. Implement on other platforms.
1057 }
1058 }
1059
1060
1061 unittest
1062 {
1063 debug(path) printf("path.expandTilde.unittest\n");
1064
1065 version (Unix)
1066 {
1067 // Retrieve the current home variable.
1068 char* c_home = getenv("HOME");
1069
1070 // Testing when there is no environment variable.
1071 unsetenv("HOME");
1072 assert(expandTilde("~/") == "~/");
1073 assert(expandTilde("~") == "~");
1074
1075 // Testing when an environment variable is set.
1076 int ret = setenv("HOME", "dmd/test\0", 1);
1077 assert(ret == 0);
1078 assert(expandTilde("~/") == "dmd/test/");
1079 assert(expandTilde("~") == "dmd/test");
1080
1081 // The same, but with a variable ending in a slash.
1082 ret = setenv("HOME", "dmd/test/\0", 1);
1083 assert(ret == 0);
1084 assert(expandTilde("~/") == "dmd/test/");
1085 assert(expandTilde("~") == "dmd/test");
1086
1087 // Recover original HOME variable before continuing.
1088 if (c_home)
1089 setenv("HOME", c_home, 1);
1090 else
1091 unsetenv("HOME");
1092
1093 // Test user expansion for root. Are there unices without /root?
1094 /*
1095 assert(expandTilde("~root") == "/root");
1096 assert(expandTilde("~root/") == "/root/");
1097 */
1098 assert(expandTilde("~Idontexist/hey") == "~Idontexist/hey");
1099 }
1100 }
1101
1102 version (Unix)
1103 {
1104
1105 /**
1106 * Replaces the tilde from path with the environment variable HOME.
1107 */
1108 private char[] expandFromEnvironment(char[] path)
1109 {
1110 assert(path.length >= 1);
1111 assert(path[0] == '~');
1112
1113 // Get HOME and use that to replace the tilde.
1114 char* home = getenv("HOME");
1115 if (home == null)
1116 return path;
1117
1118 return combineCPathWithDPath(home, path, 1);
1119 }
1120
1121
1122 /**
1123 * Joins a path from a C string to the remainder of path.
1124 *
1125 * The last path separator from c_path is discarded. The result
1126 * is joined to path[char_pos .. length] if char_pos is smaller
1127 * than length, otherwise path is not appended to c_path.
1128 */
1129 private char[] combineCPathWithDPath(char* c_path, char[] path, int char_pos)
1130 {
1131 assert(c_path != null);
1132 assert(path.length > 0);
1133 assert(char_pos >= 0);
1134
1135 // Search end of C string
1136 size_t end = std.string.strlen(c_path);
1137
1138 // Remove trailing path separator, if any
1139 if (end && c_path[end - 1] == sep[0])
1140 end--;
1141
1142 // Create our own copy, as lifetime of c_path is undocumented
1143 char[] cp = c_path[0 .. end].dup;
1144
1145 // Do we append something from path?
1146 if (char_pos < path.length)
1147 cp ~= path[char_pos .. length];
1148
1149 return cp;
1150 }
1151
1152
1153 /**
1154 * Replaces the tilde from path with the path from the user database.
1155 */
1156 private string expandFromDatabase(string path)
1157 {
1158 assert(path.length > 2 || (path.length == 2 && path[1] != sep[0]));
1159 assert(path[0] == '~');
1160
1161 // Extract username, searching for path separator.
1162 string username;
1163 ptrdiff_t last_char = find(path, sep[0]);
1164
1165 if (last_char == -1)
1166 {
1167 username = path[1 .. length] ~ '\0';
1168 last_char = username.length + 1;
1169 }
1170 else
1171 {
1172 username = path[1 .. last_char] ~ '\0';
1173 }
1174 assert(last_char > 1);
1175
1176 version (GNU_Unix_Have_getpwnam_r)
1177 {
1178
1179 // Reserve C memory for the getpwnam_r() function.
1180 passwd result;
1181 int extra_memory_size = 5 * 1024;
1182 void* extra_memory;
1183
1184 while (1)
1185 {
1186 extra_memory = std.c.stdlib.malloc(extra_memory_size);
1187 if (extra_memory == null)
1188 goto Lerror;
1189
1190 // Obtain info from database.
1191 passwd *verify;
1192 std.c.stdlib.setErrno(0);
1193 if (getpwnam_r(username.ptr, &result, cast(char*) extra_memory, extra_memory_size,
1194 &verify) == 0)
1195 {
1196 // Failure if verify doesn't point at result.
1197 if (verify != &result)
1198 // username is not found, so return path[]
1199 goto Lnotfound;
1200 break;
1201 }
1202
1203 switch (std.c.stdlib.getErrno()) {
1204 case 0:
1205 case ENOENT:
1206 case ESRCH:
1207 case EBADF:
1208 case EPERM:
1209 goto Lnotfound;
1210 case ERANGE:
1211 break;
1212 default:
1213 // not just out of memory: EMFILE, ENFILE too
1214 goto Lerror;
1215 }
1216
1217 // extra_memory isn't large enough
1218 std.c.stdlib.free(extra_memory);
1219 extra_memory_size *= 2;
1220 }
1221
1222 path = combineCPathWithDPath(result.pw_dir, path, last_char);
1223
1224 Lnotfound:
1225 std.c.stdlib.free(extra_memory);
1226 return path;
1227
1228 Lerror:
1229 // Errors are going to be caused by running out of memory
1230 if (extra_memory)
1231 std.c.stdlib.free(extra_memory);
1232 _d_OutOfMemory();
1233 return null;
1234
1235 }
1236 else
1237 {
1238 passwd * result;
1239
1240 /* This does not guarantee another thread will not
1241 use getpwnam at the same time */
1242 synchronized {
1243 result = getpwnam(username.ptr);
1244 }
1245
1246 if (result)
1247 path = combineCPathWithDPath(result.pw_dir, path, last_char);
1248 return path;
1249 }
1250 }
1251
1252 }