1 /++ 2 Functionality generic enough to be used in several places. 3 +/ 4 module lu.common; 5 6 private: 7 8 import std.typecons : Flag, No, Yes; 9 10 public: 11 12 @safe: 13 14 15 // Next 16 /++ 17 Enum of flags carrying the meaning of "what to do next". 18 +/ 19 enum Next 20 { 21 /++ 22 Keep doing whatever is being done, alternatively continue on to the next step. 23 +/ 24 continue_, 25 26 /++ 27 Halt what's being done and give it another attempt. 28 +/ 29 retry, 30 31 /++ 32 Exit or return with a positive return value. 33 +/ 34 returnSuccess, 35 36 /++ 37 Exit or abort with a negative return value. 38 +/ 39 returnFailure, 40 41 /++ 42 Fatally abort. 43 +/ 44 crash, 45 } 46 47 48 // ReturnValueException 49 /++ 50 Exception, to be thrown when an executed command returns an error value. 51 52 It is a normal [object.Exception|Exception] but with an attached command 53 and return value. 54 +/ 55 final class ReturnValueException : Exception 56 { 57 /// The command run. 58 string command; 59 60 /// The value returned. 61 int retval; 62 63 /// Create a new [ReturnValueException], without attaching anything. 64 this(const string message, 65 const string file = __FILE__, 66 const size_t line = __LINE__, 67 Throwable nextInChain = null) pure nothrow @nogc @safe 68 { 69 super(message, file, line, nextInChain); 70 } 71 72 /// Create a new [ReturnValueException], attaching a command. 73 this(const string message, 74 const string command, 75 const string file = __FILE__, 76 const size_t line = __LINE__, 77 Throwable nextInChain = null) pure nothrow @nogc @safe 78 { 79 this.command = command; 80 super(message, file, line, nextInChain); 81 } 82 83 /// Create a new [ReturnValueException], attaching a command and a returned value. 84 this(const string message, 85 const string command, 86 const int retval, 87 const string file = __FILE__, 88 const size_t line = __LINE__, 89 Throwable nextInChain = null) pure nothrow @nogc @safe 90 { 91 this.command = command; 92 this.retval = retval; 93 super(message, file, line, nextInChain); 94 } 95 } 96 97 98 // FileExistsException 99 /++ 100 Exception, to be thrown when attempting to create a file or directory and 101 finding that one already exists with the same name. 102 103 It is a normal [object.Exception|Exception] but with an attached filename string. 104 +/ 105 final class FileExistsException : Exception 106 { 107 /// The name of the file. 108 string filename; 109 110 /// Create a new [FileExistsException], without attaching a filename. 111 this(const string message, 112 const string file = __FILE__, 113 const size_t line = __LINE__, 114 Throwable nextInChain = null) pure nothrow @nogc @safe 115 { 116 super(message, file, line, nextInChain); 117 } 118 119 /// Create a new [FileExistsException], attaching a filename. 120 this(const string message, 121 const string filename, 122 const string file = __FILE__, 123 const size_t line = __LINE__, 124 Throwable nextInChain = null) pure nothrow @nogc @safe 125 { 126 this.filename = filename; 127 super(message, file, line, nextInChain); 128 } 129 } 130 131 132 // FileTypeMismatchException 133 /++ 134 Exception, to be thrown when attempting to access a file or directory and 135 finding that something with the that name exists, but is of an unexpected type. 136 137 It is a normal [object.Exception|Exception] but with an embedded filename 138 string, and an uint representing the existing file's type (file, directory, 139 symlink, ...). 140 +/ 141 final class FileTypeMismatchException : Exception 142 { 143 /// The filename of the non-FIFO. 144 string filename; 145 146 /// File attributes. 147 ushort attrs; 148 149 /// Create a new [FileTypeMismatchException], without embedding a filename. 150 this(const string message, 151 const string file = __FILE__, 152 const size_t line = __LINE__, 153 Throwable nextInChain = null) pure nothrow @nogc @safe 154 { 155 super(message, file, line, nextInChain); 156 } 157 158 /// Create a new [FileTypeMismatchException], embedding a filename. 159 this(const string message, 160 const string filename, 161 const ushort attrs, 162 const string file = __FILE__, 163 const size_t line = __LINE__, 164 Throwable nextInChain = null) pure nothrow @nogc @safe 165 { 166 this.filename = filename; 167 this.attrs = attrs; 168 super(message, file, line, nextInChain); 169 } 170 } 171 172 173 // sharedDomains 174 /++ 175 Calculates how many dot-separated suffixes two strings share. 176 177 This is useful to see to what extent two addresses are similar. 178 179 Example: 180 --- 181 int numDomains = sharedDomains("irc.freenode.net", "leguin.freenode.net"); 182 assert(numDomains == 2); // freenode.net 183 184 int numDomains2 = sharedDomains("Portlane2.EU.GameSurge.net", "services.gamesurge.net", No.caseSensitive); 185 assert(numDomains2 == 2); // gamesurge.net 186 --- 187 188 Params: 189 one = First domain string. 190 other = Second domain string. 191 caseSensitive = Whether or not comparison should be done on a 192 case-sensitive basis. 193 194 Returns: 195 The number of domains the two strings share. 196 197 TODO: 198 Support partial globs. 199 +/ 200 auto sharedDomains( 201 const string one, 202 const string other, 203 const Flag!"caseSensitive" caseSensitive = Yes.caseSensitive) pure @nogc nothrow 204 { 205 if (!one.length || !other.length) return 0; 206 207 static uint numDomains(const char[] one, const char[] other, const bool caseSensitive) 208 { 209 uint dots; 210 double doubleDots; 211 212 // If both strings are the same, act as if there's an extra dot. 213 // That gives (.)rizon.net and (.)rizon.net two suffixes. 214 215 if (caseSensitive) 216 { 217 if (one == other) ++dots; 218 } 219 else 220 { 221 import std.algorithm.comparison : equal; 222 import std.uni : asLowerCase; 223 if (one.asLowerCase.equal(other.asLowerCase)) ++dots; 224 } 225 226 foreach (immutable i; 0..one.length) 227 { 228 immutable c1 = one[$-i-1]; 229 230 if (i == other.length) 231 { 232 if (c1 == '.') ++dots; 233 break; 234 } 235 236 immutable c2 = other[$-i-1]; 237 238 if (caseSensitive) 239 { 240 if (c1 != c2) break; 241 } 242 else 243 { 244 import std.ascii : toLower; 245 if (c1.toLower != c2.toLower) break; 246 } 247 248 if (c1 == '.') 249 { 250 if (!doubleDots) 251 { 252 ++dots; 253 doubleDots = true; 254 } 255 } 256 else 257 { 258 doubleDots = false; 259 } 260 } 261 262 return dots; 263 } 264 265 return (one.length > other.length) ? 266 numDomains(one, other, cast(bool)caseSensitive) : 267 numDomains(other, one, cast(bool)caseSensitive); 268 } 269 270 /// 271 unittest 272 { 273 import std.conv : text; 274 275 immutable n1 = sharedDomains("irc.freenode.net", "help.freenode.net"); 276 assert((n1 == 2), n1.text); 277 278 immutable n2 = sharedDomains("irc.rizon.net", "services.rizon.net"); 279 assert((n2 == 2), n2.text); 280 281 immutable n3 = sharedDomains("www.google.com", "www.yahoo.com"); 282 assert((n3 == 1), n3.text); 283 284 immutable n4 = sharedDomains("www.google.se", "www.google.co.uk"); 285 assert((n4 == 0), n4.text); 286 287 immutable n5 = sharedDomains("", string.init); 288 assert((n5 == 0), n5.text); 289 290 immutable n6 = sharedDomains("irc.rizon.net", "rizon.net"); 291 assert((n6 == 2), n6.text); 292 293 immutable n7 = sharedDomains("rizon.net", "rizon.net"); 294 assert((n7 == 2), n7.text); 295 296 immutable n8 = sharedDomains("net", "net"); 297 assert((n8 == 1), n8.text); 298 299 immutable n9 = sharedDomains("forum.dlang.org", "..."); 300 assert((n9 == 0), n8.text); 301 302 immutable n10 = sharedDomains("blahrizon.net", "rizon.net"); 303 assert((n10 == 1), n10.text); 304 305 immutable n11 = sharedDomains("rizon.net", "blahrizon.net"); 306 assert((n11 == 1), n11.text); 307 308 immutable n12 = sharedDomains("rizon.net", "irc.rizon.net"); 309 assert((n12 == 2), n12.text); 310 311 immutable n13 = sharedDomains("irc.gamesurge.net", "Stuff.GameSurge.net", No.caseSensitive); 312 assert((n13 == 2), n13.text); 313 314 immutable n14 = sharedDomains("irc.freenode.net", "irc.FREENODE.net", No.caseSensitive); 315 assert((n14 == 3), n14.text); 316 317 immutable n15 = sharedDomains("irc.SpotChat.org", "irc.FREENODE.net", No.caseSensitive); 318 assert((n15 == 0), n15.text); 319 }