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