Repository: pallets/click

Issue:
Add `current_item` parameter to `ProgressBar.update()`
When using a ProgressBar in manual update mode, we can call the `ProgressBar.update(n_steps)` method to update the progress bar.  In this usage pattern, the `current_item` property doesn't get set, which means the `item_show_func` feature can't work as it should.  I propose adding an optional `current_item` parameter to update, so update would look like this:

```python
    def update(self, n_steps, current_item=None):
        self.make_step(n_steps)
        if current_item is not None:
            self.current_item = current_item
        self.render_progress()
```

This would allow a usage pattern like this (adapted from click documentation):

```python
with click.progressbar(length=total_size,
                       label='Unzipping archive',
                       item_show_func=lambda a: a.filename) as bar:
    for archive in zip_file:
        archive.extract()
        bar.update(archive.size, archive)
```

I can submit a PR for this if you don't see anything wrong with the idea.


Discussion:

Resolving patch:
diff --git a/click/_termui_impl.py b/click/_termui_impl.py
--- a/click/_termui_impl.py
+++ b/click/_termui_impl.py
@@ -260,8 +260,21 @@ def make_step(self, n_steps):
 
         self.eta_known = self.length_known
 
-    def update(self, n_steps):
+    def update(self, n_steps, current_item=None):
+        """Update the progress bar by advancing a specified number of
+        steps, and optionally set the ``current_item`` for this new
+        position.
+
+        :param n_steps: Number of steps to advance.
+        :param current_item: Optional item to set as ``current_item``
+            for the updated position.
+
+        .. versionadded:: 8.0
+            Added the ``current_item`` optional parameter.
+        """
         self.make_step(n_steps)
+        if current_item is not None:
+            self.current_item = current_item
         self.render_progress()
 
     def finish(self):
diff --git a/click/termui.py b/click/termui.py
--- a/click/termui.py
+++ b/click/termui.py
@@ -302,6 +302,20 @@ def progressbar(iterable=None, length=None, label=None, show_eta=True,
                 process_chunk(chunk)
                 bar.update(chunks.bytes)
 
+    The ``update()`` method also takes an optional value specifying the
+    ``current_item`` at the new position. This is useful when used
+    together with ``item_show_func`` to customize the output for each
+    manual step::
+
+        with click.progressbar(
+            length=total_size,
+            label='Unzipping archive',
+            item_show_func=lambda a: a.filename
+        ) as bar:
+            for archive in zip_file:
+                archive.extract()
+                bar.update(archive.size, archive)
+
     .. versionadded:: 2.0
 
     .. versionadded:: 4.0


Test patch:
diff --git a/tests/test_termui.py b/tests/test_termui.py
--- a/tests/test_termui.py
+++ b/tests/test_termui.py
@@ -239,6 +239,50 @@ def cli():
     assert '100%          ' in lines[3]
 
 
+def test_progressbar_item_show_func(runner, monkeypatch):
+    fake_clock = FakeClock()
+
+    @click.command()
+    def cli():
+        with click.progressbar(range(4), item_show_func=lambda x: "Custom {}".format(x)) as progress:
+            for _ in progress:
+                fake_clock.advance_time()
+                print("")
+
+    monkeypatch.setattr(time, 'time', fake_clock.time)
+    monkeypatch.setattr(click._termui_impl, 'isatty', lambda _: True)
+    output = runner.invoke(cli, []).output
+
+    lines = [line for line in output.split('\n') if '[' in line]
+
+    assert 'Custom 0' in lines[0]
+    assert 'Custom 1' in lines[1]
+    assert 'Custom 2' in lines[2]
+    assert 'Custom None' in lines[3]
+
+
+def test_progressbar_update_with_item_show_func(runner, monkeypatch):
+    fake_clock = FakeClock()
+
+    @click.command()
+    def cli():
+        with click.progressbar(length=6, item_show_func=lambda x: "Custom {}".format(x)) as progress:
+            while not progress.finished:
+                fake_clock.advance_time()
+                progress.update(2, progress.pos)
+                print("")
+
+    monkeypatch.setattr(time, 'time', fake_clock.time)
+    monkeypatch.setattr(click._termui_impl, 'isatty', lambda _: True)
+    output = runner.invoke(cli, []).output
+
+    lines = [line for line in output.split('\n') if '[' in line]
+
+    assert 'Custom 0' in lines[0]
+    assert 'Custom 2' in lines[1]
+    assert 'Custom 4' in lines[2]
+
+
 @pytest.mark.parametrize(
     'key_char', (u'h', u'H', u'é', u'À', u' ', u'字', u'àH', u'àR')
 )