From b1866c8f92b691969820d9485864f4f9189f5a19 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Tue, 21 Aug 2012 09:04:37 -0700 Subject: [PATCH 01/16] Change email address in docs to "authors@" alias --- README.rst | 2 +- docs/index.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 3a62d0ca..018f73ba 100644 --- a/README.rst +++ b/README.rst @@ -52,7 +52,7 @@ If you want to see new features in Pelican, don't hesitate to offer suggestions, clone the repository, etc. There are many ways to contribute_. That's open source, dude! -Send a message to "alexis at notmyidea dot org" with any requests/feedback! You +Send a message to "authors at getpelican dot com" with any requests/feedback! You can also join the team at `#pelican on Freenode`_ (or if you don't have an IRC client handy, use the webchat_ for quick feedback. diff --git a/docs/index.rst b/docs/index.rst index b04557eb..477b4342 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -44,7 +44,7 @@ If you want to see new features in Pelican, don't hesitate to offer suggestions, clone the repository, etc. There are many ways to :doc:`contribute`. That's open source, dude! -Send a message to "alexis at notmyidea dot org" with any requests/feedback! You +Send a message to "authors at getpelican dot com" with any requests/feedback! You can also join the team at `#pelican on Freenode`_ (or if you don't have an IRC client handy, use the webchat_ for quick feedback. From be5b5e880dce407ad3b80d73020aa55b4aa30fab Mon Sep 17 00:00:00 2001 From: Eric Case Date: Tue, 21 Aug 2012 14:14:10 -0700 Subject: [PATCH 02/16] added a missing space --- docs/faq.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/faq.rst b/docs/faq.rst index 4efefaf2..c5c751e6 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -76,7 +76,7 @@ include the following at the top of the article:: That meta-data can then be accessed in the template:: {% if article.modified %} - Last modified: {{ article.modified}} + Last modified: {{ article.modified }} {% endif %} How do I assign custom templates on a per-page basis? From 62f190d574c5738efa5782885cf4c40d33cb2142 Mon Sep 17 00:00:00 2001 From: tBunnyMan Date: Thu, 23 Aug 2012 07:58:39 -0700 Subject: [PATCH 03/16] use lowercase pwd since only Mac OS X allows uppercase commands Fix #473 --- pelican/tools/templates/Makefile.in | 2 +- pelican/tools/templates/develop_server.sh.in | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pelican/tools/templates/Makefile.in b/pelican/tools/templates/Makefile.in index 9a26e315..1445f0b0 100644 --- a/pelican/tools/templates/Makefile.in +++ b/pelican/tools/templates/Makefile.in @@ -1,7 +1,7 @@ PELICAN=$pelican PELICANOPTS=$pelicanopts -BASEDIR=$$(PWD) +BASEDIR=$$(pwd) INPUTDIR=$$(BASEDIR)/content OUTPUTDIR=$$(BASEDIR)/output CONFFILE=$$(BASEDIR)/pelicanconf.py diff --git a/pelican/tools/templates/develop_server.sh.in b/pelican/tools/templates/develop_server.sh.in index 2f8c07dd..b1e3b60c 100755 --- a/pelican/tools/templates/develop_server.sh.in +++ b/pelican/tools/templates/develop_server.sh.in @@ -5,7 +5,7 @@ PELICAN=$pelican PELICANOPTS=$pelicanopts -BASEDIR=$$(PWD) +BASEDIR=$$(pwd) INPUTDIR=$$BASEDIR/content OUTPUTDIR=$$BASEDIR/output CONFFILE=$$BASEDIR/pelicanconf.py From 86da6d1f2e954ae575f0b3a78d39eb3106a11c46 Mon Sep 17 00:00:00 2001 From: tBunnyMan Date: Thu, 23 Aug 2012 11:05:07 -0700 Subject: [PATCH 04/16] Check for value error caused by no valid files found with files_changed This causes an infinite loop when in auto-reload Fix #467 Fix #451 Fix #443 --- pelican/utils.py | 12 ++++++++---- tests/test_utils.py | 15 ++++++++++++++- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/pelican/utils.py b/pelican/utils.py index 53e6e52b..c79c7cee 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -241,10 +241,14 @@ def files_changed(path, extensions): yield os.stat(os.path.join(root, f)).st_mtime global LAST_MTIME - mtime = max(file_times(path)) - if mtime > LAST_MTIME: - LAST_MTIME = mtime - return True + try: + mtime = max(file_times(path)) + if mtime > LAST_MTIME: + LAST_MTIME = mtime + return True + except ValueError: + logger.info("No files found in path") + return False return False diff --git a/tests/test_utils.py b/tests/test_utils.py index 2ea756dc..7ea0cf23 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -74,7 +74,8 @@ class TestUtils(unittest.TestCase): self.assertNotIn(fr_article1, index) def test_files_changed(self): - "Test if file changes are correctly detected" + """Test if file changes are correctly detected + Make sure to handle not getting any files correctly""" path = os.path.join(os.path.dirname(__file__), 'content') filename = os.path.join(path, 'article_with_metadata.rst') @@ -90,6 +91,18 @@ class TestUtils(unittest.TestCase): self.assertEquals(changed, True) self.assertAlmostEqual(utils.LAST_MTIME, t, delta=1) + empty_path = os.path.join(os.path.dirname(__file__), 'empty') + try: + os.mkdir(empty_path) + os.mkdir(os.path.join(empty_path, "empty_folder")) + shutil.copy(__file__, empty_path) + changed = utils.files_changed(empty_path, 'rst') + self.assertFalse(changed) + except OSError: + self.fail("OSError Exception in test_files_changed test") + finally: + shutil.rmtree(empty_path, True) + def test_clean_output_dir(self): test_directory = os.path.join(os.path.dirname(__file__), 'clean_output') content = os.path.join(os.path.dirname(__file__), 'content') From a37ba369ef49ae1643a5b7602860c6be804cf8ad Mon Sep 17 00:00:00 2001 From: tBunnyMan Date: Thu, 23 Aug 2012 12:44:22 -0700 Subject: [PATCH 05/16] Implemented better "valid files not found" behavior. Used an exception so show error state. Used a bool flag to make sure the error is only shown once PER error. Updated tests to check for the correct Exception raised --- pelican/__init__.py | 9 ++++++++- pelican/utils.py | 6 ++++-- tests/test_utils.py | 4 ++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index ba48c4c7..64e334d4 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -11,7 +11,7 @@ from pelican.generators import (ArticlesGenerator, PagesGenerator, StaticGenerator, PdfGenerator, LessCSSGenerator) from pelican.log import init from pelican.settings import read_settings, _DEFAULT_CONFIG -from pelican.utils import clean_output_dir, files_changed, file_changed +from pelican.utils import clean_output_dir, files_changed, file_changed, NoFilesError from pelican.writers import Writer __major__ = 3 @@ -265,6 +265,7 @@ def main(): try: if args.autoreload: + files_found_error = True while True: try: # Check source dir for changed files ending with the given @@ -274,6 +275,8 @@ def main(): # have. if files_changed(pelican.path, pelican.markup) or \ files_changed(pelican.theme, ['']): + if files_found_error == False: + files_found_error = True pelican.run() # reload also if settings.py changed @@ -287,6 +290,10 @@ def main(): except KeyboardInterrupt: logger.warning("Keyboard interrupt, quitting.") break + except NoFilesError: + if files_found_error == True: + logger.warning("No valid files found in content. Nothing to generate.") + files_found_error = False except Exception, e: logger.warning( "Caught exception \"{}\". Reloading.".format(e) diff --git a/pelican/utils.py b/pelican/utils.py index c79c7cee..ca3015ce 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -14,6 +14,9 @@ from operator import attrgetter logger = logging.getLogger(__name__) +class NoFilesError(Exception): + pass + def get_date(string): """Return a datetime object from a string. @@ -247,8 +250,7 @@ def files_changed(path, extensions): LAST_MTIME = mtime return True except ValueError: - logger.info("No files found in path") - return False + raise NoFilesError("No files with the given extension(s) found.") return False diff --git a/tests/test_utils.py b/tests/test_utils.py index 7ea0cf23..0ebaf346 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -96,8 +96,8 @@ class TestUtils(unittest.TestCase): os.mkdir(empty_path) os.mkdir(os.path.join(empty_path, "empty_folder")) shutil.copy(__file__, empty_path) - changed = utils.files_changed(empty_path, 'rst') - self.assertFalse(changed) + with self.assertRaises(NoFilesError): + utils.files_changed(empty_path, 'rst') except OSError: self.fail("OSError Exception in test_files_changed test") finally: From 95af2e46ec95647a055a029bc81b531ecca86bb6 Mon Sep 17 00:00:00 2001 From: tBunnyMan Date: Thu, 23 Aug 2012 13:13:41 -0700 Subject: [PATCH 06/16] Missed a line in commit. --- tests/test_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_utils.py b/tests/test_utils.py index 0ebaf346..148e322a 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -6,6 +6,7 @@ import time from pelican import utils from .support import get_article, unittest +from pelican.utils import NoFilesError class TestUtils(unittest.TestCase): From 831e1d04b98e872770af578bc3bedc84d42cd933 Mon Sep 17 00:00:00 2001 From: David Marble Date: Thu, 23 Aug 2012 18:31:45 -0700 Subject: [PATCH 07/16] docs: Pelican outputs to output/ not content/. Markdown ::: syntax must be indented --- docs/getting_started.rst | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/getting_started.rst b/docs/getting_started.rst index 282d6c23..b7cbe951 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -186,7 +186,7 @@ the content. The ``pelican`` command can also be run directly:: $ pelican /path/to/your/content/ [-s path/to/your/settings.py] -The above command will generate your weblog and save it in the ``content/`` +The above command will generate your weblog and save it in the ``output/`` folder, using the default theme to produce a simple site. The default theme is simple HTML without styling and is provided so folks may use it as a basis for creating their own themes. @@ -271,19 +271,21 @@ Pelican is able to provide colorized syntax highlighting for your code blocks. To do so, you have to use the following conventions (you need to put this in your content files). -For RestructuredText:: +For RestructuredText, use the code-block directive:: .. code-block:: identifier - your code goes here + -For Markdown, format your code blocks thusly:: +For Markdown, include the language identifier just above code blocks:: - :::identifier - your code goes here + :::identifier + + + (indent both the identifier and code) -The specified identifier should be one that appears on the -`list of available lexers `_. +The specified identifier (e.g. ``python``, ``ruby``) should be one that +appears on the `list of available lexers `_. Publishing drafts ----------------- From 9435b381ed042db13292d2fb474c400d0c595f91 Mon Sep 17 00:00:00 2001 From: tBunnyMan Date: Fri, 24 Aug 2012 13:14:14 -0700 Subject: [PATCH 08/16] Cleaned up tests. Used assertItemsEqual in article generation to create more precise tests than with an elif chain Separated out categories out into their own named test for clarity Closes #405 --- tests/test_generators.py | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/tests/test_generators.py b/tests/test_generators.py index e984b484..3a4ea1e3 100644 --- a/tests/test_generators.py +++ b/tests/test_generators.py @@ -63,29 +63,26 @@ class TestArticlesGenerator(unittest.TestCase): def test_generate_context(self): - settings = _DEFAULT_CONFIG.copy() - settings['ARTICLE_DIR'] = 'content' - settings['DEFAULT_CATEGORY'] = 'Default' - generator = ArticlesGenerator(settings.copy(), settings, CUR_DIR, - _DEFAULT_CONFIG['THEME'], None, - _DEFAULT_CONFIG['MARKUP']) - generator.generate_context() - for article in generator.articles: - relfilepath = os.path.relpath(article.filename, CUR_DIR) - if relfilepath == os.path.join("TestCategory", - "article_with_category.rst"): - self.assertEquals(article.category.name, 'yeah') - elif relfilepath == os.path.join("TestCategory", - "article_without_category.rst"): - self.assertEquals(article.category.name, 'TestCategory') - elif relfilepath == "article_without_category.rst": - self.assertEquals(article.category.name, 'Default') + generator = self.get_populated_generator() + articles = self.distill_articles(generator.articles) + articles_expected = [ + [u'Article title', 'published', 'Default', 'article'], + [u'Article with template', 'published', 'Default', 'custom'], + [u'Test md File', 'published', 'test', 'article'], + [u'This is a super article !', 'published', 'Yeah', 'article'], + [u'This is an article with category !', 'published', 'yeah', 'article'], + [u'This is an article without category !', 'published', 'Default', 'article'], + [u'This is an article without category !', 'published', 'TestCategory', 'article'], + [u'This is a super article !', 'published', 'yeah', 'article'] + ] + self.assertItemsEqual(articles_expected, articles) + def test_generate_categories(self): + + generator = self.get_populated_generator() categories = [cat.name for cat, _ in generator.categories] - # assert that the categories are ordered as expected - self.assertEquals( - categories, ['Default', 'TestCategory', 'Yeah', 'test', - 'yeah']) + categories_expected = ['Default', 'TestCategory', 'Yeah', 'test', 'yeah'] + self.assertEquals(categories, categories_expected) def test_direct_templates_save_as_default(self): From d1d737777c97146037e7b0c32dc973fe93cd6dc1 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 25 Aug 2012 22:50:19 +0200 Subject: [PATCH 09/16] fix issue #480 --- docs/settings.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/settings.rst b/docs/settings.rst index c219ed12..9ea6071d 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -468,7 +468,7 @@ template tag, for example: .. code-block:: jinja {% assets filters="cssmin", output="css/style.min.css", "css/inuit.css", "css/pygment-monokai.css", "css/main.css" %} - + {% endassets %} will produce a minified css file with the version identifier: From 472063e98c6a7e170c0cad34e2ef040ddc1b21e4 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 25 Aug 2012 23:16:50 +0200 Subject: [PATCH 10/16] add some doc for webassets: - usage of the sass compiler as discussed in PR #441 - debug mode and compilers (#481) --- docs/settings.rst | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 9ea6071d..ad08f020 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -73,7 +73,7 @@ Setting name (default value) What doe `SITENAME` (``'A Pelican Blog'``) Your site name `SITEURL` Base URL of your website. Not defined by default, so it is best to specify your SITEURL; if you do not, feeds - will not be generated with properly-formed URLs. You should + will not be generated with properly-formed URLs. You should include ``http://`` and your domain, with no trailing slash at the end. Example: ``SITEURL = 'http://mydomain.com'`` `STATIC_PATHS` (``['images']``) The static paths you want to have accessible @@ -95,12 +95,12 @@ Setting name (default value) What doe index pages for collections of content e.g. tags and category index pages. `PAGINATED_DIRECT_TEMPLATES` (``('index',)``) Provides the direct templates that should be paginated. -`SUMMARY_MAX_LENGTH` (``50``) When creating a short summary of an article, this will +`SUMMARY_MAX_LENGTH` (``50``) When creating a short summary of an article, this will be the default length in words of the text created. - This only applies if your content does not otherwise - specify a summary. Setting to None will cause the summary + This only applies if your content does not otherwise + specify a summary. Setting to None will cause the summary to be a copy of the original content. - + ===================================================================== ===================================================================== .. [#] Default is the system locale. @@ -367,7 +367,7 @@ Ordering content ================================================ ===================================================== Setting name (default value) What does it do? ================================================ ===================================================== -`NEWEST_FIRST_ARCHIVES` (``True``) Order archives by newest first by date. (False: +`NEWEST_FIRST_ARCHIVES` (``True``) Order archives by newest first by date. (False: orders by date with older articles first.) `REVERSE_CATEGORY_ORDER` (``False``) Reverse the category order. (True: lists by reverse alphabetical order; default lists alphabetically.) @@ -477,6 +477,15 @@ will produce a minified css file with the version identifier: +The filters can be combined, for example to use the `sass` compiler and minify +the output:: + +.. code-block:: jinja + +{% assets filters="sass,cssmin", output="css/style.min.css", "css/style.scss" %} + +{% endassets %} + Another example for javascript: .. code-block:: jinja @@ -491,6 +500,12 @@ will produce a minified and gzipped js file: +Pelican's debug mode is propagated to webassets to disable asset packaging, +and instead work with the uncompressed assets. However, this also means that +the `less` and `sass` files are not compiled, this should be fixed in a future +version of webassets (cf. the related `bug report +`_). + .. _webassets: https://github.com/miracle2k/webassets .. _documentation: http://webassets.readthedocs.org/en/latest/builtin_filters.html From 000210d875a9eab01f4133595311598de5634388 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Mon, 27 Aug 2012 13:09:25 -0700 Subject: [PATCH 11/16] Improve devserver durability. Refs #473. Three changes: 1. Fix inconsistent "pwd" behavior by using make's $(CURDIR) builtin. 2. Change bash shebang to the more-portable form. 3. Tell users when Pelican and SimpleHTTPServer have been backgrounded. --- pelican/tools/templates/Makefile.in | 2 +- pelican/tools/templates/develop_server.sh.in | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pelican/tools/templates/Makefile.in b/pelican/tools/templates/Makefile.in index 1445f0b0..4c5a4fcb 100644 --- a/pelican/tools/templates/Makefile.in +++ b/pelican/tools/templates/Makefile.in @@ -1,7 +1,7 @@ PELICAN=$pelican PELICANOPTS=$pelicanopts -BASEDIR=$$(pwd) +BASEDIR=$$(CURDIR) INPUTDIR=$$(BASEDIR)/content OUTPUTDIR=$$(BASEDIR)/output CONFFILE=$$(BASEDIR)/pelicanconf.py diff --git a/pelican/tools/templates/develop_server.sh.in b/pelican/tools/templates/develop_server.sh.in index b1e3b60c..3e97610b 100755 --- a/pelican/tools/templates/develop_server.sh.in +++ b/pelican/tools/templates/develop_server.sh.in @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash ## # This section should match your Makefile ## @@ -65,6 +65,7 @@ function start_up(){ python -m SimpleHTTPServer & echo $$! > $$SRV_PID cd $$BASEDIR + sleep 1 && echo 'Pelican and SimpleHTTPServer processes now running in background.' } ### From bba3caa697313a26c632275a0b32fb99c8d0d90d Mon Sep 17 00:00:00 2001 From: Alexis Metaireau Date: Mon, 27 Aug 2012 18:40:02 +0200 Subject: [PATCH 12/16] avoid repeatition in the functional tests --- tests/test_pelican.py | 56 +++++++++++-------------------------------- 1 file changed, 14 insertions(+), 42 deletions(-) diff --git a/tests/test_pelican.py b/tests/test_pelican.py index 15088ed0..78f083f9 100644 --- a/tests/test_pelican.py +++ b/tests/test_pelican.py @@ -35,6 +35,18 @@ class TestPelican(unittest.TestCase): rmtree(self.temp_path) locale.setlocale(locale.LC_ALL, self.old_locale) + def assertFilesEqual(self, diff): + msg = "some generated files differ from the expected functional " \ + "tests output.\n" \ + "This is probably because the HTML generated files " \ + "changed. If these changes are normal, please refer " \ + "to docs/contribute.rst to update the expected " \ + "output of the functional tests." + + self.assertEqual(diff.left_only, [], msg=msg) + self.assertEqual(diff.right_only, [], msg=msg) + self.assertEqual(diff.diff_files, [], msg=msg) + @unittest.skip("Test failing") def test_basic_generation_works(self): # when running pelican without settings, it should pick up the default @@ -47,27 +59,7 @@ class TestPelican(unittest.TestCase): pelican.run() diff = dircmp( self.temp_path, os.sep.join((OUTPUT_PATH, "basic"))) - self.assertEqual(diff.left_only, [], msg="some generated " \ - "files are absent from the expected functional " \ - "tests output.\n" \ - "This is probably because the HTML generated files " \ - "changed. If these changes are normal, please refer " \ - "to docs/contribute.rst to update the expected " \ - "output of the functional tests.") - self.assertEqual(diff.right_only, [], msg="some files from " \ - "the expected functional tests output are absent " \ - "from the current output.\n" \ - "This is probably because the HTML generated files " \ - "changed. If these changes are normal, please refer " \ - "to docs/contribute.rst to update the expected " \ - "output of the functional tests.") - self.assertEqual(diff.diff_files, [], msg="some generated " \ - "files differ from the expected functional tests " \ - "output.\n" \ - "This is probably because the HTML generated files " \ - "changed. If these changes are normal, please refer " \ - "to docs/contribute.rst to update the expected " \ - "output of the functional tests.") + self.assertFilesEqual(diff) def test_custom_generation_works(self): # the same thing with a specified set of settings should work @@ -75,24 +67,4 @@ class TestPelican(unittest.TestCase): settings=read_settings(SAMPLE_CONFIG)) pelican.run() diff = dircmp(self.temp_path, os.sep.join((OUTPUT_PATH, "custom"))) - self.assertEqual(diff.left_only, [], msg="some generated " \ - "files are absent from the expected functional " \ - "tests output.\n" \ - "This is probably because the HTML generated files " \ - "changed. If these changes are normal, please refer " \ - "to docs/contribute.rst to update the expected " \ - "output of the functional tests.") - self.assertEqual(diff.right_only, [], msg="some files from " \ - "the expected functional tests output are absent " \ - "from the current output.\n" \ - "This is probably because the HTML generated files " \ - "changed. If these changes are normal, please refer " \ - "to docs/contribute.rst to update the expected " \ - "output of the functional tests.") - self.assertEqual(diff.diff_files, [], msg="some generated " \ - "files differ from the expected functional tests " \ - "output.\n" \ - "This is probably because the HTML generated files " \ - "changed. If these changes are normal, please refer " \ - "to docs/contribute.rst to update the expected " \ - "output of the functional tests.") + self.assertFilesEqual(diff) From 644fd4ed5f95715d2207b146de9e0eafeee96f37 Mon Sep 17 00:00:00 2001 From: tBunnyMan Date: Wed, 29 Aug 2012 12:17:59 -0700 Subject: [PATCH 13/16] Deep copy _DEFAULT_SETTINGS instead of linking. This caused the defaults to be overwritten and edge case bugs with tests. The test for empty setting needed to be updated to reflect that the method for setting up the local settings sets extra settings. --- pelican/__init__.py | 3 ++- pelican/contents.py | 3 ++- pelican/settings.py | 10 +++++----- tests/test_settings.py | 8 ++++++-- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index ba48c4c7..8c4930f9 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -1,3 +1,4 @@ +import copy import os import re import sys @@ -29,7 +30,7 @@ class Pelican(object): before doing anything else. """ if settings is None: - settings = _DEFAULT_CONFIG + settings = copy.deepcopy(_DEFAULT_CONFIG) self.path = path or settings['PATH'] if not self.path: diff --git a/pelican/contents.py b/pelican/contents.py index a5e3be8f..851607a5 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import copy import locale import logging import functools @@ -29,7 +30,7 @@ class Page(object): if not metadata: metadata = {} if not settings: - settings = _DEFAULT_CONFIG + settings = copy.deepcopy(_DEFAULT_CONFIG) self.settings = settings self._content = content diff --git a/pelican/settings.py b/pelican/settings.py index 645a9809..92c68ddc 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import copy import imp import inspect import os @@ -81,7 +82,7 @@ def read_settings(filename=None): if filename: local_settings = get_settings_from_file(filename) else: - local_settings = _DEFAULT_CONFIG + local_settings = copy.deepcopy(_DEFAULT_CONFIG) configured_settings = configure_settings(local_settings, None, filename) return configured_settings @@ -89,10 +90,9 @@ def read_settings(filename=None): def get_settings_from_module(module=None, default_settings=_DEFAULT_CONFIG): """ Load settings from a module, returning a dict. - """ - context = default_settings.copy() + context = copy.deepcopy(default_settings) if module is not None: context.update( (k, v) for k, v in inspect.getmembers(module) if k.isupper() @@ -114,7 +114,7 @@ def get_settings_from_file(filename, default_settings=_DEFAULT_CONFIG): def configure_settings(settings, default_settings=None, filename=None): """Provide optimizations, error checking, and warnings for loaded settings""" if default_settings is None: - default_settings = _DEFAULT_CONFIG + default_settings = copy.deepcopy(_DEFAULT_CONFIG) # Make the paths relative to the settings file if filename: @@ -138,7 +138,7 @@ def configure_settings(settings, default_settings=None, filename=None): for locale_ in locales: try: locale.setlocale(locale.LC_ALL, locale_) - break # break if it is successfull + break # break if it is successful except locale.Error: pass else: diff --git a/tests/test_settings.py b/tests/test_settings.py index 25df74bd..7b534616 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -1,6 +1,7 @@ +import copy from os.path import dirname, abspath, join -from pelican.settings import read_settings, configure_settings, _DEFAULT_CONFIG +from pelican.settings import read_settings, configure_settings, _DEFAULT_CONFIG, DEFAULT_THEME from .support import unittest @@ -31,7 +32,10 @@ class TestSettingsConfiguration(unittest.TestCase): def test_read_empty_settings(self): """providing no file should return the default values.""" settings = read_settings(None) - self.assertDictEqual(settings, _DEFAULT_CONFIG) + expected = copy.deepcopy(_DEFAULT_CONFIG) + expected["FEED_DOMAIN"] = '' #This is added by configure settings + self.maxDiff = None + self.assertDictEqual(settings, expected) def test_configure_settings(self): """Manipulations to settings should be applied correctly.""" From 663d1e7347116b46746c728278747658a4c36ea1 Mon Sep 17 00:00:00 2001 From: tBunnyMan Date: Thu, 30 Aug 2012 14:50:52 -0700 Subject: [PATCH 14/16] Added extra tests to help prevent regression. --- tests/test_settings.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_settings.py b/tests/test_settings.py index 7b534616..873df824 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -37,6 +37,22 @@ class TestSettingsConfiguration(unittest.TestCase): 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_configure_settings(self): """Manipulations to settings should be applied correctly.""" From b91197271694e101ec3faa90c01f7cfbff692aea Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Fri, 31 Aug 2012 23:12:09 -0400 Subject: [PATCH 15/16] Add Gittip to social icons. This allows someone to include their gittip link so people can donate to them. --- pelican/themes/notmyidea/static/css/main.css | 3 ++- .../notmyidea/static/images/icons/gittip.png | Bin 0 -> 671 bytes 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 pelican/themes/notmyidea/static/images/icons/gittip.png diff --git a/pelican/themes/notmyidea/static/css/main.css b/pelican/themes/notmyidea/static/css/main.css index bd1eba75..b4ba7888 100644 --- a/pelican/themes/notmyidea/static/css/main.css +++ b/pelican/themes/notmyidea/static/css/main.css @@ -308,7 +308,8 @@ img.left, figure.left {float: left; margin: 0 2em 2em 0;} .social a[type$='atom+xml'], .social a[type$='rss+xml'] {background-image: url('../images/icons/rss.png');} .social a[href*='twitter.com'] {background-image: url('../images/icons/twitter.png');} .social a[href*='linkedin.com'] {background-image: url('../images/icons/linkedin.png');} - .social a[href*='gitorious.org'] {background-image: url('../images/icons/gitorious.org');} + .social a[href*='gitorious.org'] {background-image: url('../images/icons/gitorious.png');} + .social a[href*='gittip.com'] {background-image: url('../images/icons/gittip.png');} /* About diff --git a/pelican/themes/notmyidea/static/images/icons/gittip.png b/pelican/themes/notmyidea/static/images/icons/gittip.png new file mode 100644 index 0000000000000000000000000000000000000000..bb12a1391ace96a9d4cedffebedd7db7d6a84d48 GIT binary patch literal 671 zcmV;Q0$}}#P)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iyq( z0|^AkZL;YA00JLLL_t(I%cYY)XcJ)=#((d3xhB;?Z4q3Q2+iPN8`Po0btyO{gS$f$ z>?&0%sHg=UtYR`L-82b;;35bKaWNLW7QsJgH?8zfG)VDJ#Ui$(&Fhf6cnLcB4F~sq z&+|Tazk5#bRx06;FS(?XPM@lt5s_ga>K!ksYDGjcxuW~t*8&JYI_Z!rx}*|LUW5@q z{j7>&K%=-!BSF%K72e^Ox7SrX27-KkiWRTI*7g_!Knd|3I zva$Zlm&dGlZ0}VOIjpL3?Dz?WjwZ3~E)ES2@nz)`k!{;KJ9(3EB*M;~UV3-$;ko++ zV~nb5IONe50Aoy(tO%+~dh8rO*Va(g)?QPio=k>~|EWZTnaAZ8ED<3Tve~h#r^P}< zn5jI%vO>NrYJ!Ao)kR)Cd+G&r&{yT-@&Yesr`z@ou;ky^b{A9EE>W*}wU8^i1L Date: Tue, 21 Aug 2012 13:08:21 +0200 Subject: [PATCH 16/16] Sitemap plugin & `get_generators` signal This is a combination of 13 commits: 1. New signal for registering custom generators 2. New plugin: pelican.plugins.sitemap 3. pelican.plugins.sitemap: more settings 4. pelican.plugins.sitemap: translations are indexed 5. pelican.plugins.sitemap: added documentation 6. pelican.plugins.sitemap: added XML DTD & W3C dates 7. pelican.plugins.sitemap: removed a bug 8. the `get_generators` can now return a tuple 9. pelican.plugins.sitemap: cleaned the code 10. pelican.plugin.sitemap: settings changes 11. sitemap plugin: improved configuration & documentation 12. sitemap plugin: :set spell 13. sitemap plugin: removed useless whitespaces --- docs/plugins.rst | 79 ++++++++++++++ pelican/__init__.py | 14 ++- pelican/plugins/sitemap.py | 208 +++++++++++++++++++++++++++++++++++++ pelican/signals.py | 1 + 4 files changed, 301 insertions(+), 1 deletion(-) create mode 100644 pelican/plugins/sitemap.py diff --git a/docs/plugins.rst b/docs/plugins.rst index 53858668..99c0429a 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -59,6 +59,9 @@ Signal Arguments Description initialized pelican object article_generate_context article_generator, metadata article_generator_init article_generator invoked in the ArticlesGenerator.__init__ +get_generators generators invoked in Pelican.get_generator_classes, + can return a Generator, or several + generator in a tuple or in a list. pages_generate_context pages_generator, metadata pages_generator_init pages_generator invoked in the PagesGenerator.__init__ ========================= ============================ ========================================= @@ -108,3 +111,79 @@ variable, as in the example:: ``github_activity`` is a list of lists. The first element is the title and the second element is the raw HTML from GitHub. + + +Sitemap +------- + +The plugin generates a sitemap of the blog. +It can generates plain text sitemaps or XML sitemaps. + +Configuration +""""""""""""" + +You can use the setting ``SITEMAP`` variable to configure the behavior of the +plugin. + +The ``SITEMAP`` variable must be a Python dictionary, it can contain tree keys: + + +- ``format``, which set the output format of the plugin (``xml`` or ``txt``) + +- ``priorities``, which is a dictionary with three keys: + + - ``articles``, the priority for the URLs of the articles and their + translations + + - ``pages``, the priority for the URLs of the static pages + + - ``indexes``, the priority for the URLs of the index pages, such as tags, + author pages, categories indexes, archives, etc... + + All the values of this dictionary must be decimal numbers between ``0`` and ``1``. + +- ``changefreqs``, which is a dictionary with three items: + + - ``articles``, the update frequency of the articles + + - ``pages``, the update frequency of the pages + + - ``indexes``, the update frequency of the index pages + + An valid value is ``always``, ``hourly``, ``daily``, ``weekly``, ``monthly``, + ``yearly`` or ``never``. + + +If a key is missing or a value is incorrect, it will be replaced with the +default value. + +The sitemap is saved in ``/sitemap.``. + +.. note:: + ``priorities`` and ``changefreqs`` are informations for search engines. + They are only used in the XML sitemaps. + For more information: + + +Example +""""""" + +Here is an example of configuration (it's also the default settings): + +.. code-block:: python + + PLUGINS=['pelican.plugins.sitemap',] + + SITEMAP = { + 'format': 'xml', + 'priorities': { + 'articles': 0.5, + 'indexes': 0.5, + 'pages': 0.5 + }, + 'changefreqs': { + 'articles': 'monthly', + 'indexes': 'daily', + 'pages': 'monthly' + } + } diff --git a/pelican/__init__.py b/pelican/__init__.py index a69752d8..b9f9bb22 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -8,7 +8,7 @@ import argparse from pelican import signals -from pelican.generators import (ArticlesGenerator, PagesGenerator, +from pelican.generators import (Generator, ArticlesGenerator, PagesGenerator, StaticGenerator, PdfGenerator, LessCSSGenerator) from pelican.log import init from pelican.settings import read_settings, _DEFAULT_CONFIG @@ -185,6 +185,18 @@ class Pelican(object): generators.append(PdfGenerator) if self.settings['LESS_GENERATOR']: # can be True or PATH to lessc generators.append(LessCSSGenerator) + + for pair in signals.get_generators.send(self): + (funct, value) = pair + + if not isinstance(value, (tuple, list)): + value = (value, ) + + for v in value: + if isinstance(v, type): + logger.debug('Found generator: {0}'.format(v)) + generators.append(v) + return generators def get_writer(self): diff --git a/pelican/plugins/sitemap.py b/pelican/plugins/sitemap.py new file mode 100644 index 00000000..6402ba9c --- /dev/null +++ b/pelican/plugins/sitemap.py @@ -0,0 +1,208 @@ +import os.path + +from datetime import datetime +from logging import debug, warning, error, info +from codecs import open + +from pelican import signals, contents + +TXT_HEADER = u"""{0}/index.html +{0}/archives.html +{0}/tags.html +{0}/categories.html +""" + +XML_HEADER = u""" + + + + {0}/index.html + {1} + {2} + {3} + + + + {0}/archives.html + {1} + {2} + {3} + + + + {0}/tags.html + {1} + {2} + {3} + + + + {0}/categories.html + {1} + {2} + {3} + +""" + +XML_URL = u""" + + {0}/{1} + {2} + {3} + {4} + +""" + +XML_FOOTER = u""" + +""" + + +def format_date(date): + if date.tzinfo: + tz = date.strftime('%s') + tz = tz[:-2] + ':' + tz[-2:] + else: + tz = "-00:00" + return date.strftime("%Y-%m-%dT%H:%M:%S") + tz + + + +class SitemapGenerator(object): + + def __init__(self, context, settings, path, theme, output_path, *null): + + self.output_path = output_path + self.context = context + self.now = datetime.now() + self.siteurl = settings.get('SITEURL') + + self.format = 'xml' + + self.changefreqs = { + 'articles': 'monthly', + 'indexes': 'daily', + 'pages': 'monthly' + } + + self.priorities = { + 'articles': 0.5, + 'indexes': 0.5, + 'pages': 0.5 + } + + config = settings.get('SITEMAP', {}) + + if not isinstance(config, dict): + warning("sitemap plugin: the SITEMAP setting must be a dict") + else: + fmt = config.get('format') + pris = config.get('priorities') + chfreqs = config.get('changefreqs') + + if fmt not in ('xml', 'txt'): + warning("sitemap plugin: SITEMAP['format'] must be `txt' or `xml'") + warning("sitemap plugin: Setting SITEMAP['format'] on `xml'") + elif fmt == 'txt': + self.format = fmt + return + + valid_keys = ('articles', 'indexes', 'pages') + valid_chfreqs = ('always', 'hourly', 'daily', 'weekly', 'monthly', + 'yearly', 'never') + + if isinstance(pris, dict): + for k, v in pris.iteritems(): + if k in valid_keys and not isinstance(v, (int, float)): + default = self.priorities[k] + warning("sitemap plugin: priorities must be numbers") + warning("sitemap plugin: setting SITEMAP['priorities']" + "['{0}'] on {1}".format(k, default)) + pris[k] = default + self.priorities.update(pris) + elif pris is not None: + warning("sitemap plugin: SITEMAP['priorities'] must be a dict") + warning("sitemap plugin: using the default values") + + if isinstance(chfreqs, dict): + for k, v in chfreqs.iteritems(): + if k in valid_keys and v not in valid_chfreqs: + default = self.changefreqs[k] + warning("sitemap plugin: invalid changefreq `{0}'".format(v)) + warning("sitemap plugin: setting SITEMAP['changefreqs']" + "['{0}'] on '{1}'".format(k, default)) + chfreqs[k] = default + self.changefreqs.update(chfreqs) + elif chfreqs is not None: + warning("sitemap plugin: SITEMAP['changefreqs'] must be a dict") + warning("sitemap plugin: using the default values") + + + + def write_url(self, page, fd): + + if getattr(page, 'status', 'published') != 'published': + return + + lastmod = format_date(getattr(page, 'date', self.now)) + + if isinstance(page, contents.Article): + pri = self.priorities['articles'] + chfreq = self.changefreqs['articles'] + elif isinstance(page, contents.Page): + pri = self.priorities['pages'] + chfreq = self.changefreqs['pages'] + else: + pri = self.priorities['indexes'] + chfreq = self.changefreqs['indexes'] + + + if self.format == 'xml': + fd.write(XML_URL.format(self.siteurl, page.url, lastmod, chfreq, pri)) + else: + fd.write(self.siteurl + '/' + loc + '\n') + + + def generate_output(self, writer): + path = os.path.join(self.output_path, 'sitemap.{0}'.format(self.format)) + + pages = self.context['pages'] + self.context['articles'] \ + + [ c for (c, a) in self.context['categories']] \ + + [ t for (t, a) in self.context['tags']] \ + + [ a for (a, b) in self.context['authors']] + + for article in self.context['articles']: + pages += article.translations + + + info('writing {0}'.format(path)) + + with open(path, 'w', encoding='utf-8') as fd: + + if self.format == 'xml': + fd.write(XML_HEADER.format( + self.siteurl, + format_date(self.now), + self.changefreqs['indexes'], + self.priorities['indexes'] + ) + ) + else: + fd.write(TXT_HEADER.format(self.siteurl)) + + for page in pages: + self.write_url(page, fd) + + if self.format == 'xml': + fd.write(XML_FOOTER) + + + +def get_generators(generators): + return SitemapGenerator + + +def register(): + signals.get_generators.connect(get_generators) diff --git a/pelican/signals.py b/pelican/signals.py index 4d9ab512..7ee88a0a 100644 --- a/pelican/signals.py +++ b/pelican/signals.py @@ -3,5 +3,6 @@ from blinker import signal initialized = signal('pelican_initialized') article_generate_context = signal('article_generate_context') article_generator_init = signal('article_generator_init') +get_generators = signal('get_generators') pages_generate_context = signal('pages_generate_context') pages_generator_init = signal('pages_generator_init')