This commit is contained in:
Wladislaw Merezhko 2012-09-07 08:01:05 +03:00
commit 1ca2a77c05
20 changed files with 438 additions and 101 deletions

View file

@ -52,7 +52,7 @@ If you want to see new features in Pelican, don't hesitate to offer
suggestions, clone the repository, etc. There are many ways to contribute_.
That's open source, dude!
Send a message to "alexis at notmyidea dot org" with any requests/feedback! You
Send a message to "authors at getpelican dot com" with any requests/feedback! You
can also join the team at `#pelican on Freenode`_ (or if you don't have an IRC
client handy, use the webchat_ for quick feedback.

View file

@ -76,7 +76,7 @@ include the following at the top of the article::
That meta-data can then be accessed in the template::
{% if article.modified %}
Last modified: {{ article.modified}}
Last modified: {{ article.modified }}
{% endif %}
How do I assign custom templates on a per-page basis?

View file

@ -186,7 +186,7 @@ the content. The ``pelican`` command can also be run directly::
$ pelican /path/to/your/content/ [-s path/to/your/settings.py]
The above command will generate your weblog and save it in the ``content/``
The above command will generate your weblog and save it in the ``output/``
folder, using the default theme to produce a simple site. The default theme is
simple HTML without styling and is provided so folks may use it as a basis for
creating their own themes.
@ -271,19 +271,21 @@ Pelican is able to provide colorized syntax highlighting for your code blocks.
To do so, you have to use the following conventions (you need to put this in
your content files).
For RestructuredText::
For RestructuredText, use the code-block directive::
.. code-block:: identifier
your code goes here
<indented code block goes here>
For Markdown, format your code blocks thusly::
For Markdown, include the language identifier just above code blocks::
:::identifier
your code goes here
:::identifier
<code goes here>
(indent both the identifier and code)
The specified identifier should be one that appears on the
`list of available lexers <http://pygments.org/docs/lexers/>`_.
The specified identifier (e.g. ``python``, ``ruby``) should be one that
appears on the `list of available lexers <http://pygments.org/docs/lexers/>`_.
Publishing drafts
-----------------

View file

@ -44,7 +44,7 @@ If you want to see new features in Pelican, don't hesitate to offer suggestions,
clone the repository, etc. There are many ways to :doc:`contribute<contribute>`.
That's open source, dude!
Send a message to "alexis at notmyidea dot org" with any requests/feedback! You
Send a message to "authors at getpelican dot com" with any requests/feedback! You
can also join the team at `#pelican on Freenode`_ (or if you don't have an IRC
client handy, use the webchat_ for quick feedback.

View file

@ -59,6 +59,9 @@ Signal Arguments Description
initialized pelican object
article_generate_context article_generator, metadata
article_generator_init article_generator invoked in the ArticlesGenerator.__init__
get_generators generators invoked in Pelican.get_generator_classes,
can return a Generator, or several
generator in a tuple or in a list.
pages_generate_context pages_generator, metadata
pages_generator_init pages_generator invoked in the PagesGenerator.__init__
========================= ============================ =========================================
@ -108,3 +111,79 @@ variable, as in the example::
``github_activity`` is a list of lists. The first element is the title
and the second element is the raw HTML from GitHub.
Sitemap
-------
The plugin generates a sitemap of the blog.
It can generates plain text sitemaps or XML sitemaps.
Configuration
"""""""""""""
You can use the setting ``SITEMAP`` variable to configure the behavior of the
plugin.
The ``SITEMAP`` variable must be a Python dictionary, it can contain tree keys:
- ``format``, which set the output format of the plugin (``xml`` or ``txt``)
- ``priorities``, which is a dictionary with three keys:
- ``articles``, the priority for the URLs of the articles and their
translations
- ``pages``, the priority for the URLs of the static pages
- ``indexes``, the priority for the URLs of the index pages, such as tags,
author pages, categories indexes, archives, etc...
All the values of this dictionary must be decimal numbers between ``0`` and ``1``.
- ``changefreqs``, which is a dictionary with three items:
- ``articles``, the update frequency of the articles
- ``pages``, the update frequency of the pages
- ``indexes``, the update frequency of the index pages
An valid value is ``always``, ``hourly``, ``daily``, ``weekly``, ``monthly``,
``yearly`` or ``never``.
If a key is missing or a value is incorrect, it will be replaced with the
default value.
The sitemap is saved in ``<output_path>/sitemap.<format>``.
.. note::
``priorities`` and ``changefreqs`` are informations for search engines.
They are only used in the XML sitemaps.
For more information: <http://www.sitemaps.org/protocol.html#xmlTagDefinitions>
Example
"""""""
Here is an example of configuration (it's also the default settings):
.. code-block:: python
PLUGINS=['pelican.plugins.sitemap',]
SITEMAP = {
'format': 'xml',
'priorities': {
'articles': 0.5,
'indexes': 0.5,
'pages': 0.5
},
'changefreqs': {
'articles': 'monthly',
'indexes': 'daily',
'pages': 'monthly'
}
}

View file

@ -73,7 +73,7 @@ Setting name (default value) What doe
`SITENAME` (``'A Pelican Blog'``) Your site name
`SITEURL` Base URL of your website. Not defined by default,
so it is best to specify your SITEURL; if you do not, feeds
will not be generated with properly-formed URLs. You should
will not be generated with properly-formed URLs. You should
include ``http://`` and your domain, with no trailing
slash at the end. Example: ``SITEURL = 'http://mydomain.com'``
`STATIC_PATHS` (``['images']``) The static paths you want to have accessible
@ -95,12 +95,12 @@ Setting name (default value) What doe
index pages for collections of content e.g. tags and
category index pages.
`PAGINATED_DIRECT_TEMPLATES` (``('index',)``) Provides the direct templates that should be paginated.
`SUMMARY_MAX_LENGTH` (``50``) When creating a short summary of an article, this will
`SUMMARY_MAX_LENGTH` (``50``) When creating a short summary of an article, this will
be the default length in words of the text created.
This only applies if your content does not otherwise
specify a summary. Setting to None will cause the summary
This only applies if your content does not otherwise
specify a summary. Setting to None will cause the summary
to be a copy of the original content.
===================================================================== =====================================================================
.. [#] Default is the system locale.
@ -367,7 +367,7 @@ Ordering content
================================================ =====================================================
Setting name (default value) What does it do?
================================================ =====================================================
`NEWEST_FIRST_ARCHIVES` (``True``) Order archives by newest first by date. (False:
`NEWEST_FIRST_ARCHIVES` (``True``) Order archives by newest first by date. (False:
orders by date with older articles first.)
`REVERSE_CATEGORY_ORDER` (``False``) Reverse the category order. (True: lists by reverse
alphabetical order; default lists alphabetically.)
@ -468,7 +468,7 @@ template tag, for example:
.. code-block:: jinja
{% assets filters="cssmin", output="css/style.min.css", "css/inuit.css", "css/pygment-monokai.css", "css/main.css" %}
<link rel="stylesheet" href="{{ ASSETS_URL }}">
<link rel="stylesheet" href="{{ ASSET_URL }}">
{% endassets %}
will produce a minified css file with the version identifier:
@ -477,6 +477,15 @@ will produce a minified css file with the version identifier:
<link href="http://{SITEURL}/theme/css/style.min.css?b3a7c807" rel="stylesheet">
The filters can be combined, for example to use the `sass` compiler and minify
the output::
.. code-block:: jinja
{% assets filters="sass,cssmin", output="css/style.min.css", "css/style.scss" %}
<link rel="stylesheet" href="{{ ASSET_URL }}">
{% endassets %}
Another example for javascript:
.. code-block:: jinja
@ -491,6 +500,12 @@ will produce a minified and gzipped js file:
<script src="http://{SITEURL}/theme/js/packed.js?00703b9d"></script>
Pelican's debug mode is propagated to webassets to disable asset packaging,
and instead work with the uncompressed assets. However, this also means that
the `less` and `sass` files are not compiled, this should be fixed in a future
version of webassets (cf. the related `bug report
<https://github.com/getpelican/pelican/issues/481>`_).
.. _webassets: https://github.com/miracle2k/webassets
.. _documentation: http://webassets.readthedocs.org/en/latest/builtin_filters.html

View file

@ -1,3 +1,4 @@
import copy
import os
import re
import sys
@ -7,11 +8,11 @@ import argparse
from pelican import signals
from pelican.generators import (ArticlesGenerator, PagesGenerator,
from pelican.generators import (Generator, ArticlesGenerator, PagesGenerator,
StaticGenerator, PdfGenerator, LessCSSGenerator)
from pelican.log import init
from pelican.settings import read_settings, _DEFAULT_CONFIG
from pelican.utils import clean_output_dir, files_changed, file_changed
from pelican.utils import clean_output_dir, files_changed, file_changed, NoFilesError
from pelican.writers import Writer
__major__ = 3
@ -29,7 +30,7 @@ class Pelican(object):
before doing anything else.
"""
if settings is None:
settings = _DEFAULT_CONFIG
settings = copy.deepcopy(_DEFAULT_CONFIG)
self.path = path or settings['PATH']
if not self.path:
@ -184,6 +185,18 @@ class Pelican(object):
generators.append(PdfGenerator)
if self.settings['LESS_GENERATOR']: # can be True or PATH to lessc
generators.append(LessCSSGenerator)
for pair in signals.get_generators.send(self):
(funct, value) = pair
if not isinstance(value, (tuple, list)):
value = (value, )
for v in value:
if isinstance(v, type):
logger.debug('Found generator: {0}'.format(v))
generators.append(v)
return generators
def get_writer(self):
@ -265,6 +278,7 @@ def main():
try:
if args.autoreload:
files_found_error = True
while True:
try:
# Check source dir for changed files ending with the given
@ -274,6 +288,8 @@ def main():
# have.
if files_changed(pelican.path, pelican.markup) or \
files_changed(pelican.theme, ['']):
if files_found_error == False:
files_found_error = True
pelican.run()
# reload also if settings.py changed
@ -287,6 +303,10 @@ def main():
except KeyboardInterrupt:
logger.warning("Keyboard interrupt, quitting.")
break
except NoFilesError:
if files_found_error == True:
logger.warning("No valid files found in content. Nothing to generate.")
files_found_error = False
except Exception, e:
logger.warning(
"Caught exception \"{}\". Reloading.".format(e)

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
import copy
import locale
import logging
import functools
@ -29,7 +30,7 @@ class Page(object):
if not metadata:
metadata = {}
if not settings:
settings = _DEFAULT_CONFIG
settings = copy.deepcopy(_DEFAULT_CONFIG)
self.settings = settings
self._content = content

208
pelican/plugins/sitemap.py Normal file
View file

@ -0,0 +1,208 @@
import os.path
from datetime import datetime
from logging import debug, warning, error, info
from codecs import open
from pelican import signals, contents
TXT_HEADER = u"""{0}/index.html
{0}/archives.html
{0}/tags.html
{0}/categories.html
"""
XML_HEADER = u"""<?xml version="1.0" encoding="utf-8"?>
<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>{0}/index.html</loc>
<lastmod>{1}</lastmod>
<changefreq>{2}</changefreq>
<priority>{3}</priority>
</url>
<url>
<loc>{0}/archives.html</loc>
<lastmod>{1}</lastmod>
<changefreq>{2}</changefreq>
<priority>{3}</priority>
</url>
<url>
<loc>{0}/tags.html</loc>
<lastmod>{1}</lastmod>
<changefreq>{2}</changefreq>
<priority>{3}</priority>
</url>
<url>
<loc>{0}/categories.html</loc>
<lastmod>{1}</lastmod>
<changefreq>{2}</changefreq>
<priority>{3}</priority>
</url>
"""
XML_URL = u"""
<url>
<loc>{0}/{1}</loc>
<lastmod>{2}</lastmod>
<changefreq>{3}</changefreq>
<priority>{4}</priority>
</url>
"""
XML_FOOTER = u"""
</urlset>
"""
def format_date(date):
if date.tzinfo:
tz = date.strftime('%s')
tz = tz[:-2] + ':' + tz[-2:]
else:
tz = "-00:00"
return date.strftime("%Y-%m-%dT%H:%M:%S") + tz
class SitemapGenerator(object):
def __init__(self, context, settings, path, theme, output_path, *null):
self.output_path = output_path
self.context = context
self.now = datetime.now()
self.siteurl = settings.get('SITEURL')
self.format = 'xml'
self.changefreqs = {
'articles': 'monthly',
'indexes': 'daily',
'pages': 'monthly'
}
self.priorities = {
'articles': 0.5,
'indexes': 0.5,
'pages': 0.5
}
config = settings.get('SITEMAP', {})
if not isinstance(config, dict):
warning("sitemap plugin: the SITEMAP setting must be a dict")
else:
fmt = config.get('format')
pris = config.get('priorities')
chfreqs = config.get('changefreqs')
if fmt not in ('xml', 'txt'):
warning("sitemap plugin: SITEMAP['format'] must be `txt' or `xml'")
warning("sitemap plugin: Setting SITEMAP['format'] on `xml'")
elif fmt == 'txt':
self.format = fmt
return
valid_keys = ('articles', 'indexes', 'pages')
valid_chfreqs = ('always', 'hourly', 'daily', 'weekly', 'monthly',
'yearly', 'never')
if isinstance(pris, dict):
for k, v in pris.iteritems():
if k in valid_keys and not isinstance(v, (int, float)):
default = self.priorities[k]
warning("sitemap plugin: priorities must be numbers")
warning("sitemap plugin: setting SITEMAP['priorities']"
"['{0}'] on {1}".format(k, default))
pris[k] = default
self.priorities.update(pris)
elif pris is not None:
warning("sitemap plugin: SITEMAP['priorities'] must be a dict")
warning("sitemap plugin: using the default values")
if isinstance(chfreqs, dict):
for k, v in chfreqs.iteritems():
if k in valid_keys and v not in valid_chfreqs:
default = self.changefreqs[k]
warning("sitemap plugin: invalid changefreq `{0}'".format(v))
warning("sitemap plugin: setting SITEMAP['changefreqs']"
"['{0}'] on '{1}'".format(k, default))
chfreqs[k] = default
self.changefreqs.update(chfreqs)
elif chfreqs is not None:
warning("sitemap plugin: SITEMAP['changefreqs'] must be a dict")
warning("sitemap plugin: using the default values")
def write_url(self, page, fd):
if getattr(page, 'status', 'published') != 'published':
return
lastmod = format_date(getattr(page, 'date', self.now))
if isinstance(page, contents.Article):
pri = self.priorities['articles']
chfreq = self.changefreqs['articles']
elif isinstance(page, contents.Page):
pri = self.priorities['pages']
chfreq = self.changefreqs['pages']
else:
pri = self.priorities['indexes']
chfreq = self.changefreqs['indexes']
if self.format == 'xml':
fd.write(XML_URL.format(self.siteurl, page.url, lastmod, chfreq, pri))
else:
fd.write(self.siteurl + '/' + loc + '\n')
def generate_output(self, writer):
path = os.path.join(self.output_path, 'sitemap.{0}'.format(self.format))
pages = self.context['pages'] + self.context['articles'] \
+ [ c for (c, a) in self.context['categories']] \
+ [ t for (t, a) in self.context['tags']] \
+ [ a for (a, b) in self.context['authors']]
for article in self.context['articles']:
pages += article.translations
info('writing {0}'.format(path))
with open(path, 'w', encoding='utf-8') as fd:
if self.format == 'xml':
fd.write(XML_HEADER.format(
self.siteurl,
format_date(self.now),
self.changefreqs['indexes'],
self.priorities['indexes']
)
)
else:
fd.write(TXT_HEADER.format(self.siteurl))
for page in pages:
self.write_url(page, fd)
if self.format == 'xml':
fd.write(XML_FOOTER)
def get_generators(generators):
return SitemapGenerator
def register():
signals.get_generators.connect(get_generators)

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
import copy
import imp
import inspect
import os
@ -81,7 +82,7 @@ def read_settings(filename=None):
if filename:
local_settings = get_settings_from_file(filename)
else:
local_settings = _DEFAULT_CONFIG
local_settings = copy.deepcopy(_DEFAULT_CONFIG)
configured_settings = configure_settings(local_settings, None, filename)
return configured_settings
@ -89,10 +90,9 @@ def read_settings(filename=None):
def get_settings_from_module(module=None, default_settings=_DEFAULT_CONFIG):
"""
Load settings from a module, returning a dict.
"""
context = default_settings.copy()
context = copy.deepcopy(default_settings)
if module is not None:
context.update(
(k, v) for k, v in inspect.getmembers(module) if k.isupper()
@ -114,7 +114,7 @@ def get_settings_from_file(filename, default_settings=_DEFAULT_CONFIG):
def configure_settings(settings, default_settings=None, filename=None):
"""Provide optimizations, error checking, and warnings for loaded settings"""
if default_settings is None:
default_settings = _DEFAULT_CONFIG
default_settings = copy.deepcopy(_DEFAULT_CONFIG)
# Make the paths relative to the settings file
if filename:
@ -138,7 +138,7 @@ def configure_settings(settings, default_settings=None, filename=None):
for locale_ in locales:
try:
locale.setlocale(locale.LC_ALL, locale_)
break # break if it is successfull
break # break if it is successful
except locale.Error:
pass
else:

View file

@ -3,5 +3,6 @@ from blinker import signal
initialized = signal('pelican_initialized')
article_generate_context = signal('article_generate_context')
article_generator_init = signal('article_generator_init')
get_generators = signal('get_generators')
pages_generate_context = signal('pages_generate_context')
pages_generator_init = signal('pages_generator_init')

View file

@ -308,7 +308,8 @@ img.left, figure.left {float: left; margin: 0 2em 2em 0;}
.social a[type$='atom+xml'], .social a[type$='rss+xml'] {background-image: url('../images/icons/rss.png');}
.social a[href*='twitter.com'] {background-image: url('../images/icons/twitter.png');}
.social a[href*='linkedin.com'] {background-image: url('../images/icons/linkedin.png');}
.social a[href*='gitorious.org'] {background-image: url('../images/icons/gitorious.org');}
.social a[href*='gitorious.org'] {background-image: url('../images/icons/gitorious.png');}
.social a[href*='gittip.com'] {background-image: url('../images/icons/gittip.png');}
/*
About

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

View file

@ -1,7 +1,7 @@
PELICAN=$pelican
PELICANOPTS=$pelicanopts
BASEDIR=$$(PWD)
BASEDIR=$$(CURDIR)
INPUTDIR=$$(BASEDIR)/content
OUTPUTDIR=$$(BASEDIR)/output
CONFFILE=$$(BASEDIR)/pelicanconf.py

View file

@ -1,11 +1,11 @@
#!/bin/bash
#!/usr/bin/env bash
##
# This section should match your Makefile
##
PELICAN=$pelican
PELICANOPTS=$pelicanopts
BASEDIR=$$(PWD)
BASEDIR=$$(pwd)
INPUTDIR=$$BASEDIR/content
OUTPUTDIR=$$BASEDIR/output
CONFFILE=$$BASEDIR/pelicanconf.py
@ -65,6 +65,7 @@ function start_up(){
python -m SimpleHTTPServer &
echo $$! > $$SRV_PID
cd $$BASEDIR
sleep 1 && echo 'Pelican and SimpleHTTPServer processes now running in background.'
}
###

View file

@ -14,6 +14,9 @@ from operator import attrgetter
logger = logging.getLogger(__name__)
class NoFilesError(Exception):
pass
def get_date(string):
"""Return a datetime object from a string.
@ -241,10 +244,13 @@ def files_changed(path, extensions):
yield os.stat(os.path.join(root, f)).st_mtime
global LAST_MTIME
mtime = max(file_times(path))
if mtime > LAST_MTIME:
LAST_MTIME = mtime
return True
try:
mtime = max(file_times(path))
if mtime > LAST_MTIME:
LAST_MTIME = mtime
return True
except ValueError:
raise NoFilesError("No files with the given extension(s) found.")
return False

View file

@ -63,29 +63,26 @@ class TestArticlesGenerator(unittest.TestCase):
def test_generate_context(self):
settings = _DEFAULT_CONFIG.copy()
settings['ARTICLE_DIR'] = 'content'
settings['DEFAULT_CATEGORY'] = 'Default'
generator = ArticlesGenerator(settings.copy(), settings, CUR_DIR,
_DEFAULT_CONFIG['THEME'], None,
_DEFAULT_CONFIG['MARKUP'])
generator.generate_context()
for article in generator.articles:
relfilepath = os.path.relpath(article.filename, CUR_DIR)
if relfilepath == os.path.join("TestCategory",
"article_with_category.rst"):
self.assertEquals(article.category.name, 'yeah')
elif relfilepath == os.path.join("TestCategory",
"article_without_category.rst"):
self.assertEquals(article.category.name, 'TestCategory')
elif relfilepath == "article_without_category.rst":
self.assertEquals(article.category.name, 'Default')
generator = self.get_populated_generator()
articles = self.distill_articles(generator.articles)
articles_expected = [
[u'Article title', 'published', 'Default', 'article'],
[u'Article with template', 'published', 'Default', 'custom'],
[u'Test md File', 'published', 'test', 'article'],
[u'This is a super article !', 'published', 'Yeah', 'article'],
[u'This is an article with category !', 'published', 'yeah', 'article'],
[u'This is an article without category !', 'published', 'Default', 'article'],
[u'This is an article without category !', 'published', 'TestCategory', 'article'],
[u'This is a super article !', 'published', 'yeah', 'article']
]
self.assertItemsEqual(articles_expected, articles)
def test_generate_categories(self):
generator = self.get_populated_generator()
categories = [cat.name for cat, _ in generator.categories]
# assert that the categories are ordered as expected
self.assertEquals(
categories, ['Default', 'TestCategory', 'Yeah', 'test',
'yeah'])
categories_expected = ['Default', 'TestCategory', 'Yeah', 'test', 'yeah']
self.assertEquals(categories, categories_expected)
def test_direct_templates_save_as_default(self):

View file

@ -35,6 +35,18 @@ class TestPelican(unittest.TestCase):
rmtree(self.temp_path)
locale.setlocale(locale.LC_ALL, self.old_locale)
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)
@unittest.skip("Test failing")
def test_basic_generation_works(self):
# when running pelican without settings, it should pick up the default
@ -47,27 +59,7 @@ class TestPelican(unittest.TestCase):
pelican.run()
diff = dircmp(
self.temp_path, os.sep.join((OUTPUT_PATH, "basic")))
self.assertEqual(diff.left_only, [], msg="some generated " \
"files are absent 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.right_only, [], msg="some files from " \
"the expected functional tests output are absent " \
"from the current 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.diff_files, [], 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.assertFilesEqual(diff)
def test_custom_generation_works(self):
# the same thing with a specified set of settings should work
@ -75,24 +67,4 @@ class TestPelican(unittest.TestCase):
settings=read_settings(SAMPLE_CONFIG))
pelican.run()
diff = dircmp(self.temp_path, os.sep.join((OUTPUT_PATH, "custom")))
self.assertEqual(diff.left_only, [], msg="some generated " \
"files are absent 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.right_only, [], msg="some files from " \
"the expected functional tests output are absent " \
"from the current 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.diff_files, [], 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.assertFilesEqual(diff)

View file

@ -1,6 +1,7 @@
import copy
from os.path import dirname, abspath, join
from pelican.settings import read_settings, configure_settings, _DEFAULT_CONFIG
from pelican.settings import read_settings, configure_settings, _DEFAULT_CONFIG, DEFAULT_THEME
from .support import unittest
@ -31,7 +32,26 @@ class TestSettingsConfiguration(unittest.TestCase):
def test_read_empty_settings(self):
"""providing no file should return the default values."""
settings = read_settings(None)
self.assertDictEqual(settings, _DEFAULT_CONFIG)
expected = copy.deepcopy(_DEFAULT_CONFIG)
expected["FEED_DOMAIN"] = '' #This is added by configure settings
self.maxDiff = None
self.assertDictEqual(settings, expected)
def test_settings_return_independent(self):
"""Make sure that the results from one settings call doesn't
effect past or future instances."""
self.PATH = abspath(dirname(__file__))
default_conf = join(self.PATH, 'default_conf.py')
settings = read_settings(default_conf)
settings['SITEURL'] = 'new-value'
new_settings = read_settings(default_conf)
self.assertNotEqual(new_settings['SITEURL'], settings['SITEURL'])
def test_defaults_not_overwritten(self):
"""This assumes 'SITENAME': 'A Pelican Blog'"""
settings = read_settings(None)
settings['SITENAME'] = 'Not a Pelican Blog'
self.assertNotEqual(settings['SITENAME'], _DEFAULT_CONFIG['SITENAME'])
def test_configure_settings(self):
"""Manipulations to settings should be applied correctly."""

View file

@ -6,6 +6,7 @@ import time
from pelican import utils
from .support import get_article, unittest
from pelican.utils import NoFilesError
class TestUtils(unittest.TestCase):
@ -74,7 +75,8 @@ class TestUtils(unittest.TestCase):
self.assertNotIn(fr_article1, index)
def test_files_changed(self):
"Test if file changes are correctly detected"
"""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')
@ -90,6 +92,18 @@ class TestUtils(unittest.TestCase):
self.assertEquals(changed, True)
self.assertAlmostEqual(utils.LAST_MTIME, t, delta=1)
empty_path = os.path.join(os.path.dirname(__file__), 'empty')
try:
os.mkdir(empty_path)
os.mkdir(os.path.join(empty_path, "empty_folder"))
shutil.copy(__file__, empty_path)
with self.assertRaises(NoFilesError):
utils.files_changed(empty_path, 'rst')
except OSError:
self.fail("OSError Exception in test_files_changed test")
finally:
shutil.rmtree(empty_path, True)
def test_clean_output_dir(self):
test_directory = os.path.join(os.path.dirname(__file__), 'clean_output')
content = os.path.join(os.path.dirname(__file__), 'content')