Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[i18n_subsites] Allow merging the base settings with the translations settings. #1350

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions Contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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``
Expand All @@ -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
5 changes: 5 additions & 0 deletions i18n_subsites/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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`
26 changes: 24 additions & 2 deletions i18n_subsites/i18n_subsites.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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,
Expand Down
6 changes: 5 additions & 1 deletion i18n_subsites/test_data/localized_theme/templates/base.html
Original file line number Diff line number Diff line change
@@ -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() }}
<link rel="stylesheet" href="{{ SITEURL }}/{{ THEME_STATIC_DIR }}/style.css" />
Expand Down
35 changes: 27 additions & 8 deletions i18n_subsites/test_data/pelicanconf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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',
},
Expand Down
82 changes: 51 additions & 31 deletions i18n_subsites/test_i18n_subsites.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'''
Expand All @@ -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'''

Expand All @@ -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</a>',
root_index
)
self.assertIn(
'example.com/test/de/author/der-tester.html">Der Tester</a>',
de_index
)

# Check jinja2 translation.
self.assertIn('Welcome to our Testing site | Acme Ltd', root_index)
self.assertIn('Willkommen Sie zur unserer Testseite | Acme AG', de_index)