1eb8dc403SDave Cobbley# -*- coding: utf-8 -*-
2eb8dc403SDave Cobbley#
3eb8dc403SDave Cobbley# progressbar  - Text progress bar library for Python.
4eb8dc403SDave Cobbley# Copyright (c) 2005 Nilton Volpato
5eb8dc403SDave Cobbley#
6*c342db35SBrad Bishop# SPDX-License-Identifier: LGPL-2.1-or-later OR BSD-3-Clause-Clear
7*c342db35SBrad Bishop#
8eb8dc403SDave Cobbley# This library is free software; you can redistribute it and/or
9eb8dc403SDave Cobbley# modify it under the terms of the GNU Lesser General Public
10eb8dc403SDave Cobbley# License as published by the Free Software Foundation; either
11eb8dc403SDave Cobbley# version 2.1 of the License, or (at your option) any later version.
12eb8dc403SDave Cobbley#
13eb8dc403SDave Cobbley# This library is distributed in the hope that it will be useful,
14eb8dc403SDave Cobbley# but WITHOUT ANY WARRANTY; without even the implied warranty of
15eb8dc403SDave Cobbley# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16eb8dc403SDave Cobbley# Lesser General Public License for more details.
17eb8dc403SDave Cobbley#
18eb8dc403SDave Cobbley# You should have received a copy of the GNU Lesser General Public
19eb8dc403SDave Cobbley# License along with this library; if not, write to the Free Software
20eb8dc403SDave Cobbley# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21eb8dc403SDave Cobbley
22eb8dc403SDave Cobbley"""Default ProgressBar widgets."""
23eb8dc403SDave Cobbley
24eb8dc403SDave Cobbleyfrom __future__ import division
25eb8dc403SDave Cobbley
26eb8dc403SDave Cobbleyimport datetime
27eb8dc403SDave Cobbleyimport math
28eb8dc403SDave Cobbley
29eb8dc403SDave Cobbleytry:
30eb8dc403SDave Cobbley    from abc import ABCMeta, abstractmethod
31eb8dc403SDave Cobbleyexcept ImportError:
32eb8dc403SDave Cobbley    AbstractWidget = object
33eb8dc403SDave Cobbley    abstractmethod = lambda fn: fn
34eb8dc403SDave Cobbleyelse:
35eb8dc403SDave Cobbley    AbstractWidget = ABCMeta('AbstractWidget', (object,), {})
36eb8dc403SDave Cobbley
37eb8dc403SDave Cobbley
38eb8dc403SDave Cobbleydef format_updatable(updatable, pbar):
39eb8dc403SDave Cobbley    if hasattr(updatable, 'update'): return updatable.update(pbar)
40eb8dc403SDave Cobbley    else: return updatable
41eb8dc403SDave Cobbley
42eb8dc403SDave Cobbley
43eb8dc403SDave Cobbleyclass Widget(AbstractWidget):
44eb8dc403SDave Cobbley    """The base class for all widgets.
45eb8dc403SDave Cobbley
46eb8dc403SDave Cobbley    The ProgressBar will call the widget's update value when the widget should
47eb8dc403SDave Cobbley    be updated. The widget's size may change between calls, but the widget may
48eb8dc403SDave Cobbley    display incorrectly if the size changes drastically and repeatedly.
49eb8dc403SDave Cobbley
50eb8dc403SDave Cobbley    The boolean TIME_SENSITIVE informs the ProgressBar that it should be
51eb8dc403SDave Cobbley    updated more often because it is time sensitive.
52eb8dc403SDave Cobbley    """
53eb8dc403SDave Cobbley
54eb8dc403SDave Cobbley    TIME_SENSITIVE = False
55eb8dc403SDave Cobbley    __slots__ = ()
56eb8dc403SDave Cobbley
57eb8dc403SDave Cobbley    @abstractmethod
58eb8dc403SDave Cobbley    def update(self, pbar):
59eb8dc403SDave Cobbley        """Updates the widget.
60eb8dc403SDave Cobbley
61eb8dc403SDave Cobbley        pbar - a reference to the calling ProgressBar
62eb8dc403SDave Cobbley        """
63eb8dc403SDave Cobbley
64eb8dc403SDave Cobbley
65eb8dc403SDave Cobbleyclass WidgetHFill(Widget):
66eb8dc403SDave Cobbley    """The base class for all variable width widgets.
67eb8dc403SDave Cobbley
68eb8dc403SDave Cobbley    This widget is much like the \\hfill command in TeX, it will expand to
69eb8dc403SDave Cobbley    fill the line. You can use more than one in the same line, and they will
70eb8dc403SDave Cobbley    all have the same width, and together will fill the line.
71eb8dc403SDave Cobbley    """
72eb8dc403SDave Cobbley
73eb8dc403SDave Cobbley    @abstractmethod
74eb8dc403SDave Cobbley    def update(self, pbar, width):
75eb8dc403SDave Cobbley        """Updates the widget providing the total width the widget must fill.
76eb8dc403SDave Cobbley
77eb8dc403SDave Cobbley        pbar - a reference to the calling ProgressBar
78eb8dc403SDave Cobbley        width - The total width the widget must fill
79eb8dc403SDave Cobbley        """
80eb8dc403SDave Cobbley
81eb8dc403SDave Cobbley
82eb8dc403SDave Cobbleyclass Timer(Widget):
83eb8dc403SDave Cobbley    """Widget which displays the elapsed seconds."""
84eb8dc403SDave Cobbley
85eb8dc403SDave Cobbley    __slots__ = ('format_string',)
86eb8dc403SDave Cobbley    TIME_SENSITIVE = True
87eb8dc403SDave Cobbley
88eb8dc403SDave Cobbley    def __init__(self, format='Elapsed Time: %s'):
89eb8dc403SDave Cobbley        self.format_string = format
90eb8dc403SDave Cobbley
91eb8dc403SDave Cobbley    @staticmethod
92eb8dc403SDave Cobbley    def format_time(seconds):
93eb8dc403SDave Cobbley        """Formats time as the string "HH:MM:SS"."""
94eb8dc403SDave Cobbley
95eb8dc403SDave Cobbley        return str(datetime.timedelta(seconds=int(seconds)))
96eb8dc403SDave Cobbley
97eb8dc403SDave Cobbley
98eb8dc403SDave Cobbley    def update(self, pbar):
99eb8dc403SDave Cobbley        """Updates the widget to show the elapsed time."""
100eb8dc403SDave Cobbley
101eb8dc403SDave Cobbley        return self.format_string % self.format_time(pbar.seconds_elapsed)
102eb8dc403SDave Cobbley
103eb8dc403SDave Cobbley
104eb8dc403SDave Cobbleyclass ETA(Timer):
105eb8dc403SDave Cobbley    """Widget which attempts to estimate the time of arrival."""
106eb8dc403SDave Cobbley
107eb8dc403SDave Cobbley    TIME_SENSITIVE = True
108eb8dc403SDave Cobbley
109eb8dc403SDave Cobbley    def update(self, pbar):
110eb8dc403SDave Cobbley        """Updates the widget to show the ETA or total time when finished."""
111eb8dc403SDave Cobbley
112eb8dc403SDave Cobbley        if pbar.currval == 0:
113eb8dc403SDave Cobbley            return 'ETA:  --:--:--'
114eb8dc403SDave Cobbley        elif pbar.finished:
115eb8dc403SDave Cobbley            return 'Time: %s' % self.format_time(pbar.seconds_elapsed)
116eb8dc403SDave Cobbley        else:
117eb8dc403SDave Cobbley            elapsed = pbar.seconds_elapsed
118eb8dc403SDave Cobbley            eta = elapsed * pbar.maxval / pbar.currval - elapsed
119eb8dc403SDave Cobbley            return 'ETA:  %s' % self.format_time(eta)
120eb8dc403SDave Cobbley
121eb8dc403SDave Cobbley
122eb8dc403SDave Cobbleyclass AdaptiveETA(Timer):
123eb8dc403SDave Cobbley    """Widget which attempts to estimate the time of arrival.
124eb8dc403SDave Cobbley
125eb8dc403SDave Cobbley    Uses a weighted average of two estimates:
126eb8dc403SDave Cobbley      1) ETA based on the total progress and time elapsed so far
127eb8dc403SDave Cobbley      2) ETA based on the progress as per the last 10 update reports
128eb8dc403SDave Cobbley
129eb8dc403SDave Cobbley    The weight depends on the current progress so that to begin with the
130eb8dc403SDave Cobbley    total progress is used and at the end only the most recent progress is
131eb8dc403SDave Cobbley    used.
132eb8dc403SDave Cobbley    """
133eb8dc403SDave Cobbley
134eb8dc403SDave Cobbley    TIME_SENSITIVE = True
135eb8dc403SDave Cobbley    NUM_SAMPLES = 10
136eb8dc403SDave Cobbley
137eb8dc403SDave Cobbley    def _update_samples(self, currval, elapsed):
138eb8dc403SDave Cobbley        sample = (currval, elapsed)
139eb8dc403SDave Cobbley        if not hasattr(self, 'samples'):
140eb8dc403SDave Cobbley            self.samples = [sample] * (self.NUM_SAMPLES + 1)
141eb8dc403SDave Cobbley        else:
142eb8dc403SDave Cobbley            self.samples.append(sample)
143eb8dc403SDave Cobbley        return self.samples.pop(0)
144eb8dc403SDave Cobbley
145eb8dc403SDave Cobbley    def _eta(self, maxval, currval, elapsed):
146eb8dc403SDave Cobbley        return elapsed * maxval / float(currval) - elapsed
147eb8dc403SDave Cobbley
148eb8dc403SDave Cobbley    def update(self, pbar):
149eb8dc403SDave Cobbley        """Updates the widget to show the ETA or total time when finished."""
150eb8dc403SDave Cobbley        if pbar.currval == 0:
151eb8dc403SDave Cobbley            return 'ETA:  --:--:--'
152eb8dc403SDave Cobbley        elif pbar.finished:
153eb8dc403SDave Cobbley            return 'Time: %s' % self.format_time(pbar.seconds_elapsed)
154eb8dc403SDave Cobbley        else:
155eb8dc403SDave Cobbley            elapsed = pbar.seconds_elapsed
156eb8dc403SDave Cobbley            currval1, elapsed1 = self._update_samples(pbar.currval, elapsed)
157eb8dc403SDave Cobbley            eta = self._eta(pbar.maxval, pbar.currval, elapsed)
158eb8dc403SDave Cobbley            if pbar.currval > currval1:
159eb8dc403SDave Cobbley                etasamp = self._eta(pbar.maxval - currval1,
160eb8dc403SDave Cobbley                                    pbar.currval - currval1,
161eb8dc403SDave Cobbley                                    elapsed - elapsed1)
162eb8dc403SDave Cobbley                weight = (pbar.currval / float(pbar.maxval)) ** 0.5
163eb8dc403SDave Cobbley                eta = (1 - weight) * eta + weight * etasamp
164eb8dc403SDave Cobbley            return 'ETA:  %s' % self.format_time(eta)
165eb8dc403SDave Cobbley
166eb8dc403SDave Cobbley
167eb8dc403SDave Cobbleyclass FileTransferSpeed(Widget):
168eb8dc403SDave Cobbley    """Widget for showing the transfer speed (useful for file transfers)."""
169eb8dc403SDave Cobbley
170eb8dc403SDave Cobbley    FORMAT = '%6.2f %s%s/s'
171eb8dc403SDave Cobbley    PREFIXES = ' kMGTPEZY'
172eb8dc403SDave Cobbley    __slots__ = ('unit',)
173eb8dc403SDave Cobbley
174eb8dc403SDave Cobbley    def __init__(self, unit='B'):
175eb8dc403SDave Cobbley        self.unit = unit
176eb8dc403SDave Cobbley
177eb8dc403SDave Cobbley    def update(self, pbar):
178eb8dc403SDave Cobbley        """Updates the widget with the current SI prefixed speed."""
179eb8dc403SDave Cobbley
180eb8dc403SDave Cobbley        if pbar.seconds_elapsed < 2e-6 or pbar.currval < 2e-6: # =~ 0
181eb8dc403SDave Cobbley            scaled = power = 0
182eb8dc403SDave Cobbley        else:
183eb8dc403SDave Cobbley            speed = pbar.currval / pbar.seconds_elapsed
184eb8dc403SDave Cobbley            power = int(math.log(speed, 1000))
185eb8dc403SDave Cobbley            scaled = speed / 1000.**power
186eb8dc403SDave Cobbley
187eb8dc403SDave Cobbley        return self.FORMAT % (scaled, self.PREFIXES[power], self.unit)
188eb8dc403SDave Cobbley
189eb8dc403SDave Cobbley
190eb8dc403SDave Cobbleyclass AnimatedMarker(Widget):
191eb8dc403SDave Cobbley    """An animated marker for the progress bar which defaults to appear as if
192eb8dc403SDave Cobbley    it were rotating.
193eb8dc403SDave Cobbley    """
194eb8dc403SDave Cobbley
195eb8dc403SDave Cobbley    __slots__ = ('markers', 'curmark')
196eb8dc403SDave Cobbley
197eb8dc403SDave Cobbley    def __init__(self, markers='|/-\\'):
198eb8dc403SDave Cobbley        self.markers = markers
199eb8dc403SDave Cobbley        self.curmark = -1
200eb8dc403SDave Cobbley
201eb8dc403SDave Cobbley    def update(self, pbar):
202eb8dc403SDave Cobbley        """Updates the widget to show the next marker or the first marker when
203eb8dc403SDave Cobbley        finished"""
204eb8dc403SDave Cobbley
205eb8dc403SDave Cobbley        if pbar.finished: return self.markers[0]
206eb8dc403SDave Cobbley
207eb8dc403SDave Cobbley        self.curmark = (self.curmark + 1) % len(self.markers)
208eb8dc403SDave Cobbley        return self.markers[self.curmark]
209eb8dc403SDave Cobbley
210eb8dc403SDave Cobbley# Alias for backwards compatibility
211eb8dc403SDave CobbleyRotatingMarker = AnimatedMarker
212eb8dc403SDave Cobbley
213eb8dc403SDave Cobbley
214eb8dc403SDave Cobbleyclass Counter(Widget):
215eb8dc403SDave Cobbley    """Displays the current count."""
216eb8dc403SDave Cobbley
217eb8dc403SDave Cobbley    __slots__ = ('format_string',)
218eb8dc403SDave Cobbley
219eb8dc403SDave Cobbley    def __init__(self, format='%d'):
220eb8dc403SDave Cobbley        self.format_string = format
221eb8dc403SDave Cobbley
222eb8dc403SDave Cobbley    def update(self, pbar):
223eb8dc403SDave Cobbley        return self.format_string % pbar.currval
224eb8dc403SDave Cobbley
225eb8dc403SDave Cobbley
226eb8dc403SDave Cobbleyclass Percentage(Widget):
227eb8dc403SDave Cobbley    """Displays the current percentage as a number with a percent sign."""
228eb8dc403SDave Cobbley
229eb8dc403SDave Cobbley    def update(self, pbar):
230eb8dc403SDave Cobbley        return '%3d%%' % pbar.percentage()
231eb8dc403SDave Cobbley
232eb8dc403SDave Cobbley
233eb8dc403SDave Cobbleyclass FormatLabel(Timer):
234eb8dc403SDave Cobbley    """Displays a formatted label."""
235eb8dc403SDave Cobbley
236eb8dc403SDave Cobbley    mapping = {
237eb8dc403SDave Cobbley        'elapsed': ('seconds_elapsed', Timer.format_time),
238eb8dc403SDave Cobbley        'finished': ('finished', None),
239eb8dc403SDave Cobbley        'last_update': ('last_update_time', None),
240eb8dc403SDave Cobbley        'max': ('maxval', None),
241eb8dc403SDave Cobbley        'seconds': ('seconds_elapsed', None),
242eb8dc403SDave Cobbley        'start': ('start_time', None),
243eb8dc403SDave Cobbley        'value': ('currval', None)
244eb8dc403SDave Cobbley    }
245eb8dc403SDave Cobbley
246eb8dc403SDave Cobbley    __slots__ = ('format_string',)
247eb8dc403SDave Cobbley    def __init__(self, format):
248eb8dc403SDave Cobbley        self.format_string = format
249eb8dc403SDave Cobbley
250eb8dc403SDave Cobbley    def update(self, pbar):
251eb8dc403SDave Cobbley        context = {}
252eb8dc403SDave Cobbley        for name, (key, transform) in self.mapping.items():
253eb8dc403SDave Cobbley            try:
254eb8dc403SDave Cobbley                value = getattr(pbar, key)
255eb8dc403SDave Cobbley
256eb8dc403SDave Cobbley                if transform is None:
257eb8dc403SDave Cobbley                   context[name] = value
258eb8dc403SDave Cobbley                else:
259eb8dc403SDave Cobbley                   context[name] = transform(value)
260eb8dc403SDave Cobbley            except: pass
261eb8dc403SDave Cobbley
262eb8dc403SDave Cobbley        return self.format_string % context
263eb8dc403SDave Cobbley
264eb8dc403SDave Cobbley
265eb8dc403SDave Cobbleyclass SimpleProgress(Widget):
266eb8dc403SDave Cobbley    """Returns progress as a count of the total (e.g.: "5 of 47")."""
267eb8dc403SDave Cobbley
268eb8dc403SDave Cobbley    __slots__ = ('sep',)
269eb8dc403SDave Cobbley
270eb8dc403SDave Cobbley    def __init__(self, sep=' of '):
271eb8dc403SDave Cobbley        self.sep = sep
272eb8dc403SDave Cobbley
273eb8dc403SDave Cobbley    def update(self, pbar):
274eb8dc403SDave Cobbley        return '%d%s%d' % (pbar.currval, self.sep, pbar.maxval)
275eb8dc403SDave Cobbley
276eb8dc403SDave Cobbley
277eb8dc403SDave Cobbleyclass Bar(WidgetHFill):
278eb8dc403SDave Cobbley    """A progress bar which stretches to fill the line."""
279eb8dc403SDave Cobbley
280eb8dc403SDave Cobbley    __slots__ = ('marker', 'left', 'right', 'fill', 'fill_left')
281eb8dc403SDave Cobbley
282eb8dc403SDave Cobbley    def __init__(self, marker='#', left='|', right='|', fill=' ',
283eb8dc403SDave Cobbley                 fill_left=True):
284eb8dc403SDave Cobbley        """Creates a customizable progress bar.
285eb8dc403SDave Cobbley
286eb8dc403SDave Cobbley        marker - string or updatable object to use as a marker
287eb8dc403SDave Cobbley        left - string or updatable object to use as a left border
288eb8dc403SDave Cobbley        right - string or updatable object to use as a right border
289eb8dc403SDave Cobbley        fill - character to use for the empty part of the progress bar
290eb8dc403SDave Cobbley        fill_left - whether to fill from the left or the right
291eb8dc403SDave Cobbley        """
292eb8dc403SDave Cobbley        self.marker = marker
293eb8dc403SDave Cobbley        self.left = left
294eb8dc403SDave Cobbley        self.right = right
295eb8dc403SDave Cobbley        self.fill = fill
296eb8dc403SDave Cobbley        self.fill_left = fill_left
297eb8dc403SDave Cobbley
298eb8dc403SDave Cobbley
299eb8dc403SDave Cobbley    def update(self, pbar, width):
300eb8dc403SDave Cobbley        """Updates the progress bar and its subcomponents."""
301eb8dc403SDave Cobbley
302eb8dc403SDave Cobbley        left, marked, right = (format_updatable(i, pbar) for i in
303eb8dc403SDave Cobbley                               (self.left, self.marker, self.right))
304eb8dc403SDave Cobbley
305eb8dc403SDave Cobbley        width -= len(left) + len(right)
306eb8dc403SDave Cobbley        # Marked must *always* have length of 1
307eb8dc403SDave Cobbley        if pbar.maxval:
308eb8dc403SDave Cobbley          marked *= int(pbar.currval / pbar.maxval * width)
309eb8dc403SDave Cobbley        else:
310eb8dc403SDave Cobbley          marked = ''
311eb8dc403SDave Cobbley
312eb8dc403SDave Cobbley        if self.fill_left:
313eb8dc403SDave Cobbley            return '%s%s%s' % (left, marked.ljust(width, self.fill), right)
314eb8dc403SDave Cobbley        else:
315eb8dc403SDave Cobbley            return '%s%s%s' % (left, marked.rjust(width, self.fill), right)
316eb8dc403SDave Cobbley
317eb8dc403SDave Cobbley
318eb8dc403SDave Cobbleyclass ReverseBar(Bar):
319eb8dc403SDave Cobbley    """A bar which has a marker which bounces from side to side."""
320eb8dc403SDave Cobbley
321eb8dc403SDave Cobbley    def __init__(self, marker='#', left='|', right='|', fill=' ',
322eb8dc403SDave Cobbley                 fill_left=False):
323eb8dc403SDave Cobbley        """Creates a customizable progress bar.
324eb8dc403SDave Cobbley
325eb8dc403SDave Cobbley        marker - string or updatable object to use as a marker
326eb8dc403SDave Cobbley        left - string or updatable object to use as a left border
327eb8dc403SDave Cobbley        right - string or updatable object to use as a right border
328eb8dc403SDave Cobbley        fill - character to use for the empty part of the progress bar
329eb8dc403SDave Cobbley        fill_left - whether to fill from the left or the right
330eb8dc403SDave Cobbley        """
331eb8dc403SDave Cobbley        self.marker = marker
332eb8dc403SDave Cobbley        self.left = left
333eb8dc403SDave Cobbley        self.right = right
334eb8dc403SDave Cobbley        self.fill = fill
335eb8dc403SDave Cobbley        self.fill_left = fill_left
336eb8dc403SDave Cobbley
337eb8dc403SDave Cobbley
338eb8dc403SDave Cobbleyclass BouncingBar(Bar):
339eb8dc403SDave Cobbley    def update(self, pbar, width):
340eb8dc403SDave Cobbley        """Updates the progress bar and its subcomponents."""
341eb8dc403SDave Cobbley
342eb8dc403SDave Cobbley        left, marker, right = (format_updatable(i, pbar) for i in
343eb8dc403SDave Cobbley                               (self.left, self.marker, self.right))
344eb8dc403SDave Cobbley
345eb8dc403SDave Cobbley        width -= len(left) + len(right)
346eb8dc403SDave Cobbley
347eb8dc403SDave Cobbley        if pbar.finished: return '%s%s%s' % (left, width * marker, right)
348eb8dc403SDave Cobbley
349eb8dc403SDave Cobbley        position = int(pbar.currval % (width * 2 - 1))
350eb8dc403SDave Cobbley        if position > width: position = width * 2 - position
351eb8dc403SDave Cobbley        lpad = self.fill * (position - 1)
352eb8dc403SDave Cobbley        rpad = self.fill * (width - len(marker) - len(lpad))
353eb8dc403SDave Cobbley
354eb8dc403SDave Cobbley        # Swap if we want to bounce the other way
355eb8dc403SDave Cobbley        if not self.fill_left: rpad, lpad = lpad, rpad
356eb8dc403SDave Cobbley
357eb8dc403SDave Cobbley        return '%s%s%s%s%s' % (left, lpad, marker, rpad, right)
358eb8dc403SDave Cobbley
359eb8dc403SDave Cobbley
360eb8dc403SDave Cobbleyclass BouncingSlider(Bar):
361eb8dc403SDave Cobbley    """
362eb8dc403SDave Cobbley    A slider that bounces back and forth in response to update() calls
363eb8dc403SDave Cobbley    without reference to the actual value. Based on a combination of
364eb8dc403SDave Cobbley    BouncingBar from a newer version of this module and RotatingMarker.
365eb8dc403SDave Cobbley    """
366eb8dc403SDave Cobbley    def __init__(self, marker='<=>'):
367eb8dc403SDave Cobbley        self.curmark = -1
368eb8dc403SDave Cobbley        self.forward = True
369eb8dc403SDave Cobbley        Bar.__init__(self, marker=marker)
370eb8dc403SDave Cobbley    def update(self, pbar, width):
371eb8dc403SDave Cobbley        left, marker, right = (format_updatable(i, pbar) for i in
372eb8dc403SDave Cobbley                               (self.left, self.marker, self.right))
373eb8dc403SDave Cobbley
374eb8dc403SDave Cobbley        width -= len(left) + len(right)
375eb8dc403SDave Cobbley        if width < 0:
376eb8dc403SDave Cobbley            return ''
377eb8dc403SDave Cobbley
378eb8dc403SDave Cobbley        if pbar.finished: return '%s%s%s' % (left, width * '=', right)
379eb8dc403SDave Cobbley
380eb8dc403SDave Cobbley        self.curmark = self.curmark + 1
381eb8dc403SDave Cobbley        position = int(self.curmark % (width * 2 - 1))
382eb8dc403SDave Cobbley        if position + len(marker) > width:
383eb8dc403SDave Cobbley            self.forward = not self.forward
384eb8dc403SDave Cobbley            self.curmark = 1
385eb8dc403SDave Cobbley            position = 1
386eb8dc403SDave Cobbley        lpad = ' ' * (position - 1)
387eb8dc403SDave Cobbley        rpad = ' ' * (width - len(marker) - len(lpad))
388eb8dc403SDave Cobbley
389eb8dc403SDave Cobbley        if not self.forward:
390eb8dc403SDave Cobbley            temp = lpad
391eb8dc403SDave Cobbley            lpad = rpad
392eb8dc403SDave Cobbley            rpad = temp
393eb8dc403SDave Cobbley        return '%s%s%s%s%s' % (left, lpad, marker, rpad, right)
394