1 """Implements various support classes and functions used in a number of
2 places in Roundup code.
3 """
5 __docformat__ = 'restructuredtext'
7 import os, time, sys, re
9 class TruthDict:
10 '''Returns True for valid keys, False for others.
11 '''
12 def __init__(self, keys):
13 if keys:
14 self.keys = {}
15 for col in keys:
16 self.keys[col] = 1
17 else:
18 self.__getitem__ = lambda name: 1
20 def __getitem__(self, name):
21 return self.keys.has_key(name)
23 def ensureParentsExist(dest):
24 if not os.path.exists(os.path.dirname(dest)):
25 os.makedirs(os.path.dirname(dest))
27 class PrioList:
28 '''Manages a sorted list.
30 Currently only implements method 'append' and iteration from a
31 full list interface.
32 Implementation: We manage a "sorted" status and sort on demand.
33 Appending to the list will require re-sorting before use.
34 >>> p = PrioList()
35 >>> for i in 5,7,1,-1:
36 ... p.append(i)
37 ...
38 >>> for k in p:
39 ... print k
40 ...
41 -1
42 1
43 5
44 7
46 '''
47 def __init__(self):
48 self.list = []
49 self.sorted = True
51 def append(self, item):
52 self.list.append(item)
53 self.sorted = False
55 def __iter__(self):
56 if not self.sorted:
57 self.list.sort()
58 self.sorted = True
59 return iter(self.list)
61 class Progress:
62 '''Progress display for console applications.
64 See __main__ block at end of file for sample usage.
65 '''
66 def __init__(self, info, sequence):
67 self.info = info
68 self.sequence = iter(sequence)
69 self.total = len(sequence)
70 self.start = self.now = time.time()
71 self.num = 0
72 self.stepsize = self.total / 100 or 1
73 self.steptimes = []
74 self.display()
76 def __iter__(self): return self
78 def next(self):
79 self.num += 1
81 if self.num > self.total:
82 print self.info, 'done', ' '*(75-len(self.info)-6)
83 sys.stdout.flush()
84 return self.sequence.next()
86 if self.num % self.stepsize:
87 return self.sequence.next()
89 self.display()
90 return self.sequence.next()
92 def display(self):
93 # figure how long we've spent - guess how long to go
94 now = time.time()
95 steptime = now - self.now
96 self.steptimes.insert(0, steptime)
97 if len(self.steptimes) > 5:
98 self.steptimes.pop()
99 steptime = sum(self.steptimes) / len(self.steptimes)
100 self.now = now
101 eta = steptime * ((self.total - self.num)/self.stepsize)
103 # tell it like it is (or might be)
104 if now - self.start > 3:
105 M = eta / 60
106 H = M / 60
107 M = M % 60
108 S = eta % 60
109 if self.total:
110 s = '%s %2d%% (ETA %02d:%02d:%02d)'%(self.info,
111 self.num * 100. / self.total, H, M, S)
112 else:
113 s = '%s 0%% (ETA %02d:%02d:%02d)'%(self.info, H, M, S)
114 elif self.total:
115 s = '%s %2d%%'%(self.info, self.num * 100. / self.total)
116 else:
117 s = '%s %d done'%(self.info, self.num)
118 sys.stdout.write(s + ' '*(75-len(s)) + '\r')
119 sys.stdout.flush()
121 LEFT = 'left'
122 LEFTN = 'left no strip'
123 RIGHT = 'right'
124 CENTER = 'center'
126 def align(line, width=70, alignment=LEFTN):
127 ''' Code from http://www.faqts.com/knowledge_base/view.phtml/aid/4476 '''
128 if alignment == CENTER:
129 line = line.strip()
130 space = width - len(line)
131 return ' '*(space/2) + line + ' '*(space/2 + space%2)
132 elif alignment == RIGHT:
133 line = line.rstrip()
134 space = width - len(line)
135 return ' '*space + line
136 else:
137 if alignment == LEFT:
138 line = line.lstrip()
139 space = width - len(line)
140 return line + ' '*space
143 def format_line(columns, positions, contents, spacer=' | ',
144 collapse_whitespace=True, wsre=re.compile(r'\s+')):
145 ''' Fill up a single row with data from the contents '''
146 l = []
147 data = 0
148 for i in range(len(columns)):
149 width, alignment = columns[i]
150 content = contents[i]
151 col = ''
152 while positions[i] < len(content):
153 word = content[positions[i]]
154 # if we hit a newline, honor it
155 if '\n' in word:
156 # chomp
157 positions[i] += 1
158 break
160 # make sure this word fits
161 if col and len(word) + len(col) > width:
162 break
164 # no whitespace at start-of-line
165 if collapse_whitespace and wsre.match(word) and not col:
166 # chomp
167 positions[i] += 1
168 continue
170 col += word
171 # chomp
172 positions[i] += 1
173 if col:
174 data = 1
175 col = align(col, width, alignment)
176 l.append(col)
178 if not data:
179 return ''
180 return spacer.join(l).rstrip()
183 def format_columns(columns, contents, spacer=' | ', collapse_whitespace=True,
184 splitre=re.compile(r'(\n|\r\n|\r|[ \t]+|\S+)')):
185 ''' Format the contents into columns, with 'spacing' between the
186 columns
187 '''
188 assert len(columns) == len(contents), \
189 'columns and contents must be same length'
191 # split the text into words, spaces/tabs and newlines
192 for i in range(len(contents)):
193 contents[i] = splitre.findall(contents[i])
195 # now process line by line
196 l = []
197 positions = [0]*len(contents)
198 while 1:
199 l.append(format_line(columns, positions, contents, spacer,
200 collapse_whitespace))
202 # are we done?
203 for i in range(len(contents)):
204 if positions[i] < len(contents[i]):
205 break
206 else:
207 break
208 return '\n'.join(l)
210 def wrap(text, width=75, alignment=LEFTN):
211 return format_columns(((width, alignment),), [text],
212 collapse_whitespace=False)
214 # Python2.3 backwards-compatibility-hack. Should be removed (and clients
215 # fixed to use built-in reversed/sorted) when we abandon support for
216 # python2.3
217 try:
218 reversed = reversed
219 except NameError:
220 def reversed(x):
221 x = list(x)
222 x.reverse()
223 return x
225 try:
226 sorted = sorted
227 except NameError:
228 def sorted(iter, cmp=None, key=None, reverse=False):
229 if key:
230 l = []
231 cnt = 0 # cnt preserves original sort-order
232 inc = [1, -1][bool(reverse)] # count down on reverse
233 for x in iter:
234 l.append ((key(x), cnt, x))
235 cnt += inc
236 else:
237 l = list(iter)
238 if cmp:
239 l.sort(cmp = cmp)
240 else:
241 l.sort()
242 if reverse:
243 l.reverse()
244 if key:
245 return [x[-1] for x in l]
246 return l
248 # vim: set et sts=4 sw=4 :