1 # A class that makes each part of a multipart message "feel" like an
2 # ordinary file, as long as you use fp.readline(). Allows recursive
3 # use, for nested multipart messages. Probably best used together
4 # with module mimetools.
9 # fp = MultiFile(real_fp)
11 # "read some lines from fp"
14 # "read lines from fp until it returns an empty string" (A)
15 # if not fp.next(): break
17 # "read remaining lines from fp until it returns an empty string"
19 # The latter sequence may be used recursively at (A).
20 # It is also allowed to use multiple push()...pop() sequences.
22 # If seekable is given as 0, the class code will not do the bookeeping
23 # it normally attempts in order to make seeks relative to the beginning of the
24 # current file part. This may be useful when using MultiFile with a non-
25 # seekable stream object.
30 Error
= 'multifile.Error'
36 def __init__(self
, fp
, seekable
=1):
38 self
.stack
= [] # Grows down
43 self
.start
= self
.fp
.tell()
44 self
.posstack
= [] # Grows down
49 return self
.fp
.tell() - self
.start
51 def seek(self
, pos
, whence
=0):
58 pos
= pos
+ self
.lastpos
60 raise Error
, "can't use whence=2 yet"
61 if not 0 <= pos
<= here
or \
62 self
.level
> 0 and pos
> self
.lastpos
:
63 raise Error
, 'bad MultiFile.seek() call'
64 self
.fp
.seek(pos
+ self
.start
)
71 line
= self
.fp
.readline()
74 self
.level
= len(self
.stack
)
75 self
.last
= (self
.level
> 0)
77 raise Error
, 'sudden EOF in MultiFile.readline()'
79 assert self
.level
== 0
80 # Fast check to see if this is just data
81 if self
.is_data(line
):
84 # Ignore trailing whitespace on marker lines
86 while line
[k
] in string
.whitespace
:
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
)):
93 if marker
== self
.section_divider(sep
):
96 elif marker
== self
.end_marker(sep
):
101 # We only get here if we see a section divider or EOM line
103 self
.lastpos
= self
.tell() - len(line
)
106 raise Error
,'Missing endmarker in MultiFile.readline()'
112 line
= self
.readline()
117 def read(self
): # Note: no size argument -- read until EOF only!
118 return string
.joinfields(self
.readlines(), '')
121 while self
.readline(): pass
122 if self
.level
> 1 or self
.last
:
127 self
.start
= self
.fp
.tell()
132 raise Error
, 'bad MultiFile.push() call'
133 self
.stack
.insert(0, sep
)
135 self
.posstack
.insert(0, self
.start
)
136 self
.start
= self
.fp
.tell()
140 raise Error
, 'bad MultiFile.pop() call'
144 abslastpos
= self
.lastpos
+ self
.start
145 self
.level
= max(0, self
.level
- 1)
148 self
.start
= self
.posstack
[0]
151 self
.lastpos
= abslastpos
- self
.start
153 def is_data(self
, line
):
154 return line
[:2] <> '--'
156 def section_divider(self
, str):
159 def end_marker(self
, str):
160 return "--" + str + "--"