Fix Pelican rendering and unit tests on Windows.

* Fix {filename} links on Windows.
  Otherwise '{filename}/foo/bar.jpg' doesn't work
* Clean up relative Posix path handling in contents.
* Use Posix paths in readers
* Environment for Popen must be strs, not unicodes.
* Ignore Git CRLF warnings.
* Replace CRLFs with LFs in inputs on Windows.
* Fix importer tests
* Fix test_contents
* Fix one last backslash in paginated output
* Skip the remaining failing locale tests on Windows.
* Document the use of forward slashes on Windows.
* Add some Fabric and ghp-import notes
This commit is contained in:
George V. Reilly 2015-01-02 23:45:44 -08:00
commit 4c25610cd8
14 changed files with 104 additions and 52 deletions

View file

@ -157,6 +157,9 @@ the other content will be placed after site generation).
To link to internal content (files in the ``content`` directory), use the
following syntax for the link target: ``{filename}path/to/file``
Note: forward slashes, ``/``,
are the required path separator in the ``{filename}`` directive
on all operating systems, including Windows.
For example, a Pelican project might be structured like this::

View file

@ -100,6 +100,18 @@ separately. Use the following command to install Fabric, prefixing with
pip install Fabric
.. note:: Installing PyCrypto on Windows
Fabric depends upon PyCrypto_, which is tricky to install
if your system doesn't have a C compiler.
For Windows users, before installing Fabric, use
``easy_install http://www.voidspace.org.uk/downloads/pycrypto26/pycrypto-2.6.win32-py2.7.exe``
per this `StackOverflow suggestion <http://stackoverflow.com/a/11405769/6364>`_
You're more likely to have success
with the Win32 versions of Python 2.7 and PyCrypto,
than with the Win64—\
even if your operating system is a 64-bit version of Windows.
Take a moment to open the ``fabfile.py`` file that was generated in your
project root. You will see a number of commands, any one of which can be
renamed, removed, and/or customized to your liking. Using the out-of-the-box
@ -179,3 +191,4 @@ executables, such as ``python3``, you can set the ``PY`` and ``PELICAN``
environment variables, respectively, to override the default executable names.)
.. _Fabric: http://fabfile.org/
.. _PyCrypto: http://pycrypto.org

View file

@ -36,9 +36,15 @@ already exist). The ``git push origin gh-pages`` command updates the remote
``gh-pages`` branch, effectively publishing the Pelican site.
.. note::
The ``github`` target of the Makefile (and the ``gh_pages`` task of the Fabfile)
created by the ``pelican-quickstart`` command
publishes the Pelican site as Project Pages, as described above.
The ``github`` target of the Makefile created by the ``pelican-quickstart``
command publishes the Pelican site as Project Pages, as described above.
.. note:: ghp-import on Windows
Until `ghp-import Pull Request #25 <https://github.com/davisp/ghp-import/pull/25>`_
is accepted, you will need to install a custom build of ghp-import:
``pip install https://github.com/chevah/ghp-import/archive/win-support.zip``
User Pages
----------
@ -86,6 +92,12 @@ output directory. For example::
STATIC_PATHS = ['images', 'extra/CNAME']
EXTRA_PATH_METADATA = {'extra/CNAME': {'path': 'CNAME'},}
Note: use forward slashes, ``/``, even on Windows.
.. hint::
You can also use the ``EXTRA_PATH_METADATA`` mechanism
to place a ``favicon.ico`` or ``robots.txt`` at the root of any site.
How to add YouTube or Vimeo Videos
==================================

View file

@ -17,7 +17,7 @@ from pelican import signals
from pelican.settings import DEFAULT_CONFIG
from pelican.utils import (slugify, truncate_html_words, memoized, strftime,
python_2_unicode_compatible, deprecated_attribute,
path_to_url, set_date_tzinfo, SafeDatetime)
path_to_url, posixize_path, set_date_tzinfo, SafeDatetime)
# Import these so that they're avalaible when you import from pelican.contents.
from pelican.urlwrappers import (URLWrapper, Author, Category, Tag) # NOQA
@ -337,17 +337,19 @@ class Content(object):
if source_path is None:
return None
return os.path.relpath(
os.path.abspath(os.path.join(self.settings['PATH'], source_path)),
os.path.abspath(self.settings['PATH'])
)
return posixize_path(
os.path.relpath(
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.source_path),
os.path.abspath(self.settings['PATH']))
)
return posixize_path(
os.path.dirname(
os.path.relpath(
os.path.abspath(self.source_path),
os.path.abspath(self.settings['PATH']))))
class Page(Content):

