Code

- fix mailgw list of methods -- use getattr so that a derived class will
[roundup.git] / roundup / support.py
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 :