##// END OF EJS Templates
progress: use stderr instead of stdout; check stderr.isatty()...
Augie Fackler -
r10788:ca6ba6ca stable
parent child Browse files
Show More
@@ -1,189 +1,192 b''
1 1 # progress.py show progress bars for some actions
2 2 #
3 3 # Copyright (C) 2010 Augie Fackler <durin42@gmail.com>
4 4 #
5 5 # This program is free software; you can redistribute it and/or modify it
6 6 # under the terms of the GNU General Public License as published by the
7 7 # Free Software Foundation; either version 2 of the License, or (at your
8 8 # option) any later version.
9 9 #
10 10 # This program is distributed in the hope that it will be useful, but
11 11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13 13 # Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License along
16 16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 18
19 19 """show progress bars for some actions
20 20
21 21 This extension uses the progress information logged by hg commands
22 22 to draw progress bars that are as informative as possible. Some progress
23 23 bars only offer indeterminate information, while others have a definite
24 24 end point.
25 25
26 26 The following settings are available::
27 27
28 28 [progress]
29 29 delay = 3 # number of seconds (float) before showing the progress bar
30 30 refresh = 0.1 # time in seconds between refreshes of the progress bar
31 31 format = topic bar number # format of the progress bar
32 32 width = <none> # if set, the maximum width of the progress information
33 33 # (that is, min(width, term width) will be used)
34 34 clear-complete = True # clear the progress bar after it's done
35 35 disable = False # if true, don't show a progress bar
36 assume-tty = False # if true, ALWAYS show a progress bar, unless
37 # disable is given
36 38
37 39 Valid entries for the format field are topic, bar, number, unit, and
38 40 item. item defaults to the last 20 characters of the item, but this
39 41 can be changed by adding either ``-<num>`` which would take the last
40 42 num characters, or ``+<num>`` for the first num characters.
41 43 """
42 44
43 45 import sys
44 46 import time
45 47
46 48 from mercurial import extensions
47 49 from mercurial import util
48 50
49 51 def spacejoin(*args):
50 52 return ' '.join(s for s in args if s)
51 53
52 54 class progbar(object):
53 55 def __init__(self, ui):
54 56 self.ui = ui
55 57 self.resetstate()
56 58
57 59 def resetstate(self):
58 60 self.topics = []
59 61 self.printed = False
60 62 self.lastprint = time.time() + float(self.ui.config(
61 63 'progress', 'delay', default=3))
62 64 self.indetcount = 0
63 65 self.refresh = float(self.ui.config(
64 66 'progress', 'refresh', default=0.1))
65 67 self.order = self.ui.configlist(
66 68 'progress', 'format',
67 69 default=['topic', 'bar', 'number'])
68 70
69 71 def show(self, topic, pos, item, unit, total):
70 72 termwidth = self.width()
71 73 self.printed = True
72 74 head = ''
73 75 needprogress = False
74 76 tail = ''
75 77 for indicator in self.order:
76 78 add = ''
77 79 if indicator == 'topic':
78 80 add = topic
79 81 elif indicator == 'number':
80 82 if total:
81 83 add = ('% ' + str(len(str(total))) +
82 84 's/%s') % (pos, total)
83 85 else:
84 86 add = str(pos)
85 87 elif indicator.startswith('item') and item:
86 88 slice = 'end'
87 89 if '-' in indicator:
88 90 wid = int(indicator.split('-')[1])
89 91 elif '+' in indicator:
90 92 slice = 'beginning'
91 93 wid = int(indicator.split('+')[1])
92 94 else:
93 95 wid = 20
94 96 if slice == 'end':
95 97 add = item[-wid:]
96 98 else:
97 99 add = item[:wid]
98 100 add += (wid - len(add)) * ' '
99 101 elif indicator == 'bar':
100 102 add = ''
101 103 needprogress = True
102 104 elif indicator == 'unit' and unit:
103 105 add = unit
104 106 if not needprogress:
105 107 head = spacejoin(head, add)
106 108 else:
107 109 tail = spacejoin(add, tail)
108 110 if needprogress:
109 111 used = 0
110 112 if head:
111 113 used += len(head) + 1
112 114 if tail:
113 115 used += len(tail) + 1
114 116 progwidth = termwidth - used - 3
115 117 if total:
116 118 amt = pos * progwidth // total
117 119 bar = '=' * (amt - 1)
118 120 if amt > 0:
119 121 bar += '>'
120 122 bar += ' ' * (progwidth - amt)
121 123 else:
122 124 progwidth -= 3
123 125 self.indetcount += 1
124 126 # mod the count by twice the width so we can make the
125 127 # cursor bounce between the right and left sides
126 128 amt = self.indetcount % (2 * progwidth)
127 129 amt -= progwidth
128 130 bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
129 131 ' ' * int(abs(amt)))
130 132 prog = ''.join(('[', bar , ']'))
131 133 out = spacejoin(head, prog, tail)
132 134 else:
133 135 out = spacejoin(head, tail)
134 sys.stdout.write('\r' + out[:termwidth])
135 sys.stdout.flush()
136 sys.stderr.write('\r' + out[:termwidth])
137 sys.stderr.flush()
136 138
137 139 def clear(self):
138 sys.stdout.write('\r%s\r' % (' ' * self.width()))
140 sys.stderr.write('\r%s\r' % (' ' * self.width()))
139 141
140 142 def complete(self):
141 143 if self.ui.configbool('progress', 'clear-complete', default=True):
142 144 self.clear()
143 145 else:
144 sys.stdout.write('\n')
145 sys.stdout.flush()
146 sys.stderr.write('\n')
147 sys.stderr.flush()
146 148
147 149 def width(self):
148 150 tw = util.termwidth()
149 151 return min(int(self.ui.config('progress', 'width', default=tw)), tw)
150 152
151 153 def progress(self, orig, topic, pos, item='', unit='', total=None):
152 154 if pos is None:
153 155 if self.topics and self.topics[-1] == topic and self.printed:
154 156 self.complete()
155 157 self.resetstate()
156 158 else:
157 159 if topic not in self.topics:
158 160 self.topics.append(topic)
159 161 now = time.time()
160 162 if (now - self.lastprint >= self.refresh
161 163 and topic == self.topics[-1]):
162 164 self.lastprint = now
163 165 self.show(topic, pos, item, unit, total)
164 166 return orig(topic, pos, item=item, unit=unit, total=total)
165 167
166 168 def write(self, orig, *args):
167 169 if self.printed:
168 170 self.clear()
169 171 return orig(*args)
170 172
171 173 sharedprog = None
172 174
173 175 def uisetup(ui):
174 176 # Apps that derive a class from ui.ui() can use
175 177 # setconfig('progress', 'disable', 'True') to disable this extension
176 178 if ui.configbool('progress', 'disable'):
177 179 return
178 if ui.interactive() and not ui.debugflag and not ui.quiet:
180 if ((sys.stderr.isatty() or ui.configbool('progress', 'assume-tty'))
181 and not ui.debugflag and not ui.quiet):
179 182 # we instantiate one globally shared progress bar to avoid
180 183 # competing progress bars when multiple UI objects get created
181 184 global sharedprog
182 185 if not sharedprog:
183 186 sharedprog = progbar(ui)
184 187 extensions.wrapfunction(ui, 'progress', sharedprog.progress)
185 188 extensions.wrapfunction(ui, 'write', sharedprog.write)
186 189 extensions.wrapfunction(ui, 'write_err', sharedprog.write)
187 190
188 191 def reposetup(ui, repo):
189 192 uisetup(repo.ui)
@@ -1,60 +1,59 b''
1 1 #!/bin/sh
2 2
3 3 cat > loop.py <<EOF
4 4 from mercurial import commands
5 5
6 6 def loop(ui, loops, **opts):
7 7 loops = int(loops)
8 8 total = None
9 9 if loops >= 0:
10 10 total = loops
11 11 loops = abs(loops)
12 12
13 13 for i in range(loops):
14 14 ui.progress('loop', i, 'loop.%d' % i, 'loopnum', total)
15 15 ui.progress('loop', None, 'loop.done', 'loopnum', total)
16 16
17 17 commands.norepo += " loop"
18 18
19 19 cmdtable = {
20 20 "loop": (loop, [], 'hg loop LOOPS'),
21 21 }
22 22 EOF
23 23
24 24 cat > filtercr.py <<EOF
25 25 import sys, re
26 26 for line in sys.stdin:
27 27 line = re.sub(r'\r+[^\n]', lambda m: '\n' + m.group()[-1:], line)
28 28 sys.stdout.write(line)
29 29 EOF
30 30
31 31 echo "[extensions]" >> $HGRCPATH
32 32 echo "progress=" >> $HGRCPATH
33 33 echo "loop=`pwd`/loop.py" >> $HGRCPATH
34 echo "[ui]" >> $HGRCPATH
35 echo "interactive=1" >> $HGRCPATH
34 echo "[progress]" >> $HGRCPATH
35 echo "assume-tty=1" >> $HGRCPATH
36 36
37 37 echo '% test default params, display nothing because of delay'
38 hg -y loop 3 | python filtercr.py
38 hg -y loop 3 2>&1 | python filtercr.py
39 39
40 echo "[progress]" >> $HGRCPATH
41 40 echo "delay=0" >> $HGRCPATH
42 41 echo "refresh=0" >> $HGRCPATH
43 42
44 43 echo '% test with delay=0, refresh=0'
45 hg -y loop 3 | python filtercr.py
44 hg -y loop 3 2>&1 | python filtercr.py
46 45
47 46 echo '% test refresh is taken in account'
48 hg -y --config progress.refresh=100 loop 3 | python filtercr.py
47 hg -y --config progress.refresh=100 loop 3 2>&1 | python filtercr.py
49 48
50 49 echo '% test format options 1'
51 hg -y --config 'progress.format=number topic item+2' loop 2 | python filtercr.py
50 hg -y --config 'progress.format=number topic item+2' loop 2 2>&1 | python filtercr.py
52 51
53 52 echo '% test format options 2'
54 hg -y --config 'progress.format=number item-3 bar' loop 2 | python filtercr.py
53 hg -y --config 'progress.format=number item-3 bar' loop 2 2>&1 | python filtercr.py
55 54
56 55 echo '% test format options and indeterminate progress'
57 hg -y --config 'progress.format=number item bar' loop -- -2 | python filtercr.py
56 hg -y --config 'progress.format=number item bar' loop -- -2 2>&1 | python filtercr.py
58 57
59 58 echo '% test immediate progress completion'
60 hg -y loop 0 | python filtercr.py
59 hg -y loop 0 2>&1 | python filtercr.py
General Comments 0
You need to be logged in to leave comments. Login now