1 // Scriptlike: Utility to aid in script-like programs.
2 // Written in the D programming language.
3 
4 /// Copyright: Copyright (C) 2014-2015 Nick Sabalausky
5 /// License:   zlib/libpng
6 /// Authors:   Nick Sabalausky
7 
8 module scriptlike.file;
9 
10 import std.algorithm;
11 import std.conv;
12 import std.datetime;
13 import std.string;
14 import std.traits;
15 import std.typecons;
16 
17 static import std.file;
18 public import std.file : FileException, SpanMode,
19 	attrIsDir, attrIsFile, attrIsSymlink;
20 static import std.path;
21 
22 import scriptlike.path;
23 
24 /// If true, all commands will be echoed. By default, they will be
25 /// echoed to stdout, but you can override this with scriptlikeCustomEcho.
26 bool scriptlikeEcho = false;
27 
28 /// Alias for backwards-compatibility. This will be deprecated in the future.
29 /// You should use scriptlikeEcho insetad.
30 alias scriptlikeTraceCommands = scriptlikeEcho;
31 
32 /++
33 If true, then run, tryRun, file write, file append, and all the echoable
34 commands that modify the filesystem will be echoed to stdout (regardless
35 of scriptlikeEcho) and NOT actually executed.
36 
37 Warning! This is NOT a "set it and forget it" switch. You must still take
38 care to write your script in a way that's dryrun-safe. Two things to remember:
39 
40 1. ONLY Scriptlike's functions will obey this setting. Calling Phobos
41 functions directly will BYPASS this setting.
42 
43 2. If part of your script relies on a command having ACTUALLY been run, then
44 that command will fail. You must avoid that situation or work around it.
45 For example:
46 
47 ---------------------
48 run(`date > tempfile`);
49 
50 // The following will FAIL or behave INCORRECTLY in dryrun mode:
51 auto data = cast(string)read("tempfile");
52 run("echo "~data);
53 ---------------------
54 
55 That may be an unrealistic example, but it demonstrates the problem: Normally,
56 the code above should run fine (at least on posix). But in dryrun mode,
57 "date" will not actually be run. Therefore, tempfile will neither be created
58 nor overwritten. Result: Either an exception reading a non-existent file,
59 or outdated information will be displayed.
60 
61 Scriptlike cannot anticipate or handle such situations. So it's up to you to
62 make sure your script is dryrun-safe.
63 +/
64 bool scriptlikeDryRun = false;
65 
66 /++
67 By default, scriptlikeEcho and scriptlikeDryRun echo to stdout.
68 You can override this behavior by setting scriptlikeCustomEcho to your own
69 sink delegate. Set this to null to go back to Scriptlike's default
70 of "echo to stdout" again.
71 
72 Note, setting this does not automatically enable echoing. You still need to
73 set either scriptlikeEcho or scriptlikeDryRun to true.
74 +/
75 void delegate(string) scriptlikeCustomEcho;
76 
77 /// Output a (lazy) string through scriptlike's echo logger.
78 /// Does nothing if scriptlikeEcho and scriptlikeDryRun are both false.
79 void echoCommand(lazy string msg)
80 {
81 	import std.stdio;
82 	
83 	if(scriptlikeEcho || scriptlikeDryRun)
84 	{
85 		if(scriptlikeCustomEcho)
86 			scriptlikeCustomEcho(msg);
87 		else
88 			writeln(msg);
89 	}
90 }
91 
92 /// Checks if the path exists as a directory.
93 ///
94 /// This is like std.file.isDir, but returns false instead of throwing if the
95 /// path doesn't exist.
96 bool existsAsDir(in string path) @trusted
97 {
98 	return std.file.exists(path) && std.file.isDir(path);
99 }
100 ///ditto
101 bool existsAsDir(in Path path) @trusted
102 {
103 	return existsAsDir(path.toRawString().to!string());
104 }
105 
106 /// Checks if the path exists as a file.
107 ///
108 /// This is like std.file.isFile, but returns false instead of throwing if the
109 /// path doesn't exist.
110 bool existsAsFile(in string path) @trusted
111 {
112 	return std.file.exists(path) && std.file.isFile(path);
113 }
114 ///ditto
115 bool existsAsFile(in Path path) @trusted
116 {
117 	return existsAsFile(path.toRawString().to!string());
118 }
119 
120 /// Checks if the path exists as a symlink.
121 ///
122 /// This is like std.file.isSymlink, but returns false instead of throwing if the
123 /// path doesn't exist.
124 bool existsAsSymlink()(in string path) @trusted
125 {
126 	return std.file.exists(path) && std.file.isSymlink(path);
127 }
128 ///ditto
129 bool existsAsSymlink(in Path path) @trusted
130 {
131 	return existsAsSymlink(path.toRawString().to!string());
132 }
133 
134 // -- std.path wrappers to support Path type, scriptlikeEcho and scriptlikeDryRun --
135 
136 /// Just like std.file.read, but takes a Path.
137 void[] read(in Path name, size_t upTo = size_t.max)
138 {
139 	return std.file.read(name.toRawString().to!string(), upTo);
140 }
141 
142 /// Just like std.file.readText, but takes a Path.
143 S readText(S = string)(in Path name)
144 {
145 	return std.file.readText(name.toRawString().to!string());
146 }
147 
148 /// Just like std.file.write, but optionally takes a Path,
149 /// and obeys scriptlikeEcho and scriptlikeDryRun.
150 void write(in Path name, const void[] buffer)
151 {
152 	write(name.toRawString().to!string(), buffer);
153 }
154 
155 ///ditto
156 void write(in string name, const void[] buffer)
157 {
158 	echoCommand(text("Write ", name));
159 	
160 	if(!scriptlikeDryRun)
161 		std.file.write(name, buffer);
162 }
163 
164 /// Just like std.file.append, but optionally takes a Path,
165 /// and obeys scriptlikeEcho and scriptlikeDryRun.
166 void append(in Path name, in void[] buffer)
167 {
168 	append(name.toRawString().to!string(), buffer);
169 }
170 
171 ///ditto
172 void append(in string name, in void[] buffer)
173 {
174 	echoCommand(text("Append ", name));
175 
176 	if(!scriptlikeDryRun)
177 		std.file.append(name, buffer);
178 }
179 
180 /// Just like std.file.rename, but optionally takes Path,
181 /// and obeys scriptlikeEcho and scriptlikeDryRun.
182 void rename(in Path from, in Path to)
183 {
184 	rename(from.toRawString().to!string(), to.toRawString().to!string());
185 }
186 
187 ///ditto
188 void rename(in string from, in Path to)
189 {
190 	rename(from, to.toRawString().to!string());
191 }
192 
193 ///ditto
194 void rename(in Path from, in string to)
195 {
196 	rename(from.toRawString().to!string(), to);
197 }
198 
199 ///ditto
200 void rename(in string from, in string to)
201 {
202 	echoCommand("rename: "~from.escapeShellArg()~" -> "~to.escapeShellArg());
203 
204 	if(!scriptlikeDryRun)
205 		std.file.rename(from, to);
206 }
207 
208 /// If 'from' exists, then rename. Otherwise do nothing.
209 /// Obeys scriptlikeEcho and scriptlikeDryRun.
210 /// Returns: Success?
211 bool tryRename(T1, T2)(T1 from, T2 to)
212 	if(
213 		(is(T1==string) || is(T1==Path)) &&
214 		(is(T2==string) || is(T2==Path))
215 	)
216 {
217 	if(from.exists())
218 	{
219 		rename(from, to);
220 		return true;
221 	}
222 
223 	return false;
224 }
225 
226 /// Just like std.file.remove, but optionally takes a Path,
227 /// and obeys scriptlikeEcho and scriptlikeDryRun.
228 void remove(in Path name)
229 {
230 	remove(name.toRawString().to!string());
231 }
232 
233 ///ditto
234 void remove(in string name)
235 {
236 	echoCommand("remove: "~name.escapeShellArg());
237 
238 	if(!scriptlikeDryRun)
239 		std.file.remove(name);
240 }
241 
242 /// If 'name' exists, then remove. Otherwise do nothing.
243 /// Obeys scriptlikeEcho and scriptlikeDryRun.
244 /// Returns: Success?
245 bool tryRemove(T)(T name) if(is(T==string) || is(T==Path))
246 {
247 	if(name.exists())
248 	{
249 		remove(name);
250 		return true;
251 	}
252 	
253 	return false;
254 }
255 
256 /// Just like std.file.getSize, but takes a Path.
257 ulong getSize(in Path name)
258 {
259 	return std.file.getSize(name.toRawString().to!string());
260 }
261 
262 /// Just like std.file.getTimes, but takes a Path.
263 void getTimes(in Path name,
264 	out SysTime accessTime,
265 	out SysTime modificationTime)
266 {
267 	std.file.getTimes(name.toRawString().to!string(), accessTime, modificationTime);
268 }
269 
270 version(ddoc_scriptlike_d)
271 {
272 	/// Windows-only. Just like std.file.getTimesWin, but takes a Path.
273 	void getTimesWin(in Path name,
274 		out SysTime fileCreationTime,
275 		out SysTime fileAccessTime,
276 		out SysTime fileModificationTime);
277 }
278 else version(Windows) void getTimesWin(in Path name,
279 	out SysTime fileCreationTime,
280 	out SysTime fileAccessTime,
281 	out SysTime fileModificationTime)
282 {
283 	std.file.getTimesWin(name.toRawString().to!string(), fileCreationTime, fileAccessTime, fileModificationTime);
284 }
285 
286 /// Just like std.file.setTimes, but optionally takes a Path,
287 /// and obeys scriptlikeEcho and scriptlikeDryRun.
288 void setTimes(in Path name,
289 	SysTime accessTime,
290 	SysTime modificationTime)
291 {
292 	setTimes(name.toRawString().to!string(), accessTime, modificationTime);
293 }
294 
295 ///ditto
296 void setTimes(in string name,
297 	SysTime accessTime,
298 	SysTime modificationTime)
299 {
300 	echoCommand(text(
301 		"setTimes: ", name.escapeShellArg(),
302 		" Accessed ", accessTime, "; Modified ", modificationTime
303 	));
304 
305 	if(!scriptlikeDryRun)
306 		std.file.setTimes(name, accessTime, modificationTime);
307 }
308 
309 /// Just like std.file.timeLastModified, but takes a Path.
310 SysTime timeLastModified(in Path name)
311 {
312 	return std.file.timeLastModified(name.toRawString().to!string());
313 }
314 
315 /// Just like std.file.timeLastModified, but takes a Path.
316 SysTime timeLastModified(in Path name, SysTime returnIfMissing)
317 {
318 	return std.file.timeLastModified(name.toRawString().to!string(), returnIfMissing);
319 }
320 
321 /// Just like std.file.exists, but takes a Path.
322 bool exists(in Path name) @trusted
323 {
324 	return std.file.exists(name.toRawString().to!string());
325 }
326 
327 /// Just like std.file.getAttributes, but takes a Path.
328 uint getAttributes(in Path name)
329 {
330 	return std.file.getAttributes(name.toRawString().to!string());
331 }
332 
333 /// Just like std.file.getLinkAttributes, but takes a Path.
334 uint getLinkAttributes(in Path name)
335 {
336 	return std.file.getLinkAttributes(name.toRawString().to!string());
337 }
338 
339 /// Just like std.file.isDir, but takes a Path.
340 @property bool isDir(in Path name)
341 {
342 	return std.file.isDir(name.toRawString().to!string());
343 }
344 
345 /// Just like std.file.isFile, but takes a Path.
346 @property bool isFile(in Path name)
347 {
348 	return std.file.isFile(name.toRawString().to!string());
349 }
350 
351 /// Just like std.file.isSymlink, but takes a Path.
352 @property bool isSymlink(Path name)
353 {
354 	return std.file.isSymlink(name.toRawString().to!string());
355 }
356 
357 /// Just like std.file.getcwd, but returns a Path.
358 Path getcwd()
359 {
360 	return Path( std.file.getcwd() );
361 }
362 
363 /// Just like std.file.chdir, but takes a Path, and echoes if scriptlikeEcho is true.
364 void chdir(in Path pathname)
365 {
366 	chdir(pathname.toRawString().to!string());
367 }
368 
369 /// Just like std.file.chdir, but echoes if scriptlikeEcho is true.
370 void chdir(in string pathname)
371 {
372 	echoCommand("chdir: "~pathname.escapeShellArg());
373 	std.file.chdir(pathname);
374 }
375 
376 /// Just like std.file.mkdir, but optionally takes a Path,
377 /// and obeys scriptlikeEcho and scriptlikeDryRun.
378 void mkdir(in Path pathname)
379 {
380 	mkdir(pathname.toRawString().to!string());
381 }
382 
383 ///ditto
384 void mkdir(in string pathname)
385 {
386 	echoCommand("mkdir: "~pathname.escapeShellArg());
387 
388 	if(!scriptlikeDryRun)
389 		std.file.mkdir(pathname);
390 }
391 
392 /// If 'name' doesn't already exist, then mkdir. Otherwise do nothing.
393 /// Obeys scriptlikeEcho and scriptlikeDryRun.
394 /// Returns: Success?
395 bool tryMkdir(T)(T name) if(is(T==string) || is(T==Path))
396 {
397 	if(!name.exists())
398 	{
399 		mkdir(name);
400 		return true;
401 	}
402 	
403 	return false;
404 }
405 
406 /// Just like std.file.mkdirRecurse, but optionally takes a Path,
407 /// and obeys scriptlikeEcho and scriptlikeDryRun.
408 void mkdirRecurse(in Path pathname)
409 {
410 	mkdirRecurse(pathname.toRawString().to!string());
411 }
412 
413 ///ditto
414 void mkdirRecurse(in string pathname)
415 {
416 	echoCommand("mkdirRecurse: "~pathname.escapeShellArg());
417 
418 	if(!scriptlikeDryRun)
419 		std.file.mkdirRecurse(pathname);
420 }
421 
422 /// If 'name' doesn't already exist, then mkdirRecurse. Otherwise do nothing.
423 /// Obeys scriptlikeEcho and scriptlikeDryRun.
424 /// Returns: Success?
425 bool tryMkdirRecurse(T)(T name) if(is(T==string) || is(T==Path))
426 {
427 	if(!name.exists())
428 	{
429 		mkdirRecurse(name);
430 		return true;
431 	}
432 	
433 	return false;
434 }
435 
436 /// Just like std.file.rmdir, but optionally takes a Path,
437 /// and obeys scriptlikeEcho and scriptlikeDryRun.
438 void rmdir(in Path pathname)
439 {
440 	rmdir(pathname.toRawString().to!string());
441 }
442 
443 ///ditto
444 void rmdir(in string pathname)
445 {
446 	echoCommand("rmdir: "~pathname.escapeShellArg());
447 
448 	if(!scriptlikeDryRun)
449 		std.file.rmdir(pathname);
450 }
451 
452 /// If 'name' exists, then rmdir. Otherwise do nothing.
453 /// Obeys scriptlikeEcho and scriptlikeDryRun.
454 /// Returns: Success?
455 bool tryRmdir(T)(T name) if(is(T==string) || is(T==Path))
456 {
457 	if(name.exists())
458 	{
459 		rmdir(name);
460 		return true;
461 	}
462 	
463 	return false;
464 }
465 
466 version(ddoc_scriptlike_d)
467 {
468 	/// Posix-only. Just like std.file.symlink, but optionally takes Path,
469 	/// and obeys scriptlikeEcho and scriptlikeDryRun.
470 	void symlink(Path original, Path link);
471 
472 	///ditto
473 	void symlink(string original, Path link);
474 
475 	///ditto
476 	void symlink(Path original, string link);
477 
478 	///ditto
479 	void symlink(string original, string link);
480 
481 	/// Posix-only. If 'original' exists, then symlink. Otherwise do nothing.
482 	/// Obeys scriptlikeEcho and scriptlikeDryRun.
483 	/// Returns: Success?
484 	bool trySymlink(T1, T2)(T1 original, T2 link)
485 		if(
486 			(is(T1==string) || is(T1==Path)) &&
487 			(is(T2==string) || is(T2==Path))
488 		);
489 
490 	/// Posix-only. Just like std.file.readLink, but operates on Path.
491 	Path readLink(Path link);
492 }
493 else version(Posix)
494 {
495 	void symlink(Path original, Path link)
496 	{
497 		symlink(original.toRawString().to!string(), link.toRawString().to!string());
498 	}
499 
500 	void symlink(string original, Path link)
501 	{
502 		symlink(original, link.toRawString().to!string());
503 	}
504 
505 	void symlink(Path original, string link)
506 	{
507 		symlink(original.toRawString().to!string(), link);
508 	}
509 
510 	void symlink(string original, string link)
511 	{
512 		echoCommand("symlink: [original] "~original.escapeShellArg()~" : [symlink] "~link.escapeShellArg());
513 
514 		if(!scriptlikeDryRun)
515 			std.file.symlink(original, link);
516 	}
517 
518 	bool trySymlink(T1, T2)(T1 original, T2 link)
519 		if(
520 			(is(T1==string) || is(T1==Path)) &&
521 			(is(T2==string) || is(T2==Path))
522 		)
523 	{
524 		if(original.exists())
525 		{
526 			symlink(original, link);
527 			return true;
528 		}
529 		
530 		return false;
531 	}
532 
533 	Path readLink(Path link)
534 	{
535 		return Path( std.file.readLink(link.toRawString().to!string()) );
536 	}
537 }
538 
539 /// Just like std.file.copy, but optionally takes Path,
540 /// and obeys scriptlikeEcho and scriptlikeDryRun.
541 void copy(in Path from, in Path to)
542 {
543 	copy(from.toRawString().to!string(), to.toRawString().to!string());
544 }
545 
546 ///ditto
547 void copy(in string from, in Path to)
548 {
549 	copy(from, to.toRawString().to!string());
550 }
551 
552 ///ditto
553 void copy(in Path from, in string to)
554 {
555 	copy(from.toRawString().to!string(), to);
556 }
557 
558 ///ditto
559 void copy(in string from, in string to)
560 {
561 	echoCommand("copy: "~from.escapeShellArg()~" -> "~to.escapeShellArg());
562 
563 	if(!scriptlikeDryRun)
564 		std.file.copy(from, to);
565 }
566 
567 /// If 'from' exists, then copy. Otherwise do nothing.
568 /// Obeys scriptlikeEcho and scriptlikeDryRun.
569 /// Returns: Success?
570 bool tryCopy(T1, T2)(T1 from, T2 to)
571 	if(
572 		(is(T1==string) || is(T1==Path)) &&
573 		(is(T2==string) || is(T2==Path))
574 	)
575 {
576 	if(from.exists())
577 	{
578 		copy(from, to);
579 		return true;
580 	}
581 	
582 	return false;
583 }
584 
585 /// Just like std.file.rmdirRecurse, but optionally takes a Path,
586 /// and obeys scriptlikeEcho and scriptlikeDryRun.
587 void rmdirRecurse(in Path pathname)
588 {
589 	rmdirRecurse(pathname.toRawString().to!string());
590 }
591 
592 ///ditto
593 void rmdirRecurse(in string pathname)
594 {
595 	echoCommand("rmdirRecurse: "~pathname.escapeShellArg());
596 
597 	if(!scriptlikeDryRun)
598 		std.file.rmdirRecurse(pathname);
599 }
600 
601 /// If 'name' exists, then rmdirRecurse. Otherwise do nothing.
602 /// Obeys scriptlikeEcho and scriptlikeDryRun.
603 /// Returns: Success?
604 bool tryRmdirRecurse(T)(T name) if(is(T==string) || is(T==Path))
605 {
606 	if(name.exists())
607 	{
608 		rmdirRecurse(name);
609 		return true;
610 	}
611 	
612 	return false;
613 }
614 
615 /// Just like std.file.dirEntries, but takes a Path.
616 auto dirEntries(Path path, SpanMode mode, bool followSymlink = true)
617 {
618 	return std.file.dirEntries(path.toRawString().to!string(), mode, followSymlink);
619 }
620 
621 /// Just like std.file.dirEntries, but takes a Path.
622 auto dirEntries(Path path, string pattern, SpanMode mode,
623 	bool followSymlink = true)
624 {
625 	return std.file.dirEntries(path.toRawString().to!string(), pattern, mode, followSymlink);
626 }
627 
628 /// Just like std.file.slurp, but takes a Path.
629 template slurp(Types...)
630 {
631 	auto slurp(Path filename, in string format)
632 	{
633 		return std.file.slurp!Types(filename.toRawString().to!string(), format);
634 	}
635 }
636 
637 /// Just like std.file.thisExePath, but returns a Path.
638 @trusted Path thisExePath()
639 {
640 	return Path( std.file.thisExePath() );
641 }
642 
643 /// Just like std.file.tempDir, but returns a Path.
644 @trusted Path tempDir()
645 {
646 	return Path( std.file.tempDir() );
647 }
648 
649 version(unittest_scriptlike_d)
650 unittest
651 {
652 	// std.file.slurp seems to randomly trigger an internal std.algorithm
653 	// assert failure on DMD 2.064.2, so don't test it there. Seems
654 	// to be fixed in DMD 2.065.
655 	static import std.compiler;
656 	static if(
657 		std.compiler.vendor == std.compiler.Vendor.digitalMars ||
658 		(std.compiler.version_major == 2 && std.compiler.version_minor == 64)
659 	)
660 		enum testSlurp = false;
661 	else
662 		enum testSlurp = true;
663 
664 	import std.stdio : writeln;
665 	import std.process : thisProcessID;
666 	import core.thread;
667 	alias copy = scriptlike.path.copy;
668 
669 	writeln("Running Scriptlike unittests: std.file wrappers");
670 	
671 	immutable tempname  = std.path.buildPath(std.file.tempDir(), "deleteme.script like.unit test.pid"  ~ to!string(thisProcessID));
672 	immutable tempname2 = std.path.buildPath(std.file.tempDir(), "deleteme.script like.unit test2.pid" ~ to!string(thisProcessID));
673 	immutable tempname3 = std.path.buildPath(std.file.tempDir(), "deleteme.script like.unit test3.pid" ~ to!string(thisProcessID), "somefile");
674 	auto tempPath  = Path(tempname);
675 	auto tempPath2 = Path(tempname2);
676 	auto tempPath3 = Path(tempname3);
677 	assert(!std.file.exists(tempname));
678 	assert(!std.file.exists(tempname2));
679 	assert(!std.file.exists( std.path.dirName(tempname3) ));
680 	assert(!std.file.exists(tempname3));
681 	
682 	{
683 		scope(exit)
684 		{
685 			if(std.file.exists(tempname)) std.file.remove(tempname);
686 		}
687 
688 		tempPath.write("stuff");
689 
690 		tempPath.append(" more");
691 		assert(tempPath.read(3) == "stu");
692 		assert(tempPath.read() == "stuff more");
693 		assert(tempPath.readText() == "stuff more");
694 		assert(tempPath.getSize() == 10);
695 
696 		if(testSlurp)
697 		{
698 			auto parsed = tempPath.slurp!(string, string)("%s %s");
699 			assert(equal(parsed, [tuple("stuff", "more")]));
700 		}
701 		
702 		SysTime timeA, timeB, timeC;
703 		tempPath.getTimes(timeA, timeB);
704 		version(Windows)
705 			tempPath.getTimesWin(timeA, timeB, timeC);
706 		tempPath.setTimes(timeA, timeB);
707 		timeA = tempPath.timeLastModified();
708 		timeA = tempPath.timeLastModified(timeB);
709 		
710 		uint attr;
711 		attr = tempPath.getAttributes();
712 		attr = tempPath.getLinkAttributes();
713 		
714 		assert(tempPath.exists());
715 		assert(tempPath.isFile());
716 		assert(tempPath.existsAsFile());
717 		assert(!tempPath.isDir());
718 		assert(!tempPath.existsAsDir());
719 		assert(!tempPath.isSymlink());
720 		assert(!tempPath.existsAsSymlink());
721 		tempPath.remove();
722 		assert(!tempPath.exists());
723 		assert(!tempPath.existsAsFile());
724 		assert(!tempPath.existsAsDir());
725 		assert(!tempPath.existsAsSymlink());
726 	}
727 	
728 	{
729 		assert(!tempPath.exists());
730 		assert(!tempPath2.exists());
731 
732 		scope(exit)
733 		{
734 			if(std.file.exists(tempname))  std.file.remove(tempname);
735 			if(std.file.exists(tempname2)) std.file.remove(tempname2);
736 		}
737 		tempPath.write("ABC");
738 		
739 		assert(tempPath.existsAsFile());
740 		assert(!tempPath2.exists());
741 
742 		tempPath.rename(tempPath2);
743 		
744 		assert(!tempPath.exists());
745 		assert(tempPath2.existsAsFile());
746 		
747 		tempPath2.copy(tempPath);
748 		
749 		assert(tempPath.existsAsFile());
750 		assert(tempPath2.existsAsFile());
751 	}
752 	
753 	{
754 		scope(exit)
755 		{
756 			if(std.file.exists(tempname))  std.file.rmdir(tempname);
757 			if(std.file.exists(tempname3)) std.file.rmdir(tempname3);
758 			if(std.file.exists( std.path.dirName(tempname3) )) std.file.rmdir( std.path.dirName(tempname3) );
759 		}
760 		
761 		assert(!tempPath.exists());
762 		assert(!tempPath3.exists());
763 		
764 		tempPath.mkdir();
765 		assert(tempPath.exists());
766 		assert(!tempPath.isFile());
767 		assert(!tempPath.existsAsFile());
768 		assert(tempPath.isDir());
769 		assert(tempPath.existsAsDir());
770 		assert(!tempPath.isSymlink());
771 		assert(!tempPath.existsAsSymlink());
772 
773 		tempPath3.mkdirRecurse();
774 		assert(tempPath3.exists());
775 		assert(!tempPath3.isFile());
776 		assert(!tempPath3.existsAsFile());
777 		assert(tempPath3.isDir());
778 		assert(tempPath3.existsAsDir());
779 		assert(!tempPath3.isSymlink());
780 		assert(!tempPath3.existsAsSymlink());
781 		
782 		auto saveDirName = std.file.getcwd();
783 		auto saveDir = Path(saveDirName);
784 		scope(exit) chdir(saveDirName);
785 
786 		tempPath.chdir();
787 		assert(getcwd() == tempname);
788 		saveDir.chdir();
789 		assert(getcwd() == saveDirName);
790 		
791 		auto entries1 = (tempPath3~"..").dirEntries(SpanMode.shallow);
792 		assert(!entries1.empty);
793 		auto entries2 = (tempPath3~"..").dirEntries("*", SpanMode.shallow);
794 		assert(!entries2.empty);
795 		auto entries3 = (tempPath3~"..").dirEntries("TUNA TUNA THIS DOES NOT EXIST TUNA WHEE", SpanMode.shallow);
796 		assert(entries3.empty);
797 		
798 		tempPath.rmdir();
799 		assert(!tempPath.exists());
800 		assert(!tempPath.existsAsFile());
801 		assert(!tempPath.existsAsDir());
802 		assert(!tempPath.existsAsSymlink());
803 
804 		tempPath3.rmdirRecurse();
805 		assert(!tempPath.exists());
806 		assert(!tempPath.existsAsFile());
807 		assert(!tempPath.existsAsDir());
808 		assert(!tempPath.existsAsSymlink());
809 	}
810 	
811 	{
812 		version(Posix)
813 		{
814 			assert(!tempPath.exists());
815 			assert(!tempPath2.exists());
816 
817 			scope(exit)
818 			{
819 				if(std.file.exists(tempname2)) std.file.remove(tempname2);
820 				if(std.file.exists(tempname))  std.file.remove(tempname);
821 			}
822 			tempPath.write("DEF");
823 			
824 			tempPath.symlink(tempPath2);
825 			assert(tempPath2.exists());
826 			assert(tempPath2.isFile());
827 			assert(tempPath2.existsAsFile());
828 			assert(!tempPath2.isDir());
829 			assert(!tempPath2.existsAsDir());
830 			assert(tempPath2.isSymlink());
831 			assert(tempPath2.existsAsSymlink());
832 			
833 			auto linkTarget = tempPath2.readLink();
834 			assert(linkTarget.toRawString() == tempname);
835 		}
836 	}
837 	
838 	{
839 		assert(!tempPath.exists());
840 
841 		scope(exit)
842 		{
843 			if(std.file.exists(tempname)) std.file.remove(tempname);
844 		}
845 
846 		import scriptlike.process;
847 		run(`echo TestScriptStuff > `~tempPath.to!string());
848 		assert(tempPath.exists());
849 		assert(tempPath.isFile());
850 		assert((cast(string)tempPath.read()).strip() == "TestScriptStuff");
851 		tempPath.remove();
852 		assert(!tempPath.exists());
853 
854 		auto errlevel = tryRun(`echo TestScriptStuff > `~tempPath.to!string());
855 		assert(tempPath.exists());
856 		assert(tempPath.isFile());
857 		assert((cast(string)tempPath.read()).strip() == "TestScriptStuff");
858 		assert(errlevel == 0);
859 		tempPath.remove();
860 		assert(!tempPath.exists());
861 
862 		import scriptlike.process;
863 		getcwd().run(`echo TestScriptStuff > `~tempPath.to!string());
864 		getcwd().tryRun(`echo TestScriptStuff > `~tempPath.to!string());
865 	}
866 	
867 	{
868 		assert(!tempPath3.exists());
869 		assert(!tempPath3.up.exists());
870 
871 		scope(exit)
872 		{
873 			if(std.file.exists(tempname3)) std.file.remove(tempname3);
874 			if(std.file.exists( std.path.dirName(tempname3) )) std.file.rmdir( std.path.dirName(tempname3) );
875 		}
876 		
877 		tempPath3.up.mkdir();
878 		assert(tempPath3.up.exists());
879 		assert(tempPath3.up.isDir());
880 				
881 		import scriptlike.process;
882 		tempPath3.up.run(`echo MoreTestStuff > `~tempPath3.baseName().to!string());
883 		assert(tempPath3.exists());
884 		assert(tempPath3.isFile());
885 		assert((cast(string)tempPath3.read()).strip() == "MoreTestStuff");
886 	}
887 
888 	{
889 		scope(exit)
890 		{
891 			if(std.file.exists(tempname))  std.file.rmdir(tempname);
892 			if(std.file.exists(tempname3)) std.file.rmdir(tempname3);
893 			if(std.file.exists( std.path.dirName(tempname3) )) std.file.rmdir( std.path.dirName(tempname3) );
894 		}
895 		
896 		assert(!tempPath.exists());
897 		assert(!tempPath3.exists());
898 		
899 		assert(!tempPath.tryRmdir());
900 		assert(!tempPath.tryRmdirRecurse());
901 		assert(!tempPath.tryRemove());
902 		assert(!tempPath.tryRename(tempPath3));
903 		version(Posix) assert(!tempPath.trySymlink(tempPath3));
904 		assert(!tempPath.tryCopy(tempPath3));
905 
906 		assert(tempPath.tryMkdir());
907 		assert(tempPath.exists());
908 		assert(!tempPath.tryMkdir());
909 		assert(!tempPath.tryMkdirRecurse());
910 
911 		assert(tempPath.tryRmdir());
912 		assert(!tempPath.exists());
913 
914 		assert(tempPath.tryMkdirRecurse());
915 		assert(tempPath.exists());
916 		assert(!tempPath.tryMkdirRecurse());
917 	}
918 }