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