python/nondist/sandbox/mailbox mailbox.py, 1.1, 1.2 libmailbox.tex, 1.2, 1.3 test_mailbox.py, 1.1, 1.2

Update of /cvsroot/python/python/nondist/sandbox/mailbox In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv31617 Modified Files: mailbox.py libmailbox.tex test_mailbox.py Log Message: * Implement all Message subclasses, including conversion. * Fix handling of new-ness by Maildir: let subdir and info vary orthogonally (so flags can be added to messages in "new"). The spec doesn't disallow this, and some implementations do so. * Miscellaneous documentation and code enhancements. Index: mailbox.py =================================================================== RCS file: /cvsroot/python/python/nondist/sandbox/mailbox/mailbox.py,v retrieving revision 1.1 retrieving revision 1.2 diff -u -d -r1.1 -r1.2 --- mailbox.py 26 Jul 2005 20:47:46 -0000 1.1 +++ mailbox.py 30 Jul 2005 22:49:08 -0000 1.2 @@ -191,9 +191,9 @@ Mailbox.__init__(self, dirname, factory) if not os.path.exists(self._path): os.mkdir(self._path, 0700) - os.mkdir(os.path.join(self._path, "tmp"), 0700) - os.mkdir(os.path.join(self._path, "new"), 0700) - os.mkdir(os.path.join(self._path, "cur"), 0700) + os.mkdir(os.path.join(self._path, 'tmp'), 0700) + os.mkdir(os.path.join(self._path, 'new'), 0700) + os.mkdir(os.path.join(self._path, 'cur'), 0700) self._toc = {} def add(self, message): @@ -252,6 +252,7 @@ self.discard(key) os.rename(os.path.join(self._path, temp_subpath), os.path.join(self._path, subdir, key + suffix)) + # XXX: the mtime should be reset to keep delivery date def get_message(self, key): """Return a Message representation or raise a KeyError.""" @@ -262,10 +263,9 @@ finally: f.close() subdir, name = os.path.split(subpath) - if subdir == 'cur': - msg.set_subdir('cur') - if name.find(':') != -1: - msg.set_info(name.split(':')[-1]) + msg.set_subdir(subdir) + if name.find(':') != -1: + msg.set_info(name.split(':')[-1]) return msg def get_string(self, key): @@ -403,6 +403,8 @@ """Initialize a Message instance.""" if isinstance(message, email.Message.Message): self._become_message(copy.deepcopy(message)) + if isinstance(message, Message): + message._explain_to(self) elif isinstance(message, str): self._become_message(email.message_from_string(message)) elif hasattr(message, "read"): @@ -431,11 +433,9 @@ def __init__(self, message=None): """Initialize a MaildirMessage instance.""" - Message.__init__(self, message) self._subdir = 'new' self._info = '' - if isinstance(message, Message): - message._explain_to(self) + Message.__init__(self, message) def get_subdir(self): """Return 'new' or 'cur'.""" @@ -453,21 +453,19 @@ if len(self._info) > 2 and self._info[:2] == '2,': return self._info[2:] else: - return "" + return '' def set_flags(self, flags): """Set the given flags and unset all others.""" - if self.get_subdir() == 'new': - self.set_subdir('cur') self._info = '2,' + ''.join(sorted(flags)) def add_flags(self, flags): - """Set the given flags without changing others.""" + """Set the given flags without changing others.""" self.set_flags(''.join(set(self.get_flags()) | set(flags))) def remove_flags(self, flags): - """Unset the given string flags (if set) without changing other.""" - if self.get_flags() != "": + """Unset the given string flags (if set) without changing others.""" + if self.get_flags() != '': self.set_flags(''.join(set(self.get_flags()) - set(flags))) def get_info(self): @@ -476,8 +474,6 @@ def set_info(self, info): """Set the message's "info" string.""" - if self.get_subdir() == 'new': - self.set_subdir('cur') if isinstance(info, str): self._info = info else: @@ -485,13 +481,307 @@ def _explain_to(self, message): """Copy Maildir-specific state to message insofar as possible.""" - if isinstance(message, Message): - return - # XXX convert to other formats as needed + if isinstance(message, MaildirMessage): + message.set_flags(self.get_flags()) + message.set_subdir(self.get_subdir()) + elif isinstance(message, _mboxMMDFMessage): + flags = set(self.get_flags()) + if 'S' in flags: + message.add_flags('R') + if self.get_subdir() == 'cur': + message.add_flags('O') + if 'T' in flags: + message.add_flags('D') + if 'F' in flags: + message.add_flags('F') + if 'R' in flags: + message.add_flags('A') + elif isinstance(message, MHMessage): + flags = set(self.get_flags()) + if 'S' not in flags: + message.join_sequence('unseen') + if 'R' in flags: + message.join_sequence('replied') + if 'F' in flags: + message.join_sequence('flagged') + elif isinstance(message, BabylMessage): + flags = set(self.get_flags()) + if 'S' not in flags: + message.add_label('unseen') + if 'T' in flags: + message.add_label('deleted') + if 'R' in flags: + message.add_label('answered') + if 'P' in flags: + message.add_label('forwarded') + elif isinstance(message, Message): + pass + else: + raise TypeError, "Cannot convert to specified type" + + +class _mboxMMDFMessage(Message): + """Message with mbox- or MMDF-specific properties.""" + + def __init__(self, message=None): + """Initialize an mboxMMDFMessage instance.""" + self.set_from('MAILER-DAEMON', True) + if isinstance(message, email.Message.Message): + unixfrom = message.get_unixfrom() + if unixfrom is not None and unixfrom[:5] == 'From ': + self.set_from(unixfrom[5:]) + elif 'Return-Path' in message: + # XXX: generate "From " line from Return-Path: and Received: + pass + Message.__init__(self, message) + + def get_from(self): + """Return contents of "From " line.""" + return self._from + + def set_from(self, from_, time_=None): + """Set "From " line, formatting and appending time_ if specified.""" + if time_ is not None: + if time_ is True: + time_ = time.gmtime() + from_ += time.strftime(" %a %b %d %H:%M:%S %Y", time_) + self._from = from_ + + def get_flags(self): + """Return as a string the flags that are set.""" + return self.get('Status', '') + self.get('X-Status', '') + + def set_flags(self, flags): + """Set the given flags and unset all others.""" + flags = set(flags) + status_flags, xstatus_flags = '', '' + for flag in ('R', 'O'): + if flag in flags: + status_flags += flag + flags.remove(flag) + for flag in ('D', 'F', 'A'): + if flag in flags: + xstatus_flags += flag + flags.remove(flag) + xstatus_flags += ''.join(sorted(flags)) + try: + self.replace_header('Status', status_flags) + except KeyError: + self.add_header('Status', status_flags) + try: + self.replace_header('X-Status', xstatus_flags) + except KeyError: + self.add_header('X-Status', xstatus_flags) + + def add_flags(self, flags): + """Set the given flags without changing others.""" + self.set_flags(''.join(set(self.get_flags()) | set(flags))) + + def remove_flags(self, flags): + """Unset the given string flags (if set) without changing others.""" + if 'Status' in self or 'X-Status' in self: + self.set_flags(''.join(set(self.get_flags()) - set(flags))) + + def _explain_to(self, message): + """Copy mbox- or MMDF-specific state to message insofar as possible.""" + if isinstance(message, MaildirMessage): + flags = set(self.get_flags()) + if 'O' in flags: + message.set_subdir('cur') + if 'F' in flags: + message.add_flags('F') + if 'A' in flags: + message.add_flags('R') + if 'R' in flags: + message.add_flags('S') + if 'D' in flags: + message.add_flags('T') + elif isinstance(message, _mboxMMDFMessage): + message.set_flags(self.get_flags()) + message.set_from(self.get_from()) + elif isinstance(message, MHMessage): + flags = set(self.get_flags()) + if 'R' not in flags: + message.join_sequence('unseen') + if 'A' in flags: + message.join_sequence('replied') + if 'F' in flags: + message.join_sequence('flagged') + elif isinstance(message, BabylMessage): + flags = set(self.get_flags()) + if 'R' not in flags: + message.add_label('unseen') + if 'D' in flags: + message.add_label('deleted') + if 'A' in flags: + message.add_label('answered') + elif isinstance(message, Message): + pass + else: + raise TypeError, "Cannot convert to specified type" + + +class mboxMessage(_mboxMMDFMessage): + """Message with mbox-specific properties.""" + + +class MHMessage(Message): + """Message with MH-specific properties.""" + + def __init__(self, message=None): + """Initialize an MHMessage instance.""" + self._sequences = [] + Message.__init__(self, message) + + def list_sequences(self): + """Return a list of sequences that include the message.""" + return self._sequences[:] + + def join_sequence(self, sequence): + """Add sequence to list of sequences including the message.""" + if isinstance(sequence, str): + if not sequence in self._sequences: + self._sequences.append(sequence) + else: + raise TypeError, "sequence must be a string" + + def leave_sequence(self, sequence): + """Remove sequence from the list of sequences including the message.""" + try: + self._sequences.remove(sequence) + except ValueError: + pass + + def _explain_to(self, message): + """Copy MH-specific state to message insofar as possible.""" + if isinstance(message, MaildirMessage): + sequences = set(self.list_sequences()) + if 'unseen' in sequences: + message.set_subdir('cur') + else: + message.set_subdir('cur') + message.add_flags('S') + if 'flagged' in sequences: + message.add_flags('F') + if 'replied' in sequences: + message.add_flags('R') + elif isinstance(message, _mboxMMDFMessage): + sequences = set(self.list_sequences()) + if 'unseen' not in sequences: + message.add_flags('RO') + else: + message.add_flags('O') + if 'flagged' in sequences: + message.add_flags('F') + if 'replied' in sequences: + message.add_flags('A') + elif isinstance(message, MHMessage): + for sequence in self.list_sequences(): + message.join_sequence(sequence) + elif isinstance(message, BabylMessage): + sequences = set(self.list_sequences()) + if 'unseen' in sequences: + message.add_label('unseen') + if 'replied' in sequences: + message.add_label('answered') + elif isinstance(message, Message): + pass + else: + raise TypeError, "Cannot convert to specified type" + + +class BabylMessage(Message): + """Message with Babyl-specific properties.""" + + def __init__(self, message=None): + """Initialize an BabylMessage instance.""" + self._labels = [] + self._visible = Message() + Message.__init__(self, message) + + def list_labels(self): + """Return a list of labels on the message.""" + return self._labels[:] + + def add_label(self, label): + """Add label to list of labels on the message.""" + if isinstance(label, str): + if label not in self._labels: + self._labels.append(label) + else: + raise TypeError, "label must be a string" + + def remove_label(self, label): + """Remove label from the list of labels on the message.""" + try: + self._labels.remove(label) + except ValueError: + pass + + def get_visible(self): + """Return a Message representation of visible headers.""" + return Message(self._visible) + + def set_visible(self, visible): + """Set the Message representation of visible headers.""" + self._visible = Message(visible) + + def update_visible(self): + """Update and/or sensibly generate a set of visible headers.""" + for header in self._visible.keys(): + if header in self: + self._visible.replace_header(header, self[header]) + else: + del self._visible[header] + for header in ('Date', 'From', 'Reply-To', 'To', 'CC', 'Subject'): + if header in self and header not in self._visible: + self._visible[header] = self[header] + + def _explain_to(self, message): + """Copy Babyl-specific state to message insofar as possible.""" + if isinstance(message, MaildirMessage): + labels = set(self.list_labels()) + if 'unseen' in labels: + message.set_subdir('cur') + else: + message.set_subdir('cur') + message.add_flags('S') + if 'forwarded' in labels or 'resent' in labels: + message.add_flags('P') + if 'answered' in labels: + message.add_flags('R') + if 'deleted' in labels: + message.add_flags('T') + elif isinstance(message, _mboxMMDFMessage): + labels = set(self.list_labels()) + if 'unseen' not in labels: + message.add_flags('RO') + else: + message.add_flags('O') + if 'deleted' in labels: + message.add_flags('D') + if 'answered' in labels: + message.add_flags('A') + elif isinstance(message, MHMessage): + labels = set(self.list_labels()) + if 'unseen' in labels: + message.join_sequence('unseen') + if 'answered' in labels: + message.join_sequence('replied') + elif isinstance(message, BabylMessage): + message.set_visible(self.get_visible()) + for label in self.list_labels(): + message.add_label(label) + elif isinstance(message, Message): + pass else: raise TypeError, "Cannot convert to specified type" +class MMDFMessage(_mboxMMDFMessage): + """Message with MMDF-specific properties.""" + + class _ProxyFile: """A read-only wrapper of a file.""" Index: libmailbox.tex =================================================================== RCS file: /cvsroot/python/python/nondist/sandbox/mailbox/libmailbox.tex,v retrieving revision 1.2 retrieving revision 1.3 diff -u -d -r1.2 -r1.3 --- libmailbox.tex 26 Jul 2005 20:47:48 -0000 1.2 +++ libmailbox.tex 30 Jul 2005 22:49:08 -0000 1.3 @@ -29,7 +29,6 @@ in a mailbox, the modified message representation must be explicitly assigned back into the \class{Mailbox} instance's mapping. - \begin{seealso} \seemodule{email}{Represent and manipulate messages.} \seemodule{poplib}{Access mail via POP3.} @@ -37,12 +36,11 @@ \seemodule{smtplib}{Transfer mail via SMTP.} \end{seealso} - \subsection{\class{Mailbox} objects} \label{mailbox-objects} \begin{classdesc*}{Mailbox} -Represents a mailbox, which may be inspected and modified. +A mailbox, which may be inspected and modified. \end{classdesc*} The \class{Mailbox} interface is dictionary-like, with small keys @@ -107,7 +105,7 @@ \class{Mailbox} instances have the following methods: -\begin{methoddesc}[Mailbox]{add}{message} +\begin{methoddesc}{add}{message} Add \var{message} to the mailbox and return the key that has been assigned to it. @@ -119,7 +117,7 @@ Otherwise, reasonable defaults for format-specific information are used. \end{methoddesc} -\begin{methoddesc}[Mailbox]{remove}{key} +\begin{methoddesc}{remove}{key} \methodline{__delitem__}{key} \methodline{discard}{key} Delete the message corresponding to \var{key} from the mailbox. @@ -131,7 +129,7 @@ format supports concurrent modification by other processes. \end{methoddesc} -\begin{methoddesc}[Mailbox]{__setitem__}{key, message} +\begin{methoddesc}{__setitem__}{key, message} Replace the message corresponding to \var{key} with the message represented by \var{message}. Raise a \exception{KeyError} exception if no message already corresponds to \var{key}. @@ -145,13 +143,13 @@ corresponds to \var{key} is left unchanged. \end{methoddesc} -\begin{methoddesc}[Mailbox]{iterkeys}{} +\begin{methoddesc}{iterkeys}{} \methodline{keys}{} Return an iterator over all keys if called as \method{iterkeys()} or return a list of keys if called as \method{keys()}. \end{methoddesc} -\begin{methoddesc}[Mailbox]{itervalues}{} +\begin{methoddesc}{itervalues}{} \methodline{__iter__}{} \methodline{values}{} Return an iterator over representations of all messages if called as @@ -162,7 +160,7 @@ \method{__iter__()} is unlike that of dictionaries, which iterate over keys.} \end{methoddesc} -\begin{methoddesc}[Mailbox]{iteritems}{} +\begin{methoddesc}{iteritems}{} \methodline{items}{} Return an iterator over (\var{key}, \var{message}) pairs, where \var{key} is a key and \var{message} is a message representation, if called as @@ -172,7 +170,7 @@ was initialized. \end{methoddesc} -\begin{methoddesc}[Mailbox]{get}{key\optional{, default=None}} +\begin{methoddesc}{get}{key\optional{, default=None}} \methodline{__getitem__}{key} Return a representation of the message corresponding to \var{key}. If no such message exists, \var{default} is returned if the method was called as @@ -182,18 +180,18 @@ \class{Mailbox} instance was initialized. \end{methoddesc} -\begin{methoddesc}[Mailbox]{get_message}{key} +\begin{methoddesc}{get_message}{key} Return a \class{Message} representation of the message corresponding to \var{key}, or raise a \exception{KeyError} exception if no such message exists. \end{methoddesc} -\begin{methoddesc}[Mailbox]{get_string}{key} +\begin{methoddesc}{get_string}{key} Return a string representation of the message corresponding to \var{key}, or raise a \exception{KeyError} exception if no such message exists. \end{methoddesc} -\begin{methoddesc}[Mailbox]{get_file}{key} +\begin{methoddesc}{get_file}{key} Return a file-like representation of the message corresponding to \var{key}, or raise a \exception{KeyError} exception if no such message exists. This file should be closed once it is no longer needed. @@ -204,21 +202,21 @@ More specific documentation is provided by each subclass.} \end{methoddesc} -\begin{methoddesc}[Mailbox]{has_key}{key} +\begin{methoddesc}{has_key}{key} \methodline{__contains__}{} Return \code{True} if \var{key} corresponds to a message, \code{False} otherwise. \end{methoddesc} -\begin{methoddesc}[Mailbox]{__len__}{} +\begin{methoddesc}{__len__}{} Return a count of messages in the mailbox. \end{methoddesc} -\begin{methoddesc}[Mailbox]{clear}{} +\begin{methoddesc}{clear}{} Delete all messages from the mailbox. \end{methoddesc} -\begin{methoddesc}[Mailbox]{pop}{key\optional{, default}} +\begin{methoddesc}{pop}{key\optional{, default}} Return a representation of the message corresponding to \var{key} and delete the message. If no such message exists, return \var{default} if it was supplied or else raise a \exception{KeyError} exception. The message is represented as a @@ -226,7 +224,7 @@ \class{Mailbox} instance was initialized. \end{methoddesc} -\begin{methoddesc}[Mailbox]{popitem}{} +\begin{methoddesc}{popitem}{} Return an arbitrary (\var{key}, \var{message}) pair, where \var{key} is a key and \var{message} is a message representation, and delete the corresponding message. If the mailbox is empty, raise a \exception{KeyError} exception. The @@ -234,7 +232,7 @@ factory was specified when the \class{Mailbox} instance was initialized. \end{methoddesc} -\begin{methoddesc}[Mailbox]{update}{arg} +\begin{methoddesc}{update}{arg} Parameter \var{arg} should be a \var{key}-to-\var{message} mapping or an iterable of (\var{key}, \var{message}) pairs. Updates the mailbox so that, for each given \var{key} and \var{message}, the message corresponding to \var{key} @@ -244,7 +242,7 @@ are not supported.} \end{methoddesc} -\begin{methoddesc}[Mailbox]{flush}{} +\begin{methoddesc}{flush}{} Write any pending changes to the filesystem. For some \class{Mailbox} subclasses, this is done automatically and calling \method{flush()} has no effect. More specific documentation is provided by each subclass. @@ -277,23 +275,23 @@ \class{Maildir} instances have all of the methods of \class{Mailbox} in addition to the following: -\begin{methoddesc}[Maildir]{list_folders}{} +\begin{methoddesc}{list_folders}{} Return a list of the names of all folders. If there are no folders, the empty list is returned. \end{methoddesc} -\begin{methoddesc}[Maildir]{open_folder}{name} +\begin{methoddesc}{open_folder}{name} Return a \class{Maildir} instance representing the folder whose name is \var{name}. The folder will be created if it does not exist. \end{methoddesc} -\begin{methoddesc}[Maildir]{remove_folder}{name} +\begin{methoddesc}{remove_folder}{name} Delete the folder whose name is \var{name}. If the folder contains any messages, an \exception{IOError} exception will be raised and the folder will not be deleted. \end{methoddesc} -\begin{methoddesc}[Maildir]{clean}{} +\begin{methoddesc}{clean}{} Delete temporary files from the mailbox that have not been accessed in the last 36 hours. The Maildir specification says that mail-reading programs should do this occassionally. @@ -307,9 +305,9 @@ simultaneously.} Some \class{Mailbox} methods implemented by \class{Maildir} deserve special -remarks: +remarks: -\begin{methoddesc}[Maildir]{add}{message} +\begin{methoddesc}{add}{message} \methodline[Maildir]{__setitem__}{key, message} \methodline[Maildir]{update}{arg} \warning{These methods generate unique file names based upon the current @@ -318,12 +316,12 @@ these methods to manipulate the same mailbox simultaneously.} \end{methoddesc} -\begin{methoddesc}[Maildir]{flush}{} +\begin{methoddesc}{flush}{} All changes to Maildir mailboxes are immediately applied. This method does nothing. \end{methoddesc} -\begin{methoddesc}[Maildir]{get_file}{key} +\begin{methoddesc}{get_file}{key} Depending upon the host platform, it may not be possible to use a \class{Maildir} instance to modify or remove the underlying message while the returned file remains open. @@ -343,18 +341,205 @@ \subsubsection{\class{mbox}} \label{mailbox-mbox} +\begin{classdesc}{mbox}{path\optional{, factory}} +A subclass of \class{Mailbox} for mailboxes in mbox format. Parameters +\var{path} and \var{factory} has the same meaning as with the module-level +\method{open()} function. +\end{classdesc} + +The mbox format is the classic format for storing mail on \UNIX{} systems. All +messages in an mbox mailbox are stored in a single file with the beginning of +each message indicated by a line whose first five characters are "From~". +Several variations of the mbox format exist to address perceived shortcomings. +In the interest of compatibility, \class{mbox} implements the original format, +which is sometimes referred to as \dfn{mboxo}. This means that the +\mailheader{Content-Length} header, if present, is ignored and that any +occurrences of "From~" at the beginning of a line in a message body are +transformed to ">From~" when storing the message although occurences of +">From~" are not transformed to "From~" when reading the message. + +Some \class{Mailbox} methods implemented by \class{mbox} deserve special +remarks: + +\begin{methoddesc}{get_file}{key} +XXX +\end{methoddesc} + +\begin{seealso} + \seelink{http://www.qmail.org/man/man5/mbox.html}{mbox man page from + qmail}{A specification of the format and its variations.} + \seelink{http://home.netscape.com/eng/mozilla/2.0/relnotes/demo/content-length.html} + {Configuring Netscape Mail on \UNIX{}: Why The Content-Length Format is + Bad}{An argument for using the original mbox format rather than a + variation.} + \seelink{http://homepages.tesco.net./\tilde{}J.deBoynePollard/FGA/mail-mbox-formats.html} + {"mbox" is a family of several mutually incompatible mailbox formats}{A + history of mbox variations.} +\end{seealso} \subsubsection{\class{MH}} \label{mailbox-mh} +\begin{classdesc}{MH}{path\optional{, factory}} +A subclass of \class{Mailbox} for mailboxes in MH format. Parameters \var{path} +and \var{factory} has the same meaning as with the module-level \method{open()} +function. +\end{classdesc} + +MH is a directory-based mailbox format invented for the MH Message Handling +System, a mail reading application. Each message in an MH mailbox resides in +its own file. An MH mailbox may contain other MH mailboxes (called +\dfn{folders}) in addition to messages. Folders may be nested indefinitely. + +MH mailboxes support \dfn{sequences}, which are named lists used to logically +group messages without moving them to sub-folders. Some mail reading programs +(although not the standard \program{mh} and \program{nmh} implementations) use +sequences to the same end as flags are used in other formats: unread messages +are added to the "unseen" sequence, replied-to messages are added to the +"replied" sequence, and important messages are added upon request to the +"flagged" sequence. + +\class{MH} instances have all of the methods of \class{Mailbox} in addition to +the following: + +\begin{methoddesc}{list_folders}{} +Return a list of the names of all folders. If there are no folders, the empty +list is returned. +\end{methoddesc} + +\begin{methoddesc}{open_folder}{name} +Return an \class{MH} instance representing the folder whose name is \var{name}. +The folder will be created if it does not exist. +\end{methoddesc} + +\begin{methoddesc}{remove_folder}{name} +Delete the folder whose name is \var{name}. If the folder contains any +messages, an \exception{IOError} exception will be raised and the folder will +not be deleted. +\end{methoddesc} + +\begin{methoddesc}{list_sequences}{} +Return a list of the names of sequences defined in the mailbox. If there are no +sequences, the empty list is returned. +\end{methoddesc} + +\begin{methoddesc}{get_sequence}{name} +Return a list of keys in sequence \var{name}. +\end{methoddesc} + +\begin{methoddesc}{set_sequence}{name, value} +Set to \var{value} the list of keys in sequence \var{name}. The sequence will +be created if it does not exist. If \var{value} is the empty list, sequence +\var{name} will be removed. +\end{methoddesc} + +\begin{methoddesc}{pack}{} +Renames messages in the mailbox as necessary to eliminate gaps in numbering. +Already-issued keys are invalidated by this operation. +\end{methoddesc} + +Some \class{Mailbox} methods implemented by \class{MH} deserve special remarks: + +\begin{methoddesc}{remove}{key} +\methodline{__delitem__}{key} +\methodline{discard}{key} +These methods immediately delete the message. The \program{mh} convention of +marking a message for deletion by prepending a comma to its name is not used. +\end{methoddesc} + +\begin{methoddesc}{get_file}{key} +XXX +\end{methoddesc} + +\begin{methoddesc}{flush}{} +All changes to MH mailboxes are immediately applied. This method does nothing. +\end{methoddesc} + +\begin{classdesc}{MH}{path\optional{, factory}} +A subclass of \class{Mailbox} for mailboxes in MH format. Parameters \var{path} +and \var{factory} has the same meaning as with the module-level \method{open()} +function. +\end{classdesc} + +\class{MH} instances have all of the methods of \class{Mailbox} in addition to +the following: + +Some \class{Mailbox} methods implemented by \class{MH} deserve special remarks: + +\begin{methoddesc}{get_file}{key} +XXX +\end{methoddesc} + +\begin{seealso} +\seelink{http://www.nongnu.org/nmh/}{nmh - Message Handling System}{Home page +of \program{nmh}, a modern version of the original \program{mh}.} +\seelink{http://www.ics.uci.edu/\tilde{}mh/book/}{MH \& nmh: Email for Users \& +Programmers}{An open-source book on \program{mh} and \program{nmh}, with some +information on the mailbox format.} +\end{seealso} \subsubsection{\class{Babyl}} \label{mailbox-babyl} +\begin{classdesc}{Babyl}{path\optional{, factory}} +A subclass of \class{Mailbox} for mailboxes in Babyl format. Parameters +\var{path} and \var{factory} has the same meaning as with the module-level +\method{open()} function. +\end{classdesc} + +Babyl is a single-file mailbox format invented for use with the Rmail mail +reading application that ships with Emacs. A Babyl mailbox begins with a +so-called options section that indicates the format of the mailbox. Messages +follow the options section, with the beginning and end of each message +indicated by control characters. Each message in a Babyl mailbox has an +accompanying list of \dfn{labels}, or short strings that record extra +information about the message. + +Some \class{Mailbox} methods implemented by \class{Babyl} deserve special +remarks: + +\begin{methoddesc}{get_file}{key} +XXX +\end{methoddesc} + +\begin{seealso} +\seelink{http://quimby.gnus.org/notes/BABYL}{Format of Version 5 Babyl Files}{A +specification of the Babyl format.} +\seelink{http://www.gnu.org/software/emacs/manual/html_node/Rmail.html}{Reading +Mail with Rmail}{The Rmail manual, with some information on Babyl semantics.} +\end{seealso} \subsubsection{\class{MMDF}} \label{mailbox-mmdf} +\begin{classdesc}{MMDF}{path\optional{, factory}} +A subclass of \class{Mailbox} for mailboxes in MMDF format. Parameters +\var{path} and \var{factory} has the same meaning as with the module-level +\method{open()} function. +\end{classdesc} + +MMDF is a single-file mailbox format invented for the Multichannel Memorandum +Distribution Facility, a mail transfer agent. Each message is in the same form +as an mbox message but is bracketed before and after by lines containing four +Control-A characters. As with the mbox format, the beginning of each message +indicated by a line whose first five characters are "From~", but because of the +additional message separators it is unnecessary to transform "From~" to +">From~" when storing messages. + +Some \class{Mailbox} methods implemented by \class{MMDF} deserve special +remarks: + +\begin{methoddesc}{get_file}{key} +XXX +\end{methoddesc} + +\begin{seealso} +\seelink{http://www.tin.org/bin/man.cgi?section=5\&topic=mmdf}{mmdf man page +from tin}{A specification of MMDF format from the documentation of tin, a +newsreader.} +\seelink{http://en.wikipedia.org/wiki/MMDF}{MMDF}{A Wikipedia article +describing the Multichannel Memorandum Distribution Facility.} +\end{seealso} \subsection{\class{Message} objects} \label{mailbox-message-objects} @@ -364,7 +549,7 @@ support mailbox-format-specific state and behavior. \begin{classdesc}{Message}{\optional{message}} -Represents a message with mailbox-format-specific properties. +A message with mailbox-format-specific properties. If \var{message} is omitted, the new instance is created in a default, empty state. If \var{message} is an \class{email.Message.Message} instance, its @@ -400,19 +585,22 @@ \label{mailbox-maildirmessage} \begin{classdesc}{MaildirMessage}{\optional{message}} -Represents a message with Maildir-specific behaviors. Parameter \var{message} +A message with Maildir-specific behaviors. Parameter \var{message} has the same meaning as with the \class{Message} constructor. \end{classdesc} Maildir messages are stored in individual files, in either the \file{new} or -the \file{cur} subdirectory of the Maildir. Typically, messages are delivered -to the \file{new} subdirectory and then moved to the \file{cur} subdirectory -as soon as the user's mail reading application detects them. Each message in -\file{cur} has an "info" section added to its file name to store information -about its state. The "info" section may take one of two forms: it may contain -a standardized list of flags or it may contain so-called experimental -information. +the \file{cur} subdirectory of the Maildir. Messages are delivered to the +\file{new} subdirectory. Typically, a mail reading application moves messages +to the \file{cur} subdirectory after the user opens and closes the mailbox, +thereby recording that the messages are old whether or not they've actually +been read. +Each message in \file{cur} has an "info" section added to its file name to +store information about its state. (Some mail readers may also add an "info" +section to messages in \file{new}.) The "info" section may take one of two +forms: it may contain "2," followed by a list of standardized flags (e.g., +"2,FR") or it may contain "1," followed by so-called experimental information. Standard flags for Maildir messages are as follows: \begin{tableiii}{l|l|l}{textrm}{Flag}{Meaning}{Explanation} @@ -426,81 +614,507 @@ \class{MaildirMessage} instances offer the following methods: -\begin{methoddesc}[MaildirMessage]{get_subdir}{} +\begin{methoddesc}{get_subdir}{} Return either "new" (if the message should be stored in the \file{new} subdirectory) or "cur" (if the message should be stored in the \file{cur} -subdirectory). \note{A Maildir message typically moves from \file{new} to -\file{cur} when an MUA first detects it, but most MUAs refer to a message as -new if it is unseen, i.e., if \code{"S" in get_flags()} is \code{False}.} +subdirectory). \note{A message is typically moved from \file{new} to \file{cur} +after its mailbox has been accessed, whether or not the message is has been +read. A message has been read if \code{"S" not in get_flags()}.} \end{methoddesc} -\begin{methoddesc}[MaildirMessage]{set_subdir}{subdir} +\begin{methoddesc}{set_subdir}{subdir} Set the subdirectory the message should be stored in. Parameter \var{subdir} must be either "new" or "cur". \end{methoddesc} -\begin{methoddesc}[MaildirMessage]{get_flags}{} +\begin{methoddesc}{get_flags}{} Return a string specifying the flags that are currently set. If the message complies with the standard Maildir format, the result is the concatenation in alphabetical order of zero or one occurrence of each of \character{D}, \character{F}, \character{P}, \character{R}, \character{S}, and \character{T}. -The empty string is returned if no flags are set, if the message is new, or if -"info" contains experimental semantics. +The empty string is returned if no flags are set or if "info" contains +experimental semantics. \end{methoddesc} -\begin{methoddesc}[MaildirMessage]{set_flags}{flags} +\begin{methoddesc}{set_flags}{flags} Set the flags specified by \var{flags} and unset all others. Parameter \var{flags} should be the concatenation in any order of zero or more occurrences of each of \character{D}, \character{F}, \character{P}, -\character{R}, \character{S}, and \character{T}. If the message is new, it -is made non-new. The current "info" is overwritten whether or not it contains -experimental information instead of flags. +\character{R}, \character{S}, and \character{T}. The current "info" is +overwritten whether or not it contains experimental information instead of +flags. \end{methoddesc} -\begin{methoddesc}[MaildirMessage]{add_flags}{flags} +\begin{methoddesc}{add_flags}{flags} Set the flags specified by \var{flags} (if they are not set), and don't change other flags. Parameter \var{flags} should be the concatenation in any order of zero or more occurrences of each of \character{D}, \character{F}, -\character{P}, \character{R}, \character{S}, and \character{T}. If the message -is new, it is made non-new. If "info" contains experimental information rather -than flags, the "info" will be overwritten. +\character{P}, \character{R}, \character{S}, and \character{T}. The current +"info" is overwritten whether or not it contains experimental information +instead of flags. \end{methoddesc} -\begin{methoddesc}[MaildirMessage]{remove_flags}{flags} +\begin{methoddesc}{remove_flags}{flags} Unset the flags specified by \var{flags} (if they are set), and don't change other flags. Parameter \var{flags} should be the concatenation in any order of zero or more occurrences of each of \character{D}, \character{F}, -\character{P}, \character{R}, \character{S}, and \character{T}. If the message -is new, it remains so. If "info" contains experimental information rather than -flags, the current "info" is not modified. +\character{P}, \character{R}, \character{S}, and \character{T}. If "info" +contains experimental information rather than flags, the current "info" is not +modified. \end{methoddesc} -\begin{methoddesc}[MaildirMessage]{get_info}{} +\begin{methoddesc}{get_info}{} Return a string containing the "info" for a message. This is useful for accessing and modifying "info" that is experimental (i.e., not a list of flags). \end{methoddesc} -\begin{methoddesc}[MaildirMessage]{set_info}{info} -Set "info" to \var{info}, which should be a string. If the message is new, it -is made non-new. +\begin{methoddesc}{set_info}{info} +Set "info" to \var{info}, which should be a string. \end{methoddesc} +When a \class{MaildirMessage} instance is created based upon an +\class{mboxMessage} or \class{MMDFMessage} instance, the \mailheader{Status} +and \mailheader{X-Status} headers are omitted and the following conversions +take place: + +\begin{tableii}{l|l}{textrm} + {Resulting state}{\class{mboxMessage} or \class{MMDFMessage} state} +\lineii{"cur" subdirectory}{O flag} +\lineii{F flag}{F flag} +\lineii{R flag}{A flag} +\lineii{S flag}{R flag} +\lineii{T flag}{D flag} +\end{tableii} + +When a \class{MaildirMessage} instance is created based upon an +\class{MHMessage} instance, the following conversions take place: + +\begin{tableii}{l|l}{textrm} + {Resulting state}{\class{MHMessage} state} +\lineii{"cur" subdirectory}{"unseen" sequence} +\lineii{"cur" subdirectory and S flag}{no "unseen" sequence} +\lineii{F flag}{"flagged" sequence} +\lineii{R flag}{"replied" sequence} +\end{tableii} + +When a \class{MaildirMessage} instance is created based upon a +\class{BabylMessage} instance, the following conversions take place: + +\begin{tableii}{l|l}{textrm} + {Resulting state}{\class{BabylMessage} state} +\lineii{"cur" subdirectory}{"unseen" label} +\lineii{"cur" subdirectory and S flag}{no "unseen" label} +\lineii{P flag}{"forwarded" or "resent" label} +\lineii{R flag}{"answered" label} +\lineii{T flag}{"deleted" label} +\end{tableii} + \subsubsection{\class{mboxMessage}} \label{mailbox-mboxmessage} +\begin{classdesc}{mboxMessage}{\optional{message}} +A message with mbox-specific behaviors. Parameter \var{message} has the same +meaning as with the \class{Message} constructor. +\end{classdesc} + +Messages in an mbox mailbox are stored together in a single file. The sender's +envelope address and the time of delivery are typically stored in a line +beginning with "From~" that is used to indicate the start of a message, though +there is considerable variation in the exact format of this data among mbox +implementations. Flags that indicate the state of the message, such as whether +it has been read or marked as important, are typically stored in +\mailheader{Status} and \mailheader{X-Status} headers. + +Conventional flags for mbox messages are as follows: + +\begin{tableiii}{l|l|l}{textrm}{Flag}{Meaning}{Explanation} +\lineiii{R}{Read}{Read by the user} +\lineiii{O}{Old}{Previously detected by mail reader} +\lineiii{D}{Deleted}{Marked for subsequent deletion} +\lineiii{F}{Flagged}{Marked by the user as important} +\lineiii{A}{Answered}{Responded to} +\end{tableiii} + +The "R" and "O" flags are stored in the \mailheader{Status} header, and the +"D", "F", and "A" flags are stored in the \mailheader{X-Status} header. The +flags and headers typically appear in the order mentioned. + +\class{mboxMessage} instances offer the following methods: + +\begin{methoddesc}{get_from}{} +Return a string representing the "From~" line that marks the start of the +message in an mbox mailbox. The leading "From~" and the trailing newline are +excluded. +\end{methoddesc} + +\begin{methoddesc}{set_from}{from_\optional{, time_=None}} +Set the "From~" line to \var{from_}, which should be specified without a +leading "From~" or trailing newline. If \var{time_} is specified, it should be +a \class{struct_time} or a tuple suitable for passing to +\method{time.strftime()}; if \var{time_} is \code{True}, the result of +\method{time.gmtime()} is used. +\end{methoddesc} + +\begin{methoddesc}{get_flags}{} +Return a string specifying the flags that are currently set. If the message +complies with the conventional format, the result is the concatenation in the +following order of zero or one occurrence of each of \character{R}, +\character{O}, \character{D}, \character{F}, and \character{A}. +\end{methoddesc} + +\begin{methoddesc}{set_flags}{flags} +Set the flags specified by \var{flags} and unset all others. Parameter +\var{flags} should be the concatenation in any order of zero or more +occurrences of each of \character{R}, \character{O}, \character{D}, +\character{F}, and \character{A}. +\end{methoddesc} + +\begin{methoddesc}{add_flags}{flags} +Set the flags specified by \var{flags} (if they are not set), and don't change +other flags. Parameter \var{flags} should be the concatenation in any order of +zero or more occurrences of each of \character{R}, \character{O}, +\character{D}, \character{F}, and \character{A}. +\end{methoddesc} + +\begin{methoddesc}{remove_flags}{flags} +Unset the flags specified by \var{flags} (if they are set), and don't change +other flags. Parameter \var{flags} should be the concatenation in any order of +zero or more occurrences of each of \character{R}, \character{O}, +\character{D}, \character{F}, and \character{A}. +\end{methoddesc} + +When an \class{mboxMessage} instance is created based upon a +\class{MaildirMessage} instance, the following conversions take place: + +\begin{tableii}{l|l}{textrm} + {Resulting state}{\class{MaildirMessage} state} +\lineii{R flag}{S flag} +\lineii{O flag}{"cur" subdirectory} +\lineii{D flag}{T flag} +\lineii{F flag}{F flag} +\lineii{A flag}{R flag} +\end{tableii} + +When an \class{mboxMessage} instance is created based upon an \class{MHMessage} +instance, the following conversions take place: + +\begin{tableii}{l|l}{textrm} + {Resulting state}{\class{MHMessage} state} +\lineii{R flag and O flag}{no "unseen" sequence} +\lineii{O flag}{"unseen" sequence} +\lineii{F flag}{"flagged" sequence} +\lineii{A flag}{"replied" sequence} +\end{tableii} + +When an \class{mboxMessage} instance is created based upon a +\class{BabylMessage} instance, the following conversions take place: + +\begin{tableii}{l|l}{textrm} + {Resulting state}{\class{BabylMessage} state} +\lineii{R flag and O flag}{no "unseen" label} +\lineii{O flag}{"unseen" label} +\lineii{D flag}{"deleted" label} +\lineii{A flag}{"answered" label} +\end{tableii} + +When a \class{Message} instance is created based upon an \class{MMDFMessage} +instance, the "From~" line is copied and all flags directly correspond: + +\begin{tableii}{l|l}{textrm} + {Resulting state}{\class{MMDFMessage} state} +\lineii{R flag}{R flag} +\lineii{O flag}{O flag} +\lineii{D flag}{D flag} +\lineii{F flag}{F flag} +\lineii{A flag}{A flag} +\end{tableii} \subsubsection{\class{MHMessage}} \label{mailbox-mhmessage} +\begin{classdesc}{MHMessage}{\optional{message}} +A message with MH-specific behaviors. Parameter \var{message} has the same +meaning as with the \class{Message} constructor. +\end{classdesc} + +MH messages do not support marks or flags in the traditional sense, but they do +support sequences, which are logical groupings of arbitrary messages. Because +sequences are often used to indicate the state of a messsage, they are +maintained by the \class{MHMessage} class even though they are not, strictly +speaking, a property of the message itself. + +Some mail user agents make use of sequences to record message state as follows: + +\begin{tableii}{l|l}{textrm}{Sequence}{Explanation} +\lineii{unseen}{Previously detected by mail reader but not read} +\lineii{replied}{Responded to} +\lineii{flagged}{Marked by the user as important} +\end{tableii} + +\class{MHMessage} instances offer the following methods: + +\begin{methoddesc}{list_sequences}{} +Return a list of the names of sequences that include the message. +\end{methoddesc} + +\begin{methoddesc}{join_sequence}{sequence} +Add \var{sequence} to the list of sequences that include the message. +\end{methoddesc} + +\begin{methoddesc}{leave_sequence}{sequence} +Remove \var{sequence} from the list of sequences that include the message. +\end{methoddesc} + +When an \class{MHMessage} instance is created based upon a +\class{MaildirMessage} instance, the following conversions take place: + +\begin{tableii}{l|l}{textrm} + {Resulting state}{\class{MaildirMessage} state} +\lineii{"unseen" sequence}{no S flag} +\lineii{"replied" sequence}{R flag} +\lineii{"flagged" sequence}{F flag} +\end{tableii} + +When an \class{MHMessage} instance is created based upon an \class{mboxMessage} +or \class{MMDFMessage} instance, the \mailheader{Status} and +\mailheader{X-Status} headers are omitted and the following conversions take +place: + +\begin{tableii}{l|l}{textrm} + {Resulting state}{\class{mboxMessage} or \class{MMDFMessage} state} +\lineii{"unseen" sequence}{no R flag} +\lineii{"replied" sequence}{A flag} +\lineii{"flagged" sequence}{F flag} +\end{tableii} + +When an \class{MHMessage} instance is created based upon a \class{BabylMessage} +instance, the following conversions take place: + +\begin{tableii}{l|l}{textrm} + {Resulting state}{\class{BabylMessage} state} +\lineii{"unseen" sequence}{"unseen" label} +\lineii{"replied" sequence}{"answered" label} +\end{tableii} \subsubsection{\class{BabylMessage}} \label{mailbox-babylmessage} +\begin{classdesc}{BabylMessage}{\optional{message}} +A message with Babyl-specific behaviors. Parameter \var{message} has the same +meaning as with the \class{Message} constructor. +\end{classdesc} + +Information about Babyl messages is recorded using \dfn{labels}, or short +strings which are stored in an MH mailbox just before each message. Some +labels are assigned special meaning and are called \dfn{attributes}. Other +labels are user-defined. The attributes are as follows: + +\begin{tableii}{l|l}{textrm}{Label}{Explanation} +\lineii{unseen}{Previously detected by mail reader but not read} +\lineii{deleted}{Marked for subsequent deletion} +\lineii{filed}{Copied to another file or mailbox} +\lineii{answered}{Responded to} +\lineii{forwarded}{Forwarded by the the user} +\lineii{edited}{Message content modified by the user} +\lineii{resent}{Resent by the user} +\end{tableii} + +Each message in a Babyl mailbox has two sets of headers, original headers and +visible headers. Visible headers are typically a subset of the original +headers reformatted to be more attractive. By default, \program{rmail} displays +only visible headers. \class{BabylMessage} uses the original headers because +they are more complete, though the visible headers may be accessed explicitly +if desired. + +\class{BabylMessage} instances offer the following methods: + +\begin{methoddesc}{list_labels}{} +Return a list of labels on the message. +\end{methoddesc} + +\begin{methoddesc}{add_label}{label} +Add \var{label} to the list of labels on the message. +\end{methoddesc} + +\begin{methoddesc}{remove_label}{label} +Remove \var{label} from the list of labels on the message. +\end{methoddesc} + +\begin{methoddesc}{get_visible}{} +Return an \class{Message} instance whose headers are the message's visible +headers and whose body is empty. +\end{methoddesc} + +\begin{methoddesc}{set_visible}{visible} +Set the message's visible headers to be the same as the headers in +\var{message}. Parameter \var{visible} should be a \class{Message} instance, an +\class{email.Message.Message} instance, a string, or a file-like object. +\end{methoddesc} + +\begin{methoddesc}{update_visible}{} +When a \class{BabylMessage} instance's original headers are modified, the +visible headers are not automatically modified to correspond. This method +updates the visible headers as follows: each visible header with a +corresponding original header is set to the value of the original header, each +visible header without a corresponding original header is removed, and any of +\mailheader{Date}, \mailheader{From}, \mailheader{Reply-To}, \mailheader{To}, +\mailheader{CC}, and \mailheader{Subject} that are present in the original +headers but not the visible headers are added to the visible headers. +\end{methoddesc} + +When a \class{BabylMessage} instance is created based upon a +\class{MaildirMessage} instance, the following conversions take place: + +\begin{tableii}{l|l}{textrm} + {Resulting state}{\class{MaildirMessage} state} +\lineii{"unseen" label}{no S flag} +\lineii{"deleted" label}{T flag} +\lineii{"answered" label}{R flag} +\lineii{"forwarded" label}{P flag} +\end{tableii} + +When a \class{BabylMessage} instance is created based upon an +\class{mboxMessage} or \class{MMDFMessage} instance, the \mailheader{Status} +and \mailheader{X-Status} headers are omitted and the following conversions +take place: + +\begin{tableii}{l|l}{textrm} + {Resulting state}{\class{mboxMessage} or \class{MMDFMessage} state} +\lineii{"unseen" label}{no R flag} +\lineii{"deleted" label}{D flag} +\lineii{"answered" label}{A flag} +\end{tableii} + +When a \class{BabylMessage} instance is created based upon an \class{MHMessage} +instance, the following conversions take place: + +\begin{tableii}{l|l}{textrm} + {Resulting state}{\class{MHMessage} state} +\lineii{"unseen" label}{"unseen" sequence} +\lineii{"answered" label}{"replied" sequence} +\end{tableii} \subsubsection{\class{MMDFMessage}} \label{mailbox-mmdfmessage} +\begin{classdesc}{MMDFMessage}{\optional{message}} +A message with MMDF-specific behaviors. Parameter \var{message} has the same +meaning as with the \class{Message} constructor. +\end{classdesc} + +As with message in an mbox mailbox, MMDF messages are stored with the sender's +address and the delivery date in an initial line beginning with "From ". +Likewise, flags that indicate the state of the message are typically stored in +\mailheader{Status} and \mailheader{X-Status} headers. + +Conventional flags for MMDF messages are identical to those of mbox message and +are as follows: + +\begin{tableiii}{l|l|l}{textrm}{Flag}{Meaning}{Explanation} +\lineiii{R}{Read}{Read by the user} +\lineiii{O}{Old}{Previously detected by mail reader} +\lineiii{D}{Deleted}{Marked for subsequent deletion} +\lineiii{F}{Flagged}{Marked by the user as important} +\lineiii{A}{Answered}{Responded to} +\end{tableiii} + +The "R" and "O" flags are stored in the \mailheader{Status} header, and the +"D", "F", and "A" flags are stored in the \mailheader{X-Status} header. The +flags and headers typically appear in the order mentioned. + +\class{MMDFMessage} instances offer the following methods, which are identical +to those offered by \class{mboxMessage}: + +\begin{methoddesc}{get_from}{} +Return a string representing the "From~" line that marks the start of the +message in an mbox mailbox. The leading "From~" and the trailing newline are +excluded. +\end{methoddesc} + +\begin{methoddesc}{set_from}{from_\optional{, time_=None}} +Set the "From~" line to \var{from_}, which should be specified without a +leading "From~" or trailing newline. If \var{time_} is specified, it should be +a \class{struct_time} or a tuple suitable for passing to +\method{time.strftime()}; if \var{time_} is \code{True}, the result of +\method{time.gmtime()} is used. +\end{methoddesc} + +\begin{methoddesc}{get_flags}{} +Return a string specifying the flags that are currently set. If the message +complies with the conventional format, the result is the concatenation in the +following order of zero or one occurrence of each of \character{R}, +\character{O}, \character{D}, \character{F}, and \character{A}. +\end{methoddesc} + +\begin{methoddesc}{set_flags}{flags} +Set the flags specified by \var{flags} and unset all others. Parameter +\var{flags} should be the concatenation in any order of zero or more +occurrences of each of \character{R}, \character{O}, \character{D}, +\character{F}, and \character{A}. +\end{methoddesc} + +\begin{methoddesc}{add_flags}{flags} +Set the flags specified by \var{flags} (if they are not set), and don't change +other flags. Parameter \var{flags} should be the concatenation in any order of +zero or more occurrences of each of \character{R}, \character{O}, +\character{D}, \character{F}, and \character{A}. +\end{methoddesc} + +\begin{methoddesc}{remove_flags}{flags} +Unset the flags specified by \var{flags} (if they are set), and don't change +other flags. Parameter \var{flags} should be the concatenation in any order of +zero or more occurrences of each of \character{R}, \character{O}, +\character{D}, \character{F}, and \character{A}. +\end{methoddesc} + +When an \class{MMDFMessage} instance is created based upon a +\class{MaildirMessage} instance, the following conversions take place: + +\begin{tableii}{l|l}{textrm} + {Resulting state}{\class{MaildirMessage} state} +\lineii{R flag}{S flag} +\lineii{O flag}{"cur" subdirectory} +\lineii{D flag}{T flag} +\lineii{F flag}{F flag} +\lineii{A flag}{R flag} +\end{tableii} + +When an \class{MMDFMessage} instance is created based upon an \class{MHMessage} +instance, the following conversions take place: + +\begin{tableii}{l|l}{textrm} + {Resulting state}{\class{MHMessage} state} +\lineii{R flag and O flag}{no "unseen" sequence} +\lineii{O flag}{"unseen" sequence} +\lineii{F flag}{"flagged" sequence} +\lineii{A flag}{"replied" sequence} +\end{tableii} + +When an \class{MMDFMessage} instance is created based upon a +\class{BabylMessage} instance, the following conversions take place: + +\begin{tableii}{l|l}{textrm} + {Resulting state}{\class{BabylMessage} state} +\lineii{R flag and O flag}{no "unseen" label} +\lineii{O flag}{"unseen" label} +\lineii{D flag}{"deleted" label} +\lineii{A flag}{"answered" label} +\end{tableii} + +When an \class{MMDFMessage} instance is created based upon an +\class{mboxMessage} instance, the "From~" line is copied and all flags directly +correspond: + +\begin{tableii}{l|l}{textrm} + {Resulting state}{\class{mboxMessage} state} +\lineii{R flag}{R flag} +\lineii{O flag}{O flag} +\lineii{D flag}{D flag} +\lineii{F flag}{F flag} +\lineii{A flag}{A flag} +\end{tableii} + \subsection{Deprecated classes} \label{mailbox-deprecated-classes} Index: test_mailbox.py =================================================================== RCS file: /cvsroot/python/python/nondist/sandbox/mailbox/test_mailbox.py,v retrieving revision 1.1 retrieving revision 1.2 diff -u -d -r1.1 -r1.2 --- test_mailbox.py 26 Jul 2005 20:47:48 -0000 1.1 +++ test_mailbox.py 30 Jul 2005 22:49:08 -0000 1.2 @@ -451,7 +451,7 @@ msg0.set_flags('TP') key = box.add(msg0) msg_returned = box.get_message(key) - self.assert_(msg_returned.get_subdir() == 'cur') + self.assert_(msg_returned.get_subdir() == 'new') self.assert_(msg_returned.get_flags() == 'PT') msg1 = mailbox.MaildirMessage(template % 1) box[key] = msg1 @@ -464,7 +464,7 @@ box[key] = msg2 box[key] = template % 3 msg_returned = box.get_message(key) - self.assert_(msg_returned.get_subdir() == 'cur') + self.assert_(msg_returned.get_subdir() == 'new') self.assert_(msg_returned.get_flags() == 'S') self.assert_(msg_returned.get_payload() == '3') @@ -696,7 +696,9 @@ # Copy self's format-specific data to other message formats. # This test is superficial; better ones are in TestMessageConversion. msg = self._factory() - for class_ in (mailbox.Message, mailbox.MaildirMessage): + for class_ in (mailbox.Message, mailbox.MaildirMessage, + mailbox.mboxMessage, mailbox.MHMessage, + mailbox.BabylMessage, mailbox.MMDFMessage): other_msg = class_() msg._explain_to(other_msg) other_msg = email.Message.Message() @@ -735,7 +737,7 @@ self.assert_(msg.get_flags() == '') self.assert_(msg.get_subdir() == 'new') msg.set_flags('F') - self.assert_(msg.get_subdir() == 'cur') + self.assert_(msg.get_subdir() == 'new') self.assert_(msg.get_flags() == 'F') msg.set_flags('SDTP') self.assert_(msg.get_flags() == 'DPST') @@ -743,7 +745,7 @@ self.assert_(msg.get_flags() == 'DFPST') msg.remove_flags('TDRP') self.assert_(msg.get_flags() == 'FS') - self.assert_(msg.get_subdir() == 'cur') + self.assert_(msg.get_subdir() == 'new') self._check_sample(msg) def test_info(self): @@ -774,8 +776,377 @@ self._check_sample(msg) +class _TestMboxMMDFMessage(TestMessage): + + _factory = mailbox._mboxMMDFMessage + + def _post_initialize_hook(self, msg): + self._check_from(msg) + + def test_initialize_with_unixfrom(self): + # Initialize with a message that already has a _unixfrom attribute + msg = mailbox.Message(_sample_message) + msg.set_unixfrom('From foo@bar blah') + msg = mailbox.mboxMessage(msg) + self.assert_(msg.get_from() == 'foo@bar blah', msg.get_from()) + + def test_from(self): + # Get and set "From " line + msg = mailbox.mboxMessage(_sample_message) + self._check_from(msg) + msg.set_from('foo bar') + self.assert_(msg.get_from() == 'foo bar') + msg.set_from('foo@bar', True) + self._check_from(msg, 'foo@bar') + msg.set_from('blah@temp', time.localtime()) + self._check_from(msg, 'blah@temp') + + def test_flags(self): + # Use get_flags(), set_flags(), add_flags(), remove_flags() + msg = mailbox.mboxMessage(_sample_message) + self.assert_(msg.get_flags() == '') + msg.set_flags('F') + self.assert_(msg.get_flags() == 'F') + msg.set_flags('XODR') + self.assert_(msg.get_flags() == 'RODX') + msg.add_flags('FA') + self.assert_(msg.get_flags() == 'RODFAX') + msg.remove_flags('FDXA') + self.assert_(msg.get_flags() == 'RO') + self._check_sample(msg) + + def _check_from(self, msg, sender=None): + # Check contents of "From " line + if sender is None: + sender = "MAILER-DAEMON" + self.assert_(re.match(sender + r" \w{3} \w{3} [\d ]\d [\d ]\d:\d{2}:" + r"\d{2} \d{4}", msg.get_from()) is not None) + + +class TestMboxMessage(_TestMboxMMDFMessage): + + _factory = mailbox.mboxMessage + + +class TestMHMessage(TestMessage): + + _factory = mailbox.MHMessage + + def _post_initialize_hook(self, msg): + self.assert_(msg._sequences == []) + + def test_sequences(self): + # List, join, and leave sequences + msg = mailbox.MHMessage(_sample_message) + self.assert_(msg.list_sequences() == []) + msg.join_sequence('unseen') + self.assert_(msg.list_sequences() == ['unseen']) + msg.join_sequence('flagged') + self.assert_(msg.list_sequences() == ['unseen', 'flagged']) + msg.join_sequence('flagged') + self.assert_(msg.list_sequences() == ['unseen', 'flagged']) + msg.leave_sequence('unseen') + self.assert_(msg.list_sequences() == ['flagged']) + msg.join_sequence('foobar') + self.assert_(msg.list_sequences() == ['flagged', 'foobar']) + msg.leave_sequence('replied') + self.assert_(msg.list_sequences() == ['flagged', 'foobar']) + + +class TestBabylMessage(TestMessage): + + _factory = mailbox.BabylMessage + + def _post_initialize_hook(self, msg): + self.assert_(msg._labels == []) + + def test_labels(self): + # List, add, and remove labels + msg = mailbox.BabylMessage(_sample_message) + self.assert_(msg.list_labels() == []) + msg.add_label('filed') + self.assert_(msg.list_labels() == ['filed']) + msg.add_label('resent') + self.assert_(msg.list_labels() == ['filed', 'resent']) + msg.add_label('resent') + self.assert_(msg.list_labels() == ['filed', 'resent']) + msg.remove_label('filed') + self.assert_(msg.list_labels() == ['resent']) + msg.add_label('foobar') + self.assert_(msg.list_labels() == ['resent', 'foobar']) + msg.remove_label('unseen') + self.assert_(msg.list_labels() == ['resent', 'foobar']) + + def test_visible(self): + # Get, set, and update visible headers + msg = mailbox.BabylMessage(_sample_message) + visible = msg.get_visible() + self.assert_(visible.keys() == []) + self.assert_(visible.get_payload() is None) + visible['User-Agent'] = 'FooBar 1.0' + visible['X-Whatever'] = 'Blah' + self.assert_(msg.get_visible().keys() == []) + msg.set_visible(visible) + visible = msg.get_visible() + self.assert_(visible.keys() == ['User-Agent', 'X-Whatever']) + self.assert_(visible['User-Agent'] == 'FooBar 1.0') + self.assert_(visible['X-Whatever'] == 'Blah') + self.assert_(visible.get_payload() is None) + msg.update_visible() + self.assert_(visible.keys() == ['User-Agent', 'X-Whatever']) + self.assert_(visible.get_payload() is None) + visible = msg.get_visible() + self.assert_(visible.keys() == ['User-Agent', 'Date', 'From', 'To', + 'Subject']) + for header in ('User-Agent', 'Date', 'From', 'To', 'Subject'): + self.assert_(visible[header] == msg[header]) + + +class TestMMDFMessage(_TestMboxMMDFMessage): + + _factory = mailbox.MMDFMessage + + class TestMessageConversion(TestBase): - pass + + def test_plain_to_x(self): + # Convert Message to all formats + for class_ in (mailbox.Message, mailbox.MaildirMessage, + mailbox.mboxMessage, mailbox.MHMessage, + mailbox.BabylMessage, mailbox.MMDFMessage): + msg_plain = mailbox.Message(_sample_message) + msg = class_(msg_plain) + self._check_sample(msg) + + def test_x_to_plain(self): + # Convert all formats to Message + for class_ in (mailbox.Message, mailbox.MaildirMessage, + mailbox.mboxMessage, mailbox.MHMessage, + mailbox.BabylMessage, mailbox.MMDFMessage): + msg = class_(_sample_message) + msg_plain = mailbox.Message(msg) + self._check_sample(msg_plain) + + def test_x_to_invalid(self): + # Convert all formats to an invalid format + for class_ in (mailbox.Message, mailbox.MaildirMessage, + mailbox.mboxMessage, mailbox.MHMessage, + mailbox.BabylMessage, mailbox.MMDFMessage): + self.assertRaises(TypeError, lambda: class_(False)) + + def test_maildir_to_maildir(self): + # Convert MaildirMessage to MaildirMessage + msg_maildir = mailbox.MaildirMessage(_sample_message) + msg_maildir.set_flags('DFPRST') + msg_maildir.set_subdir('cur') + msg = mailbox.MaildirMessage(msg_maildir) + self._check_sample(msg) + self.assert_(msg.get_flags() == 'DFPRST') + self.assert_(msg.get_subdir() == 'cur') + + def test_maildir_to_mboxmmdf(self): + # Convert MaildirMessage to mboxmessage and MMDFMessage + pairs = (('D', ''), ('F', 'F'), ('P', ''), ('R', 'A'), ('S', 'R'), + ('T', 'D'), ('DFPRST', 'RDFA')) + for class_ in (mailbox.mboxMessage, mailbox.MMDFMessage): + msg_maildir = mailbox.MaildirMessage(_sample_message) + for setting, result in pairs: + msg_maildir.set_flags(setting) + self.assert_(class_(msg_maildir).get_flags() == result) + msg_maildir.set_subdir('cur') + self.assert_(class_(msg_maildir).get_flags() == 'RODFA') + + def test_maildir_to_mh(self): + # Convert MaildirMessage to MHMessage + msg_maildir = mailbox.MaildirMessage(_sample_message) + pairs = (('D', ['unseen']), ('F', ['unseen', 'flagged']), + ('P', ['unseen']), ('R', ['unseen', 'replied']), ('S', []), + ('T', ['unseen']), ('DFPRST', ['replied', 'flagged'])) + for setting, result in pairs: + msg_maildir.set_flags(setting) + self.assert_(mailbox.MHMessage(msg_maildir).list_sequences() == \ + result) + + def test_maildir_to_babyl(self): + # Convert MaildirMessage to Babyl + msg_maildir = mailbox.MaildirMessage(_sample_message) + pairs = (('D', ['unseen']), ('F', ['unseen']), + ('P', ['unseen', 'forwarded']), ('R', ['unseen', 'answered']), + ('S', []), ('T', ['unseen', 'deleted']), + ('DFPRST', ['deleted', 'answered', 'forwarded'])) + for setting, result in pairs: + msg_maildir.set_flags(setting) + self.assert_(mailbox.BabylMessage(msg_maildir).list_labels() == \ + result) + + def test_mboxmmdf_to_maildir(self): + # Convert mboxMessage and MMDFMessage to MaildirMessage + for class_ in (mailbox.mboxMessage, mailbox.MMDFMessage): + msg_mboxMMDF = class_(_sample_message) + pairs = (('R', 'S'), ('O', ''), ('D', 'T'), ('F', 'F'), ('A', 'R'), + ('RODFA', 'FRST')) + for setting, result in pairs: + msg_mboxMMDF.set_flags(setting) + self.assert_(mailbox.MaildirMessage(msg_mboxMMDF).get_flags() \ + == result) + msg_mboxMMDF.set_flags('O') + self.assert_(mailbox.MaildirMessage(msg_mboxMMDF).get_subdir() == \ + 'cur') + + def test_mboxmmdf_to_mboxmmdf(self): + # Convert mboxMessage and MMDFMessage to mboxMessage and MMDFMessage + for class_ in (mailbox.mboxMessage, mailbox.MMDFMessage): + msg_mboxMMDF = class_(_sample_message) + msg_mboxMMDF.set_flags('RODFA') + msg_mboxMMDF.set_from('foo@bar') + for class2_ in (mailbox.mboxMessage, mailbox.MMDFMessage): + msg2 = class2_(msg_mboxMMDF) + self.assert_(msg2.get_flags() == 'RODFA') + self.assert_(msg2.get_from() == 'foo@bar') + + def test_mboxmmdf_to_mh(self): + # Convert mboxMessage and MMDFMessage to MHMessage + for class_ in (mailbox.mboxMessage, mailbox.MMDFMessage): + msg_mboxMMDF = class_(_sample_message) + pairs = (('R', []), ('O', ['unseen']), ('D', ['unseen']), + ('F', ['unseen', 'flagged']), + ('A', ['unseen', 'replied']), + ('RODFA', ['replied', 'flagged'])) + for setting, result in pairs: + msg_mboxMMDF.set_flags(setting) + self.assert_(mailbox.MHMessage(msg_mboxMMDF).list_sequences() \ + == result) + + def test_mboxmmdf_to_babyl(self): + # Convert mboxMessage and MMDFMessage to BabylMessage + for class_ in (mailbox.mboxMessage, mailbox.MMDFMessage): + msg = class_(_sample_message) + pairs = (('R', []), ('O', ['unseen']), + ('D', ['unseen', 'deleted']), ('F', ['unseen']), + ('A', ['unseen', 'answered']), + ('RODFA', ['deleted', 'answered'])) + for setting, result in pairs: + msg.set_flags(setting) + self.assert_(mailbox.BabylMessage(msg).list_labels() == result) + + def test_mh_to_maildir(self): + # Convert MHMessage to MaildirMessage + pairs = (('unseen', ''), ('replied', 'RS'), ('flagged', 'FS')) + for setting, result in pairs: + msg = mailbox.MHMessage(_sample_message) + msg.join_sequence(setting) + self.assert_(mailbox.MaildirMessage(msg).get_flags() == result) + self.assert_(mailbox.MaildirMessage(msg).get_subdir() == 'cur') + msg = mailbox.MHMessage(_sample_message) + msg.join_sequence('unseen') + msg.join_sequence('replied') + msg.join_sequence('flagged') + self.assert_(mailbox.MaildirMessage(msg).get_flags() == 'FR') + self.assert_(mailbox.MaildirMessage(msg).get_subdir() == 'cur') + + def test_mh_to_mboxmmdf(self): + # Convert MHMessage to mboxMessage and MMDFMessage + pairs = (('unseen', 'O'), ('replied', 'ROA'), ('flagged', 'ROF')) + for setting, result in pairs: + msg = mailbox.MHMessage(_sample_message) + msg.join_sequence(setting) + for class_ in (mailbox.mboxMessage, mailbox.MMDFMessage): + self.assert_(class_(msg).get_flags() == result) + msg = mailbox.MHMessage(_sample_message) + msg.join_sequence('unseen') + msg.join_sequence('replied') + msg.join_sequence('flagged') + for class_ in (mailbox.mboxMessage, mailbox.MMDFMessage): + self.assert_(class_(msg).get_flags() == 'OFA') + + def test_mh_to_mh(self): + # Convert MHMessage to MHMessage + msg = mailbox.MHMessage(_sample_message) + msg.join_sequence('unseen') + msg.join_sequence('replied') + msg.join_sequence('flagged') + self.assert_(mailbox.MHMessage(msg).list_sequences() == \ + ['unseen', 'replied', 'flagged']) + + def test_mh_to_babyl(self): + # Convert MHMessage to BabylMessage + pairs = (('unseen', ['unseen']), ('replied', ['answered']), + ('flagged', [])) + for setting, result in pairs: + msg = mailbox.MHMessage(_sample_message) + msg.join_sequence(setting) + self.assert_(mailbox.BabylMessage(msg).list_labels() == result) + msg = mailbox.MHMessage(_sample_message) + msg.join_sequence('unseen') + msg.join_sequence('replied') + msg.join_sequence('flagged') + self.assert_(mailbox.BabylMessage(msg).list_labels() == \ + ['unseen', 'answered']) + + def test_babyl_to_maildir(self): + # Convert BabylMessage to MaildirMessage + pairs = (('unseen', ''), ('deleted', 'ST'), ('filed', 'S'), + ('answered', 'RS'), ('forwarded', 'PS'), ('edited', 'S'), + ('resent', 'PS')) + for setting, result in pairs: + msg = mailbox.BabylMessage(_sample_message) + msg.add_label(setting) + self.assert_(mailbox.MaildirMessage(msg).get_flags() == result) + self.assert_(mailbox.MaildirMessage(msg).get_subdir() == 'cur') + msg = mailbox.BabylMessage(_sample_message) + for label in ('unseen', 'deleted', 'filed', 'answered', 'forwarded', + 'edited', 'resent'): + msg.add_label(label) + self.assert_(mailbox.MaildirMessage(msg).get_flags() == 'PRT') + self.assert_(mailbox.MaildirMessage(msg).get_subdir() == 'cur') + + def test_babyl_to_mboxmmdf(self): + # Convert BabylMessage to mboxMessage and MMDFMessage + pairs = (('unseen', 'O'), ('deleted', 'ROD'), ('filed', 'RO'), + ('answered', 'ROA'), ('forwarded', 'RO'), ('edited', 'RO'), + ('resent', 'RO')) + for setting, result in pairs: + for class_ in (mailbox.mboxMessage, mailbox.MMDFMessage): + msg = mailbox.BabylMessage(_sample_message) + msg.add_label(setting) + self.assert_(class_(msg).get_flags() == result) + msg = mailbox.BabylMessage(_sample_message) + for label in ('unseen', 'deleted', 'filed', 'answered', 'forwarded', + 'edited', 'resent'): + msg.add_label(label) + for class_ in (mailbox.mboxMessage, mailbox.MMDFMessage): + self.assert_(class_(msg).get_flags() == 'ODA') + + def test_babyl_to_mh(self): + # Convert BabylMessage to MHMessage + pairs = (('unseen', ['unseen']), ('deleted', []), ('filed', []), + ('answered', ['replied']), ('forwarded', []), ('edited', []), + ('resent', [])) + for setting, result in pairs: + msg = mailbox.BabylMessage(_sample_message) + msg.add_label(setting) + self.assert_(mailbox.MHMessage(msg).list_sequences() == result) + msg = mailbox.BabylMessage(_sample_message) + for label in ('unseen', 'deleted', 'filed', 'answered', 'forwarded', + 'edited', 'resent'): + msg.add_label(label) + self.assert_(mailbox.MHMessage(msg).list_sequences() == \ + ['unseen', 'replied']) + + def test_babyl_to_babyl(self): + # Convert BabylMessage to BabylMessage + msg = mailbox.BabylMessage(_sample_message) + msg.update_visible() + for label in ('unseen', 'deleted', 'filed', 'answered', 'forwarded', + 'edited', 'resent'): + msg.add_label(label) + msg2 = mailbox.BabylMessage(msg) + self.assert_(msg2.list_labels() == ['unseen', 'deleted', 'filed', + 'answered', 'forwarded', 'edited', + 'resent']) + self.assert_(msg.get_visible().keys() == msg2.get_visible().keys()) + for key in msg.get_visible().keys(): + self.assert_(msg.get_visible()[key] == msg2.get_visible()[key]) class TestProxyFileBase(TestBase): @@ -1016,8 +1387,9 @@ def test_main(): test_support.run_unittest(TestMaildir, TestMessage, TestMaildirMessage, - TestMessageConversion, TestProxyFile, - TestPartialFile) + TestMboxMessage, TestMHMessage, TestBabylMessage, + TestMMDFMessage, TestMessageConversion, + TestProxyFile, TestPartialFile) if __name__ == '__main__':
participants (1)
-
gregorykjohnsonï¼ users.sourceforge.net