Merge branch 'master' into fix-functional-tests

Conflicts:
	tests/test_generators.py
This commit is contained in:
Bruno Binet 2012-05-12 00:19:14 +02:00
commit ebfdabf2d7
25 changed files with 528 additions and 234 deletions

View file

@ -39,7 +39,7 @@ Usage
""""" """""
| pelican-import [-h] [--wpfile] [--dotclear] [--feed] [-o OUTPUT] | pelican-import [-h] [--wpfile] [--dotclear] [--feed] [-o OUTPUT]
| [--dir-cat] | [-m MARKUP][--dir-cat]
| input | input
Optional arguments Optional arguments
@ -51,6 +51,7 @@ Optional arguments
--feed Feed to parse --feed Feed to parse
-o OUTPUT, --output OUTPUT -o OUTPUT, --output OUTPUT
Output path Output path
-m MARKUP Output markup
--dir-cat Put files in directories with categories name --dir-cat Put files in directories with categories name
Examples Examples

View file

@ -23,6 +23,7 @@ Pelican currently supports:
* Publication of articles in multiple languages * Publication of articles in multiple languages
* Atom/RSS feeds * Atom/RSS feeds
* Code syntax highlighting * Code syntax highlighting
* Compilation of less css (optional)
* Import from WordPress, Dotclear, or RSS feeds * Import from WordPress, Dotclear, or RSS feeds
* Integration with external tools: Twitter, Google Analytics, etc. (optional) * Integration with external tools: Twitter, Google Analytics, etc. (optional)

View file

