From 20bf28c679a68c85f4270d0b29f1782e52a5b004 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Afonso=20Janu=C3=A1rio?= Date: Tue, 8 Sep 2026 11:08:20 +0100 Subject: [PATCH] Fix casing of names like O'Brien and d'Artagnan in all-caps input When a whole line is shouted in caps, titlecase lowercases each word before recapitalizing it, except for words caught by the apostrophe rule (d', l', o' style names). That branch only fixed up the two letters around the apostrophe and left everything after them exactly as it came in, so "O'BRIEN'S HOUSE" turned into "O'BRIEN'S House" instead of "O'Brien's House", and "D'ARTAGNAN" came out as "d'ARTAGNAN". Downcase the remainder of the word first when the line is all caps, same as the other word-handling branches already do, before applying the apostrophe-specific capitalization. --- titlecase/__init__.py | 8 ++++++-- titlecase/tests.py | 8 ++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/titlecase/__init__.py b/titlecase/__init__.py index fd24431..bdee4bb 100755 --- a/titlecase/__init__.py +++ b/titlecase/__init__.py @@ -123,10 +123,14 @@ def titlecase(text, callback=None, small_first_last=True, preserve_blank_lines=F continue if APOS_SECOND.match(word): + # Everything past the second letter was shouted along with + # the rest of the line, so it needs the same downcasing the + # other branches below give their words before recapping. + rest = word[3:].lower() if all_caps else word[3:] if len(word[0]) == 1 and word[0] not in 'aeiouAEIOU': - word = word[0].lower() + word[1] + word[2].upper() + word[3:] + word = word[0].lower() + word[1] + word[2].upper() + rest else: - word = word[0].upper() + word[1] + word[2].upper() + word[3:] + word = word[0].upper() + word[1] + word[2].upper() + rest tc_line.append(word) continue diff --git a/titlecase/tests.py b/titlecase/tests.py index 9265ec0..c1745fd 100644 --- a/titlecase/tests.py +++ b/titlecase/tests.py @@ -246,6 +246,14 @@ "O'GranGe", "O'GranGe", ), + ( + "D'ARTAGNAN AND THE THREE MUSKETEERS", + "d'Artagnan and the Three Musketeers", + ), + ( + "O'BRIEN'S HOUSE IS FOR SALE", + "O'Brien's House Is for Sale", + ), ( "o'melveny/o'doyle o'Melveny/o'doyle O'melveny/o'doyle o'melveny/o'Doyle o'melveny/O'doyle", "O'Melveny/O'Doyle O'Melveny/O'Doyle O'Melveny/O'Doyle O'Melveny/O'Doyle O'Melveny/O'Doyle",