From 5fa3504ad08b38bdd88602ba239e7a22f55a8dfb Mon Sep 17 00:00:00 2001 From: Joe Shaw Date: Wed, 8 May 2013 09:40:20 -0400 Subject: [PATCH 001/160] use string find() instead of index(). Fixes #880. We're expecting a non-match to return -1, which is what find() does, but index() instead throws a ValueError. --- pelican/tools/pelican_import.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index 9e477c2c..8ebb7659 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -37,7 +37,7 @@ def decode_wp_content(content, br=True): pre_index = 0 for pre_part in pre_parts: - start = pre_part.index(" Date: Sat, 18 May 2013 06:33:49 -0700 Subject: [PATCH 002/160] makes Pelican-quickstart debuggable in PyCharm --- pelican/tools/pelican_quickstart.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pelican/tools/pelican_quickstart.py b/pelican/tools/pelican_quickstart.py index a6045256..8d697fa3 100755 --- a/pelican/tools/pelican_quickstart.py +++ b/pelican/tools/pelican_quickstart.py @@ -283,3 +283,6 @@ needed by Pelican. print('Error: {0}'.format(e)) print('Done. Your new project is available at %s' % CONF['basedir']) + +if __name__ == "__main__": + main() \ No newline at end of file From 7a3bc410d0757ee4bec22ffaf6920e2f27878fbd Mon Sep 17 00:00:00 2001 From: Dominique Plante Date: Thu, 23 May 2013 21:53:46 -0700 Subject: [PATCH 003/160] Add test for the case where we try to read a file with an unhandled extension --- .../content/article_with_metadata.unknownextension | 12 ++++++++++++ pelican/tests/test_readers.py | 11 +++++++++++ 2 files changed, 23 insertions(+) create mode 100644 pelican/tests/content/article_with_metadata.unknownextension diff --git a/pelican/tests/content/article_with_metadata.unknownextension b/pelican/tests/content/article_with_metadata.unknownextension new file mode 100644 index 00000000..d4bac1c0 --- /dev/null +++ b/pelican/tests/content/article_with_metadata.unknownextension @@ -0,0 +1,12 @@ + +This is a super article ! +######################### + +:tags: foo, bar, foobar +:date: 2010-12-02 10:14 +:category: yeah +:author: Alexis Métaireau +:summary: + Multi-line metadata should be supported + as well as **inline markup**. +:custom_field: http://notmyidea.org diff --git a/pelican/tests/test_readers.py b/pelican/tests/test_readers.py index 47cb70ee..c3c7eaef 100644 --- a/pelican/tests/test_readers.py +++ b/pelican/tests/test_readers.py @@ -14,6 +14,17 @@ CONTENT_PATH = os.path.join(CUR_DIR, 'content') def _path(*args): return os.path.join(CONTENT_PATH, *args) +class ReaderTests(unittest.TestCase): + def test_readfile_unknown_extension(self): + f = _path('article_with_metadata.unknownextension') + with self.assertRaises(TypeError) as cm: + readers.read_file(f) + ex = cm.exception + self.assertEqual('Pelican does not know how to parse ' + f, ex.message) + #, setattr, root.c1.c2, 'text', "test") + # self.assertTrue(1 == 0) + # except TypeError: + # self.assertTrue(1 == 1) class RstReaderTest(unittest.TestCase): From 0ecae1f50d193c18980f5c023fe93b6d94e48c13 Mon Sep 17 00:00:00 2001 From: Rogdham Date: Sun, 26 May 2013 11:38:55 +0100 Subject: [PATCH 004/160] Encoding issue in pelican-quickstart. Fixes #904 --- pelican/tools/pelican_quickstart.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pelican/tools/pelican_quickstart.py b/pelican/tools/pelican_quickstart.py index a6045256..e62bc5ca 100755 --- a/pelican/tools/pelican_quickstart.py +++ b/pelican/tools/pelican_quickstart.py @@ -37,8 +37,7 @@ def _input_compat(prompt): if six.PY3: r = input(prompt) else: - # FIXME: why use this with @decoding_strings? - r = raw_input(prompt).decode('utf-8') + r = raw_input(prompt) return r if six.PY3: From 3f91165f094228b061150fc587816457e619aef1 Mon Sep 17 00:00:00 2001 From: Simon Conseil Date: Tue, 21 May 2013 17:15:56 +0200 Subject: [PATCH 005/160] Ensure that markup is a tuple. `self.markup` is a list when using the `-m|--markup` cli option, but testing the extension with `endswith` works only with tuples. --- pelican/generators.py | 2 +- pelican/tests/test_generators.py | 26 ++++++++++++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/pelican/generators.py b/pelican/generators.py index f2fa0e33..94c425f9 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -96,7 +96,7 @@ class Generator(object): extensions are allowed) """ if extensions is None: - extensions = self.markup + extensions = tuple(self.markup) basename = os.path.basename(path) if extensions is False or basename.endswith(extensions): return True diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index fa57322d..8922a8eb 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -1,14 +1,13 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals, print_function -from mock import MagicMock import os - from codecs import open -from tempfile import mkdtemp +from mock import MagicMock from shutil import rmtree +from tempfile import mkdtemp -from pelican.generators import (ArticlesGenerator, PagesGenerator, +from pelican.generators import (Generator, ArticlesGenerator, PagesGenerator, TemplatePagesGenerator) from pelican.writers import Writer from pelican.settings import _DEFAULT_CONFIG @@ -17,6 +16,25 @@ from pelican.tests.support import unittest, get_settings CUR_DIR = os.path.dirname(__file__) +class TestGenerator(unittest.TestCase): + def setUp(self): + self.settings = get_settings() + self.generator = Generator(self.settings.copy(), self.settings, + CUR_DIR, self.settings['THEME'], None, + self.settings['MARKUP']) + + def test_include_path(self): + filename = os.path.join(CUR_DIR, 'content', 'article.rst') + include_path = self.generator._include_path + self.assertTrue(include_path(filename)) + self.assertTrue(include_path(filename, extensions=('rst',))) + self.assertFalse(include_path(filename, extensions=('md',))) + + # markup must be a tuple, test that this works also with a list + self.generator.markup = ['rst', 'md'] + self.assertTrue(include_path(filename)) + + class TestArticlesGenerator(unittest.TestCase): def setUp(self): From ba3e14dd1d89bed8fcac11c5b67fd614cf52d053 Mon Sep 17 00:00:00 2001 From: Thanos Lefteris Date: Mon, 3 Jun 2013 18:23:07 +0300 Subject: [PATCH 006/160] Docs: Add six, markupsafe to install dependencies --- docs/getting_started.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/getting_started.rst b/docs/getting_started.rst index ddffb5ff..b41f8c18 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -121,6 +121,10 @@ automatically installed without any action on your part: broadcast signaling system * `unidecode `_, for ASCII transliterations of Unicode text +* `six `_, for Python 2 and 3 compatibility + utilities +* `MarkupSafe `_, for a markup safe + string implementation If you want the following optional packages, you will need to install them manually via ``pip``: From cc156299660458125f47b4c52836c4c58291b3d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20M=C3=A9taireau?= Date: Fri, 7 Jun 2013 00:50:51 +0200 Subject: [PATCH 007/160] Don't include all the .py files in the root folder --- MANIFEST.in | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 136243c0..dcf9ea45 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,3 @@ include *.rst -global-include *.py -recursive-include pelican *.html *.css *png *.in *.rst *.md *.mkd *.xml +recursive-include pelican *.html *.css *png *.in *.rst *.md *.mkd *.xml *.py include LICENSE THANKS docs/changelog.rst From edcc027d894a6933661340c203cb9cc717d2b99f Mon Sep 17 00:00:00 2001 From: Richard Brooksby Date: Sat, 8 Jun 2013 23:40:16 +0100 Subject: [PATCH 008/160] Added lxml to the list of dependencies for the tox tests. My system python installs are completely clean except for virtualenv, so tox needs a complete set of non-default modules. --- tox.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tox.ini b/tox.ini index d9d759e9..8763c963 100644 --- a/tox.ini +++ b/tox.ini @@ -15,6 +15,7 @@ deps = Markdown BeautifulSoup4 typogrify + lxml [testenv:py33] deps = @@ -23,3 +24,4 @@ deps = BeautifulSoup4 git+https://github.com/dmdm/smartypants.git#egg=smartypants git+https://github.com/dmdm/typogrify.git@py3k#egg=typogrify + lxml From 1fcf4a6550d6ef8ffb3d8c24376cae63f939f5c9 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Mon, 10 Jun 2013 19:42:53 -0700 Subject: [PATCH 009/160] Add documentation for ARCHIVES_SAVE_AS setting While this setting has existed for some time, there does not seem to have been any documentation for it until now. --- docs/settings.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/settings.rst b/docs/settings.rst index 3a32ad22..0a04f804 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -242,6 +242,7 @@ Setting name (default value) What does it do? `_SAVE_AS` The location to save content generated from direct templates. Where is the upper case template name. +`ARCHIVES_SAVE_AS` (``'archives.html'``) The location to save the article archives page. `YEAR_ARCHIVE_SAVE_AS` (False) The location to save per-year archives of your posts. `MONTH_ARCHIVE_SAVE_AS` (False) The location to save per-month archives of your From 228fc82fc9c09493748e7fdf949fc1942cad0a7e Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 11 Jun 2013 21:40:13 -0400 Subject: [PATCH 010/160] utils: Add some ISO 8601 forms to get_date() Support the forms listed by the W3C [1]. I also removed the '%Y-%d-%m' form, which can be confused with the '%Y-%m-%d' ISO form. The new ISO forms can use 'Z' to designate UTC or '[+-]HHMM' to specify offsets from UTC. Other time zone designators are not supported. The '%z' directive has only been supported since Python 3.2 [2], so if you're running Pelican on Python 2.7, you're stuck with 'Z' for UTC. Conveniently, we get ValueErrors for both invalid directives and data/format missmatches, so we don't need special handling for the 2.7 case inside get_date(). [1]: http://www.w3.org/TR/NOTE-datetime [2]: http://bugs.python.org/issue6641 --- docs/getting_started.rst | 6 ++++- pelican/tests/test_utils.py | 52 +++++++++++++++++++++++++------------ pelican/utils.py | 50 +++++++++++++++++++++++------------ 3 files changed, 74 insertions(+), 34 deletions(-) diff --git a/docs/getting_started.rst b/docs/getting_started.rst index b41f8c18..7cd3267e 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -286,7 +286,10 @@ by the directory in which the file resides. For example, a file located at ``python/foobar/myfoobar.rst`` will have a category of ``foobar``. If you would like to organize your files in other ways where the name of the subfolder would not be a good category name, you can set the setting ``USE_FOLDER_AS_CATEGORY`` -to ``False``. +to ``False``. When parsing dates given in the page metadata, Pelican supports +the W3C's `suggested subset ISO 8601`__. + +__ `W3C ISO 8601`_ If you do not explicitly specify summary metadata for a given post, the ``SUMMARY_MAX_LENGTH`` setting can be used to specify how many words from the @@ -478,3 +481,4 @@ metadata. That article will then be output to the ``drafts`` folder and not listed on the index page nor on any category page. .. _virtualenv: http://www.virtualenv.org/ +.. _W3C ISO 8601: http://www.w3.org/TR/NOTE-datetime diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index 8b0dc13e..77e3bccb 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -6,7 +6,7 @@ import os import datetime import time import locale -from sys import platform +from sys import platform, version_info from tempfile import mkdtemp from pelican.generators import TemplatePagesGenerator @@ -37,26 +37,44 @@ class TestUtils(LoggedTestCase): def test_get_date(self): # valid ones date = datetime.datetime(year=2012, month=11, day=22) - date_hour = datetime.datetime(year=2012, month=11, day=22, hour=22, - minute=11) - date_hour_sec = datetime.datetime(year=2012, month=11, day=22, hour=22, - minute=11, second=10) - dates = {'2012-11-22': date, - '2012/11/22': date, - '2012-11-22 22:11': date_hour, - '2012/11/22 22:11': date_hour, - '22-11-2012': date, - '22/11/2012': date, - '22.11.2012': date, - '2012-22-11': date, - '22.11.2012 22:11': date_hour, - '2012-11-22 22:11:10': date_hour_sec} + date_hour = datetime.datetime( + year=2012, month=11, day=22, hour=22, minute=11) + date_hour_sec = datetime.datetime( + year=2012, month=11, day=22, hour=22, minute=11, second=10) + date_hour_sec_z = datetime.datetime( + year=2012, month=11, day=22, hour=22, minute=11, second=10, + tzinfo=datetime.timezone.utc) + date_hour_sec_0430 = datetime.datetime( + year=2012, month=11, day=22, hour=22, minute=11, second=10, + tzinfo=datetime.timezone(datetime.timedelta(hours=4, minutes=30))) + date_hour_sec_frac_z = datetime.datetime( + year=2012, month=11, day=22, hour=22, minute=11, second=10, + microsecond=123000, tzinfo=datetime.timezone.utc) + dates = { + '2012-11-22': date, + '2012/11/22': date, + '2012-11-22 22:11': date_hour, + '2012/11/22 22:11': date_hour, + '22-11-2012': date, + '22/11/2012': date, + '22.11.2012': date, + '22.11.2012 22:11': date_hour, + '2012-11-22 22:11:10': date_hour_sec, + '2012-11-22T22:11:10Z': date_hour_sec_z, + '2012-11-22T22:11:10+0430': date_hour_sec_0430, + '2012-11-22T22:11:10.123Z': date_hour_sec_frac_z, + } + + # invalid ones + invalid_dates = ['2010-110-12', 'yay'] + + if version_info < (3, 2): + dates.pop('2012-11-22T22:11:10-0500') + invalid_dates.append('2012-11-22T22:11:10-0500') for value, expected in dates.items(): self.assertEqual(utils.get_date(value), expected, value) - # invalid ones - invalid_dates = ('2010-110-12', 'yay') for item in invalid_dates: self.assertRaises(ValueError, utils.get_date, item) diff --git a/pelican/utils.py b/pelican/utils.py index 4e7bdbd1..e2e69121 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -15,7 +15,7 @@ from collections import Hashable from functools import partial from codecs import open, BOM_UTF8 -from datetime import datetime +from datetime import datetime, timezone from itertools import groupby from jinja2 import Markup from operator import attrgetter @@ -180,17 +180,39 @@ def get_date(string): If no format matches the given date, raise a ValueError. """ string = re.sub(' +', ' ', string) - formats = ['%Y-%m-%d %H:%M', '%Y/%m/%d %H:%M', - '%Y-%m-%d', '%Y/%m/%d', - '%d-%m-%Y', '%Y-%d-%m', # Weird ones - '%d/%m/%Y', '%d.%m.%Y', - '%d.%m.%Y %H:%M', '%Y-%m-%d %H:%M:%S'] + formats = [ + # ISO 8601 + '%Y', + '%Y-%m', + '%Y-%m-%d', + '%Y-%m-%dT%H:%M%z', + '%Y-%m-%dT%H:%MZ', + '%Y-%m-%dT%H:%M', + '%Y-%m-%dT%H:%M:%S%z', + '%Y-%m-%dT%H:%M:%SZ', + '%Y-%m-%dT%H:%M:%S', + '%Y-%m-%dT%H:%M:%S.%f%z', + '%Y-%m-%dT%H:%M:%S.%fZ', + '%Y-%m-%dT%H:%M:%S.%f', + # end ISO 8601 forms + '%Y-%m-%d %H:%M', + '%Y-%m-%d %H:%M:%S', + '%Y/%m/%d %H:%M', + '%Y/%m/%d', + '%d-%m-%Y', + '%d.%m.%Y %H:%M', + '%d.%m.%Y', + '%d/%m/%Y', + ] for date_format in formats: try: - return datetime.strptime(string, date_format) + date = datetime.strptime(string, date_format) except ValueError: - pass - raise ValueError("'%s' is not a valid date" % string) + continue + if date_format.endswith('Z'): + date = date.replace(tzinfo=timezone.utc) + return date + raise ValueError('{0!r} is not a valid date'.format(string)) class pelican_open(object): @@ -510,15 +532,11 @@ def file_watcher(path): def set_date_tzinfo(d, tz_name=None): - """ Date without tzinfo shoudbe utc. - This function set the right tz to date that aren't utc and don't have - tzinfo. - """ - if tz_name is not None: + """Set the timezone for dates that don't have tzinfo""" + if tz_name and not d.tzinfo: tz = pytz.timezone(tz_name) return tz.localize(d) - else: - return d + return d def mkdir_p(path): From 1102143c330573f25a0fad29349cc131b171e67c Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 11 Jun 2013 22:32:51 -0400 Subject: [PATCH 011/160] utils: Use pytz instead of datetime.timezone for timezones datetime.timezone is new in Python 3.2 [1], so pytz allows us to keep support for Python 2.7. [1]: http://docs.python.org/dev/library/datetime.html#datetime.timezone --- pelican/tests/test_utils.py | 12 +++++++----- pelican/utils.py | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index 77e3bccb..911d6266 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -9,6 +9,8 @@ import locale from sys import platform, version_info from tempfile import mkdtemp +import pytz + from pelican.generators import TemplatePagesGenerator from pelican.writers import Writer from pelican.settings import read_settings @@ -43,13 +45,13 @@ class TestUtils(LoggedTestCase): year=2012, month=11, day=22, hour=22, minute=11, second=10) date_hour_sec_z = datetime.datetime( year=2012, month=11, day=22, hour=22, minute=11, second=10, - tzinfo=datetime.timezone.utc) - date_hour_sec_0430 = datetime.datetime( + tzinfo=pytz.timezone('UTC')) + date_hour_sec_est = datetime.datetime( year=2012, month=11, day=22, hour=22, minute=11, second=10, - tzinfo=datetime.timezone(datetime.timedelta(hours=4, minutes=30))) + tzinfo=pytz.timezone('EST')) date_hour_sec_frac_z = datetime.datetime( year=2012, month=11, day=22, hour=22, minute=11, second=10, - microsecond=123000, tzinfo=datetime.timezone.utc) + microsecond=123000, tzinfo=pytz.timezone('UTC')) dates = { '2012-11-22': date, '2012/11/22': date, @@ -61,7 +63,7 @@ class TestUtils(LoggedTestCase): '22.11.2012 22:11': date_hour, '2012-11-22 22:11:10': date_hour_sec, '2012-11-22T22:11:10Z': date_hour_sec_z, - '2012-11-22T22:11:10+0430': date_hour_sec_0430, + '2012-11-22T22:11:10-0500': date_hour_sec_est, '2012-11-22T22:11:10.123Z': date_hour_sec_frac_z, } diff --git a/pelican/utils.py b/pelican/utils.py index e2e69121..9ba234a5 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -15,7 +15,7 @@ from collections import Hashable from functools import partial from codecs import open, BOM_UTF8 -from datetime import datetime, timezone +from datetime import datetime from itertools import groupby from jinja2 import Markup from operator import attrgetter @@ -210,7 +210,7 @@ def get_date(string): except ValueError: continue if date_format.endswith('Z'): - date = date.replace(tzinfo=timezone.utc) + date = date.replace(tzinfo=pytz.timezone('UTC')) return date raise ValueError('{0!r} is not a valid date'.format(string)) From 5a61600bc9948c318ccad79943df2f51756adcd8 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Wed, 12 Jun 2013 14:52:23 -0400 Subject: [PATCH 012/160] tests: Avoid hidden logic with better .assert*() method choices We'll get better failure messages if we use an assertion method that understands the comparison we're trying to make. If you make the comparison by hand and assertTrue(), you don't get much constructive feedback ;). --- pelican/tests/test_generators.py | 4 ++-- pelican/tests/test_importer.py | 4 ++-- pelican/tests/test_utils.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index e3b8e20a..48aff498 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -108,7 +108,7 @@ class TestArticlesGenerator(unittest.TestCase): sorted(['Default', 'TestCategory', 'Yeah', 'test', '指導書']), sorted(['Default', 'TestCategory', 'yeah', 'test', '指導書']), ) - self.assertTrue(sorted(categories) in categories_alternatives) + self.assertIn(sorted(categories), categories_alternatives) # test for slug categories = [cat.slug for cat, _ in generator.categories] categories_expected = ['default', 'testcategory', 'yeah', 'test', @@ -136,7 +136,7 @@ class TestArticlesGenerator(unittest.TestCase): sorted(['Default', 'Yeah', 'test', '指導書']), sorted(['Default', 'yeah', 'test', '指導書']), ) - self.assertTrue(sorted(categories) in categories_alternatives) + self.assertIn(sorted(categories), categories_alternatives) # test for slug categories = [cat.slug for cat, _ in generator.categories] categories_expected = ['default', 'yeah', 'test', 'zhi-dao-shu'] diff --git a/pelican/tests/test_importer.py b/pelican/tests/test_importer.py index cb095426..53809e7e 100644 --- a/pelican/tests/test_importer.py +++ b/pelican/tests/test_importer.py @@ -76,13 +76,13 @@ class TestWordpressXmlImporter(unittest.TestCase): def test_decode_html_entities_in_titles(self): test_posts = [post for post in self.posts if post[2] == 'html-entity-test'] - self.assertTrue(len(test_posts) == 1) + self.assertEqual(len(test_posts), 1) post = test_posts[0] title = post[0] self.assertTrue(title, "A normal post with some entities in the" " title. You can't miss them.") - self.assertTrue('&' not in title) + self.assertNotIn('&', title) def test_decode_wp_content_returns_empty(self): """ Check that given an empty string we return an empty string.""" diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index 8b0dc13e..1d538ca0 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -186,7 +186,7 @@ class TestUtils(LoggedTestCase): test_directory = os.path.join(os.path.dirname(__file__), 'does_not_exist') utils.clean_output_dir(test_directory) - self.assertTrue(not os.path.exists(test_directory)) + self.assertFalse(os.path.exists(test_directory)) def test_clean_output_dir_is_file(self): test_directory = os.path.join(os.path.dirname(__file__), @@ -195,7 +195,7 @@ class TestUtils(LoggedTestCase): f.write('') f.close() utils.clean_output_dir(test_directory) - self.assertTrue(not os.path.exists(test_directory)) + self.assertFalse(os.path.exists(test_directory)) def test_strftime(self): d = datetime.date(2012, 8, 29) From ecf56829300b731711b46177cee86345d504096f Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 4 Jan 2013 14:21:38 -0500 Subject: [PATCH 013/160] readers: Instrument read_file to return Content objects The assorted generators all use read_file() to read in the file contents and metadata. Previously, they sometimes parse additional metadata, fire off signals, and initialize a pelican.contents.Content subclass on their own. We can reduce duplicated code and increase consistency by shifting all that stuff into read_file() itself, and this commit is a step in that direction. --- pelican/readers.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/pelican/readers.py b/pelican/readers.py index 816464ef..beaa8814 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -31,7 +31,7 @@ try: except ImportError: from HTMLParser import HTMLParser -from pelican.contents import Category, Tag, Author +from pelican.contents import Page, Category, Tag, Author from pelican.utils import get_date, pelican_open @@ -333,8 +333,15 @@ for cls in [Reader] + Reader.__subclasses__(): EXTENSIONS[ext] = cls -def read_file(path, fmt=None, settings=None): - """Return a reader object using the given format.""" +def read_file(base_path, path, content_class=Page, fmt=None, + settings=None, context=None, + preread_signal=None, preread_sender=None, + context_signal=None, context_sender=None): + """Return a content object parsed with the given format.""" + if preread_signal: + preread_signal.send(preread_sender) + path = os.path.abspath(os.path.join(base_path, path)) + source_path = os.path.relpath(path, base_path) base, ext = os.path.splitext(os.path.basename(path)) if not fmt: fmt = ext[1:] @@ -355,7 +362,7 @@ def read_file(path, fmt=None, settings=None): raise ValueError("Missing dependencies for %s" % fmt) metadata = parse_path_metadata( - path=path, settings=settings, process=reader.process_metadata) + path=source_path, settings=settings, process=reader.process_metadata) content, reader_metadata = reader.read(path) metadata.update(reader_metadata) @@ -365,7 +372,14 @@ def read_file(path, fmt=None, settings=None): content = typogrify(content) metadata['title'] = typogrify(metadata['title']) - return content, metadata + if context_signal: + context_signal.send(context_sender, metadata=metadata) + return content_class( + content=content, + metadata=metadata, + settings=settings, + source_path=path, + context=context) def parse_path_metadata(path, settings=None, process=None): """Extract a metadata dictionary from a file's path From 4e118eff010c4048889063ecdad804f0435ad00b Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 5 Jan 2013 21:27:30 -0500 Subject: [PATCH 014/160] readers: Add debugging logging to read_file --- pelican/readers.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pelican/readers.py b/pelican/readers.py index beaa8814..974290e6 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals, print_function +import logging import os import re try: @@ -35,6 +36,8 @@ from pelican.contents import Page, Category, Tag, Author from pelican.utils import get_date, pelican_open +logger = logging.getLogger(__name__) + METADATA_PROCESSORS = { 'tags': lambda x, y: [Tag(tag, y) for tag in x.split(',')], 'date': lambda x, y: get_date(x), @@ -343,6 +346,8 @@ def read_file(base_path, path, content_class=Page, fmt=None, path = os.path.abspath(os.path.join(base_path, path)) source_path = os.path.relpath(path, base_path) base, ext = os.path.splitext(os.path.basename(path)) + logger.debug('read file {} -> {}'.format( + source_path, content_class.__name__)) if not fmt: fmt = ext[1:] From e38e170656f174528601601c5315484525b6abd9 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 3 Jun 2013 15:09:18 -0400 Subject: [PATCH 015/160] readers: Log signal sending for read_file() --- pelican/readers.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pelican/readers.py b/pelican/readers.py index 974290e6..2de00b51 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -341,8 +341,6 @@ def read_file(base_path, path, content_class=Page, fmt=None, preread_signal=None, preread_sender=None, context_signal=None, context_sender=None): """Return a content object parsed with the given format.""" - if preread_signal: - preread_signal.send(preread_sender) path = os.path.abspath(os.path.join(base_path, path)) source_path = os.path.relpath(path, base_path) base, ext = os.path.splitext(os.path.basename(path)) @@ -354,6 +352,11 @@ def read_file(base_path, path, content_class=Page, fmt=None, if fmt not in EXTENSIONS: raise TypeError('Pelican does not know how to parse {}'.format(path)) + if preread_signal: + logger.debug('signal {}.send({})'.format( + preread_signal, preread_sender)) + preread_signal.send(preread_sender) + if settings is None: settings = {} @@ -378,6 +381,8 @@ def read_file(base_path, path, content_class=Page, fmt=None, metadata['title'] = typogrify(metadata['title']) if context_signal: + logger.debug('signal {}.send({}, )'.format( + context_signal, context_sender)) context_signal.send(context_sender, metadata=metadata) return content_class( content=content, From effe7e5e126343e9b787d0434e79599c499b8443 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 4 Jan 2013 18:53:12 -0500 Subject: [PATCH 016/160] signals: Sort signals into categories --- pelican/signals.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/pelican/signals.py b/pelican/signals.py index 92bc6249..ad2985b5 100644 --- a/pelican/signals.py +++ b/pelican/signals.py @@ -2,15 +2,27 @@ from __future__ import unicode_literals, print_function from blinker import signal +# Run-level signals: + initialized = signal('pelican_initialized') +get_generators = signal('get_generators') finalized = signal('pelican_finalized') -article_generate_preread = signal('article_generate_preread') + +# Generator-level signals + generator_init = signal('generator_init') -article_generate_context = signal('article_generate_context') + article_generator_init = signal('article_generator_init') article_generator_finalized = signal('article_generate_finalized') -get_generators = signal('get_generators') -pages_generate_context = signal('pages_generate_context') + pages_generator_init = signal('pages_generator_init') pages_generator_finalized = signal('pages_generator_finalized') + +# Page-level signals + +article_generate_preread = signal('article_generate_preread') +article_generate_context = signal('article_generate_context') + +pages_generate_context = signal('pages_generate_context') + content_object_init = signal('content_object_init') From 386cd1f3f0e6c573cc8965ace2e9bf492452cf70 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 4 Jan 2013 19:01:41 -0500 Subject: [PATCH 017/160] signals: Add missing signals Note that the `pages_*` names are plural , while the `article_*` names are singular. I'll fix this once I update the PagesGenerator. --- pelican/signals.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pelican/signals.py b/pelican/signals.py index ad2985b5..34bde68a 100644 --- a/pelican/signals.py +++ b/pelican/signals.py @@ -18,11 +18,18 @@ article_generator_finalized = signal('article_generate_finalized') pages_generator_init = signal('pages_generator_init') pages_generator_finalized = signal('pages_generator_finalized') +static_generator_init = signal('static_generator_init') +static_generator_finalized = signal('static_generator_finalized') + # Page-level signals article_generate_preread = signal('article_generate_preread') article_generate_context = signal('article_generate_context') +pages_generate_preread = signal('pages_generate_preread') pages_generate_context = signal('pages_generate_context') +static_generate_preread = signal('static_generate_preread') +static_generate_context = signal('static_generate_context') + content_object_init = signal('content_object_init') From f2d6f77462976f5ea291481cba40e041d42d9e8c Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 5 Jan 2013 21:33:28 -0500 Subject: [PATCH 018/160] signals: Fix *_generate_* signals -> *_generator_* For example, article_generate_preread is now article_generator_preread for consistency with the other preread and context signals. --- pelican/signals.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pelican/signals.py b/pelican/signals.py index 34bde68a..877b0a6b 100644 --- a/pelican/signals.py +++ b/pelican/signals.py @@ -13,7 +13,7 @@ finalized = signal('pelican_finalized') generator_init = signal('generator_init') article_generator_init = signal('article_generator_init') -article_generator_finalized = signal('article_generate_finalized') +article_generator_finalized = signal('article_generator_finalized') pages_generator_init = signal('pages_generator_init') pages_generator_finalized = signal('pages_generator_finalized') @@ -23,13 +23,13 @@ static_generator_finalized = signal('static_generator_finalized') # Page-level signals -article_generate_preread = signal('article_generate_preread') -article_generate_context = signal('article_generate_context') +article_generator_preread = signal('article_generator_preread') +article_generator_context = signal('article_generator_context') -pages_generate_preread = signal('pages_generate_preread') -pages_generate_context = signal('pages_generate_context') +pages_generator_preread = signal('pages_generator_preread') +pages_generator_context = signal('pages_generator_context') -static_generate_preread = signal('static_generate_preread') -static_generate_context = signal('static_generate_context') +static_generator_preread = signal('static_generator_preread') +static_generator_context = signal('static_generator_context') content_object_init = signal('content_object_init') From a9c530281e1928e9f9f3c2cb78312db784505ca1 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 4 Jan 2013 18:14:28 -0500 Subject: [PATCH 019/160] Move Article metadata extraction from generators to readers There's no reason why this information should be Article-specific. This commit breaks the other generators for the moment. I'll fix them shortly. --- pelican/generators.py | 41 ++++++++--------------------------- pelican/readers.py | 50 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 51 insertions(+), 40 deletions(-) diff --git a/pelican/generators.py b/pelican/generators.py index 75b61df2..7daa55d2 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -5,7 +5,6 @@ import os import math import random import logging -import datetime import shutil from codecs import open @@ -19,9 +18,7 @@ from jinja2 import ( TemplateNotFound ) -from pelican.contents import ( - Article, Page, Category, Static, is_valid_content -) +from pelican.contents import Article, Page, Static, is_valid_content from pelican.readers import read_file from pelican.utils import copy, process_translations, mkdir_p, DateFormatter from pelican import signals @@ -383,37 +380,17 @@ class ArticlesGenerator(Generator): article_path, exclude=self.settings['ARTICLE_EXCLUDES']): try: - signals.article_generate_preread.send(self) - content, metadata = read_file(f, settings=self.settings) + article = read_file( + base_path=self.path, path=f, content_class=Article, + settings=self.settings, context=self.context, + preread_signal=signals.article_generator_preread, + preread_sender=self, + context_signal=signals.article_generator_context, + context_sender=self) except Exception as e: - logger.warning('Could not process %s\n%s' % (f, str(e))) + logger.warning('Could not process {}\n{}'.format(f, e)) continue - # if no category is set, use the name of the path as a category - if 'category' not in metadata: - - if (self.settings['USE_FOLDER_AS_CATEGORY'] - and os.path.dirname(f) != article_path): - # if the article is in a subdirectory - category = os.path.basename(os.path.dirname(f)) - else: - # if the article is not in a subdirectory - category = self.settings['DEFAULT_CATEGORY'] - - if category != '': - metadata['category'] = Category(category, self.settings) - - if 'date' not in metadata and self.settings.get('DEFAULT_DATE'): - if self.settings['DEFAULT_DATE'] == 'fs': - metadata['date'] = datetime.datetime.fromtimestamp( - os.stat(f).st_ctime) - else: - metadata['date'] = datetime.datetime( - *self.settings['DEFAULT_DATE']) - - signals.article_generate_context.send(self, metadata=metadata) - article = Article(content, metadata, settings=self.settings, - source_path=f, context=self.context) if not is_valid_content(article, f): continue diff --git a/pelican/readers.py b/pelican/readers.py index 2de00b51..3cf69dcf 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals, print_function +import datetime import logging import os import re @@ -369,8 +370,13 @@ def read_file(base_path, path, content_class=Page, fmt=None, if not reader.enabled: raise ValueError("Missing dependencies for %s" % fmt) - metadata = parse_path_metadata( - path=source_path, settings=settings, process=reader.process_metadata) + metadata = default_metadata( + settings=settings, process=reader.process_metadata) + metadata.update(path_metadata( + full_path=path, source_path=source_path, settings=settings)) + metadata.update(parse_path_metadata( + source_path=source_path, settings=settings, + process=reader.process_metadata)) content, reader_metadata = reader.read(path) metadata.update(reader_metadata) @@ -391,7 +397,29 @@ def read_file(base_path, path, content_class=Page, fmt=None, source_path=path, context=context) -def parse_path_metadata(path, settings=None, process=None): + +def default_metadata(settings=None, process=None): + metadata = {} + if settings: + if 'DEFAULT_CATEGORY' in settings: + value = settings['DEFAULT_CATEGORY'] + if process: + value = process('category', value) + metadata['category'] = value + if 'DEFAULT_DATE' in settings and settings['DEFAULT_DATE'] != 'fs': + metadata['date'] = datetime.datetime(*settings['DEFAULT_DATE']) + return metadata + + +def path_metadata(full_path, source_path, settings=None): + metadata = {} + if settings and settings.get('DEFAULT_DATE', None) == 'fs': + metadata['date'] = datetime.datetime.fromtimestamp( + os.stat(path).st_ctime) + return metadata + + +def parse_path_metadata(source_path, settings=None, process=None): """Extract a metadata dictionary from a file's path >>> import pprint @@ -402,7 +430,7 @@ def parse_path_metadata(path, settings=None, process=None): ... } >>> reader = Reader(settings=settings) >>> metadata = parse_path_metadata( - ... path='my-cat/2013-01-01/my-slug.html', + ... source_path='my-cat/2013-01-01/my-slug.html', ... settings=settings, ... process=reader.process_metadata) >>> pprint.pprint(metadata) # doctest: +ELLIPSIS @@ -411,13 +439,19 @@ def parse_path_metadata(path, settings=None, process=None): 'slug': 'my-slug'} """ metadata = {} - base, ext = os.path.splitext(os.path.basename(path)) + dirname, basename = os.path.split(source_path) + base, ext = os.path.splitext(basename) + subdir = os.path.basename(dirname) if settings: + checks = [] for key,data in [('FILENAME_METADATA', base), - ('PATH_METADATA', path), + ('PATH_METADATA', source_path), ]: - regexp = settings.get(key) - if regexp: + checks.append((settings.get(key, None), data)) + if settings.get('USE_FOLDER_AS_CATEGORY', None): + checks.insert(0, ('(?P.*)', subdir)) + for regexp,data in checks: + if regexp and data: match = re.match(regexp, data) if match: # .items() for py3k compat. From 7be16dd5247e07952c009cb439734722acf019ac Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 4 Jan 2013 18:19:26 -0500 Subject: [PATCH 020/160] generators: Update PagesGenerator to use new read_file Also standardize signal names. If `article_generator_*` is singular, `page_generator_*` should be as well. Fix it from the older `pages_generator_*`. --- docs/plugins.rst | 6 +++--- pelican/generators.py | 18 +++++++++++------- pelican/signals.py | 8 ++++---- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/docs/plugins.rst b/docs/plugins.rst index 064ba73d..9e262962 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -79,9 +79,9 @@ article_generator_finalized article_generator invoked at the e 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__ -pages_generator_finalized pages_generator invoked at the end of PagesGenerator.generate_context +page_generate_context page_generator, metadata +page_generator_init page_generator invoked in the PagesGenerator.__init__ +page_generator_finalized page_generator invoked at the end of PagesGenerator.generate_context content_object_init content_object invoked at the end of Content.__init__ (see note below) ============================= ============================ =========================================================================== diff --git a/pelican/generators.py b/pelican/generators.py index 7daa55d2..a4cd273e 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -479,7 +479,7 @@ class PagesGenerator(Generator): self.hidden_pages = [] self.hidden_translations = [] super(PagesGenerator, self).__init__(*args, **kwargs) - signals.pages_generator_init.send(self) + signals.page_generator_init.send(self) def generate_context(self): all_pages = [] @@ -488,13 +488,17 @@ class PagesGenerator(Generator): os.path.join(self.path, self.settings['PAGE_DIR']), exclude=self.settings['PAGE_EXCLUDES']): try: - content, metadata = read_file(f, settings=self.settings) + page = read_file( + base_path=self.path, path=f, content_class=Page, + settings=self.settings, context=self.context, + preread_signal=signals.page_generator_preread, + preread_sender=self, + context_signal=signals.page_generator_context, + context_sender=self) except Exception as e: - logger.warning('Could not process %s\n%s' % (f, str(e))) + logger.warning('Could not process {}\n{}'.format(f, e)) continue - signals.pages_generate_context.send(self, metadata=metadata) - page = Page(content, metadata, settings=self.settings, - source_path=f, context=self.context) + if not is_valid_content(page, f): continue @@ -516,7 +520,7 @@ class PagesGenerator(Generator): self._update_context(('pages', )) self.context['PAGES'] = self.pages - signals.pages_generator_finalized.send(self) + signals.page_generator_finalized.send(self) def generate_output(self, writer): for page in chain(self.translations, self.pages, diff --git a/pelican/signals.py b/pelican/signals.py index 877b0a6b..cb010d37 100644 --- a/pelican/signals.py +++ b/pelican/signals.py @@ -15,8 +15,8 @@ generator_init = signal('generator_init') article_generator_init = signal('article_generator_init') article_generator_finalized = signal('article_generator_finalized') -pages_generator_init = signal('pages_generator_init') -pages_generator_finalized = signal('pages_generator_finalized') +page_generator_init = signal('page_generator_init') +page_generator_finalized = signal('page_generator_finalized') static_generator_init = signal('static_generator_init') static_generator_finalized = signal('static_generator_finalized') @@ -26,8 +26,8 @@ static_generator_finalized = signal('static_generator_finalized') article_generator_preread = signal('article_generator_preread') article_generator_context = signal('article_generator_context') -pages_generator_preread = signal('pages_generator_preread') -pages_generator_context = signal('pages_generator_context') +page_generator_preread = signal('page_generator_preread') +page_generator_context = signal('page_generator_context') static_generator_preread = signal('static_generator_preread') static_generator_context = signal('static_generator_context') From 1bc5b100eceab6b0ad90ab7ebecd896dbbc2bc93 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 5 Jan 2013 07:21:47 -0500 Subject: [PATCH 021/160] generators: Convert StaticGenerator to use the new read_file --- pelican/generators.py | 45 ++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/pelican/generators.py b/pelican/generators.py index a4cd273e..b777efec 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -548,32 +548,29 @@ class StaticGenerator(Generator): for static_path in self.settings['STATIC_PATHS']: for f in self.get_files( os.path.join(self.path, static_path), extensions=False): - f_rel = os.path.relpath(f, self.path) - content, metadata = read_file( - f, fmt='static', settings=self.settings) - # TODO remove this hardcoded 'static' subdirectory - metadata['save_as'] = os.path.join('static', f_rel) - metadata['url'] = pelican.utils.path_to_url(metadata['save_as']) - sc = Static( - content=None, - metadata=metadata, - settings=self.settings, - source_path=f_rel) - self.staticfiles.append(sc) - self.add_source_path(sc) + static = read_file( + base_path=self.path, path=f, content_class=Static, + fmt='static', + settings=self.settings, context=self.context, + preread_signal=signals.static_generator_preread, + preread_sender=self, + context_signal=signals.static_generator_context, + context_sender=self) + self.staticfiles.append(static) + self.add_source_path(static) + # same thing for FILES_TO_COPY for src, dest in self.settings['FILES_TO_COPY']: - content, metadata = read_file( - src, fmt='static', settings=self.settings) - metadata['save_as'] = dest - metadata['url'] = pelican.utils.path_to_url(metadata['save_as']) - sc = Static( - content=None, - metadata={'save_as': dest}, - settings=self.settings, - source_path=src) - self.staticfiles.append(sc) - self.add_source_path(sc) + static = read_file( + base_path=self.path, path=f, content_class=Static, + fmt='static', + settings=self.settings, context=self.context, + preread_signal=signals.static_generator_preread, + preread_sender=self, + context_signal=signals.static_generator_context, + context_sender=self) + self.staticfiles.append(static) + self.add_source_path(static) def generate_output(self, writer): self._copy_paths(self.settings['THEME_STATIC_PATHS'], self.theme, From 1ca0e06a273c45a52b4aab019ae95e580c6c58da Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 3 Jun 2013 15:14:11 -0400 Subject: [PATCH 022/160] Remove the FILES_TO_COPY setting We no longer instantiate the Static object in the StaticGenerator, so we can't set the save_as argument anymore. If you want to adjust the output path, use the upcoming EXTRA_PATH_METADATA setting. --- docs/settings.rst | 3 --- pelican/generators.py | 13 ------------- pelican/settings.py | 1 - 3 files changed, 17 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 0a04f804..a75badfc 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -68,9 +68,6 @@ Setting name (default value) What doe generating new files. This can be useful in preventing older, unnecessary files from persisting in your output. However, **this is a destructive setting and should be handled with extreme care.** -`FILES_TO_COPY` (``()``) A list of files (or directories) to copy from the source (inside the - content directory) to the destination (inside the output directory). - For example: ``(('extra/robots.txt', 'robots.txt'),)``. `JINJA_EXTENSIONS` (``[]``) A list of any Jinja2 extensions you want to use. `JINJA_FILTERS` (``{}``) A list of custom Jinja2 filters you want to use. The dictionary should map the filtername to the filter function. diff --git a/pelican/generators.py b/pelican/generators.py index b777efec..c48f8067 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -559,19 +559,6 @@ class StaticGenerator(Generator): self.staticfiles.append(static) self.add_source_path(static) - # same thing for FILES_TO_COPY - for src, dest in self.settings['FILES_TO_COPY']: - static = read_file( - base_path=self.path, path=f, content_class=Static, - fmt='static', - settings=self.settings, context=self.context, - preread_signal=signals.static_generator_preread, - preread_sender=self, - context_signal=signals.static_generator_context, - context_sender=self) - self.staticfiles.append(static) - self.add_source_path(static) - def generate_output(self, writer): self._copy_paths(self.settings['THEME_STATIC_PATHS'], self.theme, 'theme', self.output_path, os.curdir) diff --git a/pelican/settings.py b/pelican/settings.py index 34a2b42a..5386d172 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -95,7 +95,6 @@ DEFAULT_CONFIG = { 'DEFAULT_METADATA': (), 'FILENAME_METADATA': '(?P\d{4}-\d{2}-\d{2}).*', 'PATH_METADATA': '', - 'FILES_TO_COPY': (), 'DEFAULT_STATUS': 'published', 'ARTICLE_PERMALINK_STRUCTURE': '', 'TYPOGRIFY': False, From d43dc1b9d6ba5f0f99c6de44db214f296558c701 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 3 Jun 2013 15:15:53 -0400 Subject: [PATCH 023/160] Add the EXTRA_PATH_METADATA setting Useful for altering static output paths when you don't want to encode extra metadata in the static file's source path. --- docs/settings.rst | 1 + pelican/readers.py | 9 ++++++--- pelican/settings.py | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index a75badfc..c7d7f199 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -64,6 +64,7 @@ Setting name (default value) What doe ``'(?P\d{4}-\d{2}-\d{2})_(?P.*)'``. `PATH_METADATA` (``''``) Like ``FILENAME_METADATA``, but parsed from a page's full path relative to the content source directory. +`EXTRA_PATH_METADATA` (``{}``) Extra metadata dictionaries keyed by relative path. `DELETE_OUTPUT_DIRECTORY` (``False``) Delete the output directory, and **all** of its contents, before generating new files. This can be useful in preventing older, unnecessary files from persisting in your output. However, **this is diff --git a/pelican/readers.py b/pelican/readers.py index 3cf69dcf..3e00b430 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -413,9 +413,12 @@ def default_metadata(settings=None, process=None): def path_metadata(full_path, source_path, settings=None): metadata = {} - if settings and settings.get('DEFAULT_DATE', None) == 'fs': - metadata['date'] = datetime.datetime.fromtimestamp( - os.stat(path).st_ctime) + if settings: + if settings.get('DEFAULT_DATE', None) == 'fs': + metadata['date'] = datetime.datetime.fromtimestamp( + os.stat(full_path).st_ctime) + metadata.update(settings.get('EXTRA_PATH_METADATA', {}).get( + source_path, {})) return metadata diff --git a/pelican/settings.py b/pelican/settings.py index 5386d172..13896032 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -95,6 +95,7 @@ DEFAULT_CONFIG = { 'DEFAULT_METADATA': (), 'FILENAME_METADATA': '(?P\d{4}-\d{2}-\d{2}).*', 'PATH_METADATA': '', + 'EXTRA_PATH_METADATA': {}, 'DEFAULT_STATUS': 'published', 'ARTICLE_PERMALINK_STRUCTURE': '', 'TYPOGRIFY': False, From 7de7bd0e3715f2227ce84fcf198dcc98cc3a0582 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 5 Jan 2013 07:43:11 -0500 Subject: [PATCH 024/160] Update sample configurations from FILES_TO_COPY to EXTRA_PATH_METADATA --- pelican/tests/default_conf.py | 13 +++++++++---- samples/pelican.conf.py | 16 ++++++++++++---- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/pelican/tests/default_conf.py b/pelican/tests/default_conf.py index bc3a7dff..80a990b5 100644 --- a/pelican/tests/default_conf.py +++ b/pelican/tests/default_conf.py @@ -29,11 +29,16 @@ SOCIAL = (('twitter', 'http://twitter.com/ametaireau'), # global metadata to all the contents DEFAULT_METADATA = (('yeah', 'it is'),) -# static paths will be copied under the same name -STATIC_PATHS = ["pictures", ] +# path-specific metadata +EXTRA_PATH_METADATA = { + 'extra/robots.txt': {'path': 'robots.txt'}, + } -# A list of files to copy from the source to the destination -FILES_TO_COPY = (('extra/robots.txt', 'robots.txt'),) +# static paths will be copied without parsing their contents +STATIC_PATHS = [ + 'pictures', + 'extra/robots.txt', + ] # foobar will not be used, because it's not in caps. All configuration keys # have to be in caps diff --git a/samples/pelican.conf.py b/samples/pelican.conf.py index 70edd5f8..ad2042fd 100755 --- a/samples/pelican.conf.py +++ b/samples/pelican.conf.py @@ -34,11 +34,19 @@ SOCIAL = (('twitter', 'http://twitter.com/ametaireau'), # global metadata to all the contents DEFAULT_METADATA = (('yeah', 'it is'),) -# static paths will be copied under the same name -STATIC_PATHS = ["pictures", ] +# path-specific metadata +EXTRA_PATH_METADATA = { + 'extra/robots.txt': {'path': 'robots.txt'}, + 'pictures/Fat_Cat.jpg': {'path': 'static/pictures/Fat_Cat.jpg'}, + 'pictures/Sushi.jpg': {'path': 'static/pictures/Sushi.jpg'}, + 'pictures/Sushi_Macro.jpg': {'path': 'static/pictures/Sushi_Macro.jpg'}, + } -# A list of files to copy from the source to the destination -FILES_TO_COPY = (('extra/robots.txt', 'robots.txt'),) +# static paths will be copied without parsing their contents +STATIC_PATHS = [ + 'pictures', + 'extra/robots.txt', + ] # custom page generated with a jinja2 template TEMPLATE_PAGES = {'pages/jinja2_template.html': 'jinja2_template.html'} From 06121eda76f22e03d43ce40c5c69d45d97447253 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 3 Jun 2013 15:21:17 -0400 Subject: [PATCH 025/160] test_readers: Update to new readers.read_file --- pelican/tests/test_readers.py | 72 +++++++++++++++++------------------ 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/pelican/tests/test_readers.py b/pelican/tests/test_readers.py index a48301f9..14d42325 100644 --- a/pelican/tests/test_readers.py +++ b/pelican/tests/test_readers.py @@ -20,13 +20,13 @@ class ReaderTest(unittest.TestCase): def read_file(self, path, **kwargs): # Isolate from future API changes to readers.read_file return readers.read_file( - _path(path), settings=get_settings(**kwargs)) + base_path=CONTENT_PATH, path=path, settings=get_settings(**kwargs)) class RstReaderTest(ReaderTest): def test_article_with_metadata(self): - content, metadata = self.read_file(path='article_with_metadata.rst') + page = self.read_file(path='article_with_metadata.rst') expected = { 'category': 'yeah', 'author': 'Alexis Métaireau', @@ -40,10 +40,10 @@ class RstReaderTest(ReaderTest): } for key, value in expected.items(): - self.assertEqual(value, metadata[key], key) + self.assertEqual(value, page.metadata[key], key) def test_article_with_filename_metadata(self): - content, metadata = self.read_file( + page = self.read_file( path='2012-11-29_rst_w_filename_meta#foo-bar.rst', FILENAME_METADATA=None) expected = { @@ -51,10 +51,10 @@ class RstReaderTest(ReaderTest): 'author': 'Alexis Métaireau', 'title': 'Rst with filename metadata', } - for key, value in metadata.items(): + for key, value in page.metadata.items(): self.assertEqual(value, expected[key], key) - content, metadata = self.read_file( + page = self.read_file( path='2012-11-29_rst_w_filename_meta#foo-bar.rst', FILENAME_METADATA='(?P\d{4}-\d{2}-\d{2}).*') expected = { @@ -63,10 +63,10 @@ class RstReaderTest(ReaderTest): 'title': 'Rst with filename metadata', 'date': datetime.datetime(2012, 11, 29), } - for key, value in metadata.items(): + for key, value in page.metadata.items(): self.assertEqual(value, expected[key], key) - content, metadata = self.read_file( + page = self.read_file( path='2012-11-29_rst_w_filename_meta#foo-bar.rst', FILENAME_METADATA=( '(?P\d{4}-\d{2}-\d{2})_' @@ -80,7 +80,7 @@ class RstReaderTest(ReaderTest): 'slug': 'article_with_filename_metadata', 'mymeta': 'foo', } - for key, value in metadata.items(): + for key, value in page.metadata.items(): self.assertEqual(value, expected[key], key) def test_article_metadata_key_lowercase(self): @@ -96,23 +96,23 @@ class RstReaderTest(ReaderTest): def test_typogrify(self): # if nothing is specified in the settings, the content should be # unmodified - content, _ = self.read_file(path='article.rst') + page = self.read_file(path='article.rst') expected = ('