View file

@ -136,7 +136,7 @@ class Page(object):
# URL or SAVE_AS is a string, format it with a controlled context
context = {
'name': self.name,
'name': self.name.replace(os.sep, '/'),
'object_list': self.object_list,
'number': self.number,
'paginator': self.paginator,

View file

@ -25,7 +25,7 @@ from six.moves.html_parser import HTMLParser
from pelican import signals
from pelican.contents import Page, Category, Tag, Author
from pelican.utils import get_date, pelican_open, FileStampDataCacher, SafeDatetime
from pelican.utils import get_date, pelican_open, FileStampDataCacher, SafeDatetime, posixize_path
METADATA_PROCESSORS = {
@ -424,7 +424,7 @@ class Readers(FileStampDataCacher):
"""Return a content object parsed with the given format."""
path = os.path.abspath(os.path.join(base_path, path))
source_path = os.path.relpath(path, base_path)
source_path = posixize_path(os.path.relpath(path, base_path))
logger.debug('Read file %s -> %s',
source_path, content_class.__name__)

View file

@ -18,6 +18,7 @@ except ImportError:
load_source = imp.load_source
from os.path import isabs
from pelican.utils import posix_join
from pelican.log import LimitFilter
@ -41,11 +42,11 @@ DEFAULT_CONFIG = {
'STATIC_EXCLUDE_SOURCES': True,
'THEME_STATIC_DIR': 'theme',
'THEME_STATIC_PATHS': ['static', ],
'FEED_ALL_ATOM': os.path.join('feeds', 'all.atom.xml'),
'CATEGORY_FEED_ATOM': os.path.join('feeds', '%s.atom.xml'),
'AUTHOR_FEED_ATOM': os.path.join('feeds', '%s.atom.xml'),
'AUTHOR_FEED_RSS': os.path.join('feeds', '%s.rss.xml'),
'TRANSLATION_FEED_ATOM': os.path.join('feeds', 'all-%s.atom.xml'),
'FEED_ALL_ATOM': posix_join('feeds', 'all.atom.xml'),
'CATEGORY_FEED_ATOM': posix_join('feeds', '%s.atom.xml'),
'AUTHOR_FEED_ATOM': posix_join('feeds', '%s.atom.xml'),
'AUTHOR_FEED_RSS': posix_join('feeds', '%s.rss.xml'),
'TRANSLATION_FEED_ATOM': posix_join('feeds', 'all-%s.atom.xml'),
'FEED_MAX_ITEMS': '',
'SITEURL': '',
'SITENAME': 'A Pelican Blog',
@ -68,25 +69,25 @@ DEFAULT_CONFIG = {
'ARTICLE_LANG_URL': '{slug}-{lang}.html',
'ARTICLE_LANG_SAVE_AS': '{slug}-{lang}.html',
'DRAFT_URL': 'drafts/{slug}.html',
'DRAFT_SAVE_AS': os.path.join('drafts', '{slug}.html'),
'DRAFT_SAVE_AS': posix_join('drafts', '{slug}.html'),
'DRAFT_LANG_URL': 'drafts/{slug}-{lang}.html',
'DRAFT_LANG_SAVE_AS': os.path.join('drafts', '{slug}-{lang}.html'),
'DRAFT_LANG_SAVE_AS': posix_join('drafts', '{slug}-{lang}.html'),
'PAGE_URL': 'pages/{slug}.html',
'PAGE_SAVE_AS': os.path.join('pages', '{slug}.html'),
'PAGE_SAVE_AS': posix_join('pages', '{slug}.html'),
'PAGE_ORDER_BY': 'basename',
'PAGE_LANG_URL': 'pages/{slug}-{lang}.html',
'PAGE_LANG_SAVE_AS': os.path.join('pages', '{slug}-{lang}.html'),
'PAGE_LANG_SAVE_AS': posix_join('pages', '{slug}-{lang}.html'),
'STATIC_URL': '{path}',
'STATIC_SAVE_AS': '{path}',
'PDF_GENERATOR': False,
'PDF_STYLE_PATH': '',
'PDF_STYLE': 'twelvepoint',
'CATEGORY_URL': 'category/{slug}.html',
'CATEGORY_SAVE_AS': os.path.join('category', '{slug}.html'),
'CATEGORY_SAVE_AS': posix_join('category', '{slug}.html'),
'TAG_URL': 'tag/{slug}.html',
'TAG_SAVE_AS': os.path.join('tag', '{slug}.html'),
'TAG_SAVE_AS': posix_join('tag', '{slug}.html'),
'AUTHOR_URL': 'author/{slug}.html',
'AUTHOR_SAVE_AS': os.path.join('author', '{slug}.html'),
'AUTHOR_SAVE_AS': posix_join('author', '{slug}.html'),
'PAGINATION_PATTERNS': [
(0, '{name}{number}{extension}', '{name}{number}{extension}'),
],

View file

@ -10,7 +10,7 @@ from pelican.tests.support import unittest, get_settings
from pelican.contents import Page, Article, Static, URLWrapper
from pelican.settings import DEFAULT_CONFIG
from pelican.utils import path_to_url, truncate_html_words, SafeDatetime
from pelican.utils import path_to_url, truncate_html_words, SafeDatetime, posix_join
from pelican.signals import content_object_init
from jinja2.utils import generate_lorem_ipsum
@ -417,7 +417,7 @@ class TestStatic(unittest.TestCase):
self.context = self.settings.copy()
self.static = Static(content=None, metadata={}, settings=self.settings,
source_path=os.path.join('dir', 'foo.jpg'), context=self.context)
source_path=posix_join('dir', 'foo.jpg'), context=self.context)
self.context['filenames'] = {self.static.source_path: self.static}

View file

@ -9,7 +9,7 @@ from pelican.tools.pelican_import import wp2fields, fields2pelican, decode_wp_co
from pelican.tests.support import (unittest, temporary_folder, mute,
skipIfNoExecutable)
from pelican.utils import slugify
from pelican.utils import slugify, path_to_file_url
CUR_DIR = os.path.abspath(os.path.dirname(__file__))
WORDPRESS_XML_SAMPLE = os.path.join(CUR_DIR, 'content', 'wordpressexport.xml')
@ -293,12 +293,11 @@ class TestWordpressXMLAttachements(unittest.TestCase):
def test_download_attachments(self):
real_file = os.path.join(CUR_DIR, 'content/article.rst')
good_url = 'file://' + real_file
good_url = path_to_file_url(real_file)
bad_url = 'http://localhost:1/not_a_file.txt'
silent_da = mute()(download_attachments)
with temporary_folder() as temp:
#locations = download_attachments(temp, [good_url, bad_url])
locations = list(silent_da(temp, [good_url, bad_url]))
self.assertTrue(len(locations) == 1)
self.assertEqual(1, len(locations))
directory = locations[0]
self.assertTrue(directory.endswith('content/article.rst'))
self.assertTrue(directory.endswith(os.path.join('content', 'article.rst')), directory)

View file

@ -58,22 +58,22 @@ class TestPelican(LoggedTestCase):
locale.setlocale(locale.LC_ALL, self.old_locale)
super(TestPelican, self).tearDown()
def assertFilesEqual(self, diff):
msg = ("some generated files differ from the expected functional "
"tests output.\n"
"This is probably because the HTML generated files "
"changed. If these changes are normal, please refer "
"to docs/contribute.rst to update the expected "
"output of the functional tests.")
self.assertEqual(diff['left_only'], [], msg=msg)
self.assertEqual(diff['right_only'], [], msg=msg)
self.assertEqual(diff['diff_files'], [], msg=msg)
def assertDirsEqual(self, left_path, right_path):
out, err = subprocess.Popen(
['git', 'diff', '--no-ext-diff', '--exit-code', '-w', left_path, right_path], env={'PAGER': ''},
stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
['git', 'diff', '--no-ext-diff', '--exit-code', '-w', left_path, right_path],
env={b'PAGER': b''}, stdout=subprocess.PIPE, stderr=subprocess.PIPE
).communicate()
def ignorable_git_crlf_errors(line):
# Work around for running tests on Windows
for msg in [
"LF will be replaced by CRLF",
"The file will have its original line endings"]:
if msg in line:
return True
return False
if err:
err = '\n'.join([l for l in err.decode('utf8').splitlines()
if not ignorable_git_crlf_errors(l)])
assert not out, out
assert not err, err

View file

@ -3,6 +3,7 @@ from __future__ import unicode_literals, print_function
import copy
import os
import locale
from sys import platform
from os.path import dirname, abspath, join
from pelican.settings import (read_settings, configure_settings,
@ -107,6 +108,8 @@ class TestSettingsConfiguration(unittest.TestCase):
# locale is not specified in the settings
#reset locale to python default
if platform == 'win32':
return unittest.skip("Doesn't work on Windows")
locale.setlocale(locale.LC_ALL, str('C'))
self.assertEqual(self.settings['LOCALE'], DEFAULT_CONFIG['LOCALE'])

View file

@ -301,7 +301,7 @@ class TestUtils(LoggedTestCase):
old_locale = locale.setlocale(locale.LC_TIME)
if platform == 'win32':
locale.setlocale(locale.LC_TIME, str('Turkish'))
return unittest.skip("Doesn't work on Windows")
else:
locale.setlocale(locale.LC_TIME, str('tr_TR.UTF-8'))
@ -471,6 +471,8 @@ class TestDateFormatter(unittest.TestCase):
locale_available('French'),
'French locale needed')
def test_french_strftime(self):
if platform == 'win32':
return unittest.skip("Doesn't work on Windows")
# This test tries to reproduce an issue that occurred with python3.3 under macos10 only
locale.setlocale(locale.LC_ALL, str('fr_FR.UTF-8'))
date = utils.SafeDatetime(2014,8,14)

View file

@ -588,7 +588,8 @@ def download_attachments(output_path, urls):
filename = path.pop(-1)
localpath = ''
for item in path:
localpath = os.path.join(localpath, item)
if sys.platform != 'win32' or ':' not in item:
localpath = os.path.join(localpath, item)
full_path = os.path.join(output_path, localpath)
if not os.path.exists(full_path):
os.makedirs(full_path)

View file

@ -11,6 +11,7 @@ import os
import pytz
import re
import shutil
import sys
import traceback
import pickle
import hashlib
@ -23,6 +24,7 @@ from functools import partial
from itertools import groupby
from jinja2 import Markup
from operator import attrgetter
from posixpath import join as posix_join
logger = logging.getLogger(__name__)
@ -230,13 +232,15 @@ def get_date(string):
@contextmanager
def pelican_open(filename):
def pelican_open(filename, mode='rb', strip_crs=(sys.platform == 'win32')):
"""Open a file and return its content"""
with codecs.open(filename, encoding='utf-8') as infile:
with codecs.open(filename, mode, encoding='utf-8') as infile:
content = infile.read()
if content[0] == codecs.BOM_UTF8.decode('utf8'):
content = content[1:]
if strip_crs:
content = content.replace('\r\n', '\n')
yield content
@ -370,6 +374,13 @@ def path_to_url(path):
return '/'.join(split_all(path))
def posixize_path(rel_path):
"""Use '/' as path separator, so that source references,
like '{filename}/foo/bar.jpg' or 'extras/favicon.ico',
will work on Windows as well as on Mac and Linux."""
return rel_path.replace(os.sep, '/')
def truncate_html_words(s, num, end_text='...'):
"""Truncates HTML to a certain number of words.
@ -750,4 +761,9 @@ def is_selected_for_writing(settings, path):
return path in settings['WRITE_SELECTED']
else:
return True
def path_to_file_url(path):
'''Convert file-system path to file:// URL'''
return six.moves.urllib_parse.urljoin(
"file://", six.moves.urllib.request.pathname2url(path))