hgext/progress.py
changeset 25497 93b8b0049932
parent 25482 95f490136e75
child 25498 7a5335ed7e1a
equal deleted inserted replaced
25496:38fd17bcc083 25497:93b8b0049932
    33 the item, but this can be changed by adding either ``-<num>`` which
    33 the item, but this can be changed by adding either ``-<num>`` which
    34 would take the last num characters, or ``+<num>`` for the first num
    34 would take the last num characters, or ``+<num>`` for the first num
    35 characters.
    35 characters.
    36 """
    36 """
    37 
    37 
    38 import sys
    38 from mercurial import progress
    39 import time
       
    40 import threading
       
    41 
       
    42 from mercurial.i18n import _
       
    43 # Note for extension authors: ONLY specify testedwith = 'internal' for
       
    44 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
       
    45 # be specifying the version(s) of Mercurial they are tested with, or
       
    46 # leave the attribute unspecified.
       
    47 testedwith = 'internal'
       
    48 
       
    49 from mercurial import encoding
       
    50 
       
    51 def spacejoin(*args):
       
    52     return ' '.join(s for s in args if s)
       
    53 
       
    54 def shouldprint(ui):
       
    55     return not ui.plain() and (ui._isatty(sys.stderr) or
       
    56                                ui.configbool('progress', 'assume-tty'))
       
    57 
       
    58 def fmtremaining(seconds):
       
    59     if seconds < 60:
       
    60         # i18n: format XX seconds as "XXs"
       
    61         return _("%02ds") % (seconds)
       
    62     minutes = seconds // 60
       
    63     if minutes < 60:
       
    64         seconds -= minutes * 60
       
    65         # i18n: format X minutes and YY seconds as "XmYYs"
       
    66         return _("%dm%02ds") % (minutes, seconds)
       
    67     # we're going to ignore seconds in this case
       
    68     minutes += 1
       
    69     hours = minutes // 60
       
    70     minutes -= hours * 60
       
    71     if hours < 30:
       
    72         # i18n: format X hours and YY minutes as "XhYYm"
       
    73         return _("%dh%02dm") % (hours, minutes)
       
    74     # we're going to ignore minutes in this case
       
    75     hours += 1
       
    76     days = hours // 24
       
    77     hours -= days * 24
       
    78     if days < 15:
       
    79         # i18n: format X days and YY hours as "XdYYh"
       
    80         return _("%dd%02dh") % (days, hours)
       
    81     # we're going to ignore hours in this case
       
    82     days += 1
       
    83     weeks = days // 7
       
    84     days -= weeks * 7
       
    85     if weeks < 55:
       
    86         # i18n: format X weeks and YY days as "XwYYd"
       
    87         return _("%dw%02dd") % (weeks, days)
       
    88     # we're going to ignore days and treat a year as 52 weeks
       
    89     weeks += 1
       
    90     years = weeks // 52
       
    91     weeks -= years * 52
       
    92     # i18n: format X years and YY weeks as "XyYYw"
       
    93     return _("%dy%02dw") % (years, weeks)
       
    94 
       
    95 class progbar(object):
       
    96     def __init__(self, ui):
       
    97         self.ui = ui
       
    98         self._refreshlock = threading.Lock()
       
    99         self.resetstate()
       
   100 
       
   101     def resetstate(self):
       
   102         self.topics = []
       
   103         self.topicstates = {}
       
   104         self.starttimes = {}
       
   105         self.startvals = {}
       
   106         self.printed = False
       
   107         self.lastprint = time.time() + float(self.ui.config(
       
   108             'progress', 'delay', default=3))
       
   109         self.curtopic = None
       
   110         self.lasttopic = None
       
   111         self.indetcount = 0
       
   112         self.refresh = float(self.ui.config(
       
   113             'progress', 'refresh', default=0.1))
       
   114         self.changedelay = max(3 * self.refresh,
       
   115                                float(self.ui.config(
       
   116                                    'progress', 'changedelay', default=1)))
       
   117         self.order = self.ui.configlist(
       
   118             'progress', 'format',
       
   119             default=['topic', 'bar', 'number', 'estimate'])
       
   120 
       
   121     def show(self, now, topic, pos, item, unit, total):
       
   122         if not shouldprint(self.ui):
       
   123             return
       
   124         termwidth = self.width()
       
   125         self.printed = True
       
   126         head = ''
       
   127         needprogress = False
       
   128         tail = ''
       
   129         for indicator in self.order:
       
   130             add = ''
       
   131             if indicator == 'topic':
       
   132                 add = topic
       
   133             elif indicator == 'number':
       
   134                 if total:
       
   135                     add = ('% ' + str(len(str(total))) +
       
   136                            's/%s') % (pos, total)
       
   137                 else:
       
   138                     add = str(pos)
       
   139             elif indicator.startswith('item') and item:
       
   140                 slice = 'end'
       
   141                 if '-' in indicator:
       
   142                     wid = int(indicator.split('-')[1])
       
   143                 elif '+' in indicator:
       
   144                     slice = 'beginning'
       
   145                     wid = int(indicator.split('+')[1])
       
   146                 else:
       
   147                     wid = 20
       
   148                 if slice == 'end':
       
   149                     add = encoding.trim(item, wid, leftside=True)
       
   150                 else:
       
   151                     add = encoding.trim(item, wid)
       
   152                 add += (wid - encoding.colwidth(add)) * ' '
       
   153             elif indicator == 'bar':
       
   154                 add = ''
       
   155                 needprogress = True
       
   156             elif indicator == 'unit' and unit:
       
   157                 add = unit
       
   158             elif indicator == 'estimate':
       
   159                 add = self.estimate(topic, pos, total, now)
       
   160             elif indicator == 'speed':
       
   161                 add = self.speed(topic, pos, unit, now)
       
   162             if not needprogress:
       
   163                 head = spacejoin(head, add)
       
   164             else:
       
   165                 tail = spacejoin(tail, add)
       
   166         if needprogress:
       
   167             used = 0
       
   168             if head:
       
   169                 used += encoding.colwidth(head) + 1
       
   170             if tail:
       
   171                 used += encoding.colwidth(tail) + 1
       
   172             progwidth = termwidth - used - 3
       
   173             if total and pos <= total:
       
   174                 amt = pos * progwidth // total
       
   175                 bar = '=' * (amt - 1)
       
   176                 if amt > 0:
       
   177                     bar += '>'
       
   178                 bar += ' ' * (progwidth - amt)
       
   179             else:
       
   180                 progwidth -= 3
       
   181                 self.indetcount += 1
       
   182                 # mod the count by twice the width so we can make the
       
   183                 # cursor bounce between the right and left sides
       
   184                 amt = self.indetcount % (2 * progwidth)
       
   185                 amt -= progwidth
       
   186                 bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
       
   187                        ' ' * int(abs(amt)))
       
   188             prog = ''.join(('[', bar , ']'))
       
   189             out = spacejoin(head, prog, tail)
       
   190         else:
       
   191             out = spacejoin(head, tail)
       
   192         sys.stderr.write('\r' + encoding.trim(out, termwidth))
       
   193         self.lasttopic = topic
       
   194         sys.stderr.flush()
       
   195 
       
   196     def clear(self):
       
   197         if not shouldprint(self.ui):
       
   198             return
       
   199         sys.stderr.write('\r%s\r' % (' ' * self.width()))
       
   200 
       
   201     def complete(self):
       
   202         if not shouldprint(self.ui):
       
   203             return
       
   204         if self.ui.configbool('progress', 'clear-complete', default=True):
       
   205             self.clear()
       
   206         else:
       
   207             sys.stderr.write('\n')
       
   208         sys.stderr.flush()
       
   209 
       
   210     def width(self):
       
   211         tw = self.ui.termwidth()
       
   212         return min(int(self.ui.config('progress', 'width', default=tw)), tw)
       
   213 
       
   214     def estimate(self, topic, pos, total, now):
       
   215         if total is None:
       
   216             return ''
       
   217         initialpos = self.startvals[topic]
       
   218         target = total - initialpos
       
   219         delta = pos - initialpos
       
   220         if delta > 0:
       
   221             elapsed = now - self.starttimes[topic]
       
   222             if elapsed > float(
       
   223                 self.ui.config('progress', 'estimate', default=2)):
       
   224                 seconds = (elapsed * (target - delta)) // delta + 1
       
   225                 return fmtremaining(seconds)
       
   226         return ''
       
   227 
       
   228     def speed(self, topic, pos, unit, now):
       
   229         initialpos = self.startvals[topic]
       
   230         delta = pos - initialpos
       
   231         elapsed = now - self.starttimes[topic]
       
   232         if elapsed > float(
       
   233             self.ui.config('progress', 'estimate', default=2)):
       
   234             return _('%d %s/sec') % (delta / elapsed, unit)
       
   235         return ''
       
   236 
       
   237     def _oktoprint(self, now):
       
   238         '''Check if conditions are met to print - e.g. changedelay elapsed'''
       
   239         if (self.lasttopic is None # first time we printed
       
   240             # not a topic change
       
   241             or self.curtopic == self.lasttopic
       
   242             # it's been long enough we should print anyway
       
   243             or now - self.lastprint >= self.changedelay):
       
   244             return True
       
   245         else:
       
   246             return False
       
   247 
       
   248     def progress(self, topic, pos, item='', unit='', total=None):
       
   249         now = time.time()
       
   250         self._refreshlock.acquire()
       
   251         try:
       
   252             if pos is None:
       
   253                 self.starttimes.pop(topic, None)
       
   254                 self.startvals.pop(topic, None)
       
   255                 self.topicstates.pop(topic, None)
       
   256                 # reset the progress bar if this is the outermost topic
       
   257                 if self.topics and self.topics[0] == topic and self.printed:
       
   258                     self.complete()
       
   259                     self.resetstate()
       
   260                 # truncate the list of topics assuming all topics within
       
   261                 # this one are also closed
       
   262                 if topic in self.topics:
       
   263                     self.topics = self.topics[:self.topics.index(topic)]
       
   264                     # reset the last topic to the one we just unwound to,
       
   265                     # so that higher-level topics will be stickier than
       
   266                     # lower-level topics
       
   267                     if self.topics:
       
   268                         self.lasttopic = self.topics[-1]
       
   269                     else:
       
   270                         self.lasttopic = None
       
   271             else:
       
   272                 if topic not in self.topics:
       
   273                     self.starttimes[topic] = now
       
   274                     self.startvals[topic] = pos
       
   275                     self.topics.append(topic)
       
   276                 self.topicstates[topic] = pos, item, unit, total
       
   277                 self.curtopic = topic
       
   278                 if now - self.lastprint >= self.refresh and self.topics:
       
   279                     if self._oktoprint(now):
       
   280                         self.lastprint = now
       
   281                         self.show(now, topic, *self.topicstates[topic])
       
   282         finally:
       
   283             self._refreshlock.release()
       
   284 
    39 
   285 _singleton = None
    40 _singleton = None
   286 
    41 
   287 def uisetup(ui):
    42 def uisetup(ui):
   288     global _singleton
    43     global _singleton
   309 
    64 
   310     # Apps that derive a class from ui.ui() can use
    65     # Apps that derive a class from ui.ui() can use
   311     # setconfig('progress', 'disable', 'True') to disable this extension
    66     # setconfig('progress', 'disable', 'True') to disable this extension
   312     if ui.configbool('progress', 'disable'):
    67     if ui.configbool('progress', 'disable'):
   313         return
    68         return
   314     if shouldprint(ui) and not ui.debugflag and not ui.quiet:
    69     if progress.shouldprint(ui) and not ui.debugflag and not ui.quiet:
   315         dval = object()
    70         dval = object()
   316         if getattr(ui, '_progbar', dval) is dval:
    71         if getattr(ui, '_progbar', dval) is dval:
   317             ui.__class__ = progressui
    72             ui.__class__ = progressui
   318             # we instantiate one globally-shared progress bar to avoid
    73             # we instantiate one globally-shared progress bar to avoid
   319             # competing progress bars when multiple UI objects get created
    74             # competing progress bars when multiple UI objects get created
   320             if not progressui._progbar:
    75             if not progressui._progbar:
   321                 if _singleton is None:
    76                 if _singleton is None:
   322                     _singleton = progbar(ui)
    77                     _singleton = progress.progbar(ui)
   323                 progressui._progbar = _singleton
    78                 progressui._progbar = _singleton
   324 
    79 
   325 def reposetup(ui, repo):
    80 def reposetup(ui, repo):
   326     uisetup(repo.ui)
    81     uisetup(repo.ui)