diff -r 253c31930b32 Doc/library/pathlib.rst --- a/Doc/library/pathlib.rst Sat Jul 26 00:58:55 2014 +0200 +++ b/Doc/library/pathlib.rst Sat Jul 26 09:48:56 2014 +0200 @@ -936,3 +936,16 @@ Remove this file or symbolic link. If the path points to a directory, use :func:`Path.rmdir` instead. + +.. method:: Path.expanduser() + + Return a new :class:`Path` with expanded ``~`` and ``~user`` constructs, + as returned by :meth:`os.path.expanduser`. :exc:`ValueError` is raised if + the path can't be expanded. + + >>> p = PosixPath('~/films/Monty Python') + >>> p.expanduser() + PosixPath('/home/eric/films/Monty Python') + + .. versionadded:: 3.5 + diff -r 253c31930b32 Lib/pathlib.py --- a/Lib/pathlib.py Sat Jul 26 00:58:55 2014 +0200 +++ b/Lib/pathlib.py Sat Jul 26 09:48:56 2014 +0200 @@ -885,6 +885,17 @@ return False return True + def expanduser(self): + """ Return a new path with expanded ~ and ~user constructs + (as returned by os.path.expanduser) + """ + normed = self._flavour.pathmod.expanduser(str(self)) + obj = self._from_parts((normed,), init=False) + obj._init(template=self) + if str(obj) == str(self): + raise ValueError("The path wasn't expanded.") + return obj + class PurePosixPath(PurePath): _flavour = _posix_flavour diff -r 253c31930b32 Lib/test/test_pathlib.py --- a/Lib/test/test_pathlib.py Sat Jul 26 00:58:55 2014 +0200 +++ b/Lib/test/test_pathlib.py Sat Jul 26 09:48:56 2014 +0200 @@ -1290,6 +1290,11 @@ p = self.cls('') self.assertEqual(p.stat(), os.stat('.')) + def test_expanduser(self): + p = self.cls('~') + self.assertEqual(os.path.expanduser(str(p)), + str(p.expanduser())) + def test_exists(self): P = self.cls p = P(BASE) @@ -1884,6 +1889,24 @@ self.assertEqual(given, expect) self.assertEqual(set(p.rglob("FILEd*")), set()) + def test_expanduser_posix(self): + support.import_module("pwd") + import pwd + pwdent = pwd.getpwuid(os.getuid()) + user = pwdent.pw_name + home = pwdent.pw_dir.rstrip("/") + + p1 = self.cls('~') + p2 = self.cls('~' + user) + p3 = self.cls('~/' + user) + p4 = self.cls('~{}/foo/bar'.format(user)) + p5 = self.cls('~/test/foo') + self.assertEqual(str(p1.expanduser()), home) + self.assertEqual(str(p2.expanduser()), home) + self.assertEqual(str(p3.expanduser()), os.path.join(home, user)) + self.assertEqual(str(p4.expanduser()), home + "/foo/bar") + self.assertEqual(str(p5.expanduser()), home + "/test/foo") + @only_nt class WindowsPathTest(_BasePathTest, unittest.TestCase): @@ -1899,6 +1922,44 @@ p = P(BASE, "dirC") self.assertEqual(set(p.rglob("FILEd")), { P(BASE, "dirC/dirD/fileD") }) + def test_expanduser_nt(self): + with support.EnvironmentVarGuard() as env: + env.pop('HOME', None) + env.pop('USERPROFILE', None) + env.pop('HOMEPATH', None) + + # test that the path returns unchanged + p1 = self.cls('~') + p2 = self.cls('~test') + p3 = self.cls('~test/foo/bar') + p4 = self.cls('~/test/foo') + + msg = "The path wasn't expanded." + for p in (p1, p2, p3, p4): + with self.assertRaisesRegex(ValueError, msg): + p.expanduser() + + # test the first lookup key in the env vars + env['HOME'] = 'C:\\test' + self.assertEqual(str(p1.expanduser()), 'C:\\test') + self.assertEqual(str(p2.expanduser()), 'C:\\test') + self.assertEqual(str(p3.expanduser()), 'C:\\test\\foo\\bar') + self.assertEqual(str(p4.expanduser()), 'C:\\test\\test\\foo') + + # test that HOMEPATH is available instead + env.pop('HOME', None) + env['HOMEPATH'] = 'C:\\idle' + self.assertEqual(str(p1.expanduser()), 'C:\\idle') + self.assertEqual(str(p2.expanduser()), 'C:\\test') + self.assertEqual(str(p3.expanduser()), 'C:\\test\\foo\\bar') + self.assertEqual(str(p4.expanduser()), 'C:\\idle\\test\\foo') + + env['HOMEDRIVE'] = 'C:\\' + env['HOMEPATH'] = 'eggs' + self.assertEqual(str(p1.expanduser()), 'C:\\eggs') + self.assertEqual(str(p2.expanduser()), 'C:\\test') + self.assertEqual(str(p3.expanduser()), 'C:\\test\\foo\\bar') + self.assertEqual(str(p4.expanduser()), 'C:\\eggs\\test\\foo') if __name__ == "__main__": unittest.main()