This is some content. With some stuff to ' '"typogrify".

\n

Now with added ' 'support for ' 'TLA.

\n') - self.assertEqual(content, expected) + self.assertEqual(page.content, expected) try: # otherwise, typogrify should be applied - content, _ = self.read_file(path='article.rst', TYPOGRIFY=True) + page = self.read_file(path='article.rst', TYPOGRIFY=True) expected = ('

This is some content. With some stuff to ' '“typogrify”.

\n

Now with added ' 'support for ' 'TLA.

\n') - self.assertEqual(content, expected) + self.assertEqual(page.content, expected) except ImportError: return unittest.skip('need the typogrify distribution') @@ -225,7 +225,7 @@ class MdReaderTest(ReaderTest): def test_article_with_markdown_markup_extension(self): # test to ensure the markdown markup extension is being processed as # expected - content, metadata = self.read_file( + page = self.read_file( path='article_with_markdown_markup_extensions.md', MD_EXTENSIONS=['toc', 'codehilite', 'extra']) expected = ('
\n' @@ -239,11 +239,11 @@ class MdReaderTest(ReaderTest): '

Level1

\n' '

Level2

') - self.assertEqual(content, expected) + self.assertEqual(page.content, expected) @unittest.skipUnless(readers.Markdown, "markdown isn't installed") def test_article_with_filename_metadata(self): - content, metadata = self.read_file( + page = self.read_file( path='2012-11-30_md_w_filename_meta#foo-bar.md', FILENAME_METADATA=None) expected = { @@ -251,9 +251,9 @@ class MdReaderTest(ReaderTest): 'author': 'Alexis Métaireau', } for key, value in expected.items(): - self.assertEqual(value, metadata[key], key) + self.assertEqual(value, page.metadata[key], key) - content, metadata = self.read_file( + page = self.read_file( path='2012-11-30_md_w_filename_meta#foo-bar.md', FILENAME_METADATA='(?P\d{4}-\d{2}-\d{2}).*') expected = { @@ -262,9 +262,9 @@ class MdReaderTest(ReaderTest): 'date': datetime.datetime(2012, 11, 30), } for key, value in expected.items(): - self.assertEqual(value, metadata[key], key) + self.assertEqual(value, page.metadata[key], key) - content, metadata = self.read_file( + page = self.read_file( path='2012-11-30_md_w_filename_meta#foo-bar.md', FILENAME_METADATA=( '(?P\d{4}-\d{2}-\d{2})' @@ -278,7 +278,7 @@ class MdReaderTest(ReaderTest): 'mymeta': 'foo', } for key, value in expected.items(): - self.assertEqual(value, metadata[key], key) + self.assertEqual(value, page.metadata[key], key) class AdReaderTest(ReaderTest): @@ -286,13 +286,13 @@ class AdReaderTest(ReaderTest): @unittest.skipUnless(readers.asciidoc, "asciidoc isn't installed") def test_article_with_asc_extension(self): # Ensure the asc extension is being processed by the correct reader - content, metadata = self.read_file( + page = self.read_file( path='article_with_asc_extension.asc') expected = ('
\n

