mirror of
https://github.com/getpelican/pelican.git
synced 2025-10-15 20:28:56 +02:00
Merge pull request #1581 from georgevreilly/win-fixes
Fix Pelican rendering and unit tests on Windows.
This commit is contained in:
commit
bfbb7d4bb5
14 changed files with 104 additions and 52 deletions
|
|
@ -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::
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -62,9 +62,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
|
||||
----------
|
||||
|
|
@ -118,6 +124,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
|
||||
==================================
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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__)
|
||||
|
||||
|
|
|
|||
|
|
@ -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,22 +69,22 @@ 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}',
|
||||
'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}'),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,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')
|
||||
|
|
@ -294,12 +294,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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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'])
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -598,7 +598,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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue