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]
| [--dir-cat]
| [-m MARKUP][--dir-cat]
| input
Optional arguments
@ -51,6 +51,7 @@ Optional arguments
--feed Feed to parse
-o OUTPUT, --output OUTPUT
Output path
-m MARKUP Output markup
--dir-cat Put files in directories with categories name
Examples

View file

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

View file

@ -21,71 +21,81 @@ Here is a list of settings for Pelican:
Basic settings
==============
================================================ =====================================================
Setting name (default value) What does it do?
================================================ =====================================================
`AUTHOR` Default author (put your name)
`DATE_FORMATS` (``{}``) If you do manage multiple languages, you can
set the date formatting here. See "Date format and locales"
section below for details.
`DEFAULT_CATEGORY` (``'misc'``) The default category to fall back on.
`DEFAULT_DATE_FORMAT` (``'%a %d %B %Y'``) The default date format you want to use.
`DISPLAY_PAGES_ON_MENU` (``True``) Whether to display pages on the menu of the
template. Templates may or not honor this
setting.
`FALLBACK_ON_FS_DATE` (``True``) If True, Pelican will use the file system
timestamp information (mtime) if it can't get
date information from the metadata.
`JINJA_EXTENSIONS` (``[]``) A list of any Jinja2 extensions you want to use.
`DELETE_OUTPUT_DIRECTORY` (``False``) Delete the output directory as well as
the generated files.
`LOCALE` (''[#]_) Change the locale. A list of locales can be provided
here or a single string representing one locale.
When providing a list, all the locales will be tried
until one works.
`MARKUP` (``('rst', 'md')``) A list of available markup languages you want
to use. For the moment, the only available values
are `rst` and `md`.
`MD_EXTENSIONS` (``['codehilite','extra']``) A list of the extensions that the Markdown processor
will use. Refer to the extensions chapter in the
Python-Markdown documentation for a complete list of
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`.
`RELATIVE_URLS` (``True``) Defines whether Pelican should use relative URLs or
not.
`SITENAME` (``'A Pelican Blog'``) Your site name
`SITEURL` Base URL of your website. Not defined by default,
which means the base URL is assumed to be "/" with a
root-relative URL structure. If `SITEURL` is specified
explicitly, there should be no trailing slash at the end,
and URLs will be generated with an absolute URL structure
(including the domain). If you want to use relative URLs
instead of root-relative or absolute URLs, you should
instead use the `RELATIVE_URL` setting.
`STATIC_PATHS` (``['images']``) The static paths you want to have accessible
on the output path "static". By default,
Pelican will copy the 'images' folder to the
output folder.
`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
================================================ =====================================================
===================================================================== =====================================================================
Setting name (default value) What does it do?
===================================================================== =====================================================================
`AUTHOR` Default author (put your name)
`DATE_FORMATS` (``{}``) If you do manage multiple languages, you can
set the date formatting here. See "Date format and locales"
section below for details.
`DEFAULT_CATEGORY` (``'misc'``) The default category to fall back on.
`DEFAULT_DATE_FORMAT` (``'%a %d %B %Y'``) The default date format you want to use.
`DISPLAY_PAGES_ON_MENU` (``True``) Whether to display pages on the menu of the
template. Templates may or not honor this
setting.
`FALLBACK_ON_FS_DATE` (``True``) If True, Pelican will use the file system
timestamp information (mtime) if it can't get
date information from the metadata.
`JINJA_EXTENSIONS` (``[]``) A list of any Jinja2 extensions you want to use.
`DELETE_OUTPUT_DIRECTORY` (``False``) Delete the output directory as well as
the generated files.
`LOCALE` (''[#]_) Change the locale. A list of locales can be provided
here or a single string representing one locale.
When providing a list, all the locales will be tried
until one works.
`MARKUP` (``('rst', 'md')``) A list of available markup languages you want
to use. For the moment, the only available values
are `rst` and `md`.
`MD_EXTENSIONS` (``['codehilite','extra']``) A list of the extensions that the Markdown processor
will use. Refer to the extensions chapter in the
Python-Markdown documentation for a complete list of
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`.
`RELATIVE_URLS` (``True``) Defines whether Pelican should use relative URLs or
not.
`SITENAME` (``'A Pelican Blog'``) Your site name
`SITEURL` Base URL of your website. Not defined by default,
which means the base URL is assumed to be "/" with a
root-relative URL structure. If `SITEURL` is specified
explicitly, there should be no trailing slash at the end,
and URLs will be generated with an absolute URL structure
(including the domain). If you want to use relative URLs
instead of root-relative or absolute URLs, you should
instead use the `RELATIVE_URL` setting.
`STATIC_PATHS` (``['images']``) The static paths you want to have accessible
on the output path "static". By default,
Pelican will copy the 'images' folder to the
output folder.
`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
`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.
.. _less css: http://lesscss.org/
URL settings
------------
@ -96,14 +106,15 @@ 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
articles and pages anywhere you want.
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
particular file, Pelican will rely on the file's mtime timestamp.
.. 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
particular file, Pelican will rely on the file's mtime timestamp.
Check the Python datetime documentation at http://bit.ly/cNcJUC for more
information.
Also, you can use other file metadata attributes as well:
Also, you can use other file metadata attributes as well:
* slug
* date
@ -111,7 +122,7 @@ Also, you can use other file metadata attributes as well:
* author
* category
Example usage:
Example usage:
* ARTICLE_URL = 'posts/{date:%Y}/{date:%b}/{date:%d}/{slug}/'
* ARTICLE_SAVE_AS = 'posts/{date:%Y}/{date:%b}/{date:%d}/{slug}/index.html'
@ -140,8 +151,15 @@ Setting name (default value) what does it do?
`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_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
--------
@ -330,7 +348,7 @@ Setting name (default value) What does it do?
================================================ =====================================================
`REVERSE_ARCHIVE_ORDER` (``False``) Reverse the archives list order. (True: orders by date
in descending order, with newer articles first.)
`REVERSE_CATEGORY_ORDER` (``False``) Reverse the category order. (True: lists by reverse
`REVERSE_CATEGORY_ORDER` (``False``) Reverse the category order. (True: lists by reverse
alphabetical order; default lists alphabetically.)
================================================ =====================================================

View file

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

View file

@ -183,7 +183,12 @@ class URLWrapper(object):
def _from_settings(self, 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'))
save_as = property(functools.partial(_from_settings, key='SAVE_AS'))

View file

@ -4,6 +4,7 @@ import math
import random
import logging
import datetime
import subprocess
from collections import defaultdict
from functools import partial
@ -162,31 +163,32 @@ class ArticlesGenerator(Generator):
writer.write_feed(items, self.context,
self.settings['TRANSLATION_FEED'] % lang)
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
def generate_articles(self, write):
"""Generate the articles."""
article_template = self.get_template('article')
for article in chain(self.translations, self.articles):
write(article.save_as,
article_template, self.context, article=article,
category=article.category)
def generate_direct_templates(self, write):
"""Generate direct templates pages"""
PAGINATED_TEMPLATES = self.settings.get('PAGINATED_DIRECT_TEMPLATES')
for template in self.settings.get('DIRECT_TEMPLATES'):
paginated = {}
if template in PAGINATED_TEMPLATES:
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,
page_name=template)
# and subfolders after that
def generate_tags(self, write):
"""Generate Tags pages."""
tag_template = self.get_template('tag')
for tag, articles in self.tags.items():
articles.sort(key=attrgetter('date'), reverse=True)
@ -196,6 +198,8 @@ class ArticlesGenerator(Generator):
paginated={'articles': articles, 'dates': dates},
page_name=u'tag/%s' % tag)
def generate_categories(self, write):
"""Generate category pages."""
category_template = self.get_template('category')
for cat, articles in self.categories:
dates = [article for article in self.dates if article in articles]
@ -204,6 +208,8 @@ class ArticlesGenerator(Generator):
paginated={'articles': articles, 'dates': dates},
page_name=u'category/%s' % cat)
def generate_authors(self, write):
"""Generate Author pages."""
author_template = self.get_template('author')
for aut, articles in self.authors:
dates = [article for article in self.dates if article in articles]
@ -212,10 +218,30 @@ class ArticlesGenerator(Generator):
paginated={'articles': articles, 'dates': dates},
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:
write('drafts/%s.html' % article.slug, article_template,
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):
"""change the context"""
@ -416,3 +442,50 @@ class PdfGenerator(Generator):
for page in self.context['pages']:
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):
enabled = bool(docutils)
extension = "rst"
file_extensions = ['rst']
def _parse_metadata(self, document):
"""Return the dict containing document metadata"""
@ -111,7 +111,7 @@ class RstReader(Reader):
class MarkdownReader(Reader):
enabled = bool(Markdown)
extension = "md"
file_extensions = ['md', 'markdown', 'mkd']
extensions = ['codehilite', 'extra']
def read(self, filename):
@ -128,7 +128,7 @@ class MarkdownReader(Reader):
class HtmlReader(Reader):
extension = "html"
file_extensions = ['html', 'htm']
_re = re.compile('\<\!\-\-\#\s?[A-z0-9_-]*\s?\:s?[A-z0-9\s_-]*\s?\-\-\>')
def read(self, filename):
@ -144,7 +144,11 @@ class HtmlReader(Reader):
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):
@ -170,5 +174,6 @@ def read_file(filename, fmt=None, settings=None):
if settings and settings['TYPOGRIFY']:
from typogrify import Typogrify
content = Typogrify.typogrify(content)
metadata['title'] = Typogrify.typogrify(metadata['title'])
return content, metadata

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

View file

@ -6,7 +6,7 @@
<dl>
{% for article in dates %}
<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 %}
</dl>
</section>

