[prev in list] [next in list] [prev in thread] [next in thread] 

List:       mailman-cvs
Subject:    [Mailman-checkins] SF.net SVN: mailman: [8205]
From:       bwarsaw () users ! sourceforge ! net
Date:       2007-05-08 6:24:51
Message-ID: E1HlJ7z-00060a-Ja () sc8-pr-svn1 ! sourceforge ! net
[Download RAW message or body]

Revision: 8205
          http://svn.sourceforge.net/mailman/?rev=8205&view=rev
Author:   bwarsaw
Date:     2007-05-07 23:24:51 -0700 (Mon, 07 May 2007)

Log Message:
-----------
IMailingListAddresses.confirm_address -> made a function instead of an
attribute because it needs to take a 'cookie' argument.

IListManager.new() -> IListManager.create()
IListManager.remove() -> IListManager.delete()
IListManager.get() added

IUserManager started.  This is currently more focused on producing and
managing rosters (IRoster) instead of users directly.  This may change.

Add a bunch of roster related exceptions.  Add a 'name' field to the Roster
database model class.

Start ripping and tearing the MailList class for use with the new interfaces
and Elixir-based record objects.  For now, it's enough to totally rewrite the
__init__() and eliminate the __new__().  Under normal circumstances,
MailList's won't get created directly, but through the IListManager interface
(yes, there are lots of things that still do it the old way.  they are
broken).

Rip out the old MailList extension mechanism.  I think the new way will be to
import Mailman.ext.init_mlist() can if it exists, call it with the just
created MailList instance.  InitTempVars() simplified.  Load() simplified
(check_version is removed).

Bye bye internal_name().  fullpath() -> full_path property.

Added implementations of IMailingListIdentity and IMailingListAddresses, and
removed some obsolete methods.

Give the ListManager implementation a weakref.WeakKeyDictionary object map so
that the same model MailingList data objects get mapped to the same MailList
wrapper objects when pulled from the database.

Remove Mailman.enum module and include the standalone munepy enum package.

Add a doctest directory.  These files contain a combination of documentation
and tests.  Added a bunch of tests which show how to use the IListManager
interface, and the beginnings of the IUserManager interface.

Removed _UpdateRecords() and update_pending() functions.  We won't need to
upgrade from those older versions of Mailman.

Modernize Pending.py and ListAdmin.py.

In Defaults.py.in, use $-string substitutions for VERP_CONFIRM_FORMAT.

Modified Paths:
--------------
    branches/exp-elixir-branch/Mailman/Defaults.py.in
    branches/exp-elixir-branch/Mailman/Errors.py
    branches/exp-elixir-branch/Mailman/ListAdmin.py
    branches/exp-elixir-branch/Mailman/MailList.py
    branches/exp-elixir-branch/Mailman/Makefile.in
    branches/exp-elixir-branch/Mailman/Pending.py
    branches/exp-elixir-branch/Mailman/bin/update.py
    branches/exp-elixir-branch/Mailman/database/__init__.py
    branches/exp-elixir-branch/Mailman/database/listmanager.py
    branches/exp-elixir-branch/Mailman/database/model/mailinglist.py
    branches/exp-elixir-branch/Mailman/database/model/roster.py
    branches/exp-elixir-branch/Mailman/interfaces/listmanager.py
    branches/exp-elixir-branch/Mailman/interfaces/mlistemail.py
    branches/exp-elixir-branch/Mailman/interfaces/usermanager.py
    branches/exp-elixir-branch/Mailman/passwords.py
    branches/exp-elixir-branch/Makefile.in
    branches/exp-elixir-branch/README.txt
    branches/exp-elixir-branch/configure
    branches/exp-elixir-branch/configure.in
    branches/exp-elixir-branch/misc/Makefile.in

Added Paths:
-----------
    branches/exp-elixir-branch/Mailman/database/usermanager.py
    branches/exp-elixir-branch/Mailman/docs/
    branches/exp-elixir-branch/Mailman/docs/Makefile.in
    branches/exp-elixir-branch/Mailman/docs/__init__.py
    branches/exp-elixir-branch/Mailman/docs/mlist-addresses.txt
    branches/exp-elixir-branch/Mailman/docs/mlist-creation.txt
    branches/exp-elixir-branch/Mailman/docs/mlist-rosters.txt
    branches/exp-elixir-branch/Mailman/testing/test_mlist_addresses.py
    branches/exp-elixir-branch/Mailman/testing/test_mlist_creation.py
    branches/exp-elixir-branch/Mailman/testing/test_rosters.py
    branches/exp-elixir-branch/misc/munepy-1.0-py2.5.egg

Removed Paths:
-------------
    branches/exp-elixir-branch/Mailman/enum.py
    branches/exp-elixir-branch/Mailman/testing/test_enum.py

Modified: branches/exp-elixir-branch/Mailman/Defaults.py.in
===================================================================
--- branches/exp-elixir-branch/Mailman/Defaults.py.in	2007-05-08 03:16:04 UTC (rev \
                8204)
+++ branches/exp-elixir-branch/Mailman/Defaults.py.in	2007-05-08 06:24:51 UTC (rev \
8205) @@ -26,7 +26,7 @@
 
 import os
 
-from Mailman.enum import Enum
+from munepy import Enum
 
 
 def seconds(s): return s
@@ -674,9 +674,9 @@
 # friendly Subject: on the message, but requires cooperation from the MTA.
 # Format is like VERP_FORMAT above, but with the following substitutions:
 #
-# %(addr)s -- the list-confirm mailbox will be set here
-# %(cookie)s  -- the confirmation cookie will be set here
-VERP_CONFIRM_FORMAT = '%(addr)s+%(cookie)s'
+# $address  -- the list-confirm address
+# $cookie   -- the confirmation cookie
+VERP_CONFIRM_FORMAT = '$address+$cookie'
 
 # This is analogous to VERP_REGEXP, but for splitting apart the
 # VERP_CONFIRM_FORMAT.  MUAs have been observed that mung

Modified: branches/exp-elixir-branch/Mailman/Errors.py
===================================================================
--- branches/exp-elixir-branch/Mailman/Errors.py	2007-05-08 03:16:04 UTC (rev 8204)
+++ branches/exp-elixir-branch/Mailman/Errors.py	2007-05-08 06:24:51 UTC (rev 8205)
@@ -212,3 +212,21 @@
 
     def __str__(self):
         return 'A bad password scheme was given: %s' % self.scheme_name
+
+
+
+class UserError(MailmanError):
+    """A general user-related error occurred."""
+
+
+class RosterError(UserError):
+    """A roster-related error occurred."""
+
+
+class RosterExistsError(RosterError):
+    """The named roster already exists."""
+
+
+class NoSuchRosterError(RosterError):
+    """The named roster does not exist."""
+

Modified: branches/exp-elixir-branch/Mailman/ListAdmin.py
===================================================================
--- branches/exp-elixir-branch/Mailman/ListAdmin.py	2007-05-08 03:16:04 UTC (rev \
                8204)
