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