' 'Used for pelican test

\n' '

The quick brown fox jumped over' ' the lazy dog’s back.

\n') - self.assertEqual(content, expected) + self.assertEqual(page.content, expected) expected = { 'category': 'Blog', 'author': 'Author O. Article', @@ -302,7 +302,7 @@ class AdReaderTest(ReaderTest): } for key, value in expected.items(): - self.assertEqual(value, metadata[key], key) + self.assertEqual(value, page.metadata[key], key) @unittest.skipUnless(readers.asciidoc, "asciidoc isn't installed") def test_article_with_asc_options(self): @@ -319,24 +319,24 @@ class AdReaderTest(ReaderTest): class HTMLReaderTest(ReaderTest): def test_article_with_comments(self): - content, metadata = self.read_file(path='article_with_comments.html') + page = self.read_file(path='article_with_comments.html') self.assertEqual(''' Body content - ''', content) + ''', page.content) def test_article_with_keywords(self): - content, metadata = self.read_file(path='article_with_keywords.html') + page = self.read_file(path='article_with_keywords.html') expected = { 'tags': ['foo', 'bar', 'foobar'], } for key, value in expected.items(): - self.assertEqual(value, metadata[key], key) + self.assertEqual(value, page.metadata[key], key) def test_article_with_metadata(self): - content, metadata = self.read_file(path='article_with_metadata.html') + page = self.read_file(path='article_with_metadata.html') expected = { 'category': 'yeah', 'author': 'Alexis Métaireau', @@ -348,21 +348,19 @@ class HTMLReaderTest(ReaderTest): } for key, value in expected.items(): - self.assertEqual(value, metadata[key], key) + self.assertEqual(value, page.metadata[key], key) def test_article_with_null_attributes(self): - content, metadata = self.read_file( - path='article_with_null_attributes.html') + page = self.read_file(path='article_with_null_attributes.html') self.assertEqual(''' Ensure that empty attributes are copied properly. - ''', content) + ''', page.content) def test_article_metadata_key_lowercase(self): # Keys of metadata should be lowercase. - content, metadata = self.read_file( - path='article_with_uppercase_metadata.html') - self.assertIn('category', metadata, 'Key should be lowercase.') - self.assertEqual('Yeah', metadata.get('category'), + page = self.read_file(path='article_with_uppercase_metadata.html') + self.assertIn('category', page.metadata, 'Key should be lowercase.') + self.assertEqual('Yeah', page.metadata.get('category'), 'Value keeps cases.') From f63325aa9a459b2c510bd16297a381544a1571ea Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 3 Jun 2013 15:26:23 -0400 Subject: [PATCH 026/160] test_generators: Replace CUR_DIR with CONTENT_DIR for subdir cat. detection In situations where I've cleared ARTICLE_DIR, I've done so to ensure that there are no directories that will override the DEFAULT_CATEGORY due to USE_FOLDER_AS_CATEGORY. --- pelican/tests/test_generators.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index 48aff498..f5ed6b85 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -15,6 +15,7 @@ from pelican.settings import DEFAULT_CONFIG from pelican.tests.support import unittest, get_settings CUR_DIR = os.path.dirname(__file__) +CONTENT_DIR = os.path.join(CUR_DIR, 'content') class TestArticlesGenerator(unittest.TestCase): @@ -30,12 +31,10 @@ class TestArticlesGenerator(unittest.TestCase): """ if self.generator is None: settings = get_settings(filenames={}) - settings['ARTICLE_DIR'] = 'content' settings['DEFAULT_CATEGORY'] = 'Default' settings['DEFAULT_DATE'] = (1970, 1, 1) self.generator = ArticlesGenerator(settings.copy(), settings, - CUR_DIR, settings['THEME'], None, - settings['MARKUP']) + CONTENT_DIR, settings['THEME'], None, settings['MARKUP']) self.generator.generate_context() return self.generator @@ -118,14 +117,13 @@ class TestArticlesGenerator(unittest.TestCase): def test_do_not_use_folder_as_category(self): settings = DEFAULT_CONFIG.copy() - settings['ARTICLE_DIR'] = 'content' settings['DEFAULT_CATEGORY'] = 'Default' settings['DEFAULT_DATE'] = (1970, 1, 1) settings['USE_FOLDER_AS_CATEGORY'] = False settings['filenames'] = {} generator = ArticlesGenerator( - settings.copy(), settings, CUR_DIR, DEFAULT_CONFIG['THEME'], None, - DEFAULT_CONFIG['MARKUP']) + settings.copy(), settings, CONTENT_DIR, DEFAULT_CONFIG['THEME'], + None, DEFAULT_CONFIG['MARKUP']) generator.generate_context() # test for name # categories are grouped by slug; if two categories have the same slug @@ -213,12 +211,12 @@ class TestPageGenerator(unittest.TestCase): def test_generate_context(self): settings = get_settings(filenames={}) - settings['PAGE_DIR'] = 'TestPages' + settings['PAGE_DIR'] = 'TestPages' # relative to CUR_DIR settings['DEFAULT_DATE'] = (1970, 1, 1) - generator = PagesGenerator(settings.copy(), settings, CUR_DIR, - settings['THEME'], None, - settings['MARKUP']) + generator = PagesGenerator( + settings.copy(), settings, CUR_DIR, settings['THEME'], None, + settings['MARKUP']) generator.generate_context() pages = self.distill_pages(generator.pages) hidden_pages = self.distill_pages(generator.hidden_pages) From fdde17281d36f28cf7084a21daafa77eea9fe3cb Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 4 Jan 2013 19:47:55 -0500 Subject: [PATCH 027/160] contents: Page fallbacks for context and localsiteurl --- pelican/contents.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pelican/contents.py b/pelican/contents.py index 5f2e66b0..fd33bc4c 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -48,6 +48,8 @@ class Content(object): self.settings = settings self._content = content + if context is None: + context = {} self._context = context self.translations = [] @@ -220,7 +222,7 @@ class Content(object): @property def content(self): - return self.get_content(self._context['localsiteurl']) + return self.get_content(self._context.get('localsiteurl', '')) def _get_summary(self): """Returns the summary of an article. From 29f0aa39d2239122782fa5c1a547827cf7acedb0 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 22 Mar 2013 00:13:37 -0400 Subject: [PATCH 028/160] content: Don't update static content --- pelican/contents.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pelican/contents.py b/pelican/contents.py index fd33bc4c..1b604f19 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -171,6 +171,9 @@ class Content(object): :param siteurl: siteurl which is locally generated by the writer in case of RELATIVE_URLS. """ + if not content: + return content + hrefs = re.compile(r""" (?P<\s*[^\>]* # match tag with src and href attr (?:href|src)\s*=) From 4058cfdea44725c520dc574c033476892c235d58 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 24 Mar 2013 10:19:01 -0400 Subject: [PATCH 029/160] settings: Add a warning for folks using FILES_TO_COPY. --- pelican/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pelican/settings.py b/pelican/settings.py index 13896032..35e76346 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -257,6 +257,8 @@ def configure_settings(settings): for old,new,doc in [ ('LESS_GENERATOR', 'the Webassets plugin', None), + ('FILES_TO_COPY', 'STATIC_PATHS and EXTRA_PATH_METADATA', + 'https://github.com/getpelican/pelican/pull/795'), ]: if old in settings: message = 'The {} setting has been removed in favor of {}' From 38c22e83b6bfe7fdcd4f8987063cd240d5655539 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 3 Jun 2013 15:29:54 -0400 Subject: [PATCH 030/160] readers: Ensure the reader class is enabled before instantiating Otherwise the MarkdownReader fails with: 'bool' object is not callable if Markdown is not installed. Reported-by: Deniz Turgut --- pelican/readers.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pelican/readers.py b/pelican/readers.py index 3e00b430..bd9f5914 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -361,15 +361,17 @@ def read_file(base_path, path, content_class=Page, fmt=None, if settings is None: settings = {} - reader = EXTENSIONS[fmt](settings) + reader_class = EXTENSIONS[fmt] + if not reader_class.enabled: + raise ValueError('Missing dependencies for {}'.format(fmt)) + + reader = reader_class(settings) + settings_key = '%s_EXTENSIONS' % fmt.upper() if settings and settings_key in settings: reader.extensions = settings[settings_key] - if not reader.enabled: - raise ValueError("Missing dependencies for %s" % fmt) - metadata = default_metadata( settings=settings, process=reader.process_metadata) metadata.update(path_metadata( From 8797f0ebefee5df4edef6f9a8a98685e1cc1ee92 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 7 Jun 2013 14:56:21 -0400 Subject: [PATCH 031/160] docs/plugins.rst: Document signal name changes --- docs/plugins.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/plugins.rst b/docs/plugins.rst index 9e262962..9bf08ff3 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -104,3 +104,22 @@ request if you need them! def register(): signals.content_object_init.connect(test, sender=contents.Article) + +.. note:: + + After Pelican 3.2, signal names were standardized. Older plugins + may need to be updated to use the new names: + + ========================== =========================== + Old name New name + ========================== =========================== + article_generate_context article_generator_context + article_generate_finalized article_generator_finalized + article_generate_preread article_generator_preread + pages_generate_context page_generator_context + pages_generate_preread page_generator_preread + pages_generator_finalized page_generator_finalized + pages_generator_init page_generator_init + static_generate_context static_generator_context + static_generate_preread static_generator_preread + ========================== =========================== From ce0a9b697e08fee069cda6c12a8b9921c183830c Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 18 Jan 2013 19:00:45 -0500 Subject: [PATCH 032/160] Document path metadata extraction --- docs/getting_started.rst | 2 ++ docs/settings.rst | 49 ++++++++++++++++++++++++++++++++++++++++ pelican/settings.py | 2 +- 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/docs/getting_started.rst b/docs/getting_started.rst index b41f8c18..b2e0aeda 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -213,6 +213,8 @@ The idea behind "pages" is that they are usually not temporal in nature and are used for content that does not change very often (e.g., "About" or "Contact" pages). +.. _internal_metadata: + File metadata ------------- diff --git a/docs/settings.rst b/docs/settings.rst index c7d7f199..ffcddc7a 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -62,9 +62,12 @@ Setting name (default value) What doe For example, if you would like to extract both the date and the slug, you could set something like: ``'(?P\d{4}-\d{2}-\d{2})_(?P.*)'``. + See :ref:`path_metadata`. `PATH_METADATA` (``''``) Like ``FILENAME_METADATA``, but parsed from a page's full path relative to the content source directory. + See :ref:`path_metadata`. `EXTRA_PATH_METADATA` (``{}``) Extra metadata dictionaries keyed by relative path. + See :ref:`path_metadata`. `DELETE_OUTPUT_DIRECTORY` (``False``) Delete the output directory, and **all** of its contents, before generating new files. This can be useful in preventing older, unnecessary files from persisting in your output. However, **this is @@ -335,6 +338,52 @@ your resume, and a contact page — you could have:: 'src/resume.html': 'dest/resume.html', 'src/contact.html': 'dest/contact.html'} + +.. _path_metadata: + +Path metadata +============= + +Not all metadata needs to be `embedded in source file itself`__. For +example, blog posts are often named following a ``YYYY-MM-DD-SLUG.rst`` +pattern, or nested into ``YYYY/MM/DD-SLUG`` directories. To extract +metadata from the filename or path, set ``FILENAME_METADATA`` or +``PATH_METADATA`` to regular expressions that use Python's `group name +notation`_ ``(?P…)``. If you want to attach additional metadata +but don't want to encode it in the path, you can set +``EXTRA_PATH_METADATA``: + +.. parsed-literal:: + + EXTRA_PATH_METADATA = { + 'relative/path/to/file-1': { + 'key-1a': 'value-1a', + 'key-1b': 'value-1b', + }, + 'relative/path/to/file-2': { + 'key-2': 'value-2', + }, + } + +This can be a convenient way to shift the installed location of a +particular file: + +.. parsed-literal:: + + # Take advantage of the following defaults + # STATIC_SAVE_AS = '{path}' + # STATIC_URL = '{path}' + STATIC_PATHS = [ + 'extra/robots.txt', + ] + EXTRA_PATH_METADATA = { + 'extra/robots.txt': {'path': 'robots.txt'}, + } + +__ internal_metadata__ +.. _group name notation: + http://docs.python.org/3/library/re.html#regular-expression-syntax + Feed settings ============= diff --git a/pelican/settings.py b/pelican/settings.py index 35e76346..df3ad6b6 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -258,7 +258,7 @@ def configure_settings(settings): for old,new,doc in [ ('LESS_GENERATOR', 'the Webassets plugin', None), ('FILES_TO_COPY', 'STATIC_PATHS and EXTRA_PATH_METADATA', - 'https://github.com/getpelican/pelican/pull/795'), + 'https://github.com/getpelican/pelican/blob/master/docs/settings.rst#path-metadata'), ]: if old in settings: message = 'The {} setting has been removed in favor of {}' From 9b42b2a13082e325fabf3f8e4b3f86b3b775bfe7 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Wed, 12 Jun 2013 16:07:24 -0400 Subject: [PATCH 033/160] generators: get_files() should use paths relative to Generator.path All paths should be relative to Generator.path unless we're actively accessing the filesystem. This makes the argument less ambiguous, so we have less likelyhood of joining paths multiple times. --- pelican/generators.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/pelican/generators.py b/pelican/generators.py index c48f8067..a01281dc 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -102,23 +102,25 @@ class Generator(object): def get_files(self, path, exclude=[], extensions=None): """Return a list of files to use, based on rules - :param path: the path to search the file on + :param path: the path to search (relative to self.path) :param exclude: the list of path to exclude :param extensions: the list of allowed extensions (if False, all extensions are allowed) """ files = [] + root = os.path.join(self.path, path) - if os.path.isdir(path): - for root, dirs, temp_files in os.walk(path, followlinks=True): + if os.path.isdir(root): + for dirpath, dirs, temp_files in os.walk(root, followlinks=True): for e in exclude: if e in dirs: dirs.remove(e) + reldir = os.path.relpath(dirpath, self.path) for f in temp_files: - fp = os.path.join(root, f) + fp = os.path.join(reldir, f) if self._include_path(fp, extensions): files.append(fp) - elif os.path.exists(path) and self._include_path(path, extensions): + elif os.path.exists(root) and self._include_path(path, extensions): files.append(path) # can't walk non-directories return files @@ -372,12 +374,9 @@ class ArticlesGenerator(Generator): def generate_context(self): """Add the articles into the shared context""" - article_path = os.path.normpath( # we have to remove trailing slashes - os.path.join(self.path, self.settings['ARTICLE_DIR']) - ) all_articles = [] for f in self.get_files( - article_path, + self.settings['ARTICLE_DIR'], exclude=self.settings['ARTICLE_EXCLUDES']): try: article = read_file( @@ -485,7 +484,7 @@ class PagesGenerator(Generator): all_pages = [] hidden_pages = [] for f in self.get_files( - os.path.join(self.path, self.settings['PAGE_DIR']), + self.settings['PAGE_DIR'], exclude=self.settings['PAGE_EXCLUDES']): try: page = read_file( @@ -547,7 +546,7 @@ class StaticGenerator(Generator): # walk static paths for static_path in self.settings['STATIC_PATHS']: for f in self.get_files( - os.path.join(self.path, static_path), extensions=False): + static_path, extensions=False): static = read_file( base_path=self.path, path=f, content_class=Static, fmt='static', From 180cf9165fd0f35d57c51b4cc4111f1a5a92858c Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 13 Jun 2013 21:12:43 -0400 Subject: [PATCH 034/160] settings: Fix deprecation warning in configure_settings() The broken code came from my 1d4d86c (settings: Rework the LESS_GENERATOR removal warning for easy extension, 2013-03-24), where I put formatting placeholders ({}) into the warning message, but forgot to fill them in :/. --- pelican/settings.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pelican/settings.py b/pelican/settings.py index df3ad6b6..c6cc6c3c 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -261,9 +261,10 @@ def configure_settings(settings): 'https://github.com/getpelican/pelican/blob/master/docs/settings.rst#path-metadata'), ]: if old in settings: - message = 'The {} setting has been removed in favor of {}' + message = 'The {} setting has been removed in favor of {}'.format( + old, new) if doc: - message += ', see {} for details' + message += ', see {} for details'.format(doc) logger.warning(message) return settings From dfb29b53880ecd8c5f6ee99c09040d84fa17c57b Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sat, 15 Jun 2013 12:24:48 -0700 Subject: [PATCH 035/160] Update changelog --- docs/changelog.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index fcec0b53..0804c8b0 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,17 @@ Release history 3.3 (XXXX-XX-XX) ================ +* Rename signals for better consistency (some plugins may need to be updated) +* Move metadata extraction from generators to readers; metadata extraction no + longer article-specific +* Deprecate ``FILES_TO_COPY`` in favor of ``STATIC_PATHS`` and + ``EXTRA_PATH_METADATA`` + +3.2.1 and 3.2.2 +=============== + +* Facilitate inclusion in FreeBSD Ports Collection + 3.2 (2013-04-24) ================ From 0dee76f2598336a70d3c4535fd49217566971a44 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 5 Jan 2013 08:42:50 -0500 Subject: [PATCH 036/160] samples: Remove EXTRA_PATH_METADATA entries for pictures I'd added them earlier to test that a configuration edit could preserve the original output locations. However, it is likely that you have quite a number of static files, and we shouldn't recommend listing explicit paths for all of them. With this configuration change, the pictures will be copied into the output directory using their original relative path (e.g. `pictures/Fat_Cat.jpg` without the `static`). Any |filename|-style links will be updated automatically. If you *want* the pictures to end up in a `static` directory, it's easier to just organize your source that way. --- .../output/custom/author/alexis-metaireau2.html | 2 +- pelican/tests/output/custom/category/bar.html | 2 +- pelican/tests/output/custom/category/yeah.html | 4 ++-- pelican/tests/output/custom/feeds/all-en.atom.xml | 6 +++--- pelican/tests/output/custom/feeds/all.atom.xml | 6 +++--- pelican/tests/output/custom/feeds/all.rss.xml | 6 +++--- pelican/tests/output/custom/feeds/bar.atom.xml | 2 +- pelican/tests/output/custom/feeds/bar.rss.xml | 2 +- pelican/tests/output/custom/feeds/yeah.atom.xml | 4 ++-- pelican/tests/output/custom/feeds/yeah.rss.xml | 4 ++-- pelican/tests/output/custom/index2.html | 2 +- pelican/tests/output/custom/oh-yeah.html | 2 +- .../output/custom/pages/this-is-a-test-page.html | 2 +- .../output/custom/{static => }/pictures/Fat_Cat.jpg | Bin .../output/custom/{static => }/pictures/Sushi.jpg | Bin .../custom/{static => }/pictures/Sushi_Macro.jpg | Bin pelican/tests/output/custom/robots.txt | 2 +- pelican/tests/output/custom/tag/bar.html | 2 +- pelican/tests/output/custom/tag/foobar.html | 4 ++-- pelican/tests/output/custom/tag/oh.html | 2 +- pelican/tests/output/custom/tag/yeah.html | 2 +- .../output/custom/this-is-a-super-article.html | 4 ++-- samples/content/extra/robots.txt | 2 +- samples/pelican.conf.py | 3 --- 24 files changed, 31 insertions(+), 34 deletions(-) rename pelican/tests/output/custom/{static => }/pictures/Fat_Cat.jpg (100%) rename pelican/tests/output/custom/{static => }/pictures/Sushi.jpg (100%) rename pelican/tests/output/custom/{static => }/pictures/Sushi_Macro.jpg (100%) diff --git a/pelican/tests/output/custom/author/alexis-metaireau2.html b/pelican/tests/output/custom/author/alexis-metaireau2.html index 23346f5b..9f4a31e8 100644 --- a/pelican/tests/output/custom/author/alexis-metaireau2.html +++ b/pelican/tests/output/custom/author/alexis-metaireau2.html @@ -81,7 +81,7 @@

