Repository: pyenv-win/pyenv-win (language: python)

Issue:
bug: pip command isn't working with space in username
**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
```
C:\Users\Aaron Goh>pyenv global
pyenv global
3.7.2

C:\Users\Aaron Goh>pyenv rehash
pyenv rehash

C:\Users\Aaron Goh>pip
pip
'Scripts' is not recognized as an internal or external command,
operable program or batch file.
```

**Desktop (please complete the following information):**
 - OS: Windows 10
 - Tools used: git bash

**Additional context**
Add any other context about the problem here.
Path variable
```
echo %PATH%
C:\Users\Aaron Goh\bin;C:\Program Files\Git\mingw64\bin;C:\Program Files\Git\usr\local\bin;C:\Program Files\Git\usr\bin;C:\Program Files\Git\usr\bin;C:\Program Files\Git\mingw64\bin;C:\Program Files\Git\usr\bin;C:\Users\Aaron Goh\bin;C:\Program Files\Microsoft MPI\Bin;C:\ProgramData\Oracle\Java\javapath;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\Program Files\PuTTY;C:\Program Files\Microsoft SQL Server\130\Tools\Binn;C:\Program Files\dotnet;C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\130\Tools\Binn;C:\Program Files (x86)\Microsoft SQL Server\130\Tools\Binn;C:\Program Files\Microsoft SQL Server\130\DTS\Binn;C:\Program Files (x86)\Microsoft SQL Server\Client SDK\ODBC\130\Tools\Binn;C:\Program Files (x86)\Microsoft SQL Server\140\Tools\Binn;C:\Program Files (x86)\Microsoft SQL Server\140\DTS\Binn;C:\Program Files (x86)\Microsoft SQL Server\140\Tools\Binn\ManagementStudio;C:\Program Files (x86)\Microsoft SQL Server\130\DTS\Binn;C:\WINDOWS\System32\OpenSSH;C:\Program Files\NVIDIA Corporation\NVIDIA NvDLISR;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0;C:\WINDOWS\System32\OpenSSH;C:\Program Files\Git\cmd;**C:\Users\Aaron Goh\.pyenv\pyenv-win\bin;C:\Users\Aaron Goh\.pyenv\pyenv-win\shims;C:\Users\Aaron Goh\.pyenv\pyenv-win\bin;C:\Users\Aaron Goh\.pyenv\pyenv-win\shims;C:\Users\Aaron Goh\.pyenv\pyenv-win\bin;C:\Users\Aaron Goh\.pyenv\pyenv-win\shims;**C:\Users\Aaron Goh\AppData\Local\Microsoft\WindowsApps;C:\Users\Aaron Goh\AppData\Local\Programs\MiKTeX\miktex\bin\x64;C:\Users\Aaron Goh\AppData\Local\Programs\Microsoft VS Code\bin;C:\Users\Aaron Goh\AppData\Local\GitHubDesktop\bin;C:\Program Files\Git\usr\bin\vendor_perl;C:\Program Files\Git\usr\bin\core_perl
```
Fix exec preferring the last version instead of the first
Fixes #305


Discussion / hints:
@kirankotari I will run all unit tests with spaces in the path to manage all impacts. Can't figure yet a proper solution to easily double check all unit tests with and without spaces. Maybe we should only test with spaces (if it works with spaces, we can be pretty sure all tests would also pass without spaces).
@kirankotari all tests pass when I put a space in pyenv dir. Problems lies in `pyenv exec` for modules in `Scripts` folder.

There is a first problem when prefixing command with bindir (quotes needed in `if exist`).
But afterwards, the cmdline executed is something like:
```
C:\Users\ded\a b\.pyenv\pyenv-win\versions\3.9.4\Scripts/pip list
```
And it should be:
```
"C:\Users\ded\a b\.pyenv\pyenv-win\versions\3.9.4\Scripts/pip" list
```
But I still need to find out how to fix this properly.

I think I have a fix for windows, but I can't get pyenv working fine in `git bash` with a space in username.

Starting to think we should not support spaces in usernames.
> I think I have a fix for windows, but I can't get pyenv working fine in `git bash` with a space in username.
> 
> Starting to think we should not support spaces in usernames.

We can't ask users to change their user profile to use this library right. I will have a look for git bash
Just as a confirmation, I ended up moving away from the windows username with a space and everything now works fine

Apparently, you cannot use your Microsoft user account for a windows 10 machine because it mandates your username to be a first name + last name which is unfortunate (confirmed from a microsoft support repr).... 

