mirror of
https://github.com/getpelican/pelican.git
synced 2025-10-15 20:28:56 +02:00
Merge pull request #2747 from avaris/github-actions
Add GitHub Actions workflow
This commit is contained in:
commit
177bc2262c
21 changed files with 236 additions and 102 deletions
159
.github/workflows/main.yml
vendored
Normal file
159
.github/workflows/main.yml
vendored
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
name: build
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
env:
|
||||||
|
# color output for pytest and tox
|
||||||
|
PYTEST_ADDOPTS: "--color=yes"
|
||||||
|
PY_COLORS: 1
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: Test - ${{ matrix.config.python }} - ${{ matrix.config.os }}
|
||||||
|
runs-on: ${{ matrix.config.os }}-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
config:
|
||||||
|
- os: ubuntu
|
||||||
|
python: 3.5
|
||||||
|
- os: ubuntu
|
||||||
|
python: 3.6
|
||||||
|
- os: ubuntu
|
||||||
|
python: 3.7
|
||||||
|
- os: ubuntu
|
||||||
|
python: 3.8
|
||||||
|
- os: macos
|
||||||
|
python: 3.7
|
||||||
|
- os: windows
|
||||||
|
python: 3.7
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Setup Python ${{ matrix.config.python }}
|
||||||
|
uses: actions/setup-python@v1.1.1
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.config.python }}
|
||||||
|
- name: Set pip cache (Linux)
|
||||||
|
uses: actions/cache@v1
|
||||||
|
if: startsWith(runner.os, 'Linux')
|
||||||
|
with:
|
||||||
|
path: ~/.cache/pip
|
||||||
|
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements/*') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pip-
|
||||||
|
- name: Setup pip cache (macOS)
|
||||||
|
uses: actions/cache@v1
|
||||||
|
if: startsWith(runner.os, 'macOS')
|
||||||
|
with:
|
||||||
|
path: ~/Library/Caches/pip
|
||||||
|
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements/*') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pip-
|
||||||
|
- name: Setup pip cache (Windows)
|
||||||
|
uses: actions/cache@v1
|
||||||
|
if: startsWith(runner.os, 'Windows')
|
||||||
|
with:
|
||||||
|
path: ~\AppData\Local\pip\Cache
|
||||||
|
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements/*') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pip-
|
||||||
|
- name: Install locale (Linux)
|
||||||
|
if: startsWith(runner.os, 'Linux')
|
||||||
|
run: sudo locale-gen fr_FR.UTF-8 tr_TR.UTF-8
|
||||||
|
- name: Install pandoc
|
||||||
|
uses: r-lib/actions/setup-pandoc@v1
|
||||||
|
with:
|
||||||
|
pandoc-version: "2.9.2"
|
||||||
|
- name: Install tox
|
||||||
|
run: python -m pip install -U pip tox
|
||||||
|
- name: Info
|
||||||
|
run: |
|
||||||
|
echo "===== PYTHON ====="
|
||||||
|
python --version
|
||||||
|
echo "===== PANDOC ====="
|
||||||
|
pandoc --version | head -2
|
||||||
|
- name: Run tests
|
||||||
|
run: tox -e py${{ matrix.config.python }}
|
||||||
|
|
||||||
|
|
||||||
|
lint:
|
||||||
|
name: Lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v1.1.1
|
||||||
|
with:
|
||||||
|
python-version: 3.6
|
||||||
|
- name: Set pip cache (Linux)
|
||||||
|
uses: actions/cache@v1
|
||||||
|
if: startsWith(runner.os, 'Linux')
|
||||||
|
with:
|
||||||
|
path: ~/.cache/pip
|
||||||
|
key: ${{ runner.os }}-pip-${{ hashFiles('requirements/*') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pip-
|
||||||
|
- name: Install tox
|
||||||
|
run: python -m pip install -U pip tox
|
||||||
|
- name: Check
|
||||||
|
run: tox -e flake8
|
||||||
|
|
||||||
|
|
||||||
|
docs:
|
||||||
|
name: Build docs
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v1.1.1
|
||||||
|
with:
|
||||||
|
python-version: 3.6
|
||||||
|
- name: Set pip cache (Linux)
|
||||||
|
uses: actions/cache@v1
|
||||||
|
if: startsWith(runner.os, 'Linux')
|
||||||
|
with:
|
||||||
|
path: ~/.cache/pip
|
||||||
|
key: ${{ runner.os }}-pip-${{ hashFiles('requirements/*') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pip-
|
||||||
|
- name: Install tox
|
||||||
|
run: python -m pip install -U pip tox
|
||||||
|
- name: Check
|
||||||
|
run: tox -e docs
|
||||||
|
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
name: Deploy
|
||||||
|
needs: [test, lint, docs]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.ref=='refs/heads/master' && github.event_name!='pull_request' }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v1.1.1
|
||||||
|
with:
|
||||||
|
python-version: 3.7
|
||||||
|
- name: Check release
|
||||||
|
id: check_release
|
||||||
|
run: |
|
||||||
|
python -m pip install pip --upgrade
|
||||||
|
pip install poetry
|
||||||
|
pip install githubrelease
|
||||||
|
pip install --pre autopub
|
||||||
|
echo "##[set-output name=release;]$(autopub check)"
|
||||||
|
- name: Publish
|
||||||
|
if: ${{ steps.check_release.outputs.release=='' }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
|
PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
git remote set-url origin https://$GITHUB_TOKEN@github.com/${{ github.repository }}
|
||||||
|
autopub prepare
|
||||||
|
poetry build
|
||||||
|
autopub commit
|
||||||
|
autopub githubrelease
|
||||||
|
poetry publish -u __token__ -p $PYPI_PASSWORD
|
||||||
|
|
@ -11,15 +11,15 @@ env:
|
||||||
matrix:
|
matrix:
|
||||||
- TOX_ENV=docs
|
- TOX_ENV=docs
|
||||||
- TOX_ENV=flake8
|
- TOX_ENV=flake8
|
||||||
- TOX_ENV=py35
|
- TOX_ENV=py3.5
|
||||||
- TOX_ENV=py36
|
- TOX_ENV=py3.6
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- python: 3.7
|
- python: 3.7
|
||||||
sudo: true
|
sudo: true
|
||||||
dist: xenial
|
dist: xenial
|
||||||
env:
|
env:
|
||||||
- TOX_ENV=py37
|
- TOX_ENV=py3.7
|
||||||
addons:
|
addons:
|
||||||
apt_packages:
|
apt_packages:
|
||||||
- pandoc
|
- pandoc
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ html_show_sourcelink = False
|
||||||
|
|
||||||
def setup(app):
|
def setup(app):
|
||||||
# overrides for wide tables in RTD theme
|
# overrides for wide tables in RTD theme
|
||||||
app.add_stylesheet('theme_overrides.css') # path relative to _static
|
app.add_css_file('theme_overrides.css') # path relative to _static
|
||||||
|
|
||||||
|
|
||||||
# -- Options for LaTeX output -------------------------------------------------
|
# -- Options for LaTeX output -------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -242,7 +242,7 @@ as the name of the metadata field, except in all-lowercase characters.
|
||||||
For example, you could add a field called `FacebookImage` to your article
|
For example, you could add a field called `FacebookImage` to your article
|
||||||
metadata, as shown below:
|
metadata, as shown below:
|
||||||
|
|
||||||
.. code-block:: markdown
|
.. code-block:: md
|
||||||
|
|
||||||
Title: I love Python more than music
|
Title: I love Python more than music
|
||||||
Date: 2013-11-06 10:06
|
Date: 2013-11-06 10:06
|
||||||
|
|
|
||||||
|
|
@ -696,7 +696,7 @@ def path_metadata(full_path, source_path, settings=None):
|
||||||
# Enforce a trailing slash when checking for parent directories.
|
# Enforce a trailing slash when checking for parent directories.
|
||||||
# This prevents false positives when one file or directory's name
|
# This prevents false positives when one file or directory's name
|
||||||
# is a prefix of another's.
|
# is a prefix of another's.
|
||||||
dirpath = os.path.join(path, '')
|
dirpath = posixize_path(os.path.join(path, ''))
|
||||||
if source_path == path or source_path.startswith(dirpath):
|
if source_path == path or source_path.startswith(dirpath):
|
||||||
metadata.update(meta)
|
metadata.update(meta)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,19 @@ def locale_available(locale_):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def can_symlink():
|
||||||
|
res = True
|
||||||
|
try:
|
||||||
|
with temporary_folder() as f:
|
||||||
|
os.symlink(
|
||||||
|
f,
|
||||||
|
os.path.join(f, 'symlink')
|
||||||
|
)
|
||||||
|
except OSError:
|
||||||
|
res = False
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
def get_settings(**kwargs):
|
def get_settings(**kwargs):
|
||||||
"""Provide tweaked setting dictionaries for testing
|
"""Provide tweaked setting dictionaries for testing
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,11 @@
|
||||||
import os
|
import os
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
from pelican.generators import ArticlesGenerator, PagesGenerator
|
from pelican.generators import ArticlesGenerator, PagesGenerator
|
||||||
from pelican.tests.support import get_context, get_settings, unittest
|
from pelican.tests.support import get_context, get_settings, unittest
|
||||||
|
|
||||||
try:
|
|
||||||
from unittest.mock import MagicMock
|
|
||||||
except ImportError:
|
|
||||||
try:
|
|
||||||
from mock import MagicMock
|
|
||||||
except ImportError:
|
|
||||||
MagicMock = False
|
|
||||||
|
|
||||||
CUR_DIR = os.path.dirname(__file__)
|
CUR_DIR = os.path.dirname(__file__)
|
||||||
CONTENT_DIR = os.path.join(CUR_DIR, 'content')
|
CONTENT_DIR = os.path.join(CUR_DIR, 'content')
|
||||||
|
|
@ -131,7 +125,6 @@ class TestCache(unittest.TestCase):
|
||||||
self.assertEqual(uncached_pages, cached_pages)
|
self.assertEqual(uncached_pages, cached_pages)
|
||||||
self.assertEqual(uncached_hidden_pages, cached_hidden_pages)
|
self.assertEqual(uncached_hidden_pages, cached_hidden_pages)
|
||||||
|
|
||||||
@unittest.skipUnless(MagicMock, 'Needs Mock module')
|
|
||||||
def test_article_object_caching(self):
|
def test_article_object_caching(self):
|
||||||
"""Test Article objects caching at the generator level"""
|
"""Test Article objects caching at the generator level"""
|
||||||
settings = self._get_cache_enabled_settings()
|
settings = self._get_cache_enabled_settings()
|
||||||
|
|
@ -162,7 +155,6 @@ class TestCache(unittest.TestCase):
|
||||||
"""
|
"""
|
||||||
self.assertEqual(generator.readers.read_file.call_count, 6)
|
self.assertEqual(generator.readers.read_file.call_count, 6)
|
||||||
|
|
||||||
@unittest.skipUnless(MagicMock, 'Needs Mock module')
|
|
||||||
def test_article_reader_content_caching(self):
|
def test_article_reader_content_caching(self):
|
||||||
"""Test raw article content caching at the reader level"""
|
"""Test raw article content caching at the reader level"""
|
||||||
settings = self._get_cache_enabled_settings()
|
settings = self._get_cache_enabled_settings()
|
||||||
|
|
@ -185,7 +177,6 @@ class TestCache(unittest.TestCase):
|
||||||
for reader in readers.values():
|
for reader in readers.values():
|
||||||
self.assertEqual(reader.read.call_count, 0)
|
self.assertEqual(reader.read.call_count, 0)
|
||||||
|
|
||||||
@unittest.skipUnless(MagicMock, 'Needs Mock module')
|
|
||||||
def test_article_ignore_cache(self):
|
def test_article_ignore_cache(self):
|
||||||
"""Test that all the articles are read again when not loading cache
|
"""Test that all the articles are read again when not loading cache
|
||||||
|
|
||||||
|
|
@ -212,7 +203,6 @@ class TestCache(unittest.TestCase):
|
||||||
generator.readers.read_file.call_count,
|
generator.readers.read_file.call_count,
|
||||||
orig_call_count)
|
orig_call_count)
|
||||||
|
|
||||||
@unittest.skipUnless(MagicMock, 'Needs Mock module')
|
|
||||||
def test_page_object_caching(self):
|
def test_page_object_caching(self):
|
||||||
"""Test Page objects caching at the generator level"""
|
"""Test Page objects caching at the generator level"""
|
||||||
settings = self._get_cache_enabled_settings()
|
settings = self._get_cache_enabled_settings()
|
||||||
|
|
@ -238,7 +228,6 @@ class TestCache(unittest.TestCase):
|
||||||
"""
|
"""
|
||||||
self.assertEqual(generator.readers.read_file.call_count, 1)
|
self.assertEqual(generator.readers.read_file.call_count, 1)
|
||||||
|
|
||||||
@unittest.skipUnless(MagicMock, 'Needs Mock module')
|
|
||||||
def test_page_reader_content_caching(self):
|
def test_page_reader_content_caching(self):
|
||||||
"""Test raw page content caching at the reader level"""
|
"""Test raw page content caching at the reader level"""
|
||||||
settings = self._get_cache_enabled_settings()
|
settings = self._get_cache_enabled_settings()
|
||||||
|
|
@ -262,7 +251,6 @@ class TestCache(unittest.TestCase):
|
||||||
for reader in readers.values():
|
for reader in readers.values():
|
||||||
self.assertEqual(reader.read.call_count, 0)
|
self.assertEqual(reader.read.call_count, 0)
|
||||||
|
|
||||||
@unittest.skipUnless(MagicMock, 'Needs Mock module')
|
|
||||||
def test_page_ignore_cache(self):
|
def test_page_ignore_cache(self):
|
||||||
"""Test that all the pages are read again when not loading cache
|
"""Test that all the pages are read again when not loading cache
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,17 @@
|
||||||
import locale
|
import locale
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
from shutil import copy, rmtree
|
from shutil import copy, rmtree
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
from pelican.generators import (ArticlesGenerator, Generator, PagesGenerator,
|
from pelican.generators import (ArticlesGenerator, Generator, PagesGenerator,
|
||||||
PelicanTemplateNotFound, StaticGenerator,
|
PelicanTemplateNotFound, StaticGenerator,
|
||||||
TemplatePagesGenerator)
|
TemplatePagesGenerator)
|
||||||
from pelican.tests.support import get_context, get_settings, unittest
|
from pelican.tests.support import (can_symlink, get_context, get_settings,
|
||||||
|
unittest)
|
||||||
from pelican.writers import Writer
|
from pelican.writers import Writer
|
||||||
|
|
||||||
try:
|
|
||||||
from unittest.mock import MagicMock
|
|
||||||
except ImportError:
|
|
||||||
try:
|
|
||||||
from mock import MagicMock
|
|
||||||
except ImportError:
|
|
||||||
MagicMock = False
|
|
||||||
|
|
||||||
|
|
||||||
CUR_DIR = os.path.dirname(__file__)
|
CUR_DIR = os.path.dirname(__file__)
|
||||||
CONTENT_DIR = os.path.join(CUR_DIR, 'content')
|
CONTENT_DIR = os.path.join(CUR_DIR, 'content')
|
||||||
|
|
@ -198,7 +193,6 @@ class TestArticlesGenerator(unittest.TestCase):
|
||||||
return [[article.title, article.status, article.category.name,
|
return [[article.title, article.status, article.category.name,
|
||||||
article.template] for article in articles]
|
article.template] for article in articles]
|
||||||
|
|
||||||
@unittest.skipUnless(MagicMock, 'Needs Mock module')
|
|
||||||
def test_generate_feeds(self):
|
def test_generate_feeds(self):
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
settings['CACHE_PATH'] = self.temp_cache
|
settings['CACHE_PATH'] = self.temp_cache
|
||||||
|
|
@ -218,7 +212,6 @@ class TestArticlesGenerator(unittest.TestCase):
|
||||||
generator.generate_feeds(writer)
|
generator.generate_feeds(writer)
|
||||||
self.assertFalse(writer.write_feed.called)
|
self.assertFalse(writer.write_feed.called)
|
||||||
|
|
||||||
@unittest.skipUnless(MagicMock, 'Needs Mock module')
|
|
||||||
def test_generate_feeds_override_url(self):
|
def test_generate_feeds_override_url(self):
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
settings['CACHE_PATH'] = self.temp_cache
|
settings['CACHE_PATH'] = self.temp_cache
|
||||||
|
|
@ -334,7 +327,6 @@ class TestArticlesGenerator(unittest.TestCase):
|
||||||
categories_expected = ['default', 'yeah', 'test', 'zhi-dao-shu']
|
categories_expected = ['default', 'yeah', 'test', 'zhi-dao-shu']
|
||||||
self.assertEqual(sorted(categories), sorted(categories_expected))
|
self.assertEqual(sorted(categories), sorted(categories_expected))
|
||||||
|
|
||||||
@unittest.skipUnless(MagicMock, 'Needs Mock module')
|
|
||||||
def test_direct_templates_save_as_url_default(self):
|
def test_direct_templates_save_as_url_default(self):
|
||||||
|
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
|
|
@ -352,7 +344,6 @@ class TestArticlesGenerator(unittest.TestCase):
|
||||||
template_name='archives',
|
template_name='archives',
|
||||||
page_name='archives', url="archives.html")
|
page_name='archives', url="archives.html")
|
||||||
|
|
||||||
@unittest.skipUnless(MagicMock, 'Needs Mock module')
|
|
||||||
def test_direct_templates_save_as_url_modified(self):
|
def test_direct_templates_save_as_url_modified(self):
|
||||||
|
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
|
|
@ -373,7 +364,6 @@ class TestArticlesGenerator(unittest.TestCase):
|
||||||
page_name='archives/index',
|
page_name='archives/index',
|
||||||
url="archives/")
|
url="archives/")
|
||||||
|
|
||||||
@unittest.skipUnless(MagicMock, 'Needs Mock module')
|
|
||||||
def test_direct_templates_save_as_false(self):
|
def test_direct_templates_save_as_false(self):
|
||||||
|
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
|
|
@ -398,7 +388,6 @@ class TestArticlesGenerator(unittest.TestCase):
|
||||||
self.assertIn(custom_template, self.articles)
|
self.assertIn(custom_template, self.articles)
|
||||||
self.assertIn(standard_template, self.articles)
|
self.assertIn(standard_template, self.articles)
|
||||||
|
|
||||||
@unittest.skipUnless(MagicMock, 'Needs Mock module')
|
|
||||||
def test_period_in_timeperiod_archive(self):
|
def test_period_in_timeperiod_archive(self):
|
||||||
"""
|
"""
|
||||||
Test that the context of a generated period_archive is passed
|
Test that the context of a generated period_archive is passed
|
||||||
|
|
@ -1022,7 +1011,6 @@ class TestStaticGenerator(unittest.TestCase):
|
||||||
with open(self.endfile) as f:
|
with open(self.endfile) as f:
|
||||||
self.assertEqual(f.read(), "staticcontent")
|
self.assertEqual(f.read(), "staticcontent")
|
||||||
|
|
||||||
@unittest.skipUnless(MagicMock, 'Needs Mock module')
|
|
||||||
def test_file_update_required_when_dest_does_not_exist(self):
|
def test_file_update_required_when_dest_does_not_exist(self):
|
||||||
staticfile = MagicMock()
|
staticfile = MagicMock()
|
||||||
staticfile.source_path = self.startfile
|
staticfile.source_path = self.startfile
|
||||||
|
|
@ -1032,7 +1020,6 @@ class TestStaticGenerator(unittest.TestCase):
|
||||||
update_required = self.generator._file_update_required(staticfile)
|
update_required = self.generator._file_update_required(staticfile)
|
||||||
self.assertTrue(update_required)
|
self.assertTrue(update_required)
|
||||||
|
|
||||||
@unittest.skipUnless(MagicMock, 'Needs Mock module')
|
|
||||||
def test_dest_and_source_mtimes_are_equal(self):
|
def test_dest_and_source_mtimes_are_equal(self):
|
||||||
staticfile = MagicMock()
|
staticfile = MagicMock()
|
||||||
staticfile.source_path = self.startfile
|
staticfile.source_path = self.startfile
|
||||||
|
|
@ -1045,7 +1032,6 @@ class TestStaticGenerator(unittest.TestCase):
|
||||||
isnewer = self.generator._source_is_newer(staticfile)
|
isnewer = self.generator._source_is_newer(staticfile)
|
||||||
self.assertFalse(isnewer)
|
self.assertFalse(isnewer)
|
||||||
|
|
||||||
@unittest.skipUnless(MagicMock, 'Needs Mock module')
|
|
||||||
def test_source_is_newer(self):
|
def test_source_is_newer(self):
|
||||||
staticfile = MagicMock()
|
staticfile = MagicMock()
|
||||||
staticfile.source_path = self.startfile
|
staticfile.source_path = self.startfile
|
||||||
|
|
@ -1097,6 +1083,7 @@ class TestStaticGenerator(unittest.TestCase):
|
||||||
self.generator.generate_output(None)
|
self.generator.generate_output(None)
|
||||||
self.assertTrue(os.path.samefile(self.startfile, self.endfile))
|
self.assertTrue(os.path.samefile(self.startfile, self.endfile))
|
||||||
|
|
||||||
|
@unittest.skipUnless(can_symlink(), 'No symlink privilege')
|
||||||
def test_can_symlink_when_hardlink_not_possible(self):
|
def test_can_symlink_when_hardlink_not_possible(self):
|
||||||
self.settings['STATIC_CREATE_LINKS'] = True
|
self.settings['STATIC_CREATE_LINKS'] = True
|
||||||
with open(self.startfile, "w") as f:
|
with open(self.startfile, "w") as f:
|
||||||
|
|
@ -1104,40 +1091,29 @@ class TestStaticGenerator(unittest.TestCase):
|
||||||
os.mkdir(os.path.join(self.temp_output, "static"))
|
os.mkdir(os.path.join(self.temp_output, "static"))
|
||||||
self.generator.fallback_to_symlinks = True
|
self.generator.fallback_to_symlinks = True
|
||||||
self.generator.generate_context()
|
self.generator.generate_context()
|
||||||
try:
|
self.generator.generate_output(None)
|
||||||
self.generator.generate_output(None)
|
|
||||||
except OSError as e:
|
|
||||||
# On Windows, possibly others, due to not holding symbolic link
|
|
||||||
# privilege
|
|
||||||
self.skipTest(e)
|
|
||||||
self.assertTrue(os.path.islink(self.endfile))
|
self.assertTrue(os.path.islink(self.endfile))
|
||||||
|
|
||||||
|
@unittest.skipUnless(can_symlink(), 'No symlink privilege')
|
||||||
def test_existing_symlink_is_considered_up_to_date(self):
|
def test_existing_symlink_is_considered_up_to_date(self):
|
||||||
self.settings['STATIC_CREATE_LINKS'] = True
|
self.settings['STATIC_CREATE_LINKS'] = True
|
||||||
with open(self.startfile, "w") as f:
|
with open(self.startfile, "w") as f:
|
||||||
f.write("staticcontent")
|
f.write("staticcontent")
|
||||||
os.mkdir(os.path.join(self.temp_output, "static"))
|
os.mkdir(os.path.join(self.temp_output, "static"))
|
||||||
try:
|
os.symlink(self.startfile, self.endfile)
|
||||||
os.symlink(self.startfile, self.endfile)
|
|
||||||
except OSError as e:
|
|
||||||
# On Windows, possibly others
|
|
||||||
self.skipTest(e)
|
|
||||||
staticfile = MagicMock()
|
staticfile = MagicMock()
|
||||||
staticfile.source_path = self.startfile
|
staticfile.source_path = self.startfile
|
||||||
staticfile.save_as = self.endfile
|
staticfile.save_as = self.endfile
|
||||||
requires_update = self.generator._file_update_required(staticfile)
|
requires_update = self.generator._file_update_required(staticfile)
|
||||||
self.assertFalse(requires_update)
|
self.assertFalse(requires_update)
|
||||||
|
|
||||||
|
@unittest.skipUnless(can_symlink(), 'No symlink privilege')
|
||||||
def test_invalid_symlink_is_overwritten(self):
|
def test_invalid_symlink_is_overwritten(self):
|
||||||
self.settings['STATIC_CREATE_LINKS'] = True
|
self.settings['STATIC_CREATE_LINKS'] = True
|
||||||
with open(self.startfile, "w") as f:
|
with open(self.startfile, "w") as f:
|
||||||
f.write("staticcontent")
|
f.write("staticcontent")
|
||||||
os.mkdir(os.path.join(self.temp_output, "static"))
|
os.mkdir(os.path.join(self.temp_output, "static"))
|
||||||
try:
|
os.symlink("invalid", self.endfile)
|
||||||
os.symlink("invalid", self.endfile)
|
|
||||||
except OSError as e:
|
|
||||||
# On Windows, possibly others
|
|
||||||
self.skipTest(e)
|
|
||||||
staticfile = MagicMock()
|
staticfile = MagicMock()
|
||||||
staticfile.source_path = self.startfile
|
staticfile.source_path = self.startfile
|
||||||
staticfile.save_as = self.endfile
|
staticfile.save_as = self.endfile
|
||||||
|
|
@ -1147,8 +1123,18 @@ class TestStaticGenerator(unittest.TestCase):
|
||||||
self.generator.generate_context()
|
self.generator.generate_context()
|
||||||
self.generator.generate_output(None)
|
self.generator.generate_output(None)
|
||||||
self.assertTrue(os.path.islink(self.endfile))
|
self.assertTrue(os.path.islink(self.endfile))
|
||||||
self.assertEqual(os.path.realpath(self.endfile),
|
|
||||||
os.path.realpath(self.startfile))
|
# os.path.realpath is broken on Windows before python3.8 for symlinks.
|
||||||
|
# This is a (ugly) workaround.
|
||||||
|
# see: https://bugs.python.org/issue9949
|
||||||
|
if os.name == 'nt' and sys.version_info < (3, 8):
|
||||||
|
def get_real_path(path):
|
||||||
|
return os.readlink(path) if os.path.islink(path) else path
|
||||||
|
else:
|
||||||
|
get_real_path = os.path.realpath
|
||||||
|
|
||||||
|
self.assertEqual(get_real_path(self.endfile),
|
||||||
|
get_real_path(self.startfile))
|
||||||
|
|
||||||
def test_delete_existing_file_before_mkdir(self):
|
def test_delete_existing_file_before_mkdir(self):
|
||||||
with open(self.startfile, "w") as f:
|
with open(self.startfile, "w") as f:
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import locale
|
import locale
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
from posixpath import join as posix_join
|
||||||
|
|
||||||
from pelican.settings import DEFAULT_CONFIG
|
from pelican.settings import DEFAULT_CONFIG
|
||||||
from pelican.tests.support import (mute, skipIfNoExecutable, temporary_folder,
|
from pelican.tests.support import (mute, skipIfNoExecutable, temporary_folder,
|
||||||
|
|
@ -448,5 +449,5 @@ class TestWordpressXMLAttachements(unittest.TestCase):
|
||||||
self.assertEqual(1, len(locations))
|
self.assertEqual(1, len(locations))
|
||||||
directory = locations[0]
|
directory = locations[0]
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
directory.endswith(os.path.join('content', 'article.rst')),
|
directory.endswith(posix_join('content', 'article.rst')),
|
||||||
directory)
|
directory)
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,10 @@
|
||||||
import os
|
import os
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
from pelican import readers
|
from pelican import readers
|
||||||
from pelican.tests.support import get_settings, unittest
|
from pelican.tests.support import get_settings, unittest
|
||||||
from pelican.utils import SafeDatetime
|
from pelican.utils import SafeDatetime
|
||||||
|
|
||||||
try:
|
|
||||||
from unittest.mock import patch
|
|
||||||
except ImportError:
|
|
||||||
try:
|
|
||||||
from mock import patch
|
|
||||||
except ImportError:
|
|
||||||
patch = False
|
|
||||||
|
|
||||||
CUR_DIR = os.path.dirname(__file__)
|
CUR_DIR = os.path.dirname(__file__)
|
||||||
CONTENT_PATH = os.path.join(CUR_DIR, 'content')
|
CONTENT_PATH = os.path.join(CUR_DIR, 'content')
|
||||||
|
|
@ -125,7 +119,6 @@ class DefaultReaderTest(ReaderTest):
|
||||||
|
|
||||||
self.assertDictHasSubset(page.metadata, expected)
|
self.assertDictHasSubset(page.metadata, expected)
|
||||||
|
|
||||||
@unittest.skipUnless(patch, 'Needs Mock module')
|
|
||||||
def test_find_empty_alt(self):
|
def test_find_empty_alt(self):
|
||||||
with patch('pelican.readers.logger') as log_mock:
|
with patch('pelican.readers.logger') as log_mock:
|
||||||
content = ['<img alt="" src="test-image.png" width="300px" />',
|
content = ['<img alt="" src="test-image.png" width="300px" />',
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,8 @@
|
||||||
|
from unittest.mock import Mock
|
||||||
|
|
||||||
from pelican.tests.support import unittest
|
from pelican.tests.support import unittest
|
||||||
|
|
||||||
try:
|
|
||||||
from unittest.mock import Mock
|
|
||||||
except ImportError:
|
|
||||||
try:
|
|
||||||
from mock import Mock
|
|
||||||
except ImportError:
|
|
||||||
Mock = False
|
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipUnless(Mock, 'Needs Mock module')
|
|
||||||
class Test_abbr_role(unittest.TestCase):
|
class Test_abbr_role(unittest.TestCase):
|
||||||
def call_it(self, text):
|
def call_it(self, text):
|
||||||
from pelican.rstdirectives import abbr_role
|
from pelican.rstdirectives import abbr_role
|
||||||
|
|
|
||||||
|
|
@ -25,11 +25,10 @@ class TestServer(unittest.TestCase):
|
||||||
os.chdir(self.temp_output)
|
os.chdir(self.temp_output)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
rmtree(self.temp_output)
|
|
||||||
os.chdir(self.old_cwd)
|
os.chdir(self.old_cwd)
|
||||||
|
rmtree(self.temp_output)
|
||||||
|
|
||||||
def test_get_path_that_exists(self):
|
def test_get_path_that_exists(self):
|
||||||
|
|
||||||
handler = ComplexHTTPRequestHandler(MockRequest(), ('0.0.0.0', 8888),
|
handler = ComplexHTTPRequestHandler(MockRequest(), ('0.0.0.0', 8888),
|
||||||
self.server)
|
self.server)
|
||||||
handler.base_path = self.temp_output
|
handler.base_path = self.temp_output
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,8 @@ class TestSettingsConfiguration(unittest.TestCase):
|
||||||
settings['ARTICLE_DIR']
|
settings['ARTICLE_DIR']
|
||||||
settings['PAGE_DIR']
|
settings['PAGE_DIR']
|
||||||
|
|
||||||
|
# locale.getdefaultlocale() is broken on Windows
|
||||||
|
# See: https://bugs.python.org/issue37945
|
||||||
@unittest.skipIf(platform == 'win32', "Doesn't work on Windows")
|
@unittest.skipIf(platform == 'win32', "Doesn't work on Windows")
|
||||||
def test_default_encoding(self):
|
def test_default_encoding(self):
|
||||||
# Test that the default locale is set if not specified in settings
|
# Test that the default locale is set if not specified in settings
|
||||||
|
|
|
||||||
|
|
@ -757,35 +757,32 @@ class TestDateFormatter(unittest.TestCase):
|
||||||
|
|
||||||
|
|
||||||
class TestSanitisedJoin(unittest.TestCase):
|
class TestSanitisedJoin(unittest.TestCase):
|
||||||
@unittest.skipIf(platform == 'win32',
|
|
||||||
"Different filesystem root on Windows")
|
|
||||||
def test_detect_parent_breakout(self):
|
def test_detect_parent_breakout(self):
|
||||||
with self.assertRaisesRegex(
|
with self.assertRaisesRegex(
|
||||||
RuntimeError,
|
RuntimeError,
|
||||||
"Attempted to break out of output directory to /foo/test"):
|
"Attempted to break out of output directory to "
|
||||||
|
"(.*?:)?/foo/test"): # (.*?:)? accounts for Windows root
|
||||||
utils.sanitised_join(
|
utils.sanitised_join(
|
||||||
"/foo/bar",
|
"/foo/bar",
|
||||||
"../test"
|
"../test"
|
||||||
)
|
)
|
||||||
|
|
||||||
@unittest.skipIf(platform == 'win32',
|
|
||||||
"Different filesystem root on Windows")
|
|
||||||
def test_detect_root_breakout(self):
|
def test_detect_root_breakout(self):
|
||||||
with self.assertRaisesRegex(
|
with self.assertRaisesRegex(
|
||||||
RuntimeError,
|
RuntimeError,
|
||||||
"Attempted to break out of output directory to /test"):
|
"Attempted to break out of output directory to "
|
||||||
|
"(.*?:)?/test"): # (.*?:)? accounts for Windows root
|
||||||
utils.sanitised_join(
|
utils.sanitised_join(
|
||||||
"/foo/bar",
|
"/foo/bar",
|
||||||
"/test"
|
"/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
@unittest.skipIf(platform == 'win32',
|
|
||||||
"Different filesystem root on Windows")
|
|
||||||
def test_pass_deep_subpaths(self):
|
def test_pass_deep_subpaths(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
utils.sanitised_join(
|
utils.sanitised_join(
|
||||||
"/foo/bar",
|
"/foo/bar",
|
||||||
"test"
|
"test"
|
||||||
),
|
),
|
||||||
os.path.join("/foo/bar", "test")
|
utils.posixize_path(
|
||||||
|
os.path.abspath(os.path.join("/foo/bar", "test")))
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -728,8 +728,9 @@ def download_attachments(output_path, urls):
|
||||||
|
|
||||||
# Generate percent-encoded URL
|
# Generate percent-encoded URL
|
||||||
scheme, netloc, path, query, fragment = urlsplit(url)
|
scheme, netloc, path, query, fragment = urlsplit(url)
|
||||||
path = quote(path)
|
if scheme != 'file':
|
||||||
url = urlunsplit((scheme, netloc, path, query, fragment))
|
path = quote(path)
|
||||||
|
url = urlunsplit((scheme, netloc, path, query, fragment))
|
||||||
|
|
||||||
if not os.path.exists(full_path):
|
if not os.path.exists(full_path):
|
||||||
os.makedirs(full_path)
|
os.makedirs(full_path)
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,10 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def sanitised_join(base_directory, *parts):
|
def sanitised_join(base_directory, *parts):
|
||||||
joined = os.path.abspath(os.path.join(base_directory, *parts))
|
joined = posixize_path(
|
||||||
if not joined.startswith(os.path.abspath(base_directory)):
|
os.path.abspath(os.path.join(base_directory, *parts)))
|
||||||
|
base = posixize_path(os.path.abspath(base_directory))
|
||||||
|
if not joined.startswith(base):
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Attempted to break out of output directory to {}".format(
|
"Attempted to break out of output directory to {}".format(
|
||||||
joined
|
joined
|
||||||
|
|
@ -391,10 +393,9 @@ def get_relative_path(path):
|
||||||
|
|
||||||
def path_to_url(path):
|
def path_to_url(path):
|
||||||
"""Return the URL corresponding to a given path."""
|
"""Return the URL corresponding to a given path."""
|
||||||
if os.sep == '/':
|
if path is not None:
|
||||||
return path
|
path = posixize_path(path)
|
||||||
else:
|
return path
|
||||||
return '/'.join(split_all(path))
|
|
||||||
|
|
||||||
|
|
||||||
def posixize_path(rel_path):
|
def posixize_path(rel_path):
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
from posixpath import join as posix_join
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
from feedgenerator import Atom1Feed, Rss201rev2Feed, get_tag_uri
|
from feedgenerator import Atom1Feed, Rss201rev2Feed, get_tag_uri
|
||||||
|
|
@ -25,7 +26,7 @@ class Writer:
|
||||||
|
|
||||||
# See Content._link_replacer for details
|
# See Content._link_replacer for details
|
||||||
if self.settings['RELATIVE_URLS']:
|
if self.settings['RELATIVE_URLS']:
|
||||||
self.urljoiner = os.path.join
|
self.urljoiner = posix_join
|
||||||
else:
|
else:
|
||||||
self.urljoiner = lambda base, url: urljoin(
|
self.urljoiner = lambda base, url: urljoin(
|
||||||
base if base.endswith('/') else base + '/', url)
|
base if base.endswith('/') else base + '/', url)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
sphinx==1.4.9
|
sphinx
|
||||||
sphinx_rtd_theme
|
sphinx_rtd_theme
|
||||||
livereload
|
livereload
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
# Tests
|
# Tests
|
||||||
Pygments==2.6.1
|
Pygments==2.6.1
|
||||||
mock
|
|
||||||
pytest==5.3.5
|
pytest==5.3.5
|
||||||
pytest-cov
|
pytest-cov
|
||||||
pytest-xdist
|
pytest-xdist
|
||||||
|
|
|
||||||
2
tasks.py
2
tasks.py
|
|
@ -24,7 +24,7 @@ PRECOMMIT = (
|
||||||
@task
|
@task
|
||||||
def docbuild(c):
|
def docbuild(c):
|
||||||
"""Build documentation"""
|
"""Build documentation"""
|
||||||
c.run(f"{VENV_BIN}/sphinx-build docs docs/_build")
|
c.run(f"{VENV_BIN}/sphinx-build -W docs docs/_build")
|
||||||
|
|
||||||
|
|
||||||
@task(docbuild)
|
@task(docbuild)
|
||||||
|
|
|
||||||
13
tox.ini
13
tox.ini
|
|
@ -1,11 +1,12 @@
|
||||||
[tox]
|
[tox]
|
||||||
envlist = py{35,36,37},docs,flake8
|
envlist = py{3.5,3.6,3.7,3.8},docs,flake8
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
basepython =
|
basepython =
|
||||||
py35: python3.5
|
py3.5: python3.5
|
||||||
py36: python3.6
|
py3.6: python3.6
|
||||||
py37: python3.7
|
py3.7: python3.7
|
||||||
|
py3.8: python3.8
|
||||||
passenv = *
|
passenv = *
|
||||||
usedevelop=True
|
usedevelop=True
|
||||||
deps =
|
deps =
|
||||||
|
|
@ -13,7 +14,7 @@ deps =
|
||||||
|
|
||||||
commands =
|
commands =
|
||||||
{envpython} --version
|
{envpython} --version
|
||||||
pytest -sv --cov=pelican pelican
|
pytest -s --cov=pelican pelican
|
||||||
|
|
||||||
[testenv:docs]
|
[testenv:docs]
|
||||||
basepython = python3.6
|
basepython = python3.6
|
||||||
|
|
@ -27,7 +28,7 @@ commands =
|
||||||
filterwarnings =
|
filterwarnings =
|
||||||
default::DeprecationWarning
|
default::DeprecationWarning
|
||||||
error:.*:Warning:pelican
|
error:.*:Warning:pelican
|
||||||
addopts = -n 2
|
addopts = -n 2 -r a
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
application-import-names = pelican
|
application-import-names = pelican
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue