import re
SMALL = 'a|an|and|as|at|but|by|en|for|if|in|of|on|or|the|to|v\\ .?|via|vs\\ .?'
PUNCT = r"""!"#$%&'‘’()*+,\- ‒–—―./:;?@[\\ \] _`{|}~"""
SMALL_WORDS = re .compile ( r'^(%s)$' % SMALL, re .I )
INLINE_PERIOD = re .compile ( r'[a-z][.][a-z]' , re .I )
UC_ELSEWHERE = re .compile ( r'[%s]*?[a-zA-Z]+[A-Z]+?' % PUNCT)
SMALL_FIRST = re .compile ( r'^([%s]*)(%s)\b ' % ( PUNCT, SMALL) , re .I |re .U )
SMALL_LAST = re .compile ( r'\b (%s)[%s]?$' % ( SMALL, PUNCT) , re .I |re .U )
SMALL_AFTER_NUM = re .compile ( r'(\d +\s +)(a|an|the)\b ' , re .I |re .U )
SUBPHRASE = re .compile ( r'([:.;?!][ ])(%s)' % SMALL)
APOS_SECOND = re .compile ( r"^[dol]{1}['‘]{1}[a-z]+$" , re .I )
UC_INITIALS = re .compile ( r"^(?:[A-Z]{1}\. {1}|[A-Z]{1}\. {1}[A-Z]{1})+$" )
def capitalize( x) :
try :
return upper( x[ 0 ] ) + lower( x[ 1 :] )
except ( IndexError , TypeError , AttributeError ) :
return x
upper = _make_func( _change_case_template, 'upper' , which= 'UPPER_CASE' )
lower = _make_func( _change_case_template, 'lower' , which= 'LOWER_CASE' )
def titlecase( text) :
all_caps = upper( text) == text
pat = re .compile ( r'(\s +)' )
line = [ ]
for word in pat.split ( text) :
if not word:
continue
if pat.match ( word) is not None :
line.append ( word)
continue
if all_caps:
if UC_INITIALS.match ( word) :
line.append ( word)
continue
else :
word = icu_lower( word)
if APOS_SECOND.match ( word) :
word = word.replace ( word[ 0 ] , icu_upper( word[ 0 ] ) , 1 )
word = word[ :2 ] + icu_upper( word[ 2 ] ) + word[ 3 :]
line.append ( word)
continue
if INLINE_PERIOD.search ( word) or UC_ELSEWHERE.match ( word) :
line.append ( word)
continue
if SMALL_WORDS.match ( word) :
line.append ( icu_lower( word) )
continue
hyphenated = [ ]
for item in word.split ( '-' ) :
hyphenated.append ( CAPFIRST.sub ( lambda m: icu_upper( m.group ( 0 ) ) , item) )
line.append ( "-" .join ( hyphenated) )
result = "" .join ( line)
result = SMALL_FIRST.sub ( lambda m: '%s%s' % (
m.group ( 1 ) ,
capitalize( m.group ( 2 ) )
) , result)
result = SMALL_AFTER_NUM.sub ( lambda m: '%s%s' % ( m.group ( 1 ) ,
capitalize( m.group ( 2 ) )
) , result)
result = SMALL_LAST.sub ( lambda m: capitalize( m.group ( 0 ) ) , result)
result = SUBPHRASE.sub ( lambda m: '%s%s' % (
m.group ( 1 ) ,
capitalize( m.group ( 2 ) )
) , result)
return result
print titlecase( "test" )
aW1wb3J0IHJlCgpTTUFMTCA9ICdhfGFufGFuZHxhc3xhdHxidXR8Ynl8ZW58Zm9yfGlmfGlufG9mfG9ufG9yfHRoZXx0b3x2XFwuP3x2aWF8dnNcXC4/JwpQVU5DVCA9IHIiIiIhIiMkJSYn4oCY4oCZKCkqKyxcLeKAkuKAk+KAlOKAlS4vOjs/QFtcXFxdX2B7fH1+IiIiCgpTTUFMTF9XT1JEUyA9IHJlLmNvbXBpbGUocideKCVzKSQnICUgU01BTEwsIHJlLkkpCklOTElORV9QRVJJT0QgPSByZS5jb21waWxlKHInW2Etel1bLl1bYS16XScsIHJlLkkpClVDX0VMU0VXSEVSRSA9IHJlLmNvbXBpbGUocidbJXNdKj9bYS16QS1aXStbQS1aXSs/JyAlIFBVTkNUKQpTTUFMTF9GSVJTVCA9IHJlLmNvbXBpbGUocideKFslc10qKSglcylcYicgJSAoUFVOQ1QsIFNNQUxMKSwgcmUuSXxyZS5VKQpTTUFMTF9MQVNUID0gcmUuY29tcGlsZShyJ1xiKCVzKVslc10/JCcgJSAoU01BTEwsIFBVTkNUKSwgcmUuSXxyZS5VKQpTTUFMTF9BRlRFUl9OVU0gPSByZS5jb21waWxlKHInKFxkK1xzKykoYXxhbnx0aGUpXGInLCByZS5JfHJlLlUpClNVQlBIUkFTRSA9IHJlLmNvbXBpbGUocicoWzouOz8hXVsgXSkoJXMpJyAlIFNNQUxMKQpBUE9TX1NFQ09ORCA9IHJlLmNvbXBpbGUociJeW2RvbF17MX1bJ+KAmF17MX1bYS16XSskIiwgcmUuSSkKVUNfSU5JVElBTFMgPSByZS5jb21waWxlKHIiXig/OltBLVpdezF9XC57MX18W0EtWl17MX1cLnsxfVtBLVpdezF9KSskIikKCmRlZiBjYXBpdGFsaXplKHgpOgogICAgdHJ5OgogICAgICAgIHJldHVybiB1cHBlcih4WzBdKSArIGxvd2VyKHhbMTpdKQogICAgZXhjZXB0IChJbmRleEVycm9yLCBUeXBlRXJyb3IsIEF0dHJpYnV0ZUVycm9yKToKICAgICAgICByZXR1cm4geAoKdXBwZXIgPSBfbWFrZV9mdW5jKF9jaGFuZ2VfY2FzZV90ZW1wbGF0ZSwgJ3VwcGVyJywgd2hpY2g9J1VQUEVSX0NBU0UnKQoKbG93ZXIgPSBfbWFrZV9mdW5jKF9jaGFuZ2VfY2FzZV90ZW1wbGF0ZSwgJ2xvd2VyJywgd2hpY2g9J0xPV0VSX0NBU0UnKQoKCmRlZiB0aXRsZWNhc2UodGV4dCk6CgogICAgYWxsX2NhcHMgPSB1cHBlcih0ZXh0KSA9PSB0ZXh0CgogICAgcGF0ID0gcmUuY29tcGlsZShyJyhccyspJykKICAgIGxpbmUgPSBbXQogICAgZm9yIHdvcmQgaW4gcGF0LnNwbGl0KHRleHQpOgogICAgICAgIGlmIG5vdCB3b3JkOgogICAgICAgICAgICBjb250aW51ZQogICAgICAgIGlmIHBhdC5tYXRjaCh3b3JkKSBpcyBub3QgTm9uZToKICAgICAgICAgICAgbGluZS5hcHBlbmQod29yZCkKICAgICAgICAgICAgY29udGludWUKICAgICAgICBpZiBhbGxfY2FwczoKICAgICAgICAgICAgaWYgVUNfSU5JVElBTFMubWF0Y2god29yZCk6CiAgICAgICAgICAgICAgICBsaW5lLmFwcGVuZCh3b3JkKQogICAgICAgICAgICAgICAgY29udGludWUKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIHdvcmQgPSBpY3VfbG93ZXIod29yZCkKCiAgICAgICAgaWYgQVBPU19TRUNPTkQubWF0Y2god29yZCk6CiAgICAgICAgICAgIHdvcmQgPSB3b3JkLnJlcGxhY2Uod29yZFswXSwgaWN1X3VwcGVyKHdvcmRbMF0pLCAxKQogICAgICAgICAgICB3b3JkID0gd29yZFs6Ml0gKyBpY3VfdXBwZXIod29yZFsyXSkgKyB3b3JkWzM6XQogICAgICAgICAgICBsaW5lLmFwcGVuZCh3b3JkKQogICAgICAgICAgICBjb250aW51ZQogICAgICAgIGlmIElOTElORV9QRVJJT0Quc2VhcmNoKHdvcmQpIG9yIFVDX0VMU0VXSEVSRS5tYXRjaCh3b3JkKToKICAgICAgICAgICAgbGluZS5hcHBlbmQod29yZCkKICAgICAgICAgICAgY29udGludWUKICAgICAgICBpZiBTTUFMTF9XT1JEUy5tYXRjaCh3b3JkKToKICAgICAgICAgICAgbGluZS5hcHBlbmQoaWN1X2xvd2VyKHdvcmQpKQogICAgICAgICAgICBjb250aW51ZQoKICAgICAgICBoeXBoZW5hdGVkID0gW10KICAgICAgICBmb3IgaXRlbSBpbiB3b3JkLnNwbGl0KCctJyk6CiAgICAgICAgICAgIGh5cGhlbmF0ZWQuYXBwZW5kKENBUEZJUlNULnN1YihsYW1iZGEgbTogaWN1X3VwcGVyKG0uZ3JvdXAoMCkpLCBpdGVtKSkKICAgICAgICBsaW5lLmFwcGVuZCgiLSIuam9pbihoeXBoZW5hdGVkKSkKCiAgICByZXN1bHQgPSAiIi5qb2luKGxpbmUpCgogICAgcmVzdWx0ID0gU01BTExfRklSU1Quc3ViKGxhbWJkYSBtOiAnJXMlcycgJSAoCiAgICAgICAgbS5ncm91cCgxKSwKICAgICAgICBjYXBpdGFsaXplKG0uZ3JvdXAoMikpCiAgICApLCByZXN1bHQpCgogICAgcmVzdWx0ID0gU01BTExfQUZURVJfTlVNLnN1YihsYW1iZGEgbTogJyVzJXMnICUgKG0uZ3JvdXAoMSksCiAgICAgICAgY2FwaXRhbGl6ZShtLmdyb3VwKDIpKQogICAgKSwgcmVzdWx0KQoKICAgIHJlc3VsdCA9IFNNQUxMX0xBU1Quc3ViKGxhbWJkYSBtOiBjYXBpdGFsaXplKG0uZ3JvdXAoMCkpLCByZXN1bHQpCgogICAgcmVzdWx0ID0gU1VCUEhSQVNFLnN1YihsYW1iZGEgbTogJyVzJXMnICUgKAogICAgICAgIG0uZ3JvdXAoMSksCiAgICAgICAgY2FwaXRhbGl6ZShtLmdyb3VwKDIpKQogICAgKSwgcmVzdWx0KQoKICAgIHJldHVybiByZXN1bHQKCgpwcmludCB0aXRsZWNhc2UoICJ0ZXN0IiApCg==