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         return s.byUTF!dchar.all!(ch => pred(ch));
69     else
70         return t.map!(ch => pred(ch));
71 }
72 
73 ///
74 @safe pure unittest
75 {
76     assert(!`A`.isLowercased);
77     assert(`a`.isLowercased);
78     assert(!`Ä`.isLowercased);
79     assert(`ä`.isLowercased);
80 }
81 
82 /** Check if `s` uppercased, that is only contains upper-case characters.
83  */
84 import std.uni : isUpper;
85 bool isUppercased(S, alias pred = isUpper)(S s)
86 if (isSomeString!S)
87 {
88     import std.algorithm.searching : all;
89     import std.traits : isNarrowString;
90     import std.utf : byUTF;
91     // TODO: functionize
92     static if (isNarrowString!S)
93         return s.byUTF!dchar.all!(ch => pred(ch));
94     else
95         return t.map!(ch => pred(ch));
96 }
97 
98 @safe pure unittest
99 {
100     assert(`A`.isUppercased);
101     assert(!`a`.isUppercased);
102     assert(`Ä`.isUppercased);
103     assert(!`ä`.isUppercased);
104 }
105 
106 /** Check if `s` has proper noun capitalization.
107  *
108  * That is, `s` starts with a capital letter followed by only lower letters.
109  */
110 bool isCapitalized(S)(S s)
111 if (isSomeString!S)
112 {
113     import std.range.primitives : empty, front, popFront;
114 
115     if (s.empty) { return false; }
116 
117     import std.ascii : isDigit;
118     import std.uni : isUpper;
119     const firstDigit = s.front.isDigit;
120     const firstUpper = s.front.isUpper;
121 
122     if (!(firstDigit ||
123           firstUpper))
124         return false;
125 
126     s.popFront();
127 
128     if (s.empty)
129         return firstDigit;
130     else
131     {
132         import std.uni : isLower;
133         import std.algorithm.searching : all;
134         return s.all!(x => (x.isDigit ||
135                             x.isLower));
136     }
137 }
138 
139 ///
140 @safe pure unittest
141 {
142     assert(!``.isCapitalized);
143     assert(!`alpha`.isCapitalized);
144     assert(!`ALPHA`.isCapitalized);
145     assert(!`aThing`.isCapitalized);
146     assert(`Alpha`.isCapitalized);
147     assert(!`Jack London`.isCapitalized);
148 }
149 
150 /** Return `true` if `s` has proper name capitalization, such as in
151  * "Africa" or "South Africa".
152  */
153 bool isProperNameCapitalized(S)(S s)
154 if (isSomeString!S)
155 {
156     import nxt.splitter_ex : splitterASCII;
157     import std.algorithm.comparison : among;
158     import std.algorithm.searching : all;
159     import std.ascii : isWhite;
160     import std.uni : isUpper;
161     size_t index = 0;
162     foreach (const word; s.splitterASCII!(s => (s.isWhite || s == '-')))
163     {
164         const bool ok = ((index >= 1 &&
165                           (word.all!(word => word.isUpper) || // Henry II
166                            word.among!(`of`, `upon`))) ||
167                          word.isCapitalized);
168         if (!ok) { return false; }
169         index += 1;
170     }
171     return true;
172 }
173 
174 ///
175 @safe pure unittest
176 {
177     assert(!`alpha`.isProperNameCapitalized);
178     assert(!`alpha centauri`.isProperNameCapitalized);
179     assert(!`ALPHA`.isProperNameCapitalized);
180     assert(!`ALPHA CENTAURI`.isProperNameCapitalized);
181     assert(!`aThing`.isProperNameCapitalized);
182     assert(`Alpha`.isProperNameCapitalized);
183     assert(`Alpha Centauri`.isProperNameCapitalized);
184     assert(`11104 Airion`.isProperNameCapitalized);
185     assert(`New York City`.isProperNameCapitalized);
186     assert(`1-Hexanol`.isProperNameCapitalized);
187     assert(`11-Hexanol`.isProperNameCapitalized);
188     assert(`22nd Army`.isProperNameCapitalized);
189     assert(!`22nd army`.isProperNameCapitalized);
190     assert(`2nd World War`.isProperNameCapitalized);
191     assert(`Second World War`.isProperNameCapitalized);
192     assert(`Värmland`.isProperNameCapitalized);
193     assert(!`The big sky`.isProperNameCapitalized);
194     assert(`Suur-London`.isProperNameCapitalized);
195     assert(`Kingdom of Sweden`.isProperNameCapitalized);
196     assert(`Stratford upon Avon`.isProperNameCapitalized);
197     assert(`Henry II`.isProperNameCapitalized);
198 }