Repository: tqdm/tqdm (Python)

Issue:
Colored Progress Bars
With a few tweaks in the code I managed to add a color argument to tqdm.

![newgif1](https://user-images.githubusercontent.com/17583740/30978247-5925ad1e-a450-11e7-9f05-7ea07a46413b.gif)

Is this already an implemented feature ? Anyone likes it ?






Hints:
colours seem a bit distracting. If you do want them:

```python
from tqdm import trange
from colorama import Fore


for i in trange(int(7e7),
                bar_format="{l_bar}%s{bar}%s{r_bar}" % (Fore.BLUE, Fore.RESET)):
    pass
```
Both above mentioned methods are not working with notebook, any idea why?
notebooks (`tqdm.notebook.tqdm`) are different. They don't appear to support colour

https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20List.html#IntProgress

Right... should be possible: https://github.com/jupyter-widgets/ipywidgets/issues/2982#issuecomment-699653659

```python
from tqdm.notebook import tqdm, trange

with trange(int(1e7)) as pbar:
    pbar.container.children[-2].style.bar_color = '#ffff00'  # or 'yellow'
    for i in pbar:
        pass
```

TODO: make this easier

Patch:
diff --git a/tqdm/notebook.py b/tqdm/notebook.py
--- a/tqdm/notebook.py
+++ b/tqdm/notebook.py
@@ -184,6 +184,16 @@ def display(self, msg=None, pos=None,
             except AttributeError:
                 self.container.visible = False
 
+    @property
+    def colour(self):
+        if hasattr(self, 'container'):
+            return self.container.children[-2].style.bar_color
+
+    @colour.setter
+    def colour(self, bar_color):
+        if hasattr(self, 'container'):
+            self.container.children[-2].style.bar_color = bar_color
+
     def __init__(self, *args, **kwargs):
         kwargs = kwargs.copy()
         # Setup default output
@@ -198,6 +208,7 @@ def __init__(self, *args, **kwargs):
                 '{bar}', '<bar/>')
         # convert disable = None to False
         kwargs['disable'] = bool(kwargs.get('disable', False))
+        colour = kwargs.pop('colour', None)
         super(tqdm_notebook, self).__init__(*args, **kwargs)
         if self.disable or not kwargs['gui']:
             self.sp = lambda *_, **__: None
@@ -212,6 +223,7 @@ def __init__(self, *args, **kwargs):
         self.container = self.status_printer(
             self.fp, total, self.desc, self.ncols)
         self.sp = self.display
+        self.colour = colour
 
         # Print initial bar state
         if not self.disable:
diff --git a/tqdm/std.py b/tqdm/std.py
--- a/tqdm/std.py
+++ b/tqdm/std.py
@@ -140,8 +140,13 @@ class Bar(object):
     ASCII = " 123456789#"
     UTF = u" " + u''.join(map(_unich, range(0x258F, 0x2587, -1)))
     BLANK = "  "
+    COLOUR_RESET = '\x1b[0m'
+    COLOUR_RGB = '\x1b[38;2;%d;%d;%dm'
+    COLOURS = dict(BLACK='\x1b[30m', RED='\x1b[31m', GREEN='\x1b[32m',
+                   YELLOW='\x1b[33m', BLUE='\x1b[34m', MAGENTA='\x1b[35m',
+                   CYAN='\x1b[36m', WHITE='\x1b[37m')
 
-    def __init__(self, frac, default_len=10, charset=UTF):
+    def __init__(self, frac, default_len=10, charset=UTF, colour=None):
         if not (0 <= frac <= 1):
             warn("clamping frac to range [0, 1]", TqdmWarning, stacklevel=2)
             frac = max(0, min(1, frac))
@@ -149,6 +154,30 @@ def __init__(self, frac, default_len=10, charset=UTF):
         self.frac = frac
         self.default_len = default_len
         self.charset = charset
+        self.colour = colour
+
+    @property
+    def colour(self):
+        return self._colour
+
+    @colour.setter
+    def colour(self, value):
+        if not value:
+            self._colour = None
+            return
+        try:
+            if value.upper() in self.COLOURS:
+                self._colour = self.COLOURS[value.upper()]
+            elif value[0] == '#' and len(value) == 7:
+                self._colour = self.COLOUR_RGB % tuple(
+                    int(i, 16) for i in (value[1:3], value[3:5], value[5:7]))
+            else:
+                raise KeyError
+        except (KeyError, AttributeError):
+            warn("Unknown colour (%s); valid choices: [hex (#00ff00), %s]" % (
+                 value, ", ".join(self.COLOURS)),
+                 TqdmWarning, stacklevel=2)
+            self._colour = None
 
     def __format__(self, format_spec):
         if format_spec:
@@ -178,8 +207,10 @@ def __format__(self, format_spec):
 
         # whitespace padding
         if bar_length < N_BARS:
-            return bar + frac_bar + \
+            bar = bar + frac_bar + \
                 charset[0] * (N_BARS - bar_length - 1)
+        if self.colour:
+            return self.colour + bar + self.COLOUR_RESET
         return bar
 
 
@@ -309,7 +340,7 @@ def print_status(s):
     @staticmethod
     def format_meter(n, total, elapsed, ncols=None, prefix='', ascii=False,
                      unit='it', unit_scale=False, rate=None, bar_format=None,
-                     postfix=None, unit_divisor=1000, initial=0,
+                     postfix=None, unit_divisor=1000, initial=0, colour=None,
                      **extra_kwargs):
         """
         Return a string-based progress bar given some parameters
@@ -368,6 +399,8 @@ def format_meter(n, total, elapsed, ncols=None, prefix='', ascii=False,
             [default: 1000], ignored unless `unit_scale` is True.
         initial  : int or float, optional
             The initial counter value [default: 0].
+        colour  : str, optional
+            Bar colour (e.g. `'green'`, `'#00ff00'`).
 
         Returns
         -------
@@ -442,6 +475,7 @@ def format_meter(n, total, elapsed, ncols=None, prefix='', ascii=False,
             rate_noinv_fmt=rate_noinv_fmt, rate_inv=inv_rate,
             rate_inv_fmt=rate_inv_fmt,
             postfix=postfix, unit_divisor=unit_divisor,
+            colour=colour,
             # plus more useful definitions
             remaining=remaining_str, remaining_s=remaining,
             l_bar=l_bar, r_bar=r_bar,
@@ -483,7 +517,8 @@ def format_meter(n, total, elapsed, ncols=None, prefix='', ascii=False,
                 frac,
                 max(1, ncols - disp_len(nobar))
                 if ncols else 10,
-                charset=Bar.ASCII if ascii is True else ascii or Bar.UTF)
+                charset=Bar.ASCII if ascii is True else ascii or Bar.UTF,
+                colour=colour)
             if not _is_ascii(full_bar.charset) and _is_ascii(bar_format):
                 bar_format = _unicode(bar_format)
             res = bar_format.format(bar=full_bar, **format_dict)
@@ -501,7 +536,8 @@ def format_meter(n, total, elapsed, ncols=None, prefix='', ascii=False,
                 0,
                 max(1, ncols - disp_len(nobar))
                 if ncols else 10,
-                charset=Bar.BLANK)
+                charset=Bar.BLANK,
+                colour=colour)
             res = bar_format.format(bar=full_bar, **format_dict)
             return disp_trim(res, ncols) if ncols else res
         else:
@@ -802,7 +838,7 @@ def __init__(self, iterable=None, desc=None, total=None, leave=True,
                  unit_scale=False, dynamic_ncols=False, smoothing=0.3,
                  bar_format=None, initial=0, position=None, postfix=None,
                  unit_divisor=1000, write_bytes=None, lock_args=None,
-                 nrows=None,
+                 nrows=None, colour=None,
                  gui=False, **kwargs):
         """
         Parameters
@@ -908,6 +944,8 @@ def __init__(self, iterable=None, desc=None, total=None, leave=True,
             The screen height. If specified, hides nested bars outside this
             bound. If unspecified, attempts to use environment height.
             The fallback is 20.
+        colour  : str, optional
+            Bar colour (e.g. `'green'`, `'#00ff00'`).
         gui  : bool, optional
             WARNING: internal parameter - do not use.
             Use tqdm.gui.tqdm(...) instead. If set, will attempt to use
@@ -1029,9 +1067,10 @@ def __init__(self, iterable=None, desc=None, total=None, leave=True,
         self.dynamic_ncols = dynamic_ncols
         self.smoothing = smoothing
         self.avg_time = None
-        self._time = time
         self.bar_format = bar_format
         self.postfix = None
+        self.colour = colour
+        self._time = time
         if postfix:
             try:
                 self.set_postfix(refresh=False, **postfix)
@@ -1453,7 +1492,8 @@ def format_dict(self):
             unit_scale=self.unit_scale,
             rate=1 / self.avg_time if self.avg_time else None,
             bar_format=self.bar_format, postfix=self.postfix,
-            unit_divisor=self.unit_divisor, initial=self.initial)
+            unit_divisor=self.unit_divisor, initial=self.initial,
+            colour=self.colour)
 
     def display(self, msg=None, pos=None):
         """


Test patch:
diff --git a/tqdm/tests/tests_tqdm.py b/tqdm/tests/tests_tqdm.py
--- a/tqdm/tests/tests_tqdm.py
+++ b/tqdm/tests/tests_tqdm.py
@@ -15,7 +15,7 @@
 
 from tqdm import tqdm
 from tqdm import trange
-from tqdm import TqdmDeprecationWarning
+from tqdm import TqdmDeprecationWarning, TqdmWarning
 from tqdm.std import Bar
 from tqdm.contrib import DummyTqdmFile
 
@@ -1895,7 +1895,7 @@ def test_float_progress():
     with closing(StringIO()) as our_file:
         with trange(10, total=9.6, file=our_file) as t:
             with catch_warnings(record=True) as w:
-                simplefilter("always")
+                simplefilter("always", category=TqdmWarning)
                 for i in t:
                     if i < 9:
                         assert not w
@@ -1987,3 +1987,26 @@ def test_initial():
         out = our_file.getvalue()
         assert '10/19' in out
         assert '19/19' in out
+
+
+@with_setup(pretest, posttest)
+def test_colour():
+    """Test `colour`"""
+    with closing(StringIO()) as our_file:
+        for _ in tqdm(_range(9), file=our_file, colour="#beefed"):
+            pass
+        out = our_file.getvalue()
+        assert '\x1b[38;2;%d;%d;%dm' % (0xbe, 0xef, 0xed) in out
+
+        with catch_warnings(record=True) as w:
+            simplefilter("always", category=TqdmWarning)
+            with tqdm(total=1, file=our_file, colour="charm") as t:
+                assert w
+                t.update()
+            assert "Unknown colour" in str(w[-1].message)
+
+    with closing(StringIO()) as our_file2:
+        for _ in tqdm(_range(9), file=our_file2, colour="blue"):
+            pass
+        out = our_file2.getvalue()
+        assert '\x1b[34m' in out