1 /++ 2 + Module for handling the various CASEMAPPING methods used by IRC networks. 3 +/ 4 module virc.casemapping; 5 6 import std.algorithm : map; 7 import std.ascii : isAlpha, toLower, toUpper; 8 import std.range.primitives : isInputRange; 9 10 /++ 11 + 12 +/ 13 enum CaseMapping { 14 unknown = "", 15 rfc1459 = "rfc1459", 16 strictRFC1459 = "strict-rfc1459", 17 rfc3454 = "rfc3454", 18 ascii = "ascii" 19 } 20 /++ 21 + 22 +/ 23 auto toIRCUpper(CaseMapping caseMapping = CaseMapping.rfc1459)(string input) { 24 import std.utf : byCodeUnit; 25 static char upper(char input) { 26 if (input.isAlpha) { 27 return input.toUpper(); 28 } 29 static if (caseMapping != CaseMapping.ascii) { 30 switch (input) { 31 case '{': return '['; 32 case '}': return ']'; 33 case '|': return '\\'; 34 static if (caseMapping == CaseMapping.rfc1459) { 35 case '~': return '^'; 36 } 37 default: break; 38 } 39 } 40 return input; 41 } 42 return input.byCodeUnit.map!upper; 43 } 44 /// 45 @safe pure nothrow unittest { 46 import std.algorithm : equal; 47 assert("test".toIRCUpper!(CaseMapping.rfc1459).equal("TEST"d)); 48 assert("test".toIRCUpper!(CaseMapping.strictRFC1459).equal("TEST"d)); 49 assert("test".toIRCUpper!(CaseMapping.ascii).equal("TEST"d)); 50 assert("test{}|~".toIRCUpper!(CaseMapping.rfc1459).equal("TEST[]\\^"d)); 51 assert("test{}|~".toIRCUpper!(CaseMapping.strictRFC1459).equal("TEST[]\\~"d)); 52 assert("test{}|~".toIRCUpper!(CaseMapping.ascii).equal("TEST{}|~"d)); 53 } 54 /++ 55 + 56 +/ 57 auto toIRCLower(CaseMapping caseMapping = CaseMapping.rfc1459)(string input) { 58 import std.utf : byCodeUnit; 59 static char lower(char input) { 60 if (input.isAlpha) { 61 return input.toLower(); 62 } 63 static if (caseMapping != CaseMapping.ascii) { 64 switch (input) { 65 case '[': return '{'; 66 case ']': return '}'; 67 case '\\': return '|'; 68 static if (caseMapping == CaseMapping.rfc1459) { 69 case '^': return '~'; 70 } 71 default: break; 72 } 73 } 74 return input; 75 } 76 return input.byCodeUnit.map!lower; 77 } 78 /// 79 @safe pure nothrow unittest { 80 import std.algorithm : equal; 81 assert("TEST".toIRCLower!(CaseMapping.rfc1459).equal("test"d)); 82 assert("TEST".toIRCLower!(CaseMapping.strictRFC1459).equal("test"d)); 83 assert("TEST".toIRCLower!(CaseMapping.ascii).equal("test"d)); 84 assert("TEST[]\\^".toIRCLower!(CaseMapping.rfc1459).equal("test{}|~"d)); 85 assert("TEST[]\\~".toIRCLower!(CaseMapping.strictRFC1459).equal("test{}|~"d)); 86 assert("TEST{}|~".toIRCLower!(CaseMapping.ascii).equal("test{}|~"d)); 87 } 88 /++ 89 + 90 +/ 91 auto ircCompare(CaseMapping caseMapping = CaseMapping.rfc1459)(string input1, string input2) { 92 import std.range : zip; 93 foreach (characterDiff; zip(input1.toIRCLower!caseMapping, input2.toIRCLower!caseMapping).map!(x => x[0] - x[1])) { 94 if (characterDiff > 0) { 95 return 1; 96 } else if (characterDiff < 0) { 97 return -1; 98 } 99 } 100 return 0; 101 } 102 /// 103 @safe pure unittest { 104 assert(ircCompare!(CaseMapping.rfc1459)("TEST", "TEST") == 0); 105 assert(ircCompare!(CaseMapping.rfc1459)("TEST", "test") == 0); 106 assert(ircCompare!(CaseMapping.rfc1459)("[", "{") == 0); 107 assert(ircCompare!(CaseMapping.rfc1459)("]", "}") == 0); 108 assert(ircCompare!(CaseMapping.rfc1459)("\\", "|") == 0); 109 assert(ircCompare!(CaseMapping.rfc1459)("^", "~") == 0); 110 assert(ircCompare!(CaseMapping.strictRFC1459)("^", "~") != 0); 111 assert(ircCompare!(CaseMapping.strictRFC1459)("~", "^") != 0); 112 }