From 71578c967032dd8b4485e6b1a257ba33363fd8f5 Mon Sep 17 00:00:00 2001 From: Adi Roiban Date: Sun, 26 Sep 2021 12:37:11 +0100 Subject: [PATCH 1/2] Initial settings merge. --- Contributing.rst | 23 ++++++-- i18n_subsites/README.rst | 5 ++ i18n_subsites/i18n_subsites.py | 26 ++++++++- i18n_subsites/test_i18n_subsites.py | 82 ++++++++++++++++++----------- 4 files changed, 99 insertions(+), 37 deletions(-) diff --git a/Contributing.rst b/Contributing.rst index 4647b3c37..5d022fc6e 100644 --- a/Contributing.rst +++ b/Contributing.rst @@ -22,10 +22,6 @@ explanations and usage details into the ``ReadMe`` file. ``__init__.py`` should contain a single line with ``from .my_plugin import *``. -Place tests for your plugin in the same folder inside ``test_my_plugin.py``. -If you need content or templates in your tests, you can use the main -``test_data`` folder for that purpose. - **Note:** Each plugin can contain a LICENSE file stating the license it's released under. If there is an absence of LICENSE then it defaults to the *GNU AFFERO GENERAL PUBLIC LICENSE Version 3*. Please refer to the ``LICENSE`` @@ -37,3 +33,22 @@ order) and providing a brief description. .. _guidelines: http://docs.getpelican.com/en/latest/contribute.html#using-git-and-github .. _docs: http://docs.getpelican.com/en/latest/plugins.html#how-to-create-plugins + + +Automated testing +----------------- + +Place tests for your plugin in the same folder inside ``test_my_plugin.py``. +If you need content or templates in your tests, you can use the main +``test_data`` folder for that purpose. + +Tests for various plugins might fail as they lack dependencies or are not +maintained for the latest pelican version. +To run the test you will need at least the basic pelican packages installed +as a dependency:: + + pip install pelican + +To run the test for your pluging run:: + + python -m unittest i18n_subsites/test_i18n_subsites.py diff --git a/i18n_subsites/README.rst b/i18n_subsites/README.rst index 340109b13..2ac742626 100644 --- a/i18n_subsites/README.rst +++ b/i18n_subsites/README.rst @@ -145,6 +145,7 @@ to link to the main site. This short `howto <./implementing_language_buttons.rst>`_ shows two example implementations of language buttons. + Usage notes =========== - It is **mandatory** to specify ``lang`` metadata for each article @@ -158,8 +159,12 @@ Usage notes give articles e.g. ``name`` metadata and use it in ``ARTICLE_URL = '{name}.html'``. + Development =========== - A demo and a test site is in the ``gh-pages`` branch and can be seen at http://smartass101.github.io/pelican-plugins/ +- A demo site used for automated end to end testing is defined in + i18n_subsites/test_data. +- Run the tests using `python -m unittest i18n_subsites/test_i18n_subsites.py` diff --git a/i18n_subsites/i18n_subsites.py b/i18n_subsites/i18n_subsites.py index dc27799d4..ead7065d4 100644 --- a/i18n_subsites/i18n_subsites.py +++ b/i18n_subsites/i18n_subsites.py @@ -424,9 +424,8 @@ def create_next_subsite(pelican_obj): _MAIN_SETTINGS = None # to initialize next time else: with temporary_locale(): - settings = _MAIN_SETTINGS.copy() lang, overrides = _SUBSITE_QUEUE.popitem() - settings.update(overrides) + settings = _merge_dict(_MAIN_SETTINGS, overrides) settings = configure_settings(settings) # to set LOCALE, etc. cls = get_pelican_cls(settings) @@ -436,6 +435,29 @@ def create_next_subsite(pelican_obj): new_pelican_obj.run() +def _merge_dict(target, source): + """ + Update the values in `target` mapping based on the values from source. + + Any keys from `target` not found in `source` are kept. + """ + result = {} + for key, value in target.items(): + if key not in source: + # Keep the original value as it's not found. + result[key] = copy(value) + continue + + update = source[key] + + if isinstance(value, dict): + update = _merge_dict(value, update) + + result[key] = update + + return result + + # map: signal name -> function name _SIGNAL_HANDLERS_DB = { 'get_generators': initialize_plugin, diff --git a/i18n_subsites/test_i18n_subsites.py b/i18n_subsites/test_i18n_subsites.py index 83d0cb986..01e9ff03b 100644 --- a/i18n_subsites/test_i18n_subsites.py +++ b/i18n_subsites/test_i18n_subsites.py @@ -42,12 +42,12 @@ def test_get_pelican_cls_class(self): self.settings['PELICAN_CLASS'] = object cls = i18ns.get_pelican_cls(self.settings) self.assertIs(cls, object) - + def test_get_pelican_cls_str(self): '''Test that we get correct class given by string''' cls = i18ns.get_pelican_cls(self.settings) self.assertIs(cls, Pelican) - + class TestSitesRelpath(unittest.TestCase): '''Test relative path between sites generation''' @@ -72,7 +72,7 @@ def test_relpath_to_site(self): self.assertEqual(i18ns.relpath_to_site('en', 'de'), 'de') self.assertEqual(i18ns.relpath_to_site('de', 'en'), '..') - + class TestRegistration(unittest.TestCase): '''Test plugin registration''' @@ -91,49 +91,69 @@ def test_registration(self): self.assertIn(id(handler), sig.receivers) # clean up sig.disconnect(handler) - + class TestFullRun(unittest.TestCase): '''Test running Pelican with the Plugin''' - def setUp(self): - '''Create temporary output and cache folders''' - self.temp_path = mkdtemp(prefix='pelicantests.') - self.temp_cache = mkdtemp(prefix='pelican_cache.') - - def tearDown(self): - '''Remove output and cache folders''' - rmtree(self.temp_path) - rmtree(self.temp_cache) + def cleanDirs(self, dirs): + """ + Recursive delete each path from `dirs`. + """ + for path in dirs: + rmtree(path) + + def getContent(self, base, target): + """ + Return the text content of file. + """ + path = os.path.join(base, target) + with open(path, 'r') as stream: + return stream.read() def test_sites_generation(self): - '''Test generation of sites with the plugin - - Compare with recorded output via ``git diff``. - To generate output for comparison run the command - ``pelican -o test_data/output -s test_data/pelicanconf.py \ - test_data/content`` - Remember to remove the output/ folder before that. - ''' + """ + It will generate multiple copies of the site. + + Once copy is the default language, and the others are copies for each + enabled language in I18N_SUBSITES + from i18n_subsites/test_data/pelicanconf.py + """ + output_path = mkdtemp(prefix='pelicantests.') + cache_path = mkdtemp(prefix='pelican_cache.') + self.addCleanup(self.cleanDirs, [output_path, cache_path]) + base_path = os.path.dirname(os.path.abspath(__file__)) base_path = os.path.join(base_path, 'test_data') content_path = os.path.join(base_path, 'content') - output_path = os.path.join(base_path, 'output') settings_path = os.path.join(base_path, 'pelicanconf.py') settings = read_settings(path=settings_path, override={ 'PATH': content_path, - 'OUTPUT_PATH': self.temp_path, - 'CACHE_PATH': self.temp_cache, + 'OUTPUT_PATH': output_path, + 'CACHE_PATH': cache_path, 'PLUGINS': [i18ns], } ) pelican = Pelican(settings) pelican.run() - # compare output - out, err = subprocess.Popen( - ['git', 'diff', '--no-ext-diff', '--exit-code', '-w', output_path, - self.temp_path], env={'PAGER': ''}, - stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() - self.assertFalse(out, 'non-empty `diff` stdout:\n{}'.format(out)) - self.assertFalse(err, 'non-empty `diff` stderr:\n{}'.format(err)) + root_deploy = os.listdir(output_path) + self.assertIn('de', root_deploy) + self.assertIn('cz', root_deploy) + + root_index = self.getContent(output_path, 'index.html') + de_index = self.getContent(output_path, 'de/index.html') + + # Check pelicanconf,py translation. + self.assertIn( + 'example.com/test/author/the-tester.html">The Tester', + root_index + ) + self.assertIn( + 'example.com/test/de/author/der-tester.html">Der Tester', + de_index + ) + + # Check jinja2 translation. + self.assertIn('Welcome to our Testing site', root_index) + self.assertIn('Willkommen Sie zur unserer Testseite', de_index) From 270ae1e2ae46f881d7cba5b5ba44bb74d916b3ca Mon Sep 17 00:00:00 2001 From: Adi Roiban Date: Sun, 26 Sep 2021 13:18:15 +0100 Subject: [PATCH 2/2] Update tests. --- .../localized_theme/templates/base.html | 6 +++- i18n_subsites/test_data/pelicanconf.py | 35 ++++++++++++++----- i18n_subsites/test_i18n_subsites.py | 4 +-- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/i18n_subsites/test_data/localized_theme/templates/base.html b/i18n_subsites/test_data/localized_theme/templates/base.html index a24eb1db8..f36504f7d 100644 --- a/i18n_subsites/test_data/localized_theme/templates/base.html +++ b/i18n_subsites/test_data/localized_theme/templates/base.html @@ -1,6 +1,10 @@ {% extends "!simple/base.html" %} -{% block title %}{% trans %}Welcome to our{% endtrans %} {{ SITENAME }}{% endblock %} +{% block title %} +{% trans %}Welcome to our{% endtrans %} {{ L10N.SITE_NAME }} | {{ L10N.COMPANY.NAME }} {{ L10N.COMPANY.INCORPORATION }} +{% endblock %} + + {% block head %} {{ super() }} diff --git a/i18n_subsites/test_data/pelicanconf.py b/i18n_subsites/test_data/pelicanconf.py index 55018f271..26ac748ed 100644 --- a/i18n_subsites/test_data/pelicanconf.py +++ b/i18n_subsites/test_data/pelicanconf.py @@ -2,16 +2,10 @@ # -*- coding: utf-8 -*- # from __future__ import unicode_literals -AUTHOR = 'The Tester' -SITENAME = 'Testing site' SITEURL = 'http://example.com/test' - # to make the test suite portable TIMEZONE = 'UTC' -DEFAULT_LANG = 'en' -LOCALE = 'en_US.UTF-8' - # Generate only one feed FEED_ALL_ATOM = 'feeds_all.atom.xml' CATEGORY_FEED_ATOM = None @@ -38,15 +32,40 @@ tmpsig = signal('tmpsig') I18N_FILTER_SIGNALS = [tmpsig] +DEFAULT_LANG = 'en' + +# Base setting used to render the theme. +AUTHOR = 'The Tester' + +LOCALE = 'en_US.UTF-8' +# Having all the translatable values in a root dict help organize +# the translation and coordinate with the development and +# the translation teams. +L10N = { + 'SITE_NAME': 'Testing site', + 'COMPANY': { + # This is not translated to test deep merge. + 'NAME': 'Acme', + # This is translated. + 'INCORPORATION': 'Ltd' + }, +} + +# Translation for pelicanconf.py settings. I18N_SUBSITES = { 'de': { - 'SITENAME': 'Testseite', 'AUTHOR': 'Der Tester', + 'L10N': { + 'SITE_NAME': 'Testseite', + 'COMPANY': {'INCORPORATION': 'AG'} + }, 'LOCALE': 'de_DE.UTF-8', }, 'cz': { - 'SITENAME': 'Testovací stránka', 'AUTHOR': 'Test Testovič', + 'L10N': { + 'SITE_NAME': 'Testovací stránka', + }, 'I18N_UNTRANSLATED_PAGES': 'remove', 'I18N_UNTRANSLATED_ARTICLES': 'keep', }, diff --git a/i18n_subsites/test_i18n_subsites.py b/i18n_subsites/test_i18n_subsites.py index 01e9ff03b..759992db4 100644 --- a/i18n_subsites/test_i18n_subsites.py +++ b/i18n_subsites/test_i18n_subsites.py @@ -155,5 +155,5 @@ def test_sites_generation(self): ) # Check jinja2 translation. - self.assertIn('Welcome to our Testing site', root_index) - self.assertIn('Willkommen Sie zur unserer Testseite', de_index) + self.assertIn('Welcome to our Testing site | Acme Ltd', root_index) + self.assertIn('Willkommen Sie zur unserer Testseite | Acme AG', de_index)