+++ branches/exp-elixir-branch/Mailman/ListAdmin.py	2007-05-08 06:24:51 UTC (rev \
8205) @@ -24,6 +24,8 @@
 elsewhere.
 """
 
+from __future__ import with_statement
+
 import os
 import time
 import email
@@ -71,104 +73,99 @@
         self.next_request_id = 1
 
     def InitTempVars(self):
-        self.__db = None
-        self.__filename = os.path.join(self.fullpath(), 'request.pck')
+        self._db = None
+        self._filename = os.path.join(self.full_path, 'request.pck')
 
-    def __opendb(self):
-        if self.__db is None:
+    def _opendb(self):
+        if self._db is None:
             assert self.Locked()
             try:
-                fp = open(self.__filename)
-                try:
-                    self.__db = cPickle.load(fp)
-                finally:
-                    fp.close()
+                with open(self._filename) as fp:
+                    self._db = cPickle.load(fp)
             except IOError, e:
-                if e.errno <> errno.ENOENT: raise
-                self.__db = {}
+                if e.errno <> errno.ENOENT:
+                    raise
+                self._db = {}
                 # put version number in new database
-                self.__db['version'] = IGN, config.REQUESTS_FILE_SCHEMA_VERSION
+                self._db['version'] = IGN, config.REQUESTS_FILE_SCHEMA_VERSION
 
-    def __closedb(self):
-        if self.__db is not None:
+    def _closedb(self):
+        if self._db is not None:
             assert self.Locked()
             # Save the version number
-            self.__db['version'] = IGN, config.REQUESTS_FILE_SCHEMA_VERSION
+            self._db['version'] = IGN, config.REQUESTS_FILE_SCHEMA_VERSION
             # Now save a temp file and do the tmpfile->real file dance.  BAW:
             # should we be as paranoid as for the config.pck file?  Should we
             # use pickle?
-            tmpfile = self.__filename + '.tmp'
-            fp = open(tmpfile, 'w')
-            try:
-                cPickle.dump(self.__db, fp, 1)
+            tmpfile = self._filename + '.tmp'
+            with open(tmpfile, 'w') as fp:
+                cPickle.dump(self._db, fp, 1)
                 fp.flush()
                 os.fsync(fp.fileno())
-            finally:
-                fp.close()
-            self.__db = None
+            self._db = None
             # Do the dance
-            os.rename(tmpfile, self.__filename)
+            os.rename(tmpfile, self._filename)
 
-    def __nextid(self):
+    @property
+    def _next_id(self):
         assert self.Locked()
         while True:
+            missing = object()
             next = self.next_request_id
             self.next_request_id += 1
-            if not self.__db.has_key(next):
-                break
-        return next
+            if self._db.setdefault(next, missing) is missing:
+                yield next
 
     def SaveRequestsDb(self):
-        self.__closedb()
+        self._closedb()
 
     def NumRequestsPending(self):
-        self.__opendb()
+        self._opendb()
         # Subtract one for the version pseudo-entry
-        return len(self.__db) - 1
+        return len(self._db) - 1
 
-    def __getmsgids(self, rtype):
-        self.__opendb()
-        ids = [k for k, (op, data) in self.__db.items() if op == rtype]
-        ids.sort()
+    def _getmsgids(self, rtype):
+        self._opendb()
+        ids = sorted([k for k, (op, data) in self._db.items() if op == rtype])
         return ids
 
     def GetHeldMessageIds(self):
-        return self.__getmsgids(HELDMSG)
+        return self._getmsgids(HELDMSG)
 
     def GetSubscriptionIds(self):
-        return self.__getmsgids(SUBSCRIPTION)
+        return self._getmsgids(SUBSCRIPTION)
 
     def GetUnsubscriptionIds(self):
-        return self.__getmsgids(UNSUBSCRIPTION)
+        return self._getmsgids(UNSUBSCRIPTION)
 
     def GetRecord(self, id):
-        self.__opendb()
-        type, data = self.__db[id]
+        self._opendb()
+        type, data = self._db[id]
         return data
 
     def GetRecordType(self, id):
-        self.__opendb()
-        type, data = self.__db[id]
+        self._opendb()
+        type, data = self._db[id]
         return type
 
     def HandleRequest(self, id, value, comment=None, preserve=None,
                       forward=None, addr=None):
-        self.__opendb()
-        rtype, data = self.__db[id]
+        self._opendb()
+        rtype, data = self._db[id]
         if rtype == HELDMSG:
-            status = self.__handlepost(data, value, comment, preserve,
-                                       forward, addr)
+            status = self._handlepost(data, value, comment, preserve,
+                                      forward, addr)
         elif rtype == UNSUBSCRIPTION:
-            status = self.__handleunsubscription(data, value, comment)
+            status = self._handleunsubscription(data, value, comment)
         else:
             assert rtype == SUBSCRIPTION
-            status = self.__handlesubscription(data, value, comment)
+            status = self._handlesubscription(data, value, comment)
         if status <> DEFER:
             # BAW: Held message ids are linked to Pending cookies, allowing
             # the user to cancel their post before the moderator has approved
             # it.  We should probably remove the cookie associated with this
             # id, but we have no way currently of correlating them. :(
-            del self.__db[id]
+            del self._db[id]
 
     def HoldMessage(self, msg, reason, msgdata={}):
         # Make a copy of msgdata so that subsequent changes won't corrupt the
@@ -176,9 +173,9 @@
         # not be relevant when the message is resurrected.
         msgdata = msgdata.copy()
         # assure that the database is open for writing
-        self.__opendb()
+        self._opendb()
         # get the next unique id
-        id = self.__nextid()
+        id = self._next_id
         # get the message sender
         sender = msg.get_sender()
         # calculate the file name for the message text and write it to disk
@@ -187,8 +184,7 @@
         else:
             ext = 'txt'
         filename = 'heldmsg-%s-%d.%s' % (self.internal_name(), id, ext)
-        fp = open(os.path.join(config.DATA_DIR, filename), 'w')
-        try:
+        with open(os.path.join(config.DATA_DIR, filename), 'w') as fp:
             if config.HOLD_MESSAGES_AS_PICKLES:
                 cPickle.dump(msg, fp, 1)
             else:
@@ -196,8 +192,6 @@
                 g(msg, 1)
             fp.flush()
             os.fsync(fp.fileno())
-        finally:
-            fp.close()
         # save the information to the request database.  for held message
         # entries, each record in the database will be of the following
         # format:
@@ -211,10 +205,10 @@
         #
         msgsubject = msg.get('subject', _('(no subject)'))
         data = time.time(), sender, msgsubject, reason, filename, msgdata
-        self.__db[id] = (HELDMSG, data)
+        self._db[id] = (HELDMSG, data)
         return id
 
-    def __handlepost(self, record, value, comment, preserve, forward, addr):
+    def _handlepost(self, record, value, comment, preserve, forward, addr):
         # For backwards compatibility with pre 2.0beta3
         ptime, sender, subject, reason, filename, msgdata = record
         path = os.path.join(config.DATA_DIR, filename)
@@ -225,24 +219,19 @@
             spamfile = DASH.join(parts)
             # Preserve the message as plain text, not as a pickle
             try:
-                fp = open(path)
+                with open(path) as fp:
+                    msg = cPickle.load(fp)
             except IOError, e:
-                if e.errno <> errno.ENOENT: raise
+                if e.errno <> errno.ENOENT:
+                    raise
                 return LOST
-            try:
-                msg = cPickle.load(fp)
-            finally:
-                fp.close()
             # Save the plain text to a .msg file, not a .pck file
             outpath = os.path.join(config.SPAM_DIR, spamfile)
             head, ext = os.path.splitext(outpath)
             outpath = head + '.msg'
-            outfp = open(outpath, 'w')
-            try:
+            with open(outpath, 'w') as outfp:
                 g = Generator(outfp)
                 g(msg, 1)
-            finally:
-                outfp.close()
         # Now handle updates to the database
         rejection = None
         fp = None
@@ -256,7 +245,8 @@
             try:
                 msg = readMessage(path)
             except IOError, e:
-                if e.errno <> errno.ENOENT: raise
+                if e.errno <> errno.ENOENT:
+                    raise
                 return LOST
             msg = readMessage(path)
             msgdata['approved'] = 1
@@ -281,7 +271,7 @@
         elif value == config.REJECT:
             # Rejected
             rejection = 'Refused'
-            self.__refuse(_('Posting of your message titled "%(subject)s"'),
+            self._refuse(_('Posting of your message titled "%(subject)s"'),
                           sender, comment or _('[No reason given]'),
                           lang=self.getMemberLanguage(sender))
         else:
@@ -297,7 +287,8 @@
             try:
                 copy = readMessage(path)
             except IOError, e:
-                if e.errno <> errno.ENOENT: raise
+                if e.errno <> errno.ENOENT:
+                    raise
                 raise Errors.LostHeldMessage(path)
             # It's possible the addr is a comma separated list of addresses.
             addrs = getaddresses([addr])
@@ -354,9 +345,9 @@
 
     def HoldSubscription(self, addr, fullname, password, digest, lang):
         # Assure that the database is open for writing
-        self.__opendb()
+        self._opendb()
         # Get the next unique id
-        id = self.__nextid()
+        id = self._next_id
         # Save the information to the request database. for held subscription
         # entries, each record in the database will be one of the following
         # format:
@@ -367,7 +358,7 @@
         # the digest flag
         # the user's preferred language
         data = time.time(), addr, fullname, password, digest, lang
-        self.__db[id] = (SUBSCRIPTION, data)
+        self._db[id] = (SUBSCRIPTION, data)
         #
         # TBD: this really shouldn't go here but I'm not sure where else is
         # appropriate.
@@ -399,7 +390,7 @@
         elif value == config.DISCARD:
             pass
         elif value == config.REJECT:
-            self.__refuse(_('Subscription request'), addr,
+            self._refuse(_('Subscription request'), addr,
                           comment or _('[No reason given]'),
                           lang=lang)
         else:
@@ -413,16 +404,16 @@
                 pass
             # TBD: disgusting hack: ApprovedAddMember() can end up closing
             # the request database.
-            self.__opendb()
+            self._opendb()
         return REMOVE
 
     def HoldUnsubscription(self, addr):
         # Assure the database is open for writing
-        self.__opendb()
+        self._opendb()
         # Get the next unique id
-        id = self.__nextid()
+        id = self._next_id
         # All we need to do is save the unsubscribing address
-        self.__db[id] = (UNSUBSCRIPTION, addr)
+        self._db[id] = (UNSUBSCRIPTION, addr)
         log.info('%s: held unsubscription request from %s',
                  self.internal_name(), addr)
         # Possibly notify the administrator of the hold
@@ -444,14 +435,14 @@
                                            self.preferred_language)
             msg.send(self, **{'tomoderators': 1})
 
-    def __handleunsubscription(self, record, value, comment):
+    def _handleunsubscription(self, record, value, comment):
         addr = record
         if value == config.DEFER:
             return DEFER
         elif value == config.DISCARD:
             pass
         elif value == config.REJECT:
-            self.__refuse(_('Unsubscription request'), addr, comment)
+            self._refuse(_('Unsubscription request'), addr, comment)
         else:
             assert value == config.UNSUBSCRIBE
             try:
@@ -461,7 +452,7 @@
                 pass
         return REMOVE
 
-    def __refuse(self, request, recip, comment, origmsg=None, lang=None):
+    def _refuse(self, request, recip, comment, origmsg=None, lang=None):
         # As this message is going to the requestor, try to set the language
         # to his/her language choice, if they are a member.  Otherwise use the
         # list's preferred language.
@@ -492,82 +483,16 @@
                                        subject, text, lang)
         msg.send(self)
 
-    def _UpdateRecords(self):
-        # Subscription records have changed since MM2.0.x.  In that family,
-        # the records were of length 4, containing the request time, the
-        # address, the password, and the digest flag.  In MM2.1a2, they grew
-        # an additional language parameter at the end.  In MM2.1a4, they grew
-        # a fullname slot after the address.  This semi-public method is used
-        # by the update script to coerce all subscription records to the
-        # latest MM2.1 format.
-        #
-        # Held message records have historically either 5 or 6 items too.
-        # These always include the requests time, the sender, subject, default
-        # rejection reason, and message text.  When of length 6, it also
-        # includes the message metadata dictionary on the end of the tuple.
-        #
-        # In Mailman 2.1.5 we converted these files to pickles.
-        filename = os.path.join(self.fullpath(), 'request.db')
-        try:
-            fp = open(filename)
-            try:
-                self.__db = marshal.load(fp)
-            finally:
-                fp.close()
-            os.unlink(filename)
-        except IOError, e:
-            if e.errno <> errno.ENOENT: raise
-            filename = os.path.join(self.fullpath(), 'request.pck')
-            try:
-                fp = open(filename)
-                try:
-                    self.__db = cPickle.load(fp)
-                finally:
-                    fp.close()
-            except IOError, e:
-                if e.errno <> errno.ENOENT: raise
-                self.__db = {}
-        for id, (op, info) in self.__db.items():
-            if op == SUBSCRIPTION:
-                if len(info) == 4:
-                    # pre-2.1a2 compatibility
-                    when, addr, passwd, digest = info
-                    fullname = ''
-                    lang = self.preferred_language
-                elif len(info) == 5:
-                    # pre-2.1a4 compatibility
-                    when, addr, passwd, digest, lang = info
-                    fullname = ''
-                else:
-                    assert len(info) == 6, 'Unknown subscription record layout'
-                    continue
-                # Here's the new layout
-                self.__db[id] = when, addr, fullname, passwd, digest, lang
-            elif op == HELDMSG:
-                if len(info) == 5:
-                    when, sender, subject, reason, text = info
-                    msgdata = {}
-                else:
-                    assert len(info) == 6, 'Unknown held msg record layout'
-                    continue
-                # Here's the new layout
-                self.__db[id] = when, sender, subject, reason, text, msgdata
-        # All done
-        self.__closedb()
 
-
 
 def readMessage(path):
     # For backwards compatibility, we must be able to read either a flat text
     # file or a pickle.
     ext = os.path.splitext(path)[1]
-    fp = open(path)
-    try:
+    with open(path) as fp:
         if ext == '.txt':
             msg = email.message_from_file(fp, Message.Message)
         else:
             assert ext == '.pck'
             msg = cPickle.load(fp)
-    finally:
-        fp.close()
     return msg

Modified: branches/exp-elixir-branch/Mailman/MailList.py
===================================================================
--- branches/exp-elixir-branch/Mailman/MailList.py	2007-05-08 03:16:04 UTC (rev 8204)
+++ branches/exp-elixir-branch/Mailman/MailList.py	2007-05-08 06:24:51 UTC (rev 8205)
@@ -36,8 +36,10 @@
 
 from UserDict import UserDict
 from cStringIO import StringIO
+from string import Template
 from types import MethodType
 from urlparse import urlparse
+from zope.interface import implements
 
 from email.Header import Header
 from email.Utils import getaddresses, formataddr, parseaddr
@@ -50,6 +52,7 @@
 from Mailman.UserDesc import UserDesc
 from Mailman.configuration import config
 from Mailman.database.tables.languages import Language
+from Mailman.interfaces import *
 
 # Base classes
 from Mailman import Pending
@@ -89,66 +92,24 @@
 class MailList(object, HTMLFormatter, Deliverer, ListAdmin,
                Archiver, Digester, SecurityManager, Bouncer, GatewayManager,
                Autoresponder, TopicMgr, Pending.Pending):
-    def __new__(cls, *args, **kws):
-        # Search positional and keyword arguments to find the name of the
-        # existing list that is being opened, with the latter taking
-        # precedence.  If no name can be found, then make sure there are no
-        # arguments, otherwise it's an error.
-        if 'name' in kws:
-            listname = kws.pop('name')
-        elif not args:
-            if not kws:
-                # We're probably being created from the ORM layer, so just let
-                # the super class do its thing.
-                return super(MailList, cls).__new__(cls, *args, **kws)
-            raise ValueError("'name' argument required'")
-        else:
-            listname = args[0]
-        fqdn_listname = Utils.fqdn_listname(listname)
-        listname, hostname = Utils.split_listname(fqdn_listname)
-        mlist = database.find_list(listname, hostname)
-        if not mlist:
-            raise Errors.MMUnknownListError(fqdn_listname)
-        return mlist
 
-    #
-    # A MailList object's basic Python object model support
-    #
-    def __init__(self, name=None, lock=True, check_version=True):
-        # No timeout by default.  If you want to timeout, open the list
-        # unlocked, then lock explicitly.
-        #
+    implements(IMailingList, IMailingListAddresses, IMailingListIdentity)
+
+    def __init__(self, data):
+        self._data = data
         # Only one level of mixin inheritance allowed
         for baseclass in self.__class__.__bases__:
             if hasattr(baseclass, '__init__'):
                 baseclass.__init__(self)
         # Initialize volatile attributes
-        self.InitTempVars(name, check_version)
-        # This extension mechanism allows list-specific overrides of any
-        # method (well, except __init__(), InitTempVars(), and InitVars()
-        # I think).  Note that fullpath() will return None when we're creating
-        # the list, which will only happen when name is None.
-        if name is None:
-            return
-        filename = os.path.join(self.fullpath(), 'extend.py')
-        dict = {}
+        self.InitTempVars()
+        # Give the extension mechanism a chance to process this list.
         try:
-            execfile(filename, dict)
-        except IOError, e:
-            # Ignore missing files, but log other errors
-            if e.errno == errno.ENOENT:
-                pass
-            else:
-                elog.error('IOError reading list extension: %s', e)
+            from Mailman.ext import init_mlist
+        except ImportError:
+            pass
         else:
-            func = dict.get('extend')
-            if func:
-                func(self)
-        if lock:
-            # This will load the database.
-            self.Lock()
-        else:
-            self.Load(name, check_version)
+            init_mlist(self)
 
     def __getattr__(self, name):
         if name.startswith('_'):
@@ -212,44 +173,72 @@
     #
     # Useful accessors
     #
-    def internal_name(self):
-        return self._internal_name
-
-    def fullpath(self):
+    @property
+    def full_path(self):
         return self._full_path
 
+
+
+    # IMailingListIdentity
+
     @property
+    def list_name(self):
+        return self._data.list_name
+
+    @property
+    def host_name(self):
+        return self._data.host_name
+
+    @property
     def fqdn_listname(self):
-        return '%s@%s' % (self._internal_name, self.host_name)
+        return Utils.fqdn_listname(self._data.list_name, self._data.host_name)
 
+
+
+    # IMailingListAddresses
+
     @property
-    def no_reply_address(self):
+    def posting_address(self):
+        return self.fqdn_listname
+
+    @property
+    def noreply_address(self):
         return '%s@%s' % (config.NO_REPLY_ADDRESS, self.host_name)
 
-    def getListAddress(self, extra=None):
-        if extra is None:
-            return self.fqdn_listname
-        return '%s-%s@%s' % (self.internal_name(), extra, self.host_name)
+    @property
+    def owner_address(self):
+        return '%s-owner@%s' % (self.list_name, self.host_name)
 
-    # For backwards compatibility
-    def GetBouncesEmail(self):
-        return self.getListAddress('bounces')
+    @property
+    def request_address(self):
+        return '%s-request@%s' % (self.list_name, self.host_name)
 
-    def GetOwnerEmail(self):
-        return self.getListAddress('owner')
+    @property
+    def bounces_address(self):
+        return '%s-bounces@%s' % (self.list_name, self.host_name)
 
-    def GetRequestEmail(self, cookie=''):
-        if config.VERP_CONFIRMATIONS and cookie:
-            return self.GetConfirmEmail(cookie)
-        else:
-            return self.getListAddress('request')
+    @property
+    def join_address(self):
+        return '%s-join@%s' % (self.list_name, self.host_name)
 
-    def GetConfirmEmail(self, cookie):
-        return config.VERP_CONFIRM_FORMAT % {
-            'addr'  : '%s-confirm' % self.internal_name(),
-            'cookie': cookie,
-            } + '@' + self.host_name
+    @property
+    def leave_address(self):
+        return '%s-leave@%s' % (self.list_name, self.host_name)
 
+    @property
+    def subscribe_address(self):
+        return '%s-subscribe@%s' % (self.list_name, self.host_name)
+
+    @property
+    def unsubscribe_address(self):
+        return '%s-unsubscribe@%s' % (self.list_name, self.host_name)
+
+    def confirm_address(self, cookie):
+        local_part = Template(config.VERP_CONFIRM_FORMAT).safe_substitute(
+            address = '%s-confirm' % self.list_name,
+            cookie  = cookie)
+        return '%s@%s' % (local_part, self.host_name)
+
     def GetConfirmJoinSubject(self, listname, cookie):
         if config.VERP_CONFIRMATIONS and cookie:
             cset = i18n.get_translation().charset() or \
@@ -308,7 +297,7 @@
     #
     # Instance and subcomponent initialization
     #
-    def InitTempVars(self, name, check_version=True):
+    def InitTempVars(self):
         """Set transient variables of this and inherited classes."""
         # Because of the semantics of the database layer, it's possible that
         # this method gets called more than once on an existing object.  For
@@ -325,27 +314,9 @@
         __import__(adaptor_module)
         mod = sys.modules[adaptor_module]
         self._memberadaptor = getattr(mod, adaptor_class)(self)
-        # The timestamp is set whenever we load the state from disk.  If our
-        # timestamp is newer than the modtime of the config.pck file, we don't
-        # need to reload, otherwise... we do.
-        self._timestamp = 0
-        self._make_lock(name or '<site>')
-        # XXX FIXME Sometimes name is fully qualified, sometimes it's not.
-        if name:
-            if '@' in name:
-                self._internal_name, self.host_name = name.split('@', 1)
-                self._full_path = os.path.join(config.LIST_DATA_DIR, name)
-            else:
-                self._internal_name = name
-                self.host_name = config.DEFAULT_EMAIL_HOST
-                if check_version:
-                    self._full_path = os.path.join(config.LIST_DATA_DIR,
-                                                   name + '@' +
-                                                   self.host_name)
-                else:
-                    self._full_path = os.path.join(config.LIST_DATA_DIR, name)
-        else:
-            self._full_path = ''
+        self._make_lock(self.fqdn_listname)
+        self._full_path = os.path.join(config.LIST_DATA_DIR,
+                                       self.fqdn_listname)
         # Only one level of mixin inheritance allowed
         for baseclass in self.__class__.__bases__:
             if hasattr(baseclass, 'InitTempVars'):
@@ -597,18 +568,9 @@
         self.SaveRequestsDb()
         self.CheckHTMLArchiveDir()
 
-    def Load(self, fqdn_listname=None, check_version=True):
-        if fqdn_listname is None:
-            fqdn_listname = self.fqdn_listname
-        if not Utils.list_exists(fqdn_listname):
-            raise Errors.MMUnknownListError(fqdn_listname)
+    def Load(self):
         database.load(self)
         self._memberadaptor.load()
-        if check_version:
-            # XXX for now disable version checks.  We'll fold this into schema
-            # updates eventually.
-            #self.CheckVersion(dict)
-            self.CheckValues()
 
 
 
@@ -623,7 +585,6 @@
         self.InitVars()
         # Then reload the database (but don't recurse).  Force a reload even
         # if we have the most up-to-date state.
-        self._timestamp = 0
         self.Load(self.fqdn_listname, check_version=False)
         # We must hold the list lock in order to update the schema
         waslocked = self.Locked()

Modified: branches/exp-elixir-branch/Mailman/Makefile.in
===================================================================
--- branches/exp-elixir-branch/Mailman/Makefile.in	2007-05-08 03:16:04 UTC (rev 8204)
+++ branches/exp-elixir-branch/Mailman/Makefile.in	2007-05-08 06:24:51 UTC (rev 8205)
@@ -44,7 +44,7 @@
 
 MODULES=	$(srcdir)/*.py
 SUBDIRS=	Cgi Archiver Handlers Bouncers Queue MTA Gui Commands \
-		bin database ext interfaces testing
+		bin database docs ext interfaces testing
 
 # Modes for directories and executables created by the install
 # process.  Default to group-writable directories but

Modified: branches/exp-elixir-branch/Mailman/Pending.py
===================================================================
--- branches/exp-elixir-branch/Mailman/Pending.py	2007-05-08 03:16:04 UTC (rev 8204)
+++ branches/exp-elixir-branch/Mailman/Pending.py	2007-05-08 06:24:51 UTC (rev 8205)
@@ -17,6 +17,8 @@
 
 """Track pending actions which require confirmation."""
 
+from __future__ import with_statement
+
 import os
 import sha
 import time
@@ -51,7 +53,7 @@
 
 class Pending:
     def InitTempVars(self):
-        self.__pendfile = os.path.join(self.fullpath(), 'pending.pck')
+        self._pendfile = os.path.join(self.full_path, 'pending.pck')
 
     def pend_new(self, op, *content, **kws):
         """Create a new entry in the pending database, returning cookie for it.
