1 module nxt.capitalization; 2 3 import std.traits : isSomeString; 4 5 /** Check if `s` starts with a capital letter followed by a lower letter. 6 */ 7 bool isCapitalizedSimple(S)(S s) 8 if (isSomeString!S) 9 { 10 import std.range.primitives : empty, front, popFront; 11 import std.uni : isUpper, isLower; 12 13 if (s.empty) { return false; } 14 15 const firstUpper = s.front.isUpper; 16 if (!firstUpper) return false; 17 s.popFront(); 18 19 if (s.empty) { return false; } 20 return s.front.isLower; 21 } 22 23 @safe pure unittest 24 { 25 assert(!`A`.isCapitalizedSimple); 26 assert(!`a`.isCapitalizedSimple); 27 assert(!`alpha`.isCapitalizedSimple); 28 assert(!`ALPHA`.isCapitalizedSimple); 29 assert(!`aThing`.isCapitalizedSimple); 30 assert(`Alpha`.isCapitalizedSimple); 31 assert(`Jack London`.isCapitalizedSimple); 32 } 33 34 /** Check if `s` starts with a capital letter followed by a lower letter. 35 */ 36 bool isCapitalizedASCIIEasy(S)(S s) 37 if (isSomeString!S) 38 { 39 import std.ascii : isUpper, isLower; 40 return (s.length >= 2 && 41 s[0].isUpper && 42 s[1].isLower); 43 } 44 45 @safe pure unittest 46 { 47 assert(!`A`.isCapitalizedASCIIEasy); 48 assert(!`a`.isCapitalizedASCIIEasy); 49 assert(!`alpha`.isCapitalizedASCIIEasy); 50 assert(!`ALPHA`.isCapitalizedASCIIEasy); 51 assert(!`aThing`.isCapitalizedASCIIEasy); 52 assert(`Alpha`.isCapitalizedASCIIEasy); 53 assert(`Jack London`.isCapitalizedASCIIEasy); 54 } 55 56 import std.uni : isLower; 57 58 /** Check if `s` lowercased, that is only contains lower-case characters. 59 */ 60 bool isLowercased(S, alias pred = isLower)(S s) 61 if (isSomeString!S) 62 { 63 import std.algorithm.searching : all; 64 import std.traits : isNarrowString; 65 import std.utf : byUTF; 66 // TODO: functionize 67 static if (isNarrowString!S) 68 { 69 return s.byUTF!dchar.all!(ch => pred(ch)); 70 } 71 else 72 { 73 return t.map!(ch => pred(ch)); 74 } 75 } 76 77 /// 78 @safe pure unittest 79 { 80 assert(!`A`.isLowercased); 81 assert(`a`.isLowercased); 82 assert(!`Ä`.isLowercased); 83 assert(`ä`.isLowercased); 84 } 85 86 /** Check if `s` uppercased, that is only contains upper-case characters. 87 */ 88 import std.uni : isUpper; 89 bool isUppercased(S, alias pred = isUpper)(S s) 90 if (isSomeString!S) 91 { 92 import std.algorithm.searching : all; 93 import std.traits : isNarrowString; 94 import std.utf : byUTF; 95 // TODO: functionize 96 static if (isNarrowString!S) 97 { 98 return s.byUTF!dchar.all!(ch => pred(ch)); 99 } 100 else 101 { 102 return t.map!(ch => pred(ch)); 103 } 104 } 105 106 @safe pure unittest 107 { 108 assert(`A`.isUppercased); 109 assert(!`a`.isUppercased); 110 assert(`Ä`.isUppercased); 111 assert(!`ä`.isUppercased); 112 } 113 114 /** Check if `s` has proper noun capitalization. 115 * 116 * That is, `s` starts with a capital letter followed by only lower letters. 117 */ 118 bool isCapitalized(S)(S s) 119 if (isSomeString!S) 120 { 121 import std.range.primitives : empty, front, popFront; 122 123 if (s.empty) { return false; } 124 125 import std.ascii : isDigit; 126 import std.uni : isUpper; 127 const firstDigit = s.front.isDigit; 128 const firstUpper = s.front.isUpper; 129 130 if (!(firstDigit || 131 firstUpper)) 132 return false; 133 134 s.popFront(); 135 136 if (s.empty) 137 { 138 return firstDigit; 139 } 140 else 141 { 142 import std.uni : isLower; 143 import std.algorithm.searching : all; 144 return s.all!(x => (x.isDigit || 145 x.isLower)); 146 } 147 } 148 149 /// 150 @safe pure unittest 151 { 152 assert(!``.isCapitalized); 153 assert(!`alpha`.isCapitalized); 154 assert(!`ALPHA`.isCapitalized); 155 assert(!`aThing`.isCapitalized); 156 assert(`Alpha`.isCapitalized); 157 assert(!`Jack London`.isCapitalized); 158 } 159 160 /** Return `true` if `s` has proper name capitalization, such as in 161 * "Africa" or "South Africa". 162 */ 163 bool isProperNameCapitalized(S)(S s) 164 if (isSomeString!S) 165 { 166 import nxt.splitter_ex : splitterASCII; 167 import std.algorithm.comparison : among; 168 import std.algorithm.searching : all; 169 import std.ascii : isWhite; 170 import std.uni : isUpper; 171 size_t index = 0; 172 foreach (const word; s.splitterASCII!(s => (s.isWhite || s == '-'))) 173 { 174 const bool ok = ((index >= 1 && 175 (word.all!(word => word.isUpper) || // Henry II 176 word.among!(`of`, `upon`))) || 177 word.isCapitalized); 178 if (!ok) { return false; } 179 index += 1; 180 } 181 return true; 182 } 183 184 /// 185 @safe pure unittest 186 { 187 assert(!`alpha`.isProperNameCapitalized); 188 assert(!`alpha centauri`.isProperNameCapitalized); 189 assert(!`ALPHA`.isProperNameCapitalized); 190 assert(!`ALPHA CENTAURI`.isProperNameCapitalized); 191 assert(!`aThing`.isProperNameCapitalized); 192 assert(`Alpha`.isProperNameCapitalized); 193 assert(`Alpha Centauri`.isProperNameCapitalized); 194 assert(`11104 Airion`.isProperNameCapitalized); 195 assert(`New York City`.isProperNameCapitalized); 196 assert(`1-Hexanol`.isProperNameCapitalized); 197 assert(`11-Hexanol`.isProperNameCapitalized); 198 assert(`22nd Army`.isProperNameCapitalized); 199 assert(!`22nd army`.isProperNameCapitalized); 200 assert(`2nd World War`.isProperNameCapitalized); 201 assert(`Second World War`.isProperNameCapitalized); 202 assert(`Värmland`.isProperNameCapitalized); 203 assert(!`The big sky`.isProperNameCapitalized); 204 assert(`Suur-London`.isProperNameCapitalized); 205 assert(`Kingdom of Sweden`.isProperNameCapitalized); 206 assert(`Stratford upon Avon`.isProperNameCapitalized); 207 assert(`Henry II`.isProperNameCapitalized); 208 }