diff --git a/README.rst b/README.rst index 3a62d0ca..018f73ba 100644 --- a/README.rst +++ b/README.rst @@ -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. diff --git a/docs/faq.rst b/docs/faq.rst index 4efefaf2..c5c751e6 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -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? diff --git a/docs/getting_started.rst b/docs/getting_started.rst index 282d6c23..b7cbe951 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -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 + -For Markdown, format your code blocks thusly:: +For Markdown, include the language identifier just above code blocks:: - :::identifier - your code goes here + :::identifier + + + (indent both the identifier and code) -The specified identifier should be one that appears on the -`list of available lexers `_. +The specified identifier (e.g. ``python``, ``ruby``) should be one that +appears on the `list of available lexers `_. Publishing drafts ----------------- diff --git a/docs/index.rst b/docs/index.rst index b04557eb..477b4342 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -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`. 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. diff --git a/docs/plugins.rst b/docs/plugins.rst index 53858668..99c0429a 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -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 ``/sitemap.``. + +.. note:: + ``priorities`` and ``changefreqs`` are informations for search engines. + They are only used in the XML sitemaps. + For more information: + + +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' + } + } diff --git a/docs/settings.rst b/docs/settings.rst index c219ed12..ad08f020 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -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" %} - + {% 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: +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" %} + +{% endassets %} + Another example for javascript: .. code-block:: jinja @@ -491,6 +500,12 @@ will produce a minified and gzipped js file: +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 +`_). + .. _webassets: https://github.com/miracle2k/webassets .. _documentation: http://webassets.readthedocs.org/en/latest/builtin_filters.html diff --git a/pelican/__init__.py b/pelican/__init__.py index ba48c4c7..b9f9bb22 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -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) diff --git a/pelican/contents.py b/pelican/contents.py index a5e3be8f..851607a5 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -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 diff --git a/pelican/plugins/sitemap.py b/pelican/plugins/sitemap.py new file mode 100644 index 00000000..6402ba9c --- /dev/null +++ b/pelican/plugins/sitemap.py @@ -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""" + + + + {0}/index.html + {1} + {2} + {3} + + + + {0}/archives.html + {1} + {2} + {3} + + + + {0}/tags.html + {1} + {2} + {3} + + + + {0}/categories.html + {1} + {2} + {3} + +""" + +XML_URL = u""" + + {0}/{1} + {2} + {3} + {4} + +""" + +XML_FOOTER = u""" + +""" + + +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) diff --git a/pelican/settings.py b/pelican/settings.py index 645a9809..92c68ddc 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -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: diff --git a/pelican/signals.py b/pelican/signals.py index 4d9ab512..7ee88a0a 100644 --- a/pelican/signals.py +++ b/pelican/signals.py @@ -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') diff --git a/pelican/themes/notmyidea/static/css/main.css b/pelican/themes/notmyidea/static/css/main.css index bd1eba75..b4ba7888 100644 --- a/pelican/themes/notmyidea/static/css/main.css +++ b/pelican/themes/notmyidea/static/css/main.css @@ -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 diff --git a/pelican/themes/notmyidea/static/images/icons/gittip.png b/pelican/themes/notmyidea/static/images/icons/gittip.png new file mode 100644 index 00000000..bb12a139 Binary files /dev/null and b/pelican/themes/notmyidea/static/images/icons/gittip.png differ diff --git a/pelican/tools/templates/Makefile.in b/pelican/tools/templates/Makefile.in index 9a26e315..4c5a4fcb 100644 --- a/pelican/tools/templates/Makefile.in +++ b/pelican/tools/templates/Makefile.in @@ -1,7 +1,7 @@ PELICAN=$pelican PELICANOPTS=$pelicanopts -BASEDIR=$$(PWD) +BASEDIR=$$(CURDIR) INPUTDIR=$$(BASEDIR)/content OUTPUTDIR=$$(BASEDIR)/output CONFFILE=$$(BASEDIR)/pelicanconf.py diff --git a/pelican/tools/templates/develop_server.sh.in b/pelican/tools/templates/develop_server.sh.in index 2f8c07dd..3e97610b 100755 --- a/pelican/tools/templates/develop_server.sh.in +++ b/pelican/tools/templates/develop_server.sh.in @@ -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.' } ### diff --git a/pelican/utils.py b/pelican/utils.py index 53e6e52b..ca3015ce 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -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 diff --git a/tests/test_generators.py b/tests/test_generators.py index e984b484..3a4ea1e3 100644 --- a/tests/test_generators.py +++ b/tests/test_generators.py @@ -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): diff --git a/tests/test_pelican.py b/tests/test_pelican.py index 15088ed0..78f083f9 100644 --- a/tests/test_pelican.py +++ b/tests/test_pelican.py @@ -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) diff --git a/tests/test_settings.py b/tests/test_settings.py index 25df74bd..873df824 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -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.""" diff --git a/tests/test_utils.py b/tests/test_utils.py index 2ea756dc..148e322a 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -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')