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:   $(LINK2 https://github.com/Abscissa/scriptlike/blob/master/LICENSE.txt, zlib/libpng)
6 /// Authors:   Nick Sabalausky
7 
8 module scriptlike.fail;
9 
10 import std.conv;
11 import std.file;
12 import std.path;
13 import std.traits;
14 
15 // Throwable.toString(sink) isn't an override on DMD 2.064.2, and druntime
16 // won't even call any Throwable.toString on DMD 2.064.2 anyway, so use
17 // a fallback method if Throwable doesn't have toString(sink).
18 static if( MemberFunctionsTuple!(Throwable, "toString").length > 1 )
19 	private enum useFallback = false;
20 else
21 	private enum useFallback = true;
22 
23 /// This is the exception thrown by fail(). There's no need to create or throw
24 /// this directly, but it's public in case you have reason to catch it.
25 class Fail : Exception
26 {
27 	private this()
28 	{
29 		super(null);
30 	}
31 	
32 	private static string msg;
33 	private static Fail opCall(string msg, string file=__FILE__, int line=__LINE__)
34 	{
35 		Fail.msg = msg;
36 		throw cast(Fail) cast(void*) Fail.classinfo.init;
37 	}
38 	
39 	private static string fullMessage(string msg = Fail.msg)
40 	{
41 		auto appName = thisExePath().baseName();
42 
43 		version(Windows)
44 			appName = appName.stripExtension();
45 
46 		return appName~": ERROR: "~msg;
47 	}
48 	
49 	static if(!useFallback)
50 	{
51 		override void toString(scope void delegate(in char[]) sink) const
52 		{
53 			sink(fullMessage());
54 		}
55 	}
56 }
57 
58 /++
59 Call this to end your program with an error message for the user, and no
60 ugly stack trace. The error message is sent to stderr and the errorlevel is
61 set to non-zero.
62 
63 This is exception-safe, all cleanup code gets run.
64 
65 Your program's name is automatically detected from $(STD_FILE thisExePath).
66 
67 Note, on DMD 2.064.2, the error message is displayed BEFORE the exception is
68 thrown. So if you catch the Fail exception, the message will have already been
69 displayed. This is due to limitations in the older druntime, and is fixed
70 on DMD 2.065 and up.
71 
72 Example:
73 ----------------
74 auto id = 3;
75 fail("You forgot to provide a destination for id #", id, "!");
76 
77 // Output on DMD 2.065 and up:
78 // yourProgramName: ERROR: You forgot to provide a destination for id #3!
79 
80 // Output on DMD 2.064.2:
81 // yourProgramName: ERROR: You forgot to provide a destination for id #3!
82 // scriptlike.fail.Fail
83 ----------------
84 +/
85 void fail(T...)(T args)
86 {
87 	static if(useFallback)
88 	{
89 		import std.stdio;
90 		stderr.writeln(Fail.fullMessage( text(args) ));
91 		stderr.flush();
92 	}
93 	
94 	throw Fail( text(args) );
95 }
96 
97 /++
98 Calls fail() if the condition is false.
99 
100 This is much like $(FULL_STD_EXCEPTION enforce), but for for fail() instead of
101 arbitrary exceptions.
102 
103 Example:
104 ----------------
105 failEnforce(brokenSquareRoot(4)==2, "Reality broke! Expected 2, not ", brokenSquareRoot(4));
106 
107 // Output on DMD 2.065 and up:
108 // yourProgramName: ERROR: Reality broke! Expected 2, not 555
109 
110 // Output on DMD 2.064.2:
111 // yourProgramName: ERROR: Reality broke! Expected 2, not 555
112 // scriptlike.fail.Fail
113 ----------------
114 +/
115 void failEnforce(T...)(bool cond, T args)
116 {
117 	if(!cond)
118 		fail(args);
119 }