1 /**
2  * IO related functions
3  */
4 
5 module unit_threaded.io;
6 
7 import std.concurrency;
8 import std.stdio;
9 import std.conv;
10 
11 
12 private shared(bool) _debugOutput = false; ///whether or not to print debug messages
13 private shared(bool) _forceEscCodes = false; ///whether or not to use ANSI escape codes anyway
14 
15 
16 package void enableDebugOutput() {
17     synchronized {
18         _debugOutput = true;
19     }
20 }
21 
22 package bool isDebugOutputEnabled() {
23     synchronized {
24         return _debugOutput;
25     }
26 }
27 
28 package void forceEscCodes() {
29     _forceEscCodes = true;
30 }
31 
32 void addToOutput(ref string output, in string msg) {
33     if(_debugOutput) {
34         import std.stdio;
35         writeln(msg);
36     } else {
37         output ~= msg;
38     }
39 }
40 
41 /**
42  * Write if debug output was enabled. Not thread-safe in the sense that it
43  * will get printed out immediately and may overlap with other output.
44  */
45 void writelnUt(T...)(T args) {
46     import std.stdio;
47     if(_debugOutput) writeln("    ", args);
48 }
49 
50 package void utWrite(T...)(T args) {
51     WriterThread.get().write(args);
52 }
53 
54 package void utWriteln(T...)(T args) {
55     WriterThread.get().writeln(args);
56 }
57 
58 package void utWritelnGreen(T...)(T args) {
59     WriterThread.get().writelnGreen(args);
60 }
61 
62 package void utWritelnRed(T...)(T args) {
63     WriterThread.get().writelnRed(args);
64 }
65 
66 package void utWriteRed(T...)(T args) {
67     WriterThread.get().writeRed(args);
68 }
69 
70 package void utWriteYellow(T...)(T args) {
71     WriterThread.get().writeYellow(args);
72 }
73 
74 
75 /**
76  * Thread to output to stdout
77  */
78 class WriterThread {
79     static WriterThread get() {
80         if(!_instantiated) {
81             synchronized {
82                 if (_instance is null) {
83                     _instance = new WriterThread;
84                 }
85                 _instantiated = true;
86             }
87         }
88         return _instance;
89     }
90 
91     void write(T...)(T args) {
92         _tid.send(text(args));
93     }
94 
95     void writeln(T...)(T args) {
96         write(args, "\n");
97     }
98 
99     void writelnGreen(T...)(T args) {
100         _tid.send(green(text(args) ~ "\n"));
101     }
102 
103     void writelnRed(T...)(T args) {
104         _tid.send(red(text(args) ~ "\n"));
105     }
106 
107     void writeRed(T...)(T args) {
108         _tid.send(red(text(args)));
109     }
110 
111     void writeYellow(T...)(T args) {
112         _tid.send(yellow(text(args)));
113     }
114 
115     void join() {
116         _tid.send(thisTid); //tell it to join
117         receiveOnly!Tid(); //wait for it to join
118     }
119 
120 private:
121 
122     this() {
123         _tid = spawn(&threadWriter);
124         _escCodes = [ "red": "\033[31;1m",
125                       "green": "\033[32;1m",
126                       "yellow": "\033[33;1m",
127                       "cancel": "\033[0;;m" ];
128 
129         version(Posix) {
130             import core.sys.posix.unistd;
131             _useEscCodes = _forceEscCodes || isatty(stdout.fileno()) != 0;
132         }
133     }
134 
135     /**
136      * Generates coloured output on POSIX systems
137      */
138     string green(in string msg) const {
139         return escCode("green") ~ msg ~ escCode("cancel");
140     }
141 
142     string red(in string msg) const {
143         return escCode("red") ~ msg ~ escCode("cancel");
144     }
145 
146     string yellow(in string msg) const {
147         return escCode("yellow") ~ msg ~ escCode("cancel");
148     }
149 
150     string escCode(in string code) const {
151         return _useEscCodes ? _escCodes[code] : "";
152     }
153 
154 
155     Tid _tid;
156     string[string] _escCodes;
157     bool _useEscCodes;
158 
159     static bool _instantiated; // Thread local
160     __gshared WriterThread _instance;
161 }
162 
163 private void threadWriter() {
164     auto done = false;
165     Tid tid;
166 
167     auto saveStdout = stdout;
168     auto saveStderr = stderr;
169 
170     if(!isDebugOutputEnabled()) {
171         version(Posix) {
172             enum nullFileName = "/dev/null";
173         } else {
174             enum nullFileName = "NUL";
175         }
176 
177         stdout = File(nullFileName, "w");
178         stderr = File(nullFileName, "w");
179     }
180 
181     while(!done) {
182         string output;
183         receive(
184             (string msg) {
185                 output ~= msg;
186             },
187             (Tid i) {
188                 done = true;
189                 tid = i;
190             },
191             (OwnerTerminated trm) {
192                 done = true;
193             }
194         );
195         saveStdout.write(output);
196     }
197     saveStdout.flush();
198     stdout = saveStdout;
199     stderr = saveStderr;
200     if(tid != Tid.init) tid.send(thisTid);
201 }