This commit is contained in:
Jiachen Yang 2012-03-12 19:41:40 +09:00
commit 7c31b88f8f
21 changed files with 377 additions and 150 deletions

8
.travis.yml Normal file
View file

@ -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

View file

@ -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

View file

@ -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 <http://www.python.org/>`_.
* Write your weblog entries directly with your editor of choice (vim!)

View file

@ -4,3 +4,4 @@ docutils
feedgenerator
unittest2
pytz
mock

View file

@ -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
<http://static.mintchaos.com/projects/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

View file

@ -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)

View file

@ -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):

View file

@ -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:

View file

@ -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

View file

@ -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()

View file

@ -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 *****/

View file

@ -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;}

View file

@ -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))

View file

@ -0,0 +1,4 @@
Article title
#############
This is some content. With some stuff to "typogrify".

26
tests/support.py Normal file
View file

@ -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)

View file

@ -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:

28
tests/test_generators.py Normal file
View file

@ -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)

31
tests/test_pelican.py Normal file
View file

@ -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))

View file

@ -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 <strong>inline markup</strong>.',
'summary': 'Multi-line metadata should be supported\nas well as'\
' <strong>inline markup</strong>.',
'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 = "<p>This is some content. With some stuff to "\
"&quot;typogrify&quot;.</p>\n"
self.assertEqual(content, expected)
try:
# otherwise, typogrify should be applied
content, _ = readers.read_file(_filename('article.rst'),
settings={'TYPOGRIFY': True})
expected = "<p>This is some content. With some stuff to&nbsp;"\
"&#8220;typogrify&#8221;.</p>\n"
self.assertEqual(content, expected)
except ImportError:
return unittest.skip('need the typogrify distribution')

75
tests/test_utils.py Normal file
View file

@ -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)

View file

@ -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