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 }