import copy import locale import os from os.path import abspath, dirname, join from sys import platform from pelican.settings import (DEFAULT_CONFIG, DEFAULT_THEME, _printf_s_to_format_field, coerce_overrides, configure_settings, handle_deprecated_settings, read_settings) from pelican.tests.support import unittest class TestSettingsConfiguration(unittest.TestCase): """Provided a file, it should read it, replace the default values, append new values to the settings (if any), and apply basic settings optimizations. """ def setUp(self): self.old_locale = locale.setlocale(locale.LC_ALL) locale.setlocale(locale.LC_ALL, 'C') self.PATH = abspath(dirname(__file__)) default_conf = join(self.PATH, 'default_conf.py') self.settings = read_settings(default_conf) def tearDown(self): locale.setlocale(locale.LC_ALL, self.old_locale) def test_overwrite_existing_settings(self): self.assertEqual(self.settings.get('SITENAME'), "Alexis' log") self.assertEqual( self.settings.get('SITEURL'), 'http://blog.notmyidea.org') def test_keep_default_settings(self): # Keep default settings if not defined. self.assertEqual( self.settings.get('DEFAULT_CATEGORY'), DEFAULT_CONFIG['DEFAULT_CATEGORY']) def test_dont_copy_small_keys(self): # Do not copy keys not in caps. self.assertNotIn('foobar', self.settings) def test_read_empty_settings(self): # Ensure an empty settings file results in default settings. settings = read_settings(None) expected = copy.deepcopy(DEFAULT_CONFIG) # Added by configure settings expected['FEED_DOMAIN'] = '' expected['ARTICLE_EXCLUDES'] = ['pages'] expected['PAGE_EXCLUDES'] = [''] self.maxDiff = None self.assertDictEqual(settings, expected) def test_settings_return_independent(self): # Make sure that the results from one settings call doesn't # effect past or future instances. self.PATH = abspath(dirname(__file__)) default_conf = join(self.PATH, 'default_conf.py') settings = read_settings(default_conf) settings['SITEURL'] = 'new-value' new_settings = read_settings(default_conf) self.assertNotEqual(new_settings['SITEURL'], settings['SITEURL']) def test_defaults_not_overwritten(self): # This assumes 'SITENAME': 'A Pelican Blog' settings = read_settings(None) settings['SITENAME'] = 'Not a Pelican Blog' self.assertNotEqual(settings['SITENAME'], DEFAULT_CONFIG['SITENAME']) def test_static_path_settings_safety(self): # Disallow static paths from being strings settings = { 'STATIC_PATHS': 'foo/bar', 'THEME_STATIC_PATHS': 'bar/baz', # These 4 settings are required to run configure_settings 'PATH': '.', 'THEME': DEFAULT_THEME, 'SITEURL': 'http://blog.notmyidea.org/', 'LOCALE': '', } configure_settings(settings) self.assertEqual( settings['STATIC_PATHS'], DEFAULT_CONFIG['STATIC_PATHS']) self.assertEqual( settings['THEME_STATIC_PATHS'], DEFAULT_CONFIG['THEME_STATIC_PATHS']) def test_configure_settings(self): # Manipulations to settings should be applied correctly. settings = { 'SITEURL': 'http://blog.notmyidea.org/', 'LOCALE': '', 'PATH': os.curdir, 'THEME': DEFAULT_THEME, } configure_settings(settings) # SITEURL should not have a trailing slash self.assertEqual(settings['SITEURL'], 'http://blog.notmyidea.org') # FEED_DOMAIN, if undefined, should default to SITEURL self.assertEqual(settings['FEED_DOMAIN'], 'http://blog.notmyidea.org') settings['FEED_DOMAIN'] = 'http://feeds.example.com' configure_settings(settings) self.assertEqual(settings['FEED_DOMAIN'], 'http://feeds.example.com') def test_theme_settings_exceptions(self): settings = self.settings # Check that theme lookup in "pelican/themes" functions as expected settings['THEME'] = os.path.split(settings['THEME'])[1] configure_settings(settings) self.assertEqual(settings['THEME'], DEFAULT_THEME) # Check that non-existent theme raises exception settings['THEME'] = 'foo' self.assertRaises(Exception, configure_settings, settings) def test_deprecated_dir_setting(self): settings = self.settings settings['ARTICLE_DIR'] = 'foo' settings['PAGE_DIR'] = 'bar' settings = handle_deprecated_settings(settings) self.assertEqual(settings['ARTICLE_PATHS'], ['foo']) self.assertEqual(settings['PAGE_PATHS'], ['bar']) with self.assertRaises(KeyError): settings['ARTICLE_DIR'] settings['PAGE_DIR'] # locale.getdefaultlocale() is broken on Windows # See: https://bugs.python.org/issue37945 @unittest.skipIf(platform == 'win32', "Doesn't work on Windows") def test_default_encoding(self): # Test that the default locale is set if not specified in settings # Reset locale to Python's default locale locale.setlocale(locale.LC_ALL, 'C') self.assertEqual(self.settings['LOCALE'], DEFAULT_CONFIG['LOCALE']) configure_settings(self.settings) self.assertEqual(locale.getlocale(), locale.getdefaultlocale()) def test_invalid_settings_throw_exception(self): # Test that the path name is valid # test that 'PATH' is set settings = { } self.assertRaises(Exception, configure_settings, settings) # Test that 'PATH' is valid settings['PATH'] = '' self.assertRaises(Exception, configure_settings, settings) # Test nonexistent THEME settings['PATH'] = os.curdir settings['THEME'] = 'foo' self.assertRaises(Exception, configure_settings, settings) def test__printf_s_to_format_field(self): for s in ('%s', '{%s}', '{%s'): option = 'foo/{}/bar.baz'.format(s) result = _printf_s_to_format_field(option, 'slug') expected = option % 'qux' found = result.format(slug='qux') self.assertEqual(expected, found) def test_deprecated_extra_templates_paths(self): settings = self.settings settings['EXTRA_TEMPLATES_PATHS'] = ['/foo/bar', '/ha'] settings = handle_deprecated_settings(settings) self.assertEqual(settings['THEME_TEMPLATES_OVERRIDES'], ['/foo/bar', '/ha']) self.assertNotIn('EXTRA_TEMPLATES_PATHS', settings) def test_deprecated_paginated_direct_templates(self): settings = self.settings settings['PAGINATED_DIRECT_TEMPLATES'] = ['index', 'archives'] settings['PAGINATED_TEMPLATES'] = {'index': 10, 'category': None} settings = handle_deprecated_settings(settings) self.assertEqual(settings['PAGINATED_TEMPLATES'], {'index': 10, 'category': None, 'archives': None}) self.assertNotIn('PAGINATED_DIRECT_TEMPLATES', settings) def test_deprecated_paginated_direct_templates_from_file(self): # This is equivalent to reading a settings file that has # PAGINATED_DIRECT_TEMPLATES defined but no PAGINATED_TEMPLATES. settings = read_settings(None, override={ 'PAGINATED_DIRECT_TEMPLATES': ['index', 'archives'] }) self.assertEqual(settings['PAGINATED_TEMPLATES'], { 'archives': None, 'author': None, 'index': None, 'category': None, 'tag': None}) self.assertNotIn('PAGINATED_DIRECT_TEMPLATES', settings) def test_theme_and_extra_templates_exception(self): settings = self.settings settings['EXTRA_TEMPLATES_PATHS'] = ['/ha'] settings['THEME_TEMPLATES_OVERRIDES'] = ['/foo/bar'] self.assertRaises(Exception, handle_deprecated_settings, settings) def test_slug_and_slug_regex_substitutions_exception(self): settings = {} settings['SLUG_REGEX_SUBSTITUTIONS'] = [('C++', 'cpp')] settings['TAG_SUBSTITUTIONS'] = [('C#', 'csharp')] self.assertRaises(Exception, handle_deprecated_settings, settings) def test_deprecated_slug_substitutions(self): default_slug_regex_subs = self.settings['SLUG_REGEX_SUBSTITUTIONS'] # If no deprecated setting is set, don't set new ones settings = {} settings = handle_deprecated_settings(settings) self.assertNotIn('SLUG_REGEX_SUBSTITUTIONS', settings) self.assertNotIn('TAG_REGEX_SUBSTITUTIONS', settings) self.assertNotIn('CATEGORY_REGEX_SUBSTITUTIONS', settings) self.assertNotIn('AUTHOR_REGEX_SUBSTITUTIONS', settings) # If SLUG_SUBSTITUTIONS is set, set {SLUG, AUTHOR}_REGEX_SUBSTITUTIONS # correctly, don't set {CATEGORY, TAG}_REGEX_SUBSTITUTIONS settings = {} settings['SLUG_SUBSTITUTIONS'] = [('C++', 'cpp')] settings = handle_deprecated_settings(settings) self.assertEqual(settings.get('SLUG_REGEX_SUBSTITUTIONS'), [(r'C\+\+', 'cpp')] + default_slug_regex_subs) self.assertNotIn('TAG_REGEX_SUBSTITUTIONS', settings) self.assertNotIn('CATEGORY_REGEX_SUBSTITUTIONS', settings) self.assertEqual(settings.get('AUTHOR_REGEX_SUBSTITUTIONS'), default_slug_regex_subs) # If {CATEGORY, TAG, AUTHOR}_SUBSTITUTIONS are set, set # {CATEGORY, TAG, AUTHOR}_REGEX_SUBSTITUTIONS correctly, don't set # SLUG_REGEX_SUBSTITUTIONS settings = {} settings['TAG_SUBSTITUTIONS'] = [('C#', 'csharp')] settings['CATEGORY_SUBSTITUTIONS'] = [('C#', 'csharp')] settings['AUTHOR_SUBSTITUTIONS'] = [('Alexander Todorov', 'atodorov')] settings = handle_deprecated_settings(settings) self.assertNotIn('SLUG_REGEX_SUBSTITUTIONS', settings) self.assertEqual(settings['TAG_REGEX_SUBSTITUTIONS'], [(r'C\#', 'csharp')] + default_slug_regex_subs) self.assertEqual(settings['CATEGORY_REGEX_SUBSTITUTIONS'], [(r'C\#', 'csharp')] + default_slug_regex_subs) self.assertEqual(settings['AUTHOR_REGEX_SUBSTITUTIONS'], [(r'Alexander\ Todorov', 'atodorov')] + default_slug_regex_subs) # If {SLUG, CATEGORY, TAG, AUTHOR}_SUBSTITUTIONS are set, set # {SLUG, CATEGORY, TAG, AUTHOR}_REGEX_SUBSTITUTIONS correctly settings = {} settings['SLUG_SUBSTITUTIONS'] = [('C++', 'cpp')] settings['TAG_SUBSTITUTIONS'] = [('C#', 'csharp')] settings['CATEGORY_SUBSTITUTIONS'] = [('C#', 'csharp')] settings['AUTHOR_SUBSTITUTIONS'] = [('Alexander Todorov', 'atodorov')] settings = handle_deprecated_settings(settings) self.assertEqual(settings['TAG_REGEX_SUBSTITUTIONS'], [(r'C\+\+', 'cpp')] + [(r'C\#', 'csharp')] + default_slug_regex_subs) self.assertEqual(settings['CATEGORY_REGEX_SUBSTITUTIONS'], [(r'C\+\+', 'cpp')] + [(r'C\#', 'csharp')] + default_slug_regex_subs) self.assertEqual(settings['AUTHOR_REGEX_SUBSTITUTIONS'], [(r'Alexander\ Todorov', 'atodorov')] + default_slug_regex_subs) # Handle old 'skip' flags correctly settings = {} settings['SLUG_SUBSTITUTIONS'] = [('C++', 'cpp', True)] settings['AUTHOR_SUBSTITUTIONS'] = [('Alexander Todorov', 'atodorov', False)] settings = handle_deprecated_settings(settings) self.assertEqual(settings.get('SLUG_REGEX_SUBSTITUTIONS'), [(r'C\+\+', 'cpp')] + [(r'(?u)\A\s*', ''), (r'(?u)\s*\Z', '')]) self.assertEqual(settings['AUTHOR_REGEX_SUBSTITUTIONS'], [(r'Alexander\ Todorov', 'atodorov')] + default_slug_regex_subs) def test_deprecated_slug_substitutions_from_file(self): # This is equivalent to reading a settings file that has # SLUG_SUBSTITUTIONS defined but no SLUG_REGEX_SUBSTITUTIONS. settings = read_settings(None, override={ 'SLUG_SUBSTITUTIONS': [('C++', 'cpp')] }) self.assertEqual(settings['SLUG_REGEX_SUBSTITUTIONS'], [(r'C\+\+', 'cpp')] + self.settings['SLUG_REGEX_SUBSTITUTIONS']) self.assertNotIn('SLUG_SUBSTITUTIONS', settings) def test_coerce_overrides(self): overrides = coerce_overrides({ 'ARTICLE_EXCLUDES': '["testexcl"]', 'READERS': '{"foo": "bar"}', 'STATIC_EXCLUDE_SOURCES': 'true', 'THEME_STATIC_DIR': 'theme', }) expected = { 'ARTICLE_EXCLUDES': ["testexcl"], 'READERS': {"foo": "bar"}, 'STATIC_EXCLUDE_SOURCES': True, 'THEME_STATIC_DIR': 'theme', } self.assertDictEqual(overrides, expected)