1 /*
2  * Copyright (c) 2017-2018 sel-project
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in all
12  * copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20  * SOFTWARE.
21  *
22  */
23 /**
24  * Copyright: 2017-2018 sel-project
25  * License: MIT
26  * Authors: Kripth
27  * Source: $(HTTP github.com/sel-project/selery/source/selery/log.d, selery/log.d)
28  */
29 module selery.log;
30 
31 import std.algorithm : canFind;
32 import std.conv : to;
33 import std.string : indexOf;
34 import std.traits : EnumMembers;
35 
36 import arsd.terminal : Terminal, Color, bright = Bright;
37 
38 import selery.lang : LanguageManager, Translation, Translatable;
39 
40 /**
41  * Formatting codes for Minecraft and the system's console.
42  */
43 enum Format : string {
44 	
45 	black = "§0",
46 	darkBlue = "§1",
47 	darkGreen = "§2",
48 	darkAqua = "§3",
49 	darkRed = "§4",
50 	darkPurple = "§5",
51 	gold = "§6",
52 	gray = "§7",
53 	darkGray = "§8",
54 	blue = "§9",
55 	green = "§a",
56 	aqua = "§b",
57 	red = "§c",
58 	lightPurple = "§d",
59 	yellow = "§e",
60 	white = "§f",
61 	
62 	obfuscated = "§k",
63 	bold = "§l",
64 	strikethrough = "§m",
65 	underlined = "§n",
66 	italic = "§o",
67 	
68 	reset = "§r"
69 	
70 }
71 
72 /**
73  * Indicates a generic message. It can be a formatting code, a raw string
74  * or a translatable content.
75  */
76 struct Message {
77 
78 	enum : ubyte {
79 
80 		FORMAT = 1,
81 		TEXT = 2,
82 		TRANSLATION = 3
83 
84 	}
85 
86 	ubyte type;
87 
88 	union {
89 
90 		Format format;
91 		string text;
92 		Translation translation;
93 
94 	}
95 
96 	this(Format format) {
97 		this.type = FORMAT;
98 		this.format = format;
99 	}
100 
101 	this(string text) {
102 		this.type = TEXT;
103 		this.text = text;
104 	}
105 
106 	this(Translation translation) {
107 		this.type = TRANSLATION;
108 		this.translation = translation;
109 	}
110 
111 	/**
112 	 * Converts data into an array of messages.
113 	 */
114 	static Message[] convert(E...)(E args) {
115 		Message[] messages;
116 		foreach(arg ; args) {
117 			alias T  = typeof(arg);
118 			static if(is(T == Message) || is(T == Message[])) {
119 				messages ~= arg;
120 			} else static if(is(T == Format) || is(T == Translation)) {
121 				messages ~= Message(arg);
122 			} else static if(is(T == Translatable)) {
123 				messages ~= Message(Translation(arg));
124 			} else {
125 				messages ~= Message(to!string(arg));
126 			}
127 		}
128 		return messages;
129 	}
130 
131 }
132 
133 class Logger {
134 
135 	public Terminal* terminal;
136 	private const LanguageManager lang;
137 
138 	public this(Terminal* terminal, inout LanguageManager lang) {
139 		this.terminal = terminal;
140 		this.lang = cast(const)lang;
141 	}
142 	
143 	public void log(E...)(E args) {
144 		this.logMessage(Message.convert(args));
145 	}
146 	
147 	public void logWarning(E...)(E args) {
148 		this.log(Format.yellow, args);
149 	}
150 	
151 	public void logError(E...)(E args) {
152 		this.log(Format.red, args);
153 	}
154 
155 	public void logMessage(Message[] messages) {
156 		this.logImpl(messages);
157 	}
158 
159 	protected void logImpl(Message[] messages) {
160 		foreach(message ; messages) {
161 			final switch(message.type) {
162 				case Message.FORMAT:
163 					this.applyFormat(message.format);
164 					break;
165 				case Message.TEXT:
166 					this.writeText(message.text);
167 					break;
168 				case Message.TRANSLATION:
169 					this.writeText(this.lang.translate(message.translation.translatable.default_, message.translation.parameters));
170 					break;
171 			}
172 		}
173 		// add new line, reset formatting and print unflushed data
174 		this.terminal.writeln();
175 		this.terminal.flush();
176 		this.terminal.reset();
177 	}
178 
179 	private void writeText(string text) {
180 		immutable p = text.indexOf("§");
181 		if(p != -1 && p < text.length - 2 && "0123456789abcdefklmnor".canFind(text[p+2])) {
182 			this.terminal.write(text[0..p]);
183 			this.applyFormat(this.getFormat(text[p+2]));
184 			this.writeText(text[p+3..$]);
185 		} else {
186 			this.terminal.write(text);
187 		}
188 	}
189 
190 	private Format getFormat(char c) {
191 		final switch(c) {
192 			foreach(immutable member ; __traits(allMembers, Format)) {
193 				case mixin("Format." ~ member)[$-1]: return mixin("Format." ~ member);
194 			}
195 		}
196 	}
197 
198 	private void applyFormat(Format format) {
199 		version(Windows) this.terminal.flush(); // print with current format, then update
200 		final switch(format) {
201 			case Format.black:
202 				this.terminal.color(Color.black, Color.DEFAULT);
203 				break;
204 			case Format.darkBlue:
205 				this.terminal.color(Color.blue, Color.DEFAULT);
206 				break;
207 			case Format.darkGreen:
208 				this.terminal.color(Color.green, Color.DEFAULT);
209 				break;
210 			case Format.darkAqua:
211 				this.terminal.color(Color.cyan, Color.DEFAULT);
212 				break;
213 			case Format.darkRed:
214 				this.terminal.color(Color.red, Color.DEFAULT);
215 				break;
216 			case Format.darkPurple:
217 				this.terminal.color(Color.magenta, Color.DEFAULT);
218 				break;
219 			case Format.gold:
220 				this.terminal.color(Color.yellow, Color.DEFAULT);
221 				break;
222 			case Format.gray:
223 				this.terminal.color(Color.white, Color.DEFAULT);
224 				break;
225 			case Format.darkGray:
226 				this.terminal.color(Color.black | bright, Color.DEFAULT);
227 				break;
228 			case Format.blue:
229 				this.terminal.color(Color.blue | bright, Color.DEFAULT);
230 				break;
231 			case Format.green:
232 				this.terminal.color(Color.green | bright, Color.DEFAULT);
233 				break;
234 			case Format.aqua:
235 				this.terminal.color(Color.cyan | bright, Color.DEFAULT);
236 				break;
237 			case Format.red:
238 				this.terminal.color(Color.red | bright, Color.DEFAULT);
239 				break;
240 			case Format.lightPurple:
241 				this.terminal.color(Color.magenta | bright, Color.DEFAULT);
242 				break;
243 			case Format.yellow:
244 				this.terminal.color(Color.yellow | bright, Color.DEFAULT);
245 				break;
246 			case Format.white:
247 				this.terminal.color(Color.white | bright, Color.DEFAULT);
248 				break;
249 			case Format.underlined:
250 				this.terminal.underline(true);
251 				break;
252 			case Format.reset:
253 				this.terminal.reset();
254 				break;
255 			case Format.obfuscated:
256 			case Format.bold:
257 			case Format.strikethrough:
258 			case Format.italic:
259 				// not supported
260 				break;
261 		}
262 	}
263 	
264 }
265 
266 deprecated("Use Logger instead") void setLogger(void delegate(string, int, int) func) {}
267 
268 deprecated("Use Logger.log instead") void log(E...)(E args) {}
269 
270 deprecated("Use Logger.logWarning instead") void warning_log(E...)(E args) {}
271 
272 deprecated("Use Logger.logError instead") void error_log(E...)(E args) {}
273 
274 deprecated("Use Logger.log instead") void raw_log(E...)(E args) {}