Skip to content

Commit 5d0965f

Browse files
committed
feat: add comprehensive inflection patterns for English nouns
- Add -se/-ses pattern (impulses → impulse, responses → response) - Add Latin -um/-a plurals (datum/data, medium/media, spectrum/spectra) - Add Greek -on/-a plurals (criterion/criteria, phenomenon/phenomena) - Add extended -f/-ves patterns (wolf/wolves, scarf/scarves, leaf/leaves) - Add French -eau/-eaux plurals (bureau/bureaux, tableau/tableaux) - Add improved quiz/quizzes pattern - Add comprehensive test coverage for all new patterns - All 69 specs passing
1 parent 139b4d9 commit 5d0965f

2 files changed

Lines changed: 102 additions & 4 deletions

File tree

spec/inflector/noun_inflector_spec.cr

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,53 @@ describe Cadmium::NounInflector do
108108
subject.singularize("antennas").should eq("antenna")
109109
subject.singularize("formulas").should eq("formula")
110110
end
111+
112+
it "should handle -SES to -SE cases" do
113+
subject.singularize("impulses").should eq("impulse")
114+
subject.singularize("responses").should eq("response")
115+
subject.singularize("licenses").should eq("license")
116+
subject.singularize("verses").should eq("verse")
117+
subject.singularize("courses").should eq("course")
118+
end
119+
120+
it "should handle Latin -A to -UM cases" do
121+
subject.singularize("data").should eq("datum")
122+
subject.singularize("media").should eq("medium")
123+
subject.singularize("curricula").should eq("curriculum")
124+
subject.singularize("memoranda").should eq("memorandum")
125+
subject.singularize("bacteria").should eq("bacterium")
126+
subject.singularize("errata").should eq("erratum")
127+
subject.singularize("ova").should eq("ovum")
128+
subject.singularize("agenda").should eq("agendum")
129+
subject.singularize("referenda").should eq("referendum")
130+
subject.singularize("spectra").should eq("spectrum")
131+
subject.singularize("strata").should eq("stratum")
132+
end
133+
134+
it "should handle Greek -A to -ON cases" do
135+
subject.singularize("criteria").should eq("criterion")
136+
subject.singularize("phenomena").should eq("phenomenon")
137+
subject.singularize("automata").should eq("automaton")
138+
end
139+
140+
it "should handle extended -VES to -F cases" do
141+
subject.singularize("wolves").should eq("wolf")
142+
subject.singularize("calves").should eq("calf")
143+
subject.singularize("scarves").should eq("scarf")
144+
subject.singularize("leaves").should eq("leaf")
145+
subject.singularize("hooves").should eq("hoof")
146+
end
147+
148+
it "should handle French -EAU to -EAUX cases" do
149+
subject.singularize("bureaux").should eq("bureau")
150+
subject.singularize("tableaux").should eq("tableau")
151+
subject.singularize("chateaux").should eq("chateau")
152+
subject.singularize("plateaux").should eq("plateau")
153+
end
154+
155+
it "should handle QUIZ cases" do
156+
subject.singularize("quizzes").should eq("quiz")
157+
end
111158
end
112159

113160
describe "#pluralize" do
@@ -153,7 +200,6 @@ describe Cadmium::NounInflector do
153200
subject.pluralize("church").should eq("churches")
154201
subject.pluralize("appendix").should eq("appendixes")
155202
subject.pluralize("mess").should eq("messes")
156-
subject.pluralize("quiz").should eq("quizes")
157203
subject.pluralize("shoe").should eq("shoes")
158204
end
159205

@@ -220,5 +266,44 @@ describe Cadmium::NounInflector do
220266
subject.pluralize("vertebra").should eq("vertebrae")
221267
subject.pluralize("vita").should eq("vitae")
222268
end
269+
270+
it "should handle Latin -UM to -A cases" do
271+
subject.pluralize("datum").should eq("data")
272+
subject.pluralize("medium").should eq("media")
273+
subject.pluralize("curriculum").should eq("curricula")
274+
subject.pluralize("memorandum").should eq("memoranda")
275+
subject.pluralize("bacterium").should eq("bacteria")
276+
subject.pluralize("erratum").should eq("errata")
277+
subject.pluralize("ovum").should eq("ova")
278+
subject.pluralize("agendum").should eq("agenda")
279+
subject.pluralize("referendum").should eq("referenda")
280+
subject.pluralize("spectrum").should eq("spectra")
281+
subject.pluralize("stratum").should eq("strata")
282+
end
283+
284+
it "should handle Greek -ON to -A cases" do
285+
subject.pluralize("criterion").should eq("criteria")
286+
subject.pluralize("phenomenon").should eq("phenomena")
287+
subject.pluralize("automaton").should eq("automata")
288+
end
289+
290+
it "should handle extended -F to -VES cases" do
291+
subject.pluralize("wolf").should eq("wolves")
292+
subject.pluralize("calf").should eq("calves")
293+
subject.pluralize("scarf").should eq("scarves")
294+
subject.pluralize("leaf").should eq("leaves")
295+
subject.pluralize("hoof").should eq("hooves")
296+
end
297+
298+
it "should handle French -EAU to -EAUX cases" do
299+
subject.pluralize("bureau").should eq("bureaux")
300+
subject.pluralize("tableau").should eq("tableaux")
301+
subject.pluralize("chateau").should eq("chateaux")
302+
subject.pluralize("plateau").should eq("plateaux")
303+
end
304+
305+
it "should handle QUIZ cases" do
306+
subject.pluralize("quiz").should eq("quizzes")
307+
end
223308
end
224309
end