Why not ?

After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst ! YEAH !

-alternate text +alternate text
read more diff --git a/pelican/tests/output/custom/category/bar.html b/pelican/tests/output/custom/category/bar.html index 2c1fc646..f7f1bbf3 100644 --- a/pelican/tests/output/custom/category/bar.html +++ b/pelican/tests/output/custom/category/bar.html @@ -49,7 +49,7 @@

Why not ?

After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst ! YEAH !

-alternate text +alternate text

There are comments.

diff --git a/pelican/tests/output/custom/category/yeah.html b/pelican/tests/output/custom/category/yeah.html index 6894e3ee..f7cc8d12 100644 --- a/pelican/tests/output/custom/category/yeah.html +++ b/pelican/tests/output/custom/category/yeah.html @@ -47,8 +47,8 @@

This is a simple title

And here comes the cool stuff.

-alternate text -alternate text +alternate text +alternate text
 >>> from ipdb import set_trace
 >>> set_trace()
diff --git a/pelican/tests/output/custom/feeds/all-en.atom.xml b/pelican/tests/output/custom/feeds/all-en.atom.xml
index 5c8f9241..49d45cde 100644
--- a/pelican/tests/output/custom/feeds/all-en.atom.xml
+++ b/pelican/tests/output/custom/feeds/all-en.atom.xml
@@ -10,8 +10,8 @@
 <div class="section" id="this-is-a-simple-title">
 <h2>This is a simple title</h2>
 <p>And here comes the cool <a class="reference external" href="http://books.couchdb.org/relax/design-documents/views">stuff</a>.</p>
-<img alt="alternate text" src="http://blog.notmyidea.org/static/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
-<img alt="alternate text" src="http://blog.notmyidea.org/static/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" />
+<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
+<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" />
 <pre class="literal-block">
 &gt;&gt;&gt; from ipdb import set_trace
 &gt;&gt;&gt; set_trace()
@@ -22,7 +22,7 @@
 <h2>Why not ?</h2>
 <p>After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst !
 YEAH !</p>
-<img alt="alternate text" src="http://blog.notmyidea.org/static/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
+<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
 </div>
 Unbelievable !2010-10-15T20:30:00+02:00Alexis Métaireautag:blog.notmyidea.org,2010-10-15:unbelievable.html<p>Or completely awesome. Depends the needs.</p>
 <p><a class="reference external" href="http://blog.notmyidea.org/a-markdown-powered-article.html">a root-relative link to markdown-article</a>
diff --git a/pelican/tests/output/custom/feeds/all.atom.xml b/pelican/tests/output/custom/feeds/all.atom.xml
index ae5fe30c..3187c2aa 100644
--- a/pelican/tests/output/custom/feeds/all.atom.xml
+++ b/pelican/tests/output/custom/feeds/all.atom.xml
@@ -12,8 +12,8 @@
 <div class="section" id="this-is-a-simple-title">
 <h2>This is a simple title</h2>
 <p>And here comes the cool <a class="reference external" href="http://books.couchdb.org/relax/design-documents/views">stuff</a>.</p>
-<img alt="alternate text" src="http://blog.notmyidea.org/static/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
-<img alt="alternate text" src="http://blog.notmyidea.org/static/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" />
+<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
+<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" />
 <pre class="literal-block">
 &gt;&gt;&gt; from ipdb import set_trace
 &gt;&gt;&gt; set_trace()
@@ -24,7 +24,7 @@
 <h2>Why not ?</h2>
 <p>After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst !
 YEAH !</p>
-<img alt="alternate text" src="http://blog.notmyidea.org/static/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
+<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
 </div>
 Unbelievable !2010-10-15T20:30:00+02:00Alexis Métaireautag:blog.notmyidea.org,2010-10-15:unbelievable.html<p>Or completely awesome. Depends the needs.</p>
 <p><a class="reference external" href="http://blog.notmyidea.org/a-markdown-powered-article.html">a root-relative link to markdown-article</a>
diff --git a/pelican/tests/output/custom/feeds/all.rss.xml b/pelican/tests/output/custom/feeds/all.rss.xml
index 89176ac0..8d07bec7 100644
--- a/pelican/tests/output/custom/feeds/all.rss.xml
+++ b/pelican/tests/output/custom/feeds/all.rss.xml
@@ -12,8 +12,8 @@
 <div class="section" id="this-is-a-simple-title">
 <h2>This is a simple title</h2>
 <p>And here comes the cool <a class="reference external" href="http://books.couchdb.org/relax/design-documents/views">stuff</a>.</p>
-<img alt="alternate text" src="http://blog.notmyidea.org/static/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
-<img alt="alternate text" src="http://blog.notmyidea.org/static/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" />
+<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
+<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" />
 <pre class="literal-block">
 &gt;&gt;&gt; from ipdb import set_trace
 &gt;&gt;&gt; set_trace()
