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