@ -21,9 +21,9 @@ Here is a list of settings for Pelican:
Basic settings Basic settings
============== ==============
================================================ ===================================================== ===================================================================== =====================================================================
Setting name (default value) What does it do? Setting name (default value) What does it do?
================================================ ===================================================== ===================================================================== =====================================================================
`AUTHOR` Default author (put your name) `AUTHOR` Default author (put your name)
`DATE_FORMATS` (``{}``) If you do manage multiple languages, you can `DATE_FORMATS` (``{}``) If you do manage multiple languages, you can
set the date formatting here. See "Date format and locales" set the date formatting here. See "Date format and locales"
@ -82,10 +82,20 @@ Setting name (default value) What does it do?
generated HTML, using the `Typogrify generated HTML, using the `Typogrify
<http://static.mintchaos.com/projects/typogrify/>`_ <http://static.mintchaos.com/projects/typogrify/>`_
library library
================================================ ===================================================== `LESS_GENERATOR` (``FALSE``) Set to True or complete path to `lessc` (if not
found in system PATH) to enable compiling less
css files. Requires installation of `less css`_.
`DIRECT_TEMPLATES` (``('index', 'tags', 'categories', 'archives')``) List of templates that are used directly to render
content. Typically direct templates are used to generate
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.
===================================================================== =====================================================================
.. [#] Default is the system locale. .. [#] Default is the system locale.
.. _less css: http://lesscss.org/
URL settings URL settings
------------ ------------
@ -96,7 +106,8 @@ your articles in a location such as '{slug}/index.html' and link to them as
'{slug}' for clean URLs. These settings give you the flexibility to place your '{slug}' for clean URLs. These settings give you the flexibility to place your
articles and pages anywhere you want. articles and pages anywhere you want.
Note: If you specify a datetime directive, it will be substituted using the .. note::
If you specify a datetime directive, it will be substituted using the
input files' date metadata attribute. If the date is not specified for a input files' date metadata attribute. If the date is not specified for a
particular file, Pelican will rely on the file's mtime timestamp. particular file, Pelican will rely on the file's mtime timestamp.
@ -140,8 +151,15 @@ Setting name (default value) what does it do?
`CATEGORY_SAVE_AS` ('category/{name}.html') The location to save a category. `CATEGORY_SAVE_AS` ('category/{name}.html') The location to save a category.
`TAG_URL` ('tag/{name}.html') The URL to use for a tag. `TAG_URL` ('tag/{name}.html') The URL to use for a tag.
`TAG_SAVE_AS` ('tag/{name}.html') The location to save the tag page. `TAG_SAVE_AS` ('tag/{name}.html') The location to save the tag page.
`<DIRECT_TEMPLATE_NAME>_SAVE_AS` The location to save content generated from direct
templates. Where <DIRECT_TEMPLATE_NAME> is the
upper case template name.
================================================ ===================================================== ================================================ =====================================================
.. note::
When any of `*_SAVE_AS` is set to False, files will not be created.
Timezone Timezone
-------- --------

View file

@ -6,7 +6,7 @@ import logging
import argparse import argparse
from pelican.generators import (ArticlesGenerator, PagesGenerator, from pelican.generators import (ArticlesGenerator, PagesGenerator,
StaticGenerator, PdfGenerator) StaticGenerator, PdfGenerator, LessCSSGenerator)
from pelican.log import init from pelican.log import init
from pelican.settings import read_settings, _DEFAULT_CONFIG from pelican.settings import read_settings, _DEFAULT_CONFIG
from pelican.utils import clean_output_dir, files_changed from pelican.utils import clean_output_dir, files_changed
@ -134,6 +134,8 @@ class Pelican(object):
generators = [ArticlesGenerator, PagesGenerator, StaticGenerator] generators = [ArticlesGenerator, PagesGenerator, StaticGenerator]
if self.settings['PDF_GENERATOR']: if self.settings['PDF_GENERATOR']:
generators.append(PdfGenerator) generators.append(PdfGenerator)
if self.settings['LESS_GENERATOR']: # can be True or PATH to lessc
generators.append(LessCSSGenerator)
return generators return generators
def get_writer(self): def get_writer(self):

View file

@ -183,7 +183,12 @@ class URLWrapper(object):
def _from_settings(self, key): def _from_settings(self, key):
setting = "%s_%s" % (self.__class__.__name__.upper(), key) setting = "%s_%s" % (self.__class__.__name__.upper(), key)
return unicode(self.settings[setting]).format(**self.as_dict()) value = self.settings[setting]
if not isinstance(value, basestring):
logger.warning(u'%s is set to %s' % (setting, value))
return value
else:
return unicode(value).format(**self.as_dict())
url = property(functools.partial(_from_settings, key='URL')) url = property(functools.partial(_from_settings, key='URL'))
save_as = property(functools.partial(_from_settings, key='SAVE_AS')) save_as = property(functools.partial(_from_settings, key='SAVE_AS'))

View file

@ -4,6 +4,7 @@ import math
import random import random
import logging import logging
import datetime import datetime
import subprocess
from collections import defaultdict from collections import defaultdict
from functools import partial from functools import partial
@ -162,31 +163,32 @@ class ArticlesGenerator(Generator):
writer.write_feed(items, self.context, writer.write_feed(items, self.context,
self.settings['TRANSLATION_FEED'] % lang) self.settings['TRANSLATION_FEED'] % lang)
def generate_pages(self, writer): def generate_articles(self, write):
"""Generate the pages on the disk""" """Generate the articles."""
write = partial(writer.write_file,
relative_urls=self.settings.get('RELATIVE_URLS'))
# to minimize the number of relative path stuff modification
# in writer, articles pass first
article_template = self.get_template('article') article_template = self.get_template('article')
for article in chain(self.translations, self.articles): for article in chain(self.translations, self.articles):
write(article.save_as, write(article.save_as,
article_template, self.context, article=article, article_template, self.context, article=article,
category=article.category) category=article.category)
def generate_direct_templates(self, write):
"""Generate direct templates pages"""
PAGINATED_TEMPLATES = self.settings.get('PAGINATED_DIRECT_TEMPLATES') PAGINATED_TEMPLATES = self.settings.get('PAGINATED_DIRECT_TEMPLATES')
for template in self.settings.get('DIRECT_TEMPLATES'): for template in self.settings.get('DIRECT_TEMPLATES'):
paginated = {} paginated = {}
if template in PAGINATED_TEMPLATES: if template in PAGINATED_TEMPLATES:
paginated = {'articles': self.articles, 'dates': self.dates} paginated = {'articles': self.articles, 'dates': self.dates}
save_as = self.settings.get("%s_SAVE_AS" % template.upper(),
'%s.html' % template)
if not save_as:
continue
write('%s.html' % template, self.get_template(template), write(save_as, self.get_template(template),
self.context, blog=True, paginated=paginated, self.context, blog=True, paginated=paginated,
page_name=template) page_name=template)
# and subfolders after that def generate_tags(self, write):
"""Generate Tags pages."""
tag_template = self.get_template('tag') tag_template = self.get_template('tag')
for tag, articles in self.tags.items(): for tag, articles in self.tags.items():
articles.sort(key=attrgetter('date'), reverse=True) articles.sort(key=attrgetter('date'), reverse=True)
@ -196,6 +198,8 @@ class ArticlesGenerator(Generator):
paginated={'articles': articles, 'dates': dates}, paginated={'articles': articles, 'dates': dates},
page_name=u'tag/%s' % tag) page_name=u'tag/%s' % tag)
def generate_categories(self, write):
"""Generate category pages."""
category_template = self.get_template('category') category_template = self.get_template('category')
for cat, articles in self.categories: for cat, articles in self.categories:
dates = [article for article in self.dates if article in articles] dates = [article for article in self.dates if article in articles]
@ -204,6 +208,8 @@ class ArticlesGenerator(Generator):
paginated={'articles': articles, 'dates': dates}, paginated={'articles': articles, 'dates': dates},
page_name=u'category/%s' % cat) page_name=u'category/%s' % cat)
def generate_authors(self, write):
"""Generate Author pages."""
author_template = self.get_template('author') author_template = self.get_template('author')
for aut, articles in self.authors: for aut, articles in self.authors:
dates = [article for article in self.dates if article in articles] dates = [article for article in self.dates if article in articles]
@ -212,10 +218,30 @@ class ArticlesGenerator(Generator):
paginated={'articles': articles, 'dates': dates}, paginated={'articles': articles, 'dates': dates},
page_name=u'author/%s' % aut) page_name=u'author/%s' % aut)
def generate_drafts(self, write):
"""Generate drafts pages."""
article_template = self.get_template('article')
for article in self.drafts: for article in self.drafts:
write('drafts/%s.html' % article.slug, article_template, write('drafts/%s.html' % article.slug, article_template,
self.context, article=article, category=article.category) self.context, article=article, category=article.category)
def generate_pages(self, writer):
"""Generate the pages on the disk"""
write = partial(writer.write_file,
relative_urls=self.settings.get('RELATIVE_URLS'))
# to minimize the number of relative path stuff modification
# in writer, articles pass first
self.generate_articles(write)
self.generate_direct_templates(write)
# and subfolders after that
self.generate_tags(write)
self.generate_categories(write)
self.generate_authors(write)
self.generate_drafts(write)
def generate_context(self): def generate_context(self):
"""change the context""" """change the context"""
@ -416,3 +442,50 @@ class PdfGenerator(Generator):
for page in self.context['pages']: for page in self.context['pages']:
self._create_pdf(page, pdf_path) self._create_pdf(page, pdf_path)
class LessCSSGenerator(Generator):
"""Compile less css files."""
def _compile(self, less_file, source_dir, dest_dir):
base = os.path.relpath(less_file, source_dir)
target = os.path.splitext(
os.path.join(dest_dir, base))[0] + '.css'
target_dir = os.path.dirname(target)
if not os.path.exists(target_dir):
try:
os.makedirs(target_dir)
except OSError:
logger.error("Couldn't create the less css output folder in " +
target_dir)
subprocess.call([self._lessc, less_file, target])
logger.info(u' [ok] compiled %s' % base)
def generate_output(self, writer=None):
logger.info(u' Compiling less css')
# store out compiler here, so it won't be evaulted on each run of
# _compile
lg = self.settings['LESS_GENERATOR']
self._lessc = lg if isinstance(lg, basestring) else 'lessc'
# walk static paths
for static_path in self.settings['STATIC_PATHS']:
for f in self.get_files(
os.path.join(self.path, static_path),
extensions=['less']):
self._compile(f, self.path, self.output_path)
# walk theme static paths
theme_output_path = os.path.join(self.output_path, 'theme')
for static_path in self.settings['THEME_STATIC_PATHS']:
theme_static_path = os.path.join(self.theme, static_path)
for f in self.get_files(
theme_static_path,
extensions=['less']):
self._compile(f, theme_static_path, theme_output_path)