@@ -87,14 +89,12 @@
 
     def __load(self):
         try:
-            fp = open(self.__pendfile)
+            with open(self._pendfile) as fp:
+                return cPickle.load(fp)
         except IOError, e:
-            if e.errno <> errno.ENOENT: raise
+            if e.errno <> errno.ENOENT:
+                raise
             return {'evictions': {}}
-        try:
-            return cPickle.load(fp)
-        finally:
-            fp.close()
 
     def __save(self, db):
         evictions = db['evictions']
@@ -112,15 +112,12 @@
             if not db.has_key(cookie):
                 del evictions[cookie]
         db['version'] = config.PENDING_FILE_SCHEMA_VERSION
-        tmpfile = '%s.tmp.%d.%d' % (self.__pendfile, os.getpid(), now)
-        fp = open(tmpfile, 'w')
-        try:
+        tmpfile = '%s.tmp.%d.%d' % (self._pendfile, os.getpid(), now)
+        with open(tmpfile, 'w') as fp:
             cPickle.dump(db, fp)
             fp.flush()
             os.fsync(fp.fileno())
-        finally:
-            fp.close()
-        os.rename(tmpfile, self.__pendfile)
+        os.rename(tmpfile, self._pendfile)
 
     def pend_confirm(self, cookie, expunge=True):
         """Return data for cookie, or None if not found.

Modified: branches/exp-elixir-branch/Mailman/bin/update.py
===================================================================
--- branches/exp-elixir-branch/Mailman/bin/update.py	2007-05-08 03:16:04 UTC (rev \
                8204)
+++ branches/exp-elixir-branch/Mailman/bin/update.py	2007-05-08 06:24:51 UTC (rev \
8205) @@ -242,10 +242,6 @@
             for addr in noinfo.keys():
                 mlist.setDeliveryStatus(addr, ENABLED)
 
-    # Update the held requests database
-    print _("""Updating the held requests database.""")
-    mlist._UpdateRecords()
-
     mbox_dir = make_varabs('archives/private/%s.mbox' % (listname))
     mbox_file = make_varabs('archives/private/%s.mbox/%s' % (listname,
                                                              listname))
@@ -538,121 +534,6 @@
 
 
 
-def update_pending():
-    file20 = os.path.join(config.DATA_DIR, 'pending_subscriptions.db')
-    file214 = os.path.join(config.DATA_DIR, 'pending.pck')
-    db = None
-    # Try to load the Mailman 2.0 file
-    try:
-        fp = open(file20)
-    except IOError, e:
-        if e.errno <> errno.ENOENT:
-            raise
-    else:
-        print _('Updating Mailman 2.0 pending_subscriptions.db database')
-        db = marshal.load(fp)
-        # Convert to the pre-Mailman 2.1.5 format
-        db = Pending._update(db)
-    if db is None:
-        # Try to load the Mailman 2.1.x where x < 5, file
-        try:
-            fp = open(file214)
-        except IOError, e:
-            if e.errno <> errno.ENOENT:
-                raise
-        else:
-            print _('Updating Mailman 2.1.4 pending.pck database')
-            db = cPickle.load(fp)
-    if db is None:
-        print _('Nothing to do.')
-        return
-    # Now upgrade the database to the 2.1.5 format.  Each list now has its own
-    # pending.pck file, but only the RE_ENABLE operation actually recorded the
-    # listname in the request.  For the SUBSCRIPTION, UNSUBSCRIPTION, and
-    # CHANGE_OF_ADDRESS operations, we know the address of the person making
-    # the request so we can repend this request just for the lists the person
-    # is a member of.  For the HELD_MESSAGE operation, we can check the list's
-    # requests.pck file for correlation.  Evictions will take care of any
-    # misdirected pendings.
-    reenables_by_list = {}
-    addrops_by_address = {}
-    holds_by_id = {}
-    subs_by_address = {}
-    for key, val in db.items():
-        if key in ('evictions', 'version'):
-            continue
-        try:
-            op = val[0]
-            data = val[1:]
-        except (IndexError, ValueError):
-            print _('Ignoring bad pended data: $key: $val')
-            continue
-        if op in (Pending.UNSUBSCRIPTION, Pending.CHANGE_OF_ADDRESS):
-            # data[0] is the address being unsubscribed
-            addrops_by_address.setdefault(data[0], []).append((key, val))
-        elif op == Pending.SUBSCRIPTION:
-            # data[0] is a UserDesc object
-            addr = data[0].address
-            subs_by_address.setdefault(addr, []).append((key, val))
-        elif op == Pending.RE_ENABLE:
-            # data[0] is the mailing list's internal name
-            reenables_by_list.setdefault(data[0], []).append((key, val))
-        elif op == Pending.HELD_MESSAGE:
-            # data[0] is the hold id.  There better only be one entry per id
-            id = data[0]
-            if holds_by_id.has_key(id):
-                print _('WARNING: Ignoring duplicate pending ID: $id.')
-            else:
-                holds_by_id[id] = (key, val)
-    # Now we have to lock every list and re-pend all the appropriate
-    # requests.  Note that this will reset all the expiration dates, but that
-    # should be fine.
-    for listname in config.list_manager.names:
-        mlist = MailList.MailList(listname)
-        # This is not the most efficient way to do this because it loads and
-        # saves the pending.pck file each time. :(
-        try:
-            for cookie, data in reenables_by_list.get(listname, []):
-                mlist.pend_repend(cookie, data)
-            for id, (cookie, data) in holds_by_id.items():
-                try:
-                    rec = mlist.GetRecord(id)
-                except KeyError:
-                    # Not for this list
-                    pass
-                else:
-                    mlist.pend_repend(cookie, data)
-                    del holds_by_id[id]
-            for addr, recs in subs_by_address.items():
-                # We shouldn't have a subscription confirmation if the address
-                # is already a member of the mailing list.
-                if mlist.isMember(addr):
-                    continue
-                for cookie, data in recs:
-                    mlist.pend_repend(cookie, data)
-            for addr, recs in addrops_by_address.items():
-                # We shouldn't have unsubscriptions or change of address
-                # requests for addresses which aren't members of the list.
-                if not mlist.isMember(addr):
-                    continue
-                for cookie, data in recs:
-                    mlist.pend_repend(cookie, data)
-            mlist.Save()
-        finally:
-            mlist.Unlock()
-    try:
-        os.unlink(file20)
-    except OSError, e:
-        if e.errno <> errno.ENOENT:
-            raise
-    try:
-        os.unlink(file214)
-    except OSError, e:
-        if e.errno <> errno.ENOENT:
-            raise
-
-
-
 def main():
     parser, opts, args = parseargs()
     initialize(opts.config)
@@ -731,10 +612,6 @@
                 mlist.Unlock()
         os.unlink(wmfile)
         print _('- usenet watermarks updated and gate_watermarks removed')
-    # In Mailman 2.1, the pending database format and file name changed, but
-    # in Mailman 2.1.5 it changed again.  This should update all existing
-    # files to the 2.1.5 format.
-    update_pending()
     # In Mailman 2.1, the qfiles directory has a different structure and a
     # different content.  Also, in Mailman 2.1.5 we collapsed the message
     # files from separate .msg (pickled Message objects) and .db (marshalled

Modified: branches/exp-elixir-branch/Mailman/database/__init__.py
===================================================================
--- branches/exp-elixir-branch/Mailman/database/__init__.py	2007-05-08 03:16:04 UTC \
                (rev 8204)
+++ branches/exp-elixir-branch/Mailman/database/__init__.py	2007-05-08 06:24:51 UTC \
(rev 8205) @@ -20,6 +20,7 @@
 import os
 
 from Mailman.database.listmanager import ListManager
+from Mailman.database.usermanager import UserManager
 
 
 
@@ -33,4 +34,5 @@
     dbcontext = DBContext()
     with LockFile(lockfile):
         dbcontext.connect()
-    config.list_manager = ListManager(dbcontext)
+    config.list_manager = ListManager()
+    config.user_manager = UserManager()

Modified: branches/exp-elixir-branch/Mailman/database/listmanager.py
===================================================================
--- branches/exp-elixir-branch/Mailman/database/listmanager.py	2007-05-08 03:16:04 \
                UTC (rev 8204)
+++ branches/exp-elixir-branch/Mailman/database/listmanager.py	2007-05-08 06:24:51 \
UTC (rev 8205) @@ -15,10 +15,14 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
 # USA.
 
-"""SQLAlchemy based provider of IListManager."""
+"""SQLAlchemy/Elixir based provider of IListManager."""
 
+import weakref
+
+from elixir import *
 from zope.interface import implements
 
+from Mailman import Errors
 from Mailman.Utils import split_listname, fqdn_listname
 from Mailman.database.model import MailingList
 from Mailman.interfaces import IListManager
@@ -28,21 +32,45 @@
 class ListManager(object):
     implements(IListManager)
 
-    def __init__(self, dbcontext):
-        self._dbcontext = dbcontext
+    def __init__(self):
+        self._objectmap = weakref.WeakKeyDictionary()
 
-    def add(self, mlist):
-        self._dbcontext.api_add_list(mlist)
+    def create(self, fqdn_listname):
+        listname, hostname = split_listname(fqdn_listname)
+        mlist = MailingList.get_by(list_name=listname,
+                                   host_name=hostname)
+        if mlist:
+            raise Errors.MMListAlreadyExistsError(fqdn_listname)
+        mlist = MailingList(list_name=listname,
+                            host_name=hostname)
+        objectstore.flush()
+        from Mailman.MailList import MailList
+        wrapper = MailList(mlist)
+        self._objectmap[mlist] = wrapper
+        return wrapper
 
-    def remove(self, mlist):
-        self._dbcontext.api_remove_list(mlist)
-
     def get(self, fqdn_listname):
-        listname, hostname = Utils.split_listname(fqdn_listname)
-        return self._dbcontext.api_find_list(listname, hostname)
+        listname, hostname = split_listname(fqdn_listname)
+        mlist = MailingList.get_by(list_name=listname,
+                                   host_name=hostname)
+        if not mlist:
+            raise Errors.MMUnknownListError(fqdn_listname)
+        from Mailman.MailList import MailList
+        wrapper = self._objectmap.setdefault(mlist, MailList(mlist))
+        return wrapper
 
+    def delete(self, mlist):
+        # XXX I'm not sure I like the semantics of remove().  OT1H you don't
+        # want a MailList object that's had its backing data removed.  OTOH, I
+        # don't like reaching into the object to accomplish this.
+        mlist._data.delete()
+        objectstore.flush()
+        mlist._data = None
+
     @property
     def mailing_lists(self):
+        # Don't forget, the MailingList objects that this class manages must
+        # be wrapped in a MailList object as expected by this interface.
         for fqdn_listname in self.names:
             yield self.get(fqdn_listname)
 

Modified: branches/exp-elixir-branch/Mailman/database/model/mailinglist.py
===================================================================
--- branches/exp-elixir-branch/Mailman/database/model/mailinglist.py	2007-05-08 \
                03:16:04 UTC (rev 8204)
+++ branches/exp-elixir-branch/Mailman/database/model/mailinglist.py	2007-05-08 \
06:24:51 UTC (rev 8205) @@ -20,8 +20,10 @@
 
 class MailingList(Entity):
     with_fields(
+        # List identity
+        list_name                                   = Field(Unicode),
+        host_name                                   = Field(Unicode),
         # Attributes not directly modifiable via the web u/i
-        list_name                                   = Field(Unicode),
         web_page_url                                = Field(Unicode),
         admin_member_chunksize                      = Field(Integer),
         # Attributes which are directly modifiable via the web u/i.  The more
@@ -102,7 +104,6 @@
         goodbye_msg                                 = Field(Unicode),
         header_filter_rules                         = Field(PickleType),
         hold_these_nonmembers                       = Field(PickleType),
-        host_name                                   = Field(Unicode),
         include_list_post_header                    = Field(Boolean),
         include_rfc2369_headers                     = Field(Boolean),
         info                                        = Field(Unicode),

Modified: branches/exp-elixir-branch/Mailman/database/model/roster.py
===================================================================
--- branches/exp-elixir-branch/Mailman/database/model/roster.py	2007-05-08 03:16:04 \
                UTC (rev 8204)
+++ branches/exp-elixir-branch/Mailman/database/model/roster.py	2007-05-08 06:24:51 \
UTC (rev 8205) @@ -19,5 +19,9 @@
 
 
 class Roster(Entity):
+    with_fields(
+        name    = Field(Unicode),
+        )
+
     # Relationships
     has_and_belongs_to_many('addresses', of_kind='Address')

Added: branches/exp-elixir-branch/Mailman/database/usermanager.py
===================================================================
--- branches/exp-elixir-branch/Mailman/database/usermanager.py	                       \
                (rev 0)
+++ branches/exp-elixir-branch/Mailman/database/usermanager.py	2007-05-08 06:24:51 \
UTC (rev 8205) @@ -0,0 +1,65 @@
+# Copyright (C) 2007 by the Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+
+"""SQLAlchemy/Elixir based provider of IUserManager."""
+
+from __future__ import with_statement
+
+import os
+
+from elixir import *
+from zope.interface import implements
+
+from Mailman import Errors
+from Mailman.LockFile import LockFile
+from Mailman.configuration import config
+from Mailman.database.model import Roster
+from Mailman.interfaces import IUserManager
+
+
+
+class UserManager(object):
+    implements(IUserManager)
+
+    def __init__(self):
+        # Create the null roster if it does not already exist.  It's more
+        # likely to exist than not so try to get it before creating it.
+        lockfile = os.path.join(config.LOCK_DIR, '<umgrcreatelock>')
+        with LockFile(lockfile):
+            try:
+                self.get_roster('')
+            except Errors.NoSuchRosterError:
+                self.create_roster('')
+
+    def create_roster(self, name):
+        roster = Roster.get_by(name)
+        if roster:
+            raise Errors.RosterExistsError(name)
+        roster = Roster(name=name)
+        objectstore.flush()
+        return roster
+
+    def get_roster(self, name):
+        roster = Roster.get_by(name)
+        if not roster:
+            raise Errors.NoSuchRosterError(name)
+        return roster
+
+    @property
+    def rosters(self):
+        for roster in Roster.select():
+            yield roster


Property changes on: branches/exp-elixir-branch/Mailman/docs
___________________________________________________________________
Name: svn:ignore
   + Makefile


Copied: branches/exp-elixir-branch/Mailman/docs/Makefile.in (from rev 8203, \
branches/exp-elixir-branch/Mailman/testing/Makefile.in) \
                ===================================================================
--- branches/exp-elixir-branch/Mailman/docs/Makefile.in	                        (rev \
                0)
+++ branches/exp-elixir-branch/Mailman/docs/Makefile.in	2007-05-08 06:24:51 UTC (rev \
8205) @@ -0,0 +1,82 @@
+# Copyright (C) 1998-2007 by the Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+# 
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software 
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+
+# NOTE: Makefile.in is converted into Makefile by the configure script
+# in the parent directory.  Once configure has run, you can recreate
+# the Makefile by running just config.status.
+
+# Variables set by configure
+
+VPATH=		@srcdir@
+srcdir= 	@srcdir@
+bindir= 	@bindir@
+prefix=   	@prefix@
+exec_prefix=	@exec_prefix@
+DESTDIR=
+
+CC=		@CC@
+CHMOD=  	@CHMOD@
+INSTALL=	@INSTALL@
+
+DEFS=   	@DEFS@
+
+# Customizable but not set by configure
+
+OPT=		@OPT@
+CFLAGS=		$(OPT) $(DEFS)
+PACKAGEDIR= 	$(prefix)/Mailman/docs
+SHELL=		/bin/sh
+
+OTHERFILES=	*.txt
+MODULES=	*.py
+
+# Modes for directories and executables created by the install
+# process.  Default to group-writable directories but
+# user-only-writable for executables.
+DIRMODE=	775
+EXEMODE=	755
+FILEMODE=	644
+INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE)
+
+# Directories make should decend into
+SUBDIRS=
+
+# Rules
+
+all:
+
+install: 
+	for f in $(MODULES) $(OTHERFILES); \
+	do \
+	    $(INSTALL) -m $(FILEMODE) $(srcdir)/$$f $(DESTDIR)$(PACKAGEDIR); \
+	done
+	for d in $(SUBDIRS); \
+	do \
+	    (cd $$d; $(MAKE) DESTDIR=$(DESTDIR) install); \
+	done
+
+finish:
+
+clean:
+
+distclean:
+	-rm *.pyc
+	-rm Makefile
+	@for d in $(SUBDIRS); \
+	do \
+	    (cd $$d; $(MAKE) distclean); \
+	done

Added: branches/exp-elixir-branch/Mailman/docs/__init__.py
===================================================================

Added: branches/exp-elixir-branch/Mailman/docs/mlist-addresses.txt
===================================================================
--- branches/exp-elixir-branch/Mailman/docs/mlist-addresses.txt	                      \
                (rev 0)
+++ branches/exp-elixir-branch/Mailman/docs/mlist-addresses.txt	2007-05-08 06:24:51 \
UTC (rev 8205) @@ -0,0 +1,79 @@
+Mailing list addresses
+======================
+
+Every mailing list has a number of addresses which are publicly available.
+These are defined in the IMailingListAddresses interface.
+
+    >>> from Mailman.configuration import config
+    >>> from Mailman.interfaces import IMailingListAddresses
+    >>> mlist = config.list_manager.create('_xtest@example.com')
+    >>> IMailingListAddresses.providedBy(mlist)
+    True
+
+The posting address is where people send messages to be posted to the mailing
+list.  This is exactly the same as the fully qualified list name.
+
+    >>> mlist.fqdn_listname
+    '_xtest@example.com'
+    >>> mlist.posting_address
+    '_xtest@example.com'
+
+Messages to the mailing list's 'no reply' address always get discarded without
+prejudice.
+
+    >>> mlist.noreply_address
+    'noreply@example.com'
+
+The mailing list's owner address reaches the human moderators.
+
+    >>> mlist.owner_address
+    '_xtest-owner@example.com'
+
+The request address goes to the list's email command robot.
+
+    >>> mlist.request_address
+    '_xtest-request@example.com'
+
+The bounces address accepts and processes all potential bounces.
+
+    >>> mlist.bounces_address
+    '_xtest-bounces@example.com'
+
+The join (a.k.a. subscribe) address is where someone can email to get added to
+the mailing list.  The subscribe alias is a synonym for join, but it's
+deprecated.
+
+    >>> mlist.join_address
+    '_xtest-join@example.com'
+    >>> mlist.subscribe_address
+    '_xtest-subscribe@example.com'
+
+The leave (a.k.a. unsubscribe) address is where someone can email to get added
+to the mailing list.  The unsubscribe alias is a synonym for leave, but it's
+deprecated.
+
+    >>> mlist.leave_address
+    '_xtest-leave@example.com'
+    >>> mlist.unsubscribe_address
+    '_xtest-unsubscribe@example.com'
+
+Email confirmation messages are sent when actions such as subscriptions need
+to be confirmed.  It requires that a cookie be provided, which will be
+included in the local part of the email address.  The exact format of this is
+dependent on the VERP_CONFIRM_FORMAT configuration variable.
+
+    >>> mlist.confirm_address('cookie')
+    '_xtest-confirm+cookie@example.com'
+    >>> mlist.confirm_address('wookie')
+    '_xtest-confirm+wookie@example.com'
+
+    >>> old_format = config.VERP_CONFIRM_FORMAT
+    >>> config.VERP_CONFIRM_FORMAT = '$address---$cookie'
+    >>> mlist.confirm_address('cookie')
+    '_xtest-confirm---cookie@example.com'
+    >>> config.VERP_CONFIRM_FORMAT = old_format
+
+Clean up.
+
+    >>> for mlist in config.list_manager.mailing_lists:
+    ...     config.list_manager.delete(mlist)

Added: branches/exp-elixir-branch/Mailman/docs/mlist-creation.txt
===================================================================
--- branches/exp-elixir-branch/Mailman/docs/mlist-creation.txt	                       \
                (rev 0)
+++ branches/exp-elixir-branch/Mailman/docs/mlist-creation.txt	2007-05-08 06:24:51 \
UTC (rev 8205) @@ -0,0 +1,83 @@
+Creating a mailing list
+=======================
+
+To create a mailing list, start by importing the IListManager instance that
+talks to the database.  This is accessable via the global config object.
+
+    >>> from Mailman.configuration import config
+    >>> from Mailman.interfaces import IMailingList
+
+Creating the list returns the newly created IMailList object.
+
+    >>> mgr = config.list_manager
+    >>> mlist = mgr.create('_xtest@example.com')
+    >>> IMailingList.providedBy(mlist)
+    True
+
+This object has an identity.
+
+    >>> from Mailman.interfaces import IMailingListIdentity
+    >>> IMailingListIdentity.providedBy(mlist)
+    True
+
+All lists with identities have a short name, a host name, and a fully
+qualified listname.  This latter is what uniquely distinguishes the mailing
+list to the system.
+
+    >>> mlist.list_name
+    '_xtest'
+    >>> mlist.host_name
+    'example.com'
+    >>> mlist.fqdn_listname
+    '_xtest@example.com'
+
+If you try to create a mailing list with the same name as an existing list,
+you will get an exception.
+
+    >>> mlist_dup = mgr.create('_xtest@example.com')
+    Traceback (most recent call last):
+    ...
+    MMListAlreadyExistsError: _xtest@example.com
+
+Delete the existing list, and you can then create it again.  But don't try to
+use the deleted mlist once you've done so.
+
+    >>> mgr.delete(mlist)
+    >>> mlist.fqdn_listname
+    Traceback (most recent call last):
+    ...
+    AttributeError: fqdn_listname
+
+    >>> mlist = mgr.create('_xtest@example.com')
+    >>> mlist.fqdn_listname
+    '_xtest@example.com'
+
+When a mailing list exists, you can ask the list manager for it and you will
+always get the same object back.
+
+    >>> mlist_2 = mgr.get('_xtest@example.com')
+    >>> mlist_2 is mlist
+    True
+
+Don't try to get a list that doesn't exist yet though, or you will get an
+exception.
+
+    >>> mgr.get('_xtest_2@example.com')
+    Traceback (most recent call last):
+    ...
+    MMUnknownListError: _xtest_2@example.com
+
+Once you've created a bunch of mailing lists, you can use the list manager to
+iterate over either the list object, or the list names.
+
+    >>> mlist_3 = mgr.create('_xtest_3@example.com')
+    >>> mlist_4 = mgr.create('_xtest_4@example.com')
+    >>> sorted(mgr.names)
+    ['_xtest@example.com', '_xtest_3@example.com', '_xtest_4@example.com']
+    >>> sorted(m.fqdn_listname for m in mgr.mailing_lists)
+    ['_xtest@example.com', '_xtest_3@example.com', '_xtest_4@example.com']
+
+Clean up.
+
+    >>> for mlist in mgr.mailing_lists:
+    ...     mgr.delete(mlist)

Added: branches/exp-elixir-branch/Mailman/docs/mlist-rosters.txt
===================================================================
--- branches/exp-elixir-branch/Mailman/docs/mlist-rosters.txt	                        \
                (rev 0)
+++ branches/exp-elixir-branch/Mailman/docs/mlist-rosters.txt	2007-05-08 06:24:51 UTC \
(rev 8205) @@ -0,0 +1,14 @@
+The user manager and rosters
+============================
+
+The user manager contains a set of rosters, each roster is a collection of
+users.  The user manager always contains at least one roster, the 'null'
+roster or 'all inclusive roster'.
+
+    >>> from Mailman.configuration import config
+    >>> mgr = config.user_manager
+    >>> sorted(roster.name for roster in mgr.rosters)
+    ['']
+
+Users can belong to more than one roster, but the belong to at least
+one roster, the null roster.  The null roster returns all the users 
\ No newline at end of file

Deleted: branches/exp-elixir-branch/Mailman/enum.py
===================================================================
--- branches/exp-elixir-branch/Mailman/enum.py	2007-05-08 03:16:04 UTC (rev 8204)
+++ branches/exp-elixir-branch/Mailman/enum.py	2007-05-08 06:24:51 UTC (rev 8205)
@@ -1,132 +0,0 @@
-# Copyright (C) 2004-2007 by the Free Software Foundation, Inc.
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
-# USA.
-
-"""Enumeration meta class.
-
-To define your own enumeration, do something like:
-
->>> class Colors(Enum):
-...     red = 1
-...     green = 2
-...     blue = 3
-
-Enum subclasses cannot be instantiated, but you can convert them to integers
-and from integers.  Returned enumeration attributes are singletons and can be
-compared by identity only.
-"""
-
-COMMASPACE = ', '
-
-# Based on example by Jeremy Hylton
-# Modified and extended by Barry Warsaw
-
-
-
-class EnumMetaclass(type):
-    def __init__(cls, name, bases, dict):
-        # cls == the class being defined
-        # name == the name of the class
-        # bases == the class's bases
-        # dict == the class attributes
-        super(EnumMetaclass, cls).__init__(name, bases, dict)
-        # Store EnumValues here for easy access.
-        cls._enums = {}
-        # Figure out the set of enum values on the base classes, to ensure
-        # that we don't get any duplicate values (which would screw up
-        # conversion from integer).
-        for basecls in cls.__mro__:
-            if hasattr(basecls, '_enums'):
-                cls._enums.update(basecls._enums)
-        # For each class attribute, create an EnumValue and store that back on
-        # the class instead of the int.  Skip Python reserved names.  Also add
-        # a mapping from the integer to the instance so we can return the same
-        # object on conversion.
-        for attr in dict:
-            if not (attr.startswith('__') and attr.endswith('__')):
-                intval  = dict[attr]
-                enumval = EnumValue(name, intval, attr)
-                if intval in cls._enums:
-                    raise TypeError('Multiple enum values: %s' % enumval)
-                # Store as an attribute on the class, and save the attr name
-                setattr(cls, attr, enumval)
-                cls._enums[intval] = attr
-
-    def __getattr__(cls, name):
-        if name == '__members__':
-            return cls._enums.values()
-        raise AttributeError(name)
-
-    def __repr__(cls):
-        enums = ['%s: %d' % (cls._enums[k], k) for k in sorted(cls._enums)]
-        return '<%s {%s}>' % (cls.__name__, COMMASPACE.join(enums))
-
-    def __iter__(cls):
-        for i in sorted(cls._enums):
-            yield getattr(cls, cls._enums[i])
-
-    def __getitem__(cls, i):
-        # i can be an integer or a string
-        attr = cls._enums.get(i)
-        if attr is None:
-            # It wasn't an integer -- try attribute name
-            try:
-                return getattr(cls, i)
-            except (AttributeError, TypeError):
-                raise ValueError(i)
-        return getattr(cls, attr)
-
-    # Support both MyEnum[i] and MyEnum(i)
-    __call__ = __getitem__
-
-
-
-class EnumValue(object):
-    """Class to represent an enumeration value.
-
-    EnumValue('Color', 'red', 12) prints as 'Color.red' and can be converted
-    to the integer 12.
-    """
-    def __init__(self, classname, value, enumname):
-        self._classname = classname
-        self._value     = value
-        self._enumname  = enumname
-
-    def __repr__(self):
-        return 'EnumValue(%s, %s, %d)' % (
-            self._classname, self._enumname, self._value)
-
-    def __str__(self):
-        return self._enumname
-
-    def __int__(self):
-        return self._value
-
-    # Support only comparison by identity.  Yes, really raise
-    # NotImplementedError instead of returning NotImplemented.
-    def __eq__(self, other):
-        raise NotImplementedError
-
-    __ne__ = __eq__
-    __lt__ = __eq__
-    __gt__ = __eq__
-    __le__ = __eq__
-    __ge__ = __eq__
-
-
-
-class Enum:
-    __metaclass__ = EnumMetaclass

Modified: branches/exp-elixir-branch/Mailman/interfaces/listmanager.py
===================================================================
--- branches/exp-elixir-branch/Mailman/interfaces/listmanager.py	2007-05-08 03:16:04 \
                UTC (rev 8204)
+++ branches/exp-elixir-branch/Mailman/interfaces/listmanager.py	2007-05-08 06:24:51 \
UTC (rev 8205) @@ -30,10 +30,19 @@
     (e.g. 'mylist@example.com').
     """
 