View file

@ -1,30 +1,34 @@
{% extends "base.html" %}
{% block title %}{{ article.title }}{% endblock %}
{% block content %}
<section id="content" class="body">
<article>
<header> <h1 class="entry-title"><a href="{{ pagename }}"
rel="bookmark" title="Permalink to {{ article.title }}">{{ article.title
}}</a></h1> {% include 'twitter.html' %} </header>
<div class="entry-content">
{% include 'article_infos.html' %}
{{ article.content }}
</div><!-- /.entry-content -->
{% if DISQUS_SITENAME %}
<div class="comments">
<h2>Comments !</h2>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_identifier = "{{ article.url }}";
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = 'http://{{ DISQUS_SITENAME }}.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
</script>
</div>
{% endif %}
{% block title %}{{ article.title|striptags }}{% endblock %}
{% block content %}
<section id="content" class="body">
<article>
<header>
<h1 class="entry-title">
<a href="{{ article.url }}" rel="bookmark"
title="Permalink to {{ article.title|striptags }}">{{ article.title}}</a></h1>
{% include 'twitter.html' %}
</header>
</article>
<div class="entry-content">
{% include 'article_infos.html' %}
{{ article.content }}
</div><!-- /.entry-content -->
{% if DISQUS_SITENAME %}
<div class="comments">
<h2>Comments !</h2>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_identifier = "{{ article.url }}";
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = 'http://{{ DISQUS_SITENAME }}.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
</script>
</div>
{% endif %}
</article>
</section>
{% endblock %}

