From 20a97a4163774212ad9f16c5a2e3abcbf3ecf918 Mon Sep 17 00:00:00 2001
From: Barry Warsaw
Date: Mon, 29 Sep 2008 09:19:52 -0400
Subject: Move core Mailman modules to the new mailman.core package.
Functionality that's part of the 'application', i.e. non-essential to the
core functionality is left in mailman.app. This division of labor is still
formative.
---
mailman/app/chains.py | 115 -------------
mailman/app/commands.py | 2 +-
mailman/app/lifecycle.py | 5 +-
mailman/app/pipelines.py | 122 --------------
mailman/app/plugins.py | 65 --------
mailman/app/rules.py | 42 -----
mailman/app/styles.py | 294 ---------------------------------
mailman/archiving/__init__.py | 2 +-
mailman/core/chains.py | 115 +++++++++++++
mailman/core/pipelines.py | 122 ++++++++++++++
mailman/core/plugins.py | 65 ++++++++
mailman/core/rules.py | 44 +++++
mailman/core/styles.py | 294 +++++++++++++++++++++++++++++++++
mailman/docs/chains.txt | 3 +-
mailman/docs/lifecycle.txt | 2 +-
mailman/docs/pipelines.txt | 2 +-
mailman/docs/styles.txt | 2 +-
mailman/initialize.py | 10 +-
mailman/pipeline/cook_headers.py | 14 +-
mailman/pipeline/scrubber.py | 3 +-
mailman/queue/archive.py | 2 +-
mailman/queue/incoming.py | 2 +-
mailman/rules/docs/emergency.txt | 2 +-
mailman/rules/docs/header-matching.txt | 2 +-
mailman/tests/test_documentation.py | 2 +-
setup.py | 2 +-
26 files changed, 669 insertions(+), 666 deletions(-)
delete mode 100644 mailman/app/chains.py
delete mode 100644 mailman/app/pipelines.py
delete mode 100644 mailman/app/plugins.py
delete mode 100644 mailman/app/rules.py
delete mode 100644 mailman/app/styles.py
create mode 100644 mailman/core/chains.py
create mode 100644 mailman/core/pipelines.py
create mode 100644 mailman/core/plugins.py
create mode 100644 mailman/core/rules.py
create mode 100644 mailman/core/styles.py
diff --git a/mailman/app/chains.py b/mailman/app/chains.py
deleted file mode 100644
index bebbf5bee..000000000
--- a/mailman/app/chains.py
+++ /dev/null
@@ -1,115 +0,0 @@
-# Copyright (C) 2007-2008 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see .
-
-"""Application support for chain processing."""
-
-__all__ = [
- 'initialize',
- 'process',
- ]
-__metaclass__ = type
-
-
-from mailman.chains.accept import AcceptChain
-from mailman.chains.builtin import BuiltInChain
-from mailman.chains.discard import DiscardChain
-from mailman.chains.headers import HeaderMatchChain
-from mailman.chains.hold import HoldChain
-from mailman.chains.reject import RejectChain
-from mailman.configuration import config
-from mailman.interfaces import LinkAction
-
-
-
-def process(mlist, msg, msgdata, start_chain='built-in'):
- """Process the message through a chain.
-
- :param mlist: the IMailingList for this message.
- :param msg: The Message object.
- :param msgdata: The message metadata dictionary.
- :param start_chain: The name of the chain to start the processing with.
- """
- # Set up some bookkeeping.
- chain_stack = []
- msgdata['rule_hits'] = hits = []
- msgdata['rule_misses'] = misses = []
- # Find the starting chain and begin iterating through its links.
- chain = config.chains[start_chain]
- chain_iter = chain.get_links(mlist, msg, msgdata)
- # Loop until we've reached the end of all processing chains.
- while chain:
- # Iterate over all links in the chain. Do this outside a for-loop so
- # we can capture a chain's link iterator in mid-flight. This supports
- # the 'detour' link action
- try:
- link = chain_iter.next()
- except StopIteration:
- # This chain is exhausted. Pop the last chain on the stack and
- # continue iterating through it. If there's nothing left on the
- # chain stack then we're completely finished processing.
- if len(chain_stack) == 0:
- return
- chain, chain_iter = chain_stack.pop()
- continue
- # Process this link.
- if link.rule.check(mlist, msg, msgdata):
- if link.rule.record:
- hits.append(link.rule.name)
- # The rule matched so run its action.
- if link.action is LinkAction.jump:
- chain = link.chain
- chain_iter = chain.get_links(mlist, msg, msgdata)
- continue
- elif link.action is LinkAction.detour:
- # Push the current chain so that we can return to it when
- # the next chain is finished.
- chain_stack.append((chain, chain_iter))
- chain = link.chain
- chain_iter = chain.get_links(mlist, msg, msgdata)
- continue
- elif link.action is LinkAction.stop:
- # Stop all processing.
- return
- elif link.action is LinkAction.defer:
- # Just process the next link in the chain.
- pass
- elif link.action is LinkAction.run:
- link.function(mlist, msg, msgdata)
- else:
- raise AssertionError('Bad link action: %s' % link.action)
- else:
- # The rule did not match; keep going.
- if link.rule.record:
- misses.append(link.rule.name)
-
-
-
-def initialize():
- """Set up chains, both built-in and from the database."""
- for chain_class in (DiscardChain, HoldChain, RejectChain, AcceptChain):
- chain = chain_class()
- assert chain.name not in config.chains, (
- 'Duplicate chain name: %s' % chain.name)
- config.chains[chain.name] = chain
- # Set up a couple of other default chains.
- chain = BuiltInChain()
- config.chains[chain.name] = chain
- # Create and initialize the header matching chain.
- chain = HeaderMatchChain()
- config.chains[chain.name] = chain
- # XXX Read chains from the database and initialize them.
- pass
diff --git a/mailman/app/commands.py b/mailman/app/commands.py
index f35ea531e..6ff6fdf0a 100644
--- a/mailman/app/commands.py
+++ b/mailman/app/commands.py
@@ -23,8 +23,8 @@ __all__ = [
]
-from mailman.app.plugins import get_plugins
from mailman.configuration import config
+from mailman.core.plugins import get_plugins
from mailman.interfaces import IEmailCommand
diff --git a/mailman/app/lifecycle.py b/mailman/app/lifecycle.py
index 363ade2c4..b707fdbbd 100644
--- a/mailman/app/lifecycle.py
+++ b/mailman/app/lifecycle.py
@@ -31,14 +31,13 @@ import logging
from mailman import Utils
from mailman.Utils import ValidateEmail
-from mailman.app.plugins import get_plugin
-from mailman.app.styles import style_manager
from mailman.configuration import config
from mailman.core import errors
+from mailman.core.plugins import get_plugin
+from mailman.core.styles import style_manager
from mailman.interfaces import MemberRole
-
log = logging.getLogger('mailman.error')
diff --git a/mailman/app/pipelines.py b/mailman/app/pipelines.py
deleted file mode 100644
index 949ed9e42..000000000
--- a/mailman/app/pipelines.py
+++ /dev/null
@@ -1,122 +0,0 @@
-# Copyright (C) 2008 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see .
-
-"""Pipeline processor."""
-
-__metaclass__ = type
-__all__ = [
- 'initialize',
- 'process',
- ]
-
-
-from zope.interface import implements
-from zope.interface.verify import verifyObject
-
-from mailman.app.plugins import get_plugins
-from mailman.configuration import config
-from mailman.i18n import _
-from mailman.interfaces import IHandler, IPipeline
-
-
-
-def process(mlist, msg, msgdata, pipeline_name='built-in'):
- """Process the message through the given pipeline.
-
- :param mlist: the IMailingList for this message.
- :param msg: The Message object.
- :param msgdata: The message metadata dictionary.
- :param pipeline_name: The name of the pipeline to process through.
- """
- pipeline = config.pipelines[pipeline_name]
- for handler in pipeline:
- handler.process(mlist, msg, msgdata)
-
-
-
-class BasePipeline:
- """Base pipeline implementation."""
-
- implements(IPipeline)
-
- _default_handlers = ()
-
- def __init__(self):
- self._handlers = []
- for handler_name in self._default_handlers:
- self._handlers.append(config.handlers[handler_name])
-
- def __iter__(self):
- """See `IPipeline`."""
- for handler in self._handlers:
- yield handler
-
-
-class BuiltInPipeline(BasePipeline):
- """The built-in pipeline."""
-
- name = 'built-in'
- description = _('The built-in pipeline.')
-
- _default_handlers = (
- 'mime-delete',
- 'scrubber',
- 'tagger',
- 'calculate-recipients',
- 'avoid-duplicates',
- 'cleanse',
- 'cleanse-dkim',
- 'cook-headers',
- 'to-digest',
- 'to-archive',
- 'to-usenet',
- 'after-delivery',
- 'acknowledge',
- 'to-outgoing',
- )
-
-
-class VirginPipeline(BasePipeline):
- """The processing pipeline for virgin messages.
-
- Virgin messages are those that are crafted internally by Mailman.
- """
- name = 'virgin'
- description = _('The virgin queue pipeline.')
-
- _default_handlers = (
- 'cook-headers',
- 'to-outgoing',
- )
-
-
-
-def initialize():
- """Initialize the pipelines."""
- # Find all handlers in the registered plugins.
- for handler_finder in get_plugins('mailman.handlers'):
- for handler_class in handler_finder():
- handler = handler_class()
- verifyObject(IHandler, handler)
- assert handler.name not in config.handlers, (
- 'Duplicate handler "%s" found in %s' %
- (handler.name, handler_finder))
- config.handlers[handler.name] = handler
- # Set up some pipelines.
- for pipeline_class in (BuiltInPipeline, VirginPipeline):
- pipeline = pipeline_class()
- config.pipelines[pipeline.name] = pipeline
diff --git a/mailman/app/plugins.py b/mailman/app/plugins.py
deleted file mode 100644
index cf22ad377..000000000
--- a/mailman/app/plugins.py
+++ /dev/null
@@ -1,65 +0,0 @@
-# Copyright (C) 2007-2008 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see .
-
-"""Get a requested plugin."""
-
-import pkg_resources
-
-
-
-def get_plugin(group):
- """Get the named plugin.
-
- In general, this returns exactly one plugin. If no plugins have been
- added to the named group, the 'stock' plugin will be used. If more than
- one plugin -- other than the stock one -- exists, an exception will be
- raised.
-
- :param group: The plugin group name.
- :return: The loaded plugin.
- :raises RuntimeError: If more than one plugin overrides the stock plugin
- for the named group.
- """
- entry_points = list(pkg_resources.iter_entry_points(group))
- if len(entry_points) == 0:
- raise RuntimeError('No entry points found for group: %s' % group)
- elif len(entry_points) == 1:
- # Okay, this is the one to use.
- return entry_points[0].load()
- elif len(entry_points) == 2:
- # Find the one /not/ named 'stock'.
- entry_points = [ep for ep in entry_points if ep.name <> 'stock']
- if len(entry_points) == 0:
- raise RuntimeError('No stock plugin found for group: %s' % group)
- elif len(entry_points) == 2:
- raise RuntimeError('Too many stock plugins defined')
- else:
- raise AssertionError('Insanity')
- return entry_points[0].load()
- else:
- raise RuntimeError('Too many plugins for group: %s' % group)
-
-
-
-def get_plugins(group):
- """Get and return all plugins in the named group.
-
- :param group: Plugin group name.
- :return: The loaded plugin.
- """
- for entry_point in pkg_resources.iter_entry_points(group):
- yield entry_point.load()
diff --git a/mailman/app/rules.py b/mailman/app/rules.py
deleted file mode 100644
index b48673690..000000000
--- a/mailman/app/rules.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# Copyright (C) 2007-2008 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see .
-
-"""Various rule helpers"""
-
-__all__ = ['initialize']
-__metaclass__ = type
-
-
-from zope.interface import implements
-from zope.interface.verify import verifyObject
-
-from mailman.app.plugins import get_plugins
-from mailman.configuration import config
-from mailman.interfaces import IRule
-
-
-
-def initialize():
- """Find and register all rules in all plugins."""
- # Find rules in plugins.
- for rule_finder in get_plugins('mailman.rules'):
- for rule_class in rule_finder():
- rule = rule_class()
- verifyObject(IRule, rule)
- assert rule.name not in config.rules, (
- 'Duplicate rule "%s" found in %s' % (rule.name, rule_finder))
- config.rules[rule.name] = rule
diff --git a/mailman/app/styles.py b/mailman/app/styles.py
deleted file mode 100644
index 8f487177e..000000000
--- a/mailman/app/styles.py
+++ /dev/null
@@ -1,294 +0,0 @@
-# Copyright (C) 2007-2008 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see .
-
-"""Application of list styles to new and existing lists."""
-
-__metaclass__ = type
-__all__ = [
- 'DefaultStyle',
- 'style_manager',
- ]
-
-import datetime
-
-from operator import attrgetter
-from zope.interface import implements
-from zope.interface.verify import verifyObject
-
-from mailman import Utils
-from mailman.app.plugins import get_plugins
-from mailman.configuration import config
-from mailman.i18n import _
-from mailman.interfaces import (
- Action, DuplicateStyleError, IStyle, IStyleManager, NewsModeration,
- Personalization)
-
-
-
-class DefaultStyle:
- """The defalt (i.e. legacy) style."""
-
- implements(IStyle)
-
- name = 'default'
- priority = 0 # the lowest priority style
-
- def apply(self, mailing_list):
- """See `IStyle`."""
- # For cut-n-paste convenience.
- mlist = mailing_list
- # Most of these were ripped from the old MailList.InitVars() method.
- mlist.volume = 1
- mlist.post_id = 1
- mlist.new_member_options = config.DEFAULT_NEW_MEMBER_OPTIONS
- # This stuff is configurable
- mlist.real_name = mlist.list_name.capitalize()
- mlist.respond_to_post_requests = True
- mlist.advertised = config.DEFAULT_LIST_ADVERTISED
- mlist.max_num_recipients = config.DEFAULT_MAX_NUM_RECIPIENTS
- mlist.max_message_size = config.DEFAULT_MAX_MESSAGE_SIZE
- mlist.reply_goes_to_list = config.DEFAULT_REPLY_GOES_TO_LIST
- mlist.reply_to_address = u''
- mlist.first_strip_reply_to = config.DEFAULT_FIRST_STRIP_REPLY_TO
- mlist.admin_immed_notify = config.DEFAULT_ADMIN_IMMED_NOTIFY
- mlist.admin_notify_mchanges = (
- config.DEFAULT_ADMIN_NOTIFY_MCHANGES)
- mlist.require_explicit_destination = (
- config.DEFAULT_REQUIRE_EXPLICIT_DESTINATION)
- mlist.acceptable_aliases = config.DEFAULT_ACCEPTABLE_ALIASES
- mlist.send_reminders = config.DEFAULT_SEND_REMINDERS
- mlist.send_welcome_msg = config.DEFAULT_SEND_WELCOME_MSG
- mlist.send_goodbye_msg = config.DEFAULT_SEND_GOODBYE_MSG
- mlist.bounce_matching_headers = (
- config.DEFAULT_BOUNCE_MATCHING_HEADERS)
- mlist.header_matches = []
- mlist.anonymous_list = config.DEFAULT_ANONYMOUS_LIST
- mlist.description = u''
- mlist.info = u''
- mlist.welcome_msg = u''
- mlist.goodbye_msg = u''
- mlist.subscribe_policy = config.DEFAULT_SUBSCRIBE_POLICY
- mlist.subscribe_auto_approval = config.DEFAULT_SUBSCRIBE_AUTO_APPROVAL
- mlist.unsubscribe_policy = config.DEFAULT_UNSUBSCRIBE_POLICY
- mlist.private_roster = config.DEFAULT_PRIVATE_ROSTER
- mlist.obscure_addresses = config.DEFAULT_OBSCURE_ADDRESSES
- mlist.admin_member_chunksize = config.DEFAULT_ADMIN_MEMBER_CHUNKSIZE
- mlist.administrivia = config.DEFAULT_ADMINISTRIVIA
- mlist.preferred_language = config.DEFAULT_SERVER_LANGUAGE
- mlist.include_rfc2369_headers = True
- mlist.include_list_post_header = True
- mlist.filter_mime_types = config.DEFAULT_FILTER_MIME_TYPES
- mlist.pass_mime_types = config.DEFAULT_PASS_MIME_TYPES
- mlist.filter_filename_extensions = (
- config.DEFAULT_FILTER_FILENAME_EXTENSIONS)
- mlist.pass_filename_extensions = config.DEFAULT_PASS_FILENAME_EXTENSIONS
- mlist.filter_content = config.DEFAULT_FILTER_CONTENT
- mlist.collapse_alternatives = config.DEFAULT_COLLAPSE_ALTERNATIVES
- mlist.convert_html_to_plaintext = (
- config.DEFAULT_CONVERT_HTML_TO_PLAINTEXT)
- mlist.filter_action = config.DEFAULT_FILTER_ACTION
- # Digest related variables
- mlist.digestable = config.DEFAULT_DIGESTABLE
- mlist.digest_is_default = config.DEFAULT_DIGEST_IS_DEFAULT
- mlist.mime_is_default_digest = config.DEFAULT_MIME_IS_DEFAULT_DIGEST
- mlist.digest_size_threshhold = config.DEFAULT_DIGEST_SIZE_THRESHHOLD
- mlist.digest_send_periodic = config.DEFAULT_DIGEST_SEND_PERIODIC
- mlist.digest_header = config.DEFAULT_DIGEST_HEADER
- mlist.digest_footer = config.DEFAULT_DIGEST_FOOTER
- mlist.digest_volume_frequency = config.DEFAULT_DIGEST_VOLUME_FREQUENCY
- mlist.one_last_digest = {}
- mlist.digest_members = {}
- mlist.next_digest_number = 1
- mlist.nondigestable = config.DEFAULT_NONDIGESTABLE
- mlist.personalize = Personalization.none
- # New sender-centric moderation (privacy) options
- mlist.default_member_moderation = (
- config.DEFAULT_DEFAULT_MEMBER_MODERATION)
- # Archiver
- mlist.archive = config.DEFAULT_ARCHIVE
- mlist.archive_private = config.DEFAULT_ARCHIVE_PRIVATE
- mlist.archive_volume_frequency = (
- config.DEFAULT_ARCHIVE_VOLUME_FREQUENCY)
- mlist.emergency = False
- mlist.member_moderation_action = Action.hold
- mlist.member_moderation_notice = u''
- mlist.accept_these_nonmembers = []
- mlist.hold_these_nonmembers = []
- mlist.reject_these_nonmembers = []
- mlist.discard_these_nonmembers = []
- mlist.forward_auto_discards = config.DEFAULT_FORWARD_AUTO_DISCARDS
- mlist.generic_nonmember_action = (
- config.DEFAULT_GENERIC_NONMEMBER_ACTION)
- mlist.nonmember_rejection_notice = u''
- # Ban lists
- mlist.ban_list = []
- # Max autoresponses per day. A mapping between addresses and a
- # 2-tuple of the date of the last autoresponse and the number of
- # autoresponses sent on that date.
- mlist.hold_and_cmd_autoresponses = {}
- mlist.subject_prefix = _(config.DEFAULT_SUBJECT_PREFIX)
- mlist.msg_header = config.DEFAULT_MSG_HEADER
- mlist.msg_footer = config.DEFAULT_MSG_FOOTER
- # Set this to Never if the list's preferred language uses us-ascii,
- # otherwise set it to As Needed
- if Utils.GetCharSet(mlist.preferred_language) == 'us-ascii':
- mlist.encode_ascii_prefixes = 0
- else:
- mlist.encode_ascii_prefixes = 2
- # scrub regular delivery
- mlist.scrub_nondigest = config.DEFAULT_SCRUB_NONDIGEST
- # automatic discarding
- mlist.max_days_to_hold = config.DEFAULT_MAX_DAYS_TO_HOLD
- # Autoresponder
- mlist.autorespond_postings = False
- mlist.autorespond_admin = False
- # this value can be
- # 0 - no autoresponse on the -request line
- # 1 - autorespond, but discard the original message
- # 2 - autorespond, and forward the message on to be processed
- mlist.autorespond_requests = 0
- mlist.autoresponse_postings_text = u''
- mlist.autoresponse_admin_text = u''
- mlist.autoresponse_request_text = u''
- mlist.autoresponse_graceperiod = datetime.timedelta(days=90)
- mlist.postings_responses = {}
- mlist.admin_responses = {}
- mlist.request_responses = {}
- # Bounces
- mlist.bounce_processing = config.DEFAULT_BOUNCE_PROCESSING
- mlist.bounce_score_threshold = config.DEFAULT_BOUNCE_SCORE_THRESHOLD
- mlist.bounce_info_stale_after = config.DEFAULT_BOUNCE_INFO_STALE_AFTER
- mlist.bounce_you_are_disabled_warnings = (
- config.DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS)
- mlist.bounce_you_are_disabled_warnings_interval = (
- config.DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS_INTERVAL)
- mlist.bounce_unrecognized_goes_to_list_owner = (
- config.DEFAULT_BOUNCE_UNRECOGNIZED_GOES_TO_LIST_OWNER)
- mlist.bounce_notify_owner_on_disable = (
- config.DEFAULT_BOUNCE_NOTIFY_OWNER_ON_DISABLE)
- mlist.bounce_notify_owner_on_removal = (
- config.DEFAULT_BOUNCE_NOTIFY_OWNER_ON_REMOVAL)
- # This holds legacy member related information. It's keyed by the
- # member address, and the value is an object containing the bounce
- # score, the date of the last received bounce, and a count of the
- # notifications left to send.
- mlist.bounce_info = {}
- # New style delivery status
- mlist.delivery_status = {}
- # NNTP gateway
- mlist.nntp_host = config.DEFAULT_NNTP_HOST
- mlist.linked_newsgroup = u''
- mlist.gateway_to_news = False
- mlist.gateway_to_mail = False
- mlist.news_prefix_subject_too = True
- # In patch #401270, this was called newsgroup_is_moderated, but the
- # semantics weren't quite the same.
- mlist.news_moderation = NewsModeration.none
- # Topics
- #
- # `topics' is a list of 4-tuples of the following form:
- #
- # (name, pattern, description, emptyflag)
- #
- # name is a required arbitrary string displayed to the user when they
- # get to select their topics of interest
- #
- # pattern is a required verbose regular expression pattern which is
- # used as IGNORECASE.
- #
- # description is an optional description of what this topic is
- # supposed to match
- #
- # emptyflag is a boolean used internally in the admin interface to
- # signal whether a topic entry is new or not (new ones which do not
- # have a name or pattern are not saved when the submit button is
- # pressed).
- mlist.topics = []
- mlist.topics_enabled = False
- mlist.topics_bodylines_limit = 5
- # This is a mapping between user "names" (i.e. addresses) and
- # information about which topics that user is interested in. The
- # values are a list of topic names that the user is interested in,
- # which should match the topic names in mlist.topics above.
- #
- # If the user has not selected any topics of interest, then the rule
- # is that they will get all messages, and they will not have an entry
- # in this dictionary.
- mlist.topics_userinterest = {}
- # The processing chain that messages coming into this list get
- # processed by.
- mlist.start_chain = u'built-in'
- # The default pipeline to send accepted messages through.
- mlist.pipeline = u'built-in'
-
- def match(self, mailing_list, styles):
- """See `IStyle`."""
- # If no other styles have matched, then the default style matches.
- if len(styles) == 0:
- styles.append(self)
-
-
-
-class StyleManager:
- """The built-in style manager."""
-
- implements(IStyleManager)
-
- def __init__(self):
- """Install all styles from registered plugins, and install them."""
- self._styles = {}
- # Install all the styles provided by plugins.
- for style_factory in get_plugins('mailman.styles'):
- style = style_factory()
- # Let DuplicateStyleErrors percolate up.
- self.register(style)
-
- def get(self, name):
- """See `IStyleManager`."""
- return self._styles.get(name)
-
- def lookup(self, mailing_list):
- """See `IStyleManager`."""
- matched_styles = []
- for style in self.styles:
- style.match(mailing_list, matched_styles)
- for style in matched_styles:
- yield style
-
- @property
- def styles(self):
- """See `IStyleManager`."""
- for style in sorted(self._styles.values(),
- key=attrgetter('priority'),
- reverse=True):
- yield style
-
- def register(self, style):
- """See `IStyleManager`."""
- verifyObject(IStyle, style)
- if style.name in self._styles:
- raise DuplicateStyleError(style.name)
- self._styles[style.name] = style
-
- def unregister(self, style):
- """See `IStyleManager`."""
- # Let KeyErrors percolate up.
- del self._styles[style.name]
-
-
-
-style_manager = StyleManager()
diff --git a/mailman/archiving/__init__.py b/mailman/archiving/__init__.py
index 203d4d78c..41dff720c 100644
--- a/mailman/archiving/__init__.py
+++ b/mailman/archiving/__init__.py
@@ -21,8 +21,8 @@ __all__ = [
]
-from mailman.app.plugins import get_plugins
from mailman.configuration import config
+from mailman.core.plugins import get_plugins
def initialize():
diff --git a/mailman/core/chains.py b/mailman/core/chains.py
new file mode 100644
index 000000000..bebbf5bee
--- /dev/null
+++ b/mailman/core/chains.py
@@ -0,0 +1,115 @@
+# Copyright (C) 2007-2008 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman 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 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman 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
+# GNU Mailman. If not, see .
+
+"""Application support for chain processing."""
+
+__all__ = [
+ 'initialize',
+ 'process',
+ ]
+__metaclass__ = type
+
+
+from mailman.chains.accept import AcceptChain
+from mailman.chains.builtin import BuiltInChain
+from mailman.chains.discard import DiscardChain
+from mailman.chains.headers import HeaderMatchChain
+from mailman.chains.hold import HoldChain
+from mailman.chains.reject import RejectChain
+from mailman.configuration import config
+from mailman.interfaces import LinkAction
+
+
+
+def process(mlist, msg, msgdata, start_chain='built-in'):
+ """Process the message through a chain.
+
+ :param mlist: the IMailingList for this message.
+ :param msg: The Message object.
+ :param msgdata: The message metadata dictionary.
+ :param start_chain: The name of the chain to start the processing with.
+ """
+ # Set up some bookkeeping.
+ chain_stack = []
+ msgdata['rule_hits'] = hits = []
+ msgdata['rule_misses'] = misses = []
+ # Find the starting chain and begin iterating through its links.
+ chain = config.chains[start_chain]
+ chain_iter = chain.get_links(mlist, msg, msgdata)
+ # Loop until we've reached the end of all processing chains.
+ while chain:
+ # Iterate over all links in the chain. Do this outside a for-loop so
+ # we can capture a chain's link iterator in mid-flight. This supports
+ # the 'detour' link action
+ try:
+ link = chain_iter.next()
+ except StopIteration:
+ # This chain is exhausted. Pop the last chain on the stack and
+ # continue iterating through it. If there's nothing left on the
+ # chain stack then we're completely finished processing.
+ if len(chain_stack) == 0:
+ return
+ chain, chain_iter = chain_stack.pop()
+ continue
+ # Process this link.
+ if link.rule.check(mlist, msg, msgdata):
+ if link.rule.record:
+ hits.append(link.rule.name)
+ # The rule matched so run its action.
+ if link.action is LinkAction.jump:
+ chain = link.chain
+ chain_iter = chain.get_links(mlist, msg, msgdata)
+ continue
+ elif link.action is LinkAction.detour:
+ # Push the current chain so that we can return to it when
+ # the next chain is finished.
+ chain_stack.append((chain, chain_iter))
+ chain = link.chain
+ chain_iter = chain.get_links(mlist, msg, msgdata)
+ continue
+ elif link.action is LinkAction.stop:
+ # Stop all processing.
+ return
+ elif link.action is LinkAction.defer:
+ # Just process the next link in the chain.
+ pass
+ elif link.action is LinkAction.run:
+ link.function(mlist, msg, msgdata)
+ else:
+ raise AssertionError('Bad link action: %s' % link.action)
+ else:
+ # The rule did not match; keep going.
+ if link.rule.record:
+ misses.append(link.rule.name)
+
+
+
+def initialize():
+ """Set up chains, both built-in and from the database."""
+ for chain_class in (DiscardChain, HoldChain, RejectChain, AcceptChain):
+ chain = chain_class()
+ assert chain.name not in config.chains, (
+ 'Duplicate chain name: %s' % chain.name)
+ config.chains[chain.name] = chain
+ # Set up a couple of other default chains.
+ chain = BuiltInChain()
+ config.chains[chain.name] = chain
+ # Create and initialize the header matching chain.
+ chain = HeaderMatchChain()
+ config.chains[chain.name] = chain
+ # XXX Read chains from the database and initialize them.
+ pass
diff --git a/mailman/core/pipelines.py b/mailman/core/pipelines.py
new file mode 100644
index 000000000..c790901b2
--- /dev/null
+++ b/mailman/core/pipelines.py
@@ -0,0 +1,122 @@
+# Copyright (C) 2008 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman 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 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman 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
+# GNU Mailman. If not, see .
+
+"""Pipeline processor."""
+
+__metaclass__ = type
+__all__ = [
+ 'initialize',
+ 'process',
+ ]
+
+
+from zope.interface import implements
+from zope.interface.verify import verifyObject
+
+from mailman.configuration import config
+from mailman.core.plugins import get_plugins
+from mailman.i18n import _
+from mailman.interfaces import IHandler, IPipeline
+
+
+
+def process(mlist, msg, msgdata, pipeline_name='built-in'):
+ """Process the message through the given pipeline.
+
+ :param mlist: the IMailingList for this message.
+ :param msg: The Message object.
+ :param msgdata: The message metadata dictionary.
+ :param pipeline_name: The name of the pipeline to process through.
+ """
+ pipeline = config.pipelines[pipeline_name]
+ for handler in pipeline:
+ handler.process(mlist, msg, msgdata)
+
+
+
+class BasePipeline:
+ """Base pipeline implementation."""
+
+ implements(IPipeline)
+
+ _default_handlers = ()
+
+ def __init__(self):
+ self._handlers = []
+ for handler_name in self._default_handlers:
+ self._handlers.append(config.handlers[handler_name])
+
+ def __iter__(self):
+ """See `IPipeline`."""
+ for handler in self._handlers:
+ yield handler
+
+
+class BuiltInPipeline(BasePipeline):
+ """The built-in pipeline."""
+
+ name = 'built-in'
+ description = _('The built-in pipeline.')
+
+ _default_handlers = (
+ 'mime-delete',
+ 'scrubber',
+ 'tagger',
+ 'calculate-recipients',
+ 'avoid-duplicates',
+ 'cleanse',
+ 'cleanse-dkim',
+ 'cook-headers',
+ 'to-digest',
+ 'to-archive',
+ 'to-usenet',
+ 'after-delivery',
+ 'acknowledge',
+ 'to-outgoing',
+ )
+
+
+class VirginPipeline(BasePipeline):
+ """The processing pipeline for virgin messages.
+
+ Virgin messages are those that are crafted internally by Mailman.
+ """
+ name = 'virgin'
+ description = _('The virgin queue pipeline.')
+
+ _default_handlers = (
+ 'cook-headers',
+ 'to-outgoing',
+ )
+
+
+
+def initialize():
+ """Initialize the pipelines."""
+ # Find all handlers in the registered plugins.
+ for handler_finder in get_plugins('mailman.handlers'):
+ for handler_class in handler_finder():
+ handler = handler_class()
+ verifyObject(IHandler, handler)
+ assert handler.name not in config.handlers, (
+ 'Duplicate handler "%s" found in %s' %
+ (handler.name, handler_finder))
+ config.handlers[handler.name] = handler
+ # Set up some pipelines.
+ for pipeline_class in (BuiltInPipeline, VirginPipeline):
+ pipeline = pipeline_class()
+ config.pipelines[pipeline.name] = pipeline
diff --git a/mailman/core/plugins.py b/mailman/core/plugins.py
new file mode 100644
index 000000000..cf22ad377
--- /dev/null
+++ b/mailman/core/plugins.py
@@ -0,0 +1,65 @@
+# Copyright (C) 2007-2008 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman 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 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman 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
+# GNU Mailman. If not, see .
+
+"""Get a requested plugin."""
+
+import pkg_resources
+
+
+
+def get_plugin(group):
+ """Get the named plugin.
+
+ In general, this returns exactly one plugin. If no plugins have been
+ added to the named group, the 'stock' plugin will be used. If more than
+ one plugin -- other than the stock one -- exists, an exception will be
+ raised.
+
+ :param group: The plugin group name.
+ :return: The loaded plugin.
+ :raises RuntimeError: If more than one plugin overrides the stock plugin
+ for the named group.
+ """
+ entry_points = list(pkg_resources.iter_entry_points(group))
+ if len(entry_points) == 0:
+ raise RuntimeError('No entry points found for group: %s' % group)
+ elif len(entry_points) == 1:
+ # Okay, this is the one to use.
+ return entry_points[0].load()
+ elif len(entry_points) == 2:
+ # Find the one /not/ named 'stock'.
+ entry_points = [ep for ep in entry_points if ep.name <> 'stock']
+ if len(entry_points) == 0:
+ raise RuntimeError('No stock plugin found for group: %s' % group)
+ elif len(entry_points) == 2:
+ raise RuntimeError('Too many stock plugins defined')
+ else:
+ raise AssertionError('Insanity')
+ return entry_points[0].load()
+ else:
+ raise RuntimeError('Too many plugins for group: %s' % group)
+
+
+
+def get_plugins(group):
+ """Get and return all plugins in the named group.
+
+ :param group: Plugin group name.
+ :return: The loaded plugin.
+ """
+ for entry_point in pkg_resources.iter_entry_points(group):
+ yield entry_point.load()
diff --git a/mailman/core/rules.py b/mailman/core/rules.py
new file mode 100644
index 000000000..2c1e4fb3b
--- /dev/null
+++ b/mailman/core/rules.py
@@ -0,0 +1,44 @@
+# Copyright (C) 2007-2008 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman 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 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman 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
+# GNU Mailman. If not, see .
+
+"""Various rule helpers"""
+
+__metaclass__ = type
+__all__ = [
+ 'initialize',
+ ]
+
+
+from zope.interface import implements
+from zope.interface.verify import verifyObject
+
+from mailman.configuration import config
+from mailman.core.plugins import get_plugins
+from mailman.interfaces import IRule
+
+
+
+def initialize():
+ """Find and register all rules in all plugins."""
+ # Find rules in plugins.
+ for rule_finder in get_plugins('mailman.rules'):
+ for rule_class in rule_finder():
+ rule = rule_class()
+ verifyObject(IRule, rule)
+ assert rule.name not in config.rules, (
+ 'Duplicate rule "%s" found in %s' % (rule.name, rule_finder))
+ config.rules[rule.name] = rule
diff --git a/mailman/core/styles.py b/mailman/core/styles.py
new file mode 100644
index 000000000..96104c204
--- /dev/null
+++ b/mailman/core/styles.py
@@ -0,0 +1,294 @@
+# Copyright (C) 2007-2008 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman 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 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman 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
+# GNU Mailman. If not, see .
+
+"""Application of list styles to new and existing lists."""
+
+__metaclass__ = type
+__all__ = [
+ 'DefaultStyle',
+ 'style_manager',
+ ]
+
+import datetime
+
+from operator import attrgetter
+from zope.interface import implements
+from zope.interface.verify import verifyObject
+
+from mailman import Utils
+from mailman.configuration import config
+from mailman.core.plugins import get_plugins
+from mailman.i18n import _
+from mailman.interfaces import (
+ Action, DuplicateStyleError, IStyle, IStyleManager, NewsModeration,
+ Personalization)
+
+
+
+class DefaultStyle:
+ """The defalt (i.e. legacy) style."""
+
+ implements(IStyle)
+
+ name = 'default'
+ priority = 0 # the lowest priority style
+
+ def apply(self, mailing_list):
+ """See `IStyle`."""
+ # For cut-n-paste convenience.
+ mlist = mailing_list
+ # Most of these were ripped from the old MailList.InitVars() method.
+ mlist.volume = 1
+ mlist.post_id = 1
+ mlist.new_member_options = config.DEFAULT_NEW_MEMBER_OPTIONS
+ # This stuff is configurable
+ mlist.real_name = mlist.list_name.capitalize()
+ mlist.respond_to_post_requests = True
+ mlist.advertised = config.DEFAULT_LIST_ADVERTISED
+ mlist.max_num_recipients = config.DEFAULT_MAX_NUM_RECIPIENTS
+ mlist.max_message_size = config.DEFAULT_MAX_MESSAGE_SIZE
+ mlist.reply_goes_to_list = config.DEFAULT_REPLY_GOES_TO_LIST
+ mlist.reply_to_address = u''
+ mlist.first_strip_reply_to = config.DEFAULT_FIRST_STRIP_REPLY_TO
+ mlist.admin_immed_notify = config.DEFAULT_ADMIN_IMMED_NOTIFY
+ mlist.admin_notify_mchanges = (
+ config.DEFAULT_ADMIN_NOTIFY_MCHANGES)
+ mlist.require_explicit_destination = (
+ config.DEFAULT_REQUIRE_EXPLICIT_DESTINATION)
+ mlist.acceptable_aliases = config.DEFAULT_ACCEPTABLE_ALIASES
+ mlist.send_reminders = config.DEFAULT_SEND_REMINDERS
+ mlist.send_welcome_msg = config.DEFAULT_SEND_WELCOME_MSG
+ mlist.send_goodbye_msg = config.DEFAULT_SEND_GOODBYE_MSG
+ mlist.bounce_matching_headers = (
+ config.DEFAULT_BOUNCE_MATCHING_HEADERS)
+ mlist.header_matches = []
+ mlist.anonymous_list = config.DEFAULT_ANONYMOUS_LIST
+ mlist.description = u''
+ mlist.info = u''
+ mlist.welcome_msg = u''
+ mlist.goodbye_msg = u''
+ mlist.subscribe_policy = config.DEFAULT_SUBSCRIBE_POLICY
+ mlist.subscribe_auto_approval = config.DEFAULT_SUBSCRIBE_AUTO_APPROVAL
+ mlist.unsubscribe_policy = config.DEFAULT_UNSUBSCRIBE_POLICY
+ mlist.private_roster = config.DEFAULT_PRIVATE_ROSTER
+ mlist.obscure_addresses = config.DEFAULT_OBSCURE_ADDRESSES
+ mlist.admin_member_chunksize = config.DEFAULT_ADMIN_MEMBER_CHUNKSIZE
+ mlist.administrivia = config.DEFAULT_ADMINISTRIVIA
+ mlist.preferred_language = config.DEFAULT_SERVER_LANGUAGE
+ mlist.include_rfc2369_headers = True
+ mlist.include_list_post_header = True
+ mlist.filter_mime_types = config.DEFAULT_FILTER_MIME_TYPES
+ mlist.pass_mime_types = config.DEFAULT_PASS_MIME_TYPES
+ mlist.filter_filename_extensions = (
+ config.DEFAULT_FILTER_FILENAME_EXTENSIONS)
+ mlist.pass_filename_extensions = config.DEFAULT_PASS_FILENAME_EXTENSIONS
+ mlist.filter_content = config.DEFAULT_FILTER_CONTENT
+ mlist.collapse_alternatives = config.DEFAULT_COLLAPSE_ALTERNATIVES
+ mlist.convert_html_to_plaintext = (
+ config.DEFAULT_CONVERT_HTML_TO_PLAINTEXT)
+ mlist.filter_action = config.DEFAULT_FILTER_ACTION
+ # Digest related variables
+ mlist.digestable = config.DEFAULT_DIGESTABLE
+ mlist.digest_is_default = config.DEFAULT_DIGEST_IS_DEFAULT
+ mlist.mime_is_default_digest = config.DEFAULT_MIME_IS_DEFAULT_DIGEST
+ mlist.digest_size_threshhold = config.DEFAULT_DIGEST_SIZE_THRESHHOLD
+ mlist.digest_send_periodic = config.DEFAULT_DIGEST_SEND_PERIODIC
+ mlist.digest_header = config.DEFAULT_DIGEST_HEADER
+ mlist.digest_footer = config.DEFAULT_DIGEST_FOOTER
+ mlist.digest_volume_frequency = config.DEFAULT_DIGEST_VOLUME_FREQUENCY
+ mlist.one_last_digest = {}
+ mlist.digest_members = {}
+ mlist.next_digest_number = 1
+ mlist.nondigestable = config.DEFAULT_NONDIGESTABLE
+ mlist.personalize = Personalization.none
+ # New sender-centric moderation (privacy) options
+ mlist.default_member_moderation = (
+ config.DEFAULT_DEFAULT_MEMBER_MODERATION)
+ # Archiver
+ mlist.archive = config.DEFAULT_ARCHIVE
+ mlist.archive_private = config.DEFAULT_ARCHIVE_PRIVATE
+ mlist.archive_volume_frequency = (
+ config.DEFAULT_ARCHIVE_VOLUME_FREQUENCY)
+ mlist.emergency = False
+ mlist.member_moderation_action = Action.hold
+ mlist.member_moderation_notice = u''
+ mlist.accept_these_nonmembers = []
+ mlist.hold_these_nonmembers = []
+ mlist.reject_these_nonmembers = []
+ mlist.discard_these_nonmembers = []
+ mlist.forward_auto_discards = config.DEFAULT_FORWARD_AUTO_DISCARDS
+ mlist.generic_nonmember_action = (
+ config.DEFAULT_GENERIC_NONMEMBER_ACTION)
+ mlist.nonmember_rejection_notice = u''
+ # Ban lists
+ mlist.ban_list = []
+ # Max autoresponses per day. A mapping between addresses and a
+ # 2-tuple of the date of the last autoresponse and the number of
+ # autoresponses sent on that date.
+ mlist.hold_and_cmd_autoresponses = {}
+ mlist.subject_prefix = _(config.DEFAULT_SUBJECT_PREFIX)
+ mlist.msg_header = config.DEFAULT_MSG_HEADER
+ mlist.msg_footer = config.DEFAULT_MSG_FOOTER
+ # Set this to Never if the list's preferred language uses us-ascii,
+ # otherwise set it to As Needed
+ if Utils.GetCharSet(mlist.preferred_language) == 'us-ascii':
+ mlist.encode_ascii_prefixes = 0
+ else:
+ mlist.encode_ascii_prefixes = 2
+ # scrub regular delivery
+ mlist.scrub_nondigest = config.DEFAULT_SCRUB_NONDIGEST
+ # automatic discarding
+ mlist.max_days_to_hold = config.DEFAULT_MAX_DAYS_TO_HOLD
+ # Autoresponder
+ mlist.autorespond_postings = False
+ mlist.autorespond_admin = False
+ # this value can be
+ # 0 - no autoresponse on the -request line
+ # 1 - autorespond, but discard the original message
+ # 2 - autorespond, and forward the message on to be processed
+ mlist.autorespond_requests = 0
+ mlist.autoresponse_postings_text = u''
+ mlist.autoresponse_admin_text = u''
+ mlist.autoresponse_request_text = u''
+ mlist.autoresponse_graceperiod = datetime.timedelta(days=90)
+ mlist.postings_responses = {}
+ mlist.admin_responses = {}
+ mlist.request_responses = {}
+ # Bounces
+ mlist.bounce_processing = config.DEFAULT_BOUNCE_PROCESSING
+ mlist.bounce_score_threshold = config.DEFAULT_BOUNCE_SCORE_THRESHOLD
+ mlist.bounce_info_stale_after = config.DEFAULT_BOUNCE_INFO_STALE_AFTER
+ mlist.bounce_you_are_disabled_warnings = (
+ config.DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS)
+ mlist.bounce_you_are_disabled_warnings_interval = (
+ config.DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS_INTERVAL)
+ mlist.bounce_unrecognized_goes_to_list_owner = (
+ config.DEFAULT_BOUNCE_UNRECOGNIZED_GOES_TO_LIST_OWNER)
+ mlist.bounce_notify_owner_on_disable = (
+ config.DEFAULT_BOUNCE_NOTIFY_OWNER_ON_DISABLE)
+ mlist.bounce_notify_owner_on_removal = (
+ config.DEFAULT_BOUNCE_NOTIFY_OWNER_ON_REMOVAL)
+ # This holds legacy member related information. It's keyed by the
+ # member address, and the value is an object containing the bounce
+ # score, the date of the last received bounce, and a count of the
+ # notifications left to send.
+ mlist.bounce_info = {}
+ # New style delivery status
+ mlist.delivery_status = {}
+ # NNTP gateway
+ mlist.nntp_host = config.DEFAULT_NNTP_HOST
+ mlist.linked_newsgroup = u''
+ mlist.gateway_to_news = False
+ mlist.gateway_to_mail = False
+ mlist.news_prefix_subject_too = True
+ # In patch #401270, this was called newsgroup_is_moderated, but the
+ # semantics weren't quite the same.
+ mlist.news_moderation = NewsModeration.none
+ # Topics
+ #
+ # `topics' is a list of 4-tuples of the following form:
+ #
+ # (name, pattern, description, emptyflag)
+ #
+ # name is a required arbitrary string displayed to the user when they
+ # get to select their topics of interest
+ #
+ # pattern is a required verbose regular expression pattern which is
+ # used as IGNORECASE.
+ #
+ # description is an optional description of what this topic is
+ # supposed to match
+ #
+ # emptyflag is a boolean used internally in the admin interface to
+ # signal whether a topic entry is new or not (new ones which do not
+ # have a name or pattern are not saved when the submit button is
+ # pressed).
+ mlist.topics = []
+ mlist.topics_enabled = False
+ mlist.topics_bodylines_limit = 5
+ # This is a mapping between user "names" (i.e. addresses) and
+ # information about which topics that user is interested in. The
+ # values are a list of topic names that the user is interested in,
+ # which should match the topic names in mlist.topics above.
+ #
+ # If the user has not selected any topics of interest, then the rule
+ # is that they will get all messages, and they will not have an entry
+ # in this dictionary.
+ mlist.topics_userinterest = {}
+ # The processing chain that messages coming into this list get
+ # processed by.
+ mlist.start_chain = u'built-in'
+ # The default pipeline to send accepted messages through.
+ mlist.pipeline = u'built-in'
+
+ def match(self, mailing_list, styles):
+ """See `IStyle`."""
+ # If no other styles have matched, then the default style matches.
+ if len(styles) == 0:
+ styles.append(self)
+
+
+
+class StyleManager:
+ """The built-in style manager."""
+
+ implements(IStyleManager)
+
+ def __init__(self):
+ """Install all styles from registered plugins, and install them."""
+ self._styles = {}
+ # Install all the styles provided by plugins.
+ for style_factory in get_plugins('mailman.styles'):
+ style = style_factory()
+ # Let DuplicateStyleErrors percolate up.
+ self.register(style)
+
+ def get(self, name):
+ """See `IStyleManager`."""
+ return self._styles.get(name)
+
+ def lookup(self, mailing_list):
+ """See `IStyleManager`."""
+ matched_styles = []
+ for style in self.styles:
+ style.match(mailing_list, matched_styles)
+ for style in matched_styles:
+ yield style
+
+ @property
+ def styles(self):
+ """See `IStyleManager`."""
+ for style in sorted(self._styles.values(),
+ key=attrgetter('priority'),
+ reverse=True):
+ yield style
+
+ def register(self, style):
+ """See `IStyleManager`."""
+ verifyObject(IStyle, style)
+ if style.name in self._styles:
+ raise DuplicateStyleError(style.name)
+ self._styles[style.name] = style
+
+ def unregister(self, style):
+ """See `IStyleManager`."""
+ # Let KeyErrors percolate up.
+ del self._styles[style.name]
+
+
+
+style_manager = StyleManager()
diff --git a/mailman/docs/chains.txt b/mailman/docs/chains.txt
index 964ef0edb..1118df687 100644
--- a/mailman/docs/chains.txt
+++ b/mailman/docs/chains.txt
@@ -36,7 +36,7 @@ The Discard chain simply throws the message away.
... An important message.
... """)
- >>> from mailman.app.chains import process
+ >>> from mailman.core.chains import process
# XXX This checks the vette log file because there is no other evidence
# that this chain has done anything.
@@ -309,7 +309,6 @@ The previously created message is innocuous enough that it should pass through
all default rules. This message will end up in the pipeline queue.
>>> file_pos = fp.tell()
- >>> from mailman.app.chains import process
>>> process(mlist, msg, {})
>>> fp.seek(file_pos)
>>> print 'LOG:', fp.read()
diff --git a/mailman/docs/lifecycle.txt b/mailman/docs/lifecycle.txt
index e5b0f726c..69f656208 100644
--- a/mailman/docs/lifecycle.txt
+++ b/mailman/docs/lifecycle.txt
@@ -57,7 +57,7 @@ Start by registering a test style.
... # Applies to any test list
... if 'test' in mailing_list.fqdn_listname:
... styles.append(self)
- >>> from mailman.app.styles import style_manager
+ >>> from mailman.core.styles import style_manager
>>> style_manager.register(TestStyle())
Using the higher level interface for creating a list, applies all matching
diff --git a/mailman/docs/pipelines.txt b/mailman/docs/pipelines.txt
index c4f8488a1..ab6b99aa3 100644
--- a/mailman/docs/pipelines.txt
+++ b/mailman/docs/pipelines.txt
@@ -13,7 +13,7 @@ message once it's started.
>>> mlist.web_page_url = u'http://lists.example.com/archives/'
>>> mlist.pipeline
u'built-in'
- >>> from mailman.app.pipelines import process
+ >>> from mailman.core.pipelines import process
Processing a message
diff --git a/mailman/docs/styles.txt b/mailman/docs/styles.txt
index 88bdd9cf5..37b3b20e2 100644
--- a/mailman/docs/styles.txt
+++ b/mailman/docs/styles.txt
@@ -15,7 +15,7 @@ Let's start with a vanilla mailing list and a default style manager.
>>> from mailman.configuration import config
>>> mlist = config.db.list_manager.create(u'_xtest@example.com')
- >>> from mailman.app.styles import style_manager
+ >>> from mailman.core.styles import style_manager
The default style
diff --git a/mailman/initialize.py b/mailman/initialize.py
index fd6fd5b26..ea7c294c8 100644
--- a/mailman/initialize.py
+++ b/mailman/initialize.py
@@ -32,7 +32,7 @@ from zope.interface.verify import verifyObject
import mailman.configuration
import mailman.loginit
-from mailman.app.plugins import get_plugin
+from mailman.core.plugins import get_plugin
from mailman.interfaces import IDatabase
@@ -88,11 +88,11 @@ def initialize_2(debug=False):
mailman.configuration.config.db = database
# Initialize the rules and chains. Do the imports here so as to avoid
# circular imports.
- from mailman.archiving import initialize as initialize_archivers
- from mailman.app.chains import initialize as initialize_chains
- from mailman.app.rules import initialize as initialize_rules
- from mailman.app.pipelines import initialize as initialize_pipelines
from mailman.app.commands import initialize as initialize_commands
+ from mailman.archiving import initialize as initialize_archivers
+ from mailman.core.chains import initialize as initialize_chains
+ from mailman.core.pipelines import initialize as initialize_pipelines
+ from mailman.core.rules import initialize as initialize_rules
initialize_archivers()
initialize_rules()
initialize_chains()
diff --git a/mailman/pipeline/cook_headers.py b/mailman/pipeline/cook_headers.py
index 348380078..362c8ecf7 100644
--- a/mailman/pipeline/cook_headers.py
+++ b/mailman/pipeline/cook_headers.py
@@ -18,20 +18,22 @@
"""Cook a message's headers."""
__metaclass__ = type
-__all__ = ['CookHeaders']
+__all__ = [
+ 'CookHeaders',
+ ]
import re
-from email.Charset import Charset
-from email.Errors import HeaderParseError
-from email.Header import Header, decode_header, make_header
-from email.Utils import parseaddr, formataddr, getaddresses
+from email.charset import Charset
+from email.errors import HeaderParseError
+from email.header import Header, decode_header, make_header
+from email.utils import parseaddr, formataddr, getaddresses
from zope.interface import implements
from mailman import Utils
-from mailman.app.plugins import get_plugins
from mailman.configuration import config
+from mailman.core.plugins import get_plugins
from mailman.i18n import _
from mailman.interfaces import IHandler, Personalization, ReplyToMunging
from mailman.version import VERSION
diff --git a/mailman/pipeline/scrubber.py b/mailman/pipeline/scrubber.py
index a538de1e9..8e2b1503e 100644
--- a/mailman/pipeline/scrubber.py
+++ b/mailman/pipeline/scrubber.py
@@ -39,12 +39,13 @@ from mimetypes import guess_all_extensions
from zope.interface import implements
from mailman import Utils
-from mailman.app.plugins import get_plugin
from mailman.configuration import config
from mailman.core.errors import DiscardMessage
+from mailman.core.plugins import get_plugin
from mailman.i18n import _
from mailman.interfaces import IHandler
+
# Path characters for common platforms
pre = re.compile(r'[/\\:]')
# All other characters to strip out of Content-Disposition: filenames
diff --git a/mailman/queue/archive.py b/mailman/queue/archive.py
index 32c49804f..a10ae3e9c 100644
--- a/mailman/queue/archive.py
+++ b/mailman/queue/archive.py
@@ -32,8 +32,8 @@ from datetime import datetime
from email.Utils import parsedate_tz, mktime_tz, formatdate
from locknix.lockfile import Lock
-from mailman.app.plugins import get_plugins
from mailman.configuration import config
+from mailman.core.plugins import get_plugins
from mailman.queue import Runner
diff --git a/mailman/queue/incoming.py b/mailman/queue/incoming.py
index 635b1833a..d4decd435 100644
--- a/mailman/queue/incoming.py
+++ b/mailman/queue/incoming.py
@@ -26,8 +26,8 @@ prepared for delivery. Rejections, discards, and holds are processed
immediately.
"""
-from mailman.app.chains import process
from mailman.configuration import config
+from mailman.core.chains import process
from mailman.queue import Runner
diff --git a/mailman/rules/docs/emergency.txt b/mailman/rules/docs/emergency.txt
index 56eceaccb..6437ba626 100644
--- a/mailman/rules/docs/emergency.txt
+++ b/mailman/rules/docs/emergency.txt
@@ -19,7 +19,7 @@ list are held for moderator approval.
The emergency rule is matched as part of the built-in chain. The emergency
rule matches if the flag is set on the mailing list.
- >>> from mailman.app.chains import process
+ >>> from mailman.core.chains import process
>>> mlist.emergency = True
>>> process(mlist, msg, {}, 'built-in')
diff --git a/mailman/rules/docs/header-matching.txt b/mailman/rules/docs/header-matching.txt
index 78554526a..0dd917a71 100644
--- a/mailman/rules/docs/header-matching.txt
+++ b/mailman/rules/docs/header-matching.txt
@@ -28,7 +28,7 @@ the chain untouched (i.e. no disposition).
... This is a message.
... """)
- >>> from mailman.app.chains import process
+ >>> from mailman.core.chains import process
Pass through is seen as nothing being in the log file after processing.
diff --git a/mailman/tests/test_documentation.py b/mailman/tests/test_documentation.py
index ff3dd8378..cf53be245 100644
--- a/mailman/tests/test_documentation.py
+++ b/mailman/tests/test_documentation.py
@@ -27,8 +27,8 @@ from email import message_from_string
import mailman
from mailman.Message import Message
-from mailman.app.styles import style_manager
from mailman.configuration import config
+from mailman.core.styles import style_manager
from mailman.testing.helpers import SMTPServer
diff --git a/setup.py b/setup.py
index 60ffef3bf..42c204039 100644
--- a/setup.py
+++ b/setup.py
@@ -101,7 +101,7 @@ case second `m'. Any other spelling is incorrect.""",
'mailman.commands' : list(commands),
'mailman.database' : 'stock = mailman.database:StockDatabase',
'mailman.mta' : 'stock = mailman.MTA:Manual',
- 'mailman.styles' : 'default = mailman.app.styles:DefaultStyle',
+ 'mailman.styles' : 'default = mailman.core.styles:DefaultStyle',
'mailman.mta' : 'stock = mailman.MTA:Manual',
'mailman.rules' : 'default = mailman.rules:initialize',
'mailman.handlers' : 'default = mailman.pipeline:initialize',
--
cgit v1.2.3-70-g09d2