-    def new(fqdn_listname):
-        """Create and return a new IMailingList."""
+    def create(fqdn_listname):
+        """Create an IMailingList with the given fully qualified list name.
 
-    def remove(mlist):
+        Raises MMListAlreadyExistsError if the named list already exists.
+        """
+
+    def get(fqdn_listname):
+        """Return the IMailingList with the given fully qualified list name.
+
+        Raises MMUnknownListError if the names list does not exist.
+        """
+
+    def delete(mlist):
         """Remove the IMailingList from the backend storage."""
 
     def get(fqdn_listname):

Modified: branches/exp-elixir-branch/Mailman/interfaces/mlistemail.py
===================================================================
--- branches/exp-elixir-branch/Mailman/interfaces/mlistemail.py	2007-05-08 03:16:04 \
                UTC (rev 8204)
+++ branches/exp-elixir-branch/Mailman/interfaces/mlistemail.py	2007-05-08 06:24:51 \
UTC (rev 8205) @@ -34,9 +34,9 @@
 
     noreply_address = Attribute(
         """The address to which all messages will be immediately discarded,
-        without prejudice or record.  This address may be specific to the
-        mailing list or it may be a domain-wide noreply address.  Generally,
-        humans should never respond directly to this address.""")
+        without prejudice or record.  This address is specific to the ddomain,
+        even though it's available on the IMailingListAddresses interface.
+        Generally, humans should never respond directly to this address.""")
 
     owner_address = Attribute(
         """The address which reaches the owners and moderators of the mailing
