Index: quodlibet/util/titlecase.py
===================================================================
--- quodlibet/util/titlecase.py	(revision 0)
+++ quodlibet/util/titlecase.py	(revision 0)
@@ -0,0 +1,43 @@
+# Copyright 2007 Javier Kohen
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation
+#
+# $Id$
+
+import unicodedata
+
+def iswbound(char):
+    """Returns whether the given character is a word boundary."""
+    category = unicodedata.category(char)
+    # If it's a space separator or punctuation
+    return 'Zs' == category or 'Sk' == category or 'P' == category[0]
+
+def utitle(string):
+    """Title-case a string using a less destructive method than str.title."""
+    new_string = string[0].capitalize()
+    cap = False
+    for i in xrange(1, len(string)):
+        s = string[i]
+        # Special case apostrophe in the middle of a word.
+        if u"'" == s and string[i-1].isalpha(): cap = False
+        elif iswbound(s): cap = True
+        elif cap and s.isalpha():
+            cap = False
+            s = s.capitalize()
+        else: cap = False
+        new_string += s
+    return new_string
+
+from types import UnicodeType
+from locale import getpreferredencoding
+
+def title(string):
+    """Title-case a string using a less destructive method than str.title."""
+    if not string: return ""
+    # if the string is all uppercase, lowercase it
+    if string == string.upper(): string = string.lower()
+    if (not isinstance(string, UnicodeType)):
+        string = unicode(string.decode(getpreferredencoding()))
+    return utitle(string)

Property changes on: quodlibet/util/titlecase.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Index: quodlibet/util/__init__.py
===================================================================
--- quodlibet/util/__init__.py	(revision 4263)
+++ quodlibet/util/__init__.py	(working copy)
@@ -15,6 +15,9 @@
 import urlparse
 import warnings
 
+# title function was moved to a separate module
+from quodlibet.util.titlecase import title
+
 from quodlibet.const import FSCODING as fscoding, ENCODING
 from quodlibet.util.i18n import GlibTranslations
 
@@ -288,20 +291,6 @@
     except UnicodeError:
         return (s + " " + _("[Invalid Encoding]")).encode(charset, "replace")
 
-def title(string):
-    """Title-case a string using a less destructive method than str.title."""
-    if not string: return ""
-    new_string = string[0].capitalize()
-    cap = False
-    for s in string[1:]:
-        if s.isspace(): cap = True
-        elif cap and s.isalpha():
-            cap = False
-            s = s.capitalize()
-        else: cap = False
-        new_string += s
-    return new_string
-
 def iscommand(s):
     """True if an executable file 's' exists in the user's path, or is a
     fully-qualified existing executable file."""
Index: tests/test_titlecase.py
===================================================================
--- tests/test_titlecase.py	(revision 0)
+++ tests/test_titlecase.py	(revision 0)
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+# Copyright 2007 Javier Kohen
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation
+#
+# $Id$
+
+from tests import TestCase, add
+
+from quodlibet.util.titlecase import title
+
+class TitlecaseTests(TestCase):
+    def test_basics(s):
+        s.assertEquals(u"Mama's Boy", title(u"mama's boy"))
+        s.assertEquals(u"The A-Sides", title(u"the a-sides"))
+        s.assertEquals(u"Hello Goodbye", title(u"hello goodbye"))
+        s.assertEquals(u"Hello Goodbye", title(u"HELLO GOODBYE"))
+
+    def test_quirks(s):
+        # This character is not an apostrophe, it's a single quote!
+        s.assertEquals(u"Mama’S Boy", title(u"mama’s boy"))
+        # This is actually an accent character, not an apostrophe either.
+        s.assertEquals(u"Mama`S Boy", title(u"mama`s boy"))
+
+    def test_quotes(s):
+        s.assertEquals(u"Hello Goodbye (A Song)",
+                 title(u"hello goodbye (a song)"))
+        s.assertEquals(u"Hello Goodbye 'A Song'",
+                 title(u"hello goodbye 'a song'"))
+        s.assertEquals(u'Hello Goodbye "A Song"',
+                 title(u'hello goodbye "a song"'))
+        s.assertEquals(u"Hello Goodbye „A Song”",
+                 title(u"hello goodbye „a song”"))
+        s.assertEquals(u"Hello Goodbye ‘A Song’",
+                 title(u"hello goodbye ‘a song’"))
+        s.assertEquals(u"Hello Goodbye “A Song”",
+                 title(u"hello goodbye “a song”"))
+        s.assertEquals(u"Hello Goodbye »A Song«",
+                 title(u"hello goodbye »a song«"))
+        s.assertEquals(u"Hello Goodbye «A Song»",
+                 title(u"hello goodbye «a song»"))
+
+    def test_unicode(s):
+        s.assertEquals(u"Fooäbar",
+                 title(u"fooäbar"))
+        s.assertEquals(u"Los Años Felices",
+                 title(u"los años felices"))
+        s.assertEquals(u"Ñandú",
+                 title(u"ñandú"))
+        s.assertEquals(u"Österreich",
+                 title(u"österreich"))
+        # Not a real word - there is none with this character at the beginning
+        # but still Python doesn't capitalize the es-zed properly.
+        # s.assertEquals(u"SSbahn", title(u"ßbahn"))
+
+add(TitlecaseTests)

Property changes on: tests/test_titlecase.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

