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