]> git.proxmox.com Git - mirror_edk2.git/blame - AppPkg/Applications/Python/Python-2.7.2/Lib/mhlib.py
EmbeddedPkg: Extend NvVarStoreFormattedLib LIBRARY_CLASS
[mirror_edk2.git] / AppPkg / Applications / Python / Python-2.7.2 / Lib / mhlib.py
CommitLineData
4710c53d 1"""MH interface -- purely object-oriented (well, almost)\r
2\r
3Executive summary:\r
4\r
5import mhlib\r
6\r
7mh = mhlib.MH() # use default mailbox directory and profile\r
8mh = mhlib.MH(mailbox) # override mailbox location (default from profile)\r
9mh = mhlib.MH(mailbox, profile) # override mailbox and profile\r
10\r
11mh.error(format, ...) # print error message -- can be overridden\r
12s = mh.getprofile(key) # profile entry (None if not set)\r
13path = mh.getpath() # mailbox pathname\r
14name = mh.getcontext() # name of current folder\r
15mh.setcontext(name) # set name of current folder\r
16\r
17list = mh.listfolders() # names of top-level folders\r
18list = mh.listallfolders() # names of all folders, including subfolders\r
19list = mh.listsubfolders(name) # direct subfolders of given folder\r
20list = mh.listallsubfolders(name) # all subfolders of given folder\r
21\r
22mh.makefolder(name) # create new folder\r
23mh.deletefolder(name) # delete folder -- must have no subfolders\r
24\r
25f = mh.openfolder(name) # new open folder object\r
26\r
27f.error(format, ...) # same as mh.error(format, ...)\r
28path = f.getfullname() # folder's full pathname\r
29path = f.getsequencesfilename() # full pathname of folder's sequences file\r
30path = f.getmessagefilename(n) # full pathname of message n in folder\r
31\r
32list = f.listmessages() # list of messages in folder (as numbers)\r
33n = f.getcurrent() # get current message\r
34f.setcurrent(n) # set current message\r
35list = f.parsesequence(seq) # parse msgs syntax into list of messages\r
36n = f.getlast() # get last message (0 if no messagse)\r
37f.setlast(n) # set last message (internal use only)\r
38\r
39dict = f.getsequences() # dictionary of sequences in folder {name: list}\r
40f.putsequences(dict) # write sequences back to folder\r
41\r
42f.createmessage(n, fp) # add message from file f as number n\r
43f.removemessages(list) # remove messages in list from folder\r
44f.refilemessages(list, tofolder) # move messages in list to other folder\r
45f.movemessage(n, tofolder, ton) # move one message to a given destination\r
46f.copymessage(n, tofolder, ton) # copy one message to a given destination\r
47\r
48m = f.openmessage(n) # new open message object (costs a file descriptor)\r
49m is a derived class of mimetools.Message(rfc822.Message), with:\r
50s = m.getheadertext() # text of message's headers\r
51s = m.getheadertext(pred) # text of message's headers, filtered by pred\r
52s = m.getbodytext() # text of message's body, decoded\r
53s = m.getbodytext(0) # text of message's body, not decoded\r
54"""\r
55from warnings import warnpy3k\r
56warnpy3k("the mhlib module has been removed in Python 3.0; use the mailbox "\r
57 "module instead", stacklevel=2)\r
58del warnpy3k\r
59\r
60# XXX To do, functionality:\r
61# - annotate messages\r
62# - send messages\r
63#\r
64# XXX To do, organization:\r
65# - move IntSet to separate file\r
66# - move most Message functionality to module mimetools\r
67\r
68\r
69# Customizable defaults\r
70\r
71MH_PROFILE = '~/.mh_profile'\r
72PATH = '~/Mail'\r
73MH_SEQUENCES = '.mh_sequences'\r
74FOLDER_PROTECT = 0700\r
75\r
76\r
77# Imported modules\r
78\r
79import os\r
80import sys\r
81import re\r
82import mimetools\r
83import multifile\r
84import shutil\r
85from bisect import bisect\r
86\r
87__all__ = ["MH","Error","Folder","Message"]\r
88\r
89# Exported constants\r
90\r
91class Error(Exception):\r
92 pass\r
93\r
94\r
95class MH:\r
96 """Class representing a particular collection of folders.\r
97 Optional constructor arguments are the pathname for the directory\r
98 containing the collection, and the MH profile to use.\r
99 If either is omitted or empty a default is used; the default\r
100 directory is taken from the MH profile if it is specified there."""\r
101\r
102 def __init__(self, path = None, profile = None):\r
103 """Constructor."""\r
104 if profile is None: profile = MH_PROFILE\r
105 self.profile = os.path.expanduser(profile)\r
106 if path is None: path = self.getprofile('Path')\r
107 if not path: path = PATH\r
108 if not os.path.isabs(path) and path[0] != '~':\r
109 path = os.path.join('~', path)\r
110 path = os.path.expanduser(path)\r
111 if not os.path.isdir(path): raise Error, 'MH() path not found'\r
112 self.path = path\r
113\r
114 def __repr__(self):\r
115 """String representation."""\r
116 return 'MH(%r, %r)' % (self.path, self.profile)\r
117\r
118 def error(self, msg, *args):\r
119 """Routine to print an error. May be overridden by a derived class."""\r
120 sys.stderr.write('MH error: %s\n' % (msg % args))\r
121\r
122 def getprofile(self, key):\r
123 """Return a profile entry, None if not found."""\r
124 return pickline(self.profile, key)\r
125\r
126 def getpath(self):\r
127 """Return the path (the name of the collection's directory)."""\r
128 return self.path\r
129\r
130 def getcontext(self):\r
131 """Return the name of the current folder."""\r
132 context = pickline(os.path.join(self.getpath(), 'context'),\r
133 'Current-Folder')\r
134 if not context: context = 'inbox'\r
135 return context\r
136\r
137 def setcontext(self, context):\r
138 """Set the name of the current folder."""\r
139 fn = os.path.join(self.getpath(), 'context')\r
140 f = open(fn, "w")\r
141 f.write("Current-Folder: %s\n" % context)\r
142 f.close()\r
143\r
144 def listfolders(self):\r
145 """Return the names of the top-level folders."""\r
146 folders = []\r
147 path = self.getpath()\r
148 for name in os.listdir(path):\r
149 fullname = os.path.join(path, name)\r
150 if os.path.isdir(fullname):\r
151 folders.append(name)\r
152 folders.sort()\r
153 return folders\r
154\r
155 def listsubfolders(self, name):\r
156 """Return the names of the subfolders in a given folder\r
157 (prefixed with the given folder name)."""\r
158 fullname = os.path.join(self.path, name)\r
159 # Get the link count so we can avoid listing folders\r
160 # that have no subfolders.\r
161 nlinks = os.stat(fullname).st_nlink\r
162 if nlinks <= 2:\r
163 return []\r
164 subfolders = []\r
165 subnames = os.listdir(fullname)\r
166 for subname in subnames:\r
167 fullsubname = os.path.join(fullname, subname)\r
168 if os.path.isdir(fullsubname):\r
169 name_subname = os.path.join(name, subname)\r
170 subfolders.append(name_subname)\r
171 # Stop looking for subfolders when\r
172 # we've seen them all\r
173 nlinks = nlinks - 1\r
174 if nlinks <= 2:\r
175 break\r
176 subfolders.sort()\r
177 return subfolders\r
178\r
179 def listallfolders(self):\r
180 """Return the names of all folders and subfolders, recursively."""\r
181 return self.listallsubfolders('')\r
182\r
183 def listallsubfolders(self, name):\r
184 """Return the names of subfolders in a given folder, recursively."""\r
185 fullname = os.path.join(self.path, name)\r
186 # Get the link count so we can avoid listing folders\r
187 # that have no subfolders.\r
188 nlinks = os.stat(fullname).st_nlink\r
189 if nlinks <= 2:\r
190 return []\r
191 subfolders = []\r
192 subnames = os.listdir(fullname)\r
193 for subname in subnames:\r
194 if subname[0] == ',' or isnumeric(subname): continue\r
195 fullsubname = os.path.join(fullname, subname)\r
196 if os.path.isdir(fullsubname):\r
197 name_subname = os.path.join(name, subname)\r
198 subfolders.append(name_subname)\r
199 if not os.path.islink(fullsubname):\r
200 subsubfolders = self.listallsubfolders(\r
201 name_subname)\r
202 subfolders = subfolders + subsubfolders\r
203 # Stop looking for subfolders when\r
204 # we've seen them all\r
205 nlinks = nlinks - 1\r
206 if nlinks <= 2:\r
207 break\r
208 subfolders.sort()\r
209 return subfolders\r
210\r
211 def openfolder(self, name):\r
212 """Return a new Folder object for the named folder."""\r
213 return Folder(self, name)\r
214\r
215 def makefolder(self, name):\r
216 """Create a new folder (or raise os.error if it cannot be created)."""\r
217 protect = pickline(self.profile, 'Folder-Protect')\r
218 if protect and isnumeric(protect):\r
219 mode = int(protect, 8)\r
220 else:\r
221 mode = FOLDER_PROTECT\r
222 os.mkdir(os.path.join(self.getpath(), name), mode)\r
223\r
224 def deletefolder(self, name):\r
225 """Delete a folder. This removes files in the folder but not\r
226 subdirectories. Raise os.error if deleting the folder itself fails."""\r
227 fullname = os.path.join(self.getpath(), name)\r
228 for subname in os.listdir(fullname):\r
229 fullsubname = os.path.join(fullname, subname)\r
230 try:\r
231 os.unlink(fullsubname)\r
232 except os.error:\r
233 self.error('%s not deleted, continuing...' %\r
234 fullsubname)\r
235 os.rmdir(fullname)\r
236\r
237\r
238numericprog = re.compile('^[1-9][0-9]*$')\r
239def isnumeric(str):\r
240 return numericprog.match(str) is not None\r
241\r
242class Folder:\r
243 """Class representing a particular folder."""\r
244\r
245 def __init__(self, mh, name):\r
246 """Constructor."""\r
247 self.mh = mh\r
248 self.name = name\r
249 if not os.path.isdir(self.getfullname()):\r
250 raise Error, 'no folder %s' % name\r
251\r
252 def __repr__(self):\r
253 """String representation."""\r
254 return 'Folder(%r, %r)' % (self.mh, self.name)\r
255\r
256 def error(self, *args):\r
257 """Error message handler."""\r
258 self.mh.error(*args)\r
259\r
260 def getfullname(self):\r
261 """Return the full pathname of the folder."""\r
262 return os.path.join(self.mh.path, self.name)\r
263\r
264 def getsequencesfilename(self):\r
265 """Return the full pathname of the folder's sequences file."""\r
266 return os.path.join(self.getfullname(), MH_SEQUENCES)\r
267\r
268 def getmessagefilename(self, n):\r
269 """Return the full pathname of a message in the folder."""\r
270 return os.path.join(self.getfullname(), str(n))\r
271\r
272 def listsubfolders(self):\r
273 """Return list of direct subfolders."""\r
274 return self.mh.listsubfolders(self.name)\r
275\r
276 def listallsubfolders(self):\r
277 """Return list of all subfolders."""\r
278 return self.mh.listallsubfolders(self.name)\r
279\r
280 def listmessages(self):\r
281 """Return the list of messages currently present in the folder.\r
282 As a side effect, set self.last to the last message (or 0)."""\r
283 messages = []\r
284 match = numericprog.match\r
285 append = messages.append\r
286 for name in os.listdir(self.getfullname()):\r
287 if match(name):\r
288 append(name)\r
289 messages = map(int, messages)\r
290 messages.sort()\r
291 if messages:\r
292 self.last = messages[-1]\r
293 else:\r
294 self.last = 0\r
295 return messages\r
296\r
297 def getsequences(self):\r
298 """Return the set of sequences for the folder."""\r
299 sequences = {}\r
300 fullname = self.getsequencesfilename()\r
301 try:\r
302 f = open(fullname, 'r')\r
303 except IOError:\r
304 return sequences\r
305 while 1:\r
306 line = f.readline()\r
307 if not line: break\r
308 fields = line.split(':')\r
309 if len(fields) != 2:\r
310 self.error('bad sequence in %s: %s' %\r
311 (fullname, line.strip()))\r
312 key = fields[0].strip()\r
313 value = IntSet(fields[1].strip(), ' ').tolist()\r
314 sequences[key] = value\r
315 return sequences\r
316\r
317 def putsequences(self, sequences):\r
318 """Write the set of sequences back to the folder."""\r
319 fullname = self.getsequencesfilename()\r
320 f = None\r
321 for key, seq in sequences.iteritems():\r
322 s = IntSet('', ' ')\r
323 s.fromlist(seq)\r
324 if not f: f = open(fullname, 'w')\r
325 f.write('%s: %s\n' % (key, s.tostring()))\r
326 if not f:\r
327 try:\r
328 os.unlink(fullname)\r
329 except os.error:\r
330 pass\r
331 else:\r
332 f.close()\r
333\r
334 def getcurrent(self):\r
335 """Return the current message. Raise Error when there is none."""\r
336 seqs = self.getsequences()\r
337 try:\r
338 return max(seqs['cur'])\r
339 except (ValueError, KeyError):\r
340 raise Error, "no cur message"\r
341\r
342 def setcurrent(self, n):\r
343 """Set the current message."""\r
344 updateline(self.getsequencesfilename(), 'cur', str(n), 0)\r
345\r
346 def parsesequence(self, seq):\r
347 """Parse an MH sequence specification into a message list.\r
348 Attempt to mimic mh-sequence(5) as close as possible.\r
349 Also attempt to mimic observed behavior regarding which\r
350 conditions cause which error messages."""\r
351 # XXX Still not complete (see mh-format(5)).\r
352 # Missing are:\r
353 # - 'prev', 'next' as count\r
354 # - Sequence-Negation option\r
355 all = self.listmessages()\r
356 # Observed behavior: test for empty folder is done first\r
357 if not all:\r
358 raise Error, "no messages in %s" % self.name\r
359 # Common case first: all is frequently the default\r
360 if seq == 'all':\r
361 return all\r
362 # Test for X:Y before X-Y because 'seq:-n' matches both\r
363 i = seq.find(':')\r
364 if i >= 0:\r
365 head, dir, tail = seq[:i], '', seq[i+1:]\r
366 if tail[:1] in '-+':\r
367 dir, tail = tail[:1], tail[1:]\r
368 if not isnumeric(tail):\r
369 raise Error, "bad message list %s" % seq\r
370 try:\r
371 count = int(tail)\r
372 except (ValueError, OverflowError):\r
373 # Can't use sys.maxint because of i+count below\r
374 count = len(all)\r
375 try:\r
376 anchor = self._parseindex(head, all)\r
377 except Error, msg:\r
378 seqs = self.getsequences()\r
379 if not head in seqs:\r
380 if not msg:\r
381 msg = "bad message list %s" % seq\r
382 raise Error, msg, sys.exc_info()[2]\r
383 msgs = seqs[head]\r
384 if not msgs:\r
385 raise Error, "sequence %s empty" % head\r
386 if dir == '-':\r
387 return msgs[-count:]\r
388 else:\r
389 return msgs[:count]\r
390 else:\r
391 if not dir:\r
392 if head in ('prev', 'last'):\r
393 dir = '-'\r
394 if dir == '-':\r
395 i = bisect(all, anchor)\r
396 return all[max(0, i-count):i]\r
397 else:\r
398 i = bisect(all, anchor-1)\r
399 return all[i:i+count]\r
400 # Test for X-Y next\r
401 i = seq.find('-')\r
402 if i >= 0:\r
403 begin = self._parseindex(seq[:i], all)\r
404 end = self._parseindex(seq[i+1:], all)\r
405 i = bisect(all, begin-1)\r
406 j = bisect(all, end)\r
407 r = all[i:j]\r
408 if not r:\r
409 raise Error, "bad message list %s" % seq\r
410 return r\r
411 # Neither X:Y nor X-Y; must be a number or a (pseudo-)sequence\r
412 try:\r
413 n = self._parseindex(seq, all)\r
414 except Error, msg:\r
415 seqs = self.getsequences()\r
416 if not seq in seqs:\r
417 if not msg:\r
418 msg = "bad message list %s" % seq\r
419 raise Error, msg\r
420 return seqs[seq]\r
421 else:\r
422 if n not in all:\r
423 if isnumeric(seq):\r
424 raise Error, "message %d doesn't exist" % n\r
425 else:\r
426 raise Error, "no %s message" % seq\r
427 else:\r
428 return [n]\r
429\r
430 def _parseindex(self, seq, all):\r
431 """Internal: parse a message number (or cur, first, etc.)."""\r
432 if isnumeric(seq):\r
433 try:\r
434 return int(seq)\r
435 except (OverflowError, ValueError):\r
436 return sys.maxint\r
437 if seq in ('cur', '.'):\r
438 return self.getcurrent()\r
439 if seq == 'first':\r
440 return all[0]\r
441 if seq == 'last':\r
442 return all[-1]\r
443 if seq == 'next':\r
444 n = self.getcurrent()\r
445 i = bisect(all, n)\r
446 try:\r
447 return all[i]\r
448 except IndexError:\r
449 raise Error, "no next message"\r
450 if seq == 'prev':\r
451 n = self.getcurrent()\r
452 i = bisect(all, n-1)\r
453 if i == 0:\r
454 raise Error, "no prev message"\r
455 try:\r
456 return all[i-1]\r
457 except IndexError:\r
458 raise Error, "no prev message"\r
459 raise Error, None\r
460\r
461 def openmessage(self, n):\r
462 """Open a message -- returns a Message object."""\r
463 return Message(self, n)\r
464\r
465 def removemessages(self, list):\r
466 """Remove one or more messages -- may raise os.error."""\r
467 errors = []\r
468 deleted = []\r
469 for n in list:\r
470 path = self.getmessagefilename(n)\r
471 commapath = self.getmessagefilename(',' + str(n))\r
472 try:\r
473 os.unlink(commapath)\r
474 except os.error:\r
475 pass\r
476 try:\r
477 os.rename(path, commapath)\r
478 except os.error, msg:\r
479 errors.append(msg)\r
480 else:\r
481 deleted.append(n)\r
482 if deleted:\r
483 self.removefromallsequences(deleted)\r
484 if errors:\r
485 if len(errors) == 1:\r
486 raise os.error, errors[0]\r
487 else:\r
488 raise os.error, ('multiple errors:', errors)\r
489\r
490 def refilemessages(self, list, tofolder, keepsequences=0):\r
491 """Refile one or more messages -- may raise os.error.\r
492 'tofolder' is an open folder object."""\r
493 errors = []\r
494 refiled = {}\r
495 for n in list:\r
496 ton = tofolder.getlast() + 1\r
497 path = self.getmessagefilename(n)\r
498 topath = tofolder.getmessagefilename(ton)\r
499 try:\r
500 os.rename(path, topath)\r
501 except os.error:\r
502 # Try copying\r
503 try:\r
504 shutil.copy2(path, topath)\r
505 os.unlink(path)\r
506 except (IOError, os.error), msg:\r
507 errors.append(msg)\r
508 try:\r
509 os.unlink(topath)\r
510 except os.error:\r
511 pass\r
512 continue\r
513 tofolder.setlast(ton)\r
514 refiled[n] = ton\r
515 if refiled:\r
516 if keepsequences:\r
517 tofolder._copysequences(self, refiled.items())\r
518 self.removefromallsequences(refiled.keys())\r
519 if errors:\r
520 if len(errors) == 1:\r
521 raise os.error, errors[0]\r
522 else:\r
523 raise os.error, ('multiple errors:', errors)\r
524\r
525 def _copysequences(self, fromfolder, refileditems):\r
526 """Helper for refilemessages() to copy sequences."""\r
527 fromsequences = fromfolder.getsequences()\r
528 tosequences = self.getsequences()\r
529 changed = 0\r
530 for name, seq in fromsequences.items():\r
531 try:\r
532 toseq = tosequences[name]\r
533 new = 0\r
534 except KeyError:\r
535 toseq = []\r
536 new = 1\r
537 for fromn, ton in refileditems:\r
538 if fromn in seq:\r
539 toseq.append(ton)\r
540 changed = 1\r
541 if new and toseq:\r
542 tosequences[name] = toseq\r
543 if changed:\r
544 self.putsequences(tosequences)\r
545\r
546 def movemessage(self, n, tofolder, ton):\r
547 """Move one message over a specific destination message,\r
548 which may or may not already exist."""\r
549 path = self.getmessagefilename(n)\r
550 # Open it to check that it exists\r
551 f = open(path)\r
552 f.close()\r
553 del f\r
554 topath = tofolder.getmessagefilename(ton)\r
555 backuptopath = tofolder.getmessagefilename(',%d' % ton)\r
556 try:\r
557 os.rename(topath, backuptopath)\r
558 except os.error:\r
559 pass\r
560 try:\r
561 os.rename(path, topath)\r
562 except os.error:\r
563 # Try copying\r
564 ok = 0\r
565 try:\r
566 tofolder.setlast(None)\r
567 shutil.copy2(path, topath)\r
568 ok = 1\r
569 finally:\r
570 if not ok:\r
571 try:\r
572 os.unlink(topath)\r
573 except os.error:\r
574 pass\r
575 os.unlink(path)\r
576 self.removefromallsequences([n])\r
577\r
578 def copymessage(self, n, tofolder, ton):\r
579 """Copy one message over a specific destination message,\r
580 which may or may not already exist."""\r
581 path = self.getmessagefilename(n)\r
582 # Open it to check that it exists\r
583 f = open(path)\r
584 f.close()\r
585 del f\r
586 topath = tofolder.getmessagefilename(ton)\r
587 backuptopath = tofolder.getmessagefilename(',%d' % ton)\r
588 try:\r
589 os.rename(topath, backuptopath)\r
590 except os.error:\r
591 pass\r
592 ok = 0\r
593 try:\r
594 tofolder.setlast(None)\r
595 shutil.copy2(path, topath)\r
596 ok = 1\r
597 finally:\r
598 if not ok:\r
599 try:\r
600 os.unlink(topath)\r
601 except os.error:\r
602 pass\r
603\r
604 def createmessage(self, n, txt):\r
605 """Create a message, with text from the open file txt."""\r
606 path = self.getmessagefilename(n)\r
607 backuppath = self.getmessagefilename(',%d' % n)\r
608 try:\r
609 os.rename(path, backuppath)\r
610 except os.error:\r
611 pass\r
612 ok = 0\r
613 BUFSIZE = 16*1024\r
614 try:\r
615 f = open(path, "w")\r
616 while 1:\r
617 buf = txt.read(BUFSIZE)\r
618 if not buf:\r
619 break\r
620 f.write(buf)\r
621 f.close()\r
622 ok = 1\r
623 finally:\r
624 if not ok:\r
625 try:\r
626 os.unlink(path)\r
627 except os.error:\r
628 pass\r
629\r
630 def removefromallsequences(self, list):\r
631 """Remove one or more messages from all sequences (including last)\r
632 -- but not from 'cur'!!!"""\r
633 if hasattr(self, 'last') and self.last in list:\r
634 del self.last\r
635 sequences = self.getsequences()\r
636 changed = 0\r
637 for name, seq in sequences.items():\r
638 if name == 'cur':\r
639 continue\r
640 for n in list:\r
641 if n in seq:\r
642 seq.remove(n)\r
643 changed = 1\r
644 if not seq:\r
645 del sequences[name]\r
646 if changed:\r
647 self.putsequences(sequences)\r
648\r
649 def getlast(self):\r
650 """Return the last message number."""\r
651 if not hasattr(self, 'last'):\r
652 self.listmessages() # Set self.last\r
653 return self.last\r
654\r
655 def setlast(self, last):\r
656 """Set the last message number."""\r
657 if last is None:\r
658 if hasattr(self, 'last'):\r
659 del self.last\r
660 else:\r
661 self.last = last\r
662\r
663class Message(mimetools.Message):\r
664\r
665 def __init__(self, f, n, fp = None):\r
666 """Constructor."""\r
667 self.folder = f\r
668 self.number = n\r
669 if fp is None:\r
670 path = f.getmessagefilename(n)\r
671 fp = open(path, 'r')\r
672 mimetools.Message.__init__(self, fp)\r
673\r
674 def __repr__(self):\r
675 """String representation."""\r
676 return 'Message(%s, %s)' % (repr(self.folder), self.number)\r
677\r
678 def getheadertext(self, pred = None):\r
679 """Return the message's header text as a string. If an\r
680 argument is specified, it is used as a filter predicate to\r
681 decide which headers to return (its argument is the header\r
682 name converted to lower case)."""\r
683 if pred is None:\r
684 return ''.join(self.headers)\r
685 headers = []\r
686 hit = 0\r
687 for line in self.headers:\r
688 if not line[0].isspace():\r
689 i = line.find(':')\r
690 if i > 0:\r
691 hit = pred(line[:i].lower())\r
692 if hit: headers.append(line)\r
693 return ''.join(headers)\r
694\r
695 def getbodytext(self, decode = 1):\r
696 """Return the message's body text as string. This undoes a\r
697 Content-Transfer-Encoding, but does not interpret other MIME\r
698 features (e.g. multipart messages). To suppress decoding,\r
699 pass 0 as an argument."""\r
700 self.fp.seek(self.startofbody)\r
701 encoding = self.getencoding()\r
702 if not decode or encoding in ('', '7bit', '8bit', 'binary'):\r
703 return self.fp.read()\r
704 try:\r
705 from cStringIO import StringIO\r
706 except ImportError:\r
707 from StringIO import StringIO\r
708 output = StringIO()\r
709 mimetools.decode(self.fp, output, encoding)\r
710 return output.getvalue()\r
711\r
712 def getbodyparts(self):\r
713 """Only for multipart messages: return the message's body as a\r
714 list of SubMessage objects. Each submessage object behaves\r
715 (almost) as a Message object."""\r
716 if self.getmaintype() != 'multipart':\r
717 raise Error, 'Content-Type is not multipart/*'\r
718 bdry = self.getparam('boundary')\r
719 if not bdry:\r
720 raise Error, 'multipart/* without boundary param'\r
721 self.fp.seek(self.startofbody)\r
722 mf = multifile.MultiFile(self.fp)\r
723 mf.push(bdry)\r
724 parts = []\r
725 while mf.next():\r
726 n = "%s.%r" % (self.number, 1 + len(parts))\r
727 part = SubMessage(self.folder, n, mf)\r
728 parts.append(part)\r
729 mf.pop()\r
730 return parts\r
731\r
732 def getbody(self):\r
733 """Return body, either a string or a list of messages."""\r
734 if self.getmaintype() == 'multipart':\r
735 return self.getbodyparts()\r
736 else:\r
737 return self.getbodytext()\r
738\r
739\r
740class SubMessage(Message):\r
741\r
742 def __init__(self, f, n, fp):\r
743 """Constructor."""\r
744 Message.__init__(self, f, n, fp)\r
745 if self.getmaintype() == 'multipart':\r
746 self.body = Message.getbodyparts(self)\r
747 else:\r
748 self.body = Message.getbodytext(self)\r
749 self.bodyencoded = Message.getbodytext(self, decode=0)\r
750 # XXX If this is big, should remember file pointers\r
751\r
752 def __repr__(self):\r
753 """String representation."""\r
754 f, n, fp = self.folder, self.number, self.fp\r
755 return 'SubMessage(%s, %s, %s)' % (f, n, fp)\r
756\r
757 def getbodytext(self, decode = 1):\r
758 if not decode:\r
759 return self.bodyencoded\r
760 if type(self.body) == type(''):\r
761 return self.body\r
762\r
763 def getbodyparts(self):\r
764 if type(self.body) == type([]):\r
765 return self.body\r
766\r
767 def getbody(self):\r
768 return self.body\r
769\r
770\r
771class IntSet:\r
772 """Class implementing sets of integers.\r
773\r
774 This is an efficient representation for sets consisting of several\r
775 continuous ranges, e.g. 1-100,200-400,402-1000 is represented\r
776 internally as a list of three pairs: [(1,100), (200,400),\r
777 (402,1000)]. The internal representation is always kept normalized.\r
778\r
779 The constructor has up to three arguments:\r
780 - the string used to initialize the set (default ''),\r
781 - the separator between ranges (default ',')\r
782 - the separator between begin and end of a range (default '-')\r
783 The separators must be strings (not regexprs) and should be different.\r
784\r
785 The tostring() function yields a string that can be passed to another\r
786 IntSet constructor; __repr__() is a valid IntSet constructor itself.\r
787 """\r
788\r
789 # XXX The default begin/end separator means that negative numbers are\r
790 # not supported very well.\r
791 #\r
792 # XXX There are currently no operations to remove set elements.\r
793\r
794 def __init__(self, data = None, sep = ',', rng = '-'):\r
795 self.pairs = []\r
796 self.sep = sep\r
797 self.rng = rng\r
798 if data: self.fromstring(data)\r
799\r
800 def reset(self):\r
801 self.pairs = []\r
802\r
803 def __cmp__(self, other):\r
804 return cmp(self.pairs, other.pairs)\r
805\r
806 def __hash__(self):\r
807 return hash(self.pairs)\r
808\r
809 def __repr__(self):\r
810 return 'IntSet(%r, %r, %r)' % (self.tostring(), self.sep, self.rng)\r
811\r
812 def normalize(self):\r
813 self.pairs.sort()\r
814 i = 1\r
815 while i < len(self.pairs):\r
816 alo, ahi = self.pairs[i-1]\r
817 blo, bhi = self.pairs[i]\r
818 if ahi >= blo-1:\r
819 self.pairs[i-1:i+1] = [(alo, max(ahi, bhi))]\r
820 else:\r
821 i = i+1\r
822\r
823 def tostring(self):\r
824 s = ''\r
825 for lo, hi in self.pairs:\r
826 if lo == hi: t = repr(lo)\r
827 else: t = repr(lo) + self.rng + repr(hi)\r
828 if s: s = s + (self.sep + t)\r
829 else: s = t\r
830 return s\r
831\r
832 def tolist(self):\r
833 l = []\r
834 for lo, hi in self.pairs:\r
835 m = range(lo, hi+1)\r
836 l = l + m\r
837 return l\r
838\r
839 def fromlist(self, list):\r
840 for i in list:\r
841 self.append(i)\r
842\r
843 def clone(self):\r
844 new = IntSet()\r
845 new.pairs = self.pairs[:]\r
846 return new\r
847\r
848 def min(self):\r
849 return self.pairs[0][0]\r
850\r
851 def max(self):\r
852 return self.pairs[-1][-1]\r
853\r
854 def contains(self, x):\r
855 for lo, hi in self.pairs:\r
856 if lo <= x <= hi: return True\r
857 return False\r
858\r
859 def append(self, x):\r
860 for i in range(len(self.pairs)):\r
861 lo, hi = self.pairs[i]\r
862 if x < lo: # Need to insert before\r
863 if x+1 == lo:\r
864 self.pairs[i] = (x, hi)\r
865 else:\r
866 self.pairs.insert(i, (x, x))\r
867 if i > 0 and x-1 == self.pairs[i-1][1]:\r
868 # Merge with previous\r
869 self.pairs[i-1:i+1] = [\r
870 (self.pairs[i-1][0],\r
871 self.pairs[i][1])\r
872 ]\r
873 return\r
874 if x <= hi: # Already in set\r
875 return\r
876 i = len(self.pairs) - 1\r
877 if i >= 0:\r
878 lo, hi = self.pairs[i]\r
879 if x-1 == hi:\r
880 self.pairs[i] = lo, x\r
881 return\r
882 self.pairs.append((x, x))\r
883\r
884 def addpair(self, xlo, xhi):\r
885 if xlo > xhi: return\r
886 self.pairs.append((xlo, xhi))\r
887 self.normalize()\r
888\r
889 def fromstring(self, data):\r
890 new = []\r
891 for part in data.split(self.sep):\r
892 list = []\r
893 for subp in part.split(self.rng):\r
894 s = subp.strip()\r
895 list.append(int(s))\r
896 if len(list) == 1:\r
897 new.append((list[0], list[0]))\r
898 elif len(list) == 2 and list[0] <= list[1]:\r
899 new.append((list[0], list[1]))\r
900 else:\r
901 raise ValueError, 'bad data passed to IntSet'\r
902 self.pairs = self.pairs + new\r
903 self.normalize()\r
904\r
905\r
906# Subroutines to read/write entries in .mh_profile and .mh_sequences\r
907\r
908def pickline(file, key, casefold = 1):\r
909 try:\r
910 f = open(file, 'r')\r
911 except IOError:\r
912 return None\r
913 pat = re.escape(key) + ':'\r
914 prog = re.compile(pat, casefold and re.IGNORECASE)\r
915 while 1:\r
916 line = f.readline()\r
917 if not line: break\r
918 if prog.match(line):\r
919 text = line[len(key)+1:]\r
920 while 1:\r
921 line = f.readline()\r
922 if not line or not line[0].isspace():\r
923 break\r
924 text = text + line\r
925 return text.strip()\r
926 return None\r
927\r
928def updateline(file, key, value, casefold = 1):\r
929 try:\r
930 f = open(file, 'r')\r
931 lines = f.readlines()\r
932 f.close()\r
933 except IOError:\r
934 lines = []\r
935 pat = re.escape(key) + ':(.*)\n'\r
936 prog = re.compile(pat, casefold and re.IGNORECASE)\r
937 if value is None:\r
938 newline = None\r
939 else:\r
940 newline = '%s: %s\n' % (key, value)\r
941 for i in range(len(lines)):\r
942 line = lines[i]\r
943 if prog.match(line):\r
944 if newline is None:\r
945 del lines[i]\r
946 else:\r
947 lines[i] = newline\r
948 break\r
949 else:\r
950 if newline is not None:\r
951 lines.append(newline)\r
952 tempfile = file + "~"\r
953 f = open(tempfile, 'w')\r
954 for line in lines:\r
955 f.write(line)\r
956 f.close()\r
957 os.rename(tempfile, file)\r
958\r
959\r
960# Test program\r
961\r
962def test():\r
963 global mh, f\r
964 os.system('rm -rf $HOME/Mail/@test')\r
965 mh = MH()\r
966 def do(s): print s; print eval(s)\r
967 do('mh.listfolders()')\r
968 do('mh.listallfolders()')\r
969 testfolders = ['@test', '@test/test1', '@test/test2',\r
970 '@test/test1/test11', '@test/test1/test12',\r
971 '@test/test1/test11/test111']\r
972 for t in testfolders: do('mh.makefolder(%r)' % (t,))\r
973 do('mh.listsubfolders(\'@test\')')\r
974 do('mh.listallsubfolders(\'@test\')')\r
975 f = mh.openfolder('@test')\r
976 do('f.listsubfolders()')\r
977 do('f.listallsubfolders()')\r
978 do('f.getsequences()')\r
979 seqs = f.getsequences()\r
980 seqs['foo'] = IntSet('1-10 12-20', ' ').tolist()\r
981 print seqs\r
982 f.putsequences(seqs)\r
983 do('f.getsequences()')\r
984 for t in reversed(testfolders): do('mh.deletefolder(%r)' % (t,))\r
985 do('mh.getcontext()')\r
986 context = mh.getcontext()\r
987 f = mh.openfolder(context)\r
988 do('f.getcurrent()')\r
989 for seq in ('first', 'last', 'cur', '.', 'prev', 'next',\r
990 'first:3', 'last:3', 'cur:3', 'cur:-3',\r
991 'prev:3', 'next:3',\r
992 '1:3', '1:-3', '100:3', '100:-3', '10000:3', '10000:-3',\r
993 'all'):\r
994 try:\r
995 do('f.parsesequence(%r)' % (seq,))\r
996 except Error, msg:\r
997 print "Error:", msg\r
998 stuff = os.popen("pick %r 2>/dev/null" % (seq,)).read()\r
999 list = map(int, stuff.split())\r
1000 print list, "<-- pick"\r
1001 do('f.listmessages()')\r
1002\r
1003\r
1004if __name__ == '__main__':\r
1005 test()\r