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 }