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) {}