1"""A readline()-style interface to the parts of a multipart message. 2 3The MultiFile class makes each part of a multipart message "feel" like 4an ordinary file, as long as you use fp.readline(). Allows recursive 5use, for nested multipart messages. Probably best used together 6with module mimetools. 7 8Suggested use: 9 10real_fp = open(...) 11fp = MultiFile(real_fp) 12 13"read some lines from fp" 14fp.push(separator) 15while 1: 16 "read lines from fp until it returns an empty string" (A) 17 if not fp.next(): break 18fp.pop() 19"read remaining lines from fp until it returns an empty string" 20 21The latter sequence may be used recursively at (A). 22It is also allowed to use multiple push()...pop() sequences. 23 24If seekable is given as 0, the class code will not do the bookkeeping 25it normally attempts in order to make seeks relative to the beginning of the 26current file part. This may be useful when using MultiFile with a non- 27seekable stream object. 28""" 29from warnings import warn 30warn("the multifile module has been deprecated since Python 2.5", 31 DeprecationWarning, stacklevel=2) 32del warn 33 34__all__ = ["MultiFile","Error"] 35 36class Error(Exception): 37 pass 38 39class MultiFile: 40 41 seekable = 0 42 43 def __init__(self, fp, seekable=1): 44 self.fp = fp 45 self.stack = [] 46 self.level = 0 47 self.last = 0 48 if seekable: 49 self.seekable = 1 50 self.start = self.fp.tell() 51 self.posstack = [] 52 53 def tell(self): 54 if self.level > 0: 55 return self.lastpos 56 return self.fp.tell() - self.start 57 58 def seek(self, pos, whence=0): 59 here = self.tell() 60 if whence: 61 if whence == 1: 62 pos = pos + here 63 elif whence == 2: 64 if self.level > 0: 65 pos = pos + self.lastpos 66 else: 67 raise Error, "can't use whence=2 yet" 68 if not 0 <= pos <= here or \ 69 self.level > 0 and pos > self.lastpos: 70 raise Error, 'bad MultiFile.seek() call' 71 self.fp.seek(pos + self.start) 72 self.level = 0 73 self.last = 0 74 75 def readline(self): 76 if self.level > 0: 77 return '' 78 line = self.fp.readline() 79 # Real EOF? 80 if not line: 81 self.level = len(self.stack) 82 self.last = (self.level > 0) 83 if self.last: 84 raise Error, 'sudden EOF in MultiFile.readline()' 85 return '' 86 assert self.level == 0 87 # Fast check to see if this is just data 88 if self.is_data(line): 89 return line 90 else: 91 # Ignore trailing whitespace on marker lines 92 marker = line.rstrip() 93 # No? OK, try to match a boundary. 94 # Return the line (unstripped) if we don't. 95 for i, sep in enumerate(reversed(self.stack)): 96 if marker == self.section_divider(sep): 97 self.last = 0 98 break 99 elif marker == self.end_marker(sep): 100 self.last = 1 101 break 102 else: 103 return line 104 # We only get here if we see a section divider or EOM line 105 if self.seekable: 106 self.lastpos = self.tell() - len(line) 107 self.level = i+1 108 if self.level > 1: 109 raise Error,'Missing endmarker in MultiFile.readline()' 110 return '' 111 112 def readlines(self): 113 list = [] 114 while 1: 115 line = self.readline() 116 if not line: break 117 list.append(line) 118 return list 119 120 def read(self): # Note: no size argument -- read until EOF only! 121 return ''.join(self.readlines()) 122 123 def next(self): 124 while self.readline(): pass 125 if self.level > 1 or self.last: 126 return 0 127 self.level = 0 128 self.last = 0 129 if self.seekable: 130 self.start = self.fp.tell() 131 return 1 132 133 def push(self, sep): 134 if self.level > 0: 135 raise Error, 'bad MultiFile.push() call' 136 self.stack.append(sep) 137 if self.seekable: 138 self.posstack.append(self.start) 139 self.start = self.fp.tell() 140 141 def pop(self): 142 if self.stack == []: 143 raise Error, 'bad MultiFile.pop() call' 144 if self.level <= 1: 145 self.last = 0 146 else: 147 abslastpos = self.lastpos + self.start 148 self.level = max(0, self.level - 1) 149 self.stack.pop() 150 if self.seekable: 151 self.start = self.posstack.pop() 152 if self.level > 0: 153 self.lastpos = abslastpos - self.start 154 155 def is_data(self, line): 156 return line[:2] != '--' 157 158 def section_divider(self, str): 159 return "--" + str 160 161 def end_marker(self, str): 162 return "--" + str + "--" 163