Repository: pypa/pip

Issue:
Use fancy Unicode progress bar by default
but allow setting it to plain ASCII with `--progress-bar-style=ascii`



Discussion:
I'm generally opposed to adding CLI options if we can help it. If we switch to the unicode progress bar in what situations would someone want to _not_ use it?

If their terminal isn't set up to display Unicode characters and it looks horrible.

Usually tools that do fancy character graphics like `tree` and `pstree` have options to control this sort of thing.

This is going to cause Unicode errors on Windows, where the console doesn't support Unicode by default. And it probably breaks on Unix boxes set to a non-Unicode encoding.

I'm strongly -1 on this.

If someone insists that it goes in over my objections, I'd want it to be off by default with an opt-in CLI flag, and I'd want some sort of "clever" detection of the sys.stdout encoding and error handling settings so that it never tries to print an un-encodable character no matter what options the user chooses. Or I'd sulk horribly.

Sorry, got a bit over-excited there :-) But my objections stand.

@pfmoore What does `sys.stdout.encoding` return on Windows when the console doesn't support Unicode?

If your goal is to make the pip UI less cluttered/intimidating (which I wholeheartedly support), I might suggest we hide the option. 

If folks complain about garbled progress bars, we tell them about the option. If no one complains, we remove it in the next version. 

Yeah it should probably check the terminal's encoding. I thought about it but then deferred in this case there were -1s. 

Defaulting it to the ASCII progress bar seems safe to me. If someone asks for unicode on a non-unicode terminal, they can fix it or maybe they had a reason like the we're going t send it to a file and send it to someone else. 

@dstufft On my PC, `cp850` on a Chinese (German, Russian, American) PC, I've no idea.

@msabramo The big problem is that it doesn't give garbled output, it gives an exception:

```
>>> print("\N{RIGHT HALF BLOCK}")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Apps\Python34\lib\encodings\cp850.py", line 19, in encode
    return codecs.charmap_encode(input,self.errors,encoding_map)[0]
UnicodeEncodeError: 'charmap' codec can't encode character '\u2590' in position 0: character maps to <undefined>
```

I like #2231 better.


Resolving patch:
diff --git a/pip/utils/ui.py b/pip/utils/ui.py
--- a/pip/utils/ui.py
+++ b/pip/utils/ui.py
@@ -7,7 +7,8 @@
 from pip.compat import WINDOWS
 from pip.utils import format_size
 from pip.utils.logging import get_indentation
-from pip._vendor.progress.bar import Bar
+from pip._vendor import six
+from pip._vendor.progress.bar import Bar, IncrementalBar
 from pip._vendor.progress.helpers import WritelnMixin
 from pip._vendor.progress.spinner import Spinner
 
@@ -19,6 +20,36 @@
     colorama = None
 
 
+def _select_progress_class(preferred, fallback):
+    encoding = getattr(preferred.file, "encoding", None)
+
+    # If we don't know what encoding this file is in, then we'll just assume
+    # that it doesn't support unicode and use the ASCII bar.
+    if not encoding:
+        return fallback
+
+    # Collect all of the possible characters we want to use with the preferred
+    # bar.
+    characters = [
+        getattr(preferred, "empty_fill", six.text_type()),
+        getattr(preferred, "fill", six.text_type()),
+    ]
+    characters += list(getattr(preferred, "phases", []))
+
+    # Try to decode the characters we're using for the bar using the encoding
+    # of the given file, if this works then we'll assume that we can use the
+    # fancier bar and if not we'll fall back to the plaintext bar.
+    try:
+        six.text_type().join(characters).encode(encoding)
+    except UnicodeEncodeError:
+        return fallback
+    else:
+        return preferred
+
+
+_BaseBar = _select_progress_class(IncrementalBar, Bar)
+
+
 class DownloadProgressMixin(object):
 
     def __init__(self, *args, **kwargs):
@@ -74,7 +105,7 @@ def __init__(self, *args, **kwargs):
             self.hide_cursor = False
 
 
-class DownloadProgressBar(WindowsMixin, DownloadProgressMixin, Bar):
+class DownloadProgressBar(WindowsMixin, DownloadProgressMixin, _BaseBar):
 
     file = sys.stdout
     message = "%(percent)d%%"


Test patch: