Index: Doc/lib/libposixpath.tex =================================================================== --- Doc/lib/libposixpath.tex (revision 54408) +++ Doc/lib/libposixpath.tex (working copy) @@ -235,16 +235,25 @@ \versionadded{1.3} \end{funcdesc} -\begin{funcdesc}{splitext}{path} +\begin{funcdesc}{splitext}{path\optional{, preserve_dotfiles=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', '')}. +at most one period. -\versionchanged[Earlier versions could produce an empty root when -the only period was the first character]{2.6} +If preserve_dotfiles is True, then an input which has only a single dot +at the beginning of the path will include that dot in the root and +return a blank extension. Otherwise, the entire filename will be treated +as an extension with a blank root. + +For example, if True \code{\var{splitext}.('.cshrc')} returns +\code{('.cshrc', ''). If False, it returns \code{('', .cshrc)}}. + +\warning{The default for preserve_dotfiles will change to True in +the future, and a FutureWarning will be emitted until then.} + +\versionchanged[Earlier versions acted as if preserve_dotfiles was 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,11 +84,19 @@ # Generic implementation of splitext, to be parametrized with # the separators -def _splitext(p, sep, altsep, extsep): +def _splitext(p, sep, altsep, extsep, **kwargs): """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.""" + leading dots. If preserve_dotfile is False then a pathname with + only one dot at its start will have an empty root, otherwise + the initial dot will be ignored and the ext will be empty. + Returns "(root, ext)"; ext may always be empty.""" + + preserve_dotfiles = kwargs.pop("preserve_dotfiles", None) + + if kwargs: + raise TypeError("splitext takes at most only one keyword argument.") sepIndex = p.rfind(sep) if altsep: @@ -95,9 +104,21 @@ sepIndex = max(sepIndex, altsepIndex) dotIndex = p.rfind(extsep) + if dotIndex > sepIndex: - # skip all leading dots filenameIndex = sepIndex + 1 + if filenameIndex == dotIndex: + if preserve_dotfiles: + return p, '' + else: + if preserve_dotfiles is None: + warnings.warn( + "The default for preserve_dotfiles in splitext will " + "change to True in the future.", FutureWarning, + stacklevel=3 + ) + return p[:dotIndex], p[dotIndex:] + while filenameIndex < dotIndex: if p[filenameIndex] != extsep: return p[:dotIndex], p[dotIndex:] 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,12 @@ 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", ".*preserve_dotfiles.*") +tester('ntpath.splitext(".ext")', ('', '.ext')) +tester('ntpath.splitext(".ext", preserve_dotfiles=False)', ('', '.ext')) +tester('ntpath.splitext(".ext", preserve_dotfiles=True)', ('.ext', '')) +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,22 @@ 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", ".*preserve_dotfiles.*") + self.assertRaises(FutureWarning, splitext, '.cshrc') + self.assertRaises(TypeError, splitext) + self.assertRaises(TypeError, splitext, ".cshrc", some_arbitrary_keyword=True) + warnings.resetwarnings() + warnings.filterwarnings("ignore", ".*preserve_dotfiles.*") + self.assertEquals(splitext(".ext"), ('', '.ext')) + self.assertEquals(splitext(".ext", preserve_dotfiles=False), ('', '.ext')) + self.assertEquals(splitext(".ext", preserve_dotfiles=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,13 +58,13 @@ 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") @@ -71,15 +72,28 @@ self.splitextTest("foo.boo.biff.bar", "foo.boo.biff", ".bar") 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", ".*preserve_dotfiles.*") + self.assertRaises(FutureWarning, posixpath.splitext, '.cshrc') + warnings.resetwarnings() + + warnings.filterwarnings("ignore", ".*preserve_dotfiles.*") + self.splitextTest(".cshrc", "", ".cshrc") + self.splitextTest(".", "", ".") + + self.splitextTest(".cshrc", "", ".cshrc", preserve_dotfiles=False) + self.splitextTest(".cshrc", ".cshrc", "", preserve_dotfiles=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)