Move setting of ioready 'wait' earlier in call chain, to
[python/dscho.git] / Lib / multifile.py
blob23d9d317193969e960a74f3356b5a0716fffe6b8
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 __all__ = ["MultiFile","Error"]
32 class Error(Exception):
33 pass
35 class MultiFile:
37 seekable = 0
39 def __init__(self, fp, seekable=1):
40 self.fp = fp
41 self.stack = [] # Grows down
42 self.level = 0
43 self.last = 0
44 if seekable:
45 self.seekable = 1
46 self.start = self.fp.tell()
47 self.posstack = [] # Grows down
49 def tell(self):
50 if self.level > 0:
51 return self.lastpos
52 return self.fp.tell() - self.start
54 def seek(self, pos, whence=0):
55 here = self.tell()
56 if whence:
57 if whence == 1:
58 pos = pos + here
59 elif whence == 2:
60 if self.level > 0:
61 pos = pos + self.lastpos
62 else:
63 raise Error, "can't use whence=2 yet"
64 if not 0 <= pos <= here or \
65 self.level > 0 and pos > self.lastpos:
66 raise Error, 'bad MultiFile.seek() call'
67 self.fp.seek(pos + self.start)
68 self.level = 0
69 self.last = 0
71 def readline(self):
72 if self.level > 0:
73 return ''
74 line = self.fp.readline()
75 # Real EOF?
76 if not line:
77 self.level = len(self.stack)
78 self.last = (self.level > 0)
79 if self.last:
80 raise Error, 'sudden EOF in MultiFile.readline()'
81 return ''
82 assert self.level == 0
83 # Fast check to see if this is just data
84 if self.is_data(line):
85 return line
86 else:
87 # Ignore trailing whitespace on marker lines
88 marker = line.rstrip()
89 # No? OK, try to match a boundary.
90 # Return the line (unstripped) if we don't.
91 for i in range(len(self.stack)):
92 sep = self.stack[i]
93 if marker == self.section_divider(sep):
94 self.last = 0
95 break
96 elif marker == self.end_marker(sep):
97 self.last = 1
98 break
99 else:
100 return line
101 # We only get here if we see a section divider or EOM line
102 if self.seekable:
103 self.lastpos = self.tell() - len(line)
104 self.level = i+1
105 if self.level > 1:
106 raise Error,'Missing endmarker in MultiFile.readline()'
107 return ''
109 def readlines(self):
110 list = []
111 while 1:
112 line = self.readline()
113 if not line: break
114 list.append(line)
115 return list
117 def read(self): # Note: no size argument -- read until EOF only!
118 return ''.join(self.readlines())
120 def next(self):
121 while self.readline(): pass
122 if self.level > 1 or self.last:
123 return 0
124 self.level = 0
125 self.last = 0
126 if self.seekable:
127 self.start = self.fp.tell()
128 return 1
130 def push(self, sep):
131 if self.level > 0:
132 raise Error, 'bad MultiFile.push() call'
133 self.stack.insert(0, sep)
134 if self.seekable:
135 self.posstack.insert(0, self.start)
136 self.start = self.fp.tell()
138 def pop(self):
139 if self.stack == []:
140 raise Error, 'bad MultiFile.pop() call'
141 if self.level <= 1:
142 self.last = 0
143 else:
144 abslastpos = self.lastpos + self.start
145 self.level = max(0, self.level - 1)
146 del self.stack[0]
147 if self.seekable:
148 self.start = self.posstack.pop(0)
149 if self.level > 0:
150 self.lastpos = abslastpos - self.start
152 def is_data(self, line):
153 return line[:2] != '--'
155 def section_divider(self, str):
156 return "--" + str
158 def end_marker(self, str):
159 return "--" + str + "--"