1 /** 
2  * Core routines and types
3  *
4  * Authors: Tristan Brice Velloza Kildaire (deavmi)
5  */
6 module gogga.core;
7 
8 import dlog.core;
9 import dlog.basic;
10 import std.conv : to;
11 import dlog.utilities : flatten;
12 import std.array : join;
13 
14 /** 
15  * The gogga styles supported
16  */
17 public enum GoggaMode
18 {
19     /** 
20      * TwoKTwenty3 is: `[<file>] (<module>:<lineNumber>) <message>`
21      */
22     TwoKTwenty3,
23 
24     /** 
25      * Simple mode is just: `[<LEVEL>] <message>`
26      */
27     SIMPLE,
28 
29     /** 
30      * Rustacean mode is: `[<LEVEL>] (<filePath>/<functionName>:<lineNumber>) <message>`
31      */
32     RUSTACEAN,
33 
34     /** 
35      * Simple rustacean mode is: `[<LEVEL>] (<functionName>:<lineNumber>) <message>`
36      */
37     RUSTACEAN_SIMPLE
38 }
39 
40 /**
41  * Information obtained during compilation time (if any)
42  */
43 private struct GoggaCompInfo
44 {
45     /**
46      * compile time usage file
47      */
48     public string fullFilePath;
49 
50     /** 
51      * compile time usage file (relative)
52      */
53     public string file;
54 
55     /** 
56      * compile time usage line number
57      */
58     public ulong line;
59 
60     /** 
61      * compile time usage module
62      */
63     public string moduleName;
64 
65     /** 
66      * compile time usage function
67      */
68     public string functionName;
69 
70     /**
71      * compile time usage function (pretty)
72      */
73     public string prettyFunctionName;
74 
75     /** 
76      * Constructs the compilation information with the provided
77      * parameters
78      *
79      * Params:
80      *   __FILE_FULL_PATH__ = compile time usage file
81 	 *   __FILE__ = compile time usage file (relative)
82 	 *   __LINE__ = compile time usage line number
83 	 *   __MODULE__ = compile time usage module
84 	 *   __FUNCTION__ = compile time usage function
85 	 *   __PRETTY_FUNCTION__ = compile time usage function (pretty)
86      */
87     this(string fullFilePath, string file, ulong line, string moduleName, string functionName, string prettyFunctionName)
88     {
89         this.fullFilePath = fullFilePath;
90         this.file = file;
91         this.line = line;
92         this.moduleName = moduleName;
93         this.functionName = functionName;
94         this.prettyFunctionName = prettyFunctionName;
95     }
96 
97     /** 
98      * Flattens the known compile-time information into a string array
99      *
100      * Returns: a string[]
101      */
102     public string[] toArray()
103     {
104         return [fullFilePath, file, to!(string)(line), moduleName, functionName, prettyFunctionName];
105     }
106 }
107 
108 /** 
109  * A `GoggaMessage` which comprises
110  * of a `GoggaCompInfo` for context
111  */
112 private class GoggaMessage : BasicMessage
113 {
114     /** 
115      * The line information
116      */
117     private GoggaCompInfo ctx;
118 
119     /** 
120      * Sets the context
121      *
122      * Params:
123      *   ctx = the context
124      */
125     public void setContext(GoggaCompInfo ctx)
126     {
127         this.ctx = ctx;
128     }
129 
130     /** 
131      * Returns the context
132      *
133      * Returns: the context
134      */
135     public GoggaCompInfo getContext()
136     {
137         return this.ctx;
138     }
139 }
140 
141 /** 
142  * The Gogga transformer which
143  * applies stylization to
144  * incoming `GoggaMessage`(s)
145  */
146 private class GoggaTransform : Transform
147 {
148     /** 
149      * Mode to use for stylization
150      */
151     private GoggaMode mode;
152 
153     /** 
154      * Sets the stylization to
155      * use when transforming
156      * the message's text
157      *
158      * Params:
159      *   mode = the `GoggaMode`
160      */
161     public void setMode(GoggaMode mode)
162     {
163         this.mode = mode;
164     }
165 
166     /** 
167      * Transforms the incoming message
168      * to use the Gogga stylization. This
169      * will be a no-op if the incoming
170      * message is not a `GoggaMessage`.
171      *
172      * Params:
173      *   message = the message to transform
174      * Returns: the transformed message,
175      * else the same exact one
176      */
177     public Message transform(Message message)
178     {
179         // Only handle GoggaMessage(s)
180         GoggaMessage goggaMesg = cast(GoggaMessage)message;
181         if(goggaMesg is null)
182         {
183             return null;
184         }
185 
186         /* The generated output string */
187         string finalOutput;
188 
189 
190 
191         /* Extract the Level */
192         Level level = goggaMesg.getLevel();
193 
194         /* Extract the text */
195         string text = goggaMesg.getText();
196 
197         /* get the context data */
198         string[] context = goggaMesg.getContext().toArray();
199 
200 
201         /** 
202          * Simple mode is just: `[<LEVEL>] <message>`
203          */
204         if(this.mode == GoggaMode.SIMPLE)
205         {
206             finalOutput = cast(string)debugColor("["~to!(string)(level)~"] ", level);
207 
208             finalOutput ~= text~"\n";
209         }
210         /** 
211          * TwoKTwenty3 is: `[<file>] (<module>:<lineNumber>) <message>`
212          */
213         else if(this.mode == GoggaMode.TwoKTwenty3)
214         {
215             /* Module information (and status debugColoring) */
216             string moduleInfo = cast(string)debugColor("["~context[1]~"]", level);
217             
218             /* Function and line number info */
219             string funcInfo = cast(string)(colorSrc("("~context[4]~":"~context[2]~")"));
220 
221             finalOutput =  moduleInfo~" "~funcInfo~" "~text~"\n";
222         }
223         /** 
224          * Rustacean mode is: `[<LEVEL>] (<filePath>/<functionName>:<lineNumber>) <message>`
225          */
226         else if(this.mode == GoggaMode.RUSTACEAN)
227         {
228             finalOutput = cast(string)debugColor(to!(string)(level)~"\t", level);
229             finalOutput ~= cast(string)(colorSrc(context[1]~"/"~context[4]~":"~context[2]~"  "));
230             finalOutput ~= text~"\n";
231         }
232         /** 
233          * Simple rustacean mode is: `[<LEVEL>] (<functionName>:<lineNumber>) <message>`
234          */
235         else if(this.mode == GoggaMode.RUSTACEAN_SIMPLE)
236         {
237             finalOutput = cast(string)debugColor(to!(string)(level)~"\t", level);
238             finalOutput ~= cast(string)(colorSrc(context[4]~":"~context[2]~"  "));
239             finalOutput ~= text~"\n";
240         }
241 
242         goggaMesg.setText(finalOutput);
243         return message;
244     }
245 }
246 
247 /** 
248  * A `GoggaLogger`
249  */
250 public final class GoggaLogger : BasicLogger
251 {
252     /** 
253      * The Gogga text transformer
254      */
255     private GoggaTransform gogTrans;
256 
257     /** 
258 	 * Whether debug prints are enabled or not
259 	 */
260     private bool debugEnabled = false;
261 
262     /** 
263      * Constructs a new `GoggaLogger`
264      */
265     this()
266     {
267         super();
268         this.gogTrans = new GoggaTransform();
269         addTransform(this.gogTrans);
270     }
271 
272     /** 
273      * Sets the style of logging
274      * to use
275      *
276      * Params:
277      *   mode = the `GoggaMode`
278      */
279     public void mode(GoggaMode mode)
280     {
281         this.gogTrans.setMode(mode);
282     }
283 
284     /** 
285      * Performs the actual logging
286      * by packing up everything before
287      * sending it to the `log(Message)`
288      * method
289      *
290      * Params:
291      *   segments = the compile-time segments
292      *   info = the context
293      *   level = the log level to use
294      */
295     private void doLog(TextType...)(TextType segments, GoggaCompInfo info, Level level)
296     {
297         /* Create a new GoggaMessage */
298         GoggaMessage message = new GoggaMessage();
299 
300         /* Set context to the line information */
301         message.setContext(info);
302 
303         /* Set the level */
304         message.setLevel(level);
305 
306         /** 
307          * Grab all compile-time arguments and make them
308          * into an array, then join them together and
309          * set that text as the message's text
310          */
311         message.setText(join(flatten(segments), " "));
312 
313         /* Log this message */
314 		log(message);
315     }
316 
317     /** 
318 	 * Logs using the default context an arbitrary amount of arguments
319 	 * specifically setting the context's level to ERROR
320 	 *
321 	 * Params:
322 	 *   segments = the arbitrary argumnets (alias sequence)
323 	 *   __FILE_FULL_PATH__ = compile time usage file
324 	 *   __FILE__ = compile time usage file (relative)
325 	 *   __LINE__ = compile time usage line number
326 	 *   __MODULE__ = compile time usage module
327 	 *   __FUNCTION__ = compile time usage function
328 	 *   __PRETTY_FUNCTION__ = compile time usage function (pretty)
329 	 */
330 	public void error(TextType...)(TextType segments,
331 									string c1 = __FILE_FULL_PATH__,
332 									string c2 = __FILE__, ulong c3 = __LINE__,
333 									string c4 = __MODULE__, string c5 = __FUNCTION__,
334 									string c6 = __PRETTY_FUNCTION__)
335 	{
336 		doLog(segments, GoggaCompInfo(c1, c2, c3, c4, c5, c6), Level.ERROR);
337 	}
338 
339 	/** 
340 	 * Logs using the default context an arbitrary amount of arguments
341 	 * specifically setting the context's level to INFO
342 	 *
343 	 * Params:
344 	 *   segments = the arbitrary argumnets (alias sequence)
345 	 *   __FILE_FULL_PATH__ = compile time usage file
346 	 *   __FILE__ = compile time usage file (relative)
347 	 *   __LINE__ = compile time usage line number
348 	 *   __MODULE__ = compile time usage module
349 	 *   __FUNCTION__ = compile time usage function
350 	 *   __PRETTY_FUNCTION__ = compile time usage function (pretty)
351 	 */
352 	public void info(TextType...)(TextType segments,
353 									string c1 = __FILE_FULL_PATH__,
354 									string c2 = __FILE__, ulong c3 = __LINE__,
355 									string c4 = __MODULE__, string c5 = __FUNCTION__,
356 									string c6 = __PRETTY_FUNCTION__)
357 	{
358 		doLog(segments, GoggaCompInfo(c1, c2, c3, c4, c5, c6), Level.INFO);
359 	}
360 
361 	/** 
362 	 * Logs using the default context an arbitrary amount of arguments
363 	 * specifically setting the context's level to WARN
364 	 *
365 	 * Params:
366 	 *   segments = the arbitrary argumnets (alias sequence)
367 	 *   __FILE_FULL_PATH__ = compile time usage file
368 	 *   __FILE__ = compile time usage file (relative)
369 	 *   __LINE__ = compile time usage line number
370 	 *   __MODULE__ = compile time usage module
371 	 *   __FUNCTION__ = compile time usage function
372 	 *   __PRETTY_FUNCTION__ = compile time usage function (pretty)
373 	 */
374 	public void warn(TextType...)(TextType segments,
375 									string c1 = __FILE_FULL_PATH__,
376 									string c2 = __FILE__, ulong c3 = __LINE__,
377 									string c4 = __MODULE__, string c5 = __FUNCTION__,
378 									string c6 = __PRETTY_FUNCTION__)
379 	{
380         doLog(segments, GoggaCompInfo(c1, c2, c3, c4, c5, c6), Level.WARN);
381 	}
382 
383 	/** 
384 	 * Logs using the default context an arbitrary amount of arguments
385 	 * specifically setting the context's level to DEBUG and will
386 	 * only print if debugging is enabled
387 	 *
388 	 * Params:
389 	 *   segments = the arbitrary argumnets (alias sequence)
390 	 *   __FILE_FULL_PATH__ = compile time usage file
391 	 *   __FILE__ = compile time usage file (relative)
392 	 *   __LINE__ = compile time usage line number
393 	 *   __MODULE__ = compile time usage module
394 	 *   __FUNCTION__ = compile time usage function
395 	 *   __PRETTY_FUNCTION__ = compile time usage function (pretty)
396 	 */
397 	public void debug_(TextType...)(TextType segments,
398 									string c1 = __FILE_FULL_PATH__,
399 									string c2 = __FILE__, ulong c3 = __LINE__,
400 									string c4 = __MODULE__, string c5 = __FUNCTION__,
401 									string c6 = __PRETTY_FUNCTION__)
402 	{		
403         doLog(segments, GoggaCompInfo(c1, c2, c3, c4, c5, c6), Level.DEBUG);
404 	}
405 
406     /** 
407 	 * Alias for debug_
408 	 */
409 	public alias dbg = debug_;
410 }
411 
412 /** 
413  * Colorise the text provided accoridng to the level and then
414  * reset the colors at the end
415  *
416  * Params:
417  *   text = the text to colorise
418  *   level = the color to use
419  * Returns: the byte sequence of characters and controls
420  */
421 private byte[] debugColor(string text, Level level)
422 {
423     /* The generated message */
424     byte[] messageBytes;
425 
426     /* If INFO, set green */
427     if(level == Level.INFO)
428     {
429         messageBytes = cast(byte[])[27, '[','3','2','m'];
430     }
431     /* If WARN, set yellow */
432     else if(level == Level.WARN)
433     {
434         messageBytes = cast(byte[])[27, '[','3','1', ';', '9', '3', 'm'];
435     }
436     /* If ERROR, set red */
437     else if(level == Level.ERROR)
438     {
439         messageBytes = cast(byte[])[27, '[','3','1','m'];
440     }
441     /* If DEBUG, set pink */
442     else
443     {
444         messageBytes = cast(byte[])[27, '[','3','5','m'];
445     }
446 
447     /* Add the message */
448     messageBytes ~= cast(byte[])text;
449 
450     /* Switch back debugColor */
451     messageBytes ~= cast(byte[])[27, '[', '3', '9', 'm'];
452 
453     /* Reset coloring */
454     messageBytes ~= [27, '[', 'm'];
455 
456     return messageBytes;
457 }
458 
459 /** 
460  * Colors the provided text in a gray fashion and then
461  * resets back to normal
462  *
463  * Params:
464  *   text = the text to gray color
465  * Returns: the byte sequence of characters and controls
466  */
467 private byte[] colorSrc(string text)
468 {
469     /* The generated message */
470     byte[] messageBytes;
471 
472     /* Reset coloring */
473     messageBytes ~= [27, '[', 'm'];
474 
475     /* Color gray */
476     messageBytes ~= [27, '[', '3', '9', ';', '2', 'm'];
477 
478     /* Append the message */
479     messageBytes ~= text;
480 
481     /* Reset coloring */
482     messageBytes ~= [27, '[', 'm'];
483 
484     return messageBytes;
485 }
486 
487 version(unittest)
488 {
489 	import std.stdio : writeln, stdout;
490 }
491 
492 unittest
493 {
494     GoggaLogger gLogger = new GoggaLogger();
495     gLogger.addHandler(new FileHandler(stdout));
496     gLogger.setLevel(Level.INFO);
497 
498 	// Test the normal modes
499     gLogger.info("This is an info message");
500     gLogger.warn("This is a warning message");
501     gLogger.error("This is an error message");
502 
503 	// We shouldn't see anything as debug is off
504 	gLogger.dbg("This is a debug which is hidden", 1);
505 
506 	// Now enable debugging and you should see it
507 	gLogger.setLevel(Level.DEBUG);
508 	gLogger.dbg("This is a VISIBLE debug", true);
509 
510     // Make space between unit tests
511 	writeln();
512 }
513 
514 unittest
515 {
516     GoggaLogger gLogger = new GoggaLogger();
517     gLogger.addHandler(new FileHandler(stdout));
518     gLogger.setLevel(Level.INFO);
519 
520 	gLogger.mode(GoggaMode.TwoKTwenty3);
521 
522     // Test the normal modes
523     gLogger.info("This is an info message");
524     gLogger.warn("This is a warning message");
525     gLogger.error("This is an error message");
526 
527 	// We shouldn't see anything as debug is off
528 	gLogger.dbg("This is a debug which is hidden", 1);
529 
530 	// Now enable debugging and you should see it
531     gLogger.setLevel(Level.DEBUG);
532 	gLogger.dbg("This is a VISIBLE debug", true);
533 
534     // Make space between unit tests
535 	writeln();
536 }
537 
538 unittest
539 {
540     GoggaLogger gLogger = new GoggaLogger();
541     gLogger.addHandler(new FileHandler(stdout));
542     gLogger.setLevel(Level.INFO);
543 
544 	gLogger.mode(GoggaMode.SIMPLE);
545 
546     // Test the normal modes
547     gLogger.info("This is an info message");
548     gLogger.warn("This is a warning message");
549     gLogger.error("This is an error message");
550 
551 	// We shouldn't see anything as debug is off
552 	gLogger.dbg("This is a debug which is hidden", 1);
553 
554 	// Now enable debugging and you should see it
555     gLogger.setLevel(Level.DEBUG);
556 	gLogger.dbg("This is a VISIBLE debug", true);
557 
558     // Make space between unit tests
559 	writeln();
560 }
561 
562 unittest
563 {
564     GoggaLogger gLogger = new GoggaLogger();
565     gLogger.addHandler(new FileHandler(stdout));
566     gLogger.setLevel(Level.INFO);
567 
568 	gLogger.mode(GoggaMode.RUSTACEAN);
569 
570     // Test the normal modes
571     gLogger.info("This is an info message");
572     gLogger.warn("This is a warning message");
573     gLogger.error("This is an error message");
574 
575 	// We shouldn't see anything as debug is off
576 	gLogger.dbg("This is a debug which is hidden", 1);
577 
578 	// Now enable debugging and you should see it
579     gLogger.setLevel(Level.DEBUG);
580 	gLogger.dbg("This is a VISIBLE debug", true);
581 
582     // Make space between unit tests
583 	writeln();
584 }
585 
586 unittest
587 {
588     GoggaLogger gLogger = new GoggaLogger();
589     gLogger.addHandler(new FileHandler(stdout));
590     gLogger.setLevel(Level.INFO);
591 
592 	gLogger.mode(GoggaMode.RUSTACEAN_SIMPLE);
593 
594     // Test the normal modes
595     gLogger.info("This is an info message");
596     gLogger.warn("This is a warning message");
597     gLogger.error("This is an error message");
598 
599 	// We shouldn't see anything as debug is off
600 	gLogger.dbg("This is a debug which is hidden", 1);
601 
602 	// Now enable debugging and you should see it
603     gLogger.setLevel(Level.DEBUG);
604 	gLogger.dbg("This is a VISIBLE debug", true);
605 
606     // Make space between unit tests
607 	writeln();
608 }