From ba8fdda1bfdc72a8326e3e414921916ca0f3d20b Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Tue, 17 Dec 2024 13:42:54 -0700 Subject: [PATCH] feat(glossy): improve pluralize --- glossy/src/utils.typ | 122 +++++++++++-- glossy/tests/pluralize/test.typ | 308 ++++++++++++++++++++++++++++++++ 2 files changed, 418 insertions(+), 12 deletions(-) create mode 100644 glossy/tests/pluralize/test.typ diff --git a/glossy/src/utils.typ b/glossy/src/utils.typ index 0acab16..fbba69a 100644 --- a/glossy/src/utils.typ +++ b/glossy/src/utils.typ @@ -10,29 +10,127 @@ // word: The singular word to pluralize // Returns: The pluralized form of the word, or none if input is none #let __pluralize(word) = { - // early exit with invalid input + // Early exit for invalid input if word == none { return none } - // Define helper functions for checking endings + // Helper functions for checking endings let ends_with = (suffix) => lower(word).ends-with(suffix) let ends_with_any = (suffixes) => suffixes.any((suffix) => ends_with(suffix)) - // Handle special cases for "es" endings (s, x, z, sh, ch) - if ends_with_any(( "s", "x", "z", "sh", "ch" )) { + // Map of irregular plurals for common words + let irregulars = ( + "alumna": "alumnae", + "alumnus": "alumni", + "analysis": "analyses", + "appendix": "appendices", + "basis": "bases", + "cactus": "cacti", + "child": "children", + "crisis": "crises", + "criterion": "criteria", + "datum": "data", + "foot": "feet", + "fungus": "fungi", + "goose": "geese", + "man": "men", + "medium": "media", + "mouse": "mice", + "nucleus": "nuclei", + "oasis": "oases", + "person": "people", + "phenomenon": "phenomena", + "stimulus": "stimuli", + "thesis": "theses", + "tooth": "teeth", + "woman": "women", + ) + + // Set of plural-only words + let plural_only = ( + "advice", + "alumni", + "bison", + "children", + "criteria", + "data", + "deer", + "feet", + "fish", + "geese", + "information", + "media", + "men", + "mice", + "money", + "moose", + "octopi", + "octopodes", + "people", + "scissors", + "series", + "sheep", + "species", + "teeth", + "trousers", + "women", + ) + + // Irregular suffixes + let f_to_ves = ("leaf", "loaf", "calf", "half", "wolf", "thief") + let fe_to_ves = ("wife", "knife", "life") + let o_to_os = ("photo", "piano", "halo", "radio", "video", "studio", "solo", "taco", "memo", "zero") + + // Convenience + let w = lower(word) + + // Check for words already plural + if plural_only.contains(w) { + return word + } + + // Check for irregular singulars + if irregulars.keys().contains(w) { + return irregulars.at(w) + } + + // Standard pluralization rules + if ends_with("iz") { + word + "zes" + } else if ends_with_any(("s", "x", "z", "sh", "ch")) { word + "es" - } else if ends_with("y") and not ends_with_any(( "a", "e", "i", "o", "u" )) { - // Convert y to ies when y follows a consonant - word.slice(0, -1) + "ies" + } else if ends_with("y") { + if not ends_with_any(("ay", "ey", "iy", "oy", "uy")) { + word.slice(0, -1) + "ies" + } else { + word + "s" + } } else if ends_with("f") { - // Convert f to ves - word.slice(0, -1) + "ves" + if f_to_ves.contains(w) { + word.slice(0, -1) + "ves" + } else { + word + "s" + } } else if ends_with("fe") { - // Convert fe to ves - word.slice(0, -2) + "ves" + if fe_to_ves.contains(w) { + word.slice(0, -2) + "ves" + } else { + word + "s" + } + } else if ends_with("us") { + word.slice(0, -2) + "i" + } else if ends_with("is") { + word.slice(0, -2) + "es" + } else if ends_with("on") { + word + "s" + } else if ends_with("o") { + if o_to_os.contains(w) { + word + "s" + } else { + word + "es" + } } else { - // Default case: just add s word + "s" } } diff --git a/glossy/tests/pluralize/test.typ b/glossy/tests/pluralize/test.typ new file mode 100644 index 0000000..d9af75f --- /dev/null +++ b/glossy/tests/pluralize/test.typ @@ -0,0 +1,308 @@ +#import "/src/utils.typ": __pluralize + +#let plural_tests = ( + ("BUS", "BUSes"), + ("Box", "Boxes"), + ("Buzz", "Buzzes"), + ("CHURCH", "CHURCHes"), + ("Lady", "Ladies"), + ("Leaf", "Leaves"), + ("Loaf", "Loaves"), + ("ability", "abilities"), + ("account", "accounts"), + ("advice", "advice"), + ("age", "ages"), + ("agent", "agents"), + ("alumna", "alumnae"), + ("alumni", "alumni"), + ("alumnus", "alumni"), + ("answer", "answers"), + ("appendix", "appendices"), + ("apple", "apples"), + ("art", "arts"), + ("artist", "artists"), + ("attention", "attentions"), + ("ax", "axes"), + ("baby", "babies"), + ("ball", "balls"), + ("bank", "banks"), + ("basis", "bases"), + ("beauty", "beauties"), + ("bed", "beds"), + ("belief", "beliefs"), + ("berry", "berries"), + ("bird", "birds"), + ("book", "books"), + ("bottle", "bottles"), + ("box", "boxes"), + ("boy", "boys"), + ("branch", "branches"), + ("brother", "brothers"), + ("brush", "brushes"), + ("bus", "buses"), + ("business", "businesses"), + ("cactus", "cacti"), + ("cake", "cakes"), + ("calf", "calves"), + ("car", "cars"), + ("care", "cares"), + ("carrot", "carrots"), + ("cart", "carts"), + ("cat", "cats"), + ("chair", "chairs"), + ("chance", "chances"), + ("change", "changes"), + ("chef", "chefs"), + ("chief", "chiefs"), + ("child", "children"), + ("church", "churches"), + ("city", "cities"), + ("class", "classes"), + ("cloud", "clouds"), + ("coach", "coaches"), + ("comfort", "comforts"), + ("company", "companies"), + ("computer", "computers"), + ("confidence", "confidences"), + ("cookie", "cookies"), + ("corpus", "corpuses"), + ("country", "countries"), + ("courage", "courages"), + ("cow", "cows"), + ("criterion", "criteria"), + ("culture", "cultures"), + ("cup", "cups"), + ("danger", "dangers"), + ("dart", "darts"), + ("day", "days"), + ("deer", "deer"), + ("desk", "desks"), + ("development", "developments"), + ("dish", "dishes"), + ("dog", "dogs"), + ("door", "doors"), + ("dream", "dreams"), + ("education", "educations"), + ("egg", "eggs"), + ("elephant", "elephants"), + ("employee", "employees"), + ("energy", "energies"), + ("entertainment", "entertainments"), + ("exam", "exams"), + ("experience", "experiences"), + ("family", "families"), + ("farm", "farms"), + ("father", "fathers"), + ("fish", "fish"), + ("floor", "floors"), + ("flower", "flowers"), + ("fly", "flies"), + ("focus", "focuses"), + ("foot", "feet"), + ("fork", "forks"), + ("fox", "foxes"), + ("freedom", "freedoms"), + ("friend", "friends"), + ("future", "futures"), + ("game", "games"), + ("garden", "gardens"), + ("gas", "gases"), + ("gift", "gifts"), + ("girl", "girls"), + ("glass", "glasses"), + ("goat", "goats"), + ("goose", "geese"), + ("grass", "grasses"), + ("guy", "guys"), + ("half", "halves"), + ("hand", "hands"), + ("hat", "hats"), + ("head", "heads"), + ("health", "healths"), + ("help", "helps"), + ("hero", "heroes"), + ("history", "histories"), + ("holiday", "holidays"), + ("hope", "hopes"), + ("horse", "horses"), + ("house", "houses"), + ("idea", "ideas"), + ("image", "images"), + ("information", "information"), + ("interest", "interests"), + ("island", "islands"), + ("journey", "journeys"), + ("joy", "joys"), + ("justice", "justices"), + ("key", "keys"), + ("keyboard", "keyboards"), + ("king", "kings"), + ("kiss", "kisses"), + ("kitchen", "kitchens"), + ("knife", "knives"), + ("knowledge", "knowledges"), + ("lady", "ladies"), + ("lamp", "lamps"), + ("laptop", "laptops"), + ("law", "laws"), + ("leaf", "leaves"), + ("learning", "learnings"), + ("leg", "legs"), + ("letter", "letters"), + ("life", "lives"), + ("light", "lights"), + ("line", "lines"), + ("lion", "lions"), + ("loaf", "loaves"), + ("love", "loves"), + ("luck", "lucks"), + ("man", "men"), + ("map", "maps"), + ("market", "markets"), + ("match", "matches"), + ("memory", "memories"), + ("milk", "milks"), + ("money", "money"), + ("monitor", "monitors"), + ("month", "months"), + ("mosquito", "mosquitoes"), + ("mother", "mothers"), + ("mountain", "mountains"), + ("mouse", "mice"), + ("movie", "movies"), + ("name", "names"), + ("nation", "nations"), + ("neck", "necks"), + ("night", "nights"), + ("nucleus", "nuclei"), + ("ocean", "oceans"), + ("octopi", "octopi"), + ("octopodes", "octopodes"), + ("octopus", "octopuses"), + ("office", "offices"), + ("onion", "onions"), + ("opinion", "opinions"), + ("orange", "oranges"), + ("page", "pages"), + ("park", "parks"), + ("party", "parties"), + ("peace", "peaces"), + ("peach", "peaches"), + ("pen", "pens"), + ("person", "people"), + ("phenomenon", "phenomena"), + ("phone", "phones"), + ("photo", "photos"), + ("piano", "pianos"), + ("picture", "pictures"), + ("pig", "pigs"), + ("place", "places"), + ("plane", "planes"), + ("plant", "plants"), + ("plate", "plates"), + ("play", "plays"), + ("player", "players"), + ("pocket", "pockets"), + ("potato", "potatoes"), + ("power", "powers"), + ("printer", "printers"), + ("problem", "problems"), + ("program", "programs"), + ("proof", "proofs"), + ("puppy", "puppies"), + ("queen", "queens"), + ("quiz", "quizzes"), + ("radio", "radios"), + ("rain", "rains"), + ("respect", "respects"), + ("responsibility", "responsibilities"), + ("river", "rivers"), + ("road", "roads"), + ("roof", "roofs"), + ("room", "rooms"), + ("safe", "safes"), + ("school", "schools"), + ("scissors", "scissors"), + ("sea", "seas"), + ("series", "series"), + ("sheep", "sheep"), + ("ship", "ships"), + ("shoe", "shoes"), + ("shop", "shops"), + ("shore", "shores"), + ("sister", "sisters"), + ("sky", "skies"), + ("snow", "snows"), + ("solo", "solos"), + ("son", "sons"), + ("song", "songs"), + ("sound", "sounds"), + ("spoon", "spoons"), + ("star", "stars"), + ("station", "stations"), + ("stone", "stones"), + ("store", "stores"), + ("story", "stories"), + ("street", "streets"), + ("student", "students"), + ("studio", "studios"), + ("success", "successes"), + ("sun", "suns"), + ("syllabus", "syllabuses"), + ("table", "tables"), + ("taco", "tacos"), + ("teacher", "teachers"), + ("team", "teams"), + ("television", "televisions"), + ("test", "tests"), + ("thief", "thieves"), + ("thing", "things"), + ("time", "times"), + ("tomato", "tomatoes"), + ("tooth", "teeth"), + ("town", "towns"), + ("toy", "toys"), + ("tree", "trees"), + ("trousers", "trousers"), + ("truth", "truths"), + ("umbrella", "umbrellas"), + ("valley", "valleys"), + ("victory", "victories"), + ("video", "videos"), + ("village", "villages"), + ("wall", "walls"), + ("watch", "watches"), + ("water", "waters"), + ("wealth", "wealths"), + ("week", "weeks"), + ("wife", "wives"), + ("window", "windows"), + ("wisdom", "wisdoms"), + ("wish", "wishes"), + ("wolf", "wolves"), + ("woman", "women"), + ("wood", "woods"), + ("word", "words"), + ("work", "works"), + ("year", "years"), + ("zero", "zeros"), + (none, none), +) + +#for (singular, expected) in plural_tests { + let result = __pluralize(singular) + if result != expected { + panic( + "Pluralization failed for: " + + (if singular == none { "none" } else { str(singular) }) + + ". Expected: " + + (if expected == none { "none" } else { str(expected) }) + + ", got: " + + (if result == none { "none" } else { str(result) }) + ) + } +} + +#set page(height: auto, width: auto, margin: 1em) + +"Success! All pluralizations determined properly!"