@@ -55,9 +55,6 @@
         mailing list.  Generally, humans should never respond directly to this
         address.""")
 
-    confirm_address = Attribute(
-        """The address used for various forms of email confirmation.""")
-
     join_address = Attribute(
         """The address to which subscription requests should be sent.  See
         subscribe_address for a backward compatible alias.""")
@@ -75,3 +72,7 @@
         """Deprecated address to which unsubscription requests may be sent.
         This address is provided for backward compatibility only.  See
         leave_address for the preferred alias.""")
+
+    def confirm_address(cookie=''):
+        """The address used for various forms of email confirmation."""
+

Modified: branches/exp-elixir-branch/Mailman/interfaces/usermanager.py
===================================================================
--- branches/exp-elixir-branch/Mailman/interfaces/usermanager.py	2007-05-08 03:16:04 \
                UTC (rev 8204)
+++ branches/exp-elixir-branch/Mailman/interfaces/usermanager.py	2007-05-08 06:24:51 \
UTC (rev 8205) @@ -28,18 +28,28 @@
     users managed by different IUserManagers are completely independent.  This
     is how you can separate the user contexts for different domains, on a
     multiple domain system.
+
+    There is one special roster, the null roster ('') which contains all
+    IUsers in all IRosters.
     """
 
-    users = Attribute(
-        """An iterator over all the IUsers managed by this user manager.""")
+    def create_roster(name):
+        """Create and return the named IRoster.
 
-    def create_user():
-        """Create a new IUser, add it to this user manager and return it."""
+        Raises RosterExistsError if the named roster already exists.
+        """
 