This could definitely be nice as a feature but it might not be a good use of your guy's time since I was finding that most applications that have no issues in unix that ported over to windows nearly all fail with a space in the directory which would have  meant I'm opening a pull request like this in nearly every single repo so I've just decided to just create a new user without spaces in the name. 

I'll leave the bug request open and let you guys decide what to best do
Using the Windows Short Name in my PATH variable (obtained using dir /x) allowed me to work through this problem.

Something like...

`C:\Users\Ryan Wendel\.pyenv\pyenv-win\bin`
`C:\Users\Ryan Wendel\.pyenv\pyenv-win\shims`

changed to look like

`C:\Users\RYANWE~1\.pyenv\pyenv-win\bin`
`C:\Users\RYANWE~1\.pyenv\pyenv-win\shims`


Fix patch:
diff --git a/README.md b/README.md
index b07995f2..da242324 100644
--- a/README.md
+++ b/README.md
@@ -223,6 +223,7 @@ Now follow the steps to "[finish the installation](#finish-the-installation)".
 ## Change Log
 
 ### New in 2.64.11
+- Fix [#259](https://github.com/pyenv-win/pyenv-win/issues/259): Correctly handle spaces in `pyenv` path
 - Fix [#305](https://github.com/pyenv-win/pyenv-win/issues/305): Fix `exec` preferring the last version listed in `.python-version` instead of the first.
 
 ### New in 2.64.10
diff --git a/pyenv-win/bin/pyenv.bat b/pyenv-win/bin/pyenv.bat
index 67093711..f2e58b33 100644
--- a/pyenv-win/bin/pyenv.bat
+++ b/pyenv-win/bin/pyenv.bat
@@ -2,20 +2,20 @@
 setlocal
 chcp 1250 >nul
 
-set "pyenv=cscript //nologo "%~dp0..\libexec\pyenv.vbs""
+set "__pyenv_pyenv=cscript //nologo "%~dp0..\libexec\pyenv.vbs""
 
 :: if 'pyenv' called alone, then run pyenv.vbs
 if [%1]==[] (
-  %pyenv%
+  %__pyenv_pyenv%
   exit /b
 )
 
 if /i [%1%2]==[version] call :check_path
 
-:: use pyenv.vbs to aid resolving absolute path of "active" version into 'bindir'
-set "bindir="
-set "extrapaths="
-for /f %%i in ('%pyenv% vname') do call :extrapath "%~dp0..\versions\%%i"
+:: use pyenv.vbs to aid resolving absolute path of "active" version into '__pyenv_bindir'
+set "__pyenv_bindir="
+set "__pyenv_extrapaths="
+for /f %%i in ('%__pyenv_pyenv% vname') do call :extrapath "%~dp0..\versions\%%i"
 
 :: all help implemented as plugin
 if /i [%2]==[--help] goto :plugin
@@ -30,13 +30,13 @@ if /i [%1]==[help] (
 )
 
 :: let pyenv.vbs handle these
-set commands=rehash global local version vname version-name versions commands shims which whence help --help
-for %%a in (%commands%) do (
+set "__pyenv_commands=rehash global local version vname version-name versions commands shims which whence help --help"
+for %%a in (%__pyenv_commands%) do (
   if /i [%1]==[%%a] (
     rem endlocal not really needed here since above commands do not set any variable
     rem endlocal closed automatically with exit
     rem no need to update PATH either
-    %pyenv% %*
+    %__pyenv_pyenv% %*
     exit /b
   )
 )
@@ -46,57 +46,57 @@ if /i not [%1]==[exec] goto :plugin
 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 :exec
 
-if not exist "%bindir%" (
+if not exist "%__pyenv_bindir%" (
   echo No global python version has been set yet. Please set the global version by typing:
   echo pyenv global 3.7.2
   exit /b
 )
 
-set cmdline=%*
-set cmdline=%cmdline:~5%
+set "__pyenv_cmdline=%*"
+set "__pyenv_cmdline=%__pyenv_cmdline:~5%"
 :: update PATH to active version and run command
 :: endlocal needed only if cmdline sets a variable: SET FOO=BAR
-set "path=%extrapaths%%path%"
-%cmdline%
+set "path=%__pyenv_extrapaths%%path%"
+%__pyenv_cmdline%
 endlocal
 exit /b
 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 :plugin
-set "exe=%~dp0..\libexec\pyenv-%1"
+set "__pyenv_exe=%~dp0..\libexec\pyenv-%1"
 rem TODO needed?
-call :normalizepath %exe% exe
+call :normalizepath %__pyenv_exe% __pyenv_exe
 
-if exist "%exe%.bat" (
-  set "exe=call "%exe%.bat""
+if exist "%__pyenv_exe%.bat" (
+  set "__pyenv_exe=call "%__pyenv_exe%.bat""
 
-) else if exist "%exe.cmd%" (
-  set "exe=call "%exe%.cmd""
+) else if exist "%__pyenv_exe%.cmd" (
+  set "__pyenv_exe=call "%__pyenv_exe%.cmd""
 
-) else if exist "%exe%.vbs" (
-  set "exe=cscript //nologo "%exe%.vbs""
+) else if exist "%__pyenv_exe%.vbs" (
+  set "__pyenv_exe=cscript //nologo "%__pyenv_exe%.vbs""
 
 ) else (
   echo pyenv: no such command '%1'
   exit /b 1
 )
 
-:: replace first arg with %exe%
-set cmdline=%*
-set cmdline=%cmdline:^=^^%
-set cmdline=%cmdline:!=^!%
-set arg1=%1
-set len=1
+:: replace first arg with %__pyenv_exe%
+set "__pyenv_cmdline=%*"
+set "__pyenv_cmdline=%__pyenv_cmdline:^=^^%"
+set "__pyenv_cmdline=%__pyenv_cmdline:!=^!%"
+set "__pyenv_arg1=%1"
+set "__pyenv_len=1"
 :loop_len
-set /a len=%len%+1
-set "arg1=%arg1:~1%"
-if not [%arg1%]==[] goto :loop_len
+set /a __pyenv_len=%__pyenv_len%+1
+set "__pyenv_arg1=%__pyenv_arg1:~1%"
+if not [%__pyenv_arg1%]==[] goto :loop_len
 
 setlocal enabledelayedexpansion
-set "cmdline=!exe! !cmdline:~%len%!"
+set "__pyenv_cmdline=!__pyenv_exe! !__pyenv_cmdline:~%__pyenv_len%!"
 :: run command (no need to update PATH for plugins)
 :: endlocal needed to ensure exit will not automatically close setlocal
 :: otherwise PYTHON_VERSION will be lost
-endlocal && endlocal && %cmdline%
+endlocal && endlocal && %__pyenv_cmdline%
 exit /b
 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 :: convert path which may have relative nodes (.. or .)
@@ -107,32 +107,32 @@ goto :eof
 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 :: compute list of paths to add for all activated python versions
 :extrapath
-call :normalizepath %1 bindir
-set "extrapaths=%extrapaths%%bindir%;%bindir%\Scripts;"
+call :normalizepath %1 __pyenv_bindir
+set "__pyenv_extrapaths=%__pyenv_extrapaths%%__pyenv_bindir%;%__pyenv_bindir%\Scripts;"
 goto :eof
 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 :: check pyenv python shim is first in PATH
 :check_path
-set "python_shim=%~dp0..\shims\python.bat"
-if not exist "%python_shim%" goto :eof
-call :normalizepath %python_shim% python_shim
-set "python_where="
-for /f "delims=" %%f in ('where python') do call :set_python_where %%f
+set "__pyenv_python_shim=%~dp0..\shims\python.bat"
+if not exist "%__pyenv_python_shim%" goto :eof
+call :normalizepath "%__pyenv_python_shim%" __pyenv_python_shim
+set "__pyenv_python_where="
+for /f "delims=" %%f in ('where python') do call :set_python_where "%%f"
 :: On recent Windows versions, where finds python shim with shebang first
-if /i [%python_shim%]==[%python_where%.bat] goto :eof
-if /i [%python_shim%]==[%python_where%] goto :eof
-call :bad_path "%python_where%"
+if /i "%__pyenv_python_shim%"=="%__pyenv_python_where:~1,-1%.bat" goto :eof
+if /i "%__pyenv_python_shim%"=="%__pyenv_python_where:~1,-1%" goto :eof
+call :bad_path %__pyenv_python_where%
 exit /b 1
 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
-:: set python_where variable if empty
+:: set __pyenv_python_where variable if empty
 :set_python_where
-if [%python_where%]==[] set "python_where=%~1"
+if [%__pyenv_python_where%]==[] set "__pyenv_python_where="%~1""
 goto :eof
 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 :: tell bad PATH and exit
 :bad_path
-set bad_python=%~1
-set bad_dir=%~dp1
-echo [91mFATAL: Found [95m%bad_python%[91m version before pyenv in PATH.[0m
-echo [91mPlease remove [95m%bad_dir%[91m from PATH for pyenv to work properly.[0m
+set "__pyenv_bad_python=%~1"
+set "__pyenv_bad_dir=%~dp1"
+echo [91mFATAL: Found [95m%__pyenv_bad_python%[91m version before pyenv in PATH.[0m
+echo [91mPlease remove [95m%__pyenv_bad_dir%[91m from PATH for pyenv to work properly.[0m
 goto :eof
\ No newline at end of file
diff --git a/pyenv-win/libexec/pyenv.vbs b/pyenv-win/libexec/pyenv.vbs
index a4ce650c..b36be20b 100644
--- a/pyenv-win/libexec/pyenv.vbs
+++ b/pyenv-win/libexec/pyenv.vbs
@@ -46,7 +46,7 @@ End Function
 Sub PrintVersion(cmd, exitCode)
     ' WScript.echo "kkotari: pyenv.vbs print version..!"
     Dim help
-    help = getCommandOutput("cmd /c "& strDirLibs &"\"& cmd &".bat")
+    help = getCommandOutput("cmd /c """& strDirLibs &"\"& cmd &".bat""")
     WScript.Echo help
     WScript.Quit exitCode
 End Sub
@@ -54,7 +54,7 @@ End Sub
 Sub PrintHelp(cmd, exitCode)
     ' WScript.echo "kkotari: pyenv.vbs print help..!"
     Dim help
-    help = getCommandOutput("cmd /c "& strDirLibs &"\"& cmd &".bat --help")
+    help = getCommandOutput("cmd /c """& strDirLibs &"\"& cmd &".bat"" --help")
     WScript.Echo help
     WScript.Quit exitCode
 End Sub
@@ -154,7 +154,7 @@ Sub CommandWhich(arg)
 
     WScript.Echo "pyenv: "& arg(1) &": command not found"
 
-    version = getCommandOutput("cscript //Nologo "& WScript.ScriptFullName &" whence "& program)
+    version = getCommandOutput("cscript //Nologo """& WScript.ScriptFullName &""" whence "& program)
     If Trim(version) <> "" Then
         WScript.Echo
         WScript.Echo "The '"& arg(1) &"' command exists in these Python versions:"


Test patch:
diff --git a/tests/test_pyenv_feature_exec.py b/tests/test_pyenv_feature_exec.py
index adc13858..f5f8e01f 100644
--- a/tests/test_pyenv_feature_exec.py
+++ b/tests/test_pyenv_feature_exec.py
@@ -117,7 +117,7 @@ def test_path_not_updated(self, setup):
         with open(tmp_bat, "w") as f:
             # must chain commands because env var is lost when cmd ends
             print(f'@echo %PATH%', file=f)
-            print(f'@call {python} -V>nul', file=f)
+            print(f'@call "{python}" -V>nul', file=f)
             print(f'@echo %PATH%', file=f)
         args = ["call", tmp_bat]
         stdout, stderr = self.__class__.ctx.exec(args)
diff --git a/tests/test_pyenv_feature_shell.py b/tests/test_pyenv_feature_shell.py
index 73017a2f..419f2b94 100644
--- a/tests/test_pyenv_feature_shell.py
+++ b/tests/test_pyenv_feature_shell.py
@@ -41,7 +41,7 @@ def commands(ctx):
             tmp_bat = str(Path(ctx.local_path, "tmp.bat"))
             with open(tmp_bat, "w") as f:
                 # must chain commands because env var is lost when cmd ends
-                print(f'@call {pyenv_bat} shell 3.7.7 && call {pyenv_bat} shell', file=f)
+                print(f'@call "{pyenv_bat}" shell 3.7.7 && call "{pyenv_bat}" shell', file=f)
             args = ['cmd', '/d', '/c', 'call', tmp_bat]
             result = subprocess.run(args, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
             output = str(result.stdout, "utf-8").strip()
diff --git a/tests/test_pyenv_helpers.py b/tests/test_pyenv_helpers.py
index 031abdb0..1bde0842 100644
--- a/tests/test_pyenv_helpers.py
+++ b/tests/test_pyenv_helpers.py
@@ -103,8 +103,8 @@ def __init__(self, exec, pyenv, pyenv_path, local_path):
 class TempPyEnv:
     def __init__(self, settings):
         self.tmp_path = tempfile.TemporaryDirectory()
-        settings['pyenv_path'] = pyenv_path = Path(self.tmp_path.name, 'pyenv')
-        settings['local_path'] = local_path = Path(self.tmp_path.name, 'local')
+        settings['pyenv_path'] = pyenv_path = Path(self.tmp_path.name, 'pyenv dir with spaces')
+        settings['local_path'] = local_path = Path(self.tmp_path.name, 'local dir with spaces')
         os.mkdir(pyenv_path)
         os.mkdir(local_path)
         pyenv_setup(settings)