From 49c91d74aea5326a70fdebbac6f9d2c88a4abb63 Mon Sep 17 00:00:00 2001 From: Martin Craig <martin.craig@eng.ox.ac.uk> Date: Wed, 22 Apr 2020 13:53:28 +0100 Subject: [PATCH] Moved fslpath and winpath from fsl.utils.platform to fsl.utils.path Also minor bugfix - allow forward or backslash after \\wsl$ when specifying FSLDIR as a WSL network share --- fsl/utils/path.py | 55 +++++++++++++++++++++++++++++++++++ fsl/utils/platform.py | 56 +----------------------------------- fsl/utils/run.py | 11 ++++--- tests/test_fsl_utils_path.py | 17 +++++++++++ tests/test_platform.py | 17 ----------- 5 files changed, 78 insertions(+), 78 deletions(-) diff --git a/fsl/utils/path.py b/fsl/utils/path.py index 8d312bef5..7f7f80cf0 100644 --- a/fsl/utils/path.py +++ b/fsl/utils/path.py @@ -30,7 +30,9 @@ import os.path as op import os import glob import operator +import re +from fsl.utils.platform import platform class PathError(Exception): """``Exception`` class raised by the functions defined in this module @@ -524,3 +526,56 @@ def commonBase(paths): return base raise PathError('No common base') + +def wslpath(winpath): + """ + Convert Windows path (or a command line argument containing a Windows path) + to the equivalent WSL path (e.g. ``c:\\Users`` -> ``/mnt/c/Users``). Also supports + paths in the form ``\\wsl$\\(distro)\\users\\...`` + + :param winpath: Command line argument which may (or may not) contain a Windows path. It is assumed to be + either of the form <windows path> or --<arg>=<windows path>. Note that we don't need to + handle --arg <windows path> or -a <windows path> since in these cases the argument + and the path will be parsed as separate entities. + :return: If ``winpath`` matches a Windows path, the converted argument (including the --<arg>= portion). + Otherwise returns ``winpath`` unchanged. + """ + match = re.match(r"^(--[\w-]+=)?\\\\wsl\$[\\\/][^\\^\/]+(.*)$", winpath) + if match: + arg, path = match.group(1, 2) + if arg is None: + arg = "" + return arg + path.replace("\\", "/") + + match = re.match(r"^(--[\w-]+=)?([a-zA-z]):(.+)$", winpath) + if match: + arg, drive, path = match.group(1, 2, 3) + if arg is None: + arg = "" + return arg + "/mnt/" + drive.lower() + path.replace("\\", "/") + + return winpath + +def winpath(wslpath): + """ + Convert a WSL-local filepath (for example ``/usr/local/fsl/``) into a path that can be used from + Windows. + + If ``self.fslwsl`` is ``False``, simply returns ``wslpath`` unmodified + Otherwise, uses ``FSLDIR`` to deduce the WSL distro in use for FSL. + This requires WSL2 which supports the ``\\wsl$\`` network path. + wslpath is assumed to be an absolute path. + """ + if not platform.fslwsl: + return wslpath + else: + match = re.match(r"^\\\\wsl\$\\([^\\]+).*$", platform.fsldir) + if match: + distro = match.group(1) + else: + distro = None + + if not distro: + raise RuntimeError("Could not identify WSL installation from FSLDIR (%s)" % platform.fsldir) + + return "\\\\wsl$\\" + distro + wslpath.replace("/", "\\") diff --git a/fsl/utils/platform.py b/fsl/utils/platform.py index 173b031cb..73d0937d2 100644 --- a/fsl/utils/platform.py +++ b/fsl/utils/platform.py @@ -17,7 +17,6 @@ import os import os.path as op import sys import importlib -import re import fsl.utils.notifier as notifier @@ -294,7 +293,7 @@ class Platform(notifier.Notifier): @property def fslwsl(self): """Boolean flag indicating whether FSL is installed in Windows Subsystem for Linux """ - return self.fsldir is not None and self.fsldir.startswith("\\\\wsl$\\") + return self.fsldir is not None and self.fsldir.startswith("\\\\wsl$") @fsldir.setter def fsldir(self, value): @@ -405,59 +404,6 @@ class Platform(notifier.Notifier): """ return self.__glIsSoftware - def wslpath(self, winpath): - """ - Convert Windows path (or a command line argument containing a Windows path) - to the equivalent WSL path (e.g. ``c:\\Users`` -> ``/mnt/c/Users``). Also supports - paths in the form ``\\wsl$\\(distro)\\users\\...`` - - :param winpath: Command line argument which may (or may not) contain a Windows path. It is assumed to be - either of the form <windows path> or --<arg>=<windows path>. Note that we don't need to - handle --arg <windows path> or -a <windows path> since in these cases the argument - and the path will be parsed as separate entities. - :return: If ``winpath`` matches a Windows path, the converted argument (including the --<arg>= portion). - Otherwise returns ``winpath`` unchanged. - """ - match = re.match(r"^(--[\w-]+=)?\\\\wsl\$\\[^\\]+(\\.*)$", winpath) - if match: - arg, path = match.group(1, 2) - if arg is None: - arg = "" - return arg + path.replace("\\", "/") - - match = re.match(r"^(--[\w-]+=)?([a-zA-z]):(.+)$", winpath) - if match: - arg, drive, path = match.group(1, 2, 3) - if arg is None: - arg = "" - return arg + "/mnt/" + drive.lower() + path.replace("\\", "/") - - return winpath - - def winpath(self, wslpath): - """ - Convert a WSL-local filepath (for example ``/usr/local/fsl/``) into a path that can be used from - Windows. - - If ``self.fslwsl`` is ``False``, simply returns ``wslpath`` unmodified - Otherwise, uses ``FSLDIR`` to deduce the WSL distro in use for FSL. - This requires WSL2 which supports the ``\\wsl$\`` network path. - wslpath is assumed to be an absolute path. - """ - if not self.fslwsl: - return wslpath - else: - match = re.match(r"^\\\\wsl\$\\([^\\]+).*$", self.fsldir) - if match: - distro = match.group(1) - else: - distro = None - - if not distro: - raise RuntimeError("Could not identify WSL installation from FSLDIR (%s)" % self.fsldir) - - return "\\\\wsl$\\" + distro + wslpath.replace("/", "\\") - platform = Platform() """An instance of the :class:`Platform` class. Feel free to create your own instance, but be aware that if you do so you will not be updated of changes diff --git a/fsl/utils/run.py b/fsl/utils/run.py index 2523f50dc..acb77c741 100644 --- a/fsl/utils/run.py +++ b/fsl/utils/run.py @@ -35,7 +35,7 @@ import six from fsl.utils.platform import platform as fslplatform import fsl.utils.fslsub as fslsub import fsl.utils.tempdir as tempdir - +import fsl.utils.path as fslpath log = logging.getLogger(__name__) @@ -393,15 +393,15 @@ def wslcmd(cmdpath, *args): """ # Check if command exists in WSL (remembering that the command path may include FSLDIR which # is a Windows path) - cmdpath = fslplatform.wslpath(cmdpath) + cmdpath = fslpath.wslpath(cmdpath) retcode = sp.call(["wsl", "test", "-x", cmdpath]) if retcode == 0: # Form a new argument list and convert any Windows paths in it into WSL paths - wslargs = [fslplatform.wslpath(arg) for arg in args] + wslargs = [fslpath.wslpath(arg) for arg in args] wslargs[0] = cmdpath - local_fsldir = fslplatform.wslpath(fslplatform.fsldir) + local_fsldir = fslpath.wslpath(fslplatform.fsldir) if fslplatform.fsldevdir: - local_fsldevdir = fslplatform.wslpath(fslplatform.fsldevdir) + local_fsldevdir = fslpath.wslpath(fslplatform.fsldevdir) else: local_fsldevdir = None # Prepend important environment variables - note that it seems we cannot @@ -418,7 +418,6 @@ def wslcmd(cmdpath, *args): ] if local_fsldevdir: prepargs.append("FSLDEVDIR=%s" % local_fsldevdir) - return prepargs + wslargs else: # Command was not found in WSL with this path diff --git a/tests/test_fsl_utils_path.py b/tests/test_fsl_utils_path.py index d4cc1bec5..0361ced5e 100644 --- a/tests/test_fsl_utils_path.py +++ b/tests/test_fsl_utils_path.py @@ -13,6 +13,7 @@ import shutil import tempfile import pytest +import mock import fsl.utils.path as fslpath import fsl.data.image as fslimage @@ -1395,3 +1396,19 @@ def test_commonBase(): for ft in failtests: with pytest.raises(fslpath.PathError): fslpath.commonBase(ft) + +def test_wslpath(): + assert fslpath.wslpath('c:\\Users\\Fishcake\\image.nii.gz') == '/mnt/c/Users/Fishcake/image.nii.gz' + assert fslpath.wslpath('--input=x:\\transfers\\scratch\\image_2.nii') == '--input=/mnt/x/transfers/scratch/image_2.nii' + assert fslpath.wslpath('\\\\wsl$\\centos 7\\users\\fsl\\file.nii') == '/users/fsl/file.nii' + assert fslpath.wslpath('--file=\\\\wsl$\\centos 7\\home\\fsl\\img.nii.gz') == '--file=/home/fsl/img.nii.gz' + assert fslpath.wslpath('\\\\wsl$/centos 7/users\\fsl\\file.nii') == '/users/fsl/file.nii' + +def test_winpath(): + """ + See comment for ``test_fslwsl`` for why we are overwriting FSLDIR + """ + with mock.patch.dict('os.environ', **{ 'FSLDIR' : '\\\\wsl$\\my cool linux distro v2.0\\usr\\local\\fsl'}): + assert fslpath.winpath("/home/fsl/myfile.dat") == '\\\\wsl$\\my cool linux distro v2.0\\home\\fsl\\myfile.dat' + with mock.patch.dict('os.environ', **{ 'FSLDIR' : '/opt/fsl'}): + assert fslpath.winpath("/home/fsl/myfile.dat") == '/home/fsl/myfile.dat' diff --git a/tests/test_platform.py b/tests/test_platform.py index 46dad0ce5..9024bec82 100644 --- a/tests/test_platform.py +++ b/tests/test_platform.py @@ -226,20 +226,3 @@ def test_fslwsl(): with mock.patch.dict('os.environ', **{ 'FSLDIR' : '/usr/local/fsl'}): assert not p.fslwsl - -def test_wslpath(): - p = fslplatform.Platform() - assert p.wslpath('c:\\Users\\Fishcake\\image.nii.gz') == '/mnt/c/Users/Fishcake/image.nii.gz' - assert p.wslpath('--input=x:\\transfers\\scratch\\image_2.nii') == '--input=/mnt/x/transfers/scratch/image_2.nii' - assert p.wslpath('\\\\wsl$\\centos 7\\users\\fsl\\file.nii') == '/users/fsl/file.nii' - assert p.wslpath('--file=\\\\wsl$\\centos 7\\home\\fsl\\img.nii.gz') == '--file=/home/fsl/img.nii.gz' - -def test_winpath(): - """ - See comment for ``test_fslwsl`` - """ - p = fslplatform.Platform() - with mock.patch.dict('os.environ', **{ 'FSLDIR' : '\\\\wsl$\\my cool linux distro v2.0\\usr\\local\\fsl'}): - assert p.winpath("/home/fsl/myfile.dat") == '\\\\wsl$\\my cool linux distro v2.0\\home\\fsl\\myfile.dat' - with mock.patch.dict('os.environ', **{ 'FSLDIR' : '/opt/fsl'}): - assert p.winpath("/home/fsl/myfile.dat") == '/home/fsl/myfile.dat' -- GitLab