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