A multi-line summary should be supported'\
- u'\nas well as inline markup.
'
- self.assertEquals(expected_summary, metadata['summary'], 'summary')
-
class AdReaderTest(unittest.TestCase):
@unittest.skipUnless(readers.asciidoc, "asciidoc isn't installed")
diff --git a/tests/test_settings.py b/tests/test_settings.py
index 379e3deb..0e610c55 100644
--- a/tests/test_settings.py
+++ b/tests/test_settings.py
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals, print_function
import copy
from os.path import dirname, abspath, join
@@ -16,7 +18,7 @@ class TestSettingsConfiguration(unittest.TestCase):
self.settings = read_settings(default_conf)
def test_overwrite_existing_settings(self):
- self.assertEqual(self.settings.get('SITENAME'), u"Alexis' log")
+ self.assertEqual(self.settings.get('SITENAME'), "Alexis' log")
self.assertEqual(self.settings.get('SITEURL'),
'http://blog.notmyidea.org')
diff --git a/tests/test_utils.py b/tests/test_utils.py
index eda1388a..eddb3e25 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+from __future__ import unicode_literals, print_function
import shutil
import os
import datetime
@@ -41,10 +42,10 @@ class TestUtils(unittest.TestCase):
samples = (('this is a test', 'this-is-a-test'),
('this is a test', 'this-is-a-test'),
- (u'this → is ← a ↑ test', 'this-is-a-test'),
+ ('this → is ← a ↑ test', 'this-is-a-test'),
('this--is---a test', 'this-is-a-test'),
- (u'unicode測試許功蓋,你看到了嗎?', 'unicodece-shi-xu-gong-gai-ni-kan-dao-liao-ma'),
- (u'大飯原発4号機、18日夜起動へ', 'da-fan-yuan-fa-4hao-ji-18ri-ye-qi-dong-he'),)
+ ('unicode測試許功蓋,你看到了嗎?', 'unicodece-shi-xu-gong-gai-ni-kan-dao-liao-ma'),
+ ('大飯原発4号機、18日夜起動へ', 'da-fan-yuan-fa-4hao-ji-18ri-ye-qi-dong-he'),)
for value, expected in samples:
self.assertEquals(utils.slugify(value), expected)
diff --git a/tests/test_webassets.py b/tests/test_webassets.py
index ceba7775..87b2789c 100644
--- a/tests/test_webassets.py
+++ b/tests/test_webassets.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+# from __future__ import unicode_literals
import hashlib
import os
diff --git a/tox.ini b/tox.ini
index f7d9cf82..8c3961b9 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,15 +1,74 @@
+# This tests the unified codebase (py26-py32) of Pelican.
+# depends on some external libraries that aren't released yet.
+#
+# To run Pelican, you will already have checked out and installed them.
+#
+# Now we must tell tox about this package, otherwise tox would load the old
+# libraries from PyPi.
+#
+# Run tox from the libraries source tree. It will save its package in
+# the distshare directory from where the tests here will pick it up.
+#
+# Do that for
+# https://github.com/dmdm/smartypants.git
+#
+# and typogrify:
+# https://github.com/dmdm/typogrify/tree/py3k
+#
+# and webassets:
+# https://github.com/dmdm/webassets/tree/py3k
+#
+#
+# CAVEAT:
+# -------
+#
+# 1/
+# Please be aware that my ports of typogrify and webassets are just 2to3'd.
+# They are not backwards compatible with Python 2.
+#
+# 2/
+# Webassets still has unresolved issues, so I deactivated it for Py32 tests.
+
+
[tox]
-envlist = py26,py27
+envlist = py26,py27,py32
[testenv]
commands =
- nosetests -s tests
unit2 discover []
+ nosetests -s tests
+deps =
+
+[testenv:py26]
deps =
nose
unittest2
mock
Markdown
- BeautifulSoup
+ BeautifulSoup4
+ feedgenerator
typogrify
webassets
+
+[testenv:py27]
+deps =
+ nose
+ unittest2
+ mock
+ Markdown
+ BeautifulSoup4
+ feedgenerator
+ typogrify
+ webassets
+
+[testenv:py32]
+deps =
+ nose
+ unittest2py3k
+ mock
+ Markdown
+ BeautifulSoup4
+ feedgenerator
+# {distshare}/smartypants-1.6.0.3.zip
+# {distshare}/typogrify-2.0.0.zip
+# {distshare}/webassets-0.8.dev.zip
From d1b238638cb39442a0154740c43a89c608cee634 Mon Sep 17 00:00:00 2001
From: Dirk Makowski
Date: Fri, 11 Jan 2013 03:21:06 +0100
Subject: [PATCH 0036/1756] Update the "how to contribute" docs with py3k info.
---
docs/contribute.rst | 56 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 56 insertions(+)
diff --git a/docs/contribute.rst b/docs/contribute.rst
index c302dcc6..3ab86263 100644
--- a/docs/contribute.rst
+++ b/docs/contribute.rst
@@ -58,6 +58,21 @@ To do so, you can use the following two commands::
samples/content/
$ LC_ALL="C" pelican -o tests/output/basic/ samples/content/
+testing for python3
+-------------------
+
+On Python 3, if you have installed the Py3k compatible versions of the
+plugins manual testing with ``unit2 discover`` is also straightforward.
+
+However, you must tell tox to use those Py3k libraries. If you forget this,
+tox will pull the regular packages from PyPi and the tests will fail.
+
+Tell tox about the local packages thusly: enter the source directory of
+smartypants and run tox there. Do this again for typogrify and webassets.
+Smartypants and typogrify do not have real tests, and webassets will fail
+noisily, but as a result we get these libraries neatly packaged in tox's
+distshare directory. And this we need to run tox for Pelican.
+
Coding standards
================
@@ -67,3 +82,44 @@ eased via the `pep8 `_ or `flake8
`_ tools, the latter of which in
particular will give you some useful hints about ways in which the
code/formatting can be improved.
+
+Python3 support
+===============
+
+Here are some tips that may be useful when doing some code for both python2 and
+python3 at the same time:
+
+- Assume, every string and literal is unicode (import unicode_literals):
+
+ - Do not use prefix ``u'``.
+ - Do not encode/decode strings in the middle of sth. Follow the code to the
+ source (or target) of a string and encode/decode at the first/last possible
+ point.
+ - In other words, write your functions to expect and to return unicode.
+ - Encode/decode strings if e.g. the source is a Python function that is known
+ to handle this badly, e.g. strftime() in Python 2.
+
+- Use new syntax: print function, "except ... *as* e" (not comma) etc.
+- Refactor method calls like ``dict.iteritems()``, ``xrange()`` etc. in a way
+ that runs without code change in both Python versions.
+- Do not use magic method ``__unicode()__`` in new classes. Use only ``__str()__``
+ and decorate the class with ``@python_2_unicode_compatible``.
+- Do not start int literals with a zero. This is a syntax error in Py3k.
+- Unfortunately I did not find an octal notation that is valid in both
+ Pythons. Use decimal instead.
+- use six, e.g.:
+
+ - ``isinstance(.., basestring) -> isinstance(.., six.string_types)``
+ - ``isinstance(.., unicode) -> isinstance(.., six.text_type)``
+
+- ``setlocale()`` in Python 2 bails when we give the locale name as unicode,
+ and since we are using ``from __future__ import unicode_literals``, we do
+ that everywhere! As a workaround, I enclosed the localename with ``str()``;
+ in Python 2 this casts the name to a byte string, in Python 3 this should do
+ nothing, because the locale name already had been unicode.
+
+- Kept range() almost everywhere as-is (2to3 suggests list(range())), just
+ changed it where I felt necessary.
+
+- Changed xrange() back to range(), so it is valid in both Python versions.
+
From 1197e09626b9c3607b8a287fff769e9c34c01503 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alexis=20M=C3=A9taireau?=
Date: Fri, 11 Jan 2013 18:44:35 +0100
Subject: [PATCH 0037/1756] Revert previously erased changes
---
pelican/tools/pelican_import.py | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py
index b1a2865f..c373cfd4 100755
--- a/pelican/tools/pelican_import.py
+++ b/pelican/tools/pelican_import.py
@@ -303,7 +303,7 @@ def fields2pelican(fields, out_markup, output_path, dircat=False, strip_raw=Fals
def main():
parser = argparse.ArgumentParser(
description="Transform feed, Wordpress or Dotclear files to reST (rst) "
- "or Markdown (md) files. Be sure to have pandoc installed",
+ "or Markdown (md) files. Be sure to have pandoc installed.",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument(dest='input', help='The input file to read')
@@ -323,10 +323,10 @@ def main():
help="Strip raw HTML code that can't be converted to "
"markup such as flash embeds or iframes (wordpress import only)")
parser.add_argument('--disable-slugs', action='store_true',
- dest='disable_slugs',
- help='Disable storing slugs from imported posts within output. '
- 'With this disabled, your Pelican URLs may not be consistent '
- 'with your original posts.')
+ dest='disable_slugs',
+ help='Disable storing slugs from imported posts within output. '
+ 'With this disabled, your Pelican URLs may not be consistent '
+ 'with your original posts.')
args = parser.parse_args()
@@ -358,4 +358,4 @@ def main():
fields2pelican(fields, args.markup, args.output,
dircat=args.dircat or False,
strip_raw=args.strip_raw or False,
- strip_slugs=args.disable_slugs or False)
+ disable_slugs=args.disable_slugs or False)
From 4ac094966e06eac5d540debbd166162297a768d4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alexis=20M=C3=A9taireau?=
Date: Fri, 11 Jan 2013 18:47:22 +0100
Subject: [PATCH 0038/1756] remove py2.6 support
---
tox.ini | 13 +------------
1 file changed, 1 insertion(+), 12 deletions(-)
diff --git a/tox.ini b/tox.ini
index 8c3961b9..bc6578ac 100644
--- a/tox.ini
+++ b/tox.ini
@@ -31,7 +31,7 @@
[tox]
-envlist = py26,py27,py32
+envlist = py27,py32
[testenv]
commands =
@@ -39,17 +39,6 @@ commands =
nosetests -s tests
deps =
-[testenv:py26]
-deps =
- nose
- unittest2
- mock
- Markdown
- BeautifulSoup4
- feedgenerator
- typogrify
- webassets
-
[testenv:py27]
deps =
nose
From 149ca493e0b904a6bafd234cf05d3ea568878dc2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alexis=20M=C3=A9taireau?=
Date: Fri, 11 Jan 2013 18:55:04 +0100
Subject: [PATCH 0039/1756] Annotate py3k code when needed.
---
pelican/generators.py | 2 +-
pelican/plugins/sitemap.py | 2 ++
pelican/readers.py | 1 +
pelican/tools/pelican_import.py | 4 +++-
4 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/pelican/generators.py b/pelican/generators.py
index 0d2fa6a9..ce102a31 100644
--- a/pelican/generators.py
+++ b/pelican/generators.py
@@ -120,7 +120,7 @@ class Generator(object):
for item in items:
value = getattr(self, item)
if hasattr(value, 'items'):
- value = list(value.items())
+ value = list(value.items()) # py3k safeguard for iterators
self.context[item] = value
diff --git a/pelican/plugins/sitemap.py b/pelican/plugins/sitemap.py
index 0ade1288..8043baad 100644
--- a/pelican/plugins/sitemap.py
+++ b/pelican/plugins/sitemap.py
@@ -89,6 +89,7 @@ class SitemapGenerator(object):
'yearly', 'never')
if isinstance(pris, dict):
+ # We use items for Py3k compat. .iteritems() otherwise
for k, v in pris.items():
if k in valid_keys and not isinstance(v, (int, float)):
default = self.priorities[k]
@@ -102,6 +103,7 @@ class SitemapGenerator(object):
warning("sitemap plugin: using the default values")
if isinstance(chfreqs, dict):
+ # .items() for py3k compat.
for k, v in chfreqs.items():
if k in valid_keys and v not in valid_chfreqs:
default = self.changefreqs[k]
diff --git a/pelican/readers.py b/pelican/readers.py
index b182b6fa..5c2ae58c 100644
--- a/pelican/readers.py
+++ b/pelican/readers.py
@@ -246,6 +246,7 @@ def read_file(filename, fmt=None, settings=None):
if filename_metadata:
match = re.match(filename_metadata, base)
if match:
+ # .items() for py3k compat.
for k, v in match.groupdict().items():
if k not in metadata:
k = k.lower() # metadata must be lowercase
diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py
index c373cfd4..33041b0e 100755
--- a/pelican/tools/pelican_import.py
+++ b/pelican/tools/pelican_import.py
@@ -4,9 +4,11 @@
from __future__ import unicode_literals, print_function
import argparse
try:
+ # py3k import
from html.parser import HTMLParser
except ImportError:
- from HTMLParser import HTMLParser
+ # py2 import
+ from HTMLParser import HTMLParser # NOQA
import os
import subprocess
import sys
From d7caaded3fcb2f0a45125973fc0672c1355252f7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alexis=20M=C3=A9taireau?=
Date: Fri, 11 Jan 2013 21:24:04 +0100
Subject: [PATCH 0040/1756] Use the en-us locale for functional tests
---
tests/test_pelican.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/tests/test_pelican.py b/tests/test_pelican.py
index e5de0608..4c451794 100644
--- a/tests/test_pelican.py
+++ b/tests/test_pelican.py
@@ -10,8 +10,6 @@ from shutil import rmtree
import locale
import logging
-from mock import patch
-
from pelican import Pelican
from pelican.settings import read_settings
from .support import LogCountHandler
@@ -73,6 +71,7 @@ class TestPelican(unittest.TestCase):
settings = read_settings(filename=None, override={
'PATH': INPUT_PATH,
'OUTPUT_PATH': self.temp_path,
+ 'LOCALE': locale.normalize('en_US'),
})
pelican = Pelican(settings=settings)
pelican.run()
@@ -88,6 +87,7 @@ class TestPelican(unittest.TestCase):
settings = read_settings(filename=SAMPLE_CONFIG, override={
'PATH': INPUT_PATH,
'OUTPUT_PATH': self.temp_path,
+ 'LOCALE': locale.normalize('en_US'),
})
pelican = Pelican(settings=settings)
pelican.run()
From 476f2980ceaa553d65e127b12f34e8687543e17f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alexis=20M=C3=A9taireau?=
Date: Tue, 15 Jan 2013 14:23:00 +0100
Subject: [PATCH 0041/1756] Damn, I forgot to update travis.
---
.travis.yml | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index e92b0abc..7ccdafd5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,8 +1,7 @@
language: python
python:
- - "2.6"
- "2.7"
-# - "3.2"
+ - "3.2"
before_install:
- sudo apt-get update -qq
- sudo apt-get install -qq ruby-sass
From 2c434ebac1c64ac2da85cdeeadeaf2c3bd5307cc Mon Sep 17 00:00:00 2001
From: "W. Trevor King"
Date: Thu, 3 Jan 2013 13:54:56 -0500
Subject: [PATCH 0042/1756] contents: Add rich comparisons to URLWrapper for
easy sorting
There have been earlier attempts to sort categories and authors
[1,2,3], but they either sorted based on the object id [3], or only
sorted the main author and categories list.
This patch uses rich comparisons (keyed off URLWrapper.name, but
easily adjustable in subclasses) to make the objects sortable without
specifying a key for each sort. For example, now
{% for tag, articles in tags|sort %}
works as expected in a Jinja template.
The functools.total_ordering decorator fills in the missing rich
comparisons [4,5].
[1]: 877d454c8fa979343d4881a00977d9ac8786f178
[2]: 7f36e0ed20745c73886d96d4af303b0d858b8a53
[3]: d0ec18f4dbd623c55bdf4928ee7c363f699ef696
[4]: http://docs.python.org/2/library/functools.html#functools.total_ordering
[5]: http://docs.python.org/3/library/functools.html#functools.total_ordering
---
pelican/contents.py | 16 +++++++++++++++-
pelican/generators.py | 3 +--
2 files changed, 16 insertions(+), 3 deletions(-)
diff --git a/pelican/contents.py b/pelican/contents.py
index 4655d4cc..bd257ad8 100644
--- a/pelican/contents.py
+++ b/pelican/contents.py
@@ -243,7 +243,9 @@ class Article(Page):
class Quote(Page):
base_properties = ('author', 'date')
+
@python_2_unicode_compatible
+@functools.total_ordering
class URLWrapper(object):
def __init__(self, name, settings):
self.name = name
@@ -256,8 +258,20 @@ class URLWrapper(object):
def __hash__(self):
return hash(self.name)
+ def _key(self):
+ return self.name
+
+ def _normalize_key(self, key):
+ return six.text_type(key)
+
def __eq__(self, other):
- return self.name == other
+ return self._key() == self._normalize_key(other)
+
+ def __ne__(self, other):
+ return self._key() != self._normalize_key(other)
+
+ def __lt__(self, other):
+ return self._key() < self._normalize_key(other)
def __str__(self):
return self.name
diff --git a/pelican/generators.py b/pelican/generators.py
index ce102a31..49b6bc1d 100644
--- a/pelican/generators.py
+++ b/pelican/generators.py
@@ -416,11 +416,10 @@ class ArticlesGenerator(Generator):
# order the categories per name
self.categories = list(self.categories.items())
self.categories.sort(
- key=lambda item: item[0].name,
reverse=self.settings['REVERSE_CATEGORY_ORDER'])
self.authors = list(self.authors.items())
- self.authors.sort(key=lambda item: item[0].name)
+ self.authors.sort()
self._update_context(('articles', 'dates', 'tags', 'categories',
'tag_cloud', 'authors', 'related_posts'))
From 656b5150ff26988d71d41fb32f2f75aeca665617 Mon Sep 17 00:00:00 2001
From: "W. Trevor King"
Date: Tue, 15 Jan 2013 16:32:11 -0500
Subject: [PATCH 0043/1756] docs/themes.rst: Document URLWrapper sorting for
use in Jinja templates
---
docs/themes.rst | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/docs/themes.rst b/docs/themes.rst
index 664b4466..19fd9274 100644
--- a/docs/themes.rst
+++ b/docs/themes.rst
@@ -68,6 +68,19 @@ categories A list of (category, articles) tuples, containing
pages The list of pages
============= ===================================================
+Sorting
+-------
+
+URL wrappers (currently categories, tags, and authors), have
+comparison methods that allow them to be easily sorted by name:
+
+ {% for tag, articles in tags|sort %}
+
+If you want to sort based on different criteria, `Jinja's sort
+command`__ has a number of options.
+
+__ http://jinja.pocoo.org/docs/templates/#sort
+
index.html
----------
From bebb94c15be1ae7ff7ec241f6e1de5aa18556654 Mon Sep 17 00:00:00 2001
From: "W. Trevor King"
Date: Tue, 15 Jan 2013 22:50:58 -0500
Subject: [PATCH 0044/1756] test_contents.py: Add URLWrapper comparison tests
The name switch between wrapper_a and wrapper_b ensures that we're not
secretly comparing some other field (e.g. id(object)).
---
tests/test_contents.py | 31 ++++++++++++++++++++++++++++++-
1 file changed, 30 insertions(+), 1 deletion(-)
diff --git a/tests/test_contents.py b/tests/test_contents.py
index eb7b6514..9b5673bd 100644
--- a/tests/test_contents.py
+++ b/tests/test_contents.py
@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
from .support import unittest
-from pelican.contents import Page, Article
+from pelican.contents import Page, Article, URLWrapper
from pelican.settings import _DEFAULT_CONFIG
from pelican.utils import truncate_html_words
from pelican.signals import content_object_init
@@ -187,3 +188,31 @@ class TestArticle(TestPage):
article_kwargs['metadata']['template'] = 'custom'
custom_article = Article(**article_kwargs)
self.assertEqual('custom', custom_article.template)
+
+
+class TestURLWrapper(unittest.TestCase):
+ def test_comparisons(self):
+ """URLWrappers are sorted by name
+ """
+ wrapper_a = URLWrapper(name='first', settings={})
+ wrapper_b = URLWrapper(name='last', settings={})
+ self.assertFalse(wrapper_a > wrapper_b)
+ self.assertFalse(wrapper_a >= wrapper_b)
+ self.assertFalse(wrapper_a == wrapper_b)
+ self.assertTrue(wrapper_a != wrapper_b)
+ self.assertTrue(wrapper_a <= wrapper_b)
+ self.assertTrue(wrapper_a < wrapper_b)
+ wrapper_b.name = 'first'
+ self.assertFalse(wrapper_a > wrapper_b)
+ self.assertTrue(wrapper_a >= wrapper_b)
+ self.assertTrue(wrapper_a == wrapper_b)
+ self.assertFalse(wrapper_a != wrapper_b)
+ self.assertTrue(wrapper_a <= wrapper_b)
+ self.assertFalse(wrapper_a < wrapper_b)
+ wrapper_a.name = 'last'
+ self.assertTrue(wrapper_a > wrapper_b)
+ self.assertTrue(wrapper_a >= wrapper_b)
+ self.assertFalse(wrapper_a == wrapper_b)
+ self.assertTrue(wrapper_a != wrapper_b)
+ self.assertFalse(wrapper_a <= wrapper_b)
+ self.assertFalse(wrapper_a < wrapper_b)
From 733ab8ae6ce59749ca9152bb5ad5cdca50a3e688 Mon Sep 17 00:00:00 2001
From: "W. Trevor King"
Date: Tue, 15 Jan 2013 23:05:48 -0500
Subject: [PATCH 0045/1756] test_contents: Add tests for metadata export from
Page.url_format
---
tests/test_contents.py | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/tests/test_contents.py b/tests/test_contents.py
index a8b9877f..ba94e14e 100644
--- a/tests/test_contents.py
+++ b/tests/test_contents.py
@@ -99,6 +99,16 @@ class TestPage(unittest.TestCase):
page = Page(**self.page_kwargs)
self.assertEqual(page.save_as, "pages/foo-bar-fr.html")
+ def test_metadata_url_format(self):
+ """Arbitrary metadata should be passed through url_format()
+ """
+ page = Page(**self.page_kwargs)
+ self.assertIn('summary', page.url_format.keys())
+ page.metadata['directory'] = 'test-dir'
+ page.settings = _DEFAULT_CONFIG.copy()
+ page.settings['PAGE_SAVE_AS'] = '{directory}/{slug}'
+ self.assertEqual(page.save_as, 'test-dir/foo-bar')
+
def test_datetime(self):
"""If DATETIME is set to a tuple, it should be used to override LOCALE
"""
From d2a221c8998dff7adb4baa75c4c0f2953187cb7f Mon Sep 17 00:00:00 2001
From: "W. Trevor King"
Date: Thu, 17 Jan 2013 09:49:03 -0500
Subject: [PATCH 0046/1756] contents: Encode Unicode locales for Python 2 in
Page.__init__
Python 2.7 chokes on Unicode locales:
$ python2.7
>>> import locale
>>> locale.setlocale(locale.LC_ALL, u'ja_JP.utf8')
Traceback (most recent call last):
...
ValueError: too many values to unpack
With the addition of:
from __future__ import unicode_literals
to tests/test_contents.py in:
commit bebb94c15be1ae7ff7ec241f6e1de5aa18556654
Author: W. Trevor King
Date: Tue Jan 15 22:50:58 2013 -0500
test_contents.py: Add URLWrapper comparison tests
the locale strings in TestPage.test_datetime are interpreted as
Unicode. Rather than fixing the encoding there, this patch updates
Page to handle Unicode locales automatically.
---
pelican/contents.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/pelican/contents.py b/pelican/contents.py
index bd257ad8..dc38e32f 100644
--- a/pelican/contents.py
+++ b/pelican/contents.py
@@ -8,6 +8,7 @@ import logging
import functools
import os
import re
+import sys
from datetime import datetime
from sys import platform, stdin
@@ -86,7 +87,10 @@ class Page(object):
self.date_format = settings['DEFAULT_DATE_FORMAT']
if isinstance(self.date_format, tuple):
- locale.setlocale(locale.LC_ALL, self.date_format[0])
+ locale_string = self.date_format[0]
+ if sys.version_info < (3, ) and isinstance(locale_string, six.text_type):
+ locale_string = locale_string.encode('ascii')
+ locale.setlocale(locale.LC_ALL, locale_string)
self.date_format = self.date_format[1]
if hasattr(self, 'date'):
From 61f05672e651f185a6e78dc5827502ba0ff93137 Mon Sep 17 00:00:00 2001
From: "W. Trevor King"
Date: Fri, 4 Jan 2013 13:08:08 -0500
Subject: [PATCH 0047/1756] content: Convert StaticContent.filepath to
.filename
For reasons that are unclear to me, StaticContent introduces the
`filepath` attribute rather than using the existing (and semantically
equivalent) Page.filename. This has caused confusion before [1], and
it's probably a good idea to merge the two.
While I was touching the line, I also updated the string formatting in
StaticGenerator.generate_output to use the forward compatible
'{}'.format() syntax.
[1]: https://github.com/getpelican/pelican/issues/162#issuecomment-3000363
---
pelican/contents.py | 4 ++--
pelican/generators.py | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/pelican/contents.py b/pelican/contents.py
index 43333e18..89e0397d 100644
--- a/pelican/contents.py
+++ b/pelican/contents.py
@@ -305,11 +305,11 @@ class StaticContent(object):
settings = copy.deepcopy(_DEFAULT_CONFIG)
self.src = src
self.url = dst or src
- self.filepath = os.path.join(settings['PATH'], src)
+ self.filename = os.path.join(settings['PATH'], src)
self.save_as = os.path.join(settings['OUTPUT_PATH'], self.url)
def __str__(self):
- return self.filepath
+ return self.filename
def is_valid_content(content, f):
diff --git a/pelican/generators.py b/pelican/generators.py
index ce102a31..664f666c 100644
--- a/pelican/generators.py
+++ b/pelican/generators.py
@@ -520,8 +520,8 @@ class StaticGenerator(Generator):
# copy all StaticContent files
for sc in self.staticfiles:
mkdir_p(os.path.dirname(sc.save_as))
- shutil.copy(sc.filepath, sc.save_as)
- logger.info('copying %s to %s' % (sc.filepath, sc.save_as))
+ shutil.copy(sc.filename, sc.save_as)
+ logger.info('copying {} to {}'.format(sc.filename, sc.save_as))
class PdfGenerator(Generator):
From 004adfa5ccbb840a585bff6dd6bfd7f65cfeaad9 Mon Sep 17 00:00:00 2001
From: "W. Trevor King"
Date: Fri, 4 Jan 2013 10:50:09 -0500
Subject: [PATCH 0048/1756] content: Convert Path.filename to .source_path
Making everything consistent is a bit awkward, since this is a
commonly used attribute, but I've done my best.
Reasons for not consolidating on `filename`:
* It is often used for the "basename" (last component in the path).
Using `source_path` makes it clear that this attribute can contain
multiple components.
Reasons for not consolidating on `filepath`:
* It is barely used in the Pelican source, and therefore easy to
change.
* `path` is more Pythonic. The only place `filepath` ever show up in
the documentation for `os`, `os.path`, and `shutil` is in the
`os.path.relpath` documentation [1].
Reasons for not consolidating on `path`:
* The Page elements have both a source (this attribute) and a
destination (.save_as). To avoid confusion for developers not aware
of this, make it painfully obvious that this attribute is for the
source. Explicit is better than implicit ;).
Where I was touching the line, I also updated the string formatting in
StaticGenerator.generate_output to use the forward compatible
'{}'.format() syntax.
[1]: http://docs.python.org/2/library/os.path.html#os.path.relpath
---
pelican/contents.py | 29 +++++++++++++++--------------
pelican/generators.py | 28 ++++++++++++++--------------
pelican/readers.py | 34 +++++++++++++++++-----------------
pelican/settings.py | 14 +++++++-------
pelican/utils.py | 33 +++++++++++++++++----------------
pelican/writers.py | 22 +++++++++++-----------
6 files changed, 81 insertions(+), 79 deletions(-)
diff --git a/pelican/contents.py b/pelican/contents.py
index 89e0397d..88518b0a 100644
--- a/pelican/contents.py
+++ b/pelican/contents.py
@@ -32,7 +32,7 @@ class Page(object):
default_template = 'page'
def __init__(self, content, metadata=None, settings=None,
- filename=None, context=None):
+ source_path=None, context=None):
# init parameters
if not metadata:
metadata = {}
@@ -75,8 +75,8 @@ class Page(object):
if not hasattr(self, 'slug') and hasattr(self, 'title'):
self.slug = slugify(self.title)
- if filename:
- self.filename = filename
+ if source_path:
+ self.source_path = source_path
# manage the date format
if not hasattr(self, 'date_format'):
@@ -160,8 +160,8 @@ class Page(object):
if value.startswith('/'):
value = value[1:]
else:
- # relative to the filename of this content
- value = self.get_relative_filename(
+ # relative to the source path of this content
+ value = self.get_relative_source_path(
os.path.join(self.relative_dir, value)
)
@@ -215,24 +215,25 @@ class Page(object):
else:
return self.default_template
- def get_relative_filename(self, filename=None):
+ def get_relative_source_path(self, source_path=None):
"""Return the relative path (from the content path) to the given
- filename.
+ source_path.
- If no filename is specified, use the filename of this content object.
+ If no source path is specified, use the source path of this
+ content object.
"""
- if not filename:
- filename = self.filename
+ if not source_path:
+ source_path = self.source_path
return os.path.relpath(
- os.path.abspath(os.path.join(self.settings['PATH'], filename)),
+ os.path.abspath(os.path.join(self.settings['PATH'], source_path)),
os.path.abspath(self.settings['PATH'])
)
@property
def relative_dir(self):
return os.path.dirname(os.path.relpath(
- os.path.abspath(self.filename),
+ os.path.abspath(self.source_path),
os.path.abspath(self.settings['PATH']))
)
@@ -305,11 +306,11 @@ class StaticContent(object):
settings = copy.deepcopy(_DEFAULT_CONFIG)
self.src = src
self.url = dst or src
- self.filename = os.path.join(settings['PATH'], src)
+ self.source_path = os.path.join(settings['PATH'], src)
self.save_as = os.path.join(settings['OUTPUT_PATH'], self.url)
def __str__(self):
- return self.filename
+ return self.source_path
def is_valid_content(content, f):
diff --git a/pelican/generators.py b/pelican/generators.py
index 664f666c..01d9fc9b 100644
--- a/pelican/generators.py
+++ b/pelican/generators.py
@@ -108,8 +108,8 @@ class Generator(object):
files.append(os.sep.join((root, f)))
return files
- def add_filename(self, content):
- location = os.path.relpath(os.path.abspath(content.filename),
+ def add_source_path(self, content):
+ location = os.path.relpath(os.path.abspath(content.source_path),
os.path.abspath(self.path))
self.context['filenames'][location] = content
@@ -352,11 +352,11 @@ class ArticlesGenerator(Generator):
signals.article_generate_context.send(self, metadata=metadata)
article = Article(content, metadata, settings=self.settings,
- filename=f, context=self.context)
+ source_path=f, context=self.context)
if not is_valid_content(article, f):
continue
- self.add_filename(article)
+ self.add_source_path(article)
if article.status == "published":
if hasattr(article, 'tags'):
@@ -455,11 +455,11 @@ class PagesGenerator(Generator):
continue
signals.pages_generate_context.send(self, metadata=metadata)
page = Page(content, metadata, settings=self.settings,
- filename=f, context=self.context)
+ source_path=f, context=self.context)
if not is_valid_content(page, f):
continue
- self.add_filename(page)
+ self.add_source_path(page)
if page.status == "published":
all_pages.append(page)
@@ -520,8 +520,8 @@ class StaticGenerator(Generator):
# copy all StaticContent files
for sc in self.staticfiles:
mkdir_p(os.path.dirname(sc.save_as))
- shutil.copy(sc.filename, sc.save_as)
- logger.info('copying {} to {}'.format(sc.filename, sc.save_as))
+ shutil.copy(sc.source_path, sc.save_as)
+ logger.info('copying {} to {}'.format(sc.source_path, sc.save_as))
class PdfGenerator(Generator):
@@ -544,11 +544,11 @@ class PdfGenerator(Generator):
raise Exception("unable to find rst2pdf")
def _create_pdf(self, obj, output_path):
- if obj.filename.endswith(".rst"):
+ if obj.source_path.endswith('.rst'):
filename = obj.slug + ".pdf"
output_pdf = os.path.join(output_path, filename)
- # print "Generating pdf for", obj.filename, " in ", output_pdf
- with open(obj.filename) as f:
+ # print('Generating pdf for', obj.source_path, 'in', output_pdf)
+ with open(obj.source_path) as f:
self.pdfcreator.createPdf(text=f.read(), output=output_pdf)
logger.info(' [ok] writing %s' % output_pdf)
@@ -578,9 +578,9 @@ class SourceFileGenerator(Generator):
self.output_extension = self.settings['OUTPUT_SOURCES_EXTENSION']
def _create_source(self, obj, output_path):
- filename = os.path.splitext(obj.save_as)[0]
- dest = os.path.join(output_path, filename + self.output_extension)
- copy('', obj.filename, dest)
+ output_path = os.path.splitext(obj.save_as)[0]
+ dest = os.path.join(output_path, output_path + self.output_extension)
+ copy('', obj.source_path, dest)
def generate_output(self, writer=None):
logger.info(' Generating source files...')
diff --git a/pelican/readers.py b/pelican/readers.py
index 5c2ae58c..440bbdf8 100644
--- a/pelican/readers.py
+++ b/pelican/readers.py
@@ -109,20 +109,20 @@ class RstReader(Reader):
output[name] = self.process_metadata(name, value)
return output
- def _get_publisher(self, filename):
+ def _get_publisher(self, source_path):
extra_params = {'initial_header_level': '2'}
pub = docutils.core.Publisher(
destination_class=docutils.io.StringOutput)
pub.set_components('standalone', 'restructuredtext', 'html')
pub.writer.translator_class = PelicanHTMLTranslator
pub.process_programmatic_settings(None, extra_params, None)
- pub.set_source(source_path=filename)
+ pub.set_source(source_path=source_path)
pub.publish()
return pub
- def read(self, filename):
+ def read(self, source_path):
"""Parses restructured text"""
- pub = self._get_publisher(filename)
+ pub = self._get_publisher(source_path)
parts = pub.writer.parts
content = parts.get('body')
@@ -151,9 +151,9 @@ class MarkdownReader(Reader):
output[name] = self.process_metadata(name, value[0])
return output
- def read(self, filename):
+ def read(self, source_path):
"""Parse content and metadata of markdown files"""
- text = pelican_open(filename)
+ text = pelican_open(source_path)
md = Markdown(extensions=set(self.extensions + ['meta']))
content = md.convert(text)
@@ -165,9 +165,9 @@ class HtmlReader(Reader):
file_extensions = ['html', 'htm']
_re = re.compile('\<\!\-\-\#\s?[A-z0-9_-]*\s?\:s?[A-z0-9\s_-]*\s?\-\-\>')
- def read(self, filename):
+ def read(self, source_path):
"""Parse content and metadata of (x)HTML files"""
- with open(filename) as content:
+ with open(source_path) as content:
metadata = {'title': 'unnamed'}
for i in self._re.findall(content):
key = i.split(':')[0][5:].strip()
@@ -183,10 +183,10 @@ class AsciiDocReader(Reader):
file_extensions = ['asc']
default_options = ["--no-header-footer", "-a newline=\\n"]
- def read(self, filename):
+ def read(self, source_path):
"""Parse content and metadata of asciidoc files"""
from cStringIO import StringIO
- text = StringIO(pelican_open(filename))
+ text = StringIO(pelican_open(source_path))
content = StringIO()
ad = AsciiDocAPI()
@@ -216,14 +216,14 @@ for cls in Reader.__subclasses__():
_EXTENSIONS[ext] = cls
-def read_file(filename, fmt=None, settings=None):
+def read_file(path, fmt=None, settings=None):
"""Return a reader object using the given format."""
- base, ext = os.path.splitext(os.path.basename(filename))
+ base, ext = os.path.splitext(os.path.basename(path))
if not fmt:
fmt = ext[1:]
if fmt not in _EXTENSIONS:
- raise TypeError('Pelican does not know how to parse %s' % filename)
+ raise TypeError('Pelican does not know how to parse {}'.format(path))
reader = _EXTENSIONS[fmt](settings)
settings_key = '%s_EXTENSIONS' % fmt.upper()
@@ -234,7 +234,7 @@ def read_file(filename, fmt=None, settings=None):
if not reader.enabled:
raise ValueError("Missing dependencies for %s" % fmt)
- content, metadata = reader.read(filename)
+ content, metadata = reader.read(path)
# eventually filter the content with typogrify if asked so
if settings and settings.get('TYPOGRIFY'):
@@ -242,9 +242,9 @@ def read_file(filename, fmt=None, settings=None):
content = typogrify(content)
metadata['title'] = typogrify(metadata['title'])
- filename_metadata = settings and settings.get('FILENAME_METADATA')
- if filename_metadata:
- match = re.match(filename_metadata, base)
+ file_metadata = settings and settings.get('FILENAME_METADATA')
+ if file_metadata:
+ match = re.match(file_metadata, base)
if match:
# .items() for py3k compat.
for k, v in match.groupdict().items():
diff --git a/pelican/settings.py b/pelican/settings.py
index 9c0d8434..e4e0501d 100644
--- a/pelican/settings.py
+++ b/pelican/settings.py
@@ -84,15 +84,15 @@ _DEFAULT_CONFIG = {'PATH': '.',
}
-def read_settings(filename=None, override=None):
- if filename:
- local_settings = get_settings_from_file(filename)
+def read_settings(path=None, override=None):
+ if path:
+ local_settings = get_settings_from_file(path)
# Make the paths relative to the settings file
for p in ['PATH', 'OUTPUT_PATH', 'THEME']:
if p in local_settings and local_settings[p] is not None \
and not isabs(local_settings[p]):
absp = os.path.abspath(os.path.normpath(os.path.join(
- os.path.dirname(filename), local_settings[p])))
+ os.path.dirname(path), local_settings[p])))
if p != 'THEME' or os.path.exists(absp):
local_settings[p] = absp
else:
@@ -116,14 +116,14 @@ def get_settings_from_module(module=None, default_settings=_DEFAULT_CONFIG):
return context
-def get_settings_from_file(filename, default_settings=_DEFAULT_CONFIG):
+def get_settings_from_file(path, default_settings=_DEFAULT_CONFIG):
"""
Load settings from a file path, returning a dict.
"""
- name = os.path.basename(filename).rpartition(".")[0]
- module = imp.load_source(name, filename)
+ name = os.path.basename(path).rpartition('.')[0]
+ module = imp.load_source(name, path)
return get_settings_from_module(module, default_settings=default_settings)
diff --git a/pelican/utils.py b/pelican/utils.py
index 3a41d04e..48ed0757 100644
--- a/pelican/utils.py
+++ b/pelican/utils.py
@@ -141,9 +141,9 @@ def get_date(string):
raise ValueError("'%s' is not a valid date" % string)
-def pelican_open(filename):
+def pelican_open(path):
"""Open a file and return it's content"""
- return open(filename, encoding='utf-8').read()
+ return open(path, encoding='utf-8').read()
def slugify(value):
@@ -245,9 +245,9 @@ def clean_output_dir(path):
logger.error("Unable to delete %s, file type unknown" % file)
-def get_relative_path(filename):
- """Return the relative path from the given filename to the root path."""
- nslashes = filename.count('/')
+def get_relative_path(path):
+ """Return the relative path from the given path to the root path."""
+ nslashes = path.count('/')
if nslashes == 0:
return '.'
else:
@@ -344,15 +344,16 @@ def process_translations(content_list):
if len_ > 1:
logger.warning('there are %s variants of "%s"' % (len_, slug))
for x in default_lang_items:
- logger.warning(' %s' % x.filename)
+ logger.warning(' {}'.format(x.source_path))
elif len_ == 0:
default_lang_items = items[:1]
if not slug:
- msg = 'empty slug for %r. ' % default_lang_items[0].filename\
- + 'You can fix this by adding a title or a slug to your '\
- + 'content'
- logger.warning(msg)
+ logger.warning((
+ 'empty slug for {!r}. '
+ 'You can fix this by adding a title or a slug to your '
+ 'content'
+ ).format(default_lang_items[0].source_path))
index.extend(default_lang_items)
translations.extend([x for x in items if x not in default_lang_items])
for a in items:
@@ -388,14 +389,14 @@ def files_changed(path, extensions):
FILENAMES_MTIMES = defaultdict(int)
-def file_changed(filename):
- mtime = os.stat(filename).st_mtime
- if FILENAMES_MTIMES[filename] == 0:
- FILENAMES_MTIMES[filename] = mtime
+def file_changed(path):
+ mtime = os.stat(path).st_mtime
+ if FILENAMES_MTIMES[path] == 0:
+ FILENAMES_MTIMES[path] = mtime
return False
else:
- if mtime > FILENAMES_MTIMES[filename]:
- FILENAMES_MTIMES[filename] = mtime
+ if mtime > FILENAMES_MTIMES[path]:
+ FILENAMES_MTIMES[path] = mtime
return True
return False
diff --git a/pelican/writers.py b/pelican/writers.py
index 8374ea3e..429507a0 100644
--- a/pelican/writers.py
+++ b/pelican/writers.py
@@ -46,23 +46,23 @@ class Writer(object):
pubdate=set_date_tzinfo(item.date,
self.settings.get('TIMEZONE', None)))
- def write_feed(self, elements, context, filename=None, feed_type='atom'):
+ def write_feed(self, elements, context, path=None, feed_type='atom'):
"""Generate a feed with the list of articles provided
- Return the feed. If no output_path or filename is specified, just
+ Return the feed. If no path or output_path is specified, just
return the feed object.
:param elements: the articles to put on the feed.
:param context: the context to get the feed metadata.
- :param filename: the filename to output.
+ :param path: the path to output.
:param feed_type: the feed type to use (atom or rss)
"""
old_locale = locale.setlocale(locale.LC_ALL)
locale.setlocale(locale.LC_ALL, str('C'))
try:
- self.site_url = context.get('SITEURL', get_relative_path(filename))
+ self.site_url = context.get('SITEURL', get_relative_path(path))
self.feed_domain = context.get('FEED_DOMAIN')
- self.feed_url = '%s/%s' % (self.feed_domain, filename)
+ self.feed_url = '{}/{}'.format(self.feed_domain, path)
feed = self._create_new_feed(feed_type, context)
@@ -72,8 +72,8 @@ class Writer(object):
for i in range(max_items):
self._add_item_to_the_feed(feed, elements[i])
- if filename:
- complete_path = os.path.join(self.output_path, filename)
+ if path:
+ complete_path = os.path.join(self.output_path, path)
try:
os.makedirs(os.path.dirname(complete_path))
except Exception:
@@ -114,14 +114,14 @@ class Writer(object):
output = template.render(localcontext)
finally:
locale.setlocale(locale.LC_ALL, old_locale)
- filename = os.sep.join((output_path, name))
+ path = os.path.join(output_path, name)
try:
- os.makedirs(os.path.dirname(filename))
+ os.makedirs(os.path.dirname(path))
except Exception:
pass
- with open(filename, 'w', encoding='utf-8') as f:
+ with open(path, 'w', encoding='utf-8') as f:
f.write(output)
- logger.info('writing %s' % filename)
+ logger.info('writing {}'.format(path))
localcontext = context.copy()
if relative_urls:
From 9b574361c90e8bc394b17903802935e60ecbe3f0 Mon Sep 17 00:00:00 2001
From: "W. Trevor King"
Date: Fri, 4 Jan 2013 11:17:23 -0500
Subject: [PATCH 0049/1756] doc: convert Markdown example to source_path and
modernize
---
docs/internals.rst | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/docs/internals.rst b/docs/internals.rst
index 280e14d7..cadd300b 100644
--- a/docs/internals.rst
+++ b/docs/internals.rst
@@ -47,19 +47,17 @@ Take a look at the Markdown reader::
class MarkdownReader(Reader):
enabled = bool(Markdown)
- def read(self, filename):
+ def read(self, source_path):
"""Parse content and metadata of markdown files"""
- text = open(filename)
+ text = pelican_open(source_path)
md = Markdown(extensions = ['meta', 'codehilite'])
content = md.convert(text)
metadata = {}
for name, value in md.Meta.items():
- if name in _METADATA_FIELDS:
- meta = _METADATA_FIELDS[name](value[0])
- else:
- meta = value[0]
- metadata[name.lower()] = meta
+ name = name.lower()
+ meta = self.process_metadata(name, value[0])
+ metadata[name] = meta
return content, metadata
Simple, isn't it?
From 54a9132aea663dcdc6d8deb350e3c3e3a3381907 Mon Sep 17 00:00:00 2001
From: "W. Trevor King"
Date: Fri, 4 Jan 2013 11:22:49 -0500
Subject: [PATCH 0050/1756] tests: Update tests after filename/filepath ->
source_path
---
tests/test_generators.py | 10 +++++-----
tests/test_pelican.py | 4 ++--
tests/test_readers.py | 32 ++++++++++++++++----------------
tests/test_utils.py | 12 ++++++------
4 files changed, 29 insertions(+), 29 deletions(-)
diff --git a/tests/test_generators.py b/tests/test_generators.py
index 50f5fe3e..54fe7e61 100644
--- a/tests/test_generators.py
+++ b/tests/test_generators.py
@@ -229,20 +229,20 @@ class TestTemplatePagesGenerator(unittest.TestCase):
# create a dummy template file
template_dir = os.path.join(self.temp_content, 'template')
- template_filename = os.path.join(template_dir, 'source.html')
+ template_path = os.path.join(template_dir, 'source.html')
os.makedirs(template_dir)
- with open(template_filename, 'w') as template_file:
+ with open(template_path, 'w') as template_file:
template_file.write(self.TEMPLATE_CONTENT)
writer = Writer(self.temp_output, settings=settings)
generator.generate_output(writer)
- output_filename = os.path.join(
+ output_path = os.path.join(
self.temp_output, 'generated', 'file.html')
# output file has been generated
- self.assertTrue(os.path.exists(output_filename))
+ self.assertTrue(os.path.exists(output_path))
# output content is correct
- with open(output_filename, 'r') as output_file:
+ with open(output_path, 'r') as output_file:
self.assertEquals(output_file.read(), 'foo: bar')
diff --git a/tests/test_pelican.py b/tests/test_pelican.py
index 6a082676..ca0e22cc 100644
--- a/tests/test_pelican.py
+++ b/tests/test_pelican.py
@@ -70,7 +70,7 @@ class TestPelican(unittest.TestCase):
def test_basic_generation_works(self):
# when running pelican without settings, it should pick up the default
# ones and generate correct output without raising any exception
- settings = read_settings(filename=None, override={
+ settings = read_settings(path=None, override={
'PATH': INPUT_PATH,
'OUTPUT_PATH': self.temp_path,
'LOCALE': locale.normalize('en_US'),
@@ -86,7 +86,7 @@ class TestPelican(unittest.TestCase):
def test_custom_generation_works(self):
# the same thing with a specified set of settings should work
- settings = read_settings(filename=SAMPLE_CONFIG, override={
+ settings = read_settings(path=SAMPLE_CONFIG, override={
'PATH': INPUT_PATH,
'OUTPUT_PATH': self.temp_path,
'LOCALE': locale.normalize('en_US'),
diff --git a/tests/test_readers.py b/tests/test_readers.py
index e3cea629..75e664d5 100644
--- a/tests/test_readers.py
+++ b/tests/test_readers.py
@@ -11,7 +11,7 @@ CUR_DIR = os.path.dirname(__file__)
CONTENT_PATH = os.path.join(CUR_DIR, 'content')
-def _filename(*args):
+def _path(*args):
return os.path.join(CONTENT_PATH, *args)
@@ -19,7 +19,7 @@ class RstReaderTest(unittest.TestCase):
def test_article_with_metadata(self):
reader = readers.RstReader({})
- content, metadata = reader.read(_filename('article_with_metadata.rst'))
+ content, metadata = reader.read(_path('article_with_metadata.rst'))
expected = {
'category': 'yeah',
'author': 'Alexis Métaireau',
@@ -37,7 +37,7 @@ class RstReaderTest(unittest.TestCase):
def test_article_with_filename_metadata(self):
content, metadata = readers.read_file(
- _filename('2012-11-29_rst_w_filename_meta#foo-bar.rst'),
+ _path('2012-11-29_rst_w_filename_meta#foo-bar.rst'),
settings={})
expected = {
'category': 'yeah',
@@ -48,7 +48,7 @@ class RstReaderTest(unittest.TestCase):
self.assertEquals(value, expected[key], key)
content, metadata = readers.read_file(
- _filename('2012-11-29_rst_w_filename_meta#foo-bar.rst'),
+ _path('2012-11-29_rst_w_filename_meta#foo-bar.rst'),
settings={
'FILENAME_METADATA': '(?P\d{4}-\d{2}-\d{2}).*'
})
@@ -62,7 +62,7 @@ class RstReaderTest(unittest.TestCase):
self.assertEquals(value, expected[key], key)
content, metadata = readers.read_file(
- _filename('2012-11-29_rst_w_filename_meta#foo-bar.rst'),
+ _path('2012-11-29_rst_w_filename_meta#foo-bar.rst'),
settings={
'FILENAME_METADATA': '(?P\d{4}-\d{2}-\d{2})_' \
'_(?P.*)' \
@@ -82,7 +82,7 @@ class RstReaderTest(unittest.TestCase):
def test_article_metadata_key_lowercase(self):
"""Keys of metadata should be lowercase."""
reader = readers.RstReader({})
- content, metadata = reader.read(_filename('article_with_uppercase_metadata.rst'))
+ content, metadata = reader.read(_path('article_with_uppercase_metadata.rst'))
self.assertIn('category', metadata, "Key should be lowercase.")
self.assertEquals('Yeah', metadata.get('category'), "Value keeps cases.")
@@ -90,7 +90,7 @@ class RstReaderTest(unittest.TestCase):
def test_typogrify(self):
# if nothing is specified in the settings, the content should be
# unmodified
- content, _ = readers.read_file(_filename('article.rst'))
+ content, _ = readers.read_file(_path('article.rst'))
expected = "
This is some content. With some stuff to "\
""typogrify".
\n
Now with added "\
'support for '\
@@ -100,7 +100,7 @@ class RstReaderTest(unittest.TestCase):
try:
# otherwise, typogrify should be applied
- content, _ = readers.read_file(_filename('article.rst'),
+ content, _ = readers.read_file(_path('article.rst'),
settings={'TYPOGRIFY': True})
expected = "
This is some content. With some stuff to "\
"“typogrify”.
\n
Now with added "\
@@ -118,7 +118,7 @@ class MdReaderTest(unittest.TestCase):
def test_article_with_md_extension(self):
# test to ensure the md extension is being processed by the correct reader
reader = readers.MarkdownReader({})
- content, metadata = reader.read(_filename('article_with_md_extension.md'))
+ content, metadata = reader.read(_path('article_with_md_extension.md'))
expected = "
Test Markdown File Header
\n"\
"
Used for pelican test
\n"\
"
The quick brown fox jumped over the lazy dog's back.
"
@@ -136,7 +136,7 @@ class MdReaderTest(unittest.TestCase):
def test_article_with_mkd_extension(self):
# test to ensure the mkd extension is being processed by the correct reader
reader = readers.MarkdownReader({})
- content, metadata = reader.read(_filename('article_with_mkd_extension.mkd'))
+ content, metadata = reader.read(_path('article_with_mkd_extension.mkd'))
expected = "
Test Markdown File Header
\n"\
"
Used for pelican test
\n"\
"
This is another markdown test file. Uses the mkd extension.
"
@@ -147,7 +147,7 @@ class MdReaderTest(unittest.TestCase):
def test_article_with_markdown_markup_extension(self):
# test to ensure the markdown markup extension is being processed as expected
content, metadata = readers.read_file(
- _filename('article_with_markdown_markup_extensions.md'),
+ _path('article_with_markdown_markup_extensions.md'),
settings={'MD_EXTENSIONS': ['toc', 'codehilite', 'extra']})
expected = '
\n'\
'
\n'\
@@ -165,7 +165,7 @@ class MdReaderTest(unittest.TestCase):
@unittest.skipUnless(readers.Markdown, "markdown isn't installed")
def test_article_with_filename_metadata(self):
content, metadata = readers.read_file(
- _filename('2012-11-30_md_w_filename_meta#foo-bar.md'),
+ _path('2012-11-30_md_w_filename_meta#foo-bar.md'),
settings={})
expected = {
'category': 'yeah',
@@ -175,7 +175,7 @@ class MdReaderTest(unittest.TestCase):
self.assertEquals(value, metadata[key], key)
content, metadata = readers.read_file(
- _filename('2012-11-30_md_w_filename_meta#foo-bar.md'),
+ _path('2012-11-30_md_w_filename_meta#foo-bar.md'),
settings={
'FILENAME_METADATA': '(?P\d{4}-\d{2}-\d{2}).*'
})
@@ -188,7 +188,7 @@ class MdReaderTest(unittest.TestCase):
self.assertEquals(value, metadata[key], key)
content, metadata = readers.read_file(
- _filename('2012-11-30_md_w_filename_meta#foo-bar.md'),
+ _path('2012-11-30_md_w_filename_meta#foo-bar.md'),
settings={
'FILENAME_METADATA': '(?P\d{4}-\d{2}-\d{2})'
'_(?P.*)'
@@ -210,7 +210,7 @@ class AdReaderTest(unittest.TestCase):
def test_article_with_asc_extension(self):
# test to ensure the asc extension is being processed by the correct reader
reader = readers.AsciiDocReader({})
- content, metadata = reader.read(_filename('article_with_asc_extension.asc'))
+ content, metadata = reader.read(_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)
@@ -241,7 +241,7 @@ class AdReaderTest(unittest.TestCase):
def test_article_with_asc_options(self):
# test to ensure the ASCIIDOC_OPTIONS is being used
reader = readers.AsciiDocReader(dict(ASCIIDOC_OPTIONS=["-a revision=1.0.42"]))
- content, metadata = reader.read(_filename('article_with_asc_options.asc'))
+ content, metadata = reader.read(_path('article_with_asc_options.asc'))
expected = '\n
Used for pelican test
\n'\
'
version 1.0.42
\n'\
'
The quick brown fox jumped over the lazy dog’s back.
\n'
diff --git a/tests/test_utils.py b/tests/test_utils.py
index eddb3e25..ea4f839c 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -79,17 +79,17 @@ class TestUtils(unittest.TestCase):
"""Test if file changes are correctly detected
Make sure to handle not getting any files correctly"""
- path = os.path.join(os.path.dirname(__file__), 'content')
- filename = os.path.join(path, 'article_with_metadata.rst')
- changed = utils.files_changed(path, 'rst')
+ dirname = os.path.join(os.path.dirname(__file__), 'content')
+ path = os.path.join(dirname, 'article_with_metadata.rst')
+ changed = utils.files_changed(dirname, 'rst')
self.assertEquals(changed, True)
- changed = utils.files_changed(path, 'rst')
+ changed = utils.files_changed(dirname, 'rst')
self.assertEquals(changed, False)
t = time.time()
- os.utime(filename, (t, t))
- changed = utils.files_changed(path, 'rst')
+ os.utime(path, (t, t))
+ changed = utils.files_changed(dirname, 'rst')
self.assertEquals(changed, True)
self.assertAlmostEqual(utils.LAST_MTIME, t, delta=1)
From c3c3037a1d726f3a1e1b04a3ee0b7d88cf1c5226 Mon Sep 17 00:00:00 2001
From: "W. Trevor King"
Date: Fri, 4 Jan 2013 07:29:23 -0500
Subject: [PATCH 0051/1756] utils: Teach mkdir_p to fail if the existing target
isn't a directory
Existing symlinks to directories will still pass, because isdir()
follows symbolic links.
---
pelican/utils.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pelican/utils.py b/pelican/utils.py
index 3a41d04e..40406b1a 100644
--- a/pelican/utils.py
+++ b/pelican/utils.py
@@ -416,5 +416,5 @@ def mkdir_p(path):
try:
os.makedirs(path)
except OSError as e:
- if e.errno != errno.EEXIST:
+ if e.errno != errno.EEXIST or not os.path.isdir(path):
raise
From ec50e18a3e4ed6e823126778df39c606ce7c3036 Mon Sep 17 00:00:00 2001
From: "W. Trevor King"
Date: Fri, 4 Jan 2013 13:02:30 -0500
Subject: [PATCH 0052/1756] utils: Add deprecated_attribute decorator
---
pelican/utils.py | 43 +++++++++++++++++++++++++++++++++++++++++++
tests/test_utils.py | 11 +++++++++++
2 files changed, 54 insertions(+)
diff --git a/pelican/utils.py b/pelican/utils.py
index 48ed0757..9c3bd7be 100644
--- a/pelican/utils.py
+++ b/pelican/utils.py
@@ -6,6 +6,7 @@ import os
import re
import pytz
import shutil
+import traceback
import logging
import errno
import locale
@@ -122,6 +123,48 @@ class memoized(object):
'''Support instance methods.'''
return partial(self.__call__, obj)
+
+def deprecated_attribute(old, new, since=None, remove=None, doc=None):
+ """Attribute deprecation decorator for gentle upgrades
+
+ For example:
+
+ class MyClass (object):
+ @deprecated_attribute(
+ old='abc', new='xyz', since=(3, 2, 0), remove=(4, 1, 3))
+ def abc(): return None
+
+ def __init__(self):
+ xyz = 5
+
+ Note that the decorator needs a dummy method to attach to, but the
+ content of the dummy method is ignored.
+ """
+ def _warn():
+ version = '.'.join(six.text_type(x) for x in since)
+ message = ['{} has been deprecated since {}'.format(old, version)]
+ if remove:
+ version = '.'.join(six.text_type(x) for x in remove)
+ message.append(
+ ' and will be removed by version {}'.format(version))
+ message.append('. Use {} instead.'.format(new))
+ logger.warning(''.join(message))
+ logger.debug(''.join(
+ six.text_type(x) for x in traceback.format_stack()))
+
+ def fget(self):
+ _warn()
+ return getattr(self, new)
+
+ def fset(self, value):
+ _warn()
+ setattr(self, new, value)
+
+ def decorator(dummy):
+ return property(fget=fget, fset=fset, doc=doc)
+
+ return decorator
+
def get_date(string):
"""Return a datetime object from a string.
diff --git a/tests/test_utils.py b/tests/test_utils.py
index ea4f839c..75e87c04 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -11,6 +11,17 @@ from pelican.utils import NoFilesError
class TestUtils(unittest.TestCase):
+ _new_attribute = 'new_value'
+
+ @utils.deprecated_attribute(
+ old='_old_attribute', new='_new_attribute',
+ since=(3, 1, 0), remove=(4, 1, 3))
+ def _old_attribute(): return None
+
+ def test_deprecated_attribute(self):
+ value = self._old_attribute
+ self.assertEquals(value, self._new_attribute)
+ # TODO: check log warning
def test_get_date(self):
# valid ones
From 13cd0a4cb3f62b8166ff3957e3385c405b28f34d Mon Sep 17 00:00:00 2001
From: "W. Trevor King"
Date: Fri, 4 Jan 2013 13:03:19 -0500
Subject: [PATCH 0053/1756] contents: Add deprecation warnings for
Page.filename and StaticContent.filepath
---
pelican/contents.py | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/pelican/contents.py b/pelican/contents.py
index 88518b0a..0dc0f0e9 100644
--- a/pelican/contents.py
+++ b/pelican/contents.py
@@ -15,7 +15,7 @@ from sys import platform, stdin
from pelican.settings import _DEFAULT_CONFIG
from pelican.utils import (slugify, truncate_html_words, memoized,
- python_2_unicode_compatible)
+ python_2_unicode_compatible, deprecated_attribute)
from pelican import signals
import pelican.utils
@@ -31,6 +31,10 @@ class Page(object):
mandatory_properties = ('title',)
default_template = 'page'
+ @deprecated_attribute(old='filename', new='source_path', since=(3, 2, 0))
+ def filename():
+ return None
+
def __init__(self, content, metadata=None, settings=None,
source_path=None, context=None):
# init parameters
@@ -301,6 +305,10 @@ class Author(URLWrapper):
@python_2_unicode_compatible
class StaticContent(object):
+ @deprecated_attribute(old='filepath', new='source_path', since=(3, 2, 0))
+ def filepath():
+ return None
+
def __init__(self, src, dst=None, settings=None):
if not settings:
settings = copy.deepcopy(_DEFAULT_CONFIG)
From 4fcdaa91e96988e2754b9aec5e141e7219baae89 Mon Sep 17 00:00:00 2001
From: "W. Trevor King"
Date: Fri, 18 Jan 2013 07:27:35 -0500
Subject: [PATCH 0054/1756] tests/support: Factor LogCountHandler testing out
into LoggedTestCase
To avoid duplicating boilerplate when we need to test logged messages
outside of TestPelican.
---
tests/support.py | 21 +++++++++++++++++++++
tests/test_pelican.py | 19 +++++++------------
2 files changed, 28 insertions(+), 12 deletions(-)
diff --git a/tests/support.py b/tests/support.py
index 6011e6cd..209cc665 100644
--- a/tests/support.py
+++ b/tests/support.py
@@ -176,3 +176,24 @@ class LogCountHandler(BufferingHandler):
if (msg is None or re.match(msg, l.getMessage()))
and (level is None or l.levelno == level)
])
+
+
+class LoggedTestCase(unittest.TestCase):
+ """A test case that captures log messages
+ """
+
+ def setUp(self):
+ super(LoggedTestCase, self).setUp()
+ self._logcount_handler = LogCountHandler()
+ logging.getLogger().addHandler(self._logcount_handler)
+
+ def tearDown(self):
+ logging.getLogger().removeHandler(self._logcount_handler)
+ super(LoggedTestCase, self).tearDown()
+
+ def assertLogCountEqual(self, count=None, msg=None, **kwargs):
+ actual = self._logcount_handler.count_logs(msg=msg, **kwargs)
+ self.assertEqual(
+ actual, count,
+ msg='expected {} occurrences of {!r}, but found {}'.format(
+ count, msg, actual))
diff --git a/tests/test_pelican.py b/tests/test_pelican.py
index ca0e22cc..49e20b0a 100644
--- a/tests/test_pelican.py
+++ b/tests/test_pelican.py
@@ -1,9 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, print_function
-try:
- import unittest2 as unittest
-except ImportError:
- import unittest # NOQA
import os
from filecmp import dircmp
@@ -14,7 +10,7 @@ import logging
from pelican import Pelican
from pelican.settings import read_settings
-from .support import LogCountHandler
+from .support import LoggedTestCase
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
SAMPLES_PATH = os.path.abspath(os.sep.join((CURRENT_DIR, "..", "samples")))
@@ -39,13 +35,12 @@ def recursiveDiff(dcmp):
return diff
-class TestPelican(unittest.TestCase):
+class TestPelican(LoggedTestCase):
# general functional testing for pelican. Basically, this test case tries
# to run pelican in different situations and see how it behaves
def setUp(self):
- self.logcount_handler = LogCountHandler()
- logging.getLogger().addHandler(self.logcount_handler)
+ super(TestPelican, self).setUp()
self.temp_path = mkdtemp()
self.old_locale = locale.setlocale(locale.LC_ALL)
locale.setlocale(locale.LC_ALL, str('C'))
@@ -53,7 +48,7 @@ class TestPelican(unittest.TestCase):
def tearDown(self):
rmtree(self.temp_path)
locale.setlocale(locale.LC_ALL, self.old_locale)
- logging.getLogger().removeHandler(self.logcount_handler)
+ super(TestPelican, self).tearDown()
def assertFilesEqual(self, diff):
msg = "some generated files differ from the expected functional " \
@@ -79,10 +74,10 @@ class TestPelican(unittest.TestCase):
pelican.run()
dcmp = dircmp(self.temp_path, os.sep.join((OUTPUT_PATH, "basic")))
self.assertFilesEqual(recursiveDiff(dcmp))
- self.assertEqual(self.logcount_handler.count_logs(
+ self.assertLogCountEqual(
+ count=10,
msg="Unable to find.*skipping url replacement",
- level=logging.WARNING,
- ), 10, msg="bad number of occurences found for this log")
+ level=logging.WARNING)
def test_custom_generation_works(self):
# the same thing with a specified set of settings should work
From 13b36a5c34b7a64f36662d537c2fa192994048df Mon Sep 17 00:00:00 2001
From: "W. Trevor King"
Date: Fri, 18 Jan 2013 07:33:42 -0500
Subject: [PATCH 0055/1756] test_utils: Add log count checks to
test_deprecated_attribute
---
tests/test_utils.py | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 75e87c04..c176325e 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -1,16 +1,17 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, print_function
+import logging
import shutil
import os
import datetime
import time
from pelican import utils
-from .support import get_article, unittest
+from .support import get_article, LoggedTestCase
from pelican.utils import NoFilesError
-class TestUtils(unittest.TestCase):
+class TestUtils(LoggedTestCase):
_new_attribute = 'new_value'
@utils.deprecated_attribute(
@@ -21,7 +22,11 @@ class TestUtils(unittest.TestCase):
def test_deprecated_attribute(self):
value = self._old_attribute
self.assertEquals(value, self._new_attribute)
- # TODO: check log warning
+ self.assertLogCountEqual(
+ count=1,
+ msg=('_old_attribute has been deprecated since 3.1.0 and will be '
+ 'removed by version 4.1.3. Use _new_attribute instead'),
+ level=logging.WARNING)
def test_get_date(self):
# valid ones
From 6686f1c9bd2aaae633f97e48dac60ac82c9f7c68 Mon Sep 17 00:00:00 2001
From: "W. Trevor King"
Date: Fri, 18 Jan 2013 17:49:09 -0500
Subject: [PATCH 0056/1756] setup.py: Update trove classifiers to Python 2.7,
3.2, and 3.3
---
setup.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/setup.py b/setup.py
index 13afd8cf..4c791008 100755
--- a/setup.py
+++ b/setup.py
@@ -41,8 +41,10 @@ setup(
'Environment :: Console',
'License :: OSI Approved :: GNU Affero General Public License v3',
'Operating System :: OS Independent',
- 'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.2',
+ 'Programming Language :: Python :: 3.3',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Software Development :: Libraries :: Python Modules',
],
From 5466ffd61476205feb2a9a7fef737714b12ebf74 Mon Sep 17 00:00:00 2001
From: "W. Trevor King"
Date: Fri, 18 Jan 2013 19:45:39 -0500
Subject: [PATCH 0057/1756] .travis.yml: Test on Python 3.3 as well as 2.7 and
3.2
If we advertise 3.3 support in setup.py, we should test for it.
---
.travis.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.travis.yml b/.travis.yml
index 7ccdafd5..20f6b57e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,6 +2,7 @@ language: python
python:
- "2.7"
- "3.2"
+ - "3.3"
before_install:
- sudo apt-get update -qq
- sudo apt-get install -qq ruby-sass
From 763244dad53404a4ec126beca92d3f30d1325af2 Mon Sep 17 00:00:00 2001
From: "W. Trevor King"
Date: Fri, 18 Jan 2013 19:46:58 -0500
Subject: [PATCH 0058/1756] tox.ini: Test on Python 3.3 as well as 2.7 and 3.2
I just copied the 3.2 environment for 3.3. They should be similar,
but it's possible some packages have extra issues on 3.3.
---
tox.ini | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/tox.ini b/tox.ini
index bc6578ac..6d1a134a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -31,7 +31,7 @@
[tox]
-envlist = py27,py32
+envlist = py27,py32,py33
[testenv]
commands =
@@ -61,3 +61,15 @@ deps =
# {distshare}/smartypants-1.6.0.3.zip
# {distshare}/typogrify-2.0.0.zip
# {distshare}/webassets-0.8.dev.zip
+
+[testenv:py33]
+deps =
+ nose
+ unittest2py3k
+ mock
+ Markdown
+ BeautifulSoup4
+ feedgenerator
+# {distshare}/smartypants-1.6.0.3.zip
+# {distshare}/typogrify-2.0.0.zip
+# {distshare}/webassets-0.8.dev.zip
From b01ddbb29c3f6fb1de1794f20e19630cc8810fda Mon Sep 17 00:00:00 2001
From: Bruno Binet
Date: Sat, 19 Jan 2013 16:44:12 +0100
Subject: [PATCH 0059/1756] fix python 3.3 travis build
unittest2py3k is requiredfor python 3.3 (instead of unittest2)
---
.travis.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.travis.yml b/.travis.yml
index 20f6b57e..6048335f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,7 +8,7 @@ before_install:
- sudo apt-get install -qq ruby-sass
install:
- pip install nose mock --use-mirrors
- - if [[ $TRAVIS_PYTHON_VERSION == '3.2' ]]; then pip install --use-mirrors unittest2py3k; else pip install --use-mirrors unittest2; fi
+ - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --use-mirrors unittest2; else pip install --use-mirrors unittest2py3k; fi
- pip install . --use-mirrors
- pip install Markdown
- pip install webassets
From 67a67846aaa248c4e79f74fd6f42f8f47e641437 Mon Sep 17 00:00:00 2001
From: Bruno Binet
Date: Sat, 19 Jan 2013 17:10:12 +0100
Subject: [PATCH 0060/1756] temporary deactivate travis tests for Python 3.3
the functional tests currently fail on Python 3.3 because the feeds output
differs. (see #688)
this is a temporary fix that will make travis happy while we provide a valid
fix for this issue.
---
.travis.yml | 1 -
1 file changed, 1 deletion(-)
diff --git a/.travis.yml b/.travis.yml
index 6048335f..8b292101 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,7 +2,6 @@ language: python
python:
- "2.7"
- "3.2"
- - "3.3"
before_install:
- sudo apt-get update -qq
- sudo apt-get install -qq ruby-sass
From 0a6294a2a3bbd0d1447e1dc1e05b5e4fc2f633d1 Mon Sep 17 00:00:00 2001
From: Eric
Date: Sun, 20 Jan 2013 17:27:28 -0600
Subject: [PATCH 0061/1756] More clear example for related posts
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The existing related posts example doesn't show properties for the collection, this could be confusing to individuals who are not programmers, because as it was the code would render five bullet points.
I am also looking to add some type of loop control or if statement to the loop to detect duplicate, I tried t a dictionary sort filter through jinja, bu that through an error, the following is not guarantted to work then:
{% set LASTARTICLE = "notset" %}
{% if article.related_posts %}
{% for related_post in article.related_posts|dictsort(false, 'url') %}
{% if not (LASTARTICLE == related_post.url) %}
{% endif %}
the dicsort does not work (is this not a dict, I'm not a python guy so I'm just hacking at it.
---
docs/plugins.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/plugins.rst b/docs/plugins.rst
index 7e09810b..ca9a0dde 100644
--- a/docs/plugins.rst
+++ b/docs/plugins.rst
@@ -291,7 +291,7 @@ For example::
{% if article.related_posts %}
{% endif %}
From 688dee2dca7fdd60bf94af72d928d912f8a5ddbb Mon Sep 17 00:00:00 2001
From: Simon
Date: Tue, 18 Dec 2012 14:03:05 +0100
Subject: [PATCH 0062/1756] Improve importer documentation.
---
docs/importer.rst | 54 +++++++++++++++++++++++++----------------------
1 file changed, 29 insertions(+), 25 deletions(-)
diff --git a/docs/importer.rst b/docs/importer.rst
index c9fa3be0..7ca3142d 100644
--- a/docs/importer.rst
+++ b/docs/importer.rst
@@ -4,54 +4,56 @@
Import from other blog software
=================================
+
Description
===========
-``pelican-import`` is a command line tool for converting articles from other
-software to ReStructuredText. The supported formats are:
+``pelican-import`` is a command-line tool for converting articles from other
+software to ReStructuredText or Markdown. The supported import formats are:
- WordPress XML export
- Dotclear export
- RSS/Atom feed
-The conversion from HTML to reStructuredText relies on `pandoc
-`_. For Dotclear, if the source posts are
-written with Markdown syntax, they will not be converted (as Pelican also
-supports Markdown).
+The conversion from HTML to reStructuredText or Markdown relies on `Pandoc`_.
+For Dotclear, if the source posts are written with Markdown syntax, they will
+not be converted (as Pelican also supports Markdown).
+
Dependencies
-""""""""""""
+============
-``pelican-import`` has two dependencies not required by the rest of pelican:
+``pelican-import`` has some dependencies not required by the rest of pelican:
-- Beautiful Soup
-- pandoc
+- *BeautifulSoup*, for WordPress and Dotclear import. Can be installed like
+ any other Python package (``pip install BeautifulSoup``).
+- *Feedparser*, for feed import (``pip install feedparser``).
+- *Pandoc*, see the `Pandoc site`_ for installation instructions on your
+ operating system.
-Beautiful Soup can be installed like any other Python package::
-
- $ pip install BeautifulSoup
-
-For pandoc, install a package for your operating system from the
-`pandoc site `_.
+.. _Pandoc: http://johnmacfarlane.net/pandoc/
+.. _Pandoc site: http://johnmacfarlane.net/pandoc/installing.html
Usage
-"""""
+=====
-| pelican-import [-h] [--wpfile] [--dotclear] [--feed] [-o OUTPUT]
-| [-m MARKUP] [--dir-cat] [--strip-raw] [--disable-slugs]
-| input
+::
+
+ pelican-import [-h] [--wpfile] [--dotclear] [--feed] [-o OUTPUT]
+ [-m MARKUP] [--dir-cat] [--strip-raw] [--disable-slugs]
+ input
Positional arguments
-====================
+--------------------
input The input file to read
Optional arguments
-""""""""""""""""""
+------------------
- -h, --help show this help message and exit
- --wpfile Wordpress XML export (default: False)
+ -h, --help Show this help message and exit
+ --wpfile WordPress XML export (default: False)
--dotclear Dotclear export (default: False)
--feed Feed to parse (default: False)
-o OUTPUT, --output OUTPUT
@@ -69,6 +71,7 @@ Optional arguments
be consistent with your original posts. (default:
False)
+
Examples
========
@@ -80,10 +83,11 @@ For Dotclear::
$ pelican-import --dotclear -o ~/output ~/backup.txt
+
Tests
=====
To test the module, one can use sample files:
-- for Wordpress: http://wpcandy.com/made/the-sample-post-collection
+- for WordPress: http://wpcandy.com/made/the-sample-post-collection
- for Dotclear: http://themes.dotaddict.org/files/public/downloads/lorem-backup.txt
From 56a276d92e709b87a7f6f078e2e5fe93ad429cc5 Mon Sep 17 00:00:00 2001
From: Michael Reneer
Date: Mon, 10 Dec 2012 23:32:35 -0500
Subject: [PATCH 0063/1756] Added unit test to test the markdown file
extension.
---
.../content/article_with_markdown_extension.markdown | 10 ++++++++++
tests/test_readers.py | 11 +++++++++++
2 files changed, 21 insertions(+)
create mode 100644 tests/content/article_with_markdown_extension.markdown
diff --git a/tests/content/article_with_markdown_extension.markdown b/tests/content/article_with_markdown_extension.markdown
new file mode 100644
index 00000000..94e92871
--- /dev/null
+++ b/tests/content/article_with_markdown_extension.markdown
@@ -0,0 +1,10 @@
+title: Test markdown File
+category: test
+
+Test Markdown File Header
+=========================
+
+Used for pelican test
+---------------------
+
+This is another markdown test file. Uses the markdown extension.
diff --git a/tests/test_readers.py b/tests/test_readers.py
index 75e664d5..474b1b4b 100644
--- a/tests/test_readers.py
+++ b/tests/test_readers.py
@@ -143,6 +143,17 @@ class MdReaderTest(unittest.TestCase):
self.assertEqual(content, expected)
+ @unittest.skipUnless(readers.Markdown, "markdown isn't installed")
+ def test_article_with_mkd_extension(self):
+ # test to ensure the markdown extension is being processed by the correct reader
+ reader = readers.MarkdownReader({})
+ content, metadata = reader.read(_filename('article_with_markdown_extension.markdown'))
+ expected = "
Test Markdown File Header
\n"\
+ "
Used for pelican test
\n"\
+ "
This is another markdown test file. Uses the markdown extension.
"
+
+ self.assertEqual(content, expected)
+
@unittest.skipUnless(readers.Markdown, "markdown isn't installed")
def test_article_with_markdown_markup_extension(self):
# test to ensure the markdown markup extension is being processed as expected
From a441596b072ee124ca5720b211dc2605880c279e Mon Sep 17 00:00:00 2001
From: Michael Reneer
Date: Tue, 11 Dec 2012 10:23:43 -0500
Subject: [PATCH 0064/1756] Cleaned up markdown test cases:
- test_article_with_md_extension
- test_article_with_mkd_extension
- test_article_with_markdown_extension
and replaced with:
- test_article_with_metadata
- test_article_with_file_extensions
---
tests/content/article_with_md_extension.md | 7 ++-
tests/test_readers.py | 51 ++++++++++++----------
2 files changed, 32 insertions(+), 26 deletions(-)
diff --git a/tests/content/article_with_md_extension.md b/tests/content/article_with_md_extension.md
index 11aa22a2..1f111796 100644
--- a/tests/content/article_with_md_extension.md
+++ b/tests/content/article_with_md_extension.md
@@ -1,5 +1,8 @@
-title: Test md File
-category: test
+Title: Test md File
+Category: test
+Tags: foo, bar, foobar
+Date: 2010-12-02 10:14
+Summary: I have a lot to test
Test Markdown File Header
=========================
diff --git a/tests/test_readers.py b/tests/test_readers.py
index 474b1b4b..f7cf71d9 100644
--- a/tests/test_readers.py
+++ b/tests/test_readers.py
@@ -115,43 +115,46 @@ class RstReaderTest(unittest.TestCase):
class MdReaderTest(unittest.TestCase):
@unittest.skipUnless(readers.Markdown, "markdown isn't installed")
- def test_article_with_md_extension(self):
- # test to ensure the md extension is being processed by the correct reader
+ def test_article_with_metadata(self):
reader = readers.MarkdownReader({})
- content, metadata = reader.read(_path('article_with_md_extension.md'))
- expected = "
Test Markdown File Header
\n"\
- "
Used for pelican test
\n"\
- "
The quick brown fox jumped over the lazy dog's back.
',
+ 'date': datetime.datetime(2010, 12, 2, 10, 14),
+ 'tags': ['foo', 'bar', 'foobar'],
}
for key, value in metadata.items():
self.assertEquals(value, expected[key], key)
@unittest.skipUnless(readers.Markdown, "markdown isn't installed")
- def test_article_with_mkd_extension(self):
- # test to ensure the mkd extension is being processed by the correct reader
+ def test_article_with_file_extensions(self):
reader = readers.MarkdownReader({})
- content, metadata = reader.read(_path('article_with_mkd_extension.mkd'))
+ # test to ensure the md file extension is being processed by the
+ # correct reader
+ content, metadata = reader.read(
+ _path('article_with_md_extension.md'))
expected = "
Test Markdown File Header
\n"\
"
Used for pelican test
\n"\
- "
This is another markdown test file. Uses the mkd extension.
"
-
+ "
The quick brown fox jumped over the lazy dog's back.
"
self.assertEqual(content, expected)
-
- @unittest.skipUnless(readers.Markdown, "markdown isn't installed")
- def test_article_with_mkd_extension(self):
- # test to ensure the markdown extension is being processed by the correct reader
- reader = readers.MarkdownReader({})
- content, metadata = reader.read(_filename('article_with_markdown_extension.markdown'))
- expected = "
Test Markdown File Header
\n"\
- "
Used for pelican test
\n"\
- "
This is another markdown test file. Uses the markdown extension.
"
-
+ # test to ensure the mkd file extension is being processed by the
+ # correct reader
+ content, metadata = reader.read(
+ _path('article_with_mkd_extension.mkd'))
+ expected = "
Test Markdown File Header
\n
Used for pelican"\
+ " test
\n
This is another markdown test file. Uses"\
+ " the mkd extension.
"
+ self.assertEqual(content, expected)
+ # test to ensure the markdown file extension is being processed by the
+ # correct reader
+ content, metadata = reader.read(
+ _path('article_with_markdown_extension.markdown'))
+ expected = "
Test Markdown File Header
\n
Used for pelican"\
+ " test
\n
This is another markdown test file. Uses"\
+ " the markdown extension.
"
self.assertEqual(content, expected)
@unittest.skipUnless(readers.Markdown, "markdown isn't installed")
From 0288bf1f68095e5be48c3ebb065a6b47ffe7e13a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C4=B1dvan=20=C3=96rsvuran?=
Date: Wed, 19 Dec 2012 16:26:00 +0200
Subject: [PATCH 0065/1756] added IGNORE_FILES setting for autoreload
---
pelican/__init__.py | 5 +++--
pelican/settings.py | 3 ++-
pelican/utils.py | 7 ++++---
3 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/pelican/__init__.py b/pelican/__init__.py
index 566cb3b0..a4d61b73 100644
--- a/pelican/__init__.py
+++ b/pelican/__init__.py
@@ -44,6 +44,7 @@ class Pelican(object):
self.theme = settings['THEME']
self.output_path = settings['OUTPUT_PATH']
self.markup = settings['MARKUP']
+ self.ignore_files = settings['IGNORE_FILES']
self.delete_outputdir = settings['DELETE_OUTPUT_DIRECTORY']
self.init_path()
@@ -292,8 +293,8 @@ def main():
# restriction; all files are recursively checked if they
# have changed, no matter what extension the filenames
# have.
- if files_changed(pelican.path, pelican.markup) or \
- files_changed(pelican.theme, ['']):
+ if files_changed(pelican.path, pelican.markup, pelican.ignore_files) or \
+ files_changed(pelican.theme, [''], pelican.ignore_files):
if not files_found_error:
files_found_error = True
pelican.run()
diff --git a/pelican/settings.py b/pelican/settings.py
index e4e0501d..8d6c1608 100644
--- a/pelican/settings.py
+++ b/pelican/settings.py
@@ -80,7 +80,8 @@ _DEFAULT_CONFIG = {'PATH': '.',
'TYPOGRIFY': False,
'SUMMARY_MAX_LENGTH': 50,
'PLUGINS': [],
- 'TEMPLATE_PAGES': {}
+ 'TEMPLATE_PAGES': {},
+ 'IGNORE_FILES': []
}
diff --git a/pelican/utils.py b/pelican/utils.py
index edf5068b..cfbc3c23 100644
--- a/pelican/utils.py
+++ b/pelican/utils.py
@@ -10,6 +10,7 @@ import traceback
import logging
import errno
import locale
+import fnmatch
from collections import defaultdict, Hashable
from functools import partial
@@ -406,8 +407,7 @@ def process_translations(content_list):
LAST_MTIME = 0
-
-def files_changed(path, extensions):
+def files_changed(path, extensions, ignores=[]):
"""Return True if the files have changed since the last check"""
def file_times(path):
@@ -415,7 +415,8 @@ def files_changed(path, extensions):
for root, dirs, files in os.walk(path):
dirs[:] = [x for x in dirs if x[0] != '.']
for f in files:
- if any(f.endswith(ext) for ext in extensions):
+ if any(f.endswith(ext) for ext in extensions) \
+ and not any(fnmatch.fnmatch(f, ignore) for ignore in ignores):
yield os.stat(os.path.join(root, f)).st_mtime
global LAST_MTIME
From 5f3a3e4582f84cf4407875dff945a3d42f79b368 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Beno=C3=AEt=20HERVIER?=
Date: Thu, 24 Jan 2013 14:10:26 +0100
Subject: [PATCH 0066/1756] Update pelican/contents.py
Put a space in error message to not concatain word 'about' and the missing tag
---
pelican/contents.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pelican/contents.py b/pelican/contents.py
index 5bde3c0a..d7d7e558 100644
--- a/pelican/contents.py
+++ b/pelican/contents.py
@@ -344,6 +344,6 @@ def is_valid_content(content, f):
content.check_properties()
return True
except NameError as e:
- logger.error("Skipping %s: impossible to find informations about"
+ logger.error("Skipping %s: impossible to find informations about "
"'%s'" % (f, e))
return False
From 357f3a3da211cffeda1501e1c8fb54dc069694f6 Mon Sep 17 00:00:00 2001
From: dave mankoff
Date: Thu, 21 Jun 2012 09:05:27 -0400
Subject: [PATCH 0067/1756] properly write out charref's
---
pelican/readers.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pelican/readers.py b/pelican/readers.py
index 6fe8e894..de3df66f 100644
--- a/pelican/readers.py
+++ b/pelican/readers.py
@@ -235,7 +235,7 @@ class HTMLReader(Reader):
self._data_buffer += '&{};'.format(data)
def handle_charref(self, data):
- self._data_buffer += '&{};'.format(data)
+ self._data_buffer += '{};'.format(data)
def build_tag(self, tag, attrs, close_tag):
result = '<{}'.format(cgi.escape(tag))
From 5f639b9a3b79213d5fd631216888af71990723c2 Mon Sep 17 00:00:00 2001
From: dave mankoff
Date: Mon, 28 Jan 2013 21:46:54 -0500
Subject: [PATCH 0068/1756] git rebase master
---
pelican/readers.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/pelican/readers.py b/pelican/readers.py
index de3df66f..60fabe82 100644
--- a/pelican/readers.py
+++ b/pelican/readers.py
@@ -257,8 +257,8 @@ class HTMLReader(Reader):
return next((x[1] for x in attrs if x[0] == name), default)
def read(self, filename):
- """Parse content and metadata of markdown files"""
- with pelican_open(filename) as content:
+ """Parse content and metadata of HTML files"""
+ with open(filename) as content:
parser = self._HTMLParser(self.settings)
parser.feed(content)
parser.close()
From bf6f16e3839be680296c8325922606410bb86d8a Mon Sep 17 00:00:00 2001
From: dave mankoff
Date: Mon, 9 Jul 2012 22:43:51 -0400
Subject: [PATCH 0069/1756] add documentation for html reader
---
docs/getting_started.rst | 36 ++++++++++++++++++++++++++++++++++++
docs/internals.rst | 4 ++--
2 files changed, 38 insertions(+), 2 deletions(-)
diff --git a/docs/getting_started.rst b/docs/getting_started.rst
index 0952c7d9..7592a5ef 100644
--- a/docs/getting_started.rst
+++ b/docs/getting_started.rst
@@ -190,6 +190,42 @@ syntax for Markdown posts should follow this pattern::
This is the content of my super blog post.
+Lastly, you can use Vanilla HTML (files ending in ``.htm`` and ``.html``). Pelican
+interprets the HTML in a very straightforward manner, reading meta data out
+of ``meta`` tags, the title out of the ``title`` tag, and the body out of the
+``body`` tag::
+
+
+
+ My super title
+
+
+
+
+
+
+ This is the content of my super blog post.
+
+ Content continues down here.
+
+
+
+With HTML, there are two simple exceptions to the standard metadata. First,
+``tags`` can be specified either with the ``tags`` metadata, as is standard in
+Pelican, or with the ``keywords`` metadata, as is standard in HTML. The two can
+be used interchangeably. The second note is that summaries are done differently
+in HTML posts. Either a ``summary`` metadata tag can be supplied, or, as seen
+above, you can place an HTML comment, ````, that
+Pelican will recognize. Everything before the comment will be treated as a
+summary. The content of the post will contain everything in the body tag, with
+the special comment stripped out.
+
+Note that, aside from the title, none of this metadata is mandatory: if the date
+is not specified, Pelican will rely on the file's "mtime" timestamp, and the
+category can be determined by the directory in which the file resides. For
+example, a file located at ``python/foobar/myfoobar.rst`` will have a category of
+``foobar``.
+
Note that, aside from the title, none of this metadata is mandatory: if the
date is not specified, Pelican can rely on the file's "mtime" timestamp through
the ``DEFAULT_DATE`` setting, and the category can be determined by the
diff --git a/docs/internals.rst b/docs/internals.rst
index cadd300b..704122ba 100644
--- a/docs/internals.rst
+++ b/docs/internals.rst
@@ -23,8 +23,8 @@ The logic is separated into different classes and concepts:
on. Since those operations are commonly used, the object is created once and
then passed to the generators.
-* **Readers** are used to read from various formats (AsciiDoc, Markdown and
- reStructuredText for now, but the system is extensible). Given a file, they
+* **Readers** are used to read from various formats (AsciiDoc, HTML, Markdown and
+ reStructuredText for now, but the system is extensible). Given a file, they
return metadata (author, tags, category, etc.) and content (HTML-formatted).
* **Generators** generate the different outputs. For instance, Pelican comes with
From e6a4fe3fc40f003ad9ecba183f12a2fdc6a5adeb Mon Sep 17 00:00:00 2001
From: dave mankoff
Date: Mon, 9 Jul 2012 22:45:34 -0400
Subject: [PATCH 0070/1756] fix grammar
---
docs/getting_started.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/getting_started.rst b/docs/getting_started.rst
index 7592a5ef..c7f2e257 100644
--- a/docs/getting_started.rst
+++ b/docs/getting_started.rst
@@ -190,7 +190,7 @@ syntax for Markdown posts should follow this pattern::
This is the content of my super blog post.
-Lastly, you can use Vanilla HTML (files ending in ``.htm`` and ``.html``). Pelican
+Lastly, you can use vanilla HTML (files ending in ``.htm`` and ``.html``). Pelican
interprets the HTML in a very straightforward manner, reading meta data out
of ``meta`` tags, the title out of the ``title`` tag, and the body out of the
``body`` tag::
From 7b59b34a73560b3eeb6c737a5e8ce2e5b9c4c36b Mon Sep 17 00:00:00 2001
From: dave mankoff
Date: Mon, 28 Jan 2013 22:11:06 -0500
Subject: [PATCH 0071/1756] get tests passing
---
pelican/readers.py | 10 ++++-----
tests/content/article_with_comments.html | 7 ++++---
tests/content/article_with_metadata.html | 2 +-
tests/test_readers.py | 26 +++++++-----------------
4 files changed, 17 insertions(+), 28 deletions(-)
diff --git a/pelican/readers.py b/pelican/readers.py
index 60fabe82..9b8be192 100644
--- a/pelican/readers.py
+++ b/pelican/readers.py
@@ -223,10 +223,10 @@ class HTMLReader(Reader):
self._data_buffer += self.build_tag(tag, attrs, True)
def handle_comment(self, data):
- if self._in_body and data.strip() == 'PELICAN_END_SUMMARY':
- self.metadata['summary'] = self._data_buffer
- else:
- self._data_buffer += ''.format(data)
+ # if self._in_body and data.strip() == 'PELICAN_END_SUMMARY':
+ # self.metadata['summary'] = self._data_buffer
+ # else:
+ self._data_buffer += ''.format(data)
def handle_data(self, data):
self._data_buffer += data
@@ -258,7 +258,7 @@ class HTMLReader(Reader):
def read(self, filename):
"""Parse content and metadata of HTML files"""
- with open(filename) as content:
+ with pelican_open(filename) as content:
parser = self._HTMLParser(self.settings)
parser.feed(content)
parser.close()
diff --git a/tests/content/article_with_comments.html b/tests/content/article_with_comments.html
index f222682d..289e4a66 100644
--- a/tests/content/article_with_comments.html
+++ b/tests/content/article_with_comments.html
@@ -1,7 +1,8 @@
+
+
- Summary comment is not included.
-
-
+ Body content
+
diff --git a/tests/content/article_with_metadata.html b/tests/content/article_with_metadata.html
index 2bd77241..b108ac8a 100644
--- a/tests/content/article_with_metadata.html
+++ b/tests/content/article_with_metadata.html
@@ -5,11 +5,11 @@
+
Multi-line metadata should be supported
as well as inline markup.
-