1 module app;
2 
3 import std.stdio;
4 
5 ///
6 struct CmdOptions
7 {
8 	private string stdoutFile;
9 	private string stderrFile;
10 	private string scriptOnRestart;
11 	private int max = -1;
12 	private int minUptime = 1000;
13 
14 	///
15 	public void parse(ref string[] _args)
16 	{
17 		import std.getopt : getopt;
18 
19 		getopt(_args, "log|l", &stdoutFile, "err|e", &stderrFile, "script",
20 				&scriptOnRestart, "min-uptime", &minUptime, "max|m", &max);
21 	}
22 
23 	///
24 	public void print()
25 	{
26 		if (useStdOutFile)
27 			log("stdout file: %s", stdoutFile);
28 		if (useStdErrFile)
29 			log("stderr file: %s", stderrFile);
30 		if (scriptOnRestart.length > 0)
31 			log("restart script: %s", scriptOnRestart);
32 		if (max > 0)
33 			log("max runs: %s", max);
34 	}
35 
36 	///
37 	@property bool useStdOutFile() const
38 	{
39 		return stdoutFile.length > 0;
40 	}
41 	///
42 	@property bool useStdErrFile() const
43 	{
44 		return stderrFile.length > 0;
45 	}
46 }
47 
48 private void log(T...)(string _format, T params)
49 {
50 	import std.stdio : writeln, writefln;
51 
52 	if (_format.length == 0)
53 	{
54 		writeln("");
55 	}
56 	else
57 	{
58 		import std.datetime : Clock;
59 
60 		writefln("-- %s -- " ~ _format, Clock.currTime, params);
61 	}
62 }
63 
64 private enum helpMessage = `forever-d [options] [program] <Arguments...>
65 
66 options:
67     -m -max     Max runs of [program]. default is 0 (unlimited)
68     -l -log     File to print [program] std-out to. By default it's printed to stdout of forever-d
69     -e -err     File to print [program] std-err to. By default it's printed to stdout of forever-d
70     -script     Script run on process restart. Use [script-env] ENV variables in there.
71     -min-uptime Minimum time in milliseconds program needs to run so it will restart again. (Defaults to 1000)
72 
73 script-env:
74     FD_EXITCODE     exit code of [program]
75     FD_RESTARTS     number of restarts
76     FD_CMDLINE      the actual cmd line used for [program]`;
77 
78 ///
79 void main(string[] _args)
80 {
81 	import std.process : spawnProcess, spawnShell, wait, Config;
82 	import core.thread : Thread;
83 	import std.array : join;
84 	import std.conv : to;
85 	import std.datetime : StopWatch;
86 
87 	log("Starting forever-d");
88 
89 	if (_args.length < 2 || _args[1] == "--help")
90 	{
91 		writeln(helpMessage);
92 		return;
93 	}
94 
95 	CmdOptions options;
96 	options.parse(_args);
97 
98 	options.print();
99 
100 	auto cmdline = _args[1 .. $].join(" ");
101 
102 	string[string] envVars;
103 	int restartCount;
104 	StopWatch uptime;
105 	bool canRestart = true;
106 
107 	envVars["FD_CMDLINE"] = cmdline;
108 
109 	File outStream = stdout;
110 	File errStream = stderr;
111 
112 	while ((options.max == -1 || (options.max-- > 0)) && canRestart)
113 	{
114 		if (options.useStdOutFile)
115 		{
116 			if (restartCount > 0)
117 				outStream.detach();
118 			outStream = File(options.stdoutFile, "a+");
119 		}
120 
121 		if (options.useStdErrFile)
122 		{
123 			if (restartCount > 0)
124 				errStream.detach();
125 			errStream = File(options.stderrFile, "a+");
126 		}
127 
128 		log("Starting: '%s'", cmdline);
129 		uptime.start();
130 
131 		auto pid = spawnProcess(_args[1 .. $], std.stdio.stdin, outStream,
132 				errStream, null, Config.retainStdout);
133 
134 		auto exitCode = wait(pid);
135 		uptime.stop();
136 		if (uptime.peek().msecs < options.minUptime)
137 			canRestart = false;
138 		uptime.reset();
139 		log("");
140 		log("Process Ended. Exitcode: %s", exitCode);
141 
142 		restartCount++;
143 
144 		if (options.scriptOnRestart.length > 0)
145 		{
146 			envVars["FD_EXITCODE"] = to!string(exitCode);
147 			envVars["FD_RESTARTS"] = to!string(restartCount);
148 
149 			spawnShell(options.scriptOnRestart, envVars);
150 		}
151 	}
152 }