View file

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

View file

@ -5,7 +5,7 @@
<dl>
{% for article in dates %}
<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 %}
</dl>
{% endblock %}

View file

@ -1,19 +1,23 @@
{% extends "base.html" %}
{% block content %}
<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>
<footer class="post-info">
<abbr class="published" title="{{ article.date.isoformat() }}">
{{ article.locale_date }}
</abbr>
{% if article.author %}
<address class="vcard author">
By <a class="url fn" href="{{ SITEURL }}/{{ article.author.url }}">{{ article.author }}</a>
</address>
{% endif %}
</footer><!-- /.post-info -->
<div class="entry-content">
{{ article.content }}
</div><!-- /.entry-content -->
{% block content %}
<section id="content" class="body">
<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">
<abbr class="published" title="{{ article.date.isoformat() }}">
{{ article.locale_date }}
</abbr>
{% if article.author %}
<address class="vcard author">
By <a class="url fn" href="{{ SITEURL }}/{{ article.author.url }}">{{ article.author }}</a>
</address>
{% endif %}
</footer><!-- /.post-info -->
<div class="entry-content">
{{ article.content }}
</div><!-- /.entry-content -->
</section>
{% endblock %}

View file

@ -7,94 +7,9 @@ import argparse
from pelican import __version__
TEMPLATES = {
'Makefile' : '''
PELICAN=$pelican
PELICANOPTS=$pelicanopts
_TEMPLATES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), \
"templates")
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 = {
'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):
if answer == str:
r = ''
@ -246,20 +175,22 @@ Please answer the following questions so this script can generate the files need
except OSError, e:
print('Error: {0}'.format(e))
conf = string.Template(TEMPLATES['pelican.conf.py'])
try:
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()
except OSError, e:
print('Error: {0}'.format(e))
if mkfile:
Makefile = string.Template(TEMPLATES['Makefile'])
try:
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()
except OSError, 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 datetime import datetime
from itertools import groupby
from jinja2 import Markup
from operator import attrgetter
logger = logging.getLogger(__name__)
@ -44,6 +45,7 @@ def slugify(value):
Took from django sources.
"""
value = Markup(value).striptags()
if type(value) == unicode:
import unicodedata
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')

View file

@ -8,8 +8,8 @@ import logging
from codecs import open
from functools import partial
from feedgenerator import Atom1Feed, Rss201rev2Feed
from jinja2 import Markup
from pelican.paginator import Paginator
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):
feed_class = Rss201rev2Feed if feed_type == 'rss' else Atom1Feed
sitename = Markup(context['SITENAME']).striptags()
feed = feed_class(
title=context['SITENAME'],
title=sitename,
link=(self.site_url + '/'),
feed_url=self.feed_url,
description=context.get('SITESUBTITLE', ''))
@ -34,8 +35,9 @@ class Writer(object):
def _add_item_to_the_feed(self, feed, item):
title = Markup(item.title).striptags()
feed.add_item(
title=item.title,
title=title,
link='%s/%s' % (self.site_url, item.url),
unique_id='tag:%s,%s:%s' % (self.site_url.replace('http://', ''),
item.date.date(), item.url),
@ -99,6 +101,12 @@ class Writer(object):
: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):
"""Render the template write the file."""
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',
]
import os
import subprocess
from contextlib import contextmanager
from tempfile import mkdtemp
from shutil import rmtree
@ -35,3 +37,21 @@ def get_article(title, slug, content, lang, extra_metadata=None):
if extra_metadata is not None:
metadata.update(extra_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
import os
import re
from pelican.generators import ArticlesGenerator
from pelican.generators import ArticlesGenerator, LessCSSGenerator
from pelican.settings import _DEFAULT_CONFIG
from .support import unittest
from .support import unittest, temporary_folder, skipIfNoExecutable
CUR_DIR = os.path.dirname(__file__)
class TestArticlesGenerator(unittest.TestCase):
def test_generate_feeds(self):
@ -49,4 +51,91 @@ class TestArticlesGenerator(unittest.TestCase):
categories = [cat.name for cat, _ in generator.categories]
# assert that the categories are ordered as expected
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)
except ImportError:
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)