Repository: pallets/click

Issue:
click.edit() Support Editor Paths Containing Spaces
**Environment**
* Python 3.6.5
* click 6.7
* Windows 7

When using click.edit(), an editor with a space in the path throws an error. For example :  "C:\Program Files\Sublime Text 3\sublime_text.exe"

If I put quotes around the path name in the subprocess.Popen call [here](https://github.com/pallets/click/blob/master/click/_termui_impl.py#L425) the problem is fixed.

**Before**
```python
            c = subprocess.Popen('%s "%s"' % (editor, filename),
                                 env=environ, shell=True)
```

**After**
```python
            c = subprocess.Popen('"%s" "%s"' % (editor, filename),
                                 env=environ, shell=True)
```


Discussion:
I do not encounter an issue on Mac as I can escape the space and open Sublime Text 3.
    message = click.edit(
        text='\n\n' + '',
        editor='/Applications/Sublime\ Text.app/Contents/SharedSupport/bin/subl')

Releasing Issue, please feel free to take if you work specifically on Windows.

Resolving patch:
diff --git a/click/_compat.py b/click/_compat.py
--- a/click/_compat.py
+++ b/click/_compat.py
@@ -162,6 +162,8 @@ def seekable(self):
     iteritems = lambda x: x.iteritems()
     range_type = xrange
 
+    from pipes import quote as shlex_quote
+
     def is_bytes(x):
         return isinstance(x, (buffer, bytearray))
 
@@ -268,6 +270,8 @@ def filename_to_ui(value):
     isidentifier = lambda x: x.isidentifier()
     iteritems = lambda x: iter(x.items())
 
+    from shlex import quote as shlex_quote
+
     def is_bytes(x):
         return isinstance(x, (bytes, memoryview, bytearray))
 
diff --git a/click/_termui_impl.py b/click/_termui_impl.py
--- a/click/_termui_impl.py
+++ b/click/_termui_impl.py
@@ -16,9 +16,9 @@
 import time
 import math
 import contextlib
-from ._compat import _default_text_stdout, range_type, PY2, isatty, \
-     open_stream, strip_ansi, term_len, get_best_encoding, WIN, int_types, \
-     CYGWIN
+from ._compat import _default_text_stdout, range_type, isatty, \
+     open_stream, shlex_quote, strip_ansi, term_len, get_best_encoding, WIN, \
+     int_types, CYGWIN
 from .utils import echo
 from .exceptions import ClickException
 
@@ -328,7 +328,7 @@ def pager(generator, color=None):
     fd, filename = tempfile.mkstemp()
     os.close(fd)
     try:
-        if hasattr(os, 'system') and os.system('more "%s"' % filename) == 0:
+        if hasattr(os, 'system') and os.system("more %s" % shlex_quote(filename)) == 0:
             return _pipepager(generator, 'more', color)
         return _nullpager(stdout, generator, color)
     finally:
@@ -396,7 +396,7 @@ def _tempfilepager(generator, cmd, color):
     with open_stream(filename, 'wb')[0] as f:
         f.write(text.encode(encoding))
     try:
-        os.system(cmd + ' "' + filename + '"')
+        os.system("%s %s" % (shlex_quote(cmd), shlex_quote(filename)))
     finally:
         os.unlink(filename)
 
@@ -441,8 +441,11 @@ def edit_file(self, filename):
         else:
             environ = None
         try:
-            c = subprocess.Popen('%s "%s"' % (editor, filename),
-                                 env=environ, shell=True)
+            c = subprocess.Popen(
+                "%s %s" % (shlex_quote(editor), shlex_quote(filename)),
+                env=environ,
+                shell=True
+            )
             exit_code = c.wait()
             if exit_code != 0:
                 raise ClickException('%s: Editing failed!' % editor)
@@ -513,19 +516,16 @@ def _unquote_file(url):
     elif WIN:
         if locate:
             url = _unquote_file(url)
-            args = 'explorer /select,"%s"' % _unquote_file(
-                url.replace('"', ''))
+            args = "explorer /select,%s" % (shlex_quote(url),)
         else:
-            args = 'start %s "" "%s"' % (
-                wait and '/WAIT' or '', url.replace('"', ''))
+            args = 'start %s "" %s' % ("/WAIT" if wait else "", shlex_quote(url))
         return os.system(args)
     elif CYGWIN:
         if locate:
             url = _unquote_file(url)
-            args = 'cygstart "%s"' % (os.path.dirname(url).replace('"', ''))
+            args = "cygstart %s" % (shlex_quote(os.path.dirname(url)),)
         else:
-            args = 'cygstart %s "%s"' % (
-                wait and '-w' or '', url.replace('"', ''))
+            args = "cygstart %s %s" % ("-w" if wait else "", shlex_quote(url))
         return os.system(args)
 
     try:


Test patch:
diff --git a/tests/test_imports.py b/tests/test_imports.py
--- a/tests/test_imports.py
+++ b/tests/test_imports.py
@@ -32,7 +32,7 @@ def tracking_import(module, locals=None, globals=None, fromlist=None,
 ALLOWED_IMPORTS = set([
     'weakref', 'os', 'struct', 'collections', 'sys', 'contextlib',
     'functools', 'stat', 're', 'codecs', 'inspect', 'itertools', 'io',
-    'threading', 'colorama', 'errno', 'fcntl', 'datetime'
+    'threading', 'colorama', 'errno', 'fcntl', 'datetime', 'pipes', 'shlex'
 ])
 
 if WIN: