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