View file

@ -65,7 +65,7 @@ def render_node_to_html(document, node):
class RstReader(Reader): class RstReader(Reader):
enabled = bool(docutils) enabled = bool(docutils)
extension = "rst" file_extensions = ['rst']
def _parse_metadata(self, document): def _parse_metadata(self, document):
"""Return the dict containing document metadata""" """Return the dict containing document metadata"""
@ -111,7 +111,7 @@ class RstReader(Reader):
class MarkdownReader(Reader): class MarkdownReader(Reader):
enabled = bool(Markdown) enabled = bool(Markdown)
extension = "md" file_extensions = ['md', 'markdown', 'mkd']
extensions = ['codehilite', 'extra'] extensions = ['codehilite', 'extra']
def read(self, filename): def read(self, filename):
@ -128,7 +128,7 @@ class MarkdownReader(Reader):
class HtmlReader(Reader): class HtmlReader(Reader):
extension = "html" file_extensions = ['html', 'htm']
_re = re.compile('\<\!\-\-\#\s?[A-z0-9_-]*\s?\:s?[A-z0-9\s_-]*\s?\-\-\>') _re = re.compile('\<\!\-\-\#\s?[A-z0-9_-]*\s?\:s?[A-z0-9\s_-]*\s?\-\-\>')
def read(self, filename): def read(self, filename):
@ -144,7 +144,11 @@ class HtmlReader(Reader):
return content, metadata return content, metadata
_EXTENSIONS = dict((cls.extension, cls) for cls in Reader.__subclasses__()) _EXTENSIONS = {}
for cls in Reader.__subclasses__():
for ext in cls.file_extensions:
_EXTENSIONS[ext] = cls
def read_file(filename, fmt=None, settings=None): def read_file(filename, fmt=None, settings=None):
@ -170,5 +174,6 @@ def read_file(filename, fmt=None, settings=None):
if settings and settings['TYPOGRIFY']: if settings and settings['TYPOGRIFY']:
from typogrify import Typogrify from typogrify import Typogrify
content = Typogrify.typogrify(content) content = Typogrify.typogrify(content)
metadata['title'] = Typogrify.typogrify(metadata['title'])
return content, metadata return content, metadata