-    def remove(user):
-        """Remove the IUser from this user manager."""
+    def get_roster(name):
+        """Return the named IRoster.
 
-    def get(address):
-        """Return the IUser controlling the given address, or None if no user
-        is associated with this address.
+        Raises NoSuchRosterError if the named roster doesnot yet exist.
         """
+
+    def delete_roster(name):
+        """Delete the named IRoster.
+
+        Raises NoSuchRosterError if the named roster doesnot yet exist.
+        """
+
+    rosters = Attribute(
+        """An iterator over all IRosters managed by this user manager.""")

Modified: branches/exp-elixir-branch/Mailman/passwords.py
===================================================================
--- branches/exp-elixir-branch/Mailman/passwords.py	2007-05-08 03:16:04 UTC (rev \
                8204)
+++ branches/exp-elixir-branch/Mailman/passwords.py	2007-05-08 06:24:51 UTC (rev \
8205) @@ -28,9 +28,9 @@
 from array import array
 from base64 import urlsafe_b64decode as decode
 from base64 import urlsafe_b64encode as encode
+from munepy import Enum
 
 from Mailman import Errors
-from Mailman.enum import Enum
 
 SALT_LENGTH = 20 # bytes
 ITERATIONS  = 2000

Deleted: branches/exp-elixir-branch/Mailman/testing/test_enum.py
===================================================================
--- branches/exp-elixir-branch/Mailman/testing/test_enum.py	2007-05-08 03:16:04 UTC \
                (rev 8204)
+++ branches/exp-elixir-branch/Mailman/testing/test_enum.py	2007-05-08 06:24:51 UTC \
(rev 8205) @@ -1,125 +0,0 @@
-# Copyright (C) 2007 by the Free Software Foundation, Inc.
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
-# USA.
-
-"""Unit tests for Enums."""
-
-import operator
-import unittest
-
-from Mailman.enum import Enum
-
-
-
-class Colors(Enum):
-    red     = 1
-    green   = 2
-    blue    = 3
-
-
-class MoreColors(Colors):
-    pink    = 4
-    cyan    = 5
-
-
-class OtherColors(Enum):
-    red     = 1
-    blue    = 2
-    yellow  = 3
-
-
-
-class TestEnum(unittest.TestCase):
-    def test_enum_basics(self):
-        unless = self.failUnless
-        raises = self.assertRaises
-        # Cannot compare by equality
-        raises(NotImplementedError, operator.eq, Colors.red, Colors.red)
-        raises(NotImplementedError, operator.ne, Colors.red, Colors.red)
-        raises(NotImplementedError, operator.lt, Colors.red, Colors.red)
-        raises(NotImplementedError, operator.gt, Colors.red, Colors.red)
-        raises(NotImplementedError, operator.le, Colors.red, Colors.red)
-        raises(NotImplementedError, operator.ge, Colors.red, Colors.red)
-        raises(NotImplementedError, operator.eq, Colors.red, 1)
-        raises(NotImplementedError, operator.ne, Colors.red, 1)
-        raises(NotImplementedError, operator.lt, Colors.red, 1)
-        raises(NotImplementedError, operator.gt, Colors.red, 1)
-        raises(NotImplementedError, operator.le, Colors.red, 1)
-        raises(NotImplementedError, operator.ge, Colors.red, 1)
-        # Comparison by identity
-        unless(Colors.red is Colors.red)
-        unless(Colors.red is MoreColors.red)
-        unless(Colors.red is not OtherColors.red)
-        unless(Colors.red is not Colors.blue)
-
-    def test_enum_conversions(self):
-        eq = self.assertEqual
-        unless = self.failUnless
-        raises = self.assertRaises
-        unless(Colors.red is Colors['red'])
-        unless(Colors.red is Colors[1])
-        unless(Colors.red is Colors('red'))
-        unless(Colors.red is Colors(1))
-        unless(Colors.red is not Colors['blue'])
-        unless(Colors.red is not Colors[2])
-        unless(Colors.red is not Colors('blue'))
-        unless(Colors.red is not Colors(2))
-        unless(Colors.red is MoreColors['red'])
-        unless(Colors.red is MoreColors[1])
-        unless(Colors.red is MoreColors('red'))
-        unless(Colors.red is MoreColors(1))
-        unless(Colors.red is not OtherColors['red'])
-        unless(Colors.red is not OtherColors[1])
-        unless(Colors.red is not OtherColors('red'))
-        unless(Colors.red is not OtherColors(1))
-        raises(ValueError, Colors.__getitem__, 'magenta')
-        raises(ValueError, Colors.__getitem__, 99)
-        raises(ValueError, Colors.__call__, 'magenta')
-        raises(ValueError, Colors.__call__, 99)
-        eq(int(Colors.red), 1)
-        eq(int(Colors.blue), 3)
-        eq(int(MoreColors.red), 1)
-        eq(int(OtherColors.blue), 2)
-
-    def test_enum_duplicates(self):
-        try:
-            # This is bad because kyle and kenny have the same integer value.
-            class Bad(Enum):
-                cartman = 1
-                stan    = 2
-                kyle    = 3
-                kenny   = 3
-                butters = 4
-        except TypeError:
-            got_error = True
-        else:
-            got_error = False
-        self.failUnless(got_error)
-
-    def test_enum_iteration(self):
-        eq = self.assertEqual
-        # Iteration sorts on the int value of the enum
-        values = [str(v) for v in MoreColors]
-        eq(values, ['red', 'green', 'blue', 'pink', 'cyan'])
-        values = [int(v) for v in MoreColors]
-        eq(values, [1, 2, 3, 4, 5])
-
-
-
-def test_suite():
-    suite = unittest.TestSuite()
-    suite.addTest(unittest.makeSuite(TestEnum))
-    return suite

Added: branches/exp-elixir-branch/Mailman/testing/test_mlist_addresses.py
===================================================================
--- branches/exp-elixir-branch/Mailman/testing/test_mlist_addresses.py	               \
                (rev 0)
+++ branches/exp-elixir-branch/Mailman/testing/test_mlist_addresses.py	2007-05-08 \
06:24:51 UTC (rev 8205) @@ -0,0 +1,28 @@
+# Copyright (C) 2007 by the Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+
+"""Doctest harness for the IMailingListAddresses interface."""
+
+import doctest
+import unittest
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    suite.addTest(doctest.DocFileSuite('../docs/mlist-addresses.txt',
+                                       optionflags=doctest.ELLIPSIS))
+    return suite

Added: branches/exp-elixir-branch/Mailman/testing/test_mlist_creation.py
===================================================================
--- branches/exp-elixir-branch/Mailman/testing/test_mlist_creation.py	                \
                (rev 0)
+++ branches/exp-elixir-branch/Mailman/testing/test_mlist_creation.py	2007-05-08 \
06:24:51 UTC (rev 8205) @@ -0,0 +1,28 @@
+# Copyright (C) 2007 by the Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+
+"""Doctest harness for testing mailing list creation and deletion."""
+
+import doctest
+import unittest
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    suite.addTest(doctest.DocFileSuite('../docs/mlist-creation.txt',
+                                       optionflags=doctest.ELLIPSIS))
+    return suite

Added: branches/exp-elixir-branch/Mailman/testing/test_rosters.py
===================================================================
--- branches/exp-elixir-branch/Mailman/testing/test_rosters.py	                       \
                (rev 0)
+++ branches/exp-elixir-branch/Mailman/testing/test_rosters.py	2007-05-08 06:24:51 \
UTC (rev 8205) @@ -0,0 +1,28 @@
+# Copyright (C) 2007 by the Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+
+"""Doctest harness for testing mailing list creation and deletion."""
+
+import doctest
+import unittest
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    suite.addTest(doctest.DocFileSuite('../docs/mlist-rosters.txt',
+                                       optionflags=doctest.ELLIPSIS))
+    return suite

Modified: branches/exp-elixir-branch/Makefile.in
===================================================================
--- branches/exp-elixir-branch/Makefile.in	2007-05-08 03:16:04 UTC (rev 8204)
+++ branches/exp-elixir-branch/Makefile.in	2007-05-08 06:24:51 UTC (rev 8205)
@@ -52,7 +52,7 @@
 	bin templates scripts cron pythonlib \
 	Mailman Mailman/bin Mailman/interfaces \
 	Mailman/database Mailman/database/tables Mailman/database/model \
-	Mailman/ext Mailman/Cgi Mailman/Archiver \
+	Mailman/docs Mailman/ext Mailman/Cgi Mailman/Archiver \
 	Mailman/Handlers Mailman/Queue Mailman/Queue/tests \
 	Mailman/Bouncers \
 	Mailman/MTA Mailman/Gui Mailman/Commands messages icons \

Modified: branches/exp-elixir-branch/README.txt
===================================================================
--- branches/exp-elixir-branch/README.txt	2007-05-08 03:16:04 UTC (rev 8204)
+++ branches/exp-elixir-branch/README.txt	2007-05-08 06:24:51 UTC (rev 8205)
@@ -29,12 +29,11 @@
         http://www.gnu.org/software/mailman
         http://mailman.sf.net
 
-    Mailman 2.2 requires Python 2.4 or greater, which can be downloaded from:
+    Mailman 3.0 requires Python 2.5 or greater, which can be downloaded from:
 
         http://www.python.org
 
-    It is recommended that you use at least Python 2.4.4, the latest release
-    as of this writing (13-Jan-2007).  Python 2.5 should also work well.
+    It is recommended that you use at least Python 2.5.1.
 
     You will need an ANSI C compiler to build both Python and Mailman; gcc
     (the GNU C compiler) works just fine.  Mailman currently works only on

Modified: branches/exp-elixir-branch/configure
===================================================================
--- branches/exp-elixir-branch/configure	2007-05-08 03:16:04 UTC (rev 8204)
+++ branches/exp-elixir-branch/configure	2007-05-08 06:24:51 UTC (rev 8205)
@@ -1,27 +1,56 @@
 #! /bin/sh
-# From configure.in Revision: 8190 .
+# From configure.in Revision: 8196 .
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.59 for GNU Mailman 2.2.0a0.
+# Generated by GNU Autoconf 2.61 for GNU Mailman 2.2.0a0.
 #
-# Copyright (C) 2003 Free Software Foundation, Inc.
+# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
+# 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
 # This configure script is free software; the Free Software Foundation
 # gives unlimited permission to copy, distribute and modify it.
 ## --------------------- ##
 ## M4sh Initialization.  ##
 ## --------------------- ##
 
-# Be Bourne compatible
+# Be more Bourne compatible
+DUALCASE=1; export DUALCASE # for MKS sh
 if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
   emulate sh
   NULLCMD=:
   # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
   # is contrary to our usage.  Disable this feature.
   alias -g '${1+"$@"}'='"$@"'
-elif test -n "${BASH_VERSION+set}" && (set -o posix) >/dev/null 2>&1; then
-  set -o posix
+  setopt NO_GLOB_SUBST
+else
+  case `(set -o) 2>/dev/null` in
+  *posix*) set -o posix ;;
+esac
+
 fi
-DUALCASE=1; export DUALCASE # for MKS sh
 
+
+
+
+# PATH needs CR
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+  echo "#! /bin/sh" >conf$$.sh
+  echo  "exit 0"   >>conf$$.sh
+  chmod +x conf$$.sh
+  if (PATH="/nonexistent;."; conf$$.sh) >/dev/null 2>&1; then
+    PATH_SEPARATOR=';'
+  else
+    PATH_SEPARATOR=:
+  fi
+  rm -f conf$$.sh
+fi
+
 # Support unset when possible.
 if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then
   as_unset=unset
@@ -30,8 +59,43 @@
 fi
 
 
+# IFS
+# We need space, tab and new line, in precisely that order.  Quoting is
+# there to prevent editors from complaining about space-tab.
+# (If _AS_PATH_WALK were called with IFS unset, it would disable word
+# splitting by setting IFS to empty value.)
+as_nl='
+'
+IFS=" ""	$as_nl"
+
+# Find who we are.  Look in the path if we contain no directory separator.
+case $0 in
+  *[\\/]* ) as_myself=$0 ;;
+  *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+done
+IFS=$as_save_IFS
+
+     ;;
+esac
+# We did not find ourselves, most probably we were run as `sh COMMAND'
+# in which case we are not to be found in the path.
+if test "x$as_myself" = x; then
+  as_myself=$0
+fi
+if test ! -f "$as_myself"; then
+  echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
+  { (exit 1); exit 1; }
+fi
+
 # Work around bugs in pre-3.0 UWIN ksh.
-$as_unset ENV MAIL MAILPATH
+for as_var in ENV MAIL MAILPATH
+do ($as_unset $as_var) >/dev/null 2>&1 && $as_unset $as_var
+done
 PS1='$ '
 PS2='> '
 PS4='+ '
@@ -45,18 +109,19 @@
   if (set +x; test -z "`(eval $as_var=C; export $as_var) 2>&1`"); then
     eval $as_var=C; export $as_var
   else
-    $as_unset $as_var
+    ($as_unset $as_var) >/dev/null 2>&1 && $as_unset $as_var
   fi
 done
 
 # Required to use basename.
-if expr a : '\(a\)' >/dev/null 2>&1; then
+if expr a : '\(a\)' >/dev/null 2>&1 &&
+   test "X`expr 00001 : '.*\(...\)'`" = X001; then
   as_expr=expr
 else
   as_expr=false
 fi
 
-if (basename /) >/dev/null 2>&1 && test "X`basename / 2>&1`" = "X/"; then
+if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
   as_basename=basename
 else
   as_basename=false
@@ -64,157 +129,388 @@
 
 
 # Name of the executable.
-as_me=`$as_basename "$0" ||
+as_me=`$as_basename -- "$0" ||
 $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
 	 X"$0" : 'X\(//\)$' \| \
-	 X"$0" : 'X\(/\)$' \| \
-	 .     : '\(.\)' 2>/dev/null ||
+	 X"$0" : 'X\(/\)' \| . 2>/dev/null ||
 echo X/"$0" |
-    sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/; q; }
-  	  /^X\/\(\/\/\)$/{ s//\1/; q; }
-  	  /^X\/\(\/\).*/{ s//\1/; q; }
-  	  s/.*/./; q'`
+    sed '/^.*\/\([^/][^/]*\)\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\/\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\/\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`
 
+# CDPATH.
+$as_unset CDPATH
 
-# PATH needs CR, and LINENO needs CR and PATH.
-# Avoid depending upon Character Ranges.
-as_cr_letters='abcdefghijklmnopqrstuvwxyz'
-as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
-as_cr_Letters=$as_cr_letters$as_cr_LETTERS
-as_cr_digits='0123456789'
-as_cr_alnum=$as_cr_Letters$as_cr_digits
 
-# The user is always right.
-if test "${PATH_SEPARATOR+set}" != set; then
-  echo "#! /bin/sh" >conf$$.sh
-  echo  "exit 0"   >>conf$$.sh
-  chmod +x conf$$.sh
-  if (PATH="/nonexistent;."; conf$$.sh) >/dev/null 2>&1; then
-    PATH_SEPARATOR=';'
-  else
-    PATH_SEPARATOR=:
-  fi
-  rm -f conf$$.sh
+if test "x$CONFIG_SHELL" = x; then
+  if (eval ":") 2>/dev/null; then
+  as_have_required=yes
+else
+  as_have_required=no
 fi
 
+  if test $as_have_required = yes && 	 (eval ":
+(as_func_return () {
+  (exit \$1)
+}
+as_func_success () {
+  as_func_return 0
+}
+as_func_failure () {
+  as_func_return 1
+}
+as_func_ret_success () {
+  return 0
+}
+as_func_ret_failure () {
+  return 1
+}
 
-  as_lineno_1=$LINENO
-  as_lineno_2=$LINENO
-  as_lineno_3=`(expr $as_lineno_1 + 1) 2>/dev/null`
-  test "x$as_lineno_1" != "x$as_lineno_2" &&
-  test "x$as_lineno_3"  = "x$as_lineno_2"  || {
-  # Find who we are.  Look in the path if we contain no path at all
-  # relative or not.
-  case $0 in
-    *[\\/]* ) as_myself=$0 ;;
-    *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
-for as_dir in $PATH
-do
-  IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
-  test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
-done
+exitcode=0
+if as_func_success; then
+  :
+else
+  exitcode=1
+  echo as_func_success failed.
+fi
 
-       ;;
-  esac
-  # We did not find ourselves, most probably we were run as `sh COMMAND'
-  # in which case we are not to be found in the path.
-  if test "x$as_myself" = x; then
-    as_myself=$0
-  fi
-  if test ! -f "$as_myself"; then
-    { echo "$as_me: error: cannot find myself; rerun with an absolute path" >&2
-   { (exit 1); exit 1; }; }
-  fi
-  case $CONFIG_SHELL in
-  '')
+if as_func_failure; then
+  exitcode=1
+  echo as_func_failure succeeded.
+fi
+
+if as_func_ret_success; then
+  :
+else
+  exitcode=1
+  echo as_func_ret_success failed.
+fi
+
+if as_func_ret_failure; then
+  exitcode=1
+  echo as_func_ret_failure succeeded.
+fi
+
+if ( set x; as_func_ret_success y && test x = \"\$1\" ); then
+  :
+else
+  exitcode=1
+  echo positional parameters were not saved.
+fi
+
+test \$exitcode = 0) || { (exit 1); exit 1; }
+
+(
+  as_lineno_1=\$LINENO
+  as_lineno_2=\$LINENO
+  test \"x\$as_lineno_1\" != \"x\$as_lineno_2\" &&
+  test \"x\`expr \$as_lineno_1 + 1\`\" = \"x\$as_lineno_2\") || { (exit 1); exit 1; \
} +") 2> /dev/null; then
+  :
+else
+  as_candidate_shells=
     as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
 for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH
 do
   IFS=$as_save_IFS
   test -z "$as_dir" && as_dir=.
-  for as_base in sh bash ksh sh5; do
-	 case $as_dir in
+  case $as_dir in
 	 /*)
-	   if ("$as_dir/$as_base" -c '
+	   for as_base in sh bash ksh sh5; do
+	     as_candidate_shells="$as_candidate_shells $as_dir/$as_base"
+	   done;;
+       esac
+done
+IFS=$as_save_IFS
+
+
+      for as_shell in $as_candidate_shells $SHELL; do
+	 # Try only shells that exist, to save several forks.
+	 if { test -f "$as_shell" || test -f "$as_shell.exe"; } &&
+		{ ("$as_shell") 2> /dev/null <<\_ASEOF
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+  emulate sh
+  NULLCMD=:
+  # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '${1+"$@"}'='"$@"'
+  setopt NO_GLOB_SUBST
+else
+  case `(set -o) 2>/dev/null` in
+  *posix*) set -o posix ;;
+esac
+
+fi
+
+
+:
+_ASEOF
+}; then
+  CONFIG_SHELL=$as_shell
+	       as_have_required=yes
+	       if { "$as_shell" 2> /dev/null <<\_ASEOF
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+  emulate sh
+  NULLCMD=:
+  # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '${1+"$@"}'='"$@"'
+  setopt NO_GLOB_SUBST
+else
+  case `(set -o) 2>/dev/null` in
+  *posix*) set -o posix ;;
+esac
+
+fi
+
+
+:
+(as_func_return () {
+  (exit $1)
+}
+as_func_success () {
+  as_func_return 0
+}
+as_func_failure () {
+  as_func_return 1
+}
+as_func_ret_success () {
+  return 0
+}
+as_func_ret_failure () {
+  return 1
+}
+
+exitcode=0
+if as_func_success; then
+  :
+else
+  exitcode=1
+  echo as_func_success failed.
+fi
+
+if as_func_failure; then
+  exitcode=1
+  echo as_func_failure succeeded.
+fi
+
+if as_func_ret_success; then
+  :
+else
+  exitcode=1
+  echo as_func_ret_success failed.
+fi
+
+if as_func_ret_failure; then
+  exitcode=1
+  echo as_func_ret_failure succeeded.
+fi
+
+if ( set x; as_func_ret_success y && test x = "$1" ); then
+  :
+else
+  exitcode=1
+  echo positional parameters were not saved.
+fi
+
+test $exitcode = 0) || { (exit 1); exit 1; }
+
+(
   as_lineno_1=$LINENO
   as_lineno_2=$LINENO
-  as_lineno_3=`(expr $as_lineno_1 + 1) 2>/dev/null`
   test "x$as_lineno_1" != "x$as_lineno_2" &&
-  test "x$as_lineno_3"  = "x$as_lineno_2" ') 2>/dev/null; then
-	     $as_unset BASH_ENV || test "${BASH_ENV+set}" != set || { BASH_ENV=; export \
                BASH_ENV; }
-	     $as_unset ENV || test "${ENV+set}" != set || { ENV=; export ENV; }
-	     CONFIG_SHELL=$as_dir/$as_base
-	     export CONFIG_SHELL
-	     exec "$CONFIG_SHELL" "$0" ${1+"$@"}
-	   fi;;
-	 esac
-       done
-done
-;;
-  esac
+  test "x`expr $as_lineno_1 + 1`" = "x$as_lineno_2") || { (exit 1); exit 1; }
 
+_ASEOF
+}; then
+  break
+fi
+
+fi
+
+      done
+
+      if test "x$CONFIG_SHELL" != x; then
+  for as_var in BASH_ENV ENV
+        do ($as_unset $as_var) >/dev/null 2>&1 && $as_unset $as_var
+        done
+        export CONFIG_SHELL
+        exec "$CONFIG_SHELL" "$as_myself" ${1+"$@"}
+fi
+
+
+    if test $as_have_required = no; then
+  echo This script requires a shell more modern than all the
+      echo shells that I found on your system.  Please install a
+      echo modern shell, or manually run the script under such a
+      echo shell if you do have one.
+      { (exit 1); exit 1; }
+fi
+
+
+fi
+
+fi
+
+
+
+(eval "as_func_return () {
+  (exit \$1)
+}
+as_func_success () {
+  as_func_return 0
+}
+as_func_failure () {
+  as_func_return 1
+}
+as_func_ret_success () {
+  return 0
+}
+as_func_ret_failure () {
+  return 1
+}
+
+exitcode=0
+if as_func_success; then
+  :
+else
+  exitcode=1
+  echo as_func_success failed.
+fi
+
+if as_func_failure; then
+  exitcode=1
+  echo as_func_failure succeeded.
+fi
+
+if as_func_ret_success; then
+  :
+else
+  exitcode=1
+  echo as_func_ret_success failed.
+fi
+
+if as_func_ret_failure; then
+  exitcode=1
+  echo as_func_ret_failure succeeded.
+fi
+
+if ( set x; as_func_ret_success y && test x = \"\$1\" ); then
+  :
+else
+  exitcode=1
+  echo positional parameters were not saved.
+fi
+
+test \$exitcode = 0") || {
+  echo No shell found that supports shell functions.
+  echo Please tell autoconf@gnu.org about your system,
+  echo including any error possibly output before this
+  echo message
+}
+
+
+
+  as_lineno_1=$LINENO
+  as_lineno_2=$LINENO
+  test "x$as_lineno_1" != "x$as_lineno_2" &&
+  test "x`expr $as_lineno_1 + 1`" = "x$as_lineno_2" || {
+
   # Create $as_me.lineno as a copy of $as_myself, but with $LINENO
   # uniformly replaced by the line number.  The first 'sed' inserts a
-  # line-number line before each line; the second 'sed' does the real
-  # work.  The second script uses 'N' to pair each line-number line
-  # with the numbered line, and appends trailing '-' during
-  # substitution so that $LINENO is not a special case at line end.
+  # line-number line after each line using $LINENO; the second 'sed'
+  # does the real work.  The second script uses 'N' to pair each
+  # line-number line with the line containing $LINENO, and appends
+  # trailing '-' during substitution so that $LINENO is not a special
+  # case at line end.
   # (Raja R Harinath suggested sed '=', and Paul Eggert wrote the
-  # second 'sed' script.  Blame Lee E. McMahon for sed's syntax.  :-)
-  sed '=' <$as_myself |
+  # scripts with optimization help from Paolo Bonzini.  Blame Lee
+  # E. McMahon (1931-1989) for sed's syntax.  :-)
+  sed -n '
+    p
+    /[$]LINENO/=
+  ' <$as_myself |
     sed '
+      s/[$]LINENO.*/&-/
+      t lineno
+      b
+      :lineno
       N
-      s,$,-,
-      : loop
-      s,^\(['$as_cr_digits']*\)\(.*\)[$]LINENO\([^'$as_cr_alnum'_]\),\1\2\1\3,
+      :loop
+      s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/
       t loop
-      s,-$,,
-      s,^['$as_cr_digits']*\n,,
+      s/-\n.*//
     ' >$as_me.lineno &&
-  chmod +x $as_me.lineno ||
+  chmod +x "$as_me.lineno" ||
     { echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" \
>&2  { (exit 1); exit 1; }; }
 
   # Don't try to exec as it changes $[0], causing all sort of problems
   # (the dirname of $[0] is not the place where we might find the
-  # original and so on.  Autoconf is especially sensible to this).
-  . ./$as_me.lineno
+  # original and so on.  Autoconf is especially sensitive to this).
+  . "./$as_me.lineno"
   # Exit status is that of the last command.
   exit
 }
 
 
-case `echo "testing\c"; echo 1,2,3`,`echo -n testing; echo 1,2,3` in
-  *c*,-n*) ECHO_N= ECHO_C='
-' ECHO_T='	' ;;
-  *c*,*  ) ECHO_N=-n ECHO_C= ECHO_T= ;;
-  *)       ECHO_N= ECHO_C='\c' ECHO_T= ;;
+if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
+  as_dirname=dirname
+else
+  as_dirname=false
+fi
+
+ECHO_C= ECHO_N= ECHO_T=
+case `echo -n x` in
+-n*)
+  case `echo 'x\c'` in
+  *c*) ECHO_T='	';;	# ECHO_T is single tab character.
+  *)   ECHO_C='\c';;
+  esac;;
+*)
+  ECHO_N='-n';;
 esac
 
-if expr a : '\(a\)' >/dev/null 2>&1; then
+if expr a : '\(a\)' >/dev/null 2>&1 &&
+   test "X`expr 00001 : '.*\(...\)'`" = X001; then
   as_expr=expr
 else
   as_expr=false
 fi
 
 rm -f conf$$ conf$$.exe conf$$.file
+if test -d conf$$.dir; then
+  rm -f conf$$.dir/conf$$.file
+else
+  rm -f conf$$.dir
+  mkdir conf$$.dir
+fi
 echo >conf$$.file
 if ln -s conf$$.file conf$$ 2>/dev/null; then
-  # We could just check for DJGPP; but this test a) works b) is more generic
-  # and c) will remain valid once DJGPP supports symlinks (DJGPP 2.04).
-  if test -f conf$$.exe; then
-    # Don't use ln at all; we don't have any links
+  as_ln_s='ln -s'
+  # ... but there are two gotchas:
+  # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
+  # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
+  # In both cases, we have to default to `cp -p'.
+  ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
     as_ln_s='cp -p'
-  else
-    as_ln_s='ln -s'
-  fi
 elif ln conf$$.file conf$$ 2>/dev/null; then
   as_ln_s=ln
 else
   as_ln_s='cp -p'
 fi
-rm -f conf$$ conf$$.exe conf$$.file
+rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
+rmdir conf$$.dir 2>/dev/null
 
 if mkdir -p . 2>/dev/null; then
   as_mkdir_p=:
@@ -223,7 +519,28 @@
   as_mkdir_p=false
 fi
 
-as_executable_p="test -f"
+if test -x / >/dev/null 2>&1; then
+  as_test_x='test -x'
+else
+  if ls -dL / >/dev/null 2>&1; then
+    as_ls_L_option=L
+  else
+    as_ls_L_option=
+  fi
+  as_test_x='
+    eval sh -c '\''
+      if test -d "$1"; then
+        test -d "$1/.";
+      else
+	case $1 in
+        -*)set "./$1";;
+	esac;
+	case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in
+	???[sx]*):;;*)false;;esac;fi
+    '\'' sh
+  '
+fi
+as_executable_p=$as_test_x
 
 # Sed expression to map a string onto a valid CPP name.
 as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
@@ -232,39 +549,27 @@
 as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
 
 
-# IFS
-# We need space, tab and new line, in precisely that order.
-as_nl='
-'
-IFS=" 	$as_nl"
 
-# CDPATH.
-$as_unset CDPATH
+exec 7<&0 </dev/null 6>&1
 
-
 # Name of the host.
 # hostname on some systems (SVR3.2, Linux) returns a bogus exit status,
 # so uname gets run too.
 ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q`
 
-exec 6>&1
-
 #
 # Initializations.
 #
 ac_default_prefix=/usr/local
+ac_clean_files=
 ac_config_libobj_dir=.
+LIBOBJS=
 cross_compiling=no
 subdirs=
 MFLAGS=
 MAKEFLAGS=
 SHELL=${CONFIG_SHELL-/bin/sh}
 
-# Maximum number of lines to put in a shell here document.
-# This variable seems obsolete.  It should probably be removed, and
-# only ac_max_sed_lines should be used.
-: ${ac_max_here_lines=38}
-
 # Identity of this package.
 PACKAGE_NAME='GNU Mailman'
 PACKAGE_TARNAME='mailman'
@@ -276,43 +581,119 @@
 # Factoring default headers for most tests.
 ac_includes_default="\
 #include <stdio.h>
-#if HAVE_SYS_TYPES_H
+#ifdef HAVE_SYS_TYPES_H
 # include <sys/types.h>
 #endif
-#if HAVE_SYS_STAT_H
+#ifdef HAVE_SYS_STAT_H
 # include <sys/stat.h>
 #endif
-#if STDC_HEADERS
+#ifdef STDC_HEADERS
 # include <stdlib.h>
 # include <stddef.h>
 #else
-# if HAVE_STDLIB_H
+# ifdef HAVE_STDLIB_H
 #  include <stdlib.h>
 # endif
 #endif
-#if HAVE_STRING_H
-# if !STDC_HEADERS && HAVE_MEMORY_H
+#ifdef HAVE_STRING_H
+# if !defined STDC_HEADERS && defined HAVE_MEMORY_H
 #  include <memory.h>
 # endif
 # include <string.h>
 #endif
-#if HAVE_STRINGS_H
+#ifdef HAVE_STRINGS_H
 # include <strings.h>
 #endif
-#if HAVE_INTTYPES_H
+#ifdef HAVE_INTTYPES_H
 # include <inttypes.h>
-#else
-# if HAVE_STDINT_H
-#  include <stdint.h>
-# endif
 #endif
-#if HAVE_UNISTD_H
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#ifdef HAVE_UNISTD_H
 # include <unistd.h>
 #endif"
 
-ac_subst_vars='SHELL PATH_SEPARATOR PACKAGE_NAME PACKAGE_TARNAME PACKAGE_VERSION \
PACKAGE_STRING PACKAGE_BUGREPORT exec_prefix prefix program_transform_name bindir \
sbindir libexecdir datadir sysconfdir sharedstatedir localstatedir libdir includedir \
oldincludedir infodir mandir build_alias host_alias target_alias DEFS ECHO_C ECHO_N \
ECHO_T LIBS with_python PYTHON INSTALL_PROGRAM INSTALL_SCRIPT INSTALL_DATA LN_S \
SET_MAKE TRUE CC CFLAGS LDFLAGS CPPFLAGS ac_ct_CC EXEEXT OBJEXT OPT VAR_PREFIX \
MAILMAN_USER MAILMAN_GROUP MAIL_GROUP CGI_GROUP CGIEXT MAILHOST URLHOST LANGUAGES CPP \
EGREP SCRIPTS LIBOBJS LTLIBOBJS' +ac_subst_vars='SHELL
+PATH_SEPARATOR
+PACKAGE_NAME
+PACKAGE_TARNAME
+PACKAGE_VERSION
+PACKAGE_STRING
+PACKAGE_BUGREPORT
+exec_prefix
+prefix
+program_transform_name
+bindir
+sbindir
+libexecdir
+datarootdir
+datadir
+sysconfdir
+sharedstatedir
+localstatedir
+includedir
+oldincludedir
+docdir
+infodir
+htmldir
+dvidir
+pdfdir
+psdir
+libdir
+localedir
+mandir
+DEFS
+ECHO_C
+ECHO_N
+ECHO_T
+LIBS
+build_alias
+host_alias
+target_alias
+with_python
+PYTHON
+INSTALL_PROGRAM
+INSTALL_SCRIPT
+INSTALL_DATA
+LN_S
+SET_MAKE
+TRUE
+CC
+CFLAGS
+LDFLAGS
+CPPFLAGS
+ac_ct_CC
+EXEEXT
+OBJEXT
+OPT
+VAR_PREFIX
+MAILMAN_USER
+MAILMAN_GROUP
+MAIL_GROUP
+CGI_GROUP
+CGIEXT
+MAILHOST
+URLHOST
+LANGUAGES
+CPP
+GREP
+EGREP
+SCRIPTS
+LIBOBJS
+LTLIBOBJS'
 ac_subst_files=''
+      ac_precious_vars='build_alias
+host_alias
+target_alias
+CC
+CFLAGS
+LDFLAGS
+LIBS
+CPPFLAGS
+CPP'
 
+
 # Initialize some variables set by options.
 ac_init_help=
 ac_init_version=false
@@ -338,34 +719,48 @@
 # and all the variables that are supposed to be based on exec_prefix
 # by default will actually change.
 # Use braces instead of parens because sh, perl, etc. also accept them.
+# (The list follows the same order as the GNU Coding Standards.)
 bindir='${exec_prefix}/bin'
 sbindir='${exec_prefix}/sbin'
 libexecdir='${exec_prefix}/libexec'
-datadir='${prefix}/share'
+datarootdir='${prefix}/share'
+datadir='${datarootdir}'
 sysconfdir='${prefix}/etc'
 sharedstatedir='${prefix}/com'
 localstatedir='${prefix}/var'
-libdir='${exec_prefix}/lib'
 includedir='${prefix}/include'
 oldincludedir='/usr/include'
-infodir='${prefix}/info'
-mandir='${prefix}/man'
+docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
+infodir='${datarootdir}/info'
+htmldir='${docdir}'
+dvidir='${docdir}'
+pdfdir='${docdir}'
+psdir='${docdir}'
+libdir='${exec_prefix}/lib'
+localedir='${datarootdir}/locale'
+mandir='${datarootdir}/man'
 
 ac_prev=
+ac_dashdash=
 for ac_option
 do
   # If the previous option needs an argument, assign it.
   if test -n "$ac_prev"; then
-    eval "$ac_prev=\$ac_option"
+    eval $ac_prev=\$ac_option
     ac_prev=
     continue
   fi
 
-  ac_optarg=`expr "x$ac_option" : 'x[^=]*=\(.*\)'`
+  case $ac_option in
+  *=*)	ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;;
+  *)	ac_optarg=yes ;;
+  esac
 
   # Accept the important Cygnus configure options, so we can diagnose typos.
 
-  case $ac_option in
+  case $ac_dashdash$ac_option in
+  --)
+    ac_dashdash=yes ;;
 
   -bindir | --bindir | --bindi | --bind | --bin | --bi)
     ac_prev=bindir ;;
@@ -387,33 +782,45 @@
   --config-cache | -C)
     cache_file=config.cache ;;
 
-  -datadir | --datadir | --datadi | --datad | --data | --dat | --da)
+  -datadir | --datadir | --datadi | --datad)
     ac_prev=datadir ;;
-  -datadir=* | --datadir=* | --datadi=* | --datad=* | --data=* | --dat=* \
-  | --da=*)
+  -datadir=* | --datadir=* | --datadi=* | --datad=*)
     datadir=$ac_optarg ;;
 
+  -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \
+  | --dataroo | --dataro | --datar)
+    ac_prev=datarootdir ;;
+  -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \
+  | --dataroot=* | --dataroo=* | --dataro=* | --datar=*)
+    datarootdir=$ac_optarg ;;
+
   -disable-* | --disable-*)
     ac_feature=`expr "x$ac_option" : 'x-*disable-\(.*\)'`
     # Reject names that are not valid shell variable names.
-    expr "x$ac_feature" : ".*[^-_$as_cr_alnum]" >/dev/null &&
+    expr "x$ac_feature" : ".*[^-._$as_cr_alnum]" >/dev/null &&
       { echo "$as_me: error: invalid feature name: $ac_feature" >&2
    { (exit 1); exit 1; }; }
-    ac_feature=`echo $ac_feature | sed 's/-/_/g'`
-    eval "enable_$ac_feature=no" ;;
+    ac_feature=`echo $ac_feature | sed 's/[-.]/_/g'`
+    eval enable_$ac_feature=no ;;
 
+  -docdir | --docdir | --docdi | --doc | --do)
+    ac_prev=docdir ;;
+  -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*)
+    docdir=$ac_optarg ;;
+
+  -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv)
+    ac_prev=dvidir ;;
+  -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*)
+    dvidir=$ac_optarg ;;
+
   -enable-* | --enable-*)
     ac_feature=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'`
     # Reject names that are not valid shell variable names.
-    expr "x$ac_feature" : ".*[^-_$as_cr_alnum]" >/dev/null &&
+    expr "x$ac_feature" : ".*[^-._$as_cr_alnum]" >/dev/null &&
       { echo "$as_me: error: invalid feature name: $ac_feature" >&2
    { (exit 1); exit 1; }; }
-    ac_feature=`echo $ac_feature | sed 's/-/_/g'`
-    case $ac_option in
-      *=*) ac_optarg=`echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"`;;
-      *) ac_optarg=yes ;;
-    esac
-    eval "enable_$ac_feature='$ac_optarg'" ;;
+    ac_feature=`echo $ac_feature | sed 's/[-.]/_/g'`
+    eval enable_$ac_feature=\$ac_optarg ;;
 
   -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \
   | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \
@@ -440,6 +847,12 @@
   -host=* | --host=* | --hos=* | --ho=*)
     host_alias=$ac_optarg ;;
 
+  -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht)
+    ac_prev=htmldir ;;
+  -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \
+  | --ht=*)
+    htmldir=$ac_optarg ;;
+
   -includedir | --includedir | --includedi | --included | --include \
   | --includ | --inclu | --incl | --inc)
     ac_prev=includedir ;;
@@ -464,13 +877,16 @@
   | --libexe=* | --libex=* | --libe=*)
     libexecdir=$ac_optarg ;;
 
+  -localedir | --localedir | --localedi | --localed | --locale)
+    ac_prev=localedir ;;
+  -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*)
+    localedir=$ac_optarg ;;
+
   -localstatedir | --localstatedir | --localstatedi | --localstated \
-  | --localstate | --localstat | --localsta | --localst \
-  | --locals | --local | --loca | --loc | --lo)
+  | --localstate | --localstat | --localsta | --localst | --locals)
     ac_prev=localstatedir ;;
   -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \
-  | --localstate=* | --localstat=* | --localsta=* | --localst=* \
-  | --locals=* | --local=* | --loca=* | --loc=* | --lo=*)
+  | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*)
     localstatedir=$ac_optarg ;;
 
   -mandir | --mandir | --mandi | --mand | --man | --ma | --m)
@@ -535,6 +951,16 @@
   | --progr-tra=* | --program-tr=* | --program-t=*)
     program_transform_name=$ac_optarg ;;
 
+  -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd)
+    ac_prev=pdfdir ;;
+  -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*)
+    pdfdir=$ac_optarg ;;
+
+  -psdir | --psdir | --psdi | --psd | --ps)
+    ac_prev=psdir ;;
+  -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*)
+    psdir=$ac_optarg ;;
+
   -q | -quiet | --quiet | --quie | --qui | --qu | --q \
   | -silent | --silent | --silen | --sile | --sil)
     silent=yes ;;
@@ -587,24 +1013,20 @@
   -with-* | --with-*)
     ac_package=`expr "x$ac_option" : 'x-*with-\([^=]*\)'`
     # Reject names that are not valid shell variable names.
-    expr "x$ac_package" : ".*[^-_$as_cr_alnum]" >/dev/null &&
+    expr "x$ac_package" : ".*[^-._$as_cr_alnum]" >/dev/null &&
       { echo "$as_me: error: invalid package name: $ac_package" >&2
    { (exit 1); exit 1; }; }
-    ac_package=`echo $ac_package| sed 's/-/_/g'`
-    case $ac_option in
-      *=*) ac_optarg=`echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"`;;
-      *) ac_optarg=yes ;;
-    esac
-    eval "with_$ac_package='$ac_optarg'" ;;
+    ac_package=`echo $ac_package | sed 's/[-.]/_/g'`
+    eval with_$ac_package=\$ac_optarg ;;
 
   -without-* | --without-*)
     ac_package=`expr "x$ac_option" : 'x-*without-\(.*\)'`
     # Reject names that are not valid shell variable names.
-    expr "x$ac_package" : ".*[^-_$as_cr_alnum]" >/dev/null &&
+    expr "x$ac_package" : ".*[^-._$as_cr_alnum]" >/dev/null &&
       { echo "$as_me: error: invalid package name: $ac_package" >&2
    { (exit 1); exit 1; }; }
-    ac_package=`echo $ac_package | sed 's/-/_/g'`
-    eval "with_$ac_package=no" ;;
+    ac_package=`echo $ac_package | sed 's/[-.]/_/g'`
+    eval with_$ac_package=no ;;
 
   --x)
     # Obsolete; use --with-x.
@@ -635,8 +1057,7 @@
     expr "x$ac_envvar" : ".*[^_$as_cr_alnum]" >/dev/null &&
       { echo "$as_me: error: invalid variable name: $ac_envvar" >&2
    { (exit 1); exit 1; }; }
-    ac_optarg=`echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"`
-    eval "$ac_envvar='$ac_optarg'"
+    eval $ac_envvar=\$ac_optarg
     export $ac_envvar ;;
 
   *)
@@ -656,29 +1077,21 @@
    { (exit 1); exit 1; }; }
 fi
 
-# Be sure to have absolute paths.
-for ac_var in exec_prefix prefix
+# Be sure to have absolute directory names.

@@ Diff output truncated at 100000 characters. @@

This was sent by the SourceForge.net collaborative development platform, the world's \
largest Open Source development site. _______________________________________________
Mailman-checkins mailing list
Mailman-checkins@python.org
Unsubscribe: http://mail.python.org/mailman/options/mailman-checkins/mailman-cvs%40progressive-comp.com



[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic