mercurial/progress.py
changeset 25497 93b8b0049932
child 25581 79c75459321e
equal deleted inserted replaced
25496:38fd17bcc083 25497:93b8b0049932
       
     1 # progress.py progress bars related code
       
     2 #
       
     3 # Copyright (C) 2010 Augie Fackler <durin42@gmail.com>
       
     4 #
       
     5 # This software may be used and distributed according to the terms of the
       
     6 # GNU General Public License version 2 or any later version.
       
     7 
       
     8 import sys
       
     9 import time
       
    10 import threading
       
    11 from mercurial import encoding
       
    12 
       
    13 from mercurial.i18n import _
       
    14 
       
    15 
       
    16 def spacejoin(*args):
       
    17     return ' '.join(s for s in args if s)
       
    18 
       
    19 def shouldprint(ui):
       
    20     return not ui.plain() and (ui._isatty(sys.stderr) or
       
    21                                ui.configbool('progress', 'assume-tty'))
       
    22 
       
    23 def fmtremaining(seconds):
       
    24     """format a number of remaining seconds in humain readable way
       
    25 
       
    26     This will properly display seconds, minutes, hours, days if needed"""
       
    27     if seconds < 60:
       
    28         # i18n: format XX seconds as "XXs"
       
    29         return _("%02ds") % (seconds)
       
    30     minutes = seconds // 60
       
    31     if minutes < 60:
       
    32         seconds -= minutes * 60
       
    33         # i18n: format X minutes and YY seconds as "XmYYs"
       
    34         return _("%dm%02ds") % (minutes, seconds)
       
    35     # we're going to ignore seconds in this case
       
    36     minutes += 1
       
    37     hours = minutes // 60
       
    38     minutes -= hours * 60
       
    39     if hours < 30:
       
    40         # i18n: format X hours and YY minutes as "XhYYm"
       
    41         return _("%dh%02dm") % (hours, minutes)
       
    42     # we're going to ignore minutes in this case
       
    43     hours += 1
       
    44     days = hours // 24
       
    45     hours -= days * 24
       
    46     if days < 15:
       
    47         # i18n: format X days and YY hours as "XdYYh"
       
    48         return _("%dd%02dh") % (days, hours)
       
    49     # we're going to ignore hours in this case
       
    50     days += 1
       
    51     weeks = days // 7
       
    52     days -= weeks * 7
       
    53     if weeks < 55:
       
    54         # i18n: format X weeks and YY days as "XwYYd"
       
    55         return _("%dw%02dd") % (weeks, days)
       
    56     # we're going to ignore days and treat a year as 52 weeks
       
    57     weeks += 1
       
    58     years = weeks // 52
       
    59     weeks -= years * 52
       
    60     # i18n: format X years and YY weeks as "XyYYw"
       
    61     return _("%dy%02dw") % (years, weeks)
       
    62 
       
    63 class progbar(object):
       
    64     def __init__(self, ui):
       
    65         self.ui = ui
       
    66         self._refreshlock = threading.Lock()
       
    67         self.resetstate()
       
    68 
       
    69     def resetstate(self):
       
    70         self.topics = []
       
    71         self.topicstates = {}
       
    72         self.starttimes = {}
       
    73         self.startvals = {}
       
    74         self.printed = False
       
    75         self.lastprint = time.time() + float(self.ui.config(
       
    76             'progress', 'delay', default=3))
       
    77         self.curtopic = None
       
    78         self.lasttopic = None
       
    79         self.indetcount = 0
       
    80         self.refresh = float(self.ui.config(
       
    81             'progress', 'refresh', default=0.1))
       
    82         self.changedelay = max(3 * self.refresh,
       
    83                                float(self.ui.config(
       
    84                                    'progress', 'changedelay', default=1)))
       
    85         self.order = self.ui.configlist(
       
    86             'progress', 'format',
       
    87             default=['topic', 'bar', 'number', 'estimate'])
       
    88 
       
    89     def show(self, now, topic, pos, item, unit, total):
       
    90         if not shouldprint(self.ui):
       
    91             return
       
    92         termwidth = self.width()
       
    93         self.printed = True
       
    94         head = ''
       
    95         needprogress = False
       
    96         tail = ''
       
    97         for indicator in self.order:
       
    98             add = ''
       
    99             if indicator == 'topic':
       
   100                 add = topic
       
   101             elif indicator == 'number':
       
   102                 if total:
       
   103                     add = ('% ' + str(len(str(total))) +
       
   104                            's/%s') % (pos, total)
       
   105                 else:
       
   106                     add = str(pos)
       
   107             elif indicator.startswith('item') and item:
       
   108                 slice = 'end'
       
   109                 if '-' in indicator:
       
   110                     wid = int(indicator.split('-')[1])
       
   111                 elif '+' in indicator:
       
   112                     slice = 'beginning'
       
   113                     wid = int(indicator.split('+')[1])
       
   114                 else:
       
   115                     wid = 20
       
   116                 if slice == 'end':
       
   117                     add = encoding.trim(item, wid, leftside=True)
       
   118                 else:
       
   119                     add = encoding.trim(item, wid)
       
   120                 add += (wid - encoding.colwidth(add)) * ' '
       
   121             elif indicator == 'bar':
       
   122                 add = ''
       
   123                 needprogress = True
       
   124             elif indicator == 'unit' and unit:
       
   125                 add = unit
       
   126             elif indicator == 'estimate':
       
   127                 add = self.estimate(topic, pos, total, now)
       
   128             elif indicator == 'speed':
       
   129                 add = self.speed(topic, pos, unit, now)
       
   130             if not needprogress:
       
   131                 head = spacejoin(head, add)
       
   132             else:
       
   133                 tail = spacejoin(tail, add)
       
   134         if needprogress:
       
   135             used = 0
       
   136             if head:
       
   137                 used += encoding.colwidth(head) + 1
       
   138             if tail:
       
   139                 used += encoding.colwidth(tail) + 1
       
   140             progwidth = termwidth - used - 3
       
   141             if total and pos <= total:
       
   142                 amt = pos * progwidth // total
       
   143                 bar = '=' * (amt - 1)
       
   144                 if amt > 0:
       
   145                     bar += '>'
       
   146                 bar += ' ' * (progwidth - amt)
       
   147             else:
       
   148                 progwidth -= 3
       
   149                 self.indetcount += 1
       
   150                 # mod the count by twice the width so we can make the
       
   151                 # cursor bounce between the right and left sides
       
   152                 amt = self.indetcount % (2 * progwidth)
       
   153                 amt -= progwidth
       
   154                 bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
       
   155                        ' ' * int(abs(amt)))
       
   156             prog = ''.join(('[', bar , ']'))
       
   157             out = spacejoin(head, prog, tail)
       
   158         else:
       
   159             out = spacejoin(head, tail)
       
   160         sys.stderr.write('\r' + encoding.trim(out, termwidth))
       
   161         self.lasttopic = topic
       
   162         sys.stderr.flush()
       
   163 
       
   164     def clear(self):
       
   165         if not shouldprint(self.ui):
       
   166             return
       
   167         sys.stderr.write('\r%s\r' % (' ' * self.width()))
       
   168 
       
   169     def complete(self):
       
   170         if not shouldprint(self.ui):
       
   171             return
       
   172         if self.ui.configbool('progress', 'clear-complete', default=True):
       
   173             self.clear()
       
   174         else:
       
   175             sys.stderr.write('\n')
       
   176         sys.stderr.flush()
       
   177 
       
   178     def width(self):
       
   179         tw = self.ui.termwidth()
       
   180         return min(int(self.ui.config('progress', 'width', default=tw)), tw)
       
   181 
       
   182     def estimate(self, topic, pos, total, now):
       
   183         if total is None:
       
   184             return ''
       
   185         initialpos = self.startvals[topic]
       
   186         target = total - initialpos
       
   187         delta = pos - initialpos
       
   188         if delta > 0:
       
   189             elapsed = now - self.starttimes[topic]
       
   190             if elapsed > float(
       
   191                 self.ui.config('progress', 'estimate', default=2)):
       
   192                 seconds = (elapsed * (target - delta)) // delta + 1
       
   193                 return fmtremaining(seconds)
       
   194         return ''
       
   195 
       
   196     def speed(self, topic, pos, unit, now):
       
   197         initialpos = self.startvals[topic]
       
   198         delta = pos - initialpos
       
   199         elapsed = now - self.starttimes[topic]
       
   200         if elapsed > float(
       
   201             self.ui.config('progress', 'estimate', default=2)):
       
   202             return _('%d %s/sec') % (delta / elapsed, unit)
       
   203         return ''
       
   204 
       
   205     def _oktoprint(self, now):
       
   206         '''Check if conditions are met to print - e.g. changedelay elapsed'''
       
   207         if (self.lasttopic is None # first time we printed
       
   208             # not a topic change
       
   209             or self.curtopic == self.lasttopic
       
   210             # it's been long enough we should print anyway
       
   211             or now - self.lastprint >= self.changedelay):
       
   212             return True
       
   213         else:
       
   214             return False
       
   215 
       
   216     def progress(self, topic, pos, item='', unit='', total=None):
       
   217         now = time.time()
       
   218         self._refreshlock.acquire()
       
   219         try:
       
   220             if pos is None:
       
   221                 self.starttimes.pop(topic, None)
       
   222                 self.startvals.pop(topic, None)
       
   223                 self.topicstates.pop(topic, None)
       
   224                 # reset the progress bar if this is the outermost topic
       
   225                 if self.topics and self.topics[0] == topic and self.printed:
       
   226                     self.complete()
       
   227                     self.resetstate()
       
   228                 # truncate the list of topics assuming all topics within
       
   229                 # this one are also closed
       
   230                 if topic in self.topics:
       
   231                     self.topics = self.topics[:self.topics.index(topic)]
       
   232                     # reset the last topic to the one we just unwound to,
       
   233                     # so that higher-level topics will be stickier than
       
   234                     # lower-level topics
       
   235                     if self.topics:
       
   236                         self.lasttopic = self.topics[-1]
       
   237                     else:
       
   238                         self.lasttopic = None
       
   239             else:
       
   240                 if topic not in self.topics:
       
   241                     self.starttimes[topic] = now
       
   242                     self.startvals[topic] = pos
       
   243                     self.topics.append(topic)
       
   244                 self.topicstates[topic] = pos, item, unit, total
       
   245                 self.curtopic = topic
       
   246                 if now - self.lastprint >= self.refresh and self.topics:
       
   247                     if self._oktoprint(now):
       
   248                         self.lastprint = now
       
   249                         self.show(now, topic, *self.topicstates[topic])
       
   250         finally:
       
   251             self._refreshlock.release()
       
   252