1 /++
2 $(H2 Scriptlike $(SCRIPTLIKE_VERSION))
3 
4 Extra Scriptlike-only functionality to complement $(MODULE_STD_FILE).
5 
6 Copyright: Copyright (C) 2014-2017 Nick Sabalausky
7 License:   zlib/libpng
8 Authors:   Nick Sabalausky
9 +/
10 module scriptlike.file.extras;
11 
12 import std.algorithm;
13 import std.datetime;
14 import std.traits;
15 import std.typecons;
16 
17 static import std.file;
18 static import std.path;
19 
20 import scriptlike.core;
21 import scriptlike.path;
22 import scriptlike.file.wrappers;
23 
24 /// Checks if the path exists as a directory.
25 ///
26 /// This is like $(FULL_STD_FILE isDir), but returns false instead of
27 /// throwing if the path doesn't exist.
28 bool existsAsDir(in string path) @trusted
29 {
30 	yapFunc(path.escapeShellArg());
31 	return std.file.exists(path) && std.file.isDir(path);
32 }
33 ///ditto
34 bool existsAsDir(in Path path) @trusted
35 {
36 	return existsAsDir(path.raw);
37 }
38 
39 version(unittest_scriptlike_d)
40 unittest
41 {
42 	string file, dir, notExist;
43 
44 	testFileOperation!("existsAsDir", "string")(() {
45 		mixin(useTmpName!"file");
46 		mixin(useTmpName!"dir");
47 		mixin(useTmpName!"notExist");
48 		std.file.write(file, "abc123");
49 		std.file.mkdir(dir);
50 
51 		assert( !existsAsDir(file) );
52 		assert( existsAsDir(dir) );
53 		assert( !existsAsDir(notExist) );
54 	});
55 
56 	testFileOperation!("existsAsDir", "Path")(() {
57 		mixin(useTmpName!"file");
58 		mixin(useTmpName!"dir");
59 		mixin(useTmpName!"notExist");
60 		std.file.write(file, "abc123");
61 		std.file.mkdir(dir);
62 
63 		assert( !existsAsDir(Path(file)) );
64 		assert( existsAsDir(Path(dir)) );
65 		assert( !existsAsDir(Path(notExist)) );
66 	});
67 }
68 
69 /// Checks if the path exists as a file.
70 ///
71 /// This is like $(FULL_STD_FILE isFile), but returns false instead of
72 /// throwing if the path doesn't exist.
73 bool existsAsFile(in string path) @trusted
74 {
75 	yapFunc(path.escapeShellArg());
76 	return std.file.exists(path) && std.file.isFile(path);
77 }
78 ///ditto
79 bool existsAsFile(in Path path) @trusted
80 {
81 	return existsAsFile(path.raw);
82 }
83 
84 version(unittest_scriptlike_d)
85 unittest
86 {
87 	string file, dir, notExist;
88 
89 	testFileOperation!("existsAsFile", "string")(() {
90 		mixin(useTmpName!"file");
91 		mixin(useTmpName!"dir");
92 		mixin(useTmpName!"notExist");
93 		std.file.write(file, "abc123");
94 		std.file.mkdir(dir);
95 
96 		assert( existsAsFile(file) );
97 		assert( !existsAsFile(dir) );
98 		assert( !existsAsFile(notExist) );
99 	});
100 
101 	testFileOperation!("existsAsFile", "Path")(() {
102 		mixin(useTmpName!"file");
103 		mixin(useTmpName!"dir");
104 		mixin(useTmpName!"notExist");
105 		std.file.write(file, "abc123");
106 		std.file.mkdir(dir);
107 
108 		assert( existsAsFile(Path(file)) );
109 		assert( !existsAsFile(Path(dir)) );
110 		assert( !existsAsFile(Path(notExist)) );
111 	});
112 }
113 
114 /// Checks if the path exists as a symlink.
115 ///
116 /// This is like $(FULL_STD_FILE isSymlink), but returns false instead of
117 /// throwing if the path doesn't exist.
118 bool existsAsSymlink()(in string path) @trusted
119 {
120 	yapFunc(path.escapeShellArg());
121 	return std.file.exists(path) && std.file.isSymlink(path);
122 }
123 ///ditto
124 bool existsAsSymlink(in Path path) @trusted
125 {
126 	return existsAsSymlink(path.raw);
127 }
128 
129 version(unittest_scriptlike_d)
130 unittest
131 {
132 	string file, dir, fileLink, dirLink, notExist;
133 
134 	testFileOperation!("existsAsSymlink", "string")(() {
135 		mixin(useTmpName!"file");
136 		mixin(useTmpName!"dir");
137 		mixin(useTmpName!"fileLink");
138 		mixin(useTmpName!"dirLink");
139 		mixin(useTmpName!"notExist");
140 		std.file.write(file, "abc123");
141 		std.file.mkdir(dir);
142 		version(Posix)
143 		{
144 			std.file.symlink(file, fileLink);
145 			std.file.symlink(dir, dirLink);
146 		}
147 
148 		assert( !existsAsSymlink(file) );
149 		assert( !existsAsSymlink(dir) );
150 		assert( !existsAsSymlink(notExist) );
151 		version(Posix)
152 		{
153 			assert( existsAsSymlink(fileLink) );
154 			assert( existsAsSymlink(dirLink) );
155 		}
156 	});
157 
158 	testFileOperation!("existsAsSymlink", "Path")(() {
159 		mixin(useTmpName!"file");
160 		mixin(useTmpName!"dir");
161 		mixin(useTmpName!"fileLink");
162 		mixin(useTmpName!"dirLink");
163 		mixin(useTmpName!"notExist");
164 		std.file.write(file, "abc123");
165 		std.file.mkdir(dir);
166 		version(Posix)
167 		{
168 			std.file.symlink(file, fileLink);
169 			std.file.symlink(dir, dirLink);
170 		}
171 
172 		assert( !existsAsSymlink(Path(file)) );
173 		assert( !existsAsSymlink(Path(dir)) );
174 		assert( !existsAsSymlink(Path(notExist)) );
175 		version(Posix)
176 		{
177 			assert( existsAsSymlink(Path(fileLink)) );
178 			assert( existsAsSymlink(Path(dirLink)) );
179 		}
180 	});
181 }
182 
183 /// If 'from' exists, then rename. Otherwise, do nothing and return false.
184 ///
185 /// Supports Path and command echoing.
186 ///
187 /// Returns: Success?
188 bool tryRename(T1, T2)(T1 from, T2 to)
189 	if(
190 		(is(T1==string) || is(T1==Path)) &&
191 		(is(T2==string) || is(T2==Path))
192 	)
193 {
194 	yapFunc(from.escapeShellArg(), " -> ", to.escapeShellArg());
195 	mixin(gagEcho);
196 
197 	if(from.exists())
198 	{
199 		rename(from, to);
200 		return true;
201 	}
202 
203 	return false;
204 }
205 
206 version(unittest_scriptlike_d)
207 unittest
208 {
209 	string file1, file2, notExist1, notExist2;
210 	void checkPre()
211 	{
212 		assert(!std.file.exists(notExist1));
213 		assert(!std.file.exists(notExist2));
214 
215 		assert(!std.file.exists(file2));
216 		assert(std.file.exists(file1));
217 		assert(std.file.isFile(file1));
218 		assert(cast(string) std.file.read(file1) == "abc");
219 	}
220 
221 	void checkPost()
222 	{
223 		assert(!std.file.exists(notExist1));
224 		assert(!std.file.exists(notExist2));
225 
226 		assert(!std.file.exists(file1));
227 		assert(std.file.exists(file2));
228 		assert(std.file.isFile(file2));
229 		assert(cast(string) std.file.read(file2) == "abc");
230 	}
231 
232 	testFileOperation!("tryRename", "string,string")(() {
233 		mixin(useTmpName!"file1");
234 		mixin(useTmpName!"file2");
235 		mixin(useTmpName!"notExist1");
236 		mixin(useTmpName!"notExist2");
237 		std.file.write(file1, "abc");
238 
239 		checkPre();
240 		assert( tryRename(file1, file2) );
241 		assert( !tryRename(notExist1, notExist2) );
242 		mixin(checkResult);
243 	});
244 
245 	testFileOperation!("tryRename", "string,Path")(() {
246 		mixin(useTmpName!"file1");
247 		mixin(useTmpName!"file2");
248 		mixin(useTmpName!"notExist1");
249 		mixin(useTmpName!"notExist2");
250 		std.file.write(file1, "abc");
251 
252 		checkPre();
253 		assert( tryRename(file1, Path(file2)) );
254 		assert( !tryRename(notExist1, Path(notExist2)) );
255 		mixin(checkResult);
256 	});
257 
258 	testFileOperation!("tryRename", "Path,string")(() {
259 		mixin(useTmpName!"file1");
260 		mixin(useTmpName!"file2");
261 		mixin(useTmpName!"notExist1");
262 		mixin(useTmpName!"notExist2");
263 		std.file.write(file1, "abc");
264 
265 		checkPre();
266 		assert( tryRename(Path(file1), file2) );
267 		assert( !tryRename(Path(notExist1), notExist2) );
268 		mixin(checkResult);
269 	});
270 
271 	testFileOperation!("tryRename", "Path,Path")(() {
272 		mixin(useTmpName!"file1");
273 		mixin(useTmpName!"file2");
274 		mixin(useTmpName!"notExist1");
275 		mixin(useTmpName!"notExist2");
276 		std.file.write(file1, "abc");
277 
278 		checkPre();
279 		assert( tryRename(Path(file1), Path(file2)) );
280 		assert( !tryRename(Path(notExist1), Path(notExist2)) );
281 		mixin(checkResult);
282 	});
283 }
284 
285 /// If 'name' exists, then remove. Otherwise, do nothing and return false.
286 ///
287 /// Supports Path, command echoing and dryrun.
288 ///
289 /// Returns: Success?
290 bool tryRemove(T)(T name) if(is(T==string) || is(T==Path))
291 {
292 	yapFunc(name.escapeShellArg());
293 	mixin(gagEcho);
294 
295 	if(name.exists())
296 	{
297 		remove(name);
298 		return true;
299 	}
300 	
301 	return false;
302 }
303 
304 version(unittest_scriptlike_d)
305 unittest
306 {
307 	string file, notExist;
308 	void checkPre()
309 	{
310 		assert(std.file.exists(file));
311 		assert(std.file.isFile(file));
312 		assert(cast(string) std.file.read(file) == "abc");
313 
314 		assert(!std.file.exists(notExist));
315 	}
316 
317 	void checkPost()
318 	{
319 		assert(!std.file.exists(file));
320 		assert(!std.file.exists(notExist));
321 	}
322 
323 	testFileOperation!("tryRemove", "string")(() {
324 		mixin(useTmpName!"file");
325 		mixin(useTmpName!"notExist");
326 		std.file.write(file, "abc");
327 
328 		checkPre();
329 		assert( tryRemove(file) );
330 		assert( !tryRemove(notExist) );
331 		mixin(checkResult);
332 	});
333 
334 	testFileOperation!("tryRemove", "Path")(() {
335 		mixin(useTmpName!"file");
336 		mixin(useTmpName!"notExist");
337 		std.file.write(file, "abc");
338 
339 		checkPre();
340 		assert( tryRemove(Path(file)) );
341 		assert( !tryRemove(Path(notExist)) );
342 		mixin(checkResult);
343 	});
344 }
345 
346 /// If 'name' doesn't already exist, then mkdir. Otherwise, do nothing and return false.
347 ///
348 /// Supports Path and command echoing.
349 ///
350 /// Returns: Success?
351 bool tryMkdir(T)(T name) if(is(T==string) || is(T==Path))
352 {
353 	yapFunc(name.escapeShellArg());
354 	mixin(gagEcho);
355 
356 	if(!name.exists())
357 	{
358 		mkdir(name);
359 		return true;
360 	}
361 	
362 	return false;
363 }
364 
365 version(unittest_scriptlike_d)
366 unittest
367 {
368 	string dir, alreadyExist;
369 	void checkPre()
370 	{
371 		assert(!std.file.exists(dir));
372 		assert(std.file.exists(alreadyExist));
373 	}
374 
375 	void checkPost()
376 	{
377 		assert(std.file.exists(dir));
378 		assert(std.file.isDir(dir));
379 		assert(std.file.exists(alreadyExist));
380 	}
381 
382 	testFileOperation!("tryMkdir", "string")(() {
383 		mixin(useTmpName!"dir");
384 		mixin(useTmpName!"alreadyExist");
385 		std.file.mkdir(alreadyExist);
386 
387 		checkPre();
388 		assert( tryMkdir(dir) );
389 		assert( !tryMkdir(alreadyExist) );
390 		mixin(checkResult);
391 	});
392 
393 	testFileOperation!("tryMkdir", "Path")(() {
394 		mixin(useTmpName!"dir");
395 		mixin(useTmpName!"alreadyExist");
396 		std.file.mkdir(alreadyExist);
397 
398 		checkPre();
399 		assert( tryMkdir(Path(dir)) );
400 		assert( !tryMkdir(Path(alreadyExist)) );
401 		mixin(checkResult);
402 	});
403 }
404 
405 /// If 'name' doesn't already exist, then mkdirRecurse. Otherwise, do nothing and return false.
406 ///
407 /// Supports Path and command echoing.
408 ///
409 /// Returns: Success?
410 bool tryMkdirRecurse(T)(T name) if(is(T==string) || is(T==Path))
411 {
412 	yapFunc(name.escapeShellArg());
413 	mixin(gagEcho);
414 
415 	if(!name.exists())
416 	{
417 		mkdirRecurse(name);
418 		return true;
419 	}
420 	
421 	return false;
422 }
423 
424 version(unittest_scriptlike_d)
425 unittest
426 {
427 	string dir, alreadyExist;
428 	void checkPre()
429 	{
430 		assert(!std.file.exists(dir));
431 		assert(std.file.exists(alreadyExist));
432 	}
433 
434 	void checkPost()
435 	{
436 		assert(std.file.exists(dir));
437 		assert(std.file.isDir(dir));
438 		assert(std.file.exists(alreadyExist));
439 	}
440 
441 	testFileOperation!("tryMkdirRecurse", "string")(() {
442 		mixin(useTmpName!("dir", "subdir"));
443 		mixin(useTmpName!"alreadyExist");
444 		std.file.mkdir(alreadyExist);
445 
446 		checkPre();
447 		assert( tryMkdirRecurse(dir) );
448 		assert( !tryMkdirRecurse(alreadyExist) );
449 		mixin(checkResult);
450 	});
451 
452 	testFileOperation!("tryMkdirRecurse", "Path")(() {
453 		mixin(useTmpName!("dir", "subdir"));
454 		mixin(useTmpName!"alreadyExist");
455 		std.file.mkdir(alreadyExist);
456 
457 		checkPre();
458 		assert( tryMkdirRecurse(Path(dir)) );
459 		assert( !tryMkdirRecurse(Path(alreadyExist)) );
460 		mixin(checkResult);
461 	});
462 }
463 
464 /// If 'name' exists, then rmdir. Otherwise, do nothing and return false.
465 ///
466 /// Supports Path and command echoing.
467 ///
468 /// Returns: Success?
469 bool tryRmdir(T)(T name) if(is(T==string) || is(T==Path))
470 {
471 	yapFunc(name.escapeShellArg());
472 	mixin(gagEcho);
473 
474 	if(name.exists())
475 	{
476 		rmdir(name);
477 		return true;
478 	}
479 	
480 	return false;
481 }
482 
483 version(unittest_scriptlike_d)
484 unittest
485 {
486 	string dir, notExist;
487 	void checkPre()
488 	{
489 		assert(std.file.exists(dir));
490 		assert(std.file.isDir(dir));
491 	}
492 
493 	void checkPost()
494 	{
495 		assert(!std.file.exists(dir));
496 	}
497 
498 	testFileOperation!("tryRmdir", "string")(() {
499 		mixin(useTmpName!"dir");
500 		mixin(useTmpName!"notExist");
501 		std.file.mkdir(dir);
502 
503 		checkPre();
504 		assert( tryRmdir(dir) );
505 		assert( !tryRmdir(notExist) );
506 		mixin(checkResult);
507 	});
508 
509 	testFileOperation!("tryRmdir", "Path")(() {
510 		mixin(useTmpName!"dir");
511 		mixin(useTmpName!"notExist");
512 		std.file.mkdir(dir);
513 
514 		checkPre();
515 		assert( tryRmdir(Path(dir)) );
516 		assert( !tryRmdir(Path(notExist)) );
517 		mixin(checkResult);
518 	});
519 }
520 
521 version(docs_scriptlike_d)
522 {
523 	/// Posix-only. If 'original' exists, then symlink. Otherwise, do nothing and return false.
524 	///
525 	/// Supports Path and command echoing.
526 	///
527 	/// Returns: Success?
528 	bool trySymlink(T1, T2)(T1 original, T2 link)
529 		if(
530 			(is(T1==string) || is(T1==Path)) &&
531 			(is(T2==string) || is(T2==Path))
532 		);
533 }
534 else version(Posix)
535 {
536 	bool trySymlink(T1, T2)(T1 original, T2 link)
537 		if(
538 			(is(T1==string) || is(T1==Path)) &&
539 			(is(T2==string) || is(T2==Path))
540 		)
541 	{
542 		yapFunc("[original] ", original.escapeShellArg(), " : [symlink] ", link.escapeShellArg());
543 		mixin(gagEcho);
544 
545 		if(original.exists())
546 		{
547 			symlink(original, link);
548 			return true;
549 		}
550 		
551 		return false;
552 	}
553 
554 	version(unittest_scriptlike_d)
555 	unittest
556 	{
557 		string file, link, notExistFile, notExistLink;
558 		void checkPre()
559 		{
560 			assert(std.file.exists(file));
561 			assert(std.file.isFile(file));
562 			assert(cast(string) std.file.read(file) == "abc123");
563 			
564 			assert(!std.file.exists(link));
565 
566 			assert(!std.file.exists(notExistFile));
567 			assert(!std.file.exists(notExistLink));
568 		}
569 
570 		void checkPost()
571 		{
572 			assert(std.file.exists(file));
573 			assert(std.file.isFile(file));
574 			assert(cast(string) std.file.read(file) == "abc123");
575 			
576 			assert(std.file.exists(link));
577 			assert(std.file.isSymlink(link));
578 			assert(std.file.readLink(link) == file);
579 			assert(cast(string) std.file.read(link) == "abc123");
580 
581 			assert(!std.file.exists(notExistFile));
582 			assert(!std.file.exists(notExistLink));
583 		}
584 
585 		testFileOperation!("trySymlink", "string,string")(() {
586 			mixin(useTmpName!"file");
587 			mixin(useTmpName!"link");
588 			mixin(useTmpName!"notExistFile");
589 			mixin(useTmpName!"notExistLink");
590 			std.file.write(file, "abc123");
591 
592 			checkPre();
593 			assert( trySymlink(file, link) );
594 			assert( !trySymlink(notExistFile, notExistLink) );
595 			mixin(checkResult);
596 		});
597 
598 		testFileOperation!("trySymlink", "string,Path")(() {
599 			mixin(useTmpName!"file");
600 			mixin(useTmpName!"link");
601 			mixin(useTmpName!"notExistFile");
602 			mixin(useTmpName!"notExistLink");
603 			std.file.write(file, "abc123");
604 
605 			checkPre();
606 			assert( trySymlink(file, Path(link)) );
607 			assert( !trySymlink(notExistFile, Path(notExistLink)) );
608 			mixin(checkResult);
609 		});
610 
611 		testFileOperation!("trySymlink", "Path,string")(() {
612 			mixin(useTmpName!"file");
613 			mixin(useTmpName!"link");
614 			mixin(useTmpName!"notExistFile");
615 			mixin(useTmpName!"notExistLink");
616 			std.file.write(file, "abc123");
617 
618 			checkPre();
619 			assert( trySymlink(Path(file), link) );
620 			assert( !trySymlink(Path(notExistFile), notExistLink) );
621 			mixin(checkResult);
622 		});
623 
624 		testFileOperation!("trySymlink", "Path,Path")(() {
625 			mixin(useTmpName!"file");
626 			mixin(useTmpName!"link");
627 			mixin(useTmpName!"notExistFile");
628 			mixin(useTmpName!"notExistLink");
629 			std.file.write(file, "abc123");
630 
631 			checkPre();
632 			assert( trySymlink(Path(file), Path(link)) );
633 			assert( !trySymlink(Path(notExistFile), Path(notExistLink)) );
634 			mixin(checkResult);
635 		});
636 	}
637 }
638 
639 /// If 'from' exists, then copy. Otherwise, do nothing and return false.
640 ///
641 /// Supports Path and command echoing.
642 ///
643 /// Returns: Success?
644 bool tryCopy(T1, T2)(T1 from, T2 to)
645 	if(
646 		(is(T1==string) || is(T1==Path)) &&
647 		(is(T2==string) || is(T2==Path))
648 	)
649 {
650 	yapFunc(from.escapeShellArg(), " -> ", to.escapeShellArg());
651 	mixin(gagEcho);
652 
653 	if(from.exists())
654 	{
655 		copy(from, to);
656 		return true;
657 	}
658 	
659 	return false;
660 }
661 
662 version(unittest_scriptlike_d)
663 unittest
664 {
665 	string file1, file2, notExist1, notExist2;
666 	void checkPre()
667 	{
668 		assert(!std.file.exists(notExist1));
669 		assert(!std.file.exists(notExist2));
670 
671 		assert(std.file.exists(file1));
672 		assert(std.file.isFile(file1));
673 		assert(cast(string) std.file.read(file1) == "abc");
674 
675 		assert(!std.file.exists(file2));
676 	}
677 
678 	void checkPost()
679 	{
680 		assert(!std.file.exists(notExist1));
681 		assert(!std.file.exists(notExist2));
682 
683 		assert(std.file.exists(file1));
684 		assert(std.file.isFile(file1));
685 		assert(cast(string) std.file.read(file1) == "abc");
686 
687 		assert(std.file.exists(file2));
688 		assert(std.file.isFile(file2));
689 		assert(cast(string) std.file.read(file2) == "abc");
690 	}
691 
692 	testFileOperation!("tryCopy", "string,string")(() {
693 		mixin(useTmpName!"file1");
694 		mixin(useTmpName!"file2");
695 		mixin(useTmpName!"notExist1");
696 		mixin(useTmpName!"notExist2");
697 		std.file.write(file1, "abc");
698 
699 		checkPre();
700 		assert( tryCopy(file1, file2) );
701 		assert( !tryCopy(notExist1, notExist2) );
702 		mixin(checkResult);
703 	});
704 
705 	testFileOperation!("tryCopy", "string,Path")(() {
706 		mixin(useTmpName!"file1");
707 		mixin(useTmpName!"file2");
708 		mixin(useTmpName!"notExist1");
709 		mixin(useTmpName!"notExist2");
710 		std.file.write(file1, "abc");
711 
712 		checkPre();
713 		assert( tryCopy(file1, Path(file2)) );
714 		assert( !tryCopy(notExist1, Path(notExist2)) );
715 		mixin(checkResult);
716 	});
717 
718 	testFileOperation!("tryCopy", "Path,string")(() {
719 		mixin(useTmpName!"file1");
720 		mixin(useTmpName!"file2");
721 		mixin(useTmpName!"notExist1");
722 		mixin(useTmpName!"notExist2");
723 		std.file.write(file1, "abc");
724 
725 		checkPre();
726 		assert( tryCopy(Path(file1), file2) );
727 		assert( !tryCopy(Path(notExist1), notExist2) );
728 		mixin(checkResult);
729 	});
730 
731 	testFileOperation!("tryCopy", "Path,Path")(() {
732 		mixin(useTmpName!"file1");
733 		mixin(useTmpName!"file2");
734 		mixin(useTmpName!"notExist1");
735 		mixin(useTmpName!"notExist2");
736 		std.file.write(file1, "abc");
737 
738 		checkPre();
739 		assert( tryCopy(Path(file1), Path(file2)) );
740 		assert( !tryCopy(Path(notExist1), Path(notExist2)) );
741 		mixin(checkResult);
742 	});
743 }
744 
745 /// If 'name' exists, then rmdirRecurse. Otherwise, do nothing and return false.
746 ///
747 /// Supports Path and command echoing.
748 ///
749 /// Returns: Success?
750 bool tryRmdirRecurse(T)(T name) if(is(T==string) || is(T==Path))
751 {
752 	yapFunc(name.escapeShellArg());
753 	mixin(gagEcho);
754 
755 	if(name.exists())
756 	{
757 		rmdirRecurse(name);
758 		return true;
759 	}
760 	
761 	return false;
762 }
763 
764 version(unittest_scriptlike_d)
765 unittest
766 {
767 	string dir, notExist;
768 	void checkPre()
769 	{
770 		assert(std.file.exists(dir));
771 		assert(std.file.isDir(dir));
772 
773 		assert(!std.file.exists( notExist ));
774 	}
775 
776 	void checkPost()
777 	{
778 		assert(!std.file.exists( std.path.dirName(dir) ));
779 
780 		assert(!std.file.exists( notExist ));
781 	}
782 
783 	testFileOperation!("tryRmdirRecurse", "string")(() {
784 		mixin(useTmpName!("dir", "subdir"));
785 		mixin(useTmpName!"notExist");
786 		std.file.mkdirRecurse(dir);
787 
788 		checkPre();
789 		assert(tryRmdirRecurse( std.path.dirName(dir) ));
790 		assert(!tryRmdirRecurse( notExist ));
791 		mixin(checkResult);
792 	});
793 
794 	testFileOperation!("tryRmdirRecurse", "Path")(() {
795 		mixin(useTmpName!("dir", "subdir"));
796 		mixin(useTmpName!"notExist");
797 		std.file.mkdirRecurse(dir);
798 
799 		checkPre();
800 		assert(tryRmdirRecurse(Path( std.path.dirName(dir) )));
801 		assert(!tryRmdirRecurse(Path( notExist ) ));
802 		mixin(checkResult);
803 	});
804 }
805 
806 /// Delete `name` regardless of whether it's a file or directory.
807 /// If it's a directory, it's deleted recursively, via
808 /// $(API_FILE_WRAP rmdirRecurse). Throws if the file/directory doesn't exist.
809 ///
810 /// If you just want to make sure a file/dir is gone, and don't care whether
811 /// it already exists or not, consider using `tryRemovePath` instead.
812 ///
813 /// Supports Path and command echoing.
814 void removePath(T)(T name) if(is(T==string) || is(T==Path))
815 {
816 	yapFunc(name.escapeShellArg());
817 
818 	if(name.exists() && name.isDir())
819 		rmdirRecurse(name);
820 	else
821 		remove(name);
822 }
823 
824 version(unittest_scriptlike_d)
825 unittest
826 {
827 	import std.exception;
828 	string file, dir, notExist;
829 
830 	void checkPre()
831 	{
832 		assert(std.file.exists(file));
833 		assert(std.file.isFile(file));
834 
835 		assert(std.file.exists(dir));
836 		assert(std.file.isDir(dir));
837 
838 		assert(!std.file.exists( notExist ));
839 	}
840 
841 	void checkPost()
842 	{
843 		assert(!std.file.exists( file ));
844 		assert(!std.file.exists( std.path.dirName(dir) ));
845 		assert(!std.file.exists( notExist ));
846 	}
847 
848 	testFileOperation!("removePath", "string")(() {
849 		mixin(useTmpName!"file");
850 		mixin(useTmpName!("dir", "subdir"));
851 		mixin(useTmpName!"notExist");
852 		std.file.write(file, "abc");
853 		std.file.mkdirRecurse(dir);
854 
855 		checkPre();
856 		removePath( file );
857 		removePath( std.path.dirName(dir) );
858 		if(scriptlikeDryRun)
859 			removePath( notExist );
860 		else
861 			assertThrown(removePath( notExist ));
862 		mixin(checkResult);
863 	});
864 
865 	testFileOperation!("removePath", "Path")(() {
866 		mixin(useTmpName!"file");
867 		mixin(useTmpName!("dir", "subdir"));
868 		mixin(useTmpName!"notExist");
869 		std.file.write(file, "abc");
870 		std.file.mkdirRecurse(dir);
871 
872 		checkPre();
873 		removePath(Path( file ));
874 		removePath(Path( std.path.dirName(dir) ));
875 		if(scriptlikeDryRun)
876 			removePath(Path( notExist ));
877 		else
878 			assertThrown(removePath(Path( notExist ) ));
879 		mixin(checkResult);
880 	});
881 }
882 
883 /// If `name` exists, then delete it regardless of whether it's a file or
884 /// directory. If it doesn't already exist, do nothing and return false.
885 ///
886 /// If you want an exception to be thrown if `name` doesn't already exist,
887 /// use `removePath` instead.
888 ///
889 /// Supports Path and command echoing.
890 ///
891 /// Returns: Success?
892 bool tryRemovePath(T)(T name) if(is(T==string) || is(T==Path))
893 {
894 	yapFunc(name.escapeShellArg());
895 	mixin(gagEcho);
896 
897 	if(name.exists())
898 	{
899 		removePath(name);
900 		return true;
901 	}
902 	
903 	return false;
904 }
905 
906 version(unittest_scriptlike_d)
907 unittest
908 {
909 	string file, dir, notExist;
910 
911 	void checkPre()
912 	{
913 		assert(std.file.exists(file));
914 		assert(std.file.isFile(file));
915 
916 		assert(std.file.exists(dir));
917 		assert(std.file.isDir(dir));
918 
919 		assert(!std.file.exists( notExist ));
920 	}
921 
922 	void checkPost()
923 	{
924 		assert(!std.file.exists( file ));
925 		assert(!std.file.exists( std.path.dirName(dir) ));
926 		assert(!std.file.exists( notExist ));
927 	}
928 
929 	testFileOperation!("tryRemovePath", "string")(() {
930 		mixin(useTmpName!"file");
931 		mixin(useTmpName!("dir", "subdir"));
932 		mixin(useTmpName!"notExist");
933 		std.file.write(file, "abc");
934 		std.file.mkdirRecurse(dir);
935 
936 		checkPre();
937 		assert(tryRemovePath( file ));
938 		assert(tryRemovePath( std.path.dirName(dir) ));
939 		assert(!tryRemovePath( notExist ));
940 		mixin(checkResult);
941 	});
942 
943 	testFileOperation!("tryRemovePath", "Path")(() {
944 		mixin(useTmpName!"file");
945 		mixin(useTmpName!("dir", "subdir"));
946 		mixin(useTmpName!"notExist");
947 		std.file.write(file, "abc");
948 		std.file.mkdirRecurse(dir);
949 
950 		checkPre();
951 		assert(tryRemovePath(Path( file )));
952 		assert(tryRemovePath(Path( std.path.dirName(dir) )));
953 		assert(!tryRemovePath(Path( notExist ) ));
954 		mixin(checkResult);
955 	});
956 }
957 
958 version(docs_scriptlike_d)
959 {
960 	/// Posix-only. Check the user (ie "owner") executable bit of a file. File must exist.
961 	bool isUserExec(Path path);
962 	///ditto
963 	bool isUserExec(string path);
964 
965 	/// Posix-only. Check the group executable bit of a file. File must exist.
966 	bool isGroupExec(Path path);
967 	///ditto
968 	bool isGroupExec(string path);
969 
970 	/// Posix-only. Check the world (ie "other") executable bit of a file. File must exist.
971 	bool isWorldExec(Path path);
972 	///ditto
973 	bool isWorldExec(string path);
974 }
975 else version(Posix)
976 {
977 	bool isUserExec(Path path)
978 	{
979 		return isUserExec(path.raw);
980 	}
981 
982 	bool isUserExec(string path)
983 	{
984 		import core.sys.posix.sys.stat;
985 		return !!(getAttributes(path) & S_IXUSR);
986 	}
987 
988 	bool isGroupExec(Path path)
989 	{
990 		return isGroupExec(path.raw);
991 	}
992 
993 	bool isGroupExec(string path)
994 	{
995 		import core.sys.posix.sys.stat;
996 		return !!(getAttributes(path) & S_IXGRP);
997 	}
998 
999 	bool isWorldExec(Path path)
1000 	{
1001 		return isUserExec(path.raw);
1002 	}
1003 
1004 	bool isWorldExec(string path)
1005 	{
1006 		import core.sys.posix.sys.stat;
1007 		return !!(getAttributes(path) & S_IXOTH);
1008 	}
1009 }
1010 
1011 version(Posix)
1012 version(unittest_scriptlike_d)
1013 unittest
1014 {
1015 	import std.stdio;
1016 	writeln("Running Scriptlike unittests: isUserExec / isGroupExec / isWorldExec"); stdout.flush();
1017 
1018 	mixin(useSandbox);
1019 
1020 	import scriptlike.process : run;
1021 	
1022 	writeFile("noX.txt", "Hi");
1023 	writeFile("userX.txt", "Hi");
1024 	writeFile("groupX.txt", "Hi");
1025 	writeFile("otherX.txt", "Hi");
1026 	writeFile("allX.txt", "Hi");
1027 	run("chmod -x noX.txt");
1028 	run("chmod u+x,go-x userX.txt");
1029 	run("chmod g+x,uo-x groupX.txt");
1030 	run("chmod o+x,ug-x otherX.txt");
1031 	run("chmod +x allX.txt");
1032 
1033 	assert(!isUserExec("noX.txt"));
1034 	assert(isUserExec("userX.txt"));
1035 	assert(!isUserExec("groupX.txt"));
1036 	assert(!isUserExec("otherX.txt"));
1037 	assert(isUserExec("allX.txt"));
1038 
1039 	assert(!isGroupExec("noX.txt"));
1040 	assert(!isGroupExec("userX.txt"));
1041 	assert(isGroupExec("groupX.txt"));
1042 	assert(!isGroupExec("otherX.txt"));
1043 	assert(isGroupExec("allX.txt"));
1044 
1045 	assert(!isWorldExec("noX.txt"));
1046 	assert(!isWorldExec("userX.txt"));
1047 	assert(!isWorldExec("groupX.txt"));
1048 	assert(isWorldExec("otherX.txt"));
1049 	assert(isWorldExec("allX.txt"));
1050 }