From d7993c0e3fbc7e08b06bfb57f3ff3b824baab07a Mon Sep 17 00:00:00 2001 From: th3aftermath Date: Mon, 17 Mar 2014 11:39:57 -0400 Subject: [PATCH 1/3] Add a Base Theme Setting Adds a setting to specify the base theme you want to use. No longer do you have to use simple. Can be specified from your settings file and the command line. The base theme directory is also watched for changes so that autoreload works. --- pelican/__init__.py | 27 ++++++++++++++++++++++++--- pelican/generators.py | 17 ++++++++++++++--- pelican/settings.py | 21 +++++++++++++++++++-- pelican/tests/test_generators.py | 26 +++++++++++++------------- pelican/tests/test_settings.py | 4 +++- 5 files changed, 73 insertions(+), 22 deletions(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index 08dd484e..cc75b6be 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -44,6 +44,7 @@ class Pelican(object): self.path = settings['PATH'] self.theme = settings['THEME'] + self.base_theme = settings['BASE_THEME'] self.output_path = settings['OUTPUT_PATH'] self.ignore_files = settings['IGNORE_FILES'] self.delete_outputdir = settings['DELETE_OUTPUT_DIRECTORY'] @@ -148,6 +149,7 @@ class Pelican(object): settings=self.settings, path=self.path, theme=self.theme, + base_theme=self.base_theme, output_path=self.output_path, ) for cls in self.get_generator_classes() ] @@ -224,6 +226,11 @@ def parse_arguments(): 'specified, it will use the default one included with ' 'pelican.') + parser.add_argument('-b', '--base-theme-path', dest='base_theme', + help='Path where to find the base theme templates. If not ' + 'specified, it will use the default one included with ' + 'pelican.') + parser.add_argument('-o', '--output', dest='output', help='Where to output the generated files. If not ' 'specified, a directory will be created, named ' @@ -270,6 +277,9 @@ def get_config(args): if args.theme: abstheme = os.path.abspath(os.path.expanduser(args.theme)) config['THEME'] = abstheme if os.path.exists(abstheme) else args.theme + if args.base_theme: + absbasetheme = os.path.abspath(os.path.expanduser(args.base_theme)) + config['BASE_THEME'] = absbasetheme if os.path.exists(absbasetheme) else args.base_theme if args.delete_outputdir is not None: config['DELETE_OUTPUT_DIRECTORY'] = args.delete_outputdir @@ -280,7 +290,7 @@ def get_config(args): if not six.PY3: enc = locale.getpreferredencoding() for key in config: - if key in ('PATH', 'OUTPUT_PATH', 'THEME'): + if key in ('PATH', 'OUTPUT_PATH', 'THEME', 'BASE_THEME'): config[key] = config[key].decode(enc) return config @@ -314,6 +324,9 @@ def main(): 'theme': folder_watcher(pelican.theme, [''], pelican.ignore_files), + 'base_theme': folder_watcher(pelican.base_theme, + [''], + pelican.ignore_files), 'settings': file_watcher(args.settings)} for static_path in settings.get("STATIC_PATHS", []): @@ -321,13 +334,13 @@ def main(): try: if args.autoreload: - print(' --- AutoReload Mode: Monitoring `content`, `theme` and' + print(' --- AutoReload Mode: Monitoring `content`, `theme`, `base_theme` and' ' `settings` for changes. ---') while True: try: # Check source dir for changed files ending with the given - # extension in the settings. In the theme dir is no such + # extension in the settings. In the theme and base_theme dir there is no such # restriction; all files are recursively checked if they # have changed, no matter what extension the filenames # have. @@ -347,6 +360,10 @@ def main(): logger.warning('Empty theme folder. Using `basic` ' 'theme.') + if modified['base_theme'] is None: + logger.warning('Empty base_theme folder. Using `simple` ' + 'theme.') + pelican.run() except KeyboardInterrupt: @@ -370,6 +387,10 @@ def main(): if next(watchers['theme']) is None: logger.warning('Empty theme folder. Using `basic` theme.') + if next(watchers['base_theme']) is None: + logger.warning('Empty base_theme folder. Using `simple` ' + 'theme.') + pelican.run() except Exception as e: diff --git a/pelican/generators.py b/pelican/generators.py index bfdac1a5..bb397d61 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -30,11 +30,12 @@ logger = logging.getLogger(__name__) class Generator(object): """Baseclass generator""" - def __init__(self, context, settings, path, theme, output_path, **kwargs): + def __init__(self, context, settings, path, theme, base_theme, output_path, **kwargs): self.context = context self.settings = settings self.path = path self.theme = theme + self.base_theme = base_theme self.output_path = output_path for arg, value in kwargs.items(): @@ -53,13 +54,19 @@ class Generator(object): simple_loader = FileSystemLoader(os.path.join(theme_path, "themes", "simple", "templates")) + + base_theme = self.settings['BASE_THEME'] + + base_loader = FileSystemLoader(os.path.join(theme_path, + "themes", base_theme, "templates")) self.env = Environment( trim_blocks=True, lstrip_blocks=True, loader=ChoiceLoader([ FileSystemLoader(self._templates_path), - simple_loader, # implicit inheritance - PrefixLoader({'!simple': simple_loader}) # explicit one + base_loader, # implicit inheritance + PrefixLoader({'!simple': simple_loader}), # explicit one + PrefixLoader({'!base': base_loader}) # explicit one ]), extensions=self.settings['JINJA_EXTENSIONS'], ) @@ -606,6 +613,10 @@ class StaticGenerator(Generator): self._update_context(('staticfiles',)) def generate_output(self, writer): + + self._copy_paths(self.settings['BASE_THEME_STATIC_PATHS'], self.base_theme, + self.settings['THEME_STATIC_DIR'], self.output_path, + os.curdir) self._copy_paths(self.settings['THEME_STATIC_PATHS'], self.theme, self.settings['THEME_STATIC_DIR'], self.output_path, os.curdir) diff --git a/pelican/settings.py b/pelican/settings.py index 796678e0..92c6da3e 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -25,18 +25,23 @@ logger = logging.getLogger(__name__) DEFAULT_THEME = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'themes', 'notmyidea') +DEFAULT_BASE_THEME = os.path.join(os.path.dirname(os.path.abspath(__file__)), + 'themes', 'simple') + DEFAULT_CONFIG = { 'PATH': os.curdir, 'ARTICLE_DIR': '', 'ARTICLE_EXCLUDES': ('pages',), 'PAGE_DIR': 'pages', 'PAGE_EXCLUDES': (), + 'BASE_THEME': DEFAULT_BASE_THEME, 'THEME': DEFAULT_THEME, 'OUTPUT_PATH': 'output', 'READERS': {}, 'STATIC_PATHS': ['images', ], 'THEME_STATIC_DIR': 'theme', 'THEME_STATIC_PATHS': ['static', ], + 'BASE_THEME_STATIC_PATHS': ['static', ], 'FEED_ALL_ATOM': os.path.join('feeds', 'all.atom.xml'), 'CATEGORY_FEED_ATOM': os.path.join('feeds', '%s.atom.xml'), 'TRANSLATION_FEED_ATOM': os.path.join('feeds', 'all-%s.atom.xml'), @@ -126,12 +131,12 @@ def read_settings(path=None, override=None): if path: local_settings = get_settings_from_file(path) # Make the paths relative to the settings file - for p in ['PATH', 'OUTPUT_PATH', 'THEME', 'PLUGIN_PATH']: + for p in ['PATH', 'OUTPUT_PATH', 'THEME', 'BASE_THEME', 'PLUGIN_PATH']: if p in local_settings and local_settings[p] is not None \ and not isabs(local_settings[p]): absp = os.path.abspath(os.path.normpath(os.path.join( os.path.dirname(path), local_settings[p]))) - if p not in ('THEME', 'PLUGIN_PATH') or os.path.exists(absp): + if p not in ('THEME', 'BASE_THEME', 'PLUGIN_PATH') or os.path.exists(absp): local_settings[p] = absp else: local_settings = copy.deepcopy(DEFAULT_CONFIG) @@ -188,6 +193,18 @@ def configure_settings(settings): raise Exception("Could not find the theme %s" % settings['THEME']) + # lookup the base theme in "pelican/themes" if the given one doesn't exist + if not os.path.isdir(settings['BASE_THEME']): + theme_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + 'themes', + settings['BASE_THEME']) + if os.path.exists(theme_path): + settings['BASE_THEME'] = theme_path + else: + raise Exception("Could not find the base theme %s" + % settings['BASE_THEME']) + # standardize strings to lowercase strings for key in [ 'DEFAULT_LANG', diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index 6f13aeb6..e063ed00 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -24,7 +24,7 @@ class TestGenerator(unittest.TestCase): self.settings = get_settings() self.settings['READERS'] = {'asc': None} self.generator = Generator(self.settings.copy(), self.settings, - CUR_DIR, self.settings['THEME'], None) + CUR_DIR, self.settings['THEME'], self.settings['BASE_THEME'], None) def test_include_path(self): filename = os.path.join(CUR_DIR, 'content', 'article.rst') @@ -45,7 +45,7 @@ class TestArticlesGenerator(unittest.TestCase): cls.generator = ArticlesGenerator( context=settings.copy(), settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + path=CONTENT_DIR, theme=settings['THEME'], base_theme=settings['BASE_THEME'], output_path=None) cls.generator.generate_context() cls.articles = [[page.title, page.status, page.category.name, page.template] for page in cls.generator.articles] @@ -54,7 +54,7 @@ class TestArticlesGenerator(unittest.TestCase): settings = get_settings() generator = ArticlesGenerator( context=settings, settings=settings, - path=None, theme=settings['THEME'], output_path=None) + path=None, theme=settings['THEME'], base_theme=settings['BASE_THEME'], output_path=None) writer = MagicMock() generator.generate_feeds(writer) writer.write_feed.assert_called_with([], settings, @@ -62,7 +62,7 @@ class TestArticlesGenerator(unittest.TestCase): generator = ArticlesGenerator( context=settings, settings=get_settings(FEED_ALL_ATOM=None), - path=None, theme=settings['THEME'], output_path=None) + path=None, theme=settings['THEME'], base_theme=settings['BASE_THEME'], output_path=None) writer = MagicMock() generator.generate_feeds(writer) self.assertFalse(writer.write_feed.called) @@ -131,7 +131,7 @@ class TestArticlesGenerator(unittest.TestCase): settings['filenames'] = {} generator = ArticlesGenerator( context=settings.copy(), settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + path=CONTENT_DIR, theme=settings['THEME'], base_theme=settings['BASE_THEME'], output_path=None) generator.generate_context() # test for name # categories are grouped by slug; if two categories have the same slug @@ -153,7 +153,7 @@ class TestArticlesGenerator(unittest.TestCase): settings = get_settings(filenames={}) generator = ArticlesGenerator( context=settings, settings=settings, - path=None, theme=settings['THEME'], output_path=None) + path=None, theme=settings['THEME'], base_theme=settings['BASE_THEME'], output_path=None) write = MagicMock() generator.generate_direct_templates(write) write.assert_called_with("archives.html", @@ -167,7 +167,7 @@ class TestArticlesGenerator(unittest.TestCase): settings['ARCHIVES_SAVE_AS'] = 'archives/index.html' generator = ArticlesGenerator( context=settings, settings=settings, - path=None, theme=settings['THEME'], output_path=None) + path=None, theme=settings['THEME'], base_theme=settings['BASE_THEME'], output_path=None) write = MagicMock() generator.generate_direct_templates(write) write.assert_called_with("archives/index.html", @@ -182,7 +182,7 @@ class TestArticlesGenerator(unittest.TestCase): settings['ARCHIVES_SAVE_AS'] = 'archives/index.html' generator = ArticlesGenerator( context=settings, settings=settings, - path=None, theme=settings['THEME'], output_path=None) + path=None, theme=settings['THEME'], base_theme=settings['BASE_THEME'], output_path=None) write = MagicMock() generator.generate_direct_templates(write) write.assert_called_count == 0 @@ -208,7 +208,7 @@ class TestArticlesGenerator(unittest.TestCase): settings['YEAR_ARCHIVE_SAVE_AS'] = 'posts/{date:%Y}/index.html' generator = ArticlesGenerator( context=settings, settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + path=CONTENT_DIR, theme=settings['THEME'], base_theme=settings['BASE_THEME'], output_path=None) generator.generate_context() write = MagicMock() generator.generate_period_archives(write) @@ -225,7 +225,7 @@ class TestArticlesGenerator(unittest.TestCase): settings['MONTH_ARCHIVE_SAVE_AS'] = 'posts/{date:%Y}/{date:%b}/index.html' generator = ArticlesGenerator( context=settings, settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + path=CONTENT_DIR, theme=settings['THEME'], base_theme=settings['BASE_THEME'], output_path=None) generator.generate_context() write = MagicMock() generator.generate_period_archives(write) @@ -243,7 +243,7 @@ class TestArticlesGenerator(unittest.TestCase): settings['DAY_ARCHIVE_SAVE_AS'] = 'posts/{date:%Y}/{date:%b}/{date:%d}/index.html' generator = ArticlesGenerator( context=settings, settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + path=CONTENT_DIR, theme=settings['THEME'], base_theme=settings['BASE_THEME'], output_path=None) generator.generate_context() write = MagicMock() generator.generate_period_archives(write) @@ -285,7 +285,7 @@ class TestPageGenerator(unittest.TestCase): generator = PagesGenerator( context=settings.copy(), settings=settings, - path=CUR_DIR, theme=settings['THEME'], output_path=None) + path=CUR_DIR, theme=settings['THEME'], base_theme=settings['BASE_THEME'], output_path=None) generator.generate_context() pages = self.distill_pages(generator.pages) hidden_pages = self.distill_pages(generator.hidden_pages) @@ -329,7 +329,7 @@ class TestTemplatePagesGenerator(unittest.TestCase): generator = TemplatePagesGenerator( context={'foo': 'bar'}, settings=settings, - path=self.temp_content, theme='', output_path=self.temp_output) + path=self.temp_content, theme='', base_theme=settings['BASE_THEME'], output_path=self.temp_output) # create a dummy template file template_dir = os.path.join(self.temp_content, 'template') diff --git a/pelican/tests/test_settings.py b/pelican/tests/test_settings.py index 7907a551..56b91f36 100644 --- a/pelican/tests/test_settings.py +++ b/pelican/tests/test_settings.py @@ -6,7 +6,7 @@ import locale from os.path import dirname, abspath, join from pelican.settings import (read_settings, configure_settings, - DEFAULT_CONFIG, DEFAULT_THEME) + DEFAULT_CONFIG, DEFAULT_THEME, DEFAULT_BASE_THEME) from pelican.tests.support import unittest @@ -65,6 +65,7 @@ class TestSettingsConfiguration(unittest.TestCase): # These 4 settings are required to run configure_settings 'PATH': '.', 'THEME': DEFAULT_THEME, + 'BASE_THEME': DEFAULT_BASE_THEME, 'SITEURL': 'http://blog.notmyidea.org/', 'LOCALE': '', } @@ -82,6 +83,7 @@ class TestSettingsConfiguration(unittest.TestCase): 'LOCALE': '', 'PATH': os.curdir, 'THEME': DEFAULT_THEME, + 'BASE_THEME': DEFAULT_BASE_THEME, } configure_settings(settings) # SITEURL should not have a trailing slash From 5016797a7bc20a9e2ecc5a09502fea98a0645908 Mon Sep 17 00:00:00 2001 From: th3aftermath Date: Wed, 23 Apr 2014 18:50:38 -0400 Subject: [PATCH 2/3] add stash --- pelican/__init__.py | 28 +++------------------------- pelican/generators.py | 29 +++++++++++++---------------- pelican/settings.py | 41 +++++++++++++++++++++++------------------ 3 files changed, 39 insertions(+), 59 deletions(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index 81b9e262..d5de8f89 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -47,7 +47,6 @@ class Pelican(object): self.path = settings['PATH'] self.theme = settings['THEME'] - self.base_theme = settings['BASE_THEME'] self.output_path = settings['OUTPUT_PATH'] self.ignore_files = settings['IGNORE_FILES'] self.delete_outputdir = settings['DELETE_OUTPUT_DIRECTORY'] @@ -153,7 +152,6 @@ class Pelican(object): settings=self.settings, path=self.path, theme=self.theme, - base_theme=self.base_theme, output_path=self.output_path, ) for cls in self.get_generator_classes() ] @@ -230,11 +228,6 @@ def parse_arguments(): 'specified, it will use the default one included with ' 'pelican.') - parser.add_argument('-b', '--base-theme-path', dest='base_theme', - help='Path where to find the base theme templates. If not ' - 'specified, it will use the default one included with ' - 'pelican.') - parser.add_argument('-o', '--output', dest='output', help='Where to output the generated files. If not ' 'specified, a directory will be created, named ' @@ -290,9 +283,6 @@ def get_config(args): if args.theme: abstheme = os.path.abspath(os.path.expanduser(args.theme)) config['THEME'] = abstheme if os.path.exists(abstheme) else args.theme - if args.base_theme: - absbasetheme = os.path.abspath(os.path.expanduser(args.base_theme)) - config['BASE_THEME'] = absbasetheme if os.path.exists(absbasetheme) else args.base_theme if args.delete_outputdir is not None: config['DELETE_OUTPUT_DIRECTORY'] = args.delete_outputdir if args.ignore_cache: @@ -307,7 +297,7 @@ def get_config(args): if not six.PY3: enc = locale.getpreferredencoding() for key in config: - if key in ('PATH', 'OUTPUT_PATH', 'THEME', 'BASE_THEME'): + if key in ('PATH', 'OUTPUT_PATH', 'THEME'): config[key] = config[key].decode(enc) return config @@ -341,9 +331,6 @@ def main(): 'theme': folder_watcher(pelican.theme, [''], pelican.ignore_files), - 'base_theme': folder_watcher(pelican.base_theme, - [''], - pelican.ignore_files), 'settings': file_watcher(args.settings)} for static_path in settings.get("STATIC_PATHS", []): @@ -351,7 +338,7 @@ def main(): try: if args.autoreload: - print(' --- AutoReload Mode: Monitoring `content`, `theme`, `base_theme` and' + print(' --- AutoReload Mode: Monitoring `content`, `theme`, and' ' `settings` for changes. ---') def _ignore_cache(pelican_obj): @@ -361,7 +348,7 @@ def main(): while True: try: # Check source dir for changed files ending with the given - # extension in the settings. In the theme and base_theme dir there is no such + # extension in the settings. In the theme dir there is no such # restriction; all files are recursively checked if they # have changed, no matter what extension the filenames # have. @@ -384,11 +371,6 @@ def main(): if modified['theme'] is None: logger.warning('Empty theme folder. Using `basic` ' 'theme.') - - if modified['base_theme'] is None: - logger.warning('Empty base_theme folder. Using `simple` ' - 'theme.') - pelican.run() # restore original caching policy pelican.settings['LOAD_CONTENT_CACHE'] = original_load_cache @@ -414,10 +396,6 @@ def main(): if next(watchers['theme']) is None: logger.warning('Empty theme folder. Using `basic` theme.') - if next(watchers['base_theme']) is None: - logger.warning('Empty base_theme folder. Using `simple` ' - 'theme.') - pelican.run() except Exception as e: diff --git a/pelican/generators.py b/pelican/generators.py index 5b9b2275..298a17c5 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -30,14 +30,12 @@ logger = logging.getLogger(__name__) class Generator(object): """Baseclass generator""" - - def __init__(self, context, settings, path, theme, base_theme, output_path, + def __init__(self, context, settings, path, theme, output_path, readers_cache_name='', **kwargs): self.context = context self.settings = settings self.path = path self.theme = theme - self.base_theme = base_theme self.output_path = output_path for arg, value in kwargs.items(): @@ -52,24 +50,26 @@ class Generator(object): os.path.join(self.theme, 'templates'))) self._templates_path += self.settings['EXTRA_TEMPLATES_PATHS'] - theme_path = os.path.dirname(os.path.abspath(__file__)) + + theme_path = os.path.dirname(os.path.abspath(__file__)) simple_loader = FileSystemLoader(os.path.join(theme_path, "themes", "simple", "templates")) - base_theme = self.settings['BASE_THEME'] + themes = {} + for theme in settings['THEMES']: + themes['!' + os.path.basename(theme)] = FileSystemLoader(os.path.join(theme, "templates")) + + loader=ChoiceLoader([ + FileSystemLoader(self._templates_path), + simple_loader, #implicit inheritance + PrefixLoader(themes), # explicit one + ]) - base_loader = FileSystemLoader(os.path.join(theme_path, - "themes", base_theme, "templates")) self.env = Environment( trim_blocks=True, lstrip_blocks=True, - loader=ChoiceLoader([ - FileSystemLoader(self._templates_path), - base_loader, # implicit inheritance - PrefixLoader({'!simple': simple_loader}), # explicit one - PrefixLoader({'!base': base_loader}) # explicit one - ]), + loader=loader, extensions=self.settings['JINJA_EXTENSIONS'], ) @@ -681,9 +681,6 @@ class StaticGenerator(Generator): def generate_output(self, writer): - self._copy_paths(self.settings['BASE_THEME_STATIC_PATHS'], self.base_theme, - self.settings['THEME_STATIC_DIR'], self.output_path, - os.curdir) self._copy_paths(self.settings['THEME_STATIC_PATHS'], self.theme, self.settings['THEME_STATIC_DIR'], self.output_path, os.curdir) diff --git a/pelican/settings.py b/pelican/settings.py index 82b11c79..c3ca07a8 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -27,8 +27,6 @@ logger = logging.getLogger(__name__) DEFAULT_THEME = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'themes', 'notmyidea') -DEFAULT_BASE_THEME = os.path.join(os.path.dirname(os.path.abspath(__file__)), - 'themes', 'simple') DEFAULT_CONFIG = { 'PATH': os.curdir, @@ -36,14 +34,13 @@ DEFAULT_CONFIG = { 'ARTICLE_EXCLUDES': ('pages',), 'PAGE_DIR': 'pages', 'PAGE_EXCLUDES': (), - 'BASE_THEME': DEFAULT_BASE_THEME, 'THEME': DEFAULT_THEME, + 'THEMES':['simple',], 'OUTPUT_PATH': 'output', 'READERS': {}, 'STATIC_PATHS': ['images', ], 'THEME_STATIC_DIR': 'theme', 'THEME_STATIC_PATHS': ['static', ], - 'BASE_THEME_STATIC_PATHS': ['static', ], 'FEED_ALL_ATOM': os.path.join('feeds', 'all.atom.xml'), 'CATEGORY_FEED_ATOM': os.path.join('feeds', '%s.atom.xml'), 'AUTHOR_FEED_ATOM': os.path.join('feeds', '%s.atom.xml'), @@ -144,13 +141,12 @@ def read_settings(path=None, override=None): if path: local_settings = get_settings_from_file(path) # Make the paths relative to the settings file - - for p in ['PATH', 'OUTPUT_PATH', 'THEME', 'BASE_THEME']: + for p in ['PATH', 'OUTPUT_PATH', 'THEME']: if p in local_settings and local_settings[p] is not None \ and not isabs(local_settings[p]): absp = os.path.abspath(os.path.normpath(os.path.join( os.path.dirname(path), local_settings[p]))) - if p not in ('THEME', 'BASE_THEME') or os.path.exists(absp): + if p not in ('THEME') or os.path.exists(absp): local_settings[p] = absp if isinstance(local_settings['PLUGIN_PATH'], six.string_types): @@ -160,6 +156,14 @@ def read_settings(path=None, override=None): if 'PLUGIN_PATH' in local_settings and local_settings['PLUGIN_PATH'] is not None: local_settings['PLUGIN_PATH'] = [os.path.abspath(os.path.normpath(os.path.join(os.path.dirname(path), pluginpath))) if not isabs(pluginpath) else pluginpath for pluginpath in local_settings['PLUGIN_PATH']] + + if 'THEMES' in local_settings and local_settings[p] is not None: + for p in local_settings['THEMES']: + if p is not isabs(p): + absp = os.path.abspath(os.path.normpath(os.path.join(os.path.dirname(path), p))) + if os.path.exists(absp): + local_settings['THEMES'][p] = absp + else: local_settings = copy.deepcopy(DEFAULT_CONFIG) @@ -219,17 +223,18 @@ def configure_settings(settings): raise Exception("Could not find the theme %s" % settings['THEME']) - # lookup the base theme in "pelican/themes" if the given one doesn't exist - if not os.path.isdir(settings['BASE_THEME']): - theme_path = os.path.join( - os.path.dirname(os.path.abspath(__file__)), - 'themes', - settings['BASE_THEME']) - if os.path.exists(theme_path): - settings['BASE_THEME'] = theme_path - else: - raise Exception("Could not find the base theme %s" - % settings['BASE_THEME']) + for theme in settings['THEMES']: + if not os.path.isdir(theme): + theme_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + 'themes', + theme) + if os.path.exists(theme_path): + index = settings['THEMES'].index(theme) + settings['THEMES'][index] = theme_path + else: + raise Exception("Could not find the theme %s" + % theme) # make paths selected for writing absolute if necessary settings['WRITE_SELECTED'] = [ From d93b215f2992f8d81d36035d8a7b54a1eed9cbc3 Mon Sep 17 00:00:00 2001 From: th3aftermath Date: Wed, 23 Apr 2014 23:16:36 -0400 Subject: [PATCH 3/3] Add multi theme support --- pelican/__init__.py | 9 ++++++ pelican/generators.py | 17 +++++++---- pelican/settings.py | 14 +++++---- pelican/tests/test_generators.py | 50 ++++++++++++++++---------------- pelican/tests/test_settings.py | 6 ++-- 5 files changed, 57 insertions(+), 39 deletions(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index d5de8f89..95918ffe 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -47,6 +47,7 @@ class Pelican(object): self.path = settings['PATH'] self.theme = settings['THEME'] + self.themes = settings['THEMES'] self.output_path = settings['OUTPUT_PATH'] self.ignore_files = settings['IGNORE_FILES'] self.delete_outputdir = settings['DELETE_OUTPUT_DIRECTORY'] @@ -87,6 +88,10 @@ class Pelican(object): def _handle_deprecation(self): + if self.settings['EXTRA_TEMPLATES_PATHS']: + logger.warning('`EXTRA_TEMPLATES_PATHS` is soon to be deprecated.' + ' Use the `THEMES` setting for the same behaviour') + if self.settings.get('CLEAN_URLS', False): logger.warning('Found deprecated `CLEAN_URLS` in settings.' ' Modifying the following settings for the' @@ -152,6 +157,7 @@ class Pelican(object): settings=self.settings, path=self.path, theme=self.theme, + themes=self.themes, output_path=self.output_path, ) for cls in self.get_generator_classes() ] @@ -336,6 +342,9 @@ def main(): for static_path in settings.get("STATIC_PATHS", []): watchers[static_path] = folder_watcher(static_path, [''], pelican.ignore_files) + for theme in pelican.themes: + watchers[theme] = folder_watcher(pelican.themes[theme], [''], pelican.ignore_files) + try: if args.autoreload: print(' --- AutoReload Mode: Monitoring `content`, `theme`, and' diff --git a/pelican/generators.py b/pelican/generators.py index 298a17c5..58ec3dc4 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -30,12 +30,13 @@ logger = logging.getLogger(__name__) class Generator(object): """Baseclass generator""" - def __init__(self, context, settings, path, theme, output_path, + def __init__(self, context, settings, path, theme, themes, output_path, readers_cache_name='', **kwargs): self.context = context self.settings = settings self.path = path self.theme = theme + self.themes = themes self.output_path = output_path for arg, value in kwargs.items(): @@ -57,13 +58,13 @@ class Generator(object): "themes", "simple", "templates")) themes = {} - for theme in settings['THEMES']: - themes['!' + os.path.basename(theme)] = FileSystemLoader(os.path.join(theme, "templates")) + for theme in self.themes: + themes[theme] = FileSystemLoader(os.path.join(theme, "templates")) loader=ChoiceLoader([ FileSystemLoader(self._templates_path), - simple_loader, #implicit inheritance - PrefixLoader(themes), # explicit one + simple_loader, # implicit inheritance + PrefixLoader(themes) # explicit one ]) self.env = Environment( @@ -684,6 +685,12 @@ class StaticGenerator(Generator): self._copy_paths(self.settings['THEME_STATIC_PATHS'], self.theme, self.settings['THEME_STATIC_DIR'], self.output_path, os.curdir) + + for theme in self.themes: + self._copy_paths(self.settings['THEME_STATIC_PATHS'], self.themes[theme], + self.settings['THEME_STATIC_DIR'], self.output_path, + os.curdir) + # copy all Static files for sc in self.context['staticfiles']: source_path = os.path.join(self.path, sc.source_path) diff --git a/pelican/settings.py b/pelican/settings.py index c3ca07a8..c6f5f8d5 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -28,6 +28,9 @@ logger = logging.getLogger(__name__) DEFAULT_THEME = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'themes', 'notmyidea') +DEFAULT_THEMES = {'!simple': os.path.join(os.path.dirname(os.path.abspath(__file__)), + 'themes', 'simple')} + DEFAULT_CONFIG = { 'PATH': os.curdir, 'ARTICLE_DIR': '', @@ -35,7 +38,7 @@ DEFAULT_CONFIG = { 'PAGE_DIR': 'pages', 'PAGE_EXCLUDES': (), 'THEME': DEFAULT_THEME, - 'THEMES':['simple',], + 'THEMES': DEFAULT_THEMES, 'OUTPUT_PATH': 'output', 'READERS': {}, 'STATIC_PATHS': ['images', ], @@ -159,7 +162,7 @@ def read_settings(path=None, override=None): if 'THEMES' in local_settings and local_settings[p] is not None: for p in local_settings['THEMES']: - if p is not isabs(p): + if local_settings['THEMES'][p] is not isabs(local_settings['THEMES'][p]): absp = os.path.abspath(os.path.normpath(os.path.join(os.path.dirname(path), p))) if os.path.exists(absp): local_settings['THEMES'][p] = absp @@ -224,14 +227,13 @@ def configure_settings(settings): % settings['THEME']) for theme in settings['THEMES']: - if not os.path.isdir(theme): + if not os.path.isdir(settings['THEMES'][theme]): theme_path = os.path.join( os.path.dirname(os.path.abspath(__file__)), 'themes', - theme) + settings['THEMES'][theme]) if os.path.exists(theme_path): - index = settings['THEMES'].index(theme) - settings['THEMES'][index] = theme_path + settings['THEMES'][theme] = theme_path else: raise Exception("Could not find the theme %s" % theme) diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index b349b934..757c6f45 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -27,7 +27,7 @@ class TestGenerator(unittest.TestCase): self.settings = get_settings() self.settings['READERS'] = {'asc': None} self.generator = Generator(self.settings.copy(), self.settings, - CUR_DIR, self.settings['THEME'], self.settings['BASE_THEME'], None) + CUR_DIR, self.settings['THEME'], self.settings['THEMES'], None) def tearDown(self): locale.setlocale(locale.LC_ALL, self.old_locale) @@ -53,7 +53,7 @@ class TestArticlesGenerator(unittest.TestCase): cls.generator = ArticlesGenerator( context=settings.copy(), settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], base_theme=settings['BASE_THEME'], output_path=None) + path=CONTENT_DIR, theme=settings['THEME'], themes=settings['THEMES'], output_path=None) cls.generator.generate_context() cls.articles = [[page.title, page.status, page.category.name, page.template] for page in cls.generator.articles] @@ -69,7 +69,7 @@ class TestArticlesGenerator(unittest.TestCase): settings['CACHE_DIRECTORY'] = self.temp_cache generator = ArticlesGenerator( context=settings, settings=settings, - path=None, theme=settings['THEME'], base_theme=settings['BASE_THEME'], output_path=None) + path=None, theme=settings['THEME'], themes=settings['THEMES'], output_path=None) writer = MagicMock() generator.generate_feeds(writer) writer.write_feed.assert_called_with([], settings, @@ -77,7 +77,7 @@ class TestArticlesGenerator(unittest.TestCase): generator = ArticlesGenerator( context=settings, settings=get_settings(FEED_ALL_ATOM=None), - path=None, theme=settings['THEME'], base_theme=settings['BASE_THEME'], output_path=None) + path=None, theme=settings['THEME'], themes=settings['THEMES'], output_path=None) writer = MagicMock() generator.generate_feeds(writer) self.assertFalse(writer.write_feed.called) @@ -147,7 +147,7 @@ class TestArticlesGenerator(unittest.TestCase): settings['filenames'] = {} generator = ArticlesGenerator( context=settings.copy(), settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], base_theme=settings['BASE_THEME'], output_path=None) + path=CONTENT_DIR, theme=settings['THEME'], themes=settings['THEMES'], output_path=None) generator.generate_context() # test for name # categories are grouped by slug; if two categories have the same slug @@ -170,7 +170,7 @@ class TestArticlesGenerator(unittest.TestCase): settings['CACHE_DIRECTORY'] = self.temp_cache generator = ArticlesGenerator( context=settings, settings=settings, - path=None, theme=settings['THEME'], base_theme=settings['BASE_THEME'], output_path=None) + path=None, theme=settings['THEME'], themes=settings['THEMES'], output_path=None) write = MagicMock() generator.generate_direct_templates(write) write.assert_called_with("archives.html", @@ -185,7 +185,7 @@ class TestArticlesGenerator(unittest.TestCase): settings['CACHE_DIRECTORY'] = self.temp_cache generator = ArticlesGenerator( context=settings, settings=settings, - path=None, theme=settings['THEME'], base_theme=settings['BASE_THEME'], output_path=None) + path=None, theme=settings['THEME'], themes=settings['THEMES'], output_path=None) write = MagicMock() generator.generate_direct_templates(write) write.assert_called_with("archives/index.html", @@ -201,7 +201,7 @@ class TestArticlesGenerator(unittest.TestCase): settings['CACHE_DIRECTORY'] = self.temp_cache generator = ArticlesGenerator( context=settings, settings=settings, - path=None, theme=settings['THEME'], base_theme=settings['BASE_THEME'], output_path=None) + path=None, theme=settings['THEME'], themes=settings['THEMES'], output_path=None) write = MagicMock() generator.generate_direct_templates(write) write.assert_called_count == 0 @@ -228,7 +228,7 @@ class TestArticlesGenerator(unittest.TestCase): settings['CACHE_DIRECTORY'] = self.temp_cache generator = ArticlesGenerator( context=settings, settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], base_theme=settings['BASE_THEME'], output_path=None) + path=CONTENT_DIR, theme=settings['THEME'], themes=settings['THEMES'], output_path=None) generator.generate_context() write = MagicMock() generator.generate_period_archives(write) @@ -245,7 +245,7 @@ class TestArticlesGenerator(unittest.TestCase): settings['MONTH_ARCHIVE_SAVE_AS'] = 'posts/{date:%Y}/{date:%b}/index.html' generator = ArticlesGenerator( context=settings, settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], base_theme=settings['BASE_THEME'], output_path=None) + path=CONTENT_DIR, theme=settings['THEME'], themes=settings['THEMES'], output_path=None) generator.generate_context() write = MagicMock() generator.generate_period_archives(write) @@ -263,7 +263,7 @@ class TestArticlesGenerator(unittest.TestCase): settings['DAY_ARCHIVE_SAVE_AS'] = 'posts/{date:%Y}/{date:%b}/{date:%d}/index.html' generator = ArticlesGenerator( context=settings, settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], base_theme=settings['BASE_THEME'], output_path=None) + path=CONTENT_DIR, theme=settings['THEME'], themes=settings['THEMES'], output_path=None) generator.generate_context() write = MagicMock() generator.generate_period_archives(write) @@ -297,13 +297,13 @@ class TestArticlesGenerator(unittest.TestCase): generator = ArticlesGenerator( context=settings.copy(), settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + path=CONTENT_DIR, theme=settings['THEME'], themes=settings['THEMES'], output_path=None) generator.generate_context() self.assertTrue(hasattr(generator, '_cache')) generator = ArticlesGenerator( context=settings.copy(), settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + path=CONTENT_DIR, theme=settings['THEME'], themes=settings['THEMES'], output_path=None) generator.readers.read_file = MagicMock() generator.generate_context() generator.readers.read_file.assert_called_count == 0 @@ -316,13 +316,13 @@ class TestArticlesGenerator(unittest.TestCase): generator = ArticlesGenerator( context=settings.copy(), settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + path=CONTENT_DIR, theme=settings['THEME'], themes=settings['THEMES'], output_path=None) generator.generate_context() self.assertTrue(hasattr(generator.readers, '_cache')) generator = ArticlesGenerator( context=settings.copy(), settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + path=CONTENT_DIR, theme=settings['THEME'], themes=settings['THEMES'], output_path=None) readers = generator.readers.readers for reader in readers.values(): reader.read = MagicMock() @@ -340,7 +340,7 @@ class TestArticlesGenerator(unittest.TestCase): generator = ArticlesGenerator( context=settings.copy(), settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + path=CONTENT_DIR, theme=settings['THEME'], themes=settings['THEMES'], output_path=None) generator.readers.read_file = MagicMock() generator.generate_context() self.assertTrue(hasattr(generator, '_cache_open')) @@ -349,7 +349,7 @@ class TestArticlesGenerator(unittest.TestCase): settings['LOAD_CONTENT_CACHE'] = False generator = ArticlesGenerator( context=settings.copy(), settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + path=CONTENT_DIR, theme=settings['THEME'], themes=settings['THEMES'], output_path=None) generator.readers.read_file = MagicMock() generator.generate_context() generator.readers.read_file.assert_called_count == orig_call_count @@ -378,7 +378,7 @@ class TestPageGenerator(unittest.TestCase): generator = PagesGenerator( context=settings.copy(), settings=settings, - path=CUR_DIR, theme=settings['THEME'], base_theme=settings['BASE_THEME'], output_path=None) + path=CUR_DIR, theme=settings['THEME'], themes=settings['THEMES'], output_path=None) generator.generate_context() pages = self.distill_pages(generator.pages) hidden_pages = self.distill_pages(generator.hidden_pages) @@ -408,13 +408,13 @@ class TestPageGenerator(unittest.TestCase): generator = PagesGenerator( context=settings.copy(), settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + path=CONTENT_DIR, theme=settings['THEME'], themes=settings['THEMES'], output_path=None) generator.generate_context() self.assertTrue(hasattr(generator, '_cache')) generator = PagesGenerator( context=settings.copy(), settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + path=CONTENT_DIR, theme=settings['THEME'], themes=settings['THEMES'], output_path=None) generator.readers.read_file = MagicMock() generator.generate_context() generator.readers.read_file.assert_called_count == 0 @@ -427,13 +427,13 @@ class TestPageGenerator(unittest.TestCase): generator = PagesGenerator( context=settings.copy(), settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + path=CONTENT_DIR, theme=settings['THEME'], themes=settings['THEMES'], output_path=None) generator.generate_context() self.assertTrue(hasattr(generator.readers, '_cache')) generator = PagesGenerator( context=settings.copy(), settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + path=CONTENT_DIR, theme=settings['THEME'], themes=settings['THEMES'], output_path=None) readers = generator.readers.readers for reader in readers.values(): reader.read = MagicMock() @@ -451,7 +451,7 @@ class TestPageGenerator(unittest.TestCase): generator = PagesGenerator( context=settings.copy(), settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + path=CONTENT_DIR, theme=settings['THEME'], themes=settings['THEMES'], output_path=None) generator.readers.read_file = MagicMock() generator.generate_context() self.assertTrue(hasattr(generator, '_cache_open')) @@ -460,7 +460,7 @@ class TestPageGenerator(unittest.TestCase): settings['LOAD_CONTENT_CACHE'] = False generator = PagesGenerator( context=settings.copy(), settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + path=CONTENT_DIR, theme=settings['THEME'], themes=settings['THEMES'], output_path=None) generator.readers.read_file = MagicMock() generator.generate_context() generator.readers.read_file.assert_called_count == orig_call_count @@ -492,7 +492,7 @@ class TestTemplatePagesGenerator(unittest.TestCase): generator = TemplatePagesGenerator( context={'foo': 'bar'}, settings=settings, - path=self.temp_content, theme='', base_theme=settings['BASE_THEME'], output_path=self.temp_output) + path=self.temp_content, theme='', themes=settings['THEMES'], output_path=self.temp_output) # create a dummy template file template_dir = os.path.join(self.temp_content, 'template') diff --git a/pelican/tests/test_settings.py b/pelican/tests/test_settings.py index dcd5b8af..1e93f55f 100644 --- a/pelican/tests/test_settings.py +++ b/pelican/tests/test_settings.py @@ -6,7 +6,7 @@ import locale from os.path import dirname, abspath, join from pelican.settings import (read_settings, configure_settings, - DEFAULT_CONFIG, DEFAULT_THEME, DEFAULT_BASE_THEME) + DEFAULT_CONFIG, DEFAULT_THEME) from pelican.tests.support import unittest @@ -70,7 +70,7 @@ class TestSettingsConfiguration(unittest.TestCase): # These 4 settings are required to run configure_settings 'PATH': '.', 'THEME': DEFAULT_THEME, - 'BASE_THEME': DEFAULT_BASE_THEME, + 'THEMES': {'!simple': 'simple'}, 'SITEURL': 'http://blog.notmyidea.org/', 'LOCALE': '', } @@ -88,7 +88,7 @@ class TestSettingsConfiguration(unittest.TestCase): 'LOCALE': '', 'PATH': os.curdir, 'THEME': DEFAULT_THEME, - 'BASE_THEME': DEFAULT_BASE_THEME, + 'THEMES': {'!simple': 'simple'}, } configure_settings(settings) # SITEURL should not have a trailing slash