From fd7c113707d9df9174957d2922d2d4484826145b Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Tue, 15 Mar 2011 17:54:57 -0400 Subject: Basic tests for Utils.py findtext(). This will go away, but for now it's useful for ensuring current functionality. --- src/mailman/utilities/tests/test_templates.py | 107 ++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 src/mailman/utilities/tests/test_templates.py (limited to 'src/mailman/utilities/tests/test_templates.py') diff --git a/src/mailman/utilities/tests/test_templates.py b/src/mailman/utilities/tests/test_templates.py new file mode 100644 index 000000000..446595108 --- /dev/null +++ b/src/mailman/utilities/tests/test_templates.py @@ -0,0 +1,107 @@ +# Copyright (C) 2011 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 . + +"""Testing i18n template search and interpolation.""" + +from __future__ import absolute_import, unicode_literals + +__metaclass__ = type +__all__ = [ + 'test_suite', + ] + + +import os +import shutil +import tempfile +import unittest + +from zope.component import getUtility + +from mailman.app.lifecycle import create_list +from mailman.config import config +from mailman.interfaces.languages import ILanguageManager +from mailman.testing.layers import ConfigLayer +#from mailman.utilities.i18n import find, make + +from mailman.Utils import findtext + + + +class TestFind(unittest.TestCase): + layer = ConfigLayer + + def setUp(self): + self.template_dir = tempfile.mkdtemp() + config.push('template config', """\ + [paths.testing] + template_dir: {0} + """.format(self.template_dir)) + # The following MUST happen AFTER the push() above since pushing a new + # config also clears out the language manager. + getUtility(ILanguageManager).add('xx', 'utf-8', 'Xlandia') + self.mlist = create_list('test@example.com') + self.mlist.preferred_language = 'xx' + # Populate global tempdir with a few fake templates. + self.xxdir = os.path.join(self.template_dir, 'xx') + os.mkdir(self.xxdir) + with open(os.path.join(self.xxdir, 'global.txt'), 'w') as fp: + print >> fp, 'Global template' + self.sitedir = os.path.join(self.template_dir, 'site', 'xx') + os.makedirs(self.sitedir) + with open(os.path.join(self.sitedir, 'site.txt'), 'w') as fp: + print >> fp, 'Site template' + self.domaindir = os.path.join(self.template_dir, 'example.com', 'xx') + os.makedirs(self.domaindir) + with open(os.path.join(self.domaindir, 'domain.txt'), 'w') as fp: + print >> fp, 'Domain template' + self.listdir = os.path.join(self.mlist.data_path, 'xx') + os.makedirs(self.listdir) + with open(os.path.join(self.listdir, 'list.txt'), 'w') as fp: + print >> fp, 'List template' + + def tearDown(self): + config.pop('template config') + shutil.rmtree(self.template_dir) + shutil.rmtree(self.listdir) + + def test_find_global_template(self): + text, filename = findtext('global.txt', lang='xx') + self.assertEqual(text, 'Global template\n') + self.assertEqual(filename, os.path.join(self.xxdir, 'global.txt')) + + def test_find_site_template(self): + text, filename = findtext('site.txt', lang='xx') + self.assertEqual(text, 'Site template\n') + self.assertEqual(filename, os.path.join(self.sitedir, 'site.txt')) + + def test_find_domain_template(self): + text, filename = findtext('domain.txt', mlist=self.mlist) + self.assertEqual(text, 'Domain template\n') + self.assertEqual(filename, os.path.join(self.domaindir, 'domain.txt')) + + def test_find_list_template(self): + text, filename = findtext('list.txt', mlist=self.mlist) + self.assertEqual(text, 'List template\n') + self.assertEqual(filename, os.path.join(self.listdir, 'list.txt')) + + + +def test_suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(TestFind)) + return suite -- cgit v1.2.3-70-g09d2 From 526ef44e83b876a1b9467ba511ec0d3c7d987d36 Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Wed, 16 Mar 2011 12:32:42 -0400 Subject: Checkpointing migration of findtext() and maketext() out of Utils.py. Tests added. TODO: flesh out make(). --- src/mailman/utilities/i18n.py | 128 ++++++++++++++++++++++- src/mailman/utilities/tests/test_templates.py | 141 +++++++++++++++++++++++--- 2 files changed, 254 insertions(+), 15 deletions(-) (limited to 'src/mailman/utilities/tests/test_templates.py') diff --git a/src/mailman/utilities/i18n.py b/src/mailman/utilities/i18n.py index 45262997b..a0f49a436 100644 --- a/src/mailman/utilities/i18n.py +++ b/src/mailman/utilities/i18n.py @@ -21,14 +21,138 @@ from __future__ import absolute_import, unicode_literals __metaclass__ = type __all__ = [ + 'TemplateNotFoundError', 'find', 'make', ] +import os +import errno + +from itertools import product + +from mailman.config import config +from mailman.core.constants import system_preferences + + -def find(): - pass +class TemplateNotFoundError(Exception): + """The named template was not found.""" + + def __init__(self, template_file): + self.template_file = template_file + + + +def _search(template_file, mailing_list=None, language=None): + """Generator that provides file system search order.""" + + languages = ['en', system_preferences.preferred_language.code] + if mailing_list is not None: + languages.append(mailing_list.preferred_language.code) + if language is not None: + languages.append(language) + languages.reverse() + # File system locations to search. + paths = [config.TEMPLATE_DIR, + os.path.join(config.TEMPLATE_DIR, 'site')] + if mailing_list is not None: + paths.append(os.path.join(config.TEMPLATE_DIR, + mailing_list.host_name)) + paths.append(os.path.join(config.LIST_DATA_DIR, + mailing_list.fqdn_listname)) + paths.reverse() + for language, path in product(languages, paths): + yield os.path.join(path, language, template_file) + + + +def find(template_file, mailing_list=None, language=None): + """Locate an i18n template file. + + When something in Mailman needs a template file, it always asks for the + file through this interface. The results of the search is path to the + 'matching' template, with the search order depending on whether + `mailing_list` and `language` are provided. + + When looking for a template in a specific language, there are 4 locations + that are searched, in this order: + + * The list-specific language directory + /lists// + + * The domain-specific language directory + // + + * The site-wide language directory + /site/ + + * The global default language directory + / + + The first match stops the search. In this way, you can specialize + templates at the desired level, or if you only use the default templates, + you don't need to change anything. NEVER modify files in + / since Mailman will overwrite these when you + upgrade. Instead you can use /site. + + The path component is calculated as follows, in this order: + + * The `language` parameter if given + * `mailing_list.preferred_language` if given + * The server's default language + * English ('en') + + Languages are iterated after each of the four locations are searched. So + for example, when searching for the 'foo.txt' template, where the server's + default language is 'fr', the mailing list's (test@example.com) language + is 'de' and the `language` parameter is 'it', these locations are searched + in order: + + * /lists/test@example.com/it/foo.txt + * /example.com/it/foo.txt + * /site/it/foo.txt + * /it/foo.txt + + * /lists/test@example.com/de/foo.txt + * /example.com/de/foo.txt + * /site/de/foo.txt + * /de/foo.txt + + * /lists/test@example.com/fr/foo.txt + * /example.com/fr/foo.txt + * /site/fr/foo.txt + * /fr/foo.txt + + * /lists/test@example.com/en/foo.txt + * /example.com/en/foo.txt + * /site/en/foo.txt + * /en/foo.txt + + :param template_file: The name of the template file to search for. + :type template_file: string + :param mailing_list: Optional mailing list used as the context for + searching for the template file. The list's preferred language will + influence the search, as will the list's data directory. + :type mailing_list: `IMailingList` + :param language: Optional language code, which influences the search. + :type language: string + :return: A tuple of the file system path to the first matching template, + and an open file object allowing reading of the file. + :rtype: (string, file) + :raises TemplateNotFoundError: when the template could not be found. + """ + raw_search_order = _search(template_file, mailing_list, language) + for path in raw_search_order: + try: + fp = open(path) + except IOError as error: + if error.errno != errno.ENOENT: + raise + else: + return path, fp + raise TemplateNotFoundError(template_file) def make(): diff --git a/src/mailman/utilities/tests/test_templates.py b/src/mailman/utilities/tests/test_templates.py index 446595108..23f99b60e 100644 --- a/src/mailman/utilities/tests/test_templates.py +++ b/src/mailman/utilities/tests/test_templates.py @@ -36,13 +36,116 @@ from mailman.app.lifecycle import create_list from mailman.config import config from mailman.interfaces.languages import ILanguageManager from mailman.testing.layers import ConfigLayer -#from mailman.utilities.i18n import find, make +from mailman.utilities.i18n import TemplateNotFoundError, _search, find, make from mailman.Utils import findtext + +class TestSearchOrder(unittest.TestCase): + """Test internal search order for language templates.""" + + layer = ConfigLayer + + def setUp(self): + self.template_dir = tempfile.mkdtemp() + config.push('no template dir', """\ + [mailman] + default_language: fr + [paths.testing] + template_dir: {0}/t + var_dir: {0}/v + """.format(self.template_dir)) + language_manager = getUtility(ILanguageManager) + language_manager.add('de', 'utf-8', 'German') + language_manager.add('it', 'utf-8', 'Italian') + self.mlist = create_list('l@example.com') + self.mlist.preferred_language = 'de' + + def tearDown(self): + config.pop('no template dir') + shutil.rmtree(self.template_dir) + + def _stripped_search_order(self, template_file, + mailing_list=None, language=None): + raw_search_order = _search(template_file, mailing_list, language) + for path in raw_search_order: + yield path[len(self.template_dir):] + + def test_fully_specified_search_order(self): + search_order = self._stripped_search_order('foo.txt', self.mlist, 'it') + # language argument + self.assertEqual(next(search_order), + '/v/lists/l@example.com/it/foo.txt') + self.assertEqual(next(search_order), '/t/example.com/it/foo.txt') + self.assertEqual(next(search_order), '/t/site/it/foo.txt') + self.assertEqual(next(search_order), '/t/it/foo.txt') + # mlist.preferred_language + self.assertEqual(next(search_order), + '/v/lists/l@example.com/de/foo.txt') + self.assertEqual(next(search_order), '/t/example.com/de/foo.txt') + self.assertEqual(next(search_order), '/t/site/de/foo.txt') + self.assertEqual(next(search_order), '/t/de/foo.txt') + # site's default language + self.assertEqual(next(search_order), + '/v/lists/l@example.com/fr/foo.txt') + self.assertEqual(next(search_order), '/t/example.com/fr/foo.txt') + self.assertEqual(next(search_order), '/t/site/fr/foo.txt') + self.assertEqual(next(search_order), '/t/fr/foo.txt') + # English + self.assertEqual(next(search_order), + '/v/lists/l@example.com/en/foo.txt') + self.assertEqual(next(search_order), '/t/example.com/en/foo.txt') + self.assertEqual(next(search_order), '/t/site/en/foo.txt') + self.assertEqual(next(search_order), '/t/en/foo.txt') + + def test_no_language_argument_search_order(self): + search_order = self._stripped_search_order('foo.txt', self.mlist) + # mlist.preferred_language + self.assertEqual(next(search_order), + '/v/lists/l@example.com/de/foo.txt') + self.assertEqual(next(search_order), '/t/example.com/de/foo.txt') + self.assertEqual(next(search_order), '/t/site/de/foo.txt') + self.assertEqual(next(search_order), '/t/de/foo.txt') + # site's default language + self.assertEqual(next(search_order), + '/v/lists/l@example.com/fr/foo.txt') + self.assertEqual(next(search_order), '/t/example.com/fr/foo.txt') + self.assertEqual(next(search_order), '/t/site/fr/foo.txt') + self.assertEqual(next(search_order), '/t/fr/foo.txt') + # English + self.assertEqual(next(search_order), + '/v/lists/l@example.com/en/foo.txt') + self.assertEqual(next(search_order), '/t/example.com/en/foo.txt') + self.assertEqual(next(search_order), '/t/site/en/foo.txt') + self.assertEqual(next(search_order), '/t/en/foo.txt') + + def test_no_mailing_list_argument_search_order(self): + search_order = self._stripped_search_order('foo.txt', language='it') + # language argument + self.assertEqual(next(search_order), '/t/site/it/foo.txt') + self.assertEqual(next(search_order), '/t/it/foo.txt') + # site's default language + self.assertEqual(next(search_order), '/t/site/fr/foo.txt') + self.assertEqual(next(search_order), '/t/fr/foo.txt') + # English + self.assertEqual(next(search_order), '/t/site/en/foo.txt') + self.assertEqual(next(search_order), '/t/en/foo.txt') + + def test_no_optional_arguments_search_order(self): + search_order = self._stripped_search_order('foo.txt') + # site's default language + self.assertEqual(next(search_order), '/t/site/fr/foo.txt') + self.assertEqual(next(search_order), '/t/fr/foo.txt') + # English + self.assertEqual(next(search_order), '/t/site/en/foo.txt') + self.assertEqual(next(search_order), '/t/en/foo.txt') + + class TestFind(unittest.TestCase): + """Test template search.""" + layer = ConfigLayer def setUp(self): @@ -56,52 +159,64 @@ class TestFind(unittest.TestCase): getUtility(ILanguageManager).add('xx', 'utf-8', 'Xlandia') self.mlist = create_list('test@example.com') self.mlist.preferred_language = 'xx' + self.fp = None # Populate global tempdir with a few fake templates. self.xxdir = os.path.join(self.template_dir, 'xx') os.mkdir(self.xxdir) with open(os.path.join(self.xxdir, 'global.txt'), 'w') as fp: - print >> fp, 'Global template' + fp.write('Global template') self.sitedir = os.path.join(self.template_dir, 'site', 'xx') os.makedirs(self.sitedir) with open(os.path.join(self.sitedir, 'site.txt'), 'w') as fp: - print >> fp, 'Site template' + fp.write('Site template') self.domaindir = os.path.join(self.template_dir, 'example.com', 'xx') os.makedirs(self.domaindir) with open(os.path.join(self.domaindir, 'domain.txt'), 'w') as fp: - print >> fp, 'Domain template' + fp.write('Domain template') self.listdir = os.path.join(self.mlist.data_path, 'xx') os.makedirs(self.listdir) with open(os.path.join(self.listdir, 'list.txt'), 'w') as fp: - print >> fp, 'List template' + fp.write('List template') def tearDown(self): + if self.fp is not None: + self.fp.close() config.pop('template config') shutil.rmtree(self.template_dir) shutil.rmtree(self.listdir) def test_find_global_template(self): - text, filename = findtext('global.txt', lang='xx') - self.assertEqual(text, 'Global template\n') + filename, self.fp = find('global.txt', language='xx') self.assertEqual(filename, os.path.join(self.xxdir, 'global.txt')) + self.assertEqual(self.fp.read(), 'Global template') def test_find_site_template(self): - text, filename = findtext('site.txt', lang='xx') - self.assertEqual(text, 'Site template\n') + filename, self.fp = find('site.txt', language='xx') self.assertEqual(filename, os.path.join(self.sitedir, 'site.txt')) + self.assertEqual(self.fp.read(), 'Site template') def test_find_domain_template(self): - text, filename = findtext('domain.txt', mlist=self.mlist) - self.assertEqual(text, 'Domain template\n') + filename, self.fp = find('domain.txt', self.mlist) self.assertEqual(filename, os.path.join(self.domaindir, 'domain.txt')) + self.assertEqual(self.fp.read(), 'Domain template') def test_find_list_template(self): - text, filename = findtext('list.txt', mlist=self.mlist) - self.assertEqual(text, 'List template\n') + filename, self.fp = find('list.txt', self.mlist) self.assertEqual(filename, os.path.join(self.listdir, 'list.txt')) + self.assertEqual(self.fp.read(), 'List template') + def test_template_not_found(self): + # Python 2.6 compatibility. + try: + find('missing.txt', self.mlist) + except TemplateNotFoundError as error: + self.assertEqual(error.template_file, 'missing.txt') + else: + raise AssertionError('TemplateNotFoundError expected') def test_suite(): suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(TestSearchOrder)) suite.addTest(unittest.makeSuite(TestFind)) return suite -- cgit v1.2.3-70-g09d2 From 8f51ea23f187707f92e4ed689ef5ccb0fe292427 Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Wed, 16 Mar 2011 14:28:26 -0400 Subject: make() and some tests. --- src/mailman/utilities/i18n.py | 43 +++++++++++++++- src/mailman/utilities/tests/test_templates.py | 74 ++++++++++++++++++++++++++- 2 files changed, 113 insertions(+), 4 deletions(-) (limited to 'src/mailman/utilities/tests/test_templates.py') diff --git a/src/mailman/utilities/i18n.py b/src/mailman/utilities/i18n.py index a0f49a436..bb826e853 100644 --- a/src/mailman/utilities/i18n.py +++ b/src/mailman/utilities/i18n.py @@ -31,9 +31,14 @@ import os import errno from itertools import product +from zope.component import getUtility from mailman.config import config from mailman.core.constants import system_preferences +from mailman.interfaces.languages import ILanguageManager +from mailman.utilities.string import expand + +from mailman.Utils import wrap as wrap_text @@ -155,5 +160,39 @@ def find(template_file, mailing_list=None, language=None): raise TemplateNotFoundError(template_file) -def make(): - pass +def make(template_file, mailing_list=None, language=None, wrap=True, **kw): + """Locate and 'make' a template file. + + The template file is located as with `find()`, and the resulting text is + optionally wrapped and interpolated with the keyword argument dictionary. + + :param template_file: The name of the template file to search for. + :type template_file: string + :param mailing_list: Optional mailing list used as the context for + searching for the template file. The list's preferred language will + influence the search, as will the list's data directory. + :type mailing_list: `IMailingList` + :param language: Optional language code, which influences the search. + :type language: string + :param wrap: When True, wrap the text. + :type wrap: bool + :param **kw: Keyword arguments for template interpolation. + :return: A tuple of the file system path to the first matching template, + and an open file object allowing reading of the file. + :rtype: (string, file) + :raises TemplateNotFoundError: when the template could not be found. + """ + path, fp = find(template_file, mailing_list, language) + try: + raw_text = fp.read() + finally: + fp.close() + # The language is always the second to last path component. + parts = path.split(os.sep) + language_code = parts[-2] + charset = getUtility(ILanguageManager)[language_code].charset + template = unicode(raw_text, charset, 'replace') + text = expand(template, kw) + if wrap: + return wrap_text(text) + return text diff --git a/src/mailman/utilities/tests/test_templates.py b/src/mailman/utilities/tests/test_templates.py index 23f99b60e..b95b181f7 100644 --- a/src/mailman/utilities/tests/test_templates.py +++ b/src/mailman/utilities/tests/test_templates.py @@ -38,8 +38,6 @@ from mailman.interfaces.languages import ILanguageManager from mailman.testing.layers import ConfigLayer from mailman.utilities.i18n import TemplateNotFoundError, _search, find, make -from mailman.Utils import findtext - class TestSearchOrder(unittest.TestCase): @@ -214,9 +212,81 @@ class TestFind(unittest.TestCase): else: raise AssertionError('TemplateNotFoundError expected') + + +class TestMake(unittest.TestCase): + """Test template interpolation.""" + + layer = ConfigLayer + + def setUp(self): + self.template_dir = tempfile.mkdtemp() + config.push('template config', """\ + [paths.testing] + template_dir: {0} + """.format(self.template_dir)) + # The following MUST happen AFTER the push() above since pushing a new + # config also clears out the language manager. + getUtility(ILanguageManager).add('xx', 'utf-8', 'Xlandia') + self.mlist = create_list('test@example.com') + self.mlist.preferred_language = 'xx' + # Populate the template directory with some samples. + self.xxdir = os.path.join(self.template_dir, 'xx') + os.mkdir(self.xxdir) + with open(os.path.join(self.xxdir, 'nosub.txt'), 'w') as fp: + print >> fp, """\ +This is a global template. +It has no substitutions. +It will be wrapped. +""" + with open(os.path.join(self.xxdir, 'subs.txt'), 'w') as fp: + print >> fp, """\ +This is a $kind template. +It has $howmany substitutions. +It will be wrapped. +""" + with open(os.path.join(self.xxdir, 'nowrap.txt'), 'w') as fp: + print >> fp, """\ +This is a $kind template. +It has $howmany substitutions. +It will not be wrapped. +""" + + def tearDown(self): + config.pop('template config') + shutil.rmtree(self.template_dir) + + def test_no_substitutions(self): + self.assertEqual(make('nosub.txt', self.mlist), """\ +This is a global template. It has no substitutions. It will be +wrapped. + +""") + + def test_substitutions(self): + self.assertEqual(make('subs.txt', self.mlist, + kind='very nice', + howmany='a few'), """\ +This is a very nice template. It has a few substitutions. It will be +wrapped. + +""") + + def test_substitutions_no_wrap(self): + self.assertEqual(make('nowrap.txt', self.mlist, wrap=False, + kind='very nice', + howmany='a few'), """\ +This is a very nice template. +It has a few substitutions. +It will not be wrapped. + +""") + + def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestSearchOrder)) suite.addTest(unittest.makeSuite(TestFind)) + suite.addTest(unittest.makeSuite(TestMake)) return suite -- cgit v1.2.3-70-g09d2 From e2e7e4a3e43a1a7fcf9f909f0d0aaaeedf27f3fa Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Wed, 16 Mar 2011 15:14:48 -0400 Subject: Utils.maketext() and Utils.findtext() are gone. --- src/mailman/Archiver/Archiver.py | 12 +-- src/mailman/Archiver/HyperArch.py | 5 +- src/mailman/Utils.py | 128 -------------------------- src/mailman/app/membership.py | 12 +-- src/mailman/app/moderator.py | 40 ++++---- src/mailman/app/notifications.py | 36 ++++---- src/mailman/chains/hold.py | 33 ++++--- src/mailman/commands/cli_lists.py | 4 +- src/mailman/commands/docs/info.txt | 1 + src/mailman/pipeline/acknowledge.py | 18 ++-- src/mailman/queue/digest.py | 20 ++-- src/mailman/utilities/i18n.py | 10 +- src/mailman/utilities/tests/test_templates.py | 3 - 13 files changed, 100 insertions(+), 222 deletions(-) (limited to 'src/mailman/utilities/tests/test_templates.py') diff --git a/src/mailman/Archiver/Archiver.py b/src/mailman/Archiver/Archiver.py index f8d1baa46..fe1dec252 100644 --- a/src/mailman/Archiver/Archiver.py +++ b/src/mailman/Archiver/Archiver.py @@ -32,9 +32,9 @@ from cStringIO import StringIO from string import Template from zope.component import getUtility -from mailman import Utils from mailman.config import config from mailman.interfaces.domain import IDomainManager +from mailman.utilities.i18n import make log = logging.getLogger('mailman.error') @@ -111,11 +111,11 @@ class Archiver: fp = open(indexfile, 'w') finally: os.umask(omask) - fp.write(Utils.maketext( - 'emptyarchive.html', - {'listname': self.real_name, - 'listinfo': self.GetScriptURL('listinfo'), - }, mlist=self)) + fp.write(make('emptyarchive.html', + mailing_list=self, + listname=self.real_name, + listinfo=self.GetScriptURL('listinfo'), + )) if fp: fp.close() finally: diff --git a/src/mailman/Archiver/HyperArch.py b/src/mailman/Archiver/HyperArch.py index 218c46875..92b9de2f0 100644 --- a/src/mailman/Archiver/HyperArch.py +++ b/src/mailman/Archiver/HyperArch.py @@ -50,6 +50,7 @@ from mailman.Archiver import pipermail from mailman.config import config from mailman.core.i18n import _, ctime from mailman.interfaces.listmanager import IListManager +from mailman.utilities.i18n import find from mailman.utilities.string import uncanonstr, websafe @@ -183,8 +184,8 @@ def quick_maketext(templatefile, dict=None, lang=None, mlist=None): template = _templatecache.get(filepath) if filepath is None or template is None: # Use the basic maketext, with defaults to get the raw template - template, filepath = Utils.findtext(templatefile, lang=lang.code, - raw=True, mlist=mlist) + template, filepath = find(templatefile, mailing_list=mlist, + language=lang.code) _templatefilepathcache[cachekey] = filepath _templatecache[filepath] = template # Copied from Utils.maketext() diff --git a/src/mailman/Utils.py b/src/mailman/Utils.py index a26025fa3..cfd61eb7a 100644 --- a/src/mailman/Utils.py +++ b/src/mailman/Utils.py @@ -31,20 +31,13 @@ __all__ = [ import os import re -import errno import logging # pylint: disable-msg=E0611,W0403 from string import ascii_letters, digits, whitespace -from zope.component import getUtility import mailman.templates -from mailman.config import config -from mailman.core.i18n import _ -from mailman.interfaces.languages import ILanguageManager -from mailman.utilities.string import expand - AT = '@' CR = '\r' @@ -139,124 +132,3 @@ def wrap(text, column=70, honor_leading_ws=True): # end for text in lines # the last two newlines are bogus return wrapped[:-2] - - - -class OuterExit(Exception): - pass - -def findtext(templatefile, raw_dict=None, raw=False, lang=None, mlist=None): - # Make some text from a template file. The order of searches depends on - # whether mlist and lang are provided. Once the templatefile is found, - # string substitution is performed by interpolation in `dict'. If `raw' - # is false, the resulting text is wrapped/filled by calling wrap(). - # - # When looking for a template in a specific language, there are 4 places - # that are searched, in this order: - # - # 1. the list-specific language directory - # lists// - # - # 2. the domain-specific language directory - # templates// - # - # 3. the site-wide language directory - # templates/site/ - # - # 4. the global default language directory - # templates/ - # - # The first match found stops the search. In this way, you can specialize - # templates at the desired level, or, if you use only the default - # templates, you don't need to change anything. You should never modify - # files in the templates/ subdirectory, since Mailman will - # overwrite these when you upgrade. That's what the templates/site - # language directories are for. - # - # A further complication is that the language to search for is determined - # by both the `lang' and `mlist' arguments. The search order there is - # that if lang is given, then the 4 locations above are searched, - # substituting lang for . If no match is found, and mlist is - # given, then the 4 locations are searched using the list's preferred - # language. After that, the server default language is used for - # . If that still doesn't yield a template, then the standard - # distribution's English language template is used as an ultimate - # fallback, and when lang is not 'en', the resulting template is passed - # through the translation service. If this template is missing you've got - # big problems. ;) - # - # A word on backwards compatibility: Mailman versions prior to 2.1 stored - # templates in templates/*.{html,txt} and lists//*.{html,txt}. - # Those directories are no longer searched so if you've got customizations - # in those files, you should move them to the appropriate directory based - # on the above description. Mailman's upgrade script cannot do this for - # you. - # - # The function has been revised and renamed as it now returns both the - # template text and the path from which it retrieved the template. The - # original function is now a wrapper which just returns the template text - # as before, by calling this renamed function and discarding the second - # item returned. - # - # Calculate the languages to scan - languages = set() - if lang is not None: - languages.add(lang) - if mlist is not None: - languages.add(mlist.preferred_language.code) - languages.add(config.mailman.default_language) - assert None not in languages, 'None in languages' - # Calculate the locations to scan - searchdirs = [] - if mlist is not None: - searchdirs.append(mlist.data_path) - searchdirs.append(os.path.join(config.TEMPLATE_DIR, mlist.host_name)) - searchdirs.append(os.path.join(config.TEMPLATE_DIR, 'site')) - searchdirs.append(config.TEMPLATE_DIR) - # Start scanning - fp = None - try: - for lang in languages: - for dir in searchdirs: - filename = os.path.join(dir, lang, templatefile) - try: - fp = open(filename) - raise OuterExit - except IOError, e: - if e.errno != errno.ENOENT: - raise - # Okay, it doesn't exist, keep looping - fp = None - except OuterExit: - pass - if fp is None: - # Try one last time with the distro English template, which, unless - # you've got a really broken installation, must be there. - try: - filename = os.path.join(TEMPLATE_DIR, 'en', templatefile) - fp = open(filename) - except IOError, e: - if e.errno != errno.ENOENT: - raise - # We never found the template. BAD! - raise IOError(errno.ENOENT, 'No template file found', templatefile) - else: - # XXX BROKEN HACK - data = fp.read()[:-1] - template = _(data) - fp.close() - else: - template = fp.read() - fp.close() - charset = getUtility(ILanguageManager)[lang].charset - template = unicode(template, charset, 'replace') - text = template - if raw_dict is not None: - text = expand(template, raw_dict) - if raw: - return text, filename - return wrap(text), filename - - -def maketext(templatefile, dict=None, raw=False, lang=None, mlist=None): - return findtext(templatefile, dict, raw, lang, mlist)[0] diff --git a/src/mailman/app/membership.py b/src/mailman/app/membership.py index 8723fd781..fcbedc2f5 100644 --- a/src/mailman/app/membership.py +++ b/src/mailman/app/membership.py @@ -29,7 +29,6 @@ __all__ = [ from email.utils import formataddr from zope.component import getUtility -from mailman import Utils from mailman.app.notifications import send_goodbye_message from mailman.core.i18n import _ from mailman.email.message import OwnerNotification @@ -39,6 +38,7 @@ from mailman.interfaces.member import ( AlreadySubscribedError, MemberRole, MembershipIsBannedError, NotAMemberError) from mailman.interfaces.usermanager import IUserManager +from mailman.utilities.i18n import make @@ -149,10 +149,10 @@ def delete_member(mlist, address, admin_notif=None, userack=None): user = getUtility(IUserManager).get_user(address) realname = user.real_name subject = _('$mlist.real_name unsubscription notification') - text = Utils.maketext( - 'adminunsubscribeack.txt', - {'listname': mlist.real_name, - 'member' : formataddr((realname, address)), - }, mlist=mlist) + text = make('adminunsubscribeack.txt', + mailing_list=mlist, + listname=mlist.real_name, + member=formataddr((realname, address)), + ) msg = OwnerNotification(mlist, subject, text) msg.send(mlist) diff --git a/src/mailman/app/moderator.py b/src/mailman/app/moderator.py index cdfedd44b..50a03c833 100644 --- a/src/mailman/app/moderator.py +++ b/src/mailman/app/moderator.py @@ -48,6 +48,7 @@ from mailman.interfaces.member import ( AlreadySubscribedError, DeliveryMode, NotAMemberError) from mailman.interfaces.messages import IMessageStore from mailman.interfaces.requests import IRequests, RequestType +from mailman.utilities.i18n import make NL = '\n' @@ -209,12 +210,12 @@ def hold_subscription(mlist, address, realname, password, mode, language): if mlist.admin_immed_notify: subject = _( 'New subscription request to list $mlist.real_name from $address') - text = Utils.maketext( - 'subauth.txt', - {'username' : address, - 'listname' : mlist.fqdn_listname, - 'admindb_url': mlist.script_url('admindb'), - }, mlist=mlist) + text = make('subauth.txt', + mailing_list=mlist, + username=address, + listname=mlist.fqdn_listname, + admindb_url=mlist.script_url('admindb'), + ) # This message should appear to come from the -owner so as # to avoid any useless bounce processing. msg = UserNotification( @@ -281,12 +282,12 @@ def hold_unsubscription(mlist, address): if mlist.admin_immed_notify: subject = _( 'New unsubscription request from $mlist.real_name by $address') - text = Utils.maketext( - 'unsubauth.txt', - {'address' : address, - 'listname' : mlist.fqdn_listname, - 'admindb_url': mlist.script_url('admindb'), - }, mlist=mlist) + text = make('unsubauth.txt', + mailing_list=mlist, + address=address, + listname=mlist.fqdn_listname, + admindb_url=mlist.script_url('admindb'), + ) # This message should appear to come from the -owner so as # to avoid any useless bounce processing. msg = UserNotification( @@ -336,13 +337,14 @@ def _refuse(mlist, request, recip, comment, origmsg=None, lang=None): lang = (mlist.preferred_language if member is None else member.preferred_language) - text = Utils.maketext( - 'refuse.txt', - {'listname' : mlist.fqdn_listname, - 'request' : request, - 'reason' : comment, - 'adminaddr': mlist.owner_address, - }, lang=lang.code, mlist=mlist) + text = make('refuse.txt', + mailing_list=mlist, + language=lang.code, + listname=mlist.fqdn_listname, + request=request, + reason=comment, + adminaddr=mlist.owner_address, + ) with _.using(lang.code): # add in original message, but not wrap/filled if origmsg: diff --git a/src/mailman/app/notifications.py b/src/mailman/app/notifications.py index 985f4eece..d7e64a020 100644 --- a/src/mailman/app/notifications.py +++ b/src/mailman/app/notifications.py @@ -30,11 +30,12 @@ __all__ = [ from email.utils import formataddr from lazr.config import as_boolean -from mailman import Utils +from mailman.Utils import wrap from mailman.config import config from mailman.core.i18n import _ from mailman.email.message import OwnerNotification, UserNotification from mailman.interfaces.member import DeliveryMode +from mailman.utilities.i18n import make @@ -54,7 +55,7 @@ def send_welcome_message(mlist, address, language, delivery_mode, text=''): :type delivery_mode: DeliveryMode """ if mlist.welcome_msg: - welcome = Utils.wrap(mlist.welcome_msg) + '\n' + welcome = wrap(mlist.welcome_msg) + '\n' else: welcome = '' # Find the IMember object which is subscribed to the mailing list, because @@ -62,15 +63,16 @@ def send_welcome_message(mlist, address, language, delivery_mode, text=''): member = mlist.members.get_member(address) options_url = member.options_url # Get the text from the template. - text += Utils.maketext( - 'subscribeack.txt', { - 'real_name' : mlist.real_name, - 'posting_address' : mlist.fqdn_listname, - 'listinfo_url' : mlist.script_url('listinfo'), - 'optionsurl' : options_url, - 'request_address' : mlist.request_address, - 'welcome' : welcome, - }, lang=language.code, mlist=mlist) + text += make('subscribeack.txt', + mailing_list=mlist, + language=language.code, + real_name=mlist.real_name, + posting_address=mlist.fqdn_listname, + listinfo_url=mlist.script_url('listinfo'), + optionsurl=options_url, + request_address=mlist.request_address, + welcome=welcome, + ) if delivery_mode is not DeliveryMode.regular: digmode = _(' (Digest mode)') else: @@ -98,7 +100,7 @@ def send_goodbye_message(mlist, address, language): :type language: string """ if mlist.goodbye_msg: - goodbye = Utils.wrap(mlist.goodbye_msg) + '\n' + goodbye = wrap(mlist.goodbye_msg) + '\n' else: goodbye = '' msg = UserNotification( @@ -124,10 +126,10 @@ def send_admin_subscription_notice(mlist, address, full_name, language): with _.using(mlist.preferred_language.code): subject = _('$mlist.real_name subscription notification') full_name = full_name.encode(language.charset, 'replace') - text = Utils.maketext( - 'adminsubscribeack.txt', - {'listname' : mlist.real_name, - 'member' : formataddr((full_name, address)), - }, mlist=mlist) + text = make('adminsubscribeack.txt', + mailing_list=mlist, + listname=mlist.real_name, + member=formataddr((full_name, address)), + ) msg = OwnerNotification(mlist, subject, text) msg.send(mlist) diff --git a/src/mailman/chains/hold.py b/src/mailman/chains/hold.py index ff53621ab..fcd11ca72 100644 --- a/src/mailman/chains/hold.py +++ b/src/mailman/chains/hold.py @@ -35,7 +35,7 @@ from zope.component import getUtility from zope.event import notify from zope.interface import implements -from mailman.Utils import maketext, wrap +from mailman.Utils import wrap from mailman.app.moderator import hold_message from mailman.app.replybot import can_acknowledge from mailman.chains.base import ChainNotification, TerminalChainBase @@ -46,6 +46,7 @@ from mailman.interfaces.autorespond import IAutoResponseSet, Response from mailman.interfaces.languages import ILanguageManager from mailman.interfaces.pending import IPendable, IPendings from mailman.interfaces.usermanager import IUserManager +from mailman.utilities.i18n import make from mailman.utilities.string import oneline @@ -103,14 +104,13 @@ def autorespond_to_sender(mlist, sender, lang=None): log.info('hold autoresponse limit hit: %s', sender) response_set.response_sent(address, Response.hold) # Send this notification message instead. - text = maketext( - 'nomoretoday.txt', - {'sender' : sender, - 'listname': mlist.fqdn_listname, - 'num' : todays_count, - 'owneremail': mlist.owner_address, - }, - lang=lang) + text = make('nomoretoday.txt', + language=lang, + sender=sender, + listname=mlist.fqdn_listname, + num=todays_count, + owneremail=mlist.owner_address, + ) with _.using(lang.code): msg = UserNotification( sender, mlist.owner_address, @@ -194,8 +194,10 @@ class HoldChain(TerminalChainBase): subject = _( 'Your message to $mlist.fqdn_listname awaits moderator approval') send_language_code = msgdata.get('lang', language.code) - text = maketext('postheld.txt', substitutions, - lang=send_language_code, mlist=mlist) + text = make('postheld.txt', + mailing_list=mlist, + language=send_language_code, + **substitutions) adminaddr = mlist.bounces_address nmsg = UserNotification( msg.sender, adminaddr, subject, text, @@ -222,10 +224,11 @@ class HoldChain(TerminalChainBase): mlist.owner_address, subject, lang=language) nmsg.set_type('multipart/mixed') - text = MIMEText( - maketext('postauth.txt', substitutions, - raw=True, mlist=mlist), - _charset=charset) + text = MIMEText(make('postauth.txt', + mailing_list=mlist, + wrap=False, + **substitutions), + _charset=charset) dmsg = MIMEText(wrap(_("""\ If you reply to this message, keeping the Subject: header intact, Mailman will discard the held message. Do this if the message is spam. If you reply to diff --git a/src/mailman/commands/cli_lists.py b/src/mailman/commands/cli_lists.py index a127dd816..021ce5991 100644 --- a/src/mailman/commands/cli_lists.py +++ b/src/mailman/commands/cli_lists.py @@ -30,7 +30,6 @@ __all__ = [ from zope.component import getUtility from zope.interface import implements -from mailman.Utils import maketext from mailman.app.lifecycle import create_list, remove_list from mailman.config import config from mailman.core.constants import system_preferences @@ -42,6 +41,7 @@ from mailman.interfaces.domain import ( BadDomainSpecificationError, IDomainManager) from mailman.interfaces.languages import ILanguageManager from mailman.interfaces.listmanager import IListManager, ListAlreadyExistsError +from mailman.utilities.i18n import make @@ -213,7 +213,7 @@ class Create: requestaddr = mlist.request_address, siteowner = mlist.no_reply_address, ) - text = maketext('newlist.txt', d, mlist=mlist) + text = make('newlist.txt', mailing_list=mlist, **d) # Set the I18N language to the list's preferred language so the # header will match the template language. Stashing and restoring # the old translation context is just (healthy? :) paranoia. diff --git a/src/mailman/commands/docs/info.txt b/src/mailman/commands/docs/info.txt index bccb78fda..12fce3223 100644 --- a/src/mailman/commands/docs/info.txt +++ b/src/mailman/commands/docs/info.txt @@ -74,6 +74,7 @@ The File System Hierarchy layout is the same every by definition. PUBLIC_ARCHIVE_FILE_DIR = /var/lib/mailman/archives/public QUEUE_DIR = /var/spool/mailman SITE_PW_FILE = /var/lib/mailman/data/adm.pw + TEMPLATE_DIR = .../mailman/templates VAR_DIR = /var/lib/mailman diff --git a/src/mailman/pipeline/acknowledge.py b/src/mailman/pipeline/acknowledge.py index fcde6d6a5..e5b49ffa0 100644 --- a/src/mailman/pipeline/acknowledge.py +++ b/src/mailman/pipeline/acknowledge.py @@ -31,11 +31,11 @@ __all__ = [ from zope.component import getUtility from zope.interface import implements -from mailman import Utils from mailman.core.i18n import _ from mailman.email.message import UserNotification from mailman.interfaces.handler import IHandler from mailman.interfaces.languages import ILanguageManager +from mailman.utilities.i18n import make from mailman.utilities.string import oneline @@ -71,13 +71,15 @@ class Acknowledge: charset = language_manager[language.code].charset # Now get the acknowledgement template. realname = mlist.real_name - text = Utils.maketext( - 'postack.txt', - {'subject' : oneline(original_subject, charset), - 'listname' : realname, - 'listinfo_url': mlist.script_url('listinfo'), - 'optionsurl' : member.options_url, - }, lang=language.code, mlist=mlist, raw=True) + text = make('postack.txt', + mailing_list=mlist, + language=language.code, + wrap=False, + subject=oneline(original_subject, charset), + listname=realname, + listinfo_url=mlist.script_url('listinfo'), + optionsurl=member.options_url, + ) # Craft the outgoing message, with all headers and attributes # necessary for general delivery. Then enqueue it to the outgoing # queue. diff --git a/src/mailman/queue/digest.py b/src/mailman/queue/digest.py index a2feff448..2541e14ed 100644 --- a/src/mailman/queue/digest.py +++ b/src/mailman/queue/digest.py @@ -38,7 +38,7 @@ from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.utils import formatdate, getaddresses, make_msgid -from mailman.Utils import maketext, wrap +from mailman.Utils import wrap from mailman.config import config from mailman.core.errors import DiscardMessage from mailman.core.i18n import _ @@ -46,6 +46,7 @@ from mailman.interfaces.member import DeliveryMode, DeliveryStatus from mailman.pipeline.decorate import decorate from mailman.pipeline.scrubber import process as scrubber from mailman.queue import Runner +from mailman.utilities.i18n import make from mailman.utilities.mailbox import Mailbox from mailman.utilities.string import oneline @@ -76,15 +77,14 @@ class Digester: # digest header are separate MIME subobjects. In either case, it's # the first thing in the digest, and we can calculate it now, so go # ahead and add it now. - self._masthead = maketext( - 'masthead.txt', dict( - real_name=mlist.real_name, - got_list_email=mlist.posting_address, - got_listinfo_url=mlist.script_url('listinfo'), - got_request_email=mlist.request_address, - got_owner_email=mlist.owner_address, - ), - mlist=mlist) + self._masthead = make('masthead.txt', + mailing_list=mlist, + real_name=mlist.real_name, + got_list_email=mlist.posting_address, + got_listinfo_url=mlist.script_url('listinfo'), + got_request_email=mlist.request_address, + got_owner_email=mlist.owner_address, + ) # Set things up for the table of contents. self._header = decorate(mlist, mlist.digest_header) self._toc = StringIO() diff --git a/src/mailman/utilities/i18n.py b/src/mailman/utilities/i18n.py index bb826e853..f84baa7c6 100644 --- a/src/mailman/utilities/i18n.py +++ b/src/mailman/utilities/i18n.py @@ -35,6 +35,7 @@ from zope.component import getUtility from mailman.config import config from mailman.core.constants import system_preferences +from mailman.core.i18n import _ from mailman.interfaces.languages import ILanguageManager from mailman.utilities.string import expand @@ -184,14 +185,11 @@ def make(template_file, mailing_list=None, language=None, wrap=True, **kw): """ path, fp = find(template_file, mailing_list, language) try: - raw_text = fp.read() + # XXX BROKEN HACK + template = _(fp.read()[:-1]) finally: fp.close() - # The language is always the second to last path component. - parts = path.split(os.sep) - language_code = parts[-2] - charset = getUtility(ILanguageManager)[language_code].charset - template = unicode(raw_text, charset, 'replace') + assert isinstance(template, unicode), 'Translated template is not unicode' text = expand(template, kw) if wrap: return wrap_text(text) diff --git a/src/mailman/utilities/tests/test_templates.py b/src/mailman/utilities/tests/test_templates.py index b95b181f7..2de43ae3c 100644 --- a/src/mailman/utilities/tests/test_templates.py +++ b/src/mailman/utilities/tests/test_templates.py @@ -260,7 +260,6 @@ It will not be wrapped. self.assertEqual(make('nosub.txt', self.mlist), """\ This is a global template. It has no substitutions. It will be wrapped. - """) def test_substitutions(self): @@ -269,7 +268,6 @@ wrapped. howmany='a few'), """\ This is a very nice template. It has a few substitutions. It will be wrapped. - """) def test_substitutions_no_wrap(self): @@ -279,7 +277,6 @@ wrapped. This is a very nice template. It has a few substitutions. It will not be wrapped. - """) -- cgit v1.2.3-70-g09d2