@@ -24,7 +24,7 @@
 <h2>Why not ?</h2>
 <p>After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst !
 YEAH !</p>
-<img alt="alternate text" src="http://blog.notmyidea.org/static/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
+<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
 </div>
 Alexis MétaireauWed, 20 Oct 2010 10:14:00 +0200tag:blog.notmyidea.org,2010-10-20:oh-yeah.htmlohbaryeahUnbelievable !http://blog.notmyidea.org/unbelievable.html<p>Or completely awesome. Depends the needs.</p>
 <p><a class="reference external" href="http://blog.notmyidea.org/a-markdown-powered-article.html">a root-relative link to markdown-article</a>
diff --git a/pelican/tests/output/custom/feeds/bar.atom.xml b/pelican/tests/output/custom/feeds/bar.atom.xml
index 9945f938..99b7cc45 100644
--- a/pelican/tests/output/custom/feeds/bar.atom.xml
+++ b/pelican/tests/output/custom/feeds/bar.atom.xml
@@ -3,6 +3,6 @@
 <h2>Why not ?</h2>
 <p>After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst !
 YEAH !</p>
-<img alt="alternate text" src="http://blog.notmyidea.org/static/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
+<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
 </div>
 
\ No newline at end of file
diff --git a/pelican/tests/output/custom/feeds/bar.rss.xml b/pelican/tests/output/custom/feeds/bar.rss.xml
index 148411be..94bd93f2 100644
--- a/pelican/tests/output/custom/feeds/bar.rss.xml
+++ b/pelican/tests/output/custom/feeds/bar.rss.xml
@@ -3,6 +3,6 @@
 <h2>Why not ?</h2>
 <p>After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst !
 YEAH !</p>
-<img alt="alternate text" src="http://blog.notmyidea.org/static/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
+<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
 </div>
 Alexis MétaireauWed, 20 Oct 2010 10:14:00 +0200tag:blog.notmyidea.org,2010-10-20:oh-yeah.htmlohbaryeah
\ No newline at end of file
diff --git a/pelican/tests/output/custom/feeds/yeah.atom.xml b/pelican/tests/output/custom/feeds/yeah.atom.xml
index d2307359..1a152174 100644
--- a/pelican/tests/output/custom/feeds/yeah.atom.xml
+++ b/pelican/tests/output/custom/feeds/yeah.atom.xml
@@ -3,8 +3,8 @@
 <div class="section" id="this-is-a-simple-title">
 <h2>This is a simple title</h2>
 <p>And here comes the cool <a class="reference external" href="http://books.couchdb.org/relax/design-documents/views">stuff</a>.</p>
-<img alt="alternate text" src="http://blog.notmyidea.org/static/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
-<img alt="alternate text" src="http://blog.notmyidea.org/static/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" />
+<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
+<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" />
 <pre class="literal-block">
 &gt;&gt;&gt; from ipdb import set_trace
 &gt;&gt;&gt; set_trace()
diff --git a/pelican/tests/output/custom/feeds/yeah.rss.xml b/pelican/tests/output/custom/feeds/yeah.rss.xml
index b252c26a..85f93686 100644
--- a/pelican/tests/output/custom/feeds/yeah.rss.xml
+++ b/pelican/tests/output/custom/feeds/yeah.rss.xml
@@ -3,8 +3,8 @@
 <div class="section" id="this-is-a-simple-title">
 <h2>This is a simple title</h2>
 <p>And here comes the cool <a class="reference external" href="http://books.couchdb.org/relax/design-documents/views">stuff</a>.</p>
-<img alt="alternate text" src="http://blog.notmyidea.org/static/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
-<img alt="alternate text" src="http://blog.notmyidea.org/static/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" />
+<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
+<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" />
 <pre class="literal-block">
 &gt;&gt;&gt; from ipdb import set_trace
 &gt;&gt;&gt; set_trace()
diff --git a/pelican/tests/output/custom/index2.html b/pelican/tests/output/custom/index2.html
index 8ed54b6e..b8e2ac1a 100644
--- a/pelican/tests/output/custom/index2.html
+++ b/pelican/tests/output/custom/index2.html
@@ -132,7 +132,7 @@ as well as inline markup.

Why not ?

After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst ! YEAH !

-alternate text +alternate text
read more diff --git a/pelican/tests/output/custom/oh-yeah.html b/pelican/tests/output/custom/oh-yeah.html index f9f1f1fb..2f6ca309 100644 --- a/pelican/tests/output/custom/oh-yeah.html +++ b/pelican/tests/output/custom/oh-yeah.html @@ -52,7 +52,7 @@

Why not ?

After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst ! YEAH !

-alternate text +alternate text diff --git a/pelican/tests/output/custom/pages/this-is-a-test-page.html b/pelican/tests/output/custom/pages/this-is-a-test-page.html index 571211d9..b4cb678c 100644 --- a/pelican/tests/output/custom/pages/this-is-a-test-page.html +++ b/pelican/tests/output/custom/pages/this-is-a-test-page.html @@ -32,7 +32,7 @@

This is a test page

Just an image.

-alternate text +alternate text
diff --git a/pelican/tests/output/custom/static/pictures/Fat_Cat.jpg b/pelican/tests/output/custom/pictures/Fat_Cat.jpg similarity index 100% rename from pelican/tests/output/custom/static/pictures/Fat_Cat.jpg rename to pelican/tests/output/custom/pictures/Fat_Cat.jpg diff --git a/pelican/tests/output/custom/static/pictures/Sushi.jpg b/pelican/tests/output/custom/pictures/Sushi.jpg similarity index 100% rename from pelican/tests/output/custom/static/pictures/Sushi.jpg rename to pelican/tests/output/custom/pictures/Sushi.jpg diff --git a/pelican/tests/output/custom/static/pictures/Sushi_Macro.jpg b/pelican/tests/output/custom/pictures/Sushi_Macro.jpg similarity index 100% rename from pelican/tests/output/custom/static/pictures/Sushi_Macro.jpg rename to pelican/tests/output/custom/pictures/Sushi_Macro.jpg diff --git a/pelican/tests/output/custom/robots.txt b/pelican/tests/output/custom/robots.txt index ae5b0d05..19a6e299 100644 --- a/pelican/tests/output/custom/robots.txt +++ b/pelican/tests/output/custom/robots.txt @@ -1,2 +1,2 @@ User-agent: * -Disallow: /static/pictures +Disallow: /pictures diff --git a/pelican/tests/output/custom/tag/bar.html b/pelican/tests/output/custom/tag/bar.html index 2fed02c2..d1805e50 100644 --- a/pelican/tests/output/custom/tag/bar.html +++ b/pelican/tests/output/custom/tag/bar.html @@ -104,7 +104,7 @@ as well as inline markup.

Why not ?

After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst ! YEAH !

-alternate text +alternate text read more diff --git a/pelican/tests/output/custom/tag/foobar.html b/pelican/tests/output/custom/tag/foobar.html index 71c2d430..2c50c8ae 100644 --- a/pelican/tests/output/custom/tag/foobar.html +++ b/pelican/tests/output/custom/tag/foobar.html @@ -47,8 +47,8 @@

This is a simple title

And here comes the cool stuff.

-alternate text -alternate text +alternate text +alternate text
 >>> from ipdb import set_trace
 >>> set_trace()
diff --git a/pelican/tests/output/custom/tag/oh.html b/pelican/tests/output/custom/tag/oh.html
index 361b1fd2..b798964e 100644
--- a/pelican/tests/output/custom/tag/oh.html
+++ b/pelican/tests/output/custom/tag/oh.html
@@ -49,7 +49,7 @@
 

Why not ?

After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst ! YEAH !

-alternate text +alternate text

There are comments.

diff --git a/pelican/tests/output/custom/tag/yeah.html b/pelican/tests/output/custom/tag/yeah.html index 9bbd214b..54ad93c6 100644 --- a/pelican/tests/output/custom/tag/yeah.html +++ b/pelican/tests/output/custom/tag/yeah.html @@ -49,7 +49,7 @@

Why not ?

After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst ! YEAH !

-alternate text +alternate text

There are comments.

diff --git a/pelican/tests/output/custom/this-is-a-super-article.html b/pelican/tests/output/custom/this-is-a-super-article.html index 000bd555..1fdd8897 100644 --- a/pelican/tests/output/custom/this-is-a-super-article.html +++ b/pelican/tests/output/custom/this-is-a-super-article.html @@ -50,8 +50,8 @@

This is a simple title

And here comes the cool stuff.

-alternate text -alternate text +alternate text +alternate text
 >>> from ipdb import set_trace
 >>> set_trace()
diff --git a/samples/content/extra/robots.txt b/samples/content/extra/robots.txt
index ae5b0d05..19a6e299 100644
--- a/samples/content/extra/robots.txt
+++ b/samples/content/extra/robots.txt
@@ -1,2 +1,2 @@
 User-agent: *
-Disallow: /static/pictures
+Disallow: /pictures
diff --git a/samples/pelican.conf.py b/samples/pelican.conf.py
index ad2042fd..4d5cd06d 100755
--- a/samples/pelican.conf.py
+++ b/samples/pelican.conf.py
@@ -37,9 +37,6 @@ DEFAULT_METADATA = (('yeah', 'it is'),)
 # path-specific metadata
 EXTRA_PATH_METADATA = {
     'extra/robots.txt': {'path': 'robots.txt'},
-    'pictures/Fat_Cat.jpg': {'path': 'static/pictures/Fat_Cat.jpg'},
-    'pictures/Sushi.jpg': {'path': 'static/pictures/Sushi.jpg'},
-    'pictures/Sushi_Macro.jpg': {'path': 'static/pictures/Sushi_Macro.jpg'},
     }
 
 # static paths will be copied without parsing their contents

From 8f295f7a037e0d512181946b9b87636f4a853e26 Mon Sep 17 00:00:00 2001
From: Justin Mayer 
Date: Sun, 16 Jun 2013 08:53:45 -0700
Subject: [PATCH 037/160] PyPI now has CDN; Travis shouldn't use mirrors

Now that PyPI utilizes a CDN, the "--use-mirrors" setting slows down the
install process and has essentially been deprecated.
---
 .travis.yml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 1ff512b6..918fd3f9 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,7 +8,7 @@ before_install:
  - sudo locale-gen fr_FR.UTF-8 tr_TR.UTF-8
 install:
     - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then ln -s /usr/share/asciidoc/asciidocapi.py ~/virtualenv/python2.7/lib/python2.7/site-packages/; fi
-    - pip install mock --use-mirrors
-    - pip install . --use-mirrors
-    - pip install --use-mirrors Markdown
+    - pip install mock
+    - pip install .
+    - pip install Markdown
 script: python -m unittest discover

From 0d1866b393e79a76ab2b416eb22ac6a924b3849b Mon Sep 17 00:00:00 2001
From: "W. Trevor King" 
Date: Sat, 5 Jan 2013 11:39:06 -0500
Subject: [PATCH 038/160] Pelican.run: Use keyword arguments when initializing
 generators

This makes it easier to add new arguments to Generator subclasses.
---
 pelican/__init__.py | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/pelican/__init__.py b/pelican/__init__.py
index 7f406c4f..78a16e27 100644
--- a/pelican/__init__.py
+++ b/pelican/__init__.py
@@ -157,12 +157,12 @@ class Pelican(object):
         context['localsiteurl'] = self.settings['SITEURL']  # share
         generators = [
             cls(
-                context,
-                self.settings,
-                self.path,
-                self.theme,
-                self.output_path,
-                self.markup,
+                context=context,
+                settings=self.settings,
+                path=self.path,
+                theme=self.theme,
+                output_path=self.output_path,
+                markup=self.markup,
             ) for cls in self.get_generator_classes()
         ]
 

From 6e527e7416cc51b07c4a83b41f050bcf12618fdc Mon Sep 17 00:00:00 2001
From: "W. Trevor King" 
Date: Sat, 5 Jan 2013 14:23:02 -0500
Subject: [PATCH 039/160] test_generators: Use keyword arguments to initialize
 Generators

---
 pelican/tests/test_generators.py | 54 +++++++++++++++++++-------------
 1 file changed, 33 insertions(+), 21 deletions(-)

diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py
index f5ed6b85..54f4a232 100644
--- a/pelican/tests/test_generators.py
+++ b/pelican/tests/test_generators.py
@@ -33,8 +33,10 @@ class TestArticlesGenerator(unittest.TestCase):
             settings = get_settings(filenames={})
             settings['DEFAULT_CATEGORY'] = 'Default'
             settings['DEFAULT_DATE'] = (1970, 1, 1)
-            self.generator = ArticlesGenerator(settings.copy(), settings,
-                CONTENT_DIR, settings['THEME'], None,  settings['MARKUP'])
+            self.generator = ArticlesGenerator(
+                context=settings.copy(), settings=settings,
+                path=CONTENT_DIR, theme=settings['THEME'],
+                output_path=None, markup=settings['MARKUP'])
             self.generator.generate_context()
         return self.generator
 
@@ -52,16 +54,19 @@ class TestArticlesGenerator(unittest.TestCase):
 
     def test_generate_feeds(self):
         settings = get_settings()
-        generator = ArticlesGenerator(settings, settings, None,
-                settings['THEME'], None, settings['MARKUP'])
+        generator = ArticlesGenerator(
+            context=settings, settings=settings,
+            path=None, theme=settings['THEME'],
+            output_path=None, markup=settings['MARKUP'])
         writer = MagicMock()
         generator.generate_feeds(writer)
         writer.write_feed.assert_called_with([], settings,
                                              'feeds/all.atom.xml')
 
         generator = ArticlesGenerator(
-            settings, get_settings(FEED_ALL_ATOM=None), None,
-            settings['THEME'], None, None)
+            context=settings, settings=get_settings(FEED_ALL_ATOM=None),
+            path=None, theme=settings['THEME'],
+            output_path=None, markup=None)
         writer = MagicMock()
         generator.generate_feeds(writer)
         self.assertFalse(writer.write_feed.called)
@@ -122,8 +127,9 @@ class TestArticlesGenerator(unittest.TestCase):
         settings['USE_FOLDER_AS_CATEGORY'] = False
         settings['filenames'] = {}
         generator = ArticlesGenerator(
-            settings.copy(), settings, CONTENT_DIR, DEFAULT_CONFIG['THEME'],
-            None, DEFAULT_CONFIG['MARKUP'])
+            context=settings.copy(), settings=settings,
+            path=CONTENT_DIR, theme=DEFAULT_CONFIG['THEME'],
+            output_path=None, markup=DEFAULT_CONFIG['MARKUP'])
         generator.generate_context()
         # test for name
         # categories are grouped by slug; if two categories have the same slug
@@ -143,9 +149,10 @@ class TestArticlesGenerator(unittest.TestCase):
     def test_direct_templates_save_as_default(self):
 
         settings = get_settings(filenames={})
-        generator = ArticlesGenerator(settings, settings, None,
-                                      settings['THEME'], None,
-                                      settings['MARKUP'])
+        generator = ArticlesGenerator(
+            context=settings, settings=settings,
+            path=None, theme=settings['THEME'],
+            output_path=None, markup=settings['MARKUP'])
         write = MagicMock()
         generator.generate_direct_templates(write)
         write.assert_called_with("archives.html",
@@ -157,9 +164,10 @@ class TestArticlesGenerator(unittest.TestCase):
         settings = get_settings()
         settings['DIRECT_TEMPLATES'] = ['archives']
         settings['ARCHIVES_SAVE_AS'] = 'archives/index.html'
-        generator = ArticlesGenerator(settings, settings, None,
-                                      settings['THEME'], None,
-                                      settings['MARKUP'])
+        generator = ArticlesGenerator(
+            context=settings, settings=settings,
+            path=None, theme=settings['THEME'],
+            output_path=None, markup=settings['MARKUP'])
         write = MagicMock()
         generator.generate_direct_templates(write)
         write.assert_called_with("archives/index.html",
@@ -171,9 +179,10 @@ class TestArticlesGenerator(unittest.TestCase):
         settings = get_settings()
         settings['DIRECT_TEMPLATES'] = ['archives']
         settings['ARCHIVES_SAVE_AS'] = 'archives/index.html'
-        generator = ArticlesGenerator(settings, settings, None,
-                                      settings['THEME'], None,
-                                      settings['MARKUP'])
+        generator = ArticlesGenerator(
+            context=settings, settings=settings,
+            path=None, theme=settings['THEME'],
+            output_path=None, markup=settings['MARKUP'])
         write = MagicMock()
         generator.generate_direct_templates(write)
         write.assert_called_count == 0
@@ -215,8 +224,9 @@ class TestPageGenerator(unittest.TestCase):
         settings['DEFAULT_DATE'] = (1970, 1, 1)
 
         generator = PagesGenerator(
-            settings.copy(), settings, CUR_DIR, settings['THEME'], None,
-            settings['MARKUP'])
+            context=settings.copy(), settings=settings,
+            path=CUR_DIR, theme=settings['THEME'],
+            output_path=None, markup=settings['MARKUP'])
         generator.generate_context()
         pages = self.distill_pages(generator.pages)
         hidden_pages = self.distill_pages(generator.hidden_pages)
@@ -258,8 +268,10 @@ class TestTemplatePagesGenerator(unittest.TestCase):
                 'template/source.html': 'generated/file.html'
                 }
 
-        generator = TemplatePagesGenerator({'foo': 'bar'}, settings,
-                self.temp_content, '', self.temp_output, None)
+        generator = TemplatePagesGenerator(
+            context={'foo': 'bar'}, settings=settings,
+            path=self.temp_content, theme='',
+            output_path=self.temp_output, markup=None)
 
         # create a dummy template file
         template_dir = os.path.join(self.temp_content, 'template')

From 12dd35ef360e30f7fb5cc107069cacbe5c43595b Mon Sep 17 00:00:00 2001
From: "W. Trevor King" 
Date: Sat, 5 Jan 2013 11:41:33 -0500
Subject: [PATCH 040/160] generators: Remove wonky argument handling from
 Generator.__init__

---
 pelican/generators.py | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/pelican/generators.py b/pelican/generators.py
index a01281dc..3ebcb648 100644
--- a/pelican/generators.py
+++ b/pelican/generators.py
@@ -31,10 +31,14 @@ logger = logging.getLogger(__name__)
 class Generator(object):
     """Baseclass generator"""
 
-    def __init__(self, *args, **kwargs):
-        for idx, item in enumerate(('context', 'settings', 'path', 'theme',
-                'output_path', 'markup')):
-            setattr(self, item, args[idx])
+    def __init__(self, context, settings, path, theme, output_path, markup,
+                 **kwargs):
+        self.context = context
+        self.settings = settings
+        self.path = path
+        self.theme = theme
+        self.output_path = output_path
+        self.markup = markup
 
         for arg, value in kwargs.items():
             setattr(self, arg, value)

From 39dd4a025581cfa3b4d6256a3b8f327b26372e1d Mon Sep 17 00:00:00 2001
From: Kyle Machulis 
Date: Fri, 14 Jun 2013 12:12:19 -0700
Subject: [PATCH 041/160] Changed meta tag "contents" attribute to "content",
 to conform to HTML spec. Fixes #918

---
 docs/getting_started.rst                          | 10 +++++-----
 pelican/readers.py                                | 15 ++++++++++++---
 pelican/tests/content/article_with_keywords.html  |  2 +-
 pelican/tests/content/article_with_metadata.html  | 12 ++++++------
 .../article_with_metadata_and_contents.html       | 15 +++++++++++++++
 .../content/article_with_uppercase_metadata.html  |  2 +-
 pelican/tests/test_readers.py                     | 15 +++++++++++++++
 7 files changed, 55 insertions(+), 16 deletions(-)
 create mode 100644 pelican/tests/content/article_with_metadata_and_contents.html

diff --git a/docs/getting_started.rst b/docs/getting_started.rst
index 383acdc4..1e31f26d 100644
--- a/docs/getting_started.rst
+++ b/docs/getting_started.rst
@@ -265,11 +265,11 @@ interprets the HTML in a very straightforward manner, reading metadata from
     
         
             My super title