View file

@ -43,8 +43,8 @@ _DEFAULT_CONFIG = {'PATH': '.',
'PAGE_SAVE_AS': 'pages/{slug}.html', 'PAGE_SAVE_AS': 'pages/{slug}.html',
'PAGE_LANG_URL': 'pages/{slug}-{lang}.html', 'PAGE_LANG_URL': 'pages/{slug}-{lang}.html',
'PAGE_LANG_SAVE_AS': 'pages/{slug}-{lang}.html', 'PAGE_LANG_SAVE_AS': 'pages/{slug}-{lang}.html',
'CATEGORY_URL': 'category/{name}.html', 'CATEGORY_URL': 'category/{slug}.html',
'CATEGORY_SAVE_AS': 'category/{name}.html', 'CATEGORY_SAVE_AS': 'category/{slug}.html',
'TAG_URL': 'tag/{slug}.html', 'TAG_URL': 'tag/{slug}.html',
'TAG_SAVE_AS': 'tag/{slug}.html', 'TAG_SAVE_AS': 'tag/{slug}.html',
'AUTHOR_URL': u'author/{slug}.html', 'AUTHOR_URL': u'author/{slug}.html',
@ -67,6 +67,7 @@ _DEFAULT_CONFIG = {'PATH': '.',
'DEFAULT_STATUS': 'published', 'DEFAULT_STATUS': 'published',
'ARTICLE_PERMALINK_STRUCTURE': '', 'ARTICLE_PERMALINK_STRUCTURE': '',
'TYPOGRIFY': False, 'TYPOGRIFY': False,
'LESS_GENERATOR': False,
} }

View file

@ -4,13 +4,17 @@ body {
font:1.3em/1.3 "Hoefler Text","Georgia",Georgia,serif,sans-serif; font:1.3em/1.3 "Hoefler Text","Georgia",Georgia,serif,sans-serif;
} }
.body, #banner nav, #banner nav ul, #about, #featured, #content{ .post-info{
width: inherit; display: none;
} }
#banner nav { #banner nav {
display: none;
-moz-border-radius: 0px; -moz-border-radius: 0px;
margin-bottom: 0px; margin-bottom: 20px;
overflow: hidden;
font-size: 1em;
background: #F5F4EF;
} }
#banner nav ul{ #banner nav ul{
@ -19,10 +23,11 @@ body {
#banner nav li{ #banner nav li{
float: right; float: right;
color: #000;
} }
#banner nav li:first-child a { #banner nav li a {
-moz-border-radius: 0px; color: #000;
} }
#banner h1 { #banner h1 {

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

View file

@ -6,7 +6,7 @@
<dl> <dl>
{% for article in dates %} {% for article in dates %}
<dt>{{ article.locale_date }}</dt> <dt>{{ article.locale_date }}</dt>
<dd><a href="{{ article.url }}">{{ article.title }}</a></dd> <dd><a href="{{ SITEURL }}/{{ article.url }}">{{ article.title }}</a></dd>
{% endfor %} {% endfor %}
</dl> </dl>
</section> </section>

View file

@ -1,11 +1,15 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}{{ article.title }}{% endblock %} {% block title %}{{ article.title|striptags }}{% endblock %}
{% block content %} {% block content %}
<section id="content" class="body"> <section id="content" class="body">
<article> <article>
<header> <h1 class="entry-title"><a href="{{ pagename }}" <header>
rel="bookmark" title="Permalink to {{ article.title }}">{{ article.title <h1 class="entry-title">
}}</a></h1> {% include 'twitter.html' %} </header> <a href="{{ article.url }}" rel="bookmark"
title="Permalink to {{ article.title|striptags }}">{{ article.title}}</a></h1>
{% include 'twitter.html' %}
</header>
<div class="entry-content"> <div class="entry-content">
{% include 'article_infos.html' %} {% include 'article_infos.html' %}
{{ article.content }} {{ article.content }}

View file

@ -2,7 +2,7 @@
{% block content %} {% block content %}
<ul> <ul>
{% for category, articles in categories %} {% for category, articles in categories %}
<li><a href="{{ category.url }}">{{ category }}</a></li> <li><a href="{{ SITEURL }}/{{ category.url }}">{{ category }}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
{% endblock %} {% endblock %}

