1 #!/usr/bin/rdmd
2 
3 module ddocs.org.buffered;
4 
5 import std.algorithm;
6 import std.file;
7 import std.path: buildPath;
8 import std.stdio;
9 import std.string;
10 
11 auto helpString = q"(
12 -------------------------------------------------------------------------------
13 ddocs.org.buffered
14 Runs ddocs.org in a double-buffered manner.
15 Copyright (C) 2015 Ferdinand Majerech
16 
17 Usage: ddocs.org.buffered RESULT WORK [DDOCS.ORG-OPTIONS]
18 
19 ddocs.org.buffered runs `ddocs.org`, writing output to the WORK/public
20 directory and then switches RESULT and WORK directories. DDOCS.ORG-OPTIONS are
21 passed to `ddocs.org`.
22 -------------------------------------------------------------------------------
23 )";
24 
25 int main(string[] args)
26 {
27     if(args.length < 2)
28     {
29         writeln("Need at least 2 arguments");
30         writeln(helpString);
31         return 0;
32     }
33 
34     const resultPath = args[1];
35     const workPath   = args[2];
36     const ddocsArgs = args[3 .. $];
37 
38     const tempPath = resultPath ~ ".tmp";
39     // if tempPath exists, we've probably crashed in the middle of a previous exchange.
40     // Abort to avoid any more damage.
41     if(tempPath.exists)
42     {
43         writeln("TEMP PATH '" ~ tempPath ~ "' EXISTS: LEFTOVER FROM PREVIOUS FAILURE? ABORTING");
44         return 1;
45     }
46 
47     try
48     {
49         writeln("ddocs.org.buffered: Ensuring the result/work paths exist");
50         if(!resultPath.exists) { mkdirRecurse(resultPath); }
51         if(!workPath.exists)   { mkdirRecurse(workPath); }
52 
53         // Also ensure there's a directory to copy the complete log to.
54         const workLogPath = workPath.buildPath("logs");
55         if(!workLogPath.exists) { mkdirRecurse(workLogPath); }
56     }
57     catch(Exception e)
58     {
59         writeln("FAILED CREATING PUBLIC AND/OR WORK DIR! ERROR:\n\n", e);
60         return 2;
61     }
62 
63 
64     auto log = File(workPath.buildPath("ddocs.org.buffered.log"), "a");
65     void print(S ...)(S args)
66     {
67         writeln(args);
68         log.writeln(args);
69     }
70     import std.datetime;
71     print("---\n", Clock.currTime.toISOExtString(), "\n---");
72 
73     import std.process;
74     writeln("ddocs.org.buffered: generating documentation");
75     const processArgs = "ddocs.org" ~ ddocsArgs;
76     writeln(processArgs.map!(a => "'%s'".format(a)).joiner(" "));
77     auto pid = spawnProcess(processArgs, stdin, stdout, stderr, null, Config.none, workPath);
78     auto status = pid.wait;
79     if(status != 0)
80     {
81         print("FAILED TO GENERATE DOCUMENTATION! STATUS: ", status);
82         return 3;
83     }
84 
85     writeln("ddocs.org.buffered: archiving documentation log");
86     import std.datetime;
87     const logName = "ddocs.org-log.yaml";
88     const logNameXZ = "ddocs.org-log.yaml.xz";
89     // Remove the archived log if it a
90     const shell = "xz -3f %s && mv %s ./logs/ddocs.org-log.yaml.%s.xz"
91                   .format(logName, logNameXZ, Clock.currStdTime);
92     writeln(shell);
93     pid = spawnShell(shell, stdin, stdout, stderr, null, Config.none, workPath);
94     status = pid.wait;
95     if(status != 0)
96     {
97         print("FAILED TO ARCHIVE DOCUMENTATION LOG! STATUS: ", status);
98         return 4;
99     }
100 
101     try
102     {
103         writeln("ddocs.org.buffered: switching work and result directories");
104         resultPath.rename(tempPath);
105         workPath.rename(resultPath);
106         tempPath.rename(workPath);
107     }
108     catch(Exception e)
109     {
110         print("FAILED WORK/RESULT DIRECTORY EXCHANGE! ERROR:\n\n", e);
111         return 5;
112     }
113 
114     return 0;
115 }