-            
-            
-            
-            
-            
+            
+            
+            
+            
+            
         
         
             This is the content of my super blog post.
diff --git a/pelican/readers.py b/pelican/readers.py
index bd9f5914..fb2ccfc4 100644
--- a/pelican/readers.py
+++ b/pelican/readers.py
@@ -5,6 +5,7 @@ import datetime
 import logging
 import os
 import re
+import logging
 try:
     import docutils
     import docutils.core
@@ -47,6 +48,8 @@ METADATA_PROCESSORS = {
     'author': Author,
 }
 
+logger = logging.getLogger(__name__)
+
 
 class Reader(object):
     enabled = True
@@ -199,7 +202,7 @@ class HTMLReader(Reader):
     enabled = True
 
     class _HTMLParser(HTMLParser):
-        def __init__(self, settings):
+        def __init__(self, settings, filename):
             HTMLParser.__init__(self)
             self.body = ''
             self.metadata = {}
@@ -207,6 +210,8 @@ class HTMLReader(Reader):
 
             self._data_buffer = ''
 
+            self._filename = filename
+
             self._in_top_level = True
             self._in_head = False
             self._in_title = False
@@ -275,7 +280,11 @@ class HTMLReader(Reader):
 
         def _handle_meta_tag(self, attrs):
             name = self._attr_value(attrs, 'name').lower()
-            contents = self._attr_value(attrs, 'contents', '')
+            contents = self._attr_value(attrs, 'content', '')
+            if not contents:
+                contents = self._attr_value(attrs, 'contents', '')
+                if contents:
+                    logger.warning("Meta tag attribute 'contents' used in file %s, should be changed to 'content'", self._filename)
 
             if name == 'keywords':
                 name = 'tags'
@@ -288,7 +297,7 @@ class HTMLReader(Reader):
     def read(self, filename):
         """Parse content and metadata of HTML files"""
         with pelican_open(filename) as content:
-            parser = self._HTMLParser(self.settings)
+            parser = self._HTMLParser(self.settings, filename)
             parser.feed(content)
             parser.close()
 
diff --git a/pelican/tests/content/article_with_keywords.html b/pelican/tests/content/article_with_keywords.html
index c869f514..0744c754 100644
--- a/pelican/tests/content/article_with_keywords.html
+++ b/pelican/tests/content/article_with_keywords.html
@@ -1,6 +1,6 @@
 
     
         This is a super article !
-        
+        
     
 
diff --git a/pelican/tests/content/article_with_metadata.html b/pelican/tests/content/article_with_metadata.html
index b108ac8a..b501ea29 100644
--- a/pelican/tests/content/article_with_metadata.html
+++ b/pelican/tests/content/article_with_metadata.html
@@ -1,12 +1,12 @@
 
     
         This is a super article !
-        
-        
-        
-        
-        
-        
+        
+        
+        
+        
+        
+        
     
     
         Multi-line metadata should be supported
diff --git a/pelican/tests/content/article_with_metadata_and_contents.html b/pelican/tests/content/article_with_metadata_and_contents.html
new file mode 100644
index 00000000..b108ac8a
--- /dev/null
+++ b/pelican/tests/content/article_with_metadata_and_contents.html
@@ -0,0 +1,15 @@
+
+    
+        This is a super article !
+        
+        
+        
+        
+        
+        
+    
+    
+        Multi-line metadata should be supported
+        as well as inline markup.
+    
+
diff --git a/pelican/tests/content/article_with_uppercase_metadata.html b/pelican/tests/content/article_with_uppercase_metadata.html
index 4fe5a9ee..b4cedf39 100644
--- a/pelican/tests/content/article_with_uppercase_metadata.html
+++ b/pelican/tests/content/article_with_uppercase_metadata.html
@@ -1,6 +1,6 @@
 
     
         This is a super article !
-        
+        
     
 
diff --git a/pelican/tests/test_readers.py b/pelican/tests/test_readers.py
index 14d42325..c67b8a1f 100644
--- a/pelican/tests/test_readers.py
+++ b/pelican/tests/test_readers.py
@@ -350,6 +350,21 @@ class HTMLReaderTest(ReaderTest):
         for key, value in expected.items():
             self.assertEqual(value, page.metadata[key], key)
 
+    def test_article_with_metadata_and_contents_attrib(self):
+        page = self.read_file(path='article_with_metadata_and_contents.html')
+        expected = {
+            'category': 'yeah',
+            'author': 'Alexis Métaireau',
+            'title': 'This is a super article !',
+            'summary': 'Summary and stuff',
+            'date': datetime.datetime(2010, 12, 2, 10, 14),
+            'tags': ['foo', 'bar', 'foobar'],
+            'custom_field': 'http://notmyidea.org',
+        }
+        for key, value in expected.items():
+            self.assertEqual(value, page.metadata[key], key)
+
+
     def test_article_with_null_attributes(self):
         page = self.read_file(path='article_with_null_attributes.html')
 

From d8c9fb31d0d1a0f5f3f5319dbabbf50e36fd255b Mon Sep 17 00:00:00 2001
From: Danilo Bargen 
Date: Thu, 20 Jun 2013 00:13:57 +0200
Subject: [PATCH 042/160] Better duck typing in isinstance check

---
 pelican/__init__.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/pelican/__init__.py b/pelican/__init__.py
index 78a16e27..1739aae3 100644
--- a/pelican/__init__.py
+++ b/pelican/__init__.py
@@ -9,6 +9,7 @@ import time
 import logging
 import argparse
 import locale
+import collections
 
 from pelican import signals
 
@@ -205,7 +206,7 @@ class Pelican(object):
         for pair in signals.get_generators.send(self):
             (funct, value) = pair
 
-            if not isinstance(value, (tuple, list)):
+            if not isinstance(value, collections.Iterable):
                 value = (value, )
 
             for v in value:

From dd9f55c8bb0979d230c42cd28bb8b6fbe6d41d98 Mon Sep 17 00:00:00 2001
From: Justin Mayer 
Date: Sat, 22 Jun 2013 12:28:37 -0700
Subject: [PATCH 043/160] Clean up minor text formatting, spelling, grammar

---
 pelican/utils.py | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/pelican/utils.py b/pelican/utils.py
index 9ba234a5..fea7b953 100644
--- a/pelican/utils.py
+++ b/pelican/utils.py
@@ -67,11 +67,11 @@ def strftime(date, date_format):
 
 class DateFormatter(object):
     '''A date formatter object used as a jinja filter
-    
-    Uses the `strftime` implementation and makes sure jinja uses the locale 
+
+    Uses the `strftime` implementation and makes sure jinja uses the locale
     defined in LOCALE setting
     '''
-    
+
     def __init__(self):
         self.locale = locale.setlocale(locale.LC_TIME)
 
@@ -216,7 +216,7 @@ def get_date(string):
 
 
 class pelican_open(object):
-    """Open a file and return it's content"""
+    """Open a file and return its content"""
     def __init__(self, filename):
         self.filename = filename
 
@@ -236,7 +236,7 @@ def slugify(value):
     Normalizes string, converts to lowercase, removes non-alpha characters,
     and converts spaces to hyphens.
 