View file

@ -5,7 +5,7 @@
<dl> <dl>
{% for article in dates %} {% for article in dates %}
<dt>{{ article.locale_date }}</dt> <dt>{{ article.locale_date }}</dt>
<dd><a href="{{ article.url }}">{{ article.title }}</a></dd> <dd><a href="{{ SITEURL }}/{{ article.url }}">{{ article.title }}</a></dd>
{% endfor %} {% endfor %}
</dl> </dl>
{% endblock %} {% endblock %}

View file

@ -1,7 +1,11 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
<section id="content" class="body"> <section id="content" class="body">
<header> <h2 class="entry-title"><a href="{{ pagename }}" rel="bookmark" title="Permalink to {{ article.title}}">{{ article.title }}</a></h2> </header> <header>
<h2 class="entry-title">
<a href="{{ article.url }}" rel="bookmark"
title="Permalink to {{ article.title|striptags }}">{{ article.title }}</a></h2>
</header>
<footer class="post-info"> <footer class="post-info">
<abbr class="published" title="{{ article.date.isoformat() }}"> <abbr class="published" title="{{ article.date.isoformat() }}">
{{ article.locale_date }} {{ article.locale_date }}

View file

@ -7,94 +7,9 @@ import argparse
from pelican import __version__ from pelican import __version__
TEMPLATES = { _TEMPLATES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), \
'Makefile' : ''' "templates")
PELICAN=$pelican
PELICANOPTS=$pelicanopts
BASEDIR=$$(PWD)
INPUTDIR=$$(BASEDIR)/src
OUTPUTDIR=$$(BASEDIR)/output
CONFFILE=$$(BASEDIR)/pelican.conf.py
FTP_HOST=$ftp_host
FTP_USER=$ftp_user
FTP_TARGET_DIR=$ftp_target_dir
SSH_HOST=$ssh_host
SSH_USER=$ssh_user
SSH_TARGET_DIR=$ssh_target_dir
DROPBOX_DIR=$dropbox_dir
help:
\t@echo 'Makefile for a pelican Web site '
\t@echo ' '
\t@echo 'Usage: '
\t@echo ' make html (re)generate the web site '
\t@echo ' make clean remove the generated files '
\t@echo ' ftp_upload upload the web site using FTP '
\t@echo ' ssh_upload upload the web site using SSH '
\t@echo ' dropbox_upload upload the web site using Dropbox '
\t@echo ' '
html: clean $$(OUTPUTDIR)/index.html
\t@echo 'Done'
$$(OUTPUTDIR)/%.html:
\t$$(PELICAN) $$(INPUTDIR) -o $$(OUTPUTDIR) -s $$(CONFFILE) $$(PELICANOPTS)
clean:
\trm -fr $$(OUTPUTDIR)
\tmkdir $$(OUTPUTDIR)
dropbox_upload: $$(OUTPUTDIR)/index.html
\tcp -r $$(OUTPUTDIR)/* $$(DROPBOX_DIR)
rsync_upload: $$(OUTPUTDIR)/index.html
\trsync --delete -rvz -e ssh $(OUTPUTDIR)/* $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR)
ssh_upload: $$(OUTPUTDIR)/index.html
\tscp -r $$(OUTPUTDIR)/* $$(SSH_USER)@$$(SSH_HOST):$$(SSH_TARGET_DIR)
ftp_upload: $$(OUTPUTDIR)/index.html
\tlftp ftp://$$(FTP_USER)@$$(FTP_HOST) -e "mirror -R $$(OUTPUTDIR) $$(FTP_TARGET_DIR) ; quit"
github: $$(OUTPUTDIR)/index.html
\tghp-import $$(OUTPUTDIR)
\tgit push origin gh-pages
.PHONY: html help clean ftp_upload ssh_upload dropbox_upload github
''',
'pelican.conf.py': '''#!/usr/bin/env python
# -*- coding: utf-8 -*- #
AUTHOR = u"$author"
SITENAME = u"$sitename"
SITEURL = '/'
TIMEZONE = 'Europe/Paris'
DEFAULT_LANG='$lang'
# Blogroll
LINKS = (
('Pelican', 'http://docs.notmyidea.org/alexis/pelican/'),
('Python.org', 'http://python.org'),
('Jinja2', 'http://jinja.pocoo.org'),
('You can modify those links in your config file', '#')
)
# Social widget
SOCIAL = (
('You can add links in your config file', '#'),
)
DEFAULT_PAGINATION = $default_pagination
'''
}
CONF = { CONF = {
'pelican' : 'pelican', 'pelican' : 'pelican',
@ -112,6 +27,20 @@ CONF = {
} }
def get_template(name):
template = os.path.join(_TEMPLATES_DIR, "{0}.in".format(name))
if not os.path.isfile(template):
raise RuntimeError("Cannot open {0}".format(template))
with open(template, 'r') as fd:
line = fd.readline()
while line:
yield line
line = fd.readline()
fd.close()
def ask(question, answer=str, default=None, l=None): def ask(question, answer=str, default=None, l=None):
if answer == str: if answer == str:
r = '' r = ''
@ -246,20 +175,22 @@ Please answer the following questions so this script can generate the files need
except OSError, e: except OSError, e:
print('Error: {0}'.format(e)) print('Error: {0}'.format(e))
conf = string.Template(TEMPLATES['pelican.conf.py'])
try: try:
with open(os.path.join(CONF['basedir'], 'pelican.conf.py'), 'w') as fd: with open(os.path.join(CONF['basedir'], 'pelican.conf.py'), 'w') as fd:
fd.write(conf.safe_substitute(CONF)) for line in get_template('pelican.conf.py'):
template = string.Template(line)
fd.write(template.safe_substitute(CONF))
fd.close() fd.close()
except OSError, e: except OSError, e:
print('Error: {0}'.format(e)) print('Error: {0}'.format(e))
if mkfile: if mkfile:
Makefile = string.Template(TEMPLATES['Makefile'])
try: try:
with open(os.path.join(CONF['basedir'], 'Makefile'), 'w') as fd: with open(os.path.join(CONF['basedir'], 'Makefile'), 'w') as fd:
fd.write(Makefile.safe_substitute(CONF)) for line in get_template('Makefile'):
template = string.Template(line)
fd.write(template.safe_substitute(CONF))
fd.close() fd.close()
except OSError, e: except OSError, e:
print('Error: {0}'.format(e)) print('Error: {0}'.format(e))

View file

@ -0,0 +1,58 @@
PELICAN=$pelican
PELICANOPTS=$pelicanopts
BASEDIR=$$(PWD)
INPUTDIR=$$(BASEDIR)/src
OUTPUTDIR=$$(BASEDIR)/output
CONFFILE=$$(BASEDIR)/pelican.conf.py
FTP_HOST=$ftp_host
FTP_USER=$ftp_user
FTP_TARGET_DIR=$ftp_target_dir
SSH_HOST=$ssh_host
SSH_USER=$ssh_user
SSH_TARGET_DIR=$ssh_target_dir
DROPBOX_DIR=$dropbox_dir
help:
@echo 'Makefile for a pelican Web site '
@echo ' '
@echo 'Usage: '
@echo ' make html (re)generate the web site '
@echo ' make clean remove the generated files '
@echo ' ftp_upload upload the web site using FTP '
@echo ' ssh_upload upload the web site using SSH '
@echo ' dropbox_upload upload the web site using Dropbox '
@echo ' rsync_upload upload the web site using rsync/ssh'
@echo ' '
html: clean $$(OUTPUTDIR)/index.html
@echo 'Done'
$$(OUTPUTDIR)/%.html:
$$(PELICAN) $$(INPUTDIR) -o $$(OUTPUTDIR) -s $$(CONFFILE) $$(PELICANOPTS)
clean:
rm -fr $$(OUTPUTDIR)
mkdir $$(OUTPUTDIR)
dropbox_upload: $$(OUTPUTDIR)/index.html
cp -r $$(OUTPUTDIR)/* $$(DROPBOX_DIR)
ssh_upload: $$(OUTPUTDIR)/index.html
scp -r $$(OUTPUTDIR)/* $$(SSH_USER)@$$(SSH_HOST):$$(SSH_TARGET_DIR)
rsync_upload: $$(OUTPUTDIR)/index.html
rsync -e ssh -P -rvz --delete $(OUTPUTDIR)/* $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR)
ftp_upload: $$(OUTPUTDIR)/index.html
lftp ftp://$$(FTP_USER)@$$(FTP_HOST) -e "mirror -R $$(OUTPUTDIR) $$(FTP_TARGET_DIR) ; quit"
github: $$(OUTPUTDIR)/index.html
ghp-import $$(OUTPUTDIR)
git push origin gh-pages
.PHONY: html help clean ftp_upload ssh_upload rsync_upload dropbox_upload github

View file

@ -0,0 +1,25 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- #
AUTHOR = u"$author"
SITENAME = u"$sitename"
SITEURL = '/'
TIMEZONE = 'Europe/Paris'
DEFAULT_LANG='$lang'
# Blogroll
LINKS = (
('Pelican', 'http://docs.notmyidea.org/alexis/pelican/'),
('Python.org', 'http://python.org'),
('Jinja2', 'http://jinja.pocoo.org'),
('You can modify those links in your config file', '#')
)
# Social widget
SOCIAL = (
('You can add links in your config file', '#'),
)
DEFAULT_PAGINATION = $default_pagination

View file

@ -8,6 +8,7 @@ import logging
from codecs import open as _open from codecs import open as _open
from datetime import datetime from datetime import datetime
from itertools import groupby from itertools import groupby
from jinja2 import Markup
from operator import attrgetter from operator import attrgetter
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -44,6 +45,7 @@ def slugify(value):
Took from django sources. Took from django sources.
""" """
value = Markup(value).striptags()
if type(value) == unicode: if type(value) == unicode:
import unicodedata import unicodedata
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore') value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')

View file

@ -8,8 +8,8 @@ import logging
from codecs import open from codecs import open
from functools import partial from functools import partial
from feedgenerator import Atom1Feed, Rss201rev2Feed from feedgenerator import Atom1Feed, Rss201rev2Feed
from jinja2 import Markup
from pelican.paginator import Paginator from pelican.paginator import Paginator
from pelican.utils import get_relative_path, set_date_tzinfo from pelican.utils import get_relative_path, set_date_tzinfo
@ -25,8 +25,9 @@ class Writer(object):
def _create_new_feed(self, feed_type, context): def _create_new_feed(self, feed_type, context):
feed_class = Rss201rev2Feed if feed_type == 'rss' else Atom1Feed feed_class = Rss201rev2Feed if feed_type == 'rss' else Atom1Feed
sitename = Markup(context['SITENAME']).striptags()
feed = feed_class( feed = feed_class(
title=context['SITENAME'], title=sitename,
link=(self.site_url + '/'), link=(self.site_url + '/'),
feed_url=self.feed_url, feed_url=self.feed_url,
description=context.get('SITESUBTITLE', '')) description=context.get('SITESUBTITLE', ''))
@ -34,8 +35,9 @@ class Writer(object):
def _add_item_to_the_feed(self, feed, item): def _add_item_to_the_feed(self, feed, item):
title = Markup(item.title).striptags()
feed.add_item( feed.add_item(
title=item.title, title=title,
link='%s/%s' % (self.site_url, item.url), link='%s/%s' % (self.site_url, item.url),
unique_id='tag:%s,%s:%s' % (self.site_url.replace('http://', ''), unique_id='tag:%s,%s:%s' % (self.site_url.replace('http://', ''),
item.date.date(), item.url), item.date.date(), item.url),
@ -99,6 +101,12 @@ class Writer(object):
:param **kwargs: additional variables to pass to the templates :param **kwargs: additional variables to pass to the templates
""" """
if name is False:
return
elif not name:
# other stuff, just return for now
return
def _write_file(template, localcontext, output_path, name): def _write_file(template, localcontext, output_path, name):
"""Render the template write the file.""" """Render the template write the file."""
old_locale = locale.setlocale(locale.LC_ALL) old_locale = locale.setlocale(locale.LC_ALL)

View file

@ -0,0 +1,10 @@
title: Test md File
category: test
Test Markdown File Header
=========================
Used for pelican test
---------------------
The quick brown fox jumped over the lazy dog's back.

View file

@ -0,0 +1,10 @@
title: Test mkd File
category: test
Test Markdown File Header
=========================
Used for pelican test
---------------------
This is another markdown test file. Uses the mkd extension.

View file

@ -4,6 +4,8 @@ __all__ = [
'unittest', 'unittest',
] ]
import os
import subprocess
from contextlib import contextmanager from contextlib import contextmanager
from tempfile import mkdtemp from tempfile import mkdtemp
from shutil import rmtree from shutil import rmtree
@ -35,3 +37,21 @@ def get_article(title, slug, content, lang, extra_metadata=None):
if extra_metadata is not None: if extra_metadata is not None:
metadata.update(extra_metadata) metadata.update(extra_metadata)
return Article(content, metadata=metadata) return Article(content, metadata=metadata)
def skipIfNoExecutable(executable, valid_exit_code=1):
"""Tries to run an executable to make sure it's in the path, Skips the tests
if not found.
"""
# calling with no params the command should exit with 1
with open(os.devnull, 'w') as fnull:
try:
res = subprocess.call(executable, stdout=fnull, stderr=fnull)
except OSError:
res = None
if res != valid_exit_code:
return unittest.skip('{0} compiler not found'.format(executable))
return lambda func: func

View file

@ -2,13 +2,15 @@
from mock import MagicMock from mock import MagicMock
import os import os
import re
from pelican.generators import ArticlesGenerator from pelican.generators import ArticlesGenerator, LessCSSGenerator
from pelican.settings import _DEFAULT_CONFIG from pelican.settings import _DEFAULT_CONFIG
from .support import unittest from .support import unittest, temporary_folder, skipIfNoExecutable
CUR_DIR = os.path.dirname(__file__) CUR_DIR = os.path.dirname(__file__)
class TestArticlesGenerator(unittest.TestCase): class TestArticlesGenerator(unittest.TestCase):
def test_generate_feeds(self): def test_generate_feeds(self):
@ -49,4 +51,91 @@ class TestArticlesGenerator(unittest.TestCase):
categories = [cat.name for cat, _ in generator.categories] categories = [cat.name for cat, _ in generator.categories]
# assert that the categories are ordered as expected # assert that the categories are ordered as expected
self.assertEquals( self.assertEquals(
categories, ['Default', 'TestCategory', 'Yeah', 'yeah']) categories, ['Default', 'TestCategory', 'Yeah', 'test',
'yeah'])
def test_direct_templates_save_as_default(self):
settings = _DEFAULT_CONFIG.copy()
settings['DIRECT_TEMPLATES'] = ['archives']
generator = ArticlesGenerator(settings.copy(), settings, None,
_DEFAULT_CONFIG['THEME'], None,
_DEFAULT_CONFIG['MARKUP'])
write = MagicMock()
generator.generate_direct_templates(write)
write.assert_called_with("archives.html",
generator.get_template("archives"), settings,
blog=True, paginated={}, page_name='archives')
def test_direct_templates_save_as_modified(self):
settings = _DEFAULT_CONFIG.copy()
settings['DIRECT_TEMPLATES'] = ['archives']
settings['ARCHIVES_SAVE_AS'] = 'archives/index.html'
generator = ArticlesGenerator(settings, settings, None,
_DEFAULT_CONFIG['THEME'], None,
_DEFAULT_CONFIG['MARKUP'])
write = MagicMock()
generator.generate_direct_templates(write)
write.assert_called_with("archives/index.html",
generator.get_template("archives"), settings,
blog=True, paginated={}, page_name='archives')
def test_direct_templates_save_as_false(self):
settings = _DEFAULT_CONFIG.copy()
settings['DIRECT_TEMPLATES'] = ['archives']
settings['ARCHIVES_SAVE_AS'] = 'archives/index.html'
generator = ArticlesGenerator(settings, settings, None,
_DEFAULT_CONFIG['THEME'], None,
_DEFAULT_CONFIG['MARKUP'])
write = MagicMock()
generator.generate_direct_templates(write)
write.assert_called_count == 0
class TestLessCSSGenerator(unittest.TestCase):
LESS_CONTENT = """
@color: #4D926F;
#header {
color: @color;
}
h2 {
color: @color;
}
"""
@skipIfNoExecutable('lessc')
def test_less_compiler(self):
settings = _DEFAULT_CONFIG.copy()
settings['STATIC_PATHS'] = ['static']
settings['LESS_GENERATOR'] = True
# we'll nest here for py < 2.7 compat
with temporary_folder() as temp_content:
with temporary_folder() as temp_output:
generator = LessCSSGenerator(None, settings, temp_content,
_DEFAULT_CONFIG['THEME'], temp_output, None)
# create a dummy less file
less_dir = os.path.join(temp_content, 'static', 'css')
less_filename = os.path.join(less_dir, 'test.less')
less_output = os.path.join(temp_output, 'static', 'css',
'test.css')
os.makedirs(less_dir)
with open(less_filename, 'w') as less_file:
less_file.write(self.LESS_CONTENT)
generator.generate_output()
# we have the file ?
self.assertTrue(os.path.exists(less_output))
# was it compiled ?
self.assertIsNotNone(re.search(r'^\s+color:\s*#4D926F;$',
open(less_output).read(), re.MULTILINE | re.IGNORECASE))

View file

@ -61,3 +61,25 @@ class RstReaderTest(unittest.TestCase):
self.assertEqual(content, expected) self.assertEqual(content, expected)
except ImportError: except ImportError:
return unittest.skip('need the typogrify distribution') return unittest.skip('need the typogrify distribution')
class MdReaderTest(unittest.TestCase):
def test_article_with_md_extention(self):
# test to ensure the md extension is being processed by the correct reader
reader = readers.MarkdownReader({})
content, metadata = reader.read(_filename('article_with_md_extension.md'))
expected = "<h1>Test Markdown File Header</h1>\n"\
"<h2>Used for pelican test</h2>\n"\
"<p>The quick brown fox jumped over the lazy dog's back.</p>"
self.assertEqual(content, expected)
def test_article_with_mkd_extension(self):
# test to ensure the mkd extension is being processed by the correct reader
reader = readers.MarkdownReader({})
content, metadata = reader.read(_filename('article_with_mkd_extension.mkd'))
expected = "<h1>Test Markdown File Header</h1>\n"\
"<h2>Used for pelican test</h2>\n"\
"<p>This is another markdown test file. Uses the mkd extension.</p>"
self.assertEqual(content, expected)