src/cadmium/inflector/noun_inflector.cr

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,26 +31,39 @@ module Cadmium
3131
add_irregular("cloth", "clothes")
3232
add_irregular("hero", "heroes")
3333

34-
plural_forms.regular_forms.push({/y$/i, "ies"})
35-
plural_forms.regular_forms.push({/ife$/i, "ives"})
34+
# Most specific patterns first - order matters!
35+
plural_forms.regular_forms.push({/([^v])y$/i, "\\1ies"})
36+
plural_forms.regular_forms.push({/(criteri|phenomen|automat)on$/i, "\\1a"})
37+
plural_forms.regular_forms.push({/(curricul|dat|medi|memorand|bacteri|errat|ov|agend|referend|spectr|strat)um$/i, "\\1a"})
3638
plural_forms.regular_forms.push({/(antenn|formul|nebul|vertebr|vit)a$/i, "\\1ae"})
39+
plural_forms.regular_forms.push({/([aeiou]l|ar|ea|oo)f$/i, "\\1ves"})
40+
plural_forms.regular_forms.push({/ife$/i, "ives"})
3741
plural_forms.regular_forms.push({/(octop|vir|radi|nucle|fung|cact|stimul)us$/i, "\\1i"})
3842
plural_forms.regular_forms.push({/(buffal|tomat|tornad)o$/i, "\\1oes"})
43+
plural_forms.regular_forms.push({/(quiz)$/i, "\\1zes"})
3944
plural_forms.regular_forms.push({/(sis)$/i, "ses"})
4045
plural_forms.regular_forms.push({/(matr|vert|ind|cort)(ix|ex)$/i, "\\1ices"})
46+
plural_forms.regular_forms.push({/(bureau|tableau|chateau|plateau)$/i, "\\1x"})
4147
plural_forms.regular_forms.push({/sses$/i, "sses"})
4248
plural_forms.regular_forms.push({/(x|ch|ss|sh|s|z)$/i, "\\1es"})
4349
plural_forms.regular_forms.push({/^(?!talis|.*hu)(.*)man$/i, "\\1men"})
4450
plural_forms.regular_forms.push({/(.*)/i, "\\1s"})
4551

52+
# Most specific patterns first - order matters!
53+
singular_forms.regular_forms.push({/(.*[^us])ses$/i, "\\1se"})
4654
singular_forms.regular_forms.push({/([^v])ies$/i, "\\1y"})
47-
singular_forms.regular_forms.push({/ives$/i, "ife"})
55+
singular_forms.regular_forms.push({/(criteri|phenomen|automat)a$/i, "\\1on"})
56+
singular_forms.regular_forms.push({/(curricul|dat|medi|memorand|bacteri|errat|ov|agend|referend|spectr|strat)a$/i, "\\1um"})
4857
singular_forms.regular_forms.push({/(antenn|formul|nebul|vertebr|vit)ae$/i, "\\1a"})
58+
singular_forms.regular_forms.push({/([aeiou]l|ar|ea|oo)ves$/i, "\\1f"})
59+
singular_forms.regular_forms.push({/ives$/i, "ife"})
4960
singular_forms.regular_forms.push({/(octop|vir|radi|nucle|fung|cact|stimul)(i)$/i, "\\1us"})
5061
singular_forms.regular_forms.push({/(buffal|tomat|tornad)(oes)$/i, "\\1o"})
62+
singular_forms.regular_forms.push({/(quiz)zes$/i, "\\1"})
5163
singular_forms.regular_forms.push({/(analy|naly|synop|parenthe|diagno|the)ses$/i, "\\1sis"})
5264
singular_forms.regular_forms.push({/(vert|ind|cort)(ices)$/i, "\\1ex"})
5365
singular_forms.regular_forms.push({/(matr|append)(ices)$/i, "\\1ix"})
66+
singular_forms.regular_forms.push({/(bureau|tableau|chateau|plateau)x$/i, "\\1"})
5467
singular_forms.regular_forms.push({/(x|ch|ss|sh|s|z)es$/i, "\\1"})
5568
singular_forms.regular_forms.push({/men$/i, "man"})
5669
singular_forms.regular_forms.push({/ss$/i, "ss"})

0 commit comments

Comments
 (0)