-    Took from django sources.
+    Took from Django sources.
     """
     # TODO Maybe steal again from current Django 1.5dev
     value = Markup(value).striptags()

From e9fec3b1dc32cf9df20fc9774a63065bc14ddad8 Mon Sep 17 00:00:00 2001
From: Justin Mayer 
Date: Mon, 24 Jun 2013 13:05:00 -0700
Subject: [PATCH 044/160] Remove "clean" task from "make html"; fixes #637

This change removes the "clean" task from the "html" and "regenerate"
tasks in the default Makefile generated by pelican-quickstart. The
previous behavior ignored whether the DELETE_OUTPUT_DIRECTORY
setting was set to True or not and deleted everything in the output
directory every time the "make html" or "make regenerate" task was run.
In addition to violating the Principle of Least Astonishment, there was
also the potential for data loss if the user wasn't careful when
defining the output directory location in the Makefile.

The new behavior therefore relies primarily on the
DELETE_OUTPUT_DIRECTORY setting to control if and when the output
directory is cleaned. The default settings and Makefile generated by the
pelican-quickstart command, for example, no longer clean the output
directory when the "make html" task is run. If the user wants to change
this behavior and have the output directory cleaned on every "make html"
run, the recommended method would be to set DELETE_OUTPUT_DIRECTORY to
True in pelicanconf.py. Alternatively, the user can manually run "make
clean", with the caveat that the output directory and its contents will
be entirely destroyed, including any otherwise to-be-retained files or
folders specified in the OUTPUT_RETENTION setting. It is for that reason
that relying on the DELETE_OUTPUT_DIRECTORY setting is instead
recommended.

As before, DELETE_OUTPUT_DIRECTORY is set to True in the publishconf.py
settings file generated by the pelican-quickstart script. This way, any
potentially old and irrelevant files will be automatically removed
before the latest version of the site is transferred to the production
server environment.

In summary, this change allows for the sanest possible default settings,
while still allowing end users to customize output cleaning to their
preferred behavior with a minimum of confusion.
---
 pelican/tools/templates/Makefile.in | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/pelican/tools/templates/Makefile.in b/pelican/tools/templates/Makefile.in
index 221568aa..8890db6f 100644
--- a/pelican/tools/templates/Makefile.in
+++ b/pelican/tools/templates/Makefile.in
@@ -40,15 +40,13 @@ help:
 	@echo '                                                                       '
 
 
-html: clean $$(OUTPUTDIR)/index.html
-
-$$(OUTPUTDIR)/%.html:
+html:
 	$$(PELICAN) $$(INPUTDIR) -o $$(OUTPUTDIR) -s $$(CONFFILE) $$(PELICANOPTS)
 
 clean:
 	[ ! -d $$(OUTPUTDIR) ] || find $$(OUTPUTDIR) -mindepth 1 -delete
 
-regenerate: clean
+regenerate:
 	$$(PELICAN) -r $$(INPUTDIR) -o $$(OUTPUTDIR) -s $$(CONFFILE) $$(PELICANOPTS)
 
 serve:

From bf0a50880d35f803ec5c929bfe60b026accf9307 Mon Sep 17 00:00:00 2001
From: Justin Mayer 
Date: Mon, 24 Jun 2013 13:40:32 -0700
Subject: [PATCH 045/160] Revert "make clean" behavior to rm -rf. Fixes #773

The change to the "make clean" task in 764a2cf from "rm -rf" to instead
relying on GNU "find" appears to have broken cross-platform portability,
likely causing problems on *BSD and other platforms. This commit reverts
that change back to the previous "rm -rf" behavior.
---
 pelican/tools/templates/Makefile.in | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pelican/tools/templates/Makefile.in b/pelican/tools/templates/Makefile.in
index 8890db6f..4bc764ca 100644
--- a/pelican/tools/templates/Makefile.in
+++ b/pelican/tools/templates/Makefile.in
@@ -44,7 +44,7 @@ html:
 	$$(PELICAN) $$(INPUTDIR) -o $$(OUTPUTDIR) -s $$(CONFFILE) $$(PELICANOPTS)
 
 clean:
-	[ ! -d $$(OUTPUTDIR) ] || find $$(OUTPUTDIR) -mindepth 1 -delete
+	[ ! -d $$(OUTPUTDIR) ] || rm -rf $$(OUTPUTDIR)
 
 regenerate:
 	$$(PELICAN) -r $$(INPUTDIR) -o $$(OUTPUTDIR) -s $$(CONFFILE) $$(PELICANOPTS)

From 6f36b0a2460a41eabb1edd0bd70318c52ddadd47 Mon Sep 17 00:00:00 2001
From: Justin Mayer 
Date: Sun, 23 Jun 2013 11:44:53 -0700
Subject: [PATCH 046/160] Keep certain files when cleaning output; fix #574

If DELETE_OUTPUT_DIRECTORY is set to True, all files and directories are
deleted from the output directory. There are, however, several reasons
one might want to retain certain files/directories and avoid their
deletion from the output directory. One such use case is version control
system data: a versioned output directory can facilitate deployment via
Heroku and/or allow the user to easily revert to a prior version of the
site without having to rely on regeneration via Pelican.

This change introduces the OUTPUT_RETENTION setting, a tuple of
filenames that will be preserved when the clean_output_dir function in
pelican.utils is run. Setting OUTPUT_RETENTION = (".hg", ".git") would,
for example, prevent the relevant VCS data from being deleted when the
output directory is cleaned.
---
 docs/settings.rst           |  3 +++
 pelican/__init__.py         |  3 ++-
 pelican/settings.py         |  1 +
 pelican/tests/test_utils.py |  9 ++++++---
 pelican/utils.py            | 11 +++++++----
 5 files changed, 19 insertions(+), 8 deletions(-)

diff --git a/docs/settings.rst b/docs/settings.rst
index ffcddc7a..97662cce 100644
--- a/docs/settings.rst
+++ b/docs/settings.rst
@@ -72,6 +72,9 @@ Setting name (default value)                                            What doe
                                                                         generating new files. This can be useful in preventing older,
                                                                         unnecessary files from persisting in your output. However, **this is
                                                                         a destructive setting and should be handled with extreme care.**
+`OUTPUT_RETENTION` (``()``)                                             A tuple of filenames that should be retained and not deleted from the
+                                                                        output directory. One use case would be the preservation of version
+                                                                        control data. For example: ``(".hg", ".git", ".bzr")``
 `JINJA_EXTENSIONS` (``[]``)                                             A list of any Jinja2 extensions you want to use.
 `JINJA_FILTERS` (``{}``)                                                A list of custom Jinja2 filters you want to use.
                                                                         The dictionary should map the filtername to the filter function.
diff --git a/pelican/__init__.py b/pelican/__init__.py
index 1739aae3..53216421 100644
--- a/pelican/__init__.py
+++ b/pelican/__init__.py
@@ -49,6 +49,7 @@ class Pelican(object):
         self.markup = settings['MARKUP']
         self.ignore_files = settings['IGNORE_FILES']
         self.delete_outputdir = settings['DELETE_OUTPUT_DIRECTORY']
+        self.output_retention = settings['OUTPUT_RETENTION']
 
         self.init_path()
         self.init_plugins()
@@ -175,7 +176,7 @@ class Pelican(object):
         # explicitely asked
         if (self.delete_outputdir and not
                 os.path.realpath(self.path).startswith(self.output_path)):
-            clean_output_dir(self.output_path)
+            clean_output_dir(self.output_path, self.output_retention)
 
         writer = self.get_writer()
 
diff --git a/pelican/settings.py b/pelican/settings.py
index c6cc6c3c..1c9b48c3 100644
--- a/pelican/settings.py
+++ b/pelican/settings.py
@@ -54,6 +54,7 @@ DEFAULT_CONFIG = {
     'NEWEST_FIRST_ARCHIVES': True,
     'REVERSE_CATEGORY_ORDER': False,
     'DELETE_OUTPUT_DIRECTORY': False,
+    'OUTPUT_RETENTION': (),
     'ARTICLE_URL': '{slug}.html',
     'ARTICLE_SAVE_AS': '{slug}.html',
     'ARTICLE_LANG_URL': '{slug}-{lang}.html',
diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py
index 0713e5ed..ab35d991 100644
--- a/pelican/tests/test_utils.py
+++ b/pelican/tests/test_utils.py
@@ -193,28 +193,31 @@ class TestUtils(LoggedTestCase):
             shutil.rmtree(empty_path, True)
 
     def test_clean_output_dir(self):
+        retention = ()
         test_directory = os.path.join(os.path.dirname(__file__),
                                       'clean_output')
         content = os.path.join(os.path.dirname(__file__), 'content')
         shutil.copytree(content, test_directory)
-        utils.clean_output_dir(test_directory)
+        utils.clean_output_dir(test_directory, retention)
         self.assertTrue(os.path.isdir(test_directory))
         self.assertListEqual([], os.listdir(test_directory))
         shutil.rmtree(test_directory)
 
     def test_clean_output_dir_not_there(self):
+        retention = ()
         test_directory = os.path.join(os.path.dirname(__file__),
                                       'does_not_exist')
-        utils.clean_output_dir(test_directory)
+        utils.clean_output_dir(test_directory, retention)
         self.assertFalse(os.path.exists(test_directory))
 
     def test_clean_output_dir_is_file(self):
+        retention = ()
         test_directory = os.path.join(os.path.dirname(__file__),
                                       'this_is_a_file')
         f = open(test_directory, 'w')
         f.write('')
         f.close()
-        utils.clean_output_dir(test_directory)
+        utils.clean_output_dir(test_directory, retention)
         self.assertFalse(os.path.exists(test_directory))
 
     def test_strftime(self):
diff --git a/pelican/utils.py b/pelican/utils.py
index fea7b953..2c70ae8c 100644
--- a/pelican/utils.py
+++ b/pelican/utils.py
@@ -298,8 +298,8 @@ def copy(path, source, destination, destination_path=None, overwrite=False):
         logger.warning('skipped copy %s to %s' % (source_, destination_))
 
 
-def clean_output_dir(path):
-    """Remove all the files from the output directory"""
+def clean_output_dir(path, retention):
+    """Remove all files from output directory except those in retention list"""
 
     if not os.path.exists(path):
         logger.debug("Directory already removed: %s" % path)
@@ -312,10 +312,13 @@ def clean_output_dir(path):
             logger.error("Unable to delete file %s; %s" % (path, str(e)))
         return
 
-    # remove all the existing content from the output folder
+    # remove existing content from output folder unless in retention list
     for filename in os.listdir(path):
         file = os.path.join(path, filename)
-        if os.path.isdir(file):
+        if any(filename == retain for retain in retention):
+            logger.debug("Skipping deletion; %s is on retention list: %s" \
+                         % (filename, file))
+        elif os.path.isdir(file):
             try:
                 shutil.rmtree(file)
                 logger.debug("Deleted directory %s" % file)

From 7d37cfa7485e3a8cb1f8e0ac6001be66fb553bbc Mon Sep 17 00:00:00 2001
From: Justin Mayer 
Date: Tue, 25 Jun 2013 19:17:40 -0700
Subject: [PATCH 047/160] Missing -s flag added to command on tips doc page

---
 docs/tips.rst | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/tips.rst b/docs/tips.rst
index 64695db0..e29f73d2 100644
--- a/docs/tips.rst
+++ b/docs/tips.rst
@@ -26,7 +26,7 @@ For example, if the sources of your Pelican site are contained in a GitHub
 repository, and if you want to publish your Pelican site as Project Pages of
 this repository, you can then use the following::
 
-    $ pelican content -o output pelicanconf.py
+    $ pelican content -o output -s pelicanconf.py
     $ ghp-import output
     $ git push origin gh-pages
 

From 12fd53c27e133ed4e79c2f5a85ea8fb038c23b12 Mon Sep 17 00:00:00 2001
From: bas smit 
Date: Wed, 26 Jun 2013 13:25:20 +0200
Subject: [PATCH 048/160] Add debug target to the template makefile

If the DEBUG variable is set (e.g. DEBUG=1 make target) debugging will
be enabled by using pelicans -D flag.
---
 pelican/tools/templates/Makefile.in | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/pelican/tools/templates/Makefile.in b/pelican/tools/templates/Makefile.in
index 221568aa..fd553550 100644
--- a/pelican/tools/templates/Makefile.in
+++ b/pelican/tools/templates/Makefile.in
@@ -20,6 +20,11 @@ S3_BUCKET=$s3_bucket
 
 DROPBOX_DIR=$dropbox_dir
 
+DEBUG ?= 0
+ifeq ($(DEBUG), 1)
+	PELICANOPTS += -D
+endif 
+
 help:
 	@echo 'Makefile for a pelican Web site                                        '
 	@echo '                                                                       '

From 0d63b4520a302d5fb8e249c1d98c0043c566315b Mon Sep 17 00:00:00 2001
From: bas smit 
Date: Wed, 26 Jun 2013 13:58:35 +0200
Subject: [PATCH 049/160] Add info about debugging to the help output of the
 makefile

---
 pelican/tools/templates/Makefile.in | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/pelican/tools/templates/Makefile.in b/pelican/tools/templates/Makefile.in
index fd553550..80cf4737 100644
--- a/pelican/tools/templates/Makefile.in
+++ b/pelican/tools/templates/Makefile.in
@@ -43,7 +43,8 @@ help:
 	@echo '   s3_upload                        upload the web site via S3         '
 	@echo '   github                           upload the web site via gh-pages   '
 	@echo '                                                                       '
-
+	@echo 'Set the DEBUG variable to 1 to enable debugging, e.g. make DEBUG=1 html'
+	@echo '                                                                       '
 
 html: clean $$(OUTPUTDIR)/index.html
 

From 5d000ca2904e84c12c255a7b930fc9c38639c580 Mon Sep 17 00:00:00 2001
From: Justin Mayer 
Date: Wed, 26 Jun 2013 06:39:09 -0700
Subject: [PATCH 050/160] Add more missing -s flags to tips doc page

---
 docs/tips.rst | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/docs/tips.rst b/docs/tips.rst
index e29f73d2..430d6488 100644
--- a/docs/tips.rst
+++ b/docs/tips.rst
@@ -49,7 +49,7 @@ To publish a Pelican site as User Pages you need to *push* the content of the
 
 Again, you can take advantage of ``ghp-import``::
 
-    $ pelican content -o output pelicanconf.py
+    $ pelican content -o output -s pelicanconf.py
     $ ghp-import output
     $ git push git@github.com:elemoine/elemoine.github.com.git gh-pages:master
 
@@ -71,7 +71,7 @@ To automatically update your Pelican site on each commit you can create
 a post-commit hook. For example, you can add the following to
 ``.git/hooks/post-commit``::
 
-    pelican pelican content -o output pelicanconf.py && ghp-import output && git push origin gh-pages
+    pelican pelican content -o output -s pelicanconf.py && ghp-import output && git push origin gh-pages
 
 Tip #2:
 

From 931d57160602db2b12d06d4c2a2124504addbd93 Mon Sep 17 00:00:00 2001
From: Danilo Bargen 
Date: Fri, 28 Jun 2013 00:53:26 +0200
Subject: [PATCH 051/160] More explicit settings docs concerning list templates

I think the author list and tag list are so common that they should be
listed explicitly in the settings.
---
 docs/settings.rst | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/docs/settings.rst b/docs/settings.rst
index 97662cce..1444e174 100644
--- a/docs/settings.rst
+++ b/docs/settings.rst
@@ -237,12 +237,16 @@ Setting name (default value)                            What does it do?
                                                         use the default language.
 `PAGE_LANG_SAVE_AS` (``'pages/{slug}-{lang}.html'``)    The location we will save the page which doesn't
                                                         use the default language.
-`AUTHOR_URL` (``'author/{slug}.html'``)                 The URL to use for an author.
-`AUTHOR_SAVE_AS` (``'author/{slug}.html'``)             The location to save an author.
 `CATEGORY_URL` (``'category/{slug}.html'``)             The URL to use for a category.
 `CATEGORY_SAVE_AS` (``'category/{slug}.html'``)         The location to save a category.
 `TAG_URL` (``'tag/{slug}.html'``)                       The URL to use for a tag.
 `TAG_SAVE_AS` (``'tag/{slug}.html'``)                   The location to save the tag page.
+`TAGS_URL` (``'tag/{slug}.html'``)                      The URL to use for the tag list.
+`TAGS_SAVE_AS` (``'tags.html'``)                        The location to save the tag list.
+`AUTHOR_URL` (``'author/{slug}.html'``)                 The URL to use for an author.
+`AUTHOR_SAVE_AS` (``'author/{slug}.html'``)             The location to save an author.
+`AUTHORS_URL` (``'authors.html'``)                      The URL to use for the author list.
+`AUTHORS_SAVE_AS` (``'authors.html'``)                  The location to save the author list.
 `_SAVE_AS`                        The location to save content generated from direct
                                                         templates. Where  is the
                                                         upper case template name.

From 298151237e9f418aabcf4d70333dc3550a519914 Mon Sep 17 00:00:00 2001
From: Andrew Ma 
Date: Wed, 3 Jul 2013 22:36:52 -0700
Subject: [PATCH 052/160] Adding stackoverflow to social icons

---
 pelican/themes/notmyidea/static/css/main.css      |   1 +
 .../static/images/icons/stackoverflow.png         | Bin 0 -> 916 bytes
 2 files changed, 1 insertion(+)
 create mode 100644 pelican/themes/notmyidea/static/images/icons/stackoverflow.png

diff --git a/pelican/themes/notmyidea/static/css/main.css b/pelican/themes/notmyidea/static/css/main.css
index 7f4ca363..fa0bcf1c 100644
--- a/pelican/themes/notmyidea/static/css/main.css
+++ b/pelican/themes/notmyidea/static/css/main.css
@@ -326,6 +326,7 @@ 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*='slideshare.net'] {background-image: url('../images/icons/slideshare.png');}
 		.social a[href*='speakerdeck.com'] {background-image: url('../images/icons/speakerdeck.png');}
+		.social a[href*='stackoverflow.com'] {background-image: url('../images/icons/stackoverflow.png');}
 		.social a[href*='twitter.com'] {background-image: url('../images/icons/twitter.png');}
 		.social a[href*='vimeo.com'] {background-image: url('../images/icons/vimeo.png');}
 		.social a[href*='youtube.com'] {background-image: url('../images/icons/youtube.png');}
diff --git a/pelican/themes/notmyidea/static/images/icons/stackoverflow.png b/pelican/themes/notmyidea/static/images/icons/stackoverflow.png
new file mode 100644
index 0000000000000000000000000000000000000000..f5b65e9990dbf423ff652b297f1d0172c8c1cf27
GIT binary patch
literal 916
zcmeAS@N?(olHy`uVBq!ia0vp^0w65F1|7sn8f<3~fRJ+vJ~_8MEyP6_>_pwTJhc%)&)Mu9Ed(YG|jmTlZ9
z@KIPeZ^FK^-?9GB|W>{Q7m)EC+@8;a3mYxk}1?tCE&x6kxKjd-nU)D+_Cb1MZtP
zf6ioHdF7Ur;v^T%WC`(qYnHY?KFysk()nSxK=;)vJ-w30K5t&s)3ykMV9;LODnQ81ohPJtCw>uw6%3rniLY9y-{G%>&K7poH)Ybf9hcZ
zPx7%K&0{68BG<3+JpQ`j393DPNr7J)G`t`kgi;Fv5%&IGQ?s!oY8v6HF>egkKm6gM^6?fU09+hB7{`$2g
z!Dr>|-0l5+On2^2_f4p`m-+THD#}N=VM2Fz(!8K!0$xisQhfv_dG-h{GMTvHCdbLN
z^XFvvX21P9HE8+fMNXk>uPs~lGBfn^9g#*-%#3?x$4
ze30#20|W~;m8*zxxBvb3Dl3rX#Piv+IUi
Date: Fri, 28 Jun 2013 15:09:36 -0700
Subject: [PATCH 053/160] Updating unit tests

---
 pelican/tests/output/basic/theme/css/main.css     |   1 +
 .../basic/theme/images/icons/stackoverflow.png    | Bin 0 -> 916 bytes
 pelican/tests/output/custom/theme/css/main.css    |   1 +
 .../custom/theme/images/icons/stackoverflow.png   | Bin 0 -> 916 bytes
 4 files changed, 2 insertions(+)
 create mode 100644 pelican/tests/output/basic/theme/images/icons/stackoverflow.png
 create mode 100644 pelican/tests/output/custom/theme/images/icons/stackoverflow.png

diff --git a/pelican/tests/output/basic/theme/css/main.css b/pelican/tests/output/basic/theme/css/main.css
index 7f4ca363..fa0bcf1c 100644
--- a/pelican/tests/output/basic/theme/css/main.css
+++ b/pelican/tests/output/basic/theme/css/main.css
@@ -326,6 +326,7 @@ 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*='slideshare.net'] {background-image: url('../images/icons/slideshare.png');}
 		.social a[href*='speakerdeck.com'] {background-image: url('../images/icons/speakerdeck.png');}
+		.social a[href*='stackoverflow.com'] {background-image: url('../images/icons/stackoverflow.png');}
 		.social a[href*='twitter.com'] {background-image: url('../images/icons/twitter.png');}
 		.social a[href*='vimeo.com'] {background-image: url('../images/icons/vimeo.png');}
 		.social a[href*='youtube.com'] {background-image: url('../images/icons/youtube.png');}
diff --git a/pelican/tests/output/basic/theme/images/icons/stackoverflow.png b/pelican/tests/output/basic/theme/images/icons/stackoverflow.png
new file mode 100644
index 0000000000000000000000000000000000000000..f5b65e9990dbf423ff652b297f1d0172c8c1cf27
GIT binary patch
literal 916
zcmeAS@N?(olHy`uVBq!ia0vp^0w65F1|7sn8f<3~fRJ+vJ~_8MEyP6_>_pwTJhc%)&)Mu9Ed(YG|jmTlZ9
z@KIPeZ^FK^-?9GB|W>{Q7m)EC+@8;a3mYxk}1?tCE&x6kxKjd-nU)D+_Cb1MZtP
zf6ioHdF7Ur;v^T%WC`(qYnHY?KFysk()nSxK=;)vJ-w30K5t&s)3ykMV9;LODnQ81ohPJtCw>uw6%3rniLY9y-{G%>&K7poH)Ybf9hcZ
zPx7%K&0{68BG<3+JpQ`j393DPNr7J)G`t`kgi;Fv5%&IGQ?s!oY8v6HF>egkKm6gM^6?fU09+hB7{`$2g
z!Dr>|-0l5+On2^2_f4p`m-+THD#}N=VM2Fz(!8K!0$xisQhfv_dG-h{GMTvHCdbLN
z^XFvvX21P9HE8+fMNXk>uPs~lGBfn^9g#*-%#3?x$4
ze30#20|W~;m8*zxxBvb3Dl3rX#Piv+IUi7sn8f<3~fRJ+vJ~_8MEyP6_>_pwTJhc%)&)Mu9Ed(YG|jmTlZ9
z@KIPeZ^FK^-?9GB|W>{Q7m)EC+@8;a3mYxk}1?tCE&x6kxKjd-nU)D+_Cb1MZtP
zf6ioHdF7Ur;v^T%WC`(qYnHY?KFysk()nSxK=;)vJ-w30K5t&s)3ykMV9;LODnQ81ohPJtCw>uw6%3rniLY9y-{G%>&K7poH)Ybf9hcZ
zPx7%K&0{68BG<3+JpQ`j393DPNr7J)G`t`kgi;Fv5%&IGQ?s!oY8v6HF>egkKm6gM^6?fU09+hB7{`$2g
z!Dr>|-0l5+On2^2_f4p`m-+THD#}N=VM2Fz(!8K!0$xisQhfv_dG-h{GMTvHCdbLN
z^XFvvX21P9HE8+fMNXk>uPs~lGBfn^9g#*-%#3?x$4
ze30#20|W~;m8*zxxBvb3Dl3rX#Piv+IUi
Date: Fri, 28 Jun 2013 19:59:00 -0700
Subject: [PATCH 054/160] Document how to stop generation of certain pages

The documentation doesn't make it very clear how to prevent certain
pages from being generated, such as the Authors, Tags, and Categories
collection pages. This change makes it slightly more obvious how to
prevent these pages from being generated. Fixes #940.
---
 docs/settings.rst | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/docs/settings.rst b/docs/settings.rst
index 1444e174..78a0ddf7 100644
--- a/docs/settings.rst
+++ b/docs/settings.rst
@@ -138,8 +138,9 @@ Setting name (default value)                                            What doe
                                                                         library, which can be installed via: ``pip install typogrify``
 `DIRECT_TEMPLATES` (``('index', 'tags', 'categories', 'archives')``)    List of templates that are used directly to render
                                                                         content. Typically direct templates are used to generate
-                                                                        index pages for collections of content (e.g. tags and
-                                                                        category index pages).
+                                                                        index pages for collections of content (e.g., tags and
+                                                                        category index pages). If the tag and category collections
+                                                                        are not needed, set ``DIRECT_TEMPLATES = ('index', 'archives')``
 `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
                                                                         be the default length in words of the text created.
@@ -261,7 +262,10 @@ Setting name (default value)                            What does it do?
 
 .. note::
 
-    When any of the `*_SAVE_AS` settings is set to False, files will not be created.
+    If you do not want one or more of the default pages to be created (e.g.,
+    you are the only author on your site and thus do not need an Authors page),
+    set the corresponding ``*_SAVE_AS`` setting to ``False`` to prevent the
+    relevant page from being generated.
 
 Timezone
 --------

From 39518e15efc6535ef654b0e9e526239db62d7ac8 Mon Sep 17 00:00:00 2001
From: Andy Pearce 
Date: Fri, 14 Jun 2013 15:54:06 +0100
Subject: [PATCH 055/160] Allow text substitutions when generating slugs

The `slugify()` function used by Pelican is in general very good at
coming up with something both readable and URL-safe. However, there are
a few specific cases where it causes conflicts. One that I've run into
is using the strings `C++` and `C` as tags, both of which transform to
the slug `c`. This commit adds an optional `SLUG_SUBSTITUTIONS` setting
which is a list of 2-tuples of substitutions to be carried out
case-insensitively just prior to stripping out non-alphanumeric
characters. This allows cases like `C++` to be transformed to `CPP` or
similar. This can also improve the readability of slugs.
---
 docs/settings.rst           |  4 ++++
 pelican/contents.py         |  3 ++-
 pelican/settings.py         |  1 +
 pelican/tests/test_utils.py | 11 +++++++++++
 pelican/urlwrappers.py      | 11 ++++++-----
 pelican/utils.py            |  8 +++++---
 6 files changed, 29 insertions(+), 9 deletions(-)

diff --git a/docs/settings.rst b/docs/settings.rst
index 78a0ddf7..61ccc2b2 100644
--- a/docs/settings.rst
+++ b/docs/settings.rst
@@ -258,6 +258,10 @@ Setting name (default value)                            What does it do?
                                                         posts.
 `DAY_ARCHIVE_SAVE_AS` (False)                           The location to save per-day archives of your
                                                         posts.
+`SLUG_SUBSTITUTIONS`  (``()``)                          Substitutions to make prior to stripping out
+                                                        non-alphanumerics when generating slugs. Specified
+                                                        as a list of 2-tuples of ``(from, to)`` which are
+                                                        applied in order.
 ====================================================    =====================================================
 
 .. note::
diff --git a/pelican/contents.py b/pelican/contents.py
index 1b604f19..d56335dd 100644
--- a/pelican/contents.py
+++ b/pelican/contents.py
@@ -86,7 +86,8 @@ class Content(object):
 
         # create the slug if not existing, from the title
         if not hasattr(self, 'slug') and hasattr(self, 'title'):
-            self.slug = slugify(self.title)
+            self.slug = slugify(self.title,
+                                settings.get('SLUG_SUBSTITUTIONS', ()))
 
         self.source_path = source_path
 
diff --git a/pelican/settings.py b/pelican/settings.py
index 1c9b48c3..01203504 100644
--- a/pelican/settings.py
+++ b/pelican/settings.py
@@ -105,6 +105,7 @@ DEFAULT_CONFIG = {
     'PLUGINS': [],
     'TEMPLATE_PAGES': {},
     'IGNORE_FILES': ['.#*'],
+    'SLUG_SUBSTITUTIONS': (),
     }
 
 def read_settings(path=None, override=None):
diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py
index ab35d991..0e65003a 100644
--- a/pelican/tests/test_utils.py
+++ b/pelican/tests/test_utils.py
@@ -94,6 +94,17 @@ class TestUtils(LoggedTestCase):
         for value, expected in samples:
             self.assertEqual(utils.slugify(value), expected)
 
+    def test_slugify_substitute(self):
+
+        samples = (('C++ is based on C', 'cpp-is-based-on-c'),
+                   ('C+++ test C+ test', 'cpp-test-c-test'),
+                   ('c++, c#, C#, C++', 'cpp-c-sharp-c-sharp-cpp'),
+                   ('c++-streams', 'cpp-streams'),)
+
+        subs = (('C++', 'CPP'), ('C#', 'C-SHARP'))
+        for value, expected in samples:
+            self.assertEqual(utils.slugify(value, subs), expected)
+
     def test_get_relative_path(self):
 
         samples = ((os.path.join('test', 'test.html'), os.pardir),
diff --git a/pelican/urlwrappers.py b/pelican/urlwrappers.py
index b0df61ad..acb8e07d 100644
--- a/pelican/urlwrappers.py
+++ b/pelican/urlwrappers.py
@@ -15,10 +15,10 @@ class URLWrapper(object):
     def __init__(self, name, settings):
         # next 2 lines are redundant with the setter of the name property
         # but are here for clarity
-        self._name = name
-        self.slug = slugify(name)
-        self.name = name
         self.settings = settings
+        self._name = name
+        self.slug = slugify(name, self.settings.get('SLUG_SUBSTITUTIONS', ()))
+        self.name = name
 
     @property
     def name(self):
@@ -27,7 +27,7 @@ class URLWrapper(object):
     @name.setter
     def name(self, name):
         self._name = name
-        self.slug = slugify(name)
+        self.slug = slugify(name, self.settings.get('SLUG_SUBSTITUTIONS', ()))
 
     def as_dict(self):
         d = self.__dict__
@@ -41,7 +41,8 @@ class URLWrapper(object):
         return self.slug
 
     def _normalize_key(self, key):
-        return six.text_type(slugify(key))
+        subs = self.settings.get('SLUG_SUBSTITUTIONS', ())
+        return six.text_type(slugify(key, subs))
 
     def __eq__(self, other):
         return self._key() == self._normalize_key(other)
diff --git a/pelican/utils.py b/pelican/utils.py
index 2c70ae8c..b1524036 100644
--- a/pelican/utils.py
+++ b/pelican/utils.py
@@ -231,7 +231,7 @@ class pelican_open(object):
         pass
 
 
-def slugify(value):
+def slugify(value, substitutions=()):
     """
     Normalizes string, converts to lowercase, removes non-alpha characters,
     and converts spaces to hyphens.
@@ -249,8 +249,10 @@ def slugify(value):
     if isinstance(value, six.binary_type):
         value = value.decode('ascii')
     # still unicode
-    value = unicodedata.normalize('NFKD', value)
-    value = re.sub('[^\w\s-]', '', value).strip().lower()
+    value = unicodedata.normalize('NFKD', value).lower()
+    for src, dst in substitutions:
+        value = value.replace(src.lower(), dst.lower())
+    value = re.sub('[^\w\s-]', '', value).strip()
     value = re.sub('[-\s]+', '-', value)
     # we want only ASCII chars
     value = value.encode('ascii', 'ignore')

From 3da4c2e13e4146e1d0a58fd0e7267bb4ad957881 Mon Sep 17 00:00:00 2001
From: Stefan hr Berder 
Date: Sun, 7 Jul 2013 12:44:21 +0200
Subject: [PATCH 056/160] add port option to pelican.server

---
 pelican/server.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/pelican/server.py b/pelican/server.py
index fd99b209..24f3ae04 100644
--- a/pelican/server.py
+++ b/pelican/server.py
@@ -10,7 +10,7 @@ try:
 except ImportError:
     import socketserver  # NOQA
 
-PORT = 8000
+PORT = len(sys.argv) == 2 and int(sys.argv[1]) or 8000
 
 Handler = srvmod.SimpleHTTPRequestHandler
 
@@ -26,4 +26,4 @@ try:
     httpd.serve_forever()
 except KeyboardInterrupt as e:
     print("shutting down server")
-    httpd.socket.close()
\ No newline at end of file
+    httpd.socket.close()

