diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 00000000..8f5dc3a3
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,8 @@
+language: python
+python:
+ - "2.6"
+ - "2.7"
+install:
+ - pip install nose unittest2 mock --use-mirrors
+ - pip install . --use-mirrors
+script: nosetests -s tests
diff --git a/CHANGELOG b/CHANGELOG
index e68c6f0d..46aa68a5 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -3,6 +3,8 @@ X.X
* Refactored the way URL are handled.
* Improved the english documentation
* Fixed packaging using setuptools entrypoints
+* Added typogrify support
+* Added a way to disable feed generation
2.8
diff --git a/README.rst b/README.rst
index 2f66e54c..5012bb9c 100644
--- a/README.rst
+++ b/README.rst
@@ -1,6 +1,8 @@
Pelican
#######
+.. image:: https://secure.travis-ci.org/ametaireau/pelican.png?branch=master
+
Pelican is a simple weblog generator, written in `Python `_.
* Write your weblog entries directly with your editor of choice (vim!)
diff --git a/dev_requirements.txt b/dev_requirements.txt
index 198880ec..c7f53682 100644
--- a/dev_requirements.txt
+++ b/dev_requirements.txt
@@ -4,3 +4,4 @@ docutils
feedgenerator
unittest2
pytz
+mock
diff --git a/docs/settings.rst b/docs/settings.rst
index 69e2adc8..9eb46439 100644
--- a/docs/settings.rst
+++ b/docs/settings.rst
@@ -52,6 +52,10 @@ Setting name (default value) What does it do?
supported extensions.
`OUTPUT_PATH` (``'output/'``) Where to output the generated files.
`PATH` (``None``) Path to look at for input files.
+`PAGE_DIR' (``'pages'``) Directory to look at for pages.
+`PAGE_EXCLUDES' (``()``) A list of directories to exclude when looking for pages.
+`ARTICLE_DIR' (``''``) Directory to look at for articles.
+`ARTICLE_EXCLUDES': (``('pages',)``) A list of directories to exclude when looking for articles.
`PDF_GENERATOR` (``False``) Set to True if you want to have PDF versions
of your documents. You will need to install
`rst2pdf`.
@@ -69,6 +73,11 @@ Setting name (default value) What does it do?
`TIMEZONE` The timezone used in the date information, to
generate Atom and RSS feeds. See the "timezone"
section below for more info.
+`TYPOGRIFY` (``False``) If set to true, some
+ additional transformations will be done on the
+ generated HTML, using the `Typogrify
+ `_
+ library
================================================ =====================================================
.. [#] Default is the system locale.
@@ -199,7 +208,6 @@ Pelican generates category feeds as well as feeds for all your articles. It does
not generate feeds for tags by default, but it is possible to do so using
the ``TAG_FEED`` and ``TAG_FEED_RSS`` settings:
-
================================================ =====================================================
Setting name (default value) What does it do?
================================================ =====================================================
@@ -214,6 +222,9 @@ Setting name (default value) What does it do?
quantity is unrestricted by default.
================================================ =====================================================
+If you don't want to generate some of these feeds, set ``None`` to the
+variables above.
+
.. [2] %s is the name of the category.
Pagination
diff --git a/pelican/__init__.py b/pelican/__init__.py
index 8de68d69..0b53dbcc 100644
--- a/pelican/__init__.py
+++ b/pelican/__init__.py
@@ -6,7 +6,7 @@ import time
from pelican.generators import (ArticlesGenerator, PagesGenerator,
StaticGenerator, PdfGenerator)
-from pelican.settings import read_settings
+from pelican.settings import read_settings, _DEFAULT_CONFIG
from pelican.utils import clean_output_dir, files_changed
from pelican.writers import Writer
from pelican import log
@@ -20,52 +20,22 @@ class Pelican(object):
"""Read the settings, and performs some checks on the environment
before doing anything else.
"""
+ if settings is None:
+ settings = _DEFAULT_CONFIG
+
self.path = path or settings['PATH']
if not self.path:
- raise Exception('you need to specify a path containing the content'
+ raise Exception('You need to specify a path containing the content'
' (see pelican --help for more information)')
if self.path.endswith('/'):
self.path = self.path[:-1]
- if settings.get('CLEAN_URLS', False):
- log.warning('Found deprecated `CLEAN_URLS` in settings. Modifing'
- ' the following settings for the same behaviour.')
-
- settings['ARTICLE_URL'] = '{slug}/'
- settings['ARTICLE_LANG_URL'] = '{slug}-{lang}/'
- settings['PAGE_URL'] = 'pages/{slug}/'
- settings['PAGE_LANG_URL'] = 'pages/{slug}-{lang}/'
-
- for setting in ('ARTICLE_URL', 'ARTICLE_LANG_URL', 'PAGE_URL',
- 'PAGE_LANG_URL'):
- log.warning("%s = '%s'" % (setting, settings[setting]))
-
- if settings.get('ARTICLE_PERMALINK_STRUCTURE', False):
- log.warning('Found deprecated `ARTICLE_PERMALINK_STRUCTURE` in'
- ' settings. Modifing the following settings for'
- ' the same behaviour.')
-
- structure = settings['ARTICLE_PERMALINK_STRUCTURE']
-
- # Convert %(variable) into {variable}.
- structure = re.sub('%\((\w+)\)s', '{\g<1>}', structure)
-
- # Convert %x into {date:%x} for strftime
- structure = re.sub('(%[A-z])', '{date:\g<1>}', structure)
-
- # Strip a / prefix
- structure = re.sub('^/', '', structure)
-
- for setting in ('ARTICLE_URL', 'ARTICLE_LANG_URL', 'PAGE_URL',
- 'PAGE_LANG_URL', 'ARTICLE_SAVE_AS',
- 'ARTICLE_LANG_SAVE_AS', 'PAGE_SAVE_AS',
- 'PAGE_LANG_SAVE_AS'):
- settings[setting] = os.path.join(structure, settings[setting])
- log.warning("%s = '%s'" % (setting, settings[setting]))
-
# define the default settings
self.settings = settings
+
+ self._handle_deprecation()
+
self.theme = theme or settings['THEME']
output_path = output_path or settings['OUTPUT_PATH']
self.output_path = os.path.realpath(output_path)
@@ -82,6 +52,45 @@ class Pelican(object):
else:
raise Exception("Impossible to find the theme %s" % theme)
+ def _handle_deprecation(self):
+
+ if self.settings.get('CLEAN_URLS', False):
+ log.warning('Found deprecated `CLEAN_URLS` in settings. Modifing'
+ ' the following settings for the same behaviour.')
+
+ self.settings['ARTICLE_URL'] = '{slug}/'
+ self.settings['ARTICLE_LANG_URL'] = '{slug}-{lang}/'
+ self.settings['PAGE_URL'] = 'pages/{slug}/'
+ self.settings['PAGE_LANG_URL'] = 'pages/{slug}-{lang}/'
+
+ for setting in ('ARTICLE_URL', 'ARTICLE_LANG_URL', 'PAGE_URL',
+ 'PAGE_LANG_URL'):
+ log.warning("%s = '%s'" % (setting, self.settings[setting]))
+
+ if self.settings.get('ARTICLE_PERMALINK_STRUCTURE', False):
+ log.warning('Found deprecated `ARTICLE_PERMALINK_STRUCTURE` in'
+ ' settings. Modifing the following settings for'
+ ' the same behaviour.')
+
+ structure = self.settings['ARTICLE_PERMALINK_STRUCTURE']
+
+ # Convert %(variable) into {variable}.
+ structure = re.sub('%\((\w+)\)s', '{\g<1>}', structure)
+
+ # Convert %x into {date:%x} for strftime
+ structure = re.sub('(%[A-z])', '{date:\g<1>}', structure)
+
+ # Strip a / prefix
+ structure = re.sub('^/', '', structure)
+
+ for setting in ('ARTICLE_URL', 'ARTICLE_LANG_URL', 'PAGE_URL',
+ 'PAGE_LANG_URL', 'ARTICLE_SAVE_AS',
+ 'ARTICLE_LANG_SAVE_AS', 'PAGE_SAVE_AS',
+ 'PAGE_LANG_SAVE_AS'):
+ self.settings[setting] = os.path.join(structure,
+ self.settings[setting])
+ log.warning("%s = '%s'" % (setting, self.settings[setting]))
+
def run(self):
"""Run the generators and return"""
@@ -129,7 +138,7 @@ def main():
static blog, with restructured text input files.""")
parser.add_argument(dest='path', nargs='?',
- help='Path where to find the content files')
+ help='Path where to find the content files.')
parser.add_argument('-t', '--theme-path', dest='theme',
help='Path where to find the theme templates. If not specified, it'
'will use the default one included with pelican.')
@@ -137,28 +146,28 @@ def main():
help='Where to output the generated files. If not specified, a '
'directory will be created, named "output" in the current path.')
parser.add_argument('-m', '--markup', default=None, dest='markup',
- help='the list of markup language to use (rst or md). Please indicate '
- 'them separated by commas')
+ help='The list of markup language to use (rst or md). Please indicate '
+ 'them separated by commas.')
parser.add_argument('-s', '--settings', dest='settings', default='',
- help='the settings of the application. Default to False.')
+ help='The settings of the application. Default to False.')
parser.add_argument('-d', '--delete-output-directory',
dest='delete_outputdir',
action='store_true', help='Delete the output directory.')
parser.add_argument('-v', '--verbose', action='store_const',
const=log.INFO, dest='verbosity',
- help='Show all messages')
+ help='Show all messages.')
parser.add_argument('-q', '--quiet', action='store_const',
const=log.CRITICAL, dest='verbosity',
- help='Show only critical errors')
+ help='Show only critical errors.')
parser.add_argument('-D', '--debug', action='store_const',
const=log.DEBUG, dest='verbosity',
- help='Show all message, including debug messages')
+ help='Show all message, including debug messages.')
parser.add_argument('--version', action='version', version=__version__,
- help='Print the pelican version and exit')
+ help='Print the pelican version and exit.')
parser.add_argument('-r', '--autoreload', dest='autoreload',
action='store_true',
help="Relaunch pelican each time a modification occurs"
- " on the content files")
+ " on the content files.")
args = parser.parse_args()
log.init(args.verbosity)
diff --git a/pelican/contents.py b/pelican/contents.py
index 99740168..4f424461 100644
--- a/pelican/contents.py
+++ b/pelican/contents.py
@@ -2,6 +2,7 @@
from datetime import datetime
from os import getenv
from sys import platform, stdin
+import functools
import locale
from pelican.log import warning, error
@@ -42,7 +43,7 @@ class Page(object):
self.author = Author(settings['AUTHOR'], settings)
else:
self.author = Author(getenv('USER', 'John Doe'), settings)
- warning(u"Author of `{0}' unknow, assuming that his name is "
+ warning(u"Author of `{0}' unknown, assuming that his name is "
"`{1}'".format(filename or self.title, self.author))
# manage languages
@@ -108,17 +109,13 @@ class Page(object):
'category': getattr(self, 'category', 'misc'),
}
- @property
- def url(self):
- if self.in_default_lang:
- return self.settings['PAGE_URL'].format(**self.url_format)
- return self.settings['PAGE_LANG_URL'].format(**self.url_format)
+ def _expand_settings(self, key):
+ fq_key = ('%s_%s' % (self.__class__.__name__, key)).upper()
+ return self.settings[fq_key].format(**self.url_format)
- @property
- def save_as(self):
- if self.in_default_lang:
- return self.settings['PAGE_SAVE_AS'].format(**self.url_format)
- return self.settings['PAGE_LANG_SAVE_AS'].format(**self.url_format)
+ def get_url_setting(self, key):
+ key = key if self.in_default_lang else 'lang_%s' % key
+ return self._expand_settings(key)
@property
def content(self):
@@ -139,22 +136,13 @@ class Page(object):
summary = property(_get_summary, _set_summary, "Summary of the article."
"Based on the content. Can't be set")
+ url = property(functools.partial(get_url_setting, key='url'))
+ save_as = property(functools.partial(get_url_setting, key='save_as'))
+
class Article(Page):
mandatory_properties = ('title', 'date', 'category')
- @property
- def url(self):
- if self.in_default_lang:
- return self.settings['ARTICLE_URL'].format(**self.url_format)
- return self.settings['ARTICLE_LANG_URL'].format(**self.url_format)
-
- @property
- def save_as(self):
- if self.in_default_lang:
- return self.settings['ARTICLE_SAVE_AS'].format(**self.url_format)
- return self.settings['ARTICLE_LANG_SAVE_AS'].format(**self.url_format)
-
class Quote(Page):
base_properties = ('author', 'date')
@@ -163,8 +151,12 @@ class Quote(Page):
class URLWrapper(object):
def __init__(self, name, settings):
self.name = unicode(name)
+ self.slug = slugify(self.name)
self.settings = settings
+ def as_dict(self):
+ return self.__dict__
+
def __hash__(self):
return hash(self.name)
@@ -177,42 +169,25 @@ class URLWrapper(object):
def __unicode__(self):
return self.name
- @property
- def url(self):
- return '%s.html' % self.name
+ def _from_settings(self, key):
+ setting = "%s_%s" % (self.__class__.__name__.upper(), key)
+ return self.settings[setting].format(**self.as_dict())
+
+ url = property(functools.partial(_from_settings, key='URL'))
+ save_as = property(functools.partial(_from_settings, key='SAVE_AS'))
class Category(URLWrapper):
- @property
- def url(self):
- return self.settings['CATEGORY_URL'].format(name=self.name)
-
- @property
- def save_as(self):
- return self.settings['CATEGORY_SAVE_AS'].format(name=self.name)
+ pass
class Tag(URLWrapper):
def __init__(self, name, *args, **kwargs):
super(Tag, self).__init__(unicode.strip(name), *args, **kwargs)
- @property
- def url(self):
- return self.settings['TAG_URL'].format(name=self.name)
-
- @property
- def save_as(self):
- return self.settings['TAG_SAVE_AS'].format(name=self.name)
-
class Author(URLWrapper):
- @property
- def url(self):
- return self.settings['AUTHOR_URL'].format(name=self.name)
-
- @property
- def save_as(self):
- return self.settings['AUTHOR_SAVE_AS'].format(name=self.name)
+ pass
def is_valid_content(content, f):
diff --git a/pelican/generators.py b/pelican/generators.py
index 6ba12cf4..2987dcfe 100644
--- a/pelican/generators.py
+++ b/pelican/generators.py
@@ -118,41 +118,46 @@ class ArticlesGenerator(Generator):
def generate_feeds(self, writer):
"""Generate the feeds from the current context, and output files."""
- writer.write_feed(self.articles, self.context, self.settings['FEED'])
-
- if 'FEED_RSS' in self.settings:
+ if self.settings.get('FEED'):
writer.write_feed(self.articles, self.context,
- self.settings['FEED_RSS'], feed_type='rss')
+ self.settings['FEED'])
+
+ if self.settings.get('FEED_RSS'):
+ writer.write_feed(self.articles, self.context,
+ self.settings['FEED_RSS'], feed_type='rss')
for cat, arts in self.categories:
arts.sort(key=attrgetter('date'), reverse=True)
- writer.write_feed(arts, self.context,
- self.settings['CATEGORY_FEED'] % cat)
-
- if 'CATEGORY_FEED_RSS' in self.settings:
+ if self.settings.get('CATEGORY_FEED'):
writer.write_feed(arts, self.context,
- self.settings['CATEGORY_FEED_RSS'] % cat,
- feed_type='rss')
+ self.settings['CATEGORY_FEED'] % cat)
- if 'TAG_FEED' in self.settings:
+ if self.settings.get('CATEGORY_FEED_RSS'):
+ writer.write_feed(arts, self.context,
+ self.settings['CATEGORY_FEED_RSS'] % cat,
+ feed_type='rss')
+
+ if self.settings.get('TAG_FEED') or self.settings.get('TAG_FEED_RSS'):
for tag, arts in self.tags.items():
arts.sort(key=attrgetter('date'), reverse=True)
- writer.write_feed(arts, self.context,
- self.settings['TAG_FEED'] % tag)
+ if self.settings.get('TAG_FEED'):
+ writer.write_feed(arts, self.context,
+ self.settings['TAG_FEED'] % tag)
- if 'TAG_FEED_RSS' in self.settings:
+ if self.settings.get('TAG_FEED_RSS'):
writer.write_feed(arts, self.context,
self.settings['TAG_FEED_RSS'] % tag,
feed_type='rss')
- translations_feeds = defaultdict(list)
- for article in chain(self.articles, self.translations):
- translations_feeds[article.lang].append(article)
+ if self.settings.get('TRANSLATION_FEED'):
+ translations_feeds = defaultdict(list)
+ for article in chain(self.articles, self.translations):
+ translations_feeds[article.lang].append(article)
- for lang, items in translations_feeds.items():
- items.sort(key=attrgetter('date'), reverse=True)
- writer.write_feed(items, self.context,
- self.settings['TRANSLATION_FEED'] % lang)
+ for lang, items in translations_feeds.items():
+ items.sort(key=attrgetter('date'), reverse=True)
+ writer.write_feed(items, self.context,
+ self.settings['TRANSLATION_FEED'] % lang)
def generate_pages(self, writer):
"""Generate the pages on the disk"""
@@ -211,10 +216,10 @@ class ArticlesGenerator(Generator):
def generate_context(self):
"""change the context"""
- # return the list of files to use
- files = self.get_files(self.path, exclude=['pages', ])
all_articles = []
- for f in files:
+ for f in self.get_files(
+ os.path.join(self.path, self.settings['ARTICLE_DIR']),
+ exclude=self.settings['ARTICLE_EXCLUDES']):
try:
content, metadata = read_file(f, settings=self.settings)
except Exception, e:
@@ -316,7 +321,9 @@ class PagesGenerator(Generator):
def generate_context(self):
all_pages = []
- for f in self.get_files(os.sep.join((self.path, 'pages'))):
+ for f in self.get_files(
+ os.path.join(self.path, self.settings['PAGE_DIR']),
+ exclude=self.settings['PAGE_EXCLUDES']):
try:
content, metadata = read_file(f)
except Exception, e:
diff --git a/pelican/readers.py b/pelican/readers.py
index 5bbbfb30..a581e458 100644
--- a/pelican/readers.py
+++ b/pelican/readers.py
@@ -143,12 +143,24 @@ def read_file(filename, fmt=None, settings=None):
"""Return a reader object using the given format."""
if not fmt:
fmt = filename.split('.')[-1]
+
if fmt not in _EXTENSIONS.keys():
raise TypeError('Pelican does not know how to parse %s' % filename)
+
reader = _EXTENSIONS[fmt](settings)
settings_key = '%s_EXTENSIONS' % fmt.upper()
+
if settings and settings_key in settings:
reader.extensions = settings[settings_key]
+
if not reader.enabled:
raise ValueError("Missing dependencies for %s" % fmt)
- return reader.read(filename)
+
+ content, metadata = reader.read(filename)
+
+ # eventually filter the content with typogrify if asked so
+ if settings and settings['TYPOGRIFY']:
+ from typogrify import Typogrify
+ content = Typogrify.typogrify(content)
+
+ return content, metadata
diff --git a/pelican/settings.py b/pelican/settings.py
index 6ed76f46..b38a99fd 100644
--- a/pelican/settings.py
+++ b/pelican/settings.py
@@ -8,6 +8,10 @@ from pelican import log
DEFAULT_THEME = os.sep.join([os.path.dirname(os.path.abspath(__file__)),
"themes/notmyidea"])
_DEFAULT_CONFIG = {'PATH': None,
+ 'ARTICLE_DIR': '',
+ 'ARTICLE_EXCLUDES': ('pages',),
+ 'PAGE_DIR': 'pages',
+ 'PAGE_EXCLUDES': (),
'THEME': DEFAULT_THEME,
'OUTPUT_PATH': 'output/',
'MARKUP': ('rst', 'md'),
@@ -37,10 +41,10 @@ _DEFAULT_CONFIG = {'PATH': None,
'PAGE_LANG_SAVE_AS': 'pages/{slug}-{lang}.html',
'CATEGORY_URL': 'category/{name}.html',
'CATEGORY_SAVE_AS': 'category/{name}.html',
- 'TAG_URL': 'tag/{name}.html',
- 'TAG_SAVE_AS': 'tag/{name}.html',
- 'AUTHOR_URL': u'author/{name}.html',
- 'AUTHOR_SAVE_AS': u'author/{name}.html',
+ 'TAG_URL': 'tag/{slug}.html',
+ 'TAG_SAVE_AS': 'tag/{slug}.html',
+ 'AUTHOR_URL': u'author/{slug}.html',
+ 'AUTHOR_SAVE_AS': u'author/{slug}.html',
'RELATIVE_URLS': True,
'DEFAULT_LANG': 'en',
'TAG_CLOUD_STEPS': 4,
@@ -57,11 +61,12 @@ _DEFAULT_CONFIG = {'PATH': None,
'DEFAULT_METADATA': (),
'FILES_TO_COPY': (),
'DEFAULT_STATUS': 'published',
- 'ARTICLE_PERMALINK_STRUCTURE': ''
+ 'ARTICLE_PERMALINK_STRUCTURE': '',
+ 'TYPOGRIFY': False,
}
-def read_settings(filename):
+def read_settings(filename=None):
"""Load a Python file into a dictionary.
"""
context = _DEFAULT_CONFIG.copy()
diff --git a/pelican/themes/notmyidea/static/css/main.css b/pelican/themes/notmyidea/static/css/main.css
index b3677771..7534790f 100644
--- a/pelican/themes/notmyidea/static/css/main.css
+++ b/pelican/themes/notmyidea/static/css/main.css
@@ -10,6 +10,7 @@
/* Imports */
@import url("reset.css");
@import url("pygment.css");
+@import url("typogrify.css");
@import url(http://fonts.googleapis.com/css?family=Yanone+Kaffeesatz&subset=latin);
/***** Global *****/
diff --git a/pelican/themes/notmyidea/static/css/typogrify.css b/pelican/themes/notmyidea/static/css/typogrify.css
new file mode 100644
index 00000000..c9b34dc8
--- /dev/null
+++ b/pelican/themes/notmyidea/static/css/typogrify.css
@@ -0,0 +1,3 @@
+.caps {font-size:.92em;}
+.amp {color:#666; font-size:1.05em;font-family:"Warnock Pro", "Goudy Old Style","Palatino","Book Antiqua",serif; font-style:italic;}
+.dquo {margin-left:-.38em;}
diff --git a/pelican/utils.py b/pelican/utils.py
index 93541cc0..1b84f108 100644
--- a/pelican/utils.py
+++ b/pelican/utils.py
@@ -14,7 +14,7 @@ from pelican.log import warning, info
def get_date(string):
"""Return a datetime object from a string.
- If no format matches the given date, raise a ValuEerror
+ If no format matches the given date, raise a ValueError.
"""
string = re.sub(' +', ' ', string)
formats = ['%Y-%m-%d %H:%M', '%Y/%m/%d %H:%M',
@@ -58,8 +58,8 @@ def copy(path, source, destination, destination_path=None, overwrite=False):
:param source: the source dir
:param destination: the destination dir
:param destination_path: the destination path (optional)
- :param overwrite: wether to overwrite the destination if already exists or
- not
+ :param overwrite: whether to overwrite the destination if already exists
+ or not
"""
if not destination_path:
destination_path = path
@@ -169,13 +169,11 @@ def truncate_html_words(s, num, end_text='...'):
def process_translations(content_list):
- """ Finds all translation and returns
- tuple with two lists (index, translations).
- Index list includes items in default language
- or items which have no variant in default language.
+ """ Finds all translation and returns tuple with two lists (index,
+ translations). Index list includes items in default language or items
+ which have no variant in default language.
- Also, for each content_list item, it
- sets attribute 'translations'
+ Also, for each content_list item, it sets attribute 'translations'
"""
content_list.sort(key=attrgetter('slug'))
grouped_by_slugs = groupby(content_list, attrgetter('slug'))
@@ -185,10 +183,7 @@ def process_translations(content_list):
for slug, items in grouped_by_slugs:
items = list(items)
# find items with default language
- default_lang_items = filter(
- attrgetter('in_default_lang'),
- items
- )
+ default_lang_items = filter(attrgetter('in_default_lang'), items)
len_ = len(default_lang_items)
if len_ > 1:
warning(u'there are %s variants of "%s"' % (len_, slug))
diff --git a/tests/content/article.rst b/tests/content/article.rst
new file mode 100644
index 00000000..1707ab03
--- /dev/null
+++ b/tests/content/article.rst
@@ -0,0 +1,4 @@
+Article title
+#############
+
+This is some content. With some stuff to "typogrify".
diff --git a/tests/support.py b/tests/support.py
new file mode 100644
index 00000000..5829fe78
--- /dev/null
+++ b/tests/support.py
@@ -0,0 +1,26 @@
+from contextlib import contextmanager
+from tempfile import mkdtemp
+from shutil import rmtree
+
+from pelican.contents import Article
+
+
+@contextmanager
+def temporary_folder():
+ """creates a temporary folder, return it and delete it afterwards.
+
+ This allows to do something like this in tests:
+
+ >>> with temporary_folder() as d:
+ # do whatever you want
+ """
+ tempdir = mkdtemp()
+ yield tempdir
+ rmtree(tempdir)
+
+
+def get_article(title, slug, content, lang, extra_metadata=None):
+ metadata = {'slug': slug, 'title': title, 'lang': lang}
+ if extra_metadata is not None:
+ metadata.update(extra_metadata)
+ return Article(content, metadata=metadata)
diff --git a/tests/test_contents.py b/tests/test_contents.py
index e058e721..ed9885b6 100644
--- a/tests/test_contents.py
+++ b/tests/test_contents.py
@@ -3,18 +3,19 @@ from __future__ import with_statement
try:
from unittest2 import TestCase, skip
except ImportError, e:
- from unittest import TestCase, skip
+ from unittest import TestCase, skip # NOQA
from pelican.contents import Page
from pelican.settings import _DEFAULT_CONFIG
+
class TestPage(TestCase):
def setUp(self):
super(TestPage, self).setUp()
self.page_kwargs = {
'content': 'content',
- 'metadata':{
+ 'metadata': {
'title': 'foo bar',
'author': 'Blogger',
},
@@ -72,32 +73,38 @@ class TestPage(TestCase):
"""
from datetime import datetime
from sys import platform
- dt = datetime(2015,9,13)
+ dt = datetime(2015, 9, 13)
# make a deep copy of page_kawgs
- page_kwargs = {key:self.page_kwargs[key] for key in self.page_kwargs}
+ page_kwargs = dict([(key, self.page_kwargs[key]) for key in
+ self.page_kwargs])
for key in page_kwargs:
- if not isinstance(page_kwargs[key], dict): break
- page_kwargs[key] = {subkey:page_kwargs[key][subkey] for subkey in page_kwargs[key]}
+ if not isinstance(page_kwargs[key], dict):
+ break
+ page_kwargs[key] = dict([(subkey, page_kwargs[key][subkey])
+ for subkey in page_kwargs[key]])
# set its date to dt
page_kwargs['metadata']['date'] = dt
- page = Page( **page_kwargs)
+ page = Page(**page_kwargs)
self.assertEqual(page.locale_date,
- unicode(dt.strftime(_DEFAULT_CONFIG['DEFAULT_DATE_FORMAT']), 'utf-8'))
+ unicode(dt.strftime(_DEFAULT_CONFIG['DEFAULT_DATE_FORMAT']),
+ 'utf-8'))
+ page_kwargs['settings'] = dict([(x, _DEFAULT_CONFIG[x]) for x in
+ _DEFAULT_CONFIG])
- page_kwargs['settings'] = {x:_DEFAULT_CONFIG[x] for x in _DEFAULT_CONFIG}
# I doubt this can work on all platforms ...
if platform == "win32":
locale = 'jpn'
else:
locale = 'ja_JP.utf8'
- page_kwargs['settings']['DATE_FORMATS'] = {'jp':(locale,'%Y-%m-%d(%a)')}
+ page_kwargs['settings']['DATE_FORMATS'] = {'jp': (locale,
+ '%Y-%m-%d(%a)')}
page_kwargs['metadata']['lang'] = 'jp'
import locale as locale_module
try:
- page = Page( **page_kwargs)
+ page = Page(**page_kwargs)
self.assertEqual(page.locale_date, u'2015-09-13(\u65e5)')
# above is unicode in Japanese: 2015-09-13(“ú)
except locale_module.Error:
diff --git a/tests/test_generators.py b/tests/test_generators.py
new file mode 100644
index 00000000..20929622
--- /dev/null
+++ b/tests/test_generators.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+try:
+ import unittest2 as unittest
+except ImportError, e:
+ import unittest # NOQA
+
+from pelican.generators import ArticlesGenerator
+from pelican.settings import _DEFAULT_CONFIG
+
+from mock import MagicMock
+
+
+class TestArticlesGenerator(unittest.TestCase):
+
+ def test_generate_feeds(self):
+
+ generator = ArticlesGenerator(None, {'FEED': _DEFAULT_CONFIG['FEED']},
+ None, _DEFAULT_CONFIG['THEME'], None,
+ None)
+ writer = MagicMock()
+ generator.generate_feeds(writer)
+ writer.write_feed.assert_called_with([], None, 'feeds/all.atom.xml')
+
+ generator = ArticlesGenerator(None, {'FEED': None}, None,
+ _DEFAULT_CONFIG['THEME'], None, None)
+ writer = MagicMock()
+ generator.generate_feeds(writer)
+ self.assertFalse(writer.write_feed.called)
diff --git a/tests/test_pelican.py b/tests/test_pelican.py
new file mode 100644
index 00000000..dce4fadc
--- /dev/null
+++ b/tests/test_pelican.py
@@ -0,0 +1,31 @@
+import unittest
+import os
+
+from support import temporary_folder
+
+from pelican import Pelican
+from pelican.settings import read_settings
+
+SAMPLES_PATH = os.path.abspath(os.sep.join(
+ (os.path.dirname(os.path.abspath(__file__)), "..", "samples")))
+
+INPUT_PATH = os.path.join(SAMPLES_PATH, "content")
+SAMPLE_CONFIG = os.path.join(SAMPLES_PATH, "pelican.conf.py")
+
+
+class TestPelican(unittest.TestCase):
+ # general functional testing for pelican. Basically, this test case tries
+ # to run pelican in different situations and see how it behaves
+
+ def test_basic_generation_works(self):
+ # when running pelican without settings, it should pick up the default
+ # ones and generate the output without raising any exception / issuing
+ # any warning.
+ with temporary_folder() as temp_path:
+ pelican = Pelican(path=INPUT_PATH, output_path=temp_path)
+ pelican.run()
+
+ # the same thing with a specified set of settins should work
+ with temporary_folder() as temp_path:
+ pelican = Pelican(path=INPUT_PATH, output_path=temp_path,
+ settings=read_settings(SAMPLE_CONFIG))
diff --git a/tests/test_readers.py b/tests/test_readers.py
index 120b3125..c0b8cc41 100644
--- a/tests/test_readers.py
+++ b/tests/test_readers.py
@@ -1,8 +1,8 @@
# coding: utf-8
try:
- import unittest2
+ import unittest2 as unittest
except ImportError, e:
- import unittest as unittest2
+ import unittest
import datetime
import os
@@ -12,11 +12,12 @@ from pelican import readers
CUR_DIR = os.path.dirname(__file__)
CONTENT_PATH = os.path.join(CUR_DIR, 'content')
+
def _filename(*args):
return os.path.join(CONTENT_PATH, *args)
-class RstReaderTest(unittest2.TestCase):
+class RstReaderTest(unittest.TestCase):
def test_article_with_metadata(self):
reader = readers.RstReader({})
@@ -25,8 +26,31 @@ class RstReaderTest(unittest2.TestCase):
'category': 'yeah',
'author': u'Alexis Métaireau',
'title': 'This is a super article !',
- 'summary': 'Multi-line metadata should be supported\nas well as inline markup.',
+ 'summary': 'Multi-line metadata should be supported\nas well as'\
+ ' inline markup.',
'date': datetime.datetime(2010, 12, 2, 10, 14),
'tags': ['foo', 'bar', 'foobar'],
}
- self.assertDictEqual(metadata, expected)
+
+ for key, value in expected.items():
+ self.assertEquals(value, metadata[key], key)
+
+ def test_typogrify(self):
+ # if nothing is specified in the settings, the content should be
+ # unmodified
+ content, _ = readers.read_file(_filename('article.rst'))
+ expected = "
This is some content. With some stuff to "\
+ ""typogrify".
\n"
+
+ self.assertEqual(content, expected)
+
+ try:
+ # otherwise, typogrify should be applied
+ content, _ = readers.read_file(_filename('article.rst'),
+ settings={'TYPOGRIFY': True})
+ expected = "This is some content. With some stuff to "\
+ "“typogrify”.
\n"
+
+ self.assertEqual(content, expected)
+ except ImportError:
+ return unittest.skip('need the typogrify distribution')
diff --git a/tests/test_utils.py b/tests/test_utils.py
new file mode 100644
index 00000000..9654825e
--- /dev/null
+++ b/tests/test_utils.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+try:
+ import unittest2 as unittest
+except ImportError:
+ import unittest # NOQA
+import datetime
+
+from pelican import utils
+from pelican.contents import Article
+
+from support import get_article
+
+
+class TestUtils(unittest.TestCase):
+
+ def test_get_date(self):
+ # valid ones
+ date = datetime.datetime(year=2012, month=11, day=22)
+ date_hour = datetime.datetime(year=2012, month=11, day=22, hour=22,
+ minute=11)
+ date_hour_sec = datetime.datetime(year=2012, month=11, day=22, hour=22,
+ minute=11, second=10)
+ dates = {'2012-11-22': date,
+ '2012/11/22': date,
+ '2012-11-22 22:11': date_hour,
+ '2012/11/22 22:11': date_hour,
+ '22-11-2012': date,
+ '22/11/2012': date,
+ '22.11.2012': date,
+ '2012-22-11': date,
+ '22.11.2012 22:11': date_hour,
+ '2012-11-22 22:11:10': date_hour_sec}
+
+ for value, expected in dates.items():
+ self.assertEquals(utils.get_date(value), expected, value)
+
+ # invalid ones
+ invalid_dates = ('2010-110-12', 'yay')
+ for item in invalid_dates:
+ self.assertRaises(ValueError, utils.get_date, item)
+
+ def test_slugify(self):
+
+ samples = (('this is a test', 'this-is-a-test'),
+ ('this is a test', 'this-is-a-test'),
+ (u'this → is ↠a ↑ test', 'this-is-a-test'),
+ ('this--is---a test', 'this-is-a-test'))
+
+ for value, expected in samples:
+ self.assertEquals(utils.slugify(value), expected)
+
+ def test_get_relative_path(self):
+
+ samples = (('/test/test', '../../.'),
+ ('/test/test/', '../../../.'),
+ ('/', '../.'))
+
+ for value, expected in samples:
+ self.assertEquals(utils.get_relative_path(value), expected)
+
+ def test_process_translations(self):
+ # create a bunch of articles
+ fr_article1 = get_article(lang='fr', slug='yay', title='Un titre',
+ content='en français')
+ en_article1 = get_article(lang='en', slug='yay', title='A title',
+ content='in english')
+
+ articles = [fr_article1, en_article1]
+
+ index, trans = utils.process_translations(articles)
+
+ self.assertIn(en_article1, index)
+ self.assertIn(fr_article1, trans)
+ self.assertNotIn(en_article1, trans)
+ self.assertNotIn(fr_article1, index)
diff --git a/tox.ini b/tox.ini
index 462abf09..e1ca32f2 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,12 +1,13 @@
[tox]
-envlist = py25,py26,py27
+envlist = py26,py27
[testenv]
-commands=py.test
+commands = nosetests -s tests
deps =
+ nose
Jinja2
Pygments
docutils
feedgenerator
unittest2
- pytest
+ mock