[Jython-checkins] jython: Improve os.chdir for Windows, partially addressing #2117
jeff.allen
jython-checkins at python.org
Sat Mar 15 10:00:31 CET 2014
http://hg.python.org/jython/rev/f553f6a07fb5
changeset: 7192:f553f6a07fb5
user: Jeff Allen <ja.py at farowl.co.uk>
date: Sat Mar 08 17:49:48 2014 +0000
summary:
Improve os.chdir for Windows, partially addressing #2117
Corrects ntpath.abspath based on CPython 2.7.6 but using PySystemState.getPath;
adds logic to maximise use of native wit behind java.io.File for Windows.
files:
Lib/ntpath.py | 26 +-
Lib/test/test_chdir.py | 58 ++++-
src/org/python/core/PySystemState.java | 144 ++++++++++++-
3 files changed, 194 insertions(+), 34 deletions(-)
diff --git a/Lib/ntpath.py b/Lib/ntpath.py
--- a/Lib/ntpath.py
+++ b/Lib/ntpath.py
@@ -452,23 +452,25 @@
from nt import _getfullpathname
except ImportError: # not running on Windows - mock up something sensible
- import java.io.File
- from org.python.core.Py import newString
def abspath(path):
"""Return the absolute version of a path."""
- if not isabs(path):
+ try:
if isinstance(path, unicode):
- cwd = os.getcwdu()
+ if path:
+ path = sys.getPath(path)
+ else:
+ # Empty path must return current working directory
+ path = os.getcwdu()
else:
- cwd = os.getcwd()
- path = join(cwd, path)
- if not splitunc(path)[0] and not splitdrive(path)[0]:
- # cwd lacks a UNC mount point, so it should have a drive
- # letter (but lacks one): determine it
- canon_path = newString(java.io.File(path).getCanonicalPath())
- drive = splitdrive(canon_path)[0]
- path = join(drive, path)
+ if path:
+ path = sys.getPath(path).encode('latin-1')
+ else:
+ # Empty path must return current working directory
+ path = os.getcwd()
+ except EnvironmentError:
+ pass # Bad path - return unchanged.
+
return normpath(path)
else: # use native Windows method on Windows
diff --git a/Lib/test/test_chdir.py b/Lib/test/test_chdir.py
--- a/Lib/test/test_chdir.py
+++ b/Lib/test/test_chdir.py
@@ -194,11 +194,39 @@
self.assertEqual(os.getcwd(), os.path.realpath(dos_name))
def test_windows_getcwd_ensures_drive_letter(self):
- drive = os.path.splitdrive(self.subdir)[0]
- os.chdir('\\')
+ # subdir is in the TEMP directory, usually on C:, while the
+ # current working directory could be (for the sake of comments)
+ # D:\HOME . TEMP and HOME stand for arbitrarily long relative paths.
+
+ # Check chdir to \ occurs without change of drive letter.
+ drive0, sub0 = os.path.splitdrive(os.getcwd()) # d:, HOME
+ os.chdir('\\') # d:\
+ self.assertEqual(os.path.normcase(os.getcwd()),
+ os.path.normcase(os.path.join(drive0, '\\')))
+
+ # Check chdir to HOME occurs without change of drive letter.
+ os.chdir(sub0) # d:\HOME
+ self.assertEqual(os.path.normcase(os.getcwd()),
+ os.path.normcase(os.path.join(drive0, sub0)))
+
+ # Check chdir to path with drive letter, changes drive in cwd.
+ drive, sub = os.path.splitdrive(self.subdir) # c:, TEMP\Program Files
+ os.chdir(self.subdir) # c:\TEMP\Program Files
+ self.assertEqual(os.path.normcase(os.getcwd()),
+ os.path.normcase(os.path.join(drive, sub)))
+
+ # Check chdir to \ occurs without change of drive letter (again).
+ os.chdir('\\') # c:\
self.assertEqual(os.path.normcase(os.getcwd()),
os.path.normcase(os.path.join(drive, '\\')))
+ if drive.upper() != drive0.upper():
+ # Check chdir to (different) original drive takes us to previous directory too.
+ # You only get this test if the temp directory and cwd are on different drives.
+ os.chdir(drive0) # d:\HOME
+ self.assertEqual(os.path.normcase(os.getcwd()),
+ os.path.normcase(os.path.join(drive0, sub0)))
+
def test_windows_chdir_slash_isabs(self):
drive = os.path.splitdrive(os.getcwd())[0]
os.chdir('/')
@@ -702,17 +730,19 @@
def test_main():
- tests = [ChdirTestCase,
- ImportTestCase,
- ImportPackageTestCase,
- ZipimportTestCase,
- PyCompileTestCase,
- ExecfileTestCase,
- ExecfileTracebackTestCase,
- ListdirTestCase,
- DirsTestCase,
- FilesTestCase,
- SymlinkTestCase]
+ tests = [
+ ChdirTestCase,
+ ImportTestCase,
+ ImportPackageTestCase,
+ ZipimportTestCase,
+ PyCompileTestCase,
+ ExecfileTestCase,
+ ExecfileTracebackTestCase,
+ ListdirTestCase,
+ DirsTestCase,
+ FilesTestCase,
+ SymlinkTestCase
+ ]
if WINDOWS:
tests.append(WindowsChdirTestCase)
tests.remove(SymlinkTestCase) # os.symlink ... Availability: Unix.
@@ -720,8 +750,10 @@
if test_support.is_jython:
tests.extend((ImportJavaClassTestCase,
ImportJarTestCase))
+
if test_support.is_resource_enabled('subprocess'):
tests.append(SubprocessTestCase)
+
test_support.run_unittest(*tests)
diff --git a/src/org/python/core/PySystemState.java b/src/org/python/core/PySystemState.java
--- a/src/org/python/core/PySystemState.java
+++ b/src/org/python/core/PySystemState.java
@@ -546,22 +546,148 @@
}
private static String getPath(PySystemState sys, String path) {
- if (path == null) {
- return path;
+ if (path != null) {
+ path = getFile(sys, path).getAbsolutePath();
}
+ return path;
+ }
+ /**
+ * Resolve a path, returning a {@link File}, taking the current working directory into account.
+ *
+ * @param path a path <code>String</code>
+ * @return a resolved <code>File</code>
+ */
+ public File getFile(String path) {
+ return getFile(this, path);
+ }
+
+ /**
+ * Resolve a path, returning a {@link File}, taking the current working directory of the
+ * specified <code>PySystemState</code> into account. Use of a <code>static</code> here is a
+ * trick to avoid getting the current state if the path is absolute. (Noted that this may be
+ * needless optimisation.)
+ *
+ * @param sys a <code>PySystemState</code> or null meaning the current one
+ * @param path a path <code>String</code>
+ * @return a resolved <code>File</code>
+ */
+ private static File getFile(PySystemState sys, String path) {
File file = new File(path);
- // Python considers r'\Jython25' and '/Jython25' abspaths on Windows, unlike
- // java.io.File
- if (!file.isAbsolute()
- && (!Platform.IS_WINDOWS || !(path.startsWith("\\") || path.startsWith("/")))) {
+ if (!file.isAbsolute()) {
+ // path meaning depends on the current working directory
if (sys == null) {
sys = Py.getSystemState();
}
- file = new File(sys.getCurrentWorkingDir(), path);
+ String cwd = sys.getCurrentWorkingDir();
+ if (Platform.IS_WINDOWS) {
+ // Form absolute reference (with mysterious Windows semantics)
+ file = getWindowsFile(cwd, path);
+ } else {
+ // Form absolute reference (with single slash)
+ file = new File(cwd, path);
+ }
}
- // This needs to be performed always to trim trailing backslashes on Windows
- return file.getPath();
+ return file;
+ }
+
+ /**
+ * Resolve a relative path against the supplied current working directory or Windows environment
+ * working directory for any drive specified in the path. and return a file object. Essentially
+ * equivalent to os.path.join, but the work is done by {@link File}. The intention is that
+ * calling {@link File#getAbsolutePath()} should return the corresponding absolute path.
+ * <p>
+ * Note: in the context where we use this method, <code>path</code> is already known not to be
+ * absolute, and <code>cwd</code> is assumed to be absolute.
+ *
+ * @param cwd current working directory (of some {@link PySystemState})
+ * @param path to resolve
+ * @return specifier of the intended file
+ */
+ private static File getWindowsFile(String cwd, String path) {
+ // Assumptions: cwd is absolute and path is not absolute
+
+ // Start by getting the slashes the right (wrong) way round.
+ if (path.indexOf('/') >= 0) {
+ path = path.replace('/', '\\');
+ }
+
+ // Does path start with a drive letter?
+ char d = driveLetter(path);
+ if (d != 0) {
+ if (d == driveLetter(cwd)) {
+ /*
+ * path specifies the same drive letter as in the cwd of this PySystemState. Let
+ * File interpret the rest of the path relative to cwd as parent.
+ */
+ return new File(cwd, path.substring(2));
+ } else {
+ // Let File resolve the specified drive against the process environment.
+ return new File(path);
+ }
+
+ } else if (path.startsWith("\\")) {
+ // path replaces the file part of the cwd. (Method assumes path is not UNC.)
+ if (driveLetter(cwd) != 0) {
+ // cwd has a drive letter
+ return new File(cwd.substring(0, 2), path);
+ } else {
+ // cwd has no drive letter, so should be a UNC path \\host\share\directory\etc
+ return new File(uncShare(cwd), path);
+ }
+
+ } else {
+ // path is relative to the cwd of this PySystemState.
+ return new File(cwd, path);
+ }
+ }
+
+ /**
+ * Return the Windows drive letter from the start of the path, upper case, or 0 if
+ * the path does not start X: where X is a letter.
+ *
+ * @param path to examine
+ * @return drive letter or char 0 if no drive letter
+ */
+ private static char driveLetter(String path) {
+ if (path.length() >= 2 && path.charAt(1) == ':') {
+ // Looks like both strings start with a drive letter
+ char pathDrive = path.charAt(0);
+ if (Character.isLetter(pathDrive)) {
+ return Character.toUpperCase(pathDrive);
+ }
+ }
+ return (char)0;
+ }
+
+ /**
+ * Return the Windows UNC share name from the start of the path, or <code>null</code> if the
+ * path is not of Windows UNC type. The path has to be formed with Windows-backslashes:
+ * slashes '/' are not accepted as a substitute here.
+ *
+ * @param path to examine
+ * @return share name or null
+ */
+ private static String uncShare(String path) {
+ int n = path.length();
+ // Has to accommodate at least \\A (3 chars)
+ if (n >= 5 && path.startsWith("\\\\")) {
+ // Look for the separator backslash A\B
+ int p = path.indexOf('\\', 2);
+ // Has to be at least index 3 (path begins \\A) and 2 more characters left \B
+ if (p >= 3 && n > p + 2) {
+ // Look for directory backslash that ends the share name
+ int dir = path.indexOf('\\', p + 1);
+ if (dir < 0) {
+ // path has the form \\A\B (is just the share name)
+ return path;
+ } else if (dir > p + 1) {
+ // path has the form \\A\B\C
+ return path.substring(0, dir);
+ }
+ }
+ }
+ return null;
}
public void callExitFunc() throws PyIgnoreMethodTag {
--
Repository URL: http://hg.python.org/jython
More information about the Jython-checkins
mailing list