From 00a1cbb6b84d9faad2d7dde01b068ff113b0184e Mon Sep 17 00:00:00 2001
From: Lingzhu Xiang 
Date: Sat, 4 May 2013 06:13:11 +0800
Subject: [PATCH 057/160] Support importing Tumblr

Try to integrate Tumblr's various post types without using
additonal templates.
---
 docs/importer.rst               | 14 ++++--
 pelican/tools/pelican_import.py | 88 ++++++++++++++++++++++++++++++++-
 2 files changed, 98 insertions(+), 4 deletions(-)

diff --git a/docs/importer.rst b/docs/importer.rst
index 9a0c513e..057fecd8 100644
--- a/docs/importer.rst
+++ b/docs/importer.rst
@@ -14,6 +14,7 @@ software to reStructuredText or Markdown. The supported import formats are:
 - WordPress XML export
 - Dotclear export
 - Posterous API
+- Tumblr API
 - RSS/Atom feed
 
 The conversion from HTML to reStructuredText or Markdown relies on `Pandoc`_.
@@ -41,16 +42,17 @@ Usage
 
 ::
 
-    pelican-import [-h] [--wpfile] [--dotclear] [--posterous] [--feed] [-o OUTPUT]
+    pelican-import [-h] [--wpfile] [--dotclear] [--posterous] [--tumblr] [--feed] [-o OUTPUT]
                    [-m MARKUP] [--dir-cat] [--dir-page] [--strip-raw] [--disable-slugs]
-                   [-e EMAIL] [-p PASSWORD]
-                   input|api_token
+                   [-e EMAIL] [-p PASSWORD] [-b BLOGNAME]
+                   input|api_token|api_key
 
 Positional arguments
 --------------------
 
   input                 The input file to read
   api_token             [Posterous only] api_token can be obtained from http://posterous.com/api/
+  api_key               [Tumblr only] api_key can be obtained from http://www.tumblr.com/oauth/apps
 
 Optional arguments
 ------------------
@@ -59,6 +61,7 @@ Optional arguments
   --wpfile              WordPress XML export (default: False)
   --dotclear            Dotclear export (default: False)
   --posterous           Posterous API (default: False)
+  --tumblr              Tumblr API (default: False)
   --feed                Feed to parse (default: False)
   -o OUTPUT, --output OUTPUT
                         Output path (default: output)
@@ -80,6 +83,8 @@ Optional arguments
                         Email used to authenticate Posterous API
   -p PASSWORD, --password=PASSWORD
                         Password used to authenticate Posterous API
+  -b BLOGNAME, --blogname=BLOGNAME
+                        Blog name used in Tumblr API
 
 
 Examples
@@ -97,6 +102,9 @@ for Posterous::
 
     $ pelican-import --posterous -o ~/output --email= --password= 
 
+For Tumblr::
+
+    $ pelican-import --tumblr -o ~/output --blogname= 
 
 Tests
 =====
diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py
index 630142e7..5f637c73 100755
--- a/pelican/tools/pelican_import.py
+++ b/pelican/tools/pelican_import.py
@@ -326,6 +326,84 @@ def posterous2fields(api_token, email, password):
             yield (post.get('title'), post.get('body_cleaned'), slug, date,
                 post.get('user').get('display_name'), [], tags, kind, "html")
 
+
+def tumblr2fields(api_key, blogname):
+    """ Imports Tumblr posts (API v2)"""
+    from time import strftime, localtime
+    try:
+        # py3k import
+        import json
+    except ImportError:
+        # py2 import
+        import simplejson as json
+
+    try:
+        # py3k import
+        import urllib.request as urllib_request
+    except ImportError:
+        # py2 import
+        import urllib2 as urllib_request
+
+    def get_tumblr_posts(api_key, blogname, offset=0):
+        url = "http://api.tumblr.com/v2/blog/%s.tumblr.com/posts?api_key=%s&offset=%d&filter=raw" % (blogname, api_key, offset)
+        request = urllib_request.Request(url)
+        handle = urllib_request.urlopen(request)
+        posts = json.loads(handle.read().decode('utf-8'))
+        return posts.get('response').get('posts')
+
+    offset = 0
+    posts = get_tumblr_posts(api_key, blogname, offset)
+    while len(posts) > 0:
+        for post in posts:
+            title = post.get('title') or post.get('source_title')
+            slug = post.get('slug') or slugify(title)
+            tags = post.get('tags')
+            timestamp = post.get('timestamp')
+            date = strftime("%Y-%m-%d %H:%M:%S", localtime(int(timestamp)))
+            slug = strftime("%Y-%m-%d-", localtime(int(timestamp))) + slug
+            format = post.get('format')
+            content = post.get('body')
+            type = post.get('type')
+            if type == 'photo':
+                if format == 'markdown':
+                    fmtstr = '![%s](%s)'
+                else:
+                    fmtstr = '%s'
+                content = '\n'.join(fmtstr % (photo.get('caption'), photo.get('original_size').get('url')) for photo in post.get('photos'))
+            elif type == 'quote':
+                if format == 'markdown':
+                    fmtstr = '\n\n— %s'
+                else:
+                    fmtstr = '

— %s

' + content = post.get('text') + fmtstr % post.get('source') + elif type == 'link': + if format == 'markdown': + fmtstr = '[via](%s)\n\n' + else: + fmtstr = '

via

\n' + content = fmtstr % post.get('url') + post.get('description') + elif type == 'audio': + if format == 'markdown': + fmtstr = '[via](%s)\n\n' + else: + fmtstr = '

via

\n' + content = fmtstr % post.get('source_url') + post.get('caption') + post.get('player') + elif type == 'video': + if format == 'markdown': + fmtstr = '[via](%s)\n\n' + else: + fmtstr = '

via

\n' + content = fmtstr % post.get('source_url') + post.get('caption') + '\n'.join(player.get('embed_code') for player in post.get('player')) + elif type == 'answer': + title = post.get('question') + content = '

%s: %s

\n%s' % (post.get('asking_name'), post.get('asking_url'), post.get('question'), post.get('answer')) + + yield (title, content, slug, date, post.get('blog_name'), [type], tags, format) + + offset += len(posts) + posts = get_tumblr_posts(api_key, blogname, offset) + + def feed2fields(file): """Read a feed and yield pelican fields""" import feedparser @@ -476,6 +554,8 @@ def main(): help='Dotclear export') parser.add_argument('--posterous', action='store_true', dest='posterous', help='Posterous export') + parser.add_argument('--tumblr', action='store_true', dest='tumblr', + help='Tumblr export') parser.add_argument('--feed', action='store_true', dest='feed', help='Feed to parse') parser.add_argument('-o', '--output', dest='output', default='output', @@ -499,6 +579,8 @@ def main(): help="Email address (posterous import only)") parser.add_argument('-p', '--password', dest='password', help="Password (posterous import only)") + parser.add_argument('-b', '--blogname', dest='blogname', + help="Blog name (Tumblr import only)") args = parser.parse_args() @@ -509,10 +591,12 @@ def main(): input_type = 'dotclear' elif args.posterous: input_type = 'posterous' + elif args.tumblr: + input_type = 'tumblr' elif args.feed: input_type = 'feed' else: - error = "You must provide either --wpfile, --dotclear, --posterous or --feed options" + error = "You must provide either --wpfile, --dotclear, --posterous, --tumblr or --feed options" exit(error) if not os.path.exists(args.output): @@ -528,6 +612,8 @@ def main(): fields = dc2fields(args.input) elif input_type == 'posterous': fields = posterous2fields(args.input, args.email, args.password) + elif input_type == 'tumblr': + fields = tumblr2fields(args.input, args.blogname) elif input_type == 'feed': fields = feed2fields(args.input) From 75263fa852fae5ed6e591c18fe1afad8fe5a7b0f Mon Sep 17 00:00:00 2001 From: Lingzhu Xiang Date: Sun, 5 May 2013 02:09:24 +0800 Subject: [PATCH 058/160] Fix importing Tumblr photo caption Besides each photo's caption, the general caption is also needed. While we're at it, also add a linefeed at the end of file. --- pelican/tools/pelican_import.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index 5f637c73..3c40dd56 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -370,6 +370,7 @@ def tumblr2fields(api_key, blogname): else: fmtstr = '%s' content = '\n'.join(fmtstr % (photo.get('caption'), photo.get('original_size').get('url')) for photo in post.get('photos')) + content += '\n\n' + post.get('caption') elif type == 'quote': if format == 'markdown': fmtstr = '\n\n— %s' @@ -398,6 +399,8 @@ def tumblr2fields(api_key, blogname): title = post.get('question') content = '

%s: %s

\n%s' % (post.get('asking_name'), post.get('asking_url'), post.get('question'), post.get('answer')) + content = content.rstrip() + '\n' + yield (title, content, slug, date, post.get('blog_name'), [type], tags, format) offset += len(posts) From 241ac2400a33d6ad804012ae11b36a38e223476f Mon Sep 17 00:00:00 2001 From: Lingzhu Xiang Date: Sun, 5 May 2013 22:53:21 +0800 Subject: [PATCH 059/160] Use better titles than None for Tumblr posts without title --- pelican/tools/pelican_import.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index 3c40dd56..3b5c55f2 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -355,7 +355,7 @@ def tumblr2fields(api_key, blogname): posts = get_tumblr_posts(api_key, blogname, offset) while len(posts) > 0: for post in posts: - title = post.get('title') or post.get('source_title') + title = post.get('title') or post.get('source_title') or post.get('type').capitalize() slug = post.get('slug') or slugify(title) tags = post.get('tags') timestamp = post.get('timestamp') From 3a5db543bb8fa385ac10347d81d98778959619bc Mon Sep 17 00:00:00 2001 From: Stefan hr Berder Date: Sun, 7 Jul 2013 13:27:50 +0200 Subject: [PATCH 060/160] add port parameter to bash script --- pelican/tools/templates/develop_server.sh.in | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/pelican/tools/templates/develop_server.sh.in b/pelican/tools/templates/develop_server.sh.in index 6dd11e6d..16b61518 100755 --- a/pelican/tools/templates/develop_server.sh.in +++ b/pelican/tools/templates/develop_server.sh.in @@ -18,7 +18,7 @@ SRV_PID=$$BASEDIR/srv.pid PELICAN_PID=$$BASEDIR/pelican.pid function usage(){ - echo "usage: $$0 (stop) (start) (restart)" + echo "usage: $$0 (stop) (start) (restart) [port]" echo "This starts pelican in debug and reload mode and then launches" echo "A pelican.server to help site development. It doesn't read" echo "your pelican options so you edit any paths in your Makefile" @@ -59,13 +59,14 @@ function shut_down(){ } function start_up(){ + local port=$$1 echo "Starting up Pelican and pelican.server" shift $$PELICAN --debug --autoreload -r $$INPUTDIR -o $$OUTPUTDIR -s $$CONFFILE $$PELICANOPTS & pelican_pid=$$! echo $$pelican_pid > $$PELICAN_PID cd $$OUTPUTDIR - $PY -m pelican.server & + $PY -m pelican.server $$port & srv_pid=$$! echo $$srv_pid > $$SRV_PID cd $$BASEDIR @@ -83,15 +84,18 @@ function start_up(){ ### # MAIN ### -[[ $$# -ne 1 ]] && usage +[[ ($$# -eq 0) || ($$# -gt 2) ]] && usage +port='' +[[ $$# -eq 2 ]] && port=$$2 + if [[ $$1 == "stop" ]]; then shut_down elif [[ $$1 == "restart" ]]; then shut_down - start_up + start_up $$port elif [[ $$1 == "start" ]]; then - if ! start_up; then - shut_down + if ! start_up $$port; then + shut_down fi else usage From 689632835eb2119c2f822456dee6c11def11ced5 Mon Sep 17 00:00:00 2001 From: Stefan hr Berder Date: Sun, 7 Jul 2013 14:28:15 +0200 Subject: [PATCH 061/160] add port option to Makefile target serve/devserver --- pelican/tools/templates/Makefile.in | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pelican/tools/templates/Makefile.in b/pelican/tools/templates/Makefile.in index 4bc764ca..f2e0ccc9 100644 --- a/pelican/tools/templates/Makefile.in +++ b/pelican/tools/templates/Makefile.in @@ -28,8 +28,8 @@ help: @echo ' make clean remove the generated files ' @echo ' make regenerate regenerate files upon modification ' @echo ' make publish generate using production settings ' - @echo ' make serve serve site at http://localhost:8000' - @echo ' make devserver start/restart develop_server.sh ' + @echo ' make serve [PORT=8000] serve site at http://localhost:8000' + @echo ' make devserver [PORT=8000] start/restart develop_server.sh ' @echo ' make stopserver stop local server ' @echo ' ssh_upload upload the web site via SSH ' @echo ' rsync_upload upload the web site via rsync+ssh ' @@ -50,10 +50,18 @@ regenerate: $$(PELICAN) -r $$(INPUTDIR) -o $$(OUTPUTDIR) -s $$(CONFFILE) $$(PELICANOPTS) serve: +ifdef PORT + cd $$(OUTPUTDIR) && $(PY) -m pelican.server $$(PORT) +else cd $$(OUTPUTDIR) && $(PY) -m pelican.server +endif devserver: +ifdef PORT + $$(BASEDIR)/develop_server.sh restart $$(PORT) +else $$(BASEDIR)/develop_server.sh restart +endif stopserver: kill -9 `cat pelican.pid` From cb650c1c992a92c25040387d77ecc774e6f5a4ac Mon Sep 17 00:00:00 2001 From: Benjamin Port Date: Thu, 9 May 2013 05:06:12 +0200 Subject: [PATCH 062/160] Add filter-author option to importer Allow to import post from only one author when importing data --- docs/importer.rst | 1 + pelican/tools/pelican_import.py | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/importer.rst b/docs/importer.rst index 9a0c513e..228471da 100644 --- a/docs/importer.rst +++ b/docs/importer.rst @@ -69,6 +69,7 @@ Optional arguments (default: False) --dir-page Put files recognised as pages in "pages/" sub- directory (wordpress import only) (default: False) + --filter-author Import only post from the specified author. --strip-raw Strip raw HTML code that can't be converted to markup such as flash embeds or iframes (wordpress import only) (default: False) diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index 630142e7..59b767f9 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -376,9 +376,11 @@ def build_markdown_header(title, date, author, categories, tags, slug): def fields2pelican(fields, out_markup, output_path, dircat=False, strip_raw=False, disable_slugs=False, - dirpage=False, filename_template=None): + dirpage=False, filename_template=None, filter_author=None): for (title, content, filename, date, author, categories, tags, kind, in_markup) in fields: + if filter_author and filter_author != author: + continue slug = not disable_slugs and filename or None if (in_markup == "markdown") or (out_markup == "markdown") : ext = '.md' @@ -487,6 +489,8 @@ def main(): parser.add_argument('--dir-page', action='store_true', dest='dirpage', help=('Put files recognised as pages in "pages/" sub-directory' ' (wordpress import only)')) + parser.add_argument('--filter-author', dest='author', + help='Import only post from the specified author') parser.add_argument('--strip-raw', action='store_true', dest='strip_raw', help="Strip raw HTML code that can't be converted to " "markup such as flash embeds or iframes (wordpress import only)") @@ -537,4 +541,5 @@ def main(): dircat=args.dircat or False, dirpage=args.dirpage or False, strip_raw=args.strip_raw or False, - disable_slugs=args.disable_slugs or False) + disable_slugs=args.disable_slugs or False, + filter_author=args.author) From 6c5444eb6833cda2ea9129321c3a4ba43505b772 Mon Sep 17 00:00:00 2001 From: Nick Moore Date: Sun, 14 Jul 2013 23:01:16 +1000 Subject: [PATCH 063/160] do slug_substitutions on category and author ... --- pelican/contents.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pelican/contents.py b/pelican/contents.py index d56335dd..ed213c31 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -141,14 +141,21 @@ class Content(object): """Returns the URL, formatted with the proper values""" metadata = copy.copy(self.metadata) path = self.metadata.get('path', self.get_relative_source_path()) + default_category = self.settings['DEFAULT_CATEGORY'] + slug_substitutions = self.settings.get('SLUG_SUBSTITUTIONS', ()) metadata.update({ 'path': path_to_url(path), 'slug': getattr(self, 'slug', ''), 'lang': getattr(self, 'lang', 'en'), 'date': getattr(self, 'date', datetime.now()), - 'author': getattr(self, 'author', ''), - 'category': getattr(self, 'category', - self.settings['DEFAULT_CATEGORY']), + 'author': slugify( + getattr(self, 'author', ''), + slug_substitutions + ), + 'category': slugify( + getattr(self, 'category', default_category), + slug_substitutions + ) }) return metadata From 9b7ae20aa9182e7d3c10bea111716bb4e12edbd8 Mon Sep 17 00:00:00 2001 From: Nick Moore Date: Mon, 15 Jul 2013 00:22:05 +1000 Subject: [PATCH 064/160] test for author & category slugification --- pelican/tests/test_contents.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pelican/tests/test_contents.py b/pelican/tests/test_contents.py index c081639d..af97db3f 100644 --- a/pelican/tests/test_contents.py +++ b/pelican/tests/test_contents.py @@ -191,6 +191,20 @@ class TestArticle(TestPage): custom_article = Article(**article_kwargs) self.assertEqual('custom', custom_article.template) + def test_slugify_category_author(self): + settings = get_settings() + settings['SLUG_SUBSTITUTIONS'] = [ ('C#', 'csharp') ] + settings['ARTICLE_URL'] = '{author}/{category}/{slug}/' + settings['ARTICLE_SAVE_AS'] = '{author}/{category}/{slug}/index.html' + article_kwargs = self._copy_page_kwargs() + article_kwargs['metadata']['author'] = "O'Brien" + article_kwargs['metadata']['category'] = 'C# & stuff' + article_kwargs['metadata']['title'] = 'fnord' + article_kwargs['settings'] = settings + article = Article(**article_kwargs) + self.assertEqual(article.url, 'obrien/csharp-stuff/fnord/') + self.assertEqual(article.save_as, 'obrien/csharp-stuff/fnord/index.html') + class TestURLWrapper(unittest.TestCase): def test_comparisons(self): From 4ca5d908ff767fc07a95a8b31487dcbb9416e2e5 Mon Sep 17 00:00:00 2001 From: Chris Howie Date: Mon, 15 Jul 2013 16:48:08 -0400 Subject: [PATCH 065/160] Update tag cloud documentation for SLUG_SUBSTITUTIONS --- docs/settings.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/settings.rst b/docs/settings.rst index 61ccc2b2..2c16ca83 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -497,7 +497,7 @@ The default theme does not support tag clouds, but it is pretty easy to add:: From f43742c3f0b6b3a260ff5c338d7c4309865b0b54 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Mon, 15 Jul 2013 14:25:39 -0700 Subject: [PATCH 066/160] Add missing SITEURL variable to tag cloud docs --- docs/settings.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 2c16ca83..eb0d028f 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -493,11 +493,11 @@ Setting name (default value) What does it do? `TAG_CLOUD_MAX_ITEMS` (``100``) Maximum number of tags in the cloud. ================================================ ===================================================== -The default theme does not support tag clouds, but it is pretty easy to add:: +The default theme does not include a tag cloud, but it is pretty easy to add:: From c5008f61e0a480afb954a5258748eec576ee9a72 Mon Sep 17 00:00:00 2001 From: Jude N Date: Tue, 16 Jul 2013 23:44:53 -0400 Subject: [PATCH 067/160] Adding a FEED_ALL_ATOM check in the "social" div. The head section has a tests for FEED_ALL_ATOM when building the atom link. This diff add a similar test in the "social" div. --- pelican/themes/notmyidea/templates/base.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pelican/themes/notmyidea/templates/base.html b/pelican/themes/notmyidea/templates/base.html index 9bf4b12b..e44e20fd 100644 --- a/pelican/themes/notmyidea/templates/base.html +++ b/pelican/themes/notmyidea/templates/base.html @@ -53,7 +53,9 @@ - -
-
-
+
+ -
+