1 /++
2 Utility to aid in script-like programs.
3 
4 This is deliberately created as one file for easier usage
5 in script-like programs.
6 
7 Written in the D programming language.
8 Tested with DMD 2.064.2
9 Licensed under The zlib/libpng License
10 
11 Homepage:
12 https://github.com/abscissa/scriptlike
13 
14 API Reference:
15 http://semitwist.com/scriptlike
16 +/
17 
18 module scriptlike;
19 
20 // Import privately because this module wraps all the members.
21 import std.file;
22 import std.path;
23 
24 // Automatically pull in anything likely to be useful for scripts.
25 // curl is deliberately left out here because it involves an extra
26 // link dependency.
27 public import std.algorithm;
28 public import std.array;
29 public import std.bigint;
30 public import std.conv;
31 public import std.datetime;
32 public import std.exception;
33 public import std.getopt;
34 public import std.math;
35 public import std.process;
36 public import std.random;
37 public import std.range;
38 public import std.regex;
39 public import std.stdio;
40 public import std.string;
41 public import std.system;
42 public import std.traits;
43 public import std.typecons;
44 public import std.typetuple;
45 public import std.uni;
46 public import std.variant;
47 
48 /++
49 In your main(), catch this Fail exception, then output Fail.msg and
50 return an error code.
51 
52 Example:
53 
54 int main()
55 {
56 	try
57 	{
58 		// Your code here
59 	}
60 	catch(Fail e)
61 	{
62 		writeln("mytool: ERROR: ", e.msg);
63 		return 1;
64 	}
65 	
66 	return 0;
67 }
68 +/
69 class Fail : Exception
70 {
71 	this(string msg, string file=__FILE__, int line=__LINE__)
72 	{
73 		super(msg, file, line);
74 	}
75 }
76 
77 /// If you've set up your main() to handle the Fail exception (as shown in
78 /// Fail's documentation, then call this to end your program with an error
79 /// message in an exception-safe way.
80 void fail(string msg, string file=__FILE__, int line=__LINE__)
81 {
82 	throw new Fail(msg, file, line);
83 }
84 
85 //TODO: Support optional OutputRange sink as an alternative to stdout
86 /// If true, all commands will be echoed to stdout
87 bool scriptlikeTraceCommands = false;
88 
89 string escapeShellPath(T)(T str) if(isSomeString!T)
90 {
91 	string s = str.to!string();
92 
93 	if(str.canFind(' '))
94 	{
95 		version(Windows)
96 			return escapeWindowsArgument(s);
97 		else version(Posix)
98 			return escapeShellFileName(s);
99 		else
100 			static assert(0, "This platform not supported.");
101 	}
102 	else
103 		return s;
104 }
105 
106 /// Helper for creating an Ext.
107 ///
108 /// Returns either Ext!char, Ext!wchar or Ext!dchar depending on the
109 /// type of string given.
110 auto ext(T)(T str) if(isSomeString!T)
111 {
112 	return Ext!( Unqual!(ElementEncodingType!T) )(str);
113 }
114 
115 /// Represents a file extension.
116 struct Ext(C = char) if( is(C==char) || is(C==wchar) || is(C==dchar) )
117 {
118 	private immutable(C)[] str;
119 	
120 	/// Main constructor.
121 	this(immutable(C)[] extension = null)
122 	{
123 		this.str = extension;
124 	}
125 	
126 	/// Convert to string.
127 	string toString()
128 	{
129 		return str.to!string();
130 	}
131 	
132 	/// Convert to string, wstring or dstring, depending on the type of Ext.
133 	immutable(C)[] toRawString()
134 	{
135 		return str;
136 	}
137 	
138 	/// Compare using OS-specific case-sensitivity rules. If you want to force
139 	/// case-sensitive or case-insensistive, then call filenameCmp instead.
140 	int opCmp(ref const Ext!C other) const
141 	{
142 		return filenameCmp(this.str, other.str);
143 	}
144 
145 	///ditto
146 	int opCmp(Ext!C other) const
147 	{
148 		return filenameCmp(this.str, other.str);
149 	}
150 
151 	///ditto
152 	int opCmp(string other) const
153 	{
154 		return filenameCmp(this.str, other);
155 	}
156 
157 	/// Compare using OS-specific case-sensitivity rules. If you want to force
158 	/// case-sensitive or case-insensistive, then call filenameCmp instead.
159 	int opEquals(ref const Ext!C other) const
160 	{
161 		return opCmp(other) == 0;
162 	}
163 
164 	///ditto
165 	int opEquals(Ext!C other) const
166 	{
167 		return opCmp(other) == 0;
168 	}
169 
170 	///ditto
171 	int opEquals(string other) const
172 	{
173 		return opCmp(other) == 0;
174 	}
175 }
176 
177 /// Helper for creating a Path.
178 ///
179 /// Returns either Path!char, Path!wchar or Path!dchar depending on the
180 /// type of string given.
181 auto path(T)(T str = ".") if(isSomeString!T)
182 {
183 	return Path!( Unqual!(ElementEncodingType!T) )(str);
184 }
185 
186 /// Represents a filesystem path. The path is always kept normalized
187 /// automatically (as performed by buildNormalizedPathFixed).
188 ///
189 /// wchar and dchar versions not yet supported, blocked by DMD issue #12112
190 struct Path(C = char) if( is(C==char) /+|| is(C==wchar) || is(C==dchar)+/ )
191 {
192 	private immutable(C)[] str = ".";
193 	
194 	/// Main constructor.
195 	this()(const(C)[] path = ".") @safe pure nothrow
196 	{
197 		this.str = buildNormalizedPathFixed(path);
198 	}
199 	
200 	/++
201 	Convert from one type of Path to another.
202 	Example:
203 		auto pathStr  = Path("foo");
204 		auto pathWStr = Path!wchar(pathStr);
205 		auto pathDStr = Path!dchar(pathStr);
206 		pathStr = Path(pathWStr);
207 		pathStr = Path(pathDStr);
208 	+/
209 	this(T)(T[] path = ".") if(isSomeChar!T)
210 	{
211 		this.str = to!(immutable(C)[])( buildNormalizedPathFixed(path.to!string()) );
212 	}
213 	
214 	@trusted pure nothrow invariant()
215 	{
216 		assert(str == buildNormalizedPathFixed(str));
217 	}
218 	
219 	/// Convert to string, quoting or escaping spaces if necessary.
220 	string toString()
221 	{
222 		return escapeShellPath(str);
223 	}
224 	
225 	/// Convert to string, wstring or dstring, depending on the type of Path.
226 	/// Does NOT do any escaping, even if path contains spaces.
227 	immutable(C)[] toRawString()
228 	{
229 		return str;
230 	}
231 
232 	/// Concatenates two paths, with a directory separator in between.
233 	Path!C opBinary(string op)(Path!C rhs) if(op=="~")
234 	{
235 		Path!C newPath;
236 		newPath.str = buildNormalizedPathFixed(this.str, rhs.str);
237 		return newPath;
238 	}
239 	
240 	///ditto
241 	Path!C opBinary(string op)(const(C)[] rhs) if(op=="~")
242 	{
243 		Path!C newPath;
244 		newPath.str = buildNormalizedPathFixed(this.str, rhs);
245 		return newPath;
246 	}
247 	
248 	///ditto
249 	Path!C opBinaryRight(string op)(const(C)[] lhs) if(op=="~")
250 	{
251 		Path!C newPath;
252 		newPath.str = buildNormalizedPathFixed(lhs, this.str);
253 		return newPath;
254 	}
255 	
256 	/// Appends an extension to a path. Naturally, a directory separator
257 	/// is NOT inserted in between.
258 	Path!C opBinary(string op)(Ext!C rhs) if(op=="~")
259 	{
260 		Path!C newPath;
261 		newPath.str = this.str.setExtension(rhs.str);
262 		return newPath;
263 	}
264 	
265 	/// Appends a path to this one, with a directory separator in between.
266 	Path!C opOpAssign(string op)(Path!C rhs) if(op=="~")
267 	{
268 		str = buildNormalizedPathFixed(str, rhs.str);
269 		return this;
270 	}
271 	
272 	///ditto
273 	Path!C opOpAssign(string op)(const(C)[] rhs) if(op=="~")
274 	{
275 		str = buildNormalizedPathFixed(str, rhs);
276 		return this;
277 	}
278 	
279 	/// Appends an extension to this path. Naturally, a directory separator
280 	/// is NOT inserted in between.
281 	Path!C opOpAssign(string op)(Ext!C rhs) if(op=="~")
282 	{
283 		str = str.setExtension(rhs.str);
284 		return this;
285 	}
286 	
287 	/// Compare using OS-specific case-sensitivity rules. If you want to force
288 	/// case-sensitive or case-insensistive, then call filenameCmp instead.
289 	int opCmp(ref const Path!C other) const
290 	{
291 		return filenameCmp(this.str, other.str);
292 	}
293 
294 	///ditto
295 	int opCmp(Path!C other) const
296 	{
297 		return filenameCmp(this.str, other.str);
298 	}
299 
300 	///ditto
301 	int opCmp(string other) const
302 	{
303 		return filenameCmp(this.str, other);
304 	}
305 
306 	/// Compare using OS-specific case-sensitivity rules. If you want to force
307 	/// case-sensitive or case-insensistive, then call filenameCmp instead.
308 	int opEquals(ref const Path!C other) const
309 	{
310 		return opCmp(other) == 0;
311 	}
312 
313 	///ditto
314 	int opEquals(Path!C other) const
315 	{
316 		return opCmp(other) == 0;
317 	}
318 
319 	///ditto
320 	int opEquals(string other) const
321 	{
322 		return opCmp(other) == 0;
323 	}
324 
325 	/// Returns the parent path, according to std.path.dirName.
326 	@property Path!C up()
327 	{
328 		return this.dirName();
329 	}
330 	
331 	/// Is this path equal to empty string?
332 	@property bool empty()
333 	{
334 		return str == "";
335 	}
336 }
337 
338 /// Convenience aliases
339 alias extOf      = extension;
340 alias stripExt   = stripExtension;   ///ditto
341 alias setExt     = setExtension;     ///ditto
342 alias defaultExt = defaultExtension; ///ditto
343 
344 /// Checks if the path exists as a directory.
345 ///
346 /// This is like std.file.isDir, but returns false instead of throwing if the
347 /// path doesn't exist.
348 bool existsAsDir()(in char[] path) @trusted
349 {
350 	return exists(path) && isDir(path);
351 }
352 ///ditto
353 bool existsAsDir(C)(in Path!C path) @trusted if(isSomeChar!C)
354 {
355 	return existsAsDir(path.str.to!string());
356 }
357 
358 /// Checks if the path exists as a file.
359 ///
360 /// This is like std.file.isFile, but returns false instead of throwing if the
361 /// path doesn't exist.
362 bool existsAsFile()(in char[] path) @trusted
363 {
364 	return exists(path) && isFile(path);
365 }
366 ///ditto
367 bool existsAsFile(C)(in Path!C path) @trusted if(isSomeChar!C)
368 {
369 	return existsAsFile(path.str.to!string());
370 }
371 
372 /// Checks if the path exists as a symlink.
373 ///
374 /// This is like std.file.isSymlink, but returns false instead of throwing if the
375 /// path doesn't exist.
376 bool existsAsSymlink()(in char[] path) @trusted
377 {
378 	return exists(path) && isSymlink(path);
379 }
380 ///ditto
381 bool existsAsSymlink(C)(in Path!C path) @trusted if(isSomeChar!C)
382 {
383 	return existsAsSymlink(path.str.to!string());
384 }
385 
386 /// Like buildNormalizedPath, but if the result is the current directory,
387 /// this returns "." instead of "". However, if all the inputs are "", or there
388 /// are no inputs, this still returns "" just like buildNormalizedPath.
389 immutable(C)[] buildNormalizedPathFixed(C)(const(C[])[] paths...)
390 	@trusted pure nothrow
391 	if(isSomeChar!C)
392 {
393 	if(all!`a==""`(paths))
394 		return "";
395 	
396 	auto result = buildNormalizedPath(paths);
397 	return result==""? "." : result;
398 }
399 
400 private void echoCommand(lazy string command)
401 {
402 	if(scriptlikeTraceCommands)
403 		writeln(command);
404 }
405 
406 /// Runs a command, through the system's command shell interpreter,
407 /// in typical shell-script style: Synchronously, with the command's
408 /// stdout/in/err automatically forwarded through your
409 /// program's stdout/in/err.
410 ///
411 /// Optionally takes a working directory to run the command from.
412 ///
413 /// The command is echoed if scriptlikeTraceCommands is true.
414 int runShell()(string command)
415 {
416 	echoCommand(command);
417 	return system(command);
418 }
419 
420 ///ditto
421 int runShell(C)(Path!C workingDirectory, string command)
422 {
423 	auto saveDir = getcwd();
424 	workingDirectory.chdir();
425 	scope(exit) saveDir.chdir();
426 	
427 	return runShell(command);
428 }
429 
430 // -- Wrappers for std.path --------------------
431 
432 /// Part of workaround for DMD Issue #12111
433 alias dirSeparator   = std.path.dirSeparator;
434 alias pathSeparator  = std.path.pathSeparator; ///ditto
435 alias isDirSeparator = std.path.isDirSeparator; ///ditto
436 alias CaseSensitive  = std.path.CaseSensitive; ///ditto
437 alias osDefaultCaseSensitivity = std.path.osDefaultCaseSensitivity; ///ditto
438 alias buildPath                = std.path.buildPath; ///ditto
439 alias buildNormalizedPath      = std.path.buildNormalizedPath; ///ditto
440 
441 /// Just like std.path.baseName, but operates on Path.
442 Path!C baseName(C)(Path!C path)
443 	@trusted pure
444 {
445 	return Path!C( path.str.baseName() );
446 }
447 
448 ///ditto
449 Path!C baseName(CaseSensitive cs = CaseSensitive.osDefault, C, C1)
450 	(Path!C path, in C1[] suffix)
451 	@safe pure
452 	if(isSomeChar!C1)
453 {
454 	return Path!C( path.str.baseName!cs(suffix) );
455 }
456 
457 /// Part of workaround for DMD Issue #12111
458 inout(C)[] baseName(C)(inout(C)[] path)
459 	@trusted pure
460 	if (isSomeChar!C)
461 {
462 	return std.path.baseName(path);
463 }
464 
465 ///ditto
466 inout(C)[] baseName(CaseSensitive cs = CaseSensitive.osDefault, C, C1)
467 	(inout(C)[] path, in C1[] suffix)
468 	@safe pure
469 	if (isSomeChar!C && isSomeChar!C1)
470 {
471 	return std.path.baseName(path, suffix);
472 }
473 
474 /// Just like std.path.dirName, but operates on Path.
475 Path!C dirName(C)(Path!C path) if(isSomeChar!C)
476 {
477 	return Path!C( path.str.dirName() );
478 }
479 
480 /// Part of workaround for DMD Issue #12111
481 C[] dirName(C)(C[] path)
482 	if (isSomeChar!C)
483 {
484 	return std.path.dirName(path);
485 }
486 
487 /// Just like std.path.rootName, but operates on Path.
488 Path!C rootName(C)(Path!C path) @safe pure nothrow
489 {
490 	return Path!C( path.str.rootName() );
491 }
492 
493 /// Part of workaround for DMD Issue #12111
494 inout(C)[] rootName(C)(inout(C)[] path)  @safe pure nothrow  if (isSomeChar!C)
495 {
496 	return std.path.rootName(path);
497 }
498 
499 /// Just like std.path.driveName, but operates on Path.
500 Path!C driveName(C)(Path!C path) @safe pure nothrow
501 {
502 	return Path!C( path.str.driveName() );
503 }
504 
505 /// Part of workaround for DMD Issue #12111
506 inout(C)[] driveName(C)(inout(C)[] path)  @safe pure nothrow
507 	if (isSomeChar!C)
508 {
509 	return std.path.driveName(path);
510 }
511 
512 /// Just like std.path.stripDrive, but operates on Path.
513 Path!C stripDrive(C)(Path!C path) @safe pure nothrow
514 {
515 	return Path!C( path.str.stripDrive() );
516 }
517 
518 /// Part of workaround for DMD Issue #12111
519 inout(C)[] stripDrive(C)(inout(C)[] path)  @safe pure nothrow  if (isSomeChar!C)
520 {
521 	return std.path.stripDrive(path);
522 }
523 
524 /// Just like std.path.extension, but takes a Path and returns an Ext.
525 Ext!C extension(C)(in Path!C path) @safe pure nothrow
526 {
527 	return Ext!C( path.str.extension() );
528 }
529 
530 /// Part of workaround for DMD Issue #12111
531 inout(C)[] extension(C)(inout(C)[] path)  @safe pure nothrow  if (isSomeChar!C)
532 {
533 	return std.path.extension(path);
534 }
535 
536 /// Just like std.path.stripExtension, but operates on Path.
537 Path!C stripExtension(C)(Path!C path) @safe pure nothrow
538 {
539 	return Path!C( path.str.stripExtension() );
540 }
541 
542 /// Part of workaround for DMD Issue #12111
543 inout(C)[] stripExtension(C)(inout(C)[] path)  @safe pure nothrow
544 	if (isSomeChar!C)
545 {
546 	return std.path.stripExtension(path);
547 }
548 
549 /// Just like std.path.setExtension, but operates on Path.
550 Path!C setExtension(C, C2)(Path!C path, const(C2)[] ext)
551 	@trusted pure nothrow
552 	if(is(C == Unqual!C2))
553 {
554 	return Path!C( path.str.setExtension(ext) );
555 }
556 
557 ///ditto
558 Path!C setExtension(C)(Path!C path, Ext!C ext)
559 	@trusted pure nothrow
560 {
561 	return path.setExtension(ext.toString());
562 }
563 
564 /// Part of workaround for DMD Issue #12111
565 immutable(Unqual!C1)[] setExtension(C1, C2)(in C1[] path, in C2[] ext)
566 	@trusted pure nothrow
567 	if (isSomeChar!C1 && !is(C1 == immutable) && is(Unqual!C1 == Unqual!C2))
568 {
569 	return std.path.setExtension(path, ext);
570 }
571 
572 /// Part of workaround for DMD Issue #12111
573 immutable(C1)[] setExtension(C1, C2)(immutable(C1)[] path, const(C2)[] ext)
574 	@trusted pure nothrow
575 	if (isSomeChar!C1 && is(Unqual!C1 == Unqual!C2))
576 {
577 	return std.path.setExtension(path, ext);
578 }
579 
580 /// Just like std.path.defaultExtension, but operates on Path and optionally Ext.
581 Path!C defaultExtension(C, C2)(Path!C path, in C2[] ext)
582 	@trusted pure
583 	if(is(C == Unqual!C2))
584 {
585 	return Path!C( path.str.defaultExtension(ext) );
586 }
587 
588 ///ditto
589 Path!C defaultExtension(C)(Path!C path, Ext!C ext)
590 	@trusted pure
591 {
592 	return path.defaultExtension(ext.toString());
593 }
594 
595 /// Part of workaround for DMD Issue #12111
596 immutable(Unqual!C1)[] defaultExtension(C1, C2)(in C1[] path, in C2[] ext)
597 	@trusted pure
598 	if (isSomeChar!C1 && is(Unqual!C1 == Unqual!C2))
599 {
600 	return std.path.defaultExtension(path, ext);
601 }
602 
603 /// Just like std.path.pathSplitter. Note this returns a range of strings,
604 /// not a range of Path.
605 auto pathSplitter(C)(Path!C path) @safe pure nothrow
606 {
607 	return pathSplitter(path.str);
608 }
609 
610 /// Part of workaround for DMD Issue #12111
611 ReturnType!(std.path.pathSplitter!C) pathSplitter(C)(const(C)[] path) @safe pure nothrow
612 	if (isSomeChar!C)
613 {
614 	return std.path.pathSplitter(path);
615 }
616 
617 /// Just like std.path.isRooted, but operates on Path.
618 bool isRooted(C)(in Path!C path) @safe pure nothrow
619 {
620 	return path.str.isRooted();
621 }
622 
623 /// Part of workaround for DMD Issue #12111
624 bool isRooted(C)(in C[] path)  @safe pure nothrow  if (isSomeChar!C)
625 {
626 	return std.path.isRooted(path);
627 }
628 
629 /// Just like std.path.isAbsolute, but operates on Path.
630 bool isAbsolute(C)(in Path!C path) @safe pure nothrow
631 {
632 	return path.str.isAbsolute();
633 }
634 
635 /// Part of workaround for DMD Issue #12111
636 bool isAbsolute(C)(in C[] path)  @safe pure nothrow
637 	if (isSomeChar!C)
638 {
639 	return std.path.isAbsolute(path);
640 }
641 
642 /// Just like std.path.absolutePath, but operates on Path.
643 Path!C absolutePath(C)(Path!C path, lazy string base = getcwd())
644 	@safe pure
645 {
646 	return Path!C( path.str.absolutePath(base) );
647 }
648 
649 ///ditto
650 Path!C absolutePath(C)(Path!C path, Path!C base)
651 	@safe pure
652 {
653 	return Path!C( path.str.absolutePath(base.str.to!string()) );
654 }
655 
656 /// Part of workaround for DMD Issue #12111
657 string absolutePath(string path, lazy string base = getcwd())
658 	@safe pure
659 {
660 	return std.path.absolutePath(path, base);
661 }
662 
663 /// Just like std.path.relativePath, but operates on Path.
664 Path!C relativePath(CaseSensitive cs = CaseSensitive.osDefault, C)
665 	(Path!C path, lazy string base = getcwd())
666 {
667 	return Path!C( path.str.relativePath!cs(base) );
668 }
669 
670 ///ditto
671 Path!C relativePath(CaseSensitive cs = CaseSensitive.osDefault, C)
672 	(Path!C path, Path!C base)
673 {
674 	return Path!C( path.str.relativePath!cs(base.str.to!string()) );
675 }
676 
677 /// Part of workaround for DMD Issue #12111
678 string relativePath(CaseSensitive cs = CaseSensitive.osDefault)
679 	(string path, lazy string base = getcwd())
680 {
681 	return std.path.relativePath(path, base);
682 }
683 
684 /// Part of workaround for DMD Issue #12111
685 int filenameCharCmp(CaseSensitive cs = CaseSensitive.osDefault)(dchar a, dchar b)
686 	@safe pure nothrow
687 {
688 	return std.path.filenameCharCmp(a, b);
689 }
690 
691 /// Just like std.path.filenameCmp, but operates on Path.
692 int filenameCmp(CaseSensitive cs = CaseSensitive.osDefault, C, C2)
693 	(Path!C path, Path!C2 filename2)
694 	@safe pure
695 {
696 	return path.str.filenameCmp(filename2.str);
697 }
698 
699 ///ditto
700 int filenameCmp(CaseSensitive cs = CaseSensitive.osDefault, C, C2)
701 	(Path!C path, const(C2)[] filename2)
702 	@safe pure
703 	if(isSomeChar!C2)
704 {
705 	return path.str.filenameCmp(filename2);
706 }
707 
708 ///ditto
709 int filenameCmp(CaseSensitive cs = CaseSensitive.osDefault, C, C2)
710 	(const(C)[] path, Path!C2[] filename2)
711 	@safe pure
712 	if(isSomeChar!C)
713 {
714 	return path.filenameCmp(filename2.str);
715 }
716 
717 /// Part of workaround for DMD Issue #12111
718 int filenameCmp(CaseSensitive cs = CaseSensitive.osDefault, C1, C2)
719 	(const(C1)[] filename1, const(C2)[] filename2)
720 	@safe pure
721 	if (isSomeChar!C1 && isSomeChar!C2)
722 {
723 	return std.path.filenameCmp(filename1, filename2);
724 }
725 
726 /// Just like std.path.globMatch, but operates on Path.
727 bool globMatch(CaseSensitive cs = CaseSensitive.osDefault, C)
728 	(Path!C path, const(C)[] pattern)
729 	@safe pure nothrow
730 {
731 	return path.str.globMatch!cs(pattern);
732 }
733 
734 /// Part of workaround for DMD Issue #12111
735 bool globMatch(CaseSensitive cs = CaseSensitive.osDefault, C)
736 	(const(C)[] path, const(C)[] pattern)
737 	@safe pure nothrow
738 	if (isSomeChar!C)
739 {
740 	return std.path.globMatch(path, pattern);
741 }
742 
743 /// Just like std.path.isValidFilename, but operates on Path.
744 bool isValidFilename(C)(in Path!C path) @safe pure nothrow
745 {
746 	return path.str.isValidFilename();
747 }
748 
749 /// Part of workaround for DMD Issue #12111
750 bool isValidFilename(C)(in C[] filename)  @safe pure nothrow  if (isSomeChar!C)
751 {
752 	return std.path.isValidFilename(filename);
753 }
754 
755 /// Just like std.path.isValidPath, but operates on Path.
756 bool isValidPath(C)(in Path!C path) @safe pure nothrow
757 {
758 	return path.str.isValidPath();
759 }
760 
761 /// Part of workaround for DMD Issue #12111
762 bool isValidPath(C)(in C[] path)  @safe pure nothrow  if (isSomeChar!C)
763 {
764 	return std.path.isValidPath(path);
765 }
766 
767 /// Just like std.path.expandTilde, but operates on Path.
768 Path!C expandTilde(C)(Path!C path)
769 {
770 	static if( is(C == char) )
771 		return Path!C( path.str.expandTilde() );
772 	else
773 		return Path!C( path.to!string().expandTilde().to!(C[])() );
774 }
775 
776 /// Part of workaround for DMD Issue #12111
777 string expandTilde(string inputPath)
778 {
779 	return std.path.expandTilde(inputPath);
780 }
781 
782 // -- Wrappers for std.file --------------------
783 
784 /// Part of workaround for DMD Issue #12111
785 alias FileException = std.file.FileException;
786 alias SpanMode      = std.file.SpanMode;
787 alias attrIsDir     = std.file.attrIsDir;
788 alias attrIsFile    = std.file.attrIsFile;
789 alias attrIsSymlink = std.file.attrIsSymlink;
790 alias getcwd        = std.file.getcwd;
791 alias thisExePath   = std.file.thisExePath;
792 alias tempDir       = std.file.tempDir;
793 
794 /// Just like std.file.read, but takes a Path.
795 void[] read(C)(in Path!C name, size_t upTo = size_t.max) if(isSomeChar!C)
796 {
797 	return read(name.str.to!string(), upTo);
798 }
799 
800 /// Part of workaround for DMD Issue #12111
801 void[] read(in char[] name, size_t upTo = size_t.max)
802 {
803 	return std.file.read(name, upTo);
804 }
805 
806 /// Just like std.file.readText, but takes a Path.
807 template readText(S = string)
808 {
809 	S readText(C)(in Path!C name) if(isSomeChar!C)
810 	{
811 		return std.file.readText(name.str.to!string());
812 	}
813 }
814 
815 /// Part of workaround for DMD Issue #12111
816 S readText(S = string)(in char[] name)
817 {
818 	return std.file.readText(name);
819 }
820 
821 /// Just like std.file.write, but takes a Path.
822 void write(C)(in Path!C name, const void[] buffer) if(isSomeChar!C)
823 {
824 	write(name.str.to!string(), buffer);
825 }
826 
827 /// Part of workaround for DMD Issue #12111
828 void write(in char[] name, const void[] buffer)
829 {
830 	std.file.write(name, buffer);
831 }
832 
833 /// Just like std.file.append, but takes a Path.
834 void append(C)(in Path!C name, in void[] buffer) if(isSomeChar!C)
835 {
836 	append(name.str.to!string(), buffer);
837 }
838 
839 /// Part of workaround for DMD Issue #12111
840 void append(in char[] name, in void[] buffer)
841 {
842 	std.file.append(name, buffer);
843 }
844 
845 /// Just like std.file.rename, but takes Path, and echoes if scriptlikeTraceCommands is true.
846 void rename(C)(in Path!C from, in Path!C to) if(isSomeChar!C)
847 {
848 	rename(from.str.to!string(), to.str.to!string());
849 }
850 
851 ///ditto
852 void rename(C)(in char[] from, in Path!C to) if(isSomeChar!C)
853 {
854 	rename(from, to.str.to!string());
855 }
856 
857 ///ditto
858 void rename(C)(in Path!C from, in char[] to) if(isSomeChar!C)
859 {
860 	rename(from.str.to!string(), to);
861 }
862 
863 /// Just like std.file.rename, but echoes if scriptlikeTraceCommands is true.
864 void rename(in char[] from, in char[] to)
865 {
866 	echoCommand("rename: "~from.escapeShellPath()~" -> "~to.escapeShellPath());
867 	std.file.rename(from, to);
868 }
869 
870 /// If 'from' exists, then rename. Otherwise do nothing.
871 /// Returns: Success?
872 bool tryRename(T1, T2)(T1 from, T2 to)
873 {
874 	if(from.exists())
875 	{
876 		rename(from, to);
877 		return true;
878 	}
879 
880 	return false;
881 }
882 
883 /// Just like std.file.remove, but takes a Path, and echoes if scriptlikeTraceCommands is true.
884 void remove(C)(in Path!C name) if(isSomeChar!C)
885 {
886 	remove(name.str.to!string());
887 }
888 
889 /// Just like std.file.remove, but echoes if scriptlikeTraceCommands is true.
890 void remove(in char[] name)
891 {
892 	echoCommand("remove: "~name.escapeShellPath());
893 	std.file.remove(name);
894 }
895 
896 /// If 'name' exists, then remove. Otherwise do nothing.
897 /// Returns: Success?
898 bool tryRemove(T)(T name)
899 {
900 	if(name.exists())
901 	{
902 		remove(name);
903 		return true;
904 	}
905 	
906 	return false;
907 }
908 
909 /// Just like std.file.getSize, but takes a Path.
910 ulong getSize(C)(in Path!C name) if(isSomeChar!C)
911 {
912 	return getSize(name.str.to!string());
913 }
914 
915 /// Part of workaround for DMD Issue #12111
916 ulong getSize(in char[] name)
917 {
918 	return std.file.getSize(name);
919 }
920 
921 /// Just like std.file.getTimes, but takes a Path.
922 void getTimes(C)(in Path!C name,
923 	out SysTime accessTime,
924 	out SysTime modificationTime) if(isSomeChar!C)
925 {
926 	getTimes(name.str.to!string(), accessTime, modificationTime);
927 }
928 
929 /// Part of workaround for DMD Issue #12111
930 void getTimes(in char[] name,
931 	out SysTime accessTime,
932 	out SysTime modificationTime)
933 {
934 	std.file.getTimes(name, accessTime, modificationTime);
935 }
936 
937 /// Windows-only. Just like std.file.getTimesWin, but takes a Path.
938 version(Windows) void getTimesWin(C)(in Path!C name,
939 	out SysTime fileCreationTime,
940 	out SysTime fileAccessTime,
941 	out SysTime fileModificationTime) if(isSomeChar!C)
942 {
943 	getTimesWin(name.str.to!string(), fileCreationTime, fileAccessTime, fileModificationTime);
944 }
945 
946 /// Part of workaround for DMD Issue #12111
947 version(Windows) void getTimesWin(in char[] name,
948 	out SysTime fileCreationTime,
949 	out SysTime fileAccessTime,
950 	out SysTime fileModificationTime)
951 {
952 	std.file.getTimesWin(name, fileCreationTime, fileAccessTime, fileModificationTime);
953 }
954 
955 /// Just like std.file.setTimes, but takes a Path.
956 void setTimes(C)(in Path!C name,
957 	SysTime accessTime,
958 	SysTime modificationTime) if(isSomeChar!C)
959 {
960 	setTimes(name.str.to!string(), accessTime, modificationTime);
961 }
962 
963 /// Part of workaround for DMD Issue #12111
964 void setTimes(in char[] name,
965 	SysTime accessTime,
966 	SysTime modificationTime)
967 {
968 	return std.file.setTimes(name, accessTime, modificationTime);
969 }
970 
971 /// Just like std.file.timeLastModified, but takes a Path.
972 SysTime timeLastModified(C)(in Path!C name) if(isSomeChar!C)
973 {
974 	return timeLastModified(name.str.to!string());
975 }
976 
977 /// Just like std.file.timeLastModified, but takes a Path.
978 SysTime timeLastModified(C)(in Path!C name, SysTime returnIfMissing) if(isSomeChar!C)
979 {
980 	return timeLastModified(name.str.to!string(), returnIfMissing);
981 }
982 
983 /// Part of workaround for DMD Issue #12111
984 SysTime timeLastModified(in char[] name)
985 {
986 	return std.file.timeLastModified(name);
987 }
988 
989 ///ditto
990 SysTime timeLastModified(in char[] name, SysTime returnIfMissing)
991 {
992 	return std.file.timeLastModified(name, returnIfMissing);
993 }
994 
995 /// Just like std.file.exists, but takes a Path.
996 bool exists(C)(in Path!C name) @trusted if(isSomeChar!C)
997 {
998 	return exists(name.str.to!string());
999 }
1000 
1001 /// Part of workaround for DMD Issue #12111
1002 bool exists(in char[] name) @trusted
1003 {
1004 	return std.file.exists(name);
1005 }
1006 
1007 /// Just like std.file.getAttributes, but takes a Path.
1008 uint getAttributes(C)(in Path!C name) if(isSomeChar!C)
1009 {
1010 	return getAttributes(name.str.to!string());
1011 }
1012 
1013 /// Part of workaround for DMD Issue #12111
1014 uint getAttributes(in char[] name)
1015 {
1016 	return std.file.getAttributes(name);
1017 }
1018 
1019 /// Just like std.file.getLinkAttributes, but takes a Path.
1020 uint getLinkAttributes(C)(in Path!C name) if(isSomeChar!C)
1021 {
1022 	return getLinkAttributes(name.str.to!string());
1023 }
1024 
1025 /// Part of workaround for DMD Issue #12111
1026 uint getLinkAttributes(in char[] name)
1027 {
1028 	return std.file.getLinkAttributes(name);
1029 }
1030 
1031 /// Just like std.file.isDir, but takes a Path.
1032 @property bool isDir(C)(in Path!C name) if(isSomeChar!C)
1033 {
1034 	return isDir(name.str.to!string());
1035 }
1036 
1037 /// Part of workaround for DMD Issue #12111
1038 @property bool isDir(in char[] name)
1039 {
1040 	return std.file.isDir(name);
1041 }
1042 
1043 /// Just like std.file.isFile, but takes a Path.
1044 @property bool isFile(C)(in Path!C name) if(isSomeChar!C)
1045 {
1046 	return isFile(name.str.to!string());
1047 }
1048 
1049 /// Part of workaround for DMD Issue #12111
1050 @property bool isFile(in char[] name)
1051 {
1052 	return std.file.isFile(name);
1053 }
1054 
1055 /// Just like std.file.isSymlink, but takes a Path.
1056 @property bool isSymlink(C)(Path!C name) if(isSomeChar!C)
1057 {
1058 	return isSymlink(name.str.to!string());
1059 }
1060 
1061 /// Part of workaround for DMD Issue #12111
1062 @property bool isSymlink(C)(const(C)[] name)
1063 {
1064 	return std.file.isSymlink(name);
1065 }
1066 
1067 /// Just like std.file.chdir, but takes a Path, and echoes if scriptlikeTraceCommands is true.
1068 void chdir(C)(in Path!C pathname) if(isSomeChar!C)
1069 {
1070 	chdir(pathname.str.to!string());
1071 }
1072 
1073 /// Just like std.file.chdir, but echoes if scriptlikeTraceCommands is true.
1074 void chdir(in char[] pathname)
1075 {
1076 	echoCommand("chdir: "~pathname.escapeShellPath());
1077 	std.file.chdir(pathname);
1078 }
1079 
1080 /// Just like std.file.mkdir, but takes a Path, and echoes if scriptlikeTraceCommands is true.
1081 void mkdir(C)(in Path!C pathname) if(isSomeChar!C)
1082 {
1083 	mkdir(pathname.str.to!string());
1084 }
1085 
1086 /// Just like std.file.mkdir, but echoes if scriptlikeTraceCommands is true.
1087 void mkdir(in char[] pathname)
1088 {
1089 	echoCommand("mkdir: "~pathname.escapeShellPath());
1090 	std.file.mkdir(pathname);
1091 }
1092 
1093 /// If 'name' doesn't already exist, then mkdir. Otherwise do nothing.
1094 /// Returns: Success?
1095 bool tryMkdir(T)(T name)
1096 {
1097 	if(!name.exists())
1098 	{
1099 		mkdir(name);
1100 		return true;
1101 	}
1102 	
1103 	return false;
1104 }
1105 
1106 /// Just like std.file.mkdirRecurse, but takes a Path, and echoes if scriptlikeTraceCommands is true.
1107 void mkdirRecurse(C)(in Path!C pathname) if(isSomeChar!C)
1108 {
1109 	mkdirRecurse(pathname.str.to!string());
1110 }
1111 
1112 /// Just like std.file.mkdirRecurse, but echoes if scriptlikeTraceCommands is true.
1113 void mkdirRecurse(in char[] pathname)
1114 {
1115 	echoCommand("mkdirRecurse: "~pathname.escapeShellPath());
1116 	std.file.mkdirRecurse(pathname);
1117 }
1118 
1119 /// If 'name' doesn't already exist, then mkdirRecurse. Otherwise do nothing.
1120 /// Returns: Success?
1121 bool tryMkdirRecurse(T)(T name)
1122 {
1123 	if(!name.exists())
1124 	{
1125 		mkdirRecurse(name);
1126 		return true;
1127 	}
1128 	
1129 	return false;
1130 }
1131 
1132 /// Just like std.file.rmdir, but takes a Path, and echoes if scriptlikeTraceCommands is true.
1133 void rmdir(C)(in Path!C pathname) if(isSomeChar!C)
1134 {
1135 	rmdir(pathname.str.to!string());
1136 }
1137 
1138 /// Just like std.file.rmdir, but echoes if scriptlikeTraceCommands is true.
1139 void rmdir(in char[] pathname)
1140 {
1141 	echoCommand("rmdir: "~pathname.escapeShellPath());
1142 	std.file.rmdir(pathname);
1143 }
1144 
1145 /// If 'name' exists, then rmdir. Otherwise do nothing.
1146 /// Returns: Success?
1147 bool tryRmdir(T)(T name)
1148 {
1149 	if(name.exists())
1150 	{
1151 		rmdir(name);
1152 		return true;
1153 	}
1154 	
1155 	return false;
1156 }
1157 
1158 /// Posix-only. Just like std.file.symlink, but takes Path, and echoes if scriptlikeTraceCommands is true.
1159 version(Posix) void symlink(C1, C2)(Path!C1 original, Path!C2 link) if(isSomeChar!C1 && isSomeChar!C2)
1160 {
1161 	symlink(original.str.to!string(), link.str.to!string());
1162 }
1163 
1164 ///ditto
1165 version(Posix) void symlink(C1, C2)(const(C1)[] original, Path!C2 link) if(isSomeChar!C1 && isSomeChar!C2)
1166 {
1167 	symlink(original, link.str.to!string());
1168 }
1169 
1170 ///ditto
1171 version(Posix) void symlink(C1, C2)(Path!C1 original, const(C2)[] link) if(isSomeChar!C1 && isSomeChar!C2)
1172 {
1173 	symlink(original.str.to!string(), link);
1174 }
1175 
1176 /// Just like std.file.symlink, but echoes if scriptlikeTraceCommands is true.
1177 version(Posix) void symlink(C1, C2)(const(C1)[] original, const(C2)[] link)
1178 {
1179 	echoCommand("symlink: [original] "~original.escapeShellPath()~" : [symlink] "~link.escapeShellPath());
1180 	std.file.symlink(original, link);
1181 }
1182 
1183 /// If 'original' exists, then symlink. Otherwise do nothing.
1184 /// Returns: Success?
1185 version(Posix) bool trySymlink(T1, T2)(T1 original, T2 link)
1186 {
1187 	if(original.exists())
1188 	{
1189 		symlink(original, link);
1190 		return true;
1191 	}
1192 	
1193 	return false;
1194 }
1195 
1196 /// Posix-only. Just like std.file.readLink, but operates on Path.
1197 version(Posix) Path!C readLink(C)(Path!C link) if(isSomeChar!C)
1198 {
1199 	return Path!C( readLink(link.str.to!string()) );
1200 }
1201 
1202 /// Part of workaround for DMD Issue #12111
1203 version(Posix) string readLink(C)(const(C)[] link)
1204 {
1205 	return std.file.readLink(link);
1206 }
1207 
1208 /// Just like std.file.copy, but takes Path, and echoes if scriptlikeTraceCommands is true.
1209 void copy(C)(in Path!C from, in Path!C to) if(isSomeChar!C)
1210 {
1211 	copy(from.str.to!string(), to.str.to!string());
1212 }
1213 
1214 ///ditto
1215 void copy(C)(in char[] from, in Path!C to) if(isSomeChar!C)
1216 {
1217 	copy(from, to.str.to!string());
1218 }
1219 
1220 ///ditto
1221 void copy(C)(in Path!C from, in char[] to) if(isSomeChar!C)
1222 {
1223 	copy(from.str.to!string(), to);
1224 }
1225 
1226 /// Just like std.file.copy, but echoes if scriptlikeTraceCommands is true.
1227 void copy(in char[] from, in char[] to)
1228 {
1229 	echoCommand("copy: "~from.escapeShellPath()~" -> "~to.escapeShellPath());
1230 	std.file.copy(from, to);
1231 }
1232 
1233 /// If 'from' exists, then copy. Otherwise do nothing.
1234 /// Returns: Success?
1235 bool tryCopy(T1, T2)(T1 from, T2 to)
1236 {
1237 	if(from.exists())
1238 	{
1239 		copy(from, to);
1240 		return true;
1241 	}
1242 	
1243 	return false;
1244 }
1245 
1246 /// Just like std.file.rmdirRecurse, but takes a Path, and echoes if scriptlikeTraceCommands is true.
1247 void rmdirRecurse(C)(in Path!C pathname) if(isSomeChar!C)
1248 {
1249 	rmdirRecurse(pathname.str.to!string());
1250 }
1251 
1252 /// Just like std.file.rmdirRecurse, but echoes if scriptlikeTraceCommands is true.
1253 void rmdirRecurse(in char[] pathname)
1254 {
1255 	echoCommand("rmdirRecurse: "~pathname.escapeShellPath());
1256 	std.file.rmdirRecurse(pathname);
1257 }
1258 
1259 /// If 'name' exists, then rmdirRecurse. Otherwise do nothing.
1260 /// Returns: Success?
1261 bool tryRmdirRecurse(T)(T name)
1262 {
1263 	if(name.exists())
1264 	{
1265 		rmdirRecurse(name);
1266 		return true;
1267 	}
1268 	
1269 	return false;
1270 }
1271 
1272 /// Just like std.file.dirEntries, but takes a Path.
1273 auto dirEntries(C)(Path!C path, SpanMode mode, bool followSymlink = true) if(isSomeChar!C)
1274 {
1275 	return dirEntries(path.str.to!string(), mode, followSymlink);
1276 }
1277 
1278 /// Just like std.file.dirEntries, but takes a Path.
1279 auto dirEntries(C)(Path!C path, string pattern, SpanMode mode,
1280 	bool followSymlink = true) if(isSomeChar!C)
1281 {
1282 	return dirEntries(path.str.to!string(), pattern, mode, followSymlink);
1283 }
1284 
1285 /// Part of workaround for DMD Issue #12111
1286 auto dirEntries(string path, SpanMode mode, bool followSymlink = true)
1287 {
1288 	return std.file.dirEntries(path, mode, followSymlink);
1289 }
1290 
1291 ///ditto
1292 auto dirEntries(string path, string pattern, SpanMode mode,
1293 	bool followSymlink = true)
1294 {
1295 	return std.file.dirEntries(path, pattern, mode, followSymlink);
1296 }
1297 
1298 /// Just like std.file.slurp, but takes a Path.
1299 template slurp(Types...)
1300 {
1301 	auto slurp(C)(Path!C filename, in char[] format) if(isSomeChar!C)
1302 	{
1303 		return std.file.slurp!Types(filename.str.to!string(), format);
1304 	}
1305 }
1306 
1307 /// Part of workaround for DMD Issue #12111
1308 Select!(Types.length == 1, Types[0][], Tuple!(Types)[])
1309 slurp(Types...)(string filename, in char[] format)
1310 {
1311 	return std.file.slurp(filename, format);
1312 }
1313 
1314 // The unittests in this module mainly check that all the templates compile
1315 // correctly and that the appropriate Phobos functions are correctly called.
1316 //
1317 // A completely thorough testing of the behavior of such functions is
1318 // occasionally left to Phobos itself as it is outside the scope of these tests.
1319 
1320 version(unittest_scriptlike_d)
1321 unittest
1322 {
1323 	import std.stdio : writeln;
1324 	writeln("Running 'scriptlike.d' unittests: std.path wrappers");
1325 	
1326 	alias dirSep = dirSeparator;
1327 
1328 	foreach(C; TypeTuple!(char/+, wchar, dchar+/))
1329 	{
1330 		//pragma(msg, "==="~C.stringof);
1331 
1332 		{
1333 			auto e = ext(".txt");
1334 			assert(e != ext(".dat"));
1335 			assert(e == ext(".txt"));
1336 			version(Windows)
1337 				assert(e == ext(".TXT"));
1338 			else version(OSX)
1339 				assert(e == ext(".TXT"));
1340 			else version(Posix)
1341 				assert(e != ext(".TXT"));
1342 			else
1343 				static assert(0, "This platform not supported.");
1344 			
1345 			// Test the other comparison overloads
1346 			assert(e != Ext!C(".dat"));
1347 			assert(e == Ext!C(".txt"));
1348 			assert(Ext!C(".dat") != e);
1349 			assert(Ext!C(".txt") == e);
1350 			assert(".dat" != e);
1351 			assert(".txt" == e);
1352 		}
1353 
1354 		auto p = Path!C();
1355 		assert(p.str == ".");
1356 		assert(!p.empty);
1357 		
1358 		assert(Path!C("").empty);
1359 		assert(path(cast(immutable(C)[])"").empty);
1360 		
1361 		p = path(cast(immutable(C)[])".");
1362 		assert(!p.empty);
1363 		
1364 		version(Windows)
1365 			auto testStrings = [cast(immutable(C)[])"/foo/bar", "/foo/bar/", `\foo\bar`, `\foo\bar\`];
1366 		else version(Posix)
1367 			auto testStrings = [cast(immutable(C)[])"/foo/bar", "/foo/bar/"];
1368 		else
1369 			static assert(0, "This platform not supported.");
1370 		
1371 		foreach(str; testStrings)
1372 		{
1373 			writeln("  testing str: ", str);
1374 			
1375 			p = Path!C(str);
1376 			assert(!p.empty);
1377 			assert(p.str == dirSep~"foo"~dirSep~"bar");
1378 			
1379 			p = path(str);
1380 			assert(p.str == dirSep~"foo"~dirSep~"bar");
1381 			assert(p.toRawString() == p.str);
1382 			assert(p.toString()    == p.str.to!string());
1383 			
1384 			assert(p.up.toString() == dirSep~"foo");
1385 			assert(p.up.up.toString() == dirSep);
1386 
1387 			assert((p~"sub").toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"sub");
1388 			assert((p~"sub"~"2").toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"sub"~dirSep~"2");
1389 			assert((p~path("sub")).toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"sub");
1390 			
1391 			version(Windows)
1392 				assert((p~"sub dir").toString() == `"`~dirSep~"foo"~dirSep~"bar"~dirSep~"sub dir"~`"`);
1393 			else version(Posix)
1394 				assert((p~"sub dir").toString() == `'`~dirSep~"foo"~dirSep~"bar"~dirSep~`sub dir'`);
1395 			else
1396 				static assert(0, "This platform not supported.");
1397 
1398 			assert(("dir"~p).toString() == dirSep~"foo"~dirSep~"bar");
1399 			assert(("dir"~path(str[1..$])).toString() == "dir"~dirSep~"foo"~dirSep~"bar");
1400 			
1401 			p ~= "blah";
1402 			assert(p.toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"blah");
1403 			
1404 			p ~= path("more");
1405 			assert(p.toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"blah"~dirSep~"more");
1406 			
1407 			p ~= "..";
1408 			assert(p.toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"blah");
1409 			
1410 			p ~= path("..");
1411 			assert(p.toString() == dirSep~"foo"~dirSep~"bar");
1412 			
1413 			p ~= "sub dir";
1414 			p ~= "..";
1415 			assert(p.toString() == dirSep~"foo"~dirSep~"bar");
1416 			
1417 			p ~= "filename";
1418 			assert((p~Ext!C(".txt")).toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.txt");
1419 			assert((p~Ext!C("txt")).toString()  == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.txt");
1420 			assert((p~Ext!C("")).toString()     == dirSep~"foo"~dirSep~"bar"~dirSep~"filename");
1421 
1422 			p ~= ext(".ext");
1423 			assert(p.toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext");
1424 			assert(p.baseName().toString() == "filename.ext");
1425 			assert(p.dirName().toString() == dirSep~"foo"~dirSep~"bar");
1426 			assert(p.rootName().toString() == dirSep);
1427 			assert(p.driveName().toString() == "");
1428 			assert(p.stripDrive().toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext");
1429 			version(Windows)
1430 			{
1431 				assert(( path("C:"~p.toRawString()) ).toString() == "C:"~dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext");
1432 				assert(( path("C:"~p.toRawString()) ).stripDrive().toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext");
1433 			}
1434 			assert(p.extension().toString() == ".ext");
1435 			assert(p.stripExtension().toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename");
1436 			assert(p.setExtension(".txt").toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.txt");
1437 			assert(p.setExtension("txt").toString()  == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.txt");
1438 			assert(p.setExtension("").toString()     == dirSep~"foo"~dirSep~"bar"~dirSep~"filename");
1439 			assert(p.setExtension(Ext!C(".txt")).toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.txt");
1440 			assert(p.setExtension(Ext!C("txt")).toString()  == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.txt");
1441 			assert(p.setExtension(Ext!C("")).toString()     == dirSep~"foo"~dirSep~"bar"~dirSep~"filename");
1442 
1443 			assert(p.defaultExtension(".dat").toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext");
1444 			assert(p.stripExtension().defaultExtension(".dat").toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.dat");
1445 
1446 			assert(equal(p.pathSplitter(), [dirSep, "foo", "bar", "filename.ext"]));
1447 
1448 			assert(p.isRooted());
1449 			version(Windows)
1450 				assert(!p.isAbsolute());
1451 			else version(Posix)
1452 				assert(p.isAbsolute());
1453 			else
1454 				static assert(0, "This platform not supported.");
1455 
1456 			assert(!( path("dir"~p.toRawString()) ).isRooted());
1457 			assert(!( path("dir"~p.toRawString()) ).isAbsolute());
1458 			
1459 			version(Windows)
1460 			{
1461 				assert(( path("dir"~p.toRawString()) ).absolutePath("C:/main").toString() == "C:"~dirSep~"main"~dirSep~"dir"~dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext");
1462 				assert(( path("C:"~p.toRawString()) ).relativePath("C:/foo").toString() == "bar"~dirSep~"filename.ext");
1463 				assert(( path("C:"~p.toRawString()) ).relativePath("C:/foo/bar").toString() == "filename.ext");
1464 			}
1465 			else version(Posix)
1466 			{
1467 				assert(( path("dir"~p.toRawString()) ).absolutePath("/main").toString() == dirSep~"main"~dirSep~"dir"~dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext");
1468 				assert(p.relativePath("/foo").toString() == "bar"~dirSep~"filename.ext");
1469 				assert(p.relativePath("/foo/bar").toString() == "filename.ext");
1470 			}
1471 			else
1472 				static assert(0, "This platform not supported.");
1473 
1474 			assert(p.filenameCmp(dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext") == 0);
1475 			assert(p.filenameCmp(dirSep~"faa"~dirSep~"bat"~dirSep~"filename.ext") != 0);
1476 			assert(p.globMatch("*foo*name.ext"));
1477 			assert(!p.globMatch("*foo*Bname.ext"));
1478 
1479 			assert(!p.isValidFilename());
1480 			assert(p.baseName().isValidFilename());
1481 			assert(p.isValidPath());
1482 			
1483 			assert(p.expandTilde().toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext");
1484 			
1485 			assert(p != path("/dir/subdir/filename.ext"));
1486 			assert(p == path("/foo/bar/filename.ext"));
1487 			version(Windows)
1488 				assert(p == path("/FOO/BAR/FILENAME.EXT"));
1489 			else version(OSX)
1490 				assert(p == path("/FOO/BAR/FILENAME.EXT"));
1491 			else version(Posix)
1492 				assert(p != path("/FOO/BAR/FILENAME.EXT"));
1493 			else
1494 				static assert(0, "This platform not supported.");
1495 			
1496 			// Test the other comparison overloads
1497 			assert(p != Path!C("/dir/subdir/filename.ext"));
1498 			assert(p == Path!C("/foo/bar/filename.ext"));
1499 			assert(Path!C("/dir/subdir/filename.ext") != p);
1500 			assert(Path!C("/foo/bar/filename.ext")    == p);
1501 			assert("/dir/subdir/filename.ext" != p);
1502 			assert("/foo/bar/filename.ext"    == p);
1503 		}
1504 	}
1505 }
1506 
1507 version(unittest_scriptlike_d)
1508 unittest
1509 {
1510 	// std.file.slurp seems to randomly trigger an internal std.algorithm
1511 	// assert failure on DMD 2.064.2, so don't test it there. Seems
1512 	// to be fixed in DMD 2.065.
1513 	static import std.compiler;
1514 	static if(
1515 		std.compiler.vendor == std.compiler.Vendor.digitalMars ||
1516 		(std.compiler.version_major == 2 && std.compiler.version_minor == 64)
1517 	)
1518 		enum testSlurp = false;
1519 	else
1520 		enum testSlurp = true;
1521 
1522 	import std.stdio : writeln;
1523 	import core.thread;
1524 
1525 	writeln("Running 'scriptlike.d' unittests: std.file wrappers");
1526 	
1527 	immutable tempname  = buildPath(tempDir(), "deleteme.script like.unit test.pid"  ~ to!string(thisProcessID));
1528 	immutable tempname2 = buildPath(tempDir(), "deleteme.script like.unit test2.pid" ~ to!string(thisProcessID));
1529 	immutable tempname3 = buildPath(tempDir(), "deleteme.script like.unit test3.pid" ~ to!string(thisProcessID), "somefile");
1530 	auto tempPath  = path(tempname);
1531 	auto tempPath2 = path(tempname2);
1532 	auto tempPath3 = path(tempname3);
1533 	assert(!tempname.exists());
1534 	assert(!tempname2.exists());
1535 	assert(!tempname3.dirName().exists());
1536 	assert(!tempname3.exists());
1537 	
1538 	{
1539 		scope(exit)
1540 		{
1541 			if(exists(tempname)) remove(tempname);
1542 		}
1543 
1544 		tempPath.write("stuff");
1545 
1546 		tempPath.append(" more");
1547 		assert(tempPath.read(3) == "stu");
1548 		assert(tempPath.read() == "stuff more");
1549 		assert(tempPath.readText() == "stuff more");
1550 		assert(tempPath.getSize() == 10);
1551 
1552 		if(testSlurp)
1553 		{
1554 			auto parsed = tempPath.slurp!(string, string)("%s %s");
1555 			assert(equal(parsed, [tuple("stuff", "more")]));
1556 		}
1557 		
1558 		SysTime timeA, timeB, timeC;
1559 		tempPath.getTimes(timeA, timeB);
1560 		version(Windows)
1561 			tempPath.getTimesWin(timeA, timeB, timeC);
1562 		tempPath.setTimes(timeA, timeB);
1563 		timeA = tempPath.timeLastModified();
1564 		timeA = tempPath.timeLastModified(timeB);
1565 		
1566 		uint attr;
1567 		attr = tempPath.getAttributes();
1568 		attr = tempPath.getLinkAttributes();
1569 		
1570 		assert(tempPath.exists());
1571 		assert(tempPath.isFile());
1572 		assert(tempPath.existsAsFile());
1573 		assert(!tempPath.isDir());
1574 		assert(!tempPath.existsAsDir());
1575 		assert(!tempPath.isSymlink());
1576 		assert(!tempPath.existsAsSymlink());
1577 		tempPath.remove();
1578 		assert(!tempPath.exists());
1579 		assert(!tempPath.existsAsFile());
1580 		assert(!tempPath.existsAsDir());
1581 		assert(!tempPath.existsAsSymlink());
1582 	}
1583 	
1584 	{
1585 		assert(!tempPath.exists());
1586 		assert(!tempPath2.exists());
1587 
1588 		scope(exit)
1589 		{
1590 			if(exists(tempname))  remove(tempname);
1591 			if(exists(tempname2)) remove(tempname2);
1592 		}
1593 		tempPath.write("ABC");
1594 		
1595 		assert(tempPath.existsAsFile());
1596 		assert(!tempPath2.exists());
1597 
1598 		tempPath.rename(tempPath2);
1599 		
1600 		assert(!tempPath.exists());
1601 		assert(tempPath2.existsAsFile());
1602 		
1603 		tempPath2.copy(tempPath);
1604 		
1605 		assert(tempPath.existsAsFile());
1606 		assert(tempPath2.existsAsFile());
1607 	}
1608 	
1609 	{
1610 		scope(exit)
1611 		{
1612 			if(exists(tempname))  rmdir(tempname);
1613 			if(exists(tempname3)) rmdir(tempname3);
1614 			if(exists(tempname3.dirName())) rmdir(tempname3.dirName());
1615 		}
1616 		
1617 		assert(!tempPath.exists());
1618 		assert(!tempPath3.exists());
1619 		
1620 		tempPath.mkdir();
1621 		assert(tempPath.exists());
1622 		assert(!tempPath.isFile());
1623 		assert(!tempPath.existsAsFile());
1624 		assert(tempPath.isDir());
1625 		assert(tempPath.existsAsDir());
1626 		assert(!tempPath.isSymlink());
1627 		assert(!tempPath.existsAsSymlink());
1628 
1629 		tempPath3.mkdirRecurse();
1630 		assert(tempPath3.exists());
1631 		assert(!tempPath3.isFile());
1632 		assert(!tempPath3.existsAsFile());
1633 		assert(tempPath3.isDir());
1634 		assert(tempPath3.existsAsDir());
1635 		assert(!tempPath3.isSymlink());
1636 		assert(!tempPath3.existsAsSymlink());
1637 		
1638 		auto saveDirName = getcwd();
1639 		auto saveDir = path(saveDirName);
1640 		scope(exit) chdir(saveDirName);
1641 
1642 		tempPath.chdir();
1643 		assert(getcwd() == tempname);
1644 		saveDir.chdir();
1645 		assert(getcwd() == saveDirName);
1646 		
1647 		auto entries1 = (tempPath3~"..").dirEntries(SpanMode.shallow);
1648 		assert(!entries1.empty);
1649 		auto entries2 = (tempPath3~"..").dirEntries("*", SpanMode.shallow);
1650 		assert(!entries2.empty);
1651 		auto entries3 = (tempPath3~"..").dirEntries("TUNA TUNA THIS DOES NOT EXIST TUNA WHEE", SpanMode.shallow);
1652 		assert(entries3.empty);
1653 		
1654 		tempPath.rmdir();
1655 		assert(!tempPath.exists());
1656 		assert(!tempPath.existsAsFile());
1657 		assert(!tempPath.existsAsDir());
1658 		assert(!tempPath.existsAsSymlink());
1659 
1660 		tempPath3.rmdirRecurse();
1661 		assert(!tempPath.exists());
1662 		assert(!tempPath.existsAsFile());
1663 		assert(!tempPath.existsAsDir());
1664 		assert(!tempPath.existsAsSymlink());
1665 	}
1666 	
1667 	{
1668 		version(Posix)
1669 		{
1670 			assert(!tempPath.exists());
1671 			assert(!tempPath2.exists());
1672 
1673 			scope(exit)
1674 			{
1675 				if(exists(tempname2)) remove(tempname2);
1676 				if(exists(tempname))  remove(tempname);
1677 			}
1678 			tempPath.write("DEF");
1679 			
1680 			tempPath.symlink(tempPath2);
1681 			assert(tempPath2.exists());
1682 			assert(tempPath2.isFile());
1683 			assert(tempPath2.existsAsFile());
1684 			assert(!tempPath2.isDir());
1685 			assert(!tempPath2.existsAsDir());
1686 			assert(tempPath2.isSymlink());
1687 			assert(tempPath2.existsAsSymlink());
1688 			
1689 			auto linkTarget = tempPath2.readLink();
1690 			assert(linkTarget.toRawString() == tempname);
1691 		}
1692 	}
1693 	
1694 	{
1695 		assert(!tempPath.exists());
1696 
1697 		scope(exit)
1698 		{
1699 			if(exists(tempname)) remove(tempname);
1700 		}
1701 		
1702 		runShell(`echo TestScriptStuff > `~tempPath.to!string());
1703 		assert(tempPath.exists());
1704 		assert(tempPath.isFile());
1705 		assert((cast(string)tempPath.read()).strip() == "TestScriptStuff");
1706 	}
1707 	
1708 	{
1709 		assert(!tempPath3.exists());
1710 		assert(!tempPath3.up.exists());
1711 
1712 		scope(exit)
1713 		{
1714 			if(exists(tempname3)) remove(tempname3);
1715 			if(exists(tempname3.dirName())) rmdir(tempname3.dirName());
1716 		}
1717 		
1718 		tempPath3.up.mkdir();
1719 		assert(tempPath3.up.exists());
1720 		assert(tempPath3.up.isDir());
1721 				
1722 		tempPath3.up.runShell(`echo MoreTestStuff > `~tempPath3.baseName().to!string());
1723 		assert(tempPath3.exists());
1724 		assert(tempPath3.isFile());
1725 		assert((cast(string)tempPath3.read()).strip() == "MoreTestStuff");
1726 	}
1727 
1728 	{
1729 		scope(exit)
1730 		{
1731 			if(exists(tempname))  rmdir(tempname);
1732 			if(exists(tempname3)) rmdir(tempname3);
1733 			if(exists(tempname3.dirName())) rmdir(tempname3.dirName());
1734 		}
1735 		
1736 		assert(!tempPath.exists());
1737 		assert(!tempPath3.exists());
1738 		
1739 		assert(!tempPath.tryRmdir());
1740 		assert(!tempPath.tryRmdirRecurse());
1741 		assert(!tempPath.tryRemove());
1742 		assert(!tempPath.tryRename(tempPath3));
1743 		version(Posix) assert(!tempPath.trySymlink(tempPath3));
1744 		assert(!tempPath.tryCopy(tempPath3));
1745 
1746 		assert(tempPath.tryMkdir());
1747 		assert(tempPath.exists());
1748 		assert(!tempPath.tryMkdir());
1749 		assert(!tempPath.tryMkdirRecurse());
1750 
1751 		assert(tempPath.tryRmdir());
1752 		assert(!tempPath.exists());
1753 
1754 		assert(tempPath.tryMkdirRecurse());
1755 		assert(tempPath.exists());
1756 		assert(!tempPath.tryMkdirRecurse());
1757 	}
1758 }