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