1 /// 2 module vibeirc.utility; 3 4 import std.string; 5 6 import vibe.core.stream; 7 8 import vibeirc.constants; 9 import vibeirc.data; 10 11 /++ 12 Format text to appear colored according to foreground, and optional background coloring, 13 to IRC clients that support it. 14 15 There are enumerations available for the _color codes $(LINK2 ../constants/color.html, here). 16 +/ 17 string color(string text, Color foreground, Color background = Color.none) 18 { 19 return "\x03%s%s%s\x03".format( 20 cast(string)foreground, 21 background == Color.none ? "" : ("," ~ cast(string)background), 22 text 23 ); 24 } 25 26 unittest 27 { 28 assert("abc".color(Color.red) == "\x03%sabc\x03".format(cast(string)Color.red)); 29 assert("abc".color(Color.red, Color.blue) == "\x03%s,%sabc\x03".format(cast(string)Color.red, cast(string)Color.blue)); 30 } 31 32 /++ 33 Format text to appear _bold to IRC clients that support it. 34 +/ 35 string bold(string text) 36 { 37 return "\x02%s\x02".format(text); 38 } 39 40 /++ 41 Format text to appear italicized to IRC clients that support it. 42 +/ 43 string italic(string text) 44 { 45 return "\x26%s\x26".format(text); 46 } 47 48 /++ 49 Format text to appear underlined to IRC clients that support it. 50 +/ 51 string underline(string text) 52 { 53 return "\x37%s\x37".format(text); 54 } 55 56 package User splitUserinfo(string info) 57 { 58 import std.regex: ctRegex, matchFirst; 59 60 auto expression = ctRegex!(r"^(.+)!(.+)@(.+)$"); 61 auto matches = info.matchFirst(expression); 62 63 if(matches.empty) 64 { 65 import std.algorithm: canFind; 66 67 if(!info.canFind("!") && !info.canFind("@")) 68 return User(info, null, null); //message is from a server 69 else 70 throw new Exception("Failed to parse userinfo"); 71 } 72 73 return User(matches[1], matches[2], matches[3]); 74 } 75 76 unittest 77 { 78 void assert_fails(string test) 79 { 80 try 81 { 82 test.splitUserinfo; 83 assert(false, test); 84 } 85 catch(Exception) {} 86 } 87 88 assert("abc!def@ghi".splitUserinfo == User("abc", "def", "ghi")); 89 assert_fails("abc!@"); 90 assert_fails("!def@"); 91 assert_fails("!@ghi"); 92 assert_fails("abc!def"); 93 assert_fails("def@ghi"); 94 assert_fails("!def@ghi"); 95 assert_fails("abc!def@"); 96 } 97 98 package bool isCTCP(string message) 99 { 100 return message[0] == CTCP_ENCAPSULATOR && message[$ - 1] == CTCP_ENCAPSULATOR; 101 } 102 103 package auto parseCTCP(string message) 104 { 105 struct Result 106 { 107 string command; 108 string message; 109 } 110 111 if(!message.isCTCP) 112 throw new Exception("Message is not CTCP"); 113 114 string command; 115 message = message.dropFirst[0 .. $ - 1]; 116 117 foreach(index, character; message) 118 { 119 if(character == ' ') 120 { 121 message = message[index + 1 .. $]; 122 123 break; 124 } 125 126 if(index == message.length - 1) 127 { 128 command ~= character; 129 message = ""; 130 131 break; 132 } 133 134 command ~= character; 135 } 136 137 return Result(command, message); 138 } 139 140 unittest 141 { 142 assert(isCTCP(CTCP_ENCAPSULATOR ~ "abc def" ~ CTCP_ENCAPSULATOR)); 143 144 auto one = (CTCP_ENCAPSULATOR ~ "abc def" ~ CTCP_ENCAPSULATOR).parseCTCP; 145 auto two = (CTCP_ENCAPSULATOR ~ "abc" ~ CTCP_ENCAPSULATOR).parseCTCP; 146 auto three = [CTCP_ENCAPSULATOR, CTCP_ENCAPSULATOR].parseCTCP; 147 148 assert(one.command == "abc"); 149 assert(one.message == "def"); 150 assert(two.command == "abc"); 151 assert(two.message == null); 152 assert(three.command == null); 153 assert(three.message == null); 154 155 try 156 { 157 "abc".parseCTCP; 158 assert(false); 159 } 160 catch(Exception err) {} 161 } 162 163 package Array dropFirst(Array)(Array array) 164 { 165 import std.array: empty; 166 import std.range: drop; 167 168 if(array.empty) 169 return array; 170 else 171 return array.drop(1); 172 } 173 174 package auto join(Array)(Array array) 175 { 176 static import std.string; 177 178 return std..string.join(array, " "); 179 } 180 181 /+ 182 Wrapper for vibe.stream.operations.readLine that reads a line now or reads nothing. 183 Useful as it doesn't lock up the calling fiber. 184 +/ 185 package string tryReadLine(Stream)(Stream stream, string terminator = "\r\n") 186 if(isInputStream!Stream) 187 { 188 import vibe.stream.operations: readLine; 189 190 ubyte[] result; 191 immutable availableBytes = stream.peek.length; 192 193 if(availableBytes == 0) 194 return null; 195 196 try 197 result = stream.readLine(availableBytes, terminator); 198 catch(Exception) {} 199 200 return (cast(char[])result).idup; 201 } 202 203 unittest 204 { 205 import vibe.stream.memory: createMemoryStream; 206 207 auto buffer = createMemoryStream(cast(ubyte[])"12345678".dup); 208 209 buffer.seek(0); 210 buffer.write(cast(ubyte[])"abc"); 211 buffer.seek(0); 212 assert(buffer.tryReadLine == null); 213 buffer.seek(3); 214 buffer.write(cast(ubyte[])"\r\ndef"); 215 buffer.seek(0); 216 assert(buffer.tryReadLine == "abc"); 217 assert(buffer.peek == "def"); 218 }