Index: Doc/lib/libposixpath.tex =================================================================== --- Doc/lib/libposixpath.tex (revision 54408) +++ Doc/lib/libposixpath.tex (working copy) @@ -235,16 +235,33 @@ \versionadded{1.3} \end{funcdesc} -\begin{funcdesc}{splitext}{path} +\begin{funcdesc}{splitext}{path\optional{, ignore_leading_dot=False}\optional{, all_ext=False}} Split the pathname \var{path} into a pair \code{(\var{root}, \var{ext})} such that \code{\var{root} + \var{ext} == \var{path}}, -and \var{ext} is empty or begins with a period and contains -at most one period. Leading periods on the basename are -ignored; \code{\var{splitext}.('.cshrc')} returns -\code{('.cshrc', '')}. +and \var{ext} is empty or begins with a period. -\versionchanged[Earlier versions could produce an empty root when -the only period was the first character]{2.6} +If \var{all_ext} is False, then the extension will be considered the +last period in the \var{path}. Otherwise, it will be the first period +and the result may have multiple periods within it. + +For example, if True \code{\var{splitext}.('python.tar.gz)} returns +\code{('python', '.tar.gz')}, otherwise it returns +\code{('python.tar', '.gz)} + +If ignore_leading_dot is True, then if the first character in \var{path} +is a period then it will be ignored for the purposes of determining +where the extension is. If a file has only one period at the beginning +of the line, then ignore_leading_dot of True will cause the full +\var{path} to be returned with an empty extension. + +For example, if True \code{\var{splitext}.('.cshrc')} returns +\code{('.cshrc', ''). If False, it returns \code{('', '.cshrc')}}. + +\warning{The default for ignore_leading_dot will change to True in +the future, and a FutureWarning will be emitted until then.} + +\versionchanged[Earlier versions acted as if both all_ext and +ignore_leading_dot were always False.]{2.6} \end{funcdesc} \begin{funcdesc}{splitunc}{path} Index: Lib/genericpath.py =================================================================== --- Lib/genericpath.py (revision 54408) +++ Lib/genericpath.py (working copy) @@ -5,6 +5,7 @@ """ import os import stat +import warnings __all__ = ['commonprefix', 'exists', 'getatime', 'getctime', 'getmtime', 'getsize', 'isdir', 'isfile'] @@ -83,24 +84,58 @@ # Generic implementation of splitext, to be parametrized with # the separators -def _splitext(p, sep, altsep, extsep): +def _splitext(p, sep, altsep, extsep, ignore_leading_dot=None, all_ext=False): """Split the extension from a pathname. - Extension is everything from the last dot to the end, ignoring - leading dots. Returns "(root, ext)"; ext may be empty.""" + The root of the pathname is everything from the beginning of the string + to the extension marker. + + If all_ext is False then the extension marker is the last dot in the + filename, if any. Otherwise it is the first dot. + + If ignore_leading_dot is True, then a dot that occurs as the first + character in the filename is ignored for the purposes of determining + the extension marker. If False, then a filename that begins with a + dot will have an empty root. + Returns "(root, ext)", either of which may be empty.""" + + if not p or extsep not in p: + return p, '' + sepIndex = p.rfind(sep) if altsep: altsepIndex = p.rfind(altsep) sepIndex = max(sepIndex, altsepIndex) - + dotIndex = p.rfind(extsep) + if dotIndex > sepIndex: - # skip all leading dots filenameIndex = sepIndex + 1 + + if filenameIndex == dotIndex: + if ignore_leading_dot: + return p, '' + else: + if ignore_leading_dot is None: + warnings.warn( + "The default for ignore_leading_dot in splitext will " + "change to True in the future.", FutureWarning, + stacklevel=3 + ) + return p[:sepIndex+1], p[sepIndex+1:] + + if all_ext: + dotIndex = p.find(extsep, filenameIndex) + if filenameIndex == dotIndex: + if not ignore_leading_dot: + return p[:sepIndex+1], p[sepIndex+1:] + else: + dotIndex = p.find(extsep, dotIndex+1) + while filenameIndex < dotIndex: if p[filenameIndex] != extsep: return p[:dotIndex], p[dotIndex:] filenameIndex += 1 - + return p, '' Index: Lib/posixpath.py =================================================================== --- Lib/posixpath.py (revision 54408) +++ Lib/posixpath.py (working copy) @@ -88,8 +88,8 @@ # pathname component; the root is everything before that. # It is always true that root + ext == p. -def splitext(p): - return genericpath._splitext(p, sep, altsep, extsep) +def splitext(p, **kwargs): + return genericpath._splitext(p, sep, altsep, extsep, **kwargs) splitext.__doc__ = genericpath._splitext.__doc__ # Split a pathname into a drive specification and the rest of the Index: Lib/ntpath.py =================================================================== --- Lib/ntpath.py (revision 54408) +++ Lib/ntpath.py (working copy) @@ -182,8 +182,8 @@ # pathname component; the root is everything before that. # It is always true that root + ext == p. -def splitext(p): - return genericpath._splitext(p, sep, altsep, extsep) +def splitext(p, **kwargs): + return genericpath._splitext(p, sep, altsep, extsep, **kwargs) splitext.__doc__ = genericpath._splitext.__doc__ Index: Lib/macpath.py =================================================================== --- Lib/macpath.py (revision 54408) +++ Lib/macpath.py (working copy) @@ -69,8 +69,8 @@ return path, file -def splitext(p): - return genericpath._splitext(p, sep, altsep, extsep) +def splitext(p, **kwargs): + return genericpath._splitext(p, sep, altsep, extsep, **kwargs) splitext.__doc__ = genericpath._splitext.__doc__ def splitdrive(p): Index: Lib/test/test_ntpath.py =================================================================== --- Lib/test/test_ntpath.py (revision 54408) +++ Lib/test/test_ntpath.py (working copy) @@ -1,4 +1,5 @@ import ntpath +import warnings from test.test_support import verbose, TestFailed import os @@ -18,7 +19,6 @@ tester('ntpath.splitext("foo.ext")', ('foo', '.ext')) tester('ntpath.splitext("/foo/foo.ext")', ('/foo/foo', '.ext')) -tester('ntpath.splitext(".ext")', ('.ext', '')) tester('ntpath.splitext("\\foo.ext\\foo")', ('\\foo.ext\\foo', '')) tester('ntpath.splitext("foo.ext\\")', ('foo.ext\\', '')) tester('ntpath.splitext("")', ('', '')) @@ -27,6 +27,21 @@ tester('ntpath.splitext("xx\\foo.bar.ext")', ('xx\\foo.bar', '.ext')) tester('ntpath.splitext("c:a/b\\c.d")', ('c:a/b\\c', '.d')) +warnings.filterwarnings("ignore", ".*ignore_leading_dot.*") +tester('ntpath.splitext(".ext")', ('', '.ext')) +tester('ntpath.splitext(".ext", ignore_leading_dot=False)', ('', '.ext')) +tester('ntpath.splitext(".ext", ignore_leading_dot=True)', ('.ext', '')) +tester('ntpath.splitext("foo.tar.gz")', ('foo.tar', '.gz')) +tester('ntpath.splitext("foo.tar.gz", all_ext=True)', ('foo', '.tar.gz')) +tester('ntpath.splitext("foo.tar.gz", all_ext=False)', ('foo.tar', '.gz')) +tester('ntpath.splitext(".foo.tar.gz")', ('.foo.tar', '.gz')) +tester('ntpath.splitext(".foo.tar.gz", all_ext=False)', ('.foo.tar', '.gz')) +tester('ntpath.splitext(".foo.tar.gz", all_ext=True)', ('', '.foo.tar.gz')) +tester('ntpath.splitext(".foo.tar.gz", all_ext=False, ignore_leading_dot=True)', ('.foo.tar', '.gz')) +tester('ntpath.splitext(".foo.tar.gz", all_ext=False, ignore_leading_dot=False)', ('.foo.tar', '.gz')) +tester('ntpath.splitext(".foo.tar.gz", all_ext=True, ignore_leading_dot=True)', ('.foo', '.tar.gz')) +warnings.resetwarnings() + tester('ntpath.splitdrive("c:\\foo\\bar")', ('c:', '\\foo\\bar')) tester('ntpath.splitunc("\\\\conky\\mountpoint\\foo\\bar")', Index: Lib/test/test_macpath.py =================================================================== --- Lib/test/test_macpath.py (revision 54408) +++ Lib/test/test_macpath.py (working copy) @@ -1,8 +1,8 @@ import macpath from test import test_support import unittest +import warnings - class MacPathTestCase(unittest.TestCase): def test_abspath(self): @@ -48,12 +48,21 @@ splitext = macpath.splitext self.assertEquals(splitext(":foo.ext"), (':foo', '.ext')) self.assertEquals(splitext("foo:foo.ext"), ('foo:foo', '.ext')) - self.assertEquals(splitext(".ext"), ('.ext', '')) self.assertEquals(splitext("foo.ext:foo"), ('foo.ext:foo', '')) self.assertEquals(splitext(":foo.ext:"), (':foo.ext:', '')) self.assertEquals(splitext(""), ('', '')) self.assertEquals(splitext("foo.bar.ext"), ('foo.bar', '.ext')) + warnings.filterwarnings("error", ".*ignore_leading_dot.*") + self.assertRaises(FutureWarning, splitext, '.cshrc') + self.assertRaises(TypeError, splitext) + self.assertRaises(TypeError, splitext, ".cshrc", some_arbitrary_keyword=True) + warnings.resetwarnings() + warnings.filterwarnings("ignore", ".*ignore_leading_dot.*") + self.assertEquals(splitext(".ext"), ('', '.ext')) + self.assertEquals(splitext(".ext", ignore_leading_dot=False), ('', '.ext')) + self.assertEquals(splitext(".ext", ignore_leading_dot=True), ('.ext', '')) + warnings.resetwarnings() def test_main(): test_support.run_unittest(MacPathTestCase) Index: Lib/test/test_posixpath.py =================================================================== --- Lib/test/test_posixpath.py (revision 54408) +++ Lib/test/test_posixpath.py (working copy) @@ -2,6 +2,7 @@ from test import test_support import posixpath, os +import warnings from posixpath import realpath, abspath, join, dirname, basename # An absolute path to a temporary filename for testing. We can't rely on TESTFN @@ -57,29 +58,68 @@ self.assertRaises(TypeError, posixpath.split) - def splitextTest(self, path, filename, ext): - self.assertEqual(posixpath.splitext(path), (filename, ext)) - self.assertEqual(posixpath.splitext("/" + path), ("/" + filename, ext)) - self.assertEqual(posixpath.splitext("abc/" + path), ("abc/" + filename, ext)) - self.assertEqual(posixpath.splitext("abc.def/" + path), ("abc.def/" + filename, ext)) - self.assertEqual(posixpath.splitext("/abc.def/" + path), ("/abc.def/" + filename, ext)) - self.assertEqual(posixpath.splitext(path + "/"), (filename + ext + "/", "")) + def splitextTest(self, path, filename, ext, **kwargs): + self.assertEqual(posixpath.splitext(path, **kwargs), (filename, ext)) + self.assertEqual(posixpath.splitext("/" + path, **kwargs), ("/" + filename, ext)) + self.assertEqual(posixpath.splitext("abc/" + path, **kwargs), ("abc/" + filename, ext)) + self.assertEqual(posixpath.splitext("abc.def/" + path, **kwargs), ("abc.def/" + filename, ext)) + self.assertEqual(posixpath.splitext("/abc.def/" + path, **kwargs), ("/abc.def/" + filename, ext)) + self.assertEqual(posixpath.splitext(path + "/", **kwargs), (filename + ext + "/", "")) def test_splitext(self): self.splitextTest("foo.bar", "foo", ".bar") + + # just making sure no combination of the new options leads to basic behavior breaking + self.splitextTest("foo.bar", "foo", ".bar", ignore_leading_dot=False) + self.splitextTest("foo.bar", "foo", ".bar", ignore_leading_dot=True) + self.splitextTest("foo.bar", "foo", ".bar", all_ext=False) + self.splitextTest("foo.bar", "foo", ".bar", all_ext=True) + self.splitextTest("foo.bar", "foo", ".bar", all_ext=True, ignore_leading_dot=True) + self.splitextTest("foo.bar", "foo", ".bar", all_ext=True, ignore_leading_dot=False) + self.splitextTest("foo.bar", "foo", ".bar", all_ext=False, ignore_leading_dot=True) + self.splitextTest("foo.boo.bar", "foo.boo", ".bar") self.splitextTest("foo.boo.biff.bar", "foo.boo.biff", ".bar") + self.splitextTest("foo.boo.bar", "foo.boo", ".bar", all_ext=False) + self.splitextTest("foo.boo.bar", "foo.boo", ".bar", ignore_leading_dot=False) + self.splitextTest("foo.boo.bar", "foo.boo", ".bar", ignore_leading_dot=True) + self.splitextTest("foo.boo.biff.bar", "foo.boo.biff", ".bar", all_ext=False) + self.splitextTest("foo.boo.bar", "foo", ".boo.bar", all_ext=True) + self.splitextTest("foo.boo.biff.bar", "foo", ".boo.biff.bar", all_ext=True) + self.splitextTest("foo.boo.biff.bar", "foo", ".boo.biff.bar", all_ext=True, ignore_leading_dot=True) + self.splitextTest("foo.boo.biff.bar", "foo", ".boo.biff.bar", all_ext=True, ignore_leading_dot=False) + self.splitextTest(".csh.rc", ".csh", ".rc") self.splitextTest("nodots", "nodots", "") - self.splitextTest(".cshrc", ".cshrc", "") self.splitextTest("...manydots", "...manydots", "") self.splitextTest("...manydots.ext", "...manydots", ".ext") - self.splitextTest(".", ".", "") self.splitextTest("..", "..", "") self.splitextTest("........", "........", "") self.splitextTest("", "", "") self.assertRaises(TypeError, posixpath.splitext) + warnings.filterwarnings("error", ".*ignore_leading_dot.*") + self.assertRaises(FutureWarning, posixpath.splitext, '.cshrc') + warnings.resetwarnings() + + warnings.filterwarnings("ignore", ".*ignore_leading_dot.*") + self.splitextTest(".cshrc", "", ".cshrc") + self.splitextTest(".", "", ".") + + self.splitextTest(".cshrc", "", ".cshrc", ignore_leading_dot=False) + self.splitextTest(".cshrc", ".cshrc", "", ignore_leading_dot=True) + + self.splitextTest(".hidden.tar.gz", ".hidden.tar", ".gz") + self.splitextTest(".hidden.tar.gz", "", ".hidden.tar.gz", all_ext=True) + self.splitextTest(".hidden.tar.gz", ".hidden.tar", ".gz", ignore_leading_dot=False) + self.splitextTest(".hidden.tar.gz", ".hidden.tar", ".gz", ignore_leading_dot=True) + self.splitextTest(".hidden.tar.gz", ".hidden.tar", ".gz", ignore_leading_dot=False, all_ext=False) + self.splitextTest(".hidden.tar.gz", ".hidden", ".tar.gz", ignore_leading_dot=True, all_ext=True) + + self.assertRaises(TypeError, posixpath.splitext, "filename.ext", True) + self.assertRaises(TypeError, posixpath.splitext, ".cshrc", some_arbitrary_keyword=True) + warnings.resetwarnings() + def test_isabs(self): self.assertIs(posixpath.isabs(""), False) self.assertIs(posixpath.isabs("/"), True)