1 /**
2 	* Core inflection
3 	*
4 	* Copyright: © 2015 David Monagle
5 	* License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 	* Authors: David Monagle
7 */
8 module inflections.Inflector;
9 
10 import std.regex;
11 import std.array;
12 import std.algorithm;
13 import std.uni;
14 
15 interface InflectionInterface {
16 	string inflect(const string input);
17 	bool matches(const string input) const;
18 }
19 
20 class InflectionRule(string expression, string format, string matchOptions = "") : InflectionInterface {
21 	private static auto _regex = ctRegex!(expression, matchOptions);
22 
23 	static InflectionInterface opCall() {
24 		return cast(InflectionInterface)(new InflectionRule!(expression, format, matchOptions));
25 	}
26 
27 	override string inflect(const string input) {
28 		return input.replaceFirst(_regex, format);
29 	}
30 	
31 	bool matches(const string input) const {
32 		return cast(bool)input.matchFirst(_regex);
33 	}
34 }
35 
36 unittest {
37 	auto testInflector = InflectionRule!(`$`, `s`, "i")();
38 	assert (testInflector.inflect("apple") == "apples");
39 }
40 
41 struct Translation {
42 	string singular;
43 	string plural;
44 
45 	bool canPlural(const string input) {
46 		if (input.length < singular.length) return false;
47 		if (input[$ - singular.length..$].toLower == singular) {
48 			return true;
49 		}
50 		return false;
51 	}
52 
53 	string toPlural(const string input) {
54 		assert(input.length >= singular.length);
55 		return input[0..$-singular.length] ~ plural;
56 	}
57 
58 	bool canSingular(const string input) {
59 		if (input.length < plural.length) return false;
60 		if (input[$ - plural.length..$].toLower == plural) {
61 			return true;
62 		}
63 		return false;
64 	}
65 
66 	string toSingular(const string input) {
67 		assert(input.length >= plural.length);
68 		return input[0..$-plural.length] ~ singular;
69 	}
70 }
71 
72 unittest {
73 	auto t = Translation("person", "people");
74 	assert(t.toPlural("oneperson") == "onepeople");
75 	assert(t.toSingular("onepeople") == "oneperson");
76 }
77 
78 class Inflector {
79 	static InflectionInterface[] _plurals;
80 	static InflectionInterface[] _singulars;
81 	static Translation[] _irregulars;
82 	static string[] _uncountables;
83 
84 	static void plural(string expression, string format, string matchOptions = "")() {
85 		_plurals ~= InflectionRule!(expression, format, matchOptions)();
86 	}
87 	
88 	static void singular(string expression, string format, string matchOptions = "")() {
89 		_singulars ~= InflectionRule!(expression, format, matchOptions)();
90 	}
91 
92 	static void irregular(string singular, string plural) {
93 		_irregulars ~= Translation(singular, plural);
94 	}
95 	
96 	static void uncountable(string[] values ...) {
97 		_uncountables ~= values;
98 	}
99 	
100 	static string transform(bool plural)(const string input) {
101 		static if (plural) {
102 			alias _transforms = _plurals;
103 		}
104 		else {
105 			alias _transforms = _singulars;
106 		}
107 
108 		auto lowerInput = input.toLower;
109 
110 		if (_uncountables.canFind(lowerInput)) return input;
111 
112 		foreach(irregular; _irregulars) {
113 			static if (plural) {
114 				if (irregular.canPlural(input)) return irregular.toPlural(input);
115 			}
116 			else {
117 				if (irregular.canSingular(input)) return irregular.toSingular(input);
118 			}
119 		}
120 
121 		foreach(rule; _transforms) {
122 			if (rule.matches(input)) 
123 				return rule.inflect(input);
124 		}
125 		
126 		return input;
127 	}
128 
129 	alias pluralize = transform!true;
130 	alias singularize = transform!false;
131 }