1
0
Fork 0
forked from github/pelican

merge win-encoding

This commit is contained in:
Alexis Métaireau 2012-10-25 14:47:57 +02:00
commit dd299d272b
56 changed files with 1031 additions and 375 deletions

View file

@ -1,10 +1,8 @@
Jinja2>=2.4
Pygments
docutils
feedgenerator
# Tests
unittest2
pytz
mock
# Optional Packages
Markdown
blinker
BeautifulSoup
typogrify
webassets

View file

@ -23,7 +23,7 @@ different projects.
To create a virtual environment, use the following syntax::
$ mkvirtualenv pelican
$ mkvirtualenv pelican
To clone the Pelican source::
@ -65,5 +65,5 @@ Try to respect what is described in the `PEP8 specification
<http://www.python.org/dev/peps/pep-0008/>`_ when providing patches. This can be
eased via the `pep8 <http://pypi.python.org/pypi/pep8>`_ or `flake8
<http://pypi.python.org/pypi/flake8/>`_ tools, the latter of which in
particular will give you some useful hints about ways in which the
particular will give you some useful hints about ways in which the
code/formatting can be improved.

View file

@ -26,7 +26,7 @@ suggestions or problems you might have via IRC or the issue tracker.
If you want to contribute, please fork `the git repository
<https://github.com/getpelican/pelican/>`_, create a new feature branch, make
your changes, and issue a pull request. Someone will review your changes as soon
as possible. Please refer to the :doc:`How to Contribute <contribute>` section
as possible. Please refer to the :doc:`How to Contribute <contribute>` section
for more details.
You can also contribute by creating themes and improving the documentation.
@ -43,7 +43,7 @@ I'm creating my own theme. How do I use Pygments for syntax highlighting?
Pygments adds some classes to the generated content. These classes are used by
themes to style code syntax highlighting via CSS. Specifically, you can
customize the appearance of your syntax highlighting via the ``.codehilite pre``
customize the appearance of your syntax highlighting via the ``.codehilite pre``
class in your theme's CSS file. To see how various styles can be used to render
Django code, for example, you can use the demo `on the project website
<http://pygments.org/demo/15101/>`_.
@ -105,7 +105,7 @@ I'm getting a warning about feeds generated without SITEURL being set properly
In order to properly generate all URLs properly in Pelican you will need to set
``SITEURL`` to the full path of your blog. When using ``make html`` and the
default Makefile provided by the `pelican-quickstart` bootstrap script to test
build your site, it's normal to see this warning since ``SITEURL`` is
build your site, it's normal to see this warning since ``SITEURL`` is
deliberately left undefined. If configured properly no other ``make`` commands
should result in this warning.
@ -124,5 +124,5 @@ setting names). Here is an exact list of the renamed setting names::
Older 2.x themes that referenced the old setting names may not link properly.
In order to rectify this, please update your theme for compatibility with 3.0+
by changing the relevant values in your template files. For an example of
by changing the relevant values in your template files. For an example of
complete feed headers and usage please check out the ``simple`` theme.

View file

@ -77,8 +77,11 @@ Traductions
DEFAULT_LANG :
Le langage par défaut à utiliser. «*en*» par défaut ;
TRANSLATION_FEED :
Chemin du flux pour les traductions.
TRANSLATION_FEED_ATOM :
Chemin du flux Atom pour les traductions.
TRANSLATION_FEED_RSS :
Chemin du flux RSS pour les traductions.
Thèmes
@ -155,7 +158,5 @@ SITEURL :
STATIC_PATHS :
Les chemins statiques que vous voulez avoir accès sur le chemin de sortie "statique" ;
MARKDOWN_EXTENSIONS :
Liste des extentions Markdown que vous souhaitez utiliser ;

View file

@ -17,7 +17,7 @@ While the above is the simplest method, the recommended approach is to create
a virtual environment for Pelican via virtualenv_ and virtualenvwrapper_ before
installing Pelican. Assuming you've followed the virtualenvwrapper
`installation <http://virtualenvwrapper.readthedocs.org/en/latest/install.html>`_
and `shell configuration
and `shell configuration
<http://virtualenvwrapper.readthedocs.org/en/latest/install.html#shell-startup-file>`_
steps, you can then open a new terminal session and create a new virtual
environment for Pelican::
@ -26,7 +26,7 @@ environment for Pelican::
Once the virtual environment has been created and activated, Pelican can be
be installed via ``pip`` or ``easy_install`` as noted above. Alternatively, if
you have the project source, you can install Pelican using the distutils
you have the project source, you can install Pelican using the distutils
method::
$ cd path-to-Pelican-source
@ -69,6 +69,7 @@ Optionally:
* pygments, for syntax highlighting
* Markdown, for supporting Markdown as an input format
* Typogrify, for typographical enhancements
Kickstart a blog
================
@ -209,7 +210,7 @@ Pages
If you create a folder named ``pages``, all the files in it will be used to
generate static pages.
Then, use the ``DISPLAY_PAGES_ON_MENU`` setting, which will add all the pages to
Then, use the ``DISPLAY_PAGES_ON_MENU`` setting, which will add all the pages to
the menu.
If you want to exclude any pages from being linked to or listed in the menu
@ -219,7 +220,7 @@ things like making error pages that fit the generated theme of your site.
Importing an existing blog
--------------------------
It is possible to import your blog from Dotclear, WordPress, and RSS feeds using
It is possible to import your blog from Dotclear, WordPress, and RSS feeds using
a simple script. See :ref:`import`.
Translations
@ -277,14 +278,15 @@ For RestructuredText, use the code-block directive::
<indented code block goes here>
For Markdown, include the language identifier just above code blocks::
For Markdown, include the language identifier just above the code block,
indenting both the identifier and code::
A block of text.
:::identifier
<code goes here>
(indent both the identifier and code)
The specified identifier (e.g. ``python``, ``ruby``) should be one that
The specified identifier (e.g. ``python``, ``ruby``) should be one that
appears on the `list of available lexers <http://pygments.org/docs/lexers/>`_.
Publishing drafts

View file

@ -31,7 +31,7 @@ BeatifulSoup can be installed like any other Python package::
$ pip install BeautifulSoup
For pandoc, install a package for your operating system from the
For pandoc, install a package for your operating system from the
`pandoc site <http://johnmacfarlane.net/pandoc/installing.html>`_.

View file

@ -3,7 +3,7 @@ Pelican
Pelican is a static site generator, written in Python_.
* Write your weblog entries directly with your editor of choice (vim!)
* Write your weblog entries directly with your editor of choice (vim!)
in reStructuredText_ or Markdown_
* Includes a simple CLI tool to (re)generate the weblog
* Easy to interface with DVCSes and web hooks
@ -79,4 +79,4 @@ A French version of the documentation is available at :doc:`fr/index`.
.. _`Pelican documentation`: http://docs.getpelican.com/latest/
.. _`Pelican's internals`: http://docs.getpelican.com/en/latest/internals.html
.. _`#pelican on Freenode`: irc://irc.freenode.net/pelican
.. _webchat: http://webchat.freenode.net/?channels=pelican&uio=d4
.. _webchat: http://webchat.freenode.net/?channels=pelican&uio=d4

View file

@ -52,7 +52,7 @@ Take a look at the Markdown reader::
text = open(filename)
md = Markdown(extensions = ['meta', 'codehilite'])
content = md.convert(text)
metadata = {}
for name, value in md.Meta.items():
if name in _METADATA_FIELDS:
@ -81,7 +81,7 @@ both; only the existing ones will be called.
context is shared between all generators, and will be passed to the
templates. For instance, the ``PageGenerator`` ``generate_context`` method
finds all the pages, transforms them into objects, and populates the context
with them. Be careful *not* to output anything using this context at this
with them. Be careful *not* to output anything using this context at this
stage, as it is likely to change by the effect of other generators.
* ``generate_output`` is then called. And guess what is it made for? Oh,

View file

@ -64,7 +64,7 @@ In this example, we can see there are three themes available: ``notmyidea``, ``s
Note that you can combine the ``--list`` option with the ``-v`` or ``--verbose`` option to get more verbose output, like this:
.. code-block:: console
$ pelican-themes -v -l
/usr/local/lib/python2.6/dist-packages/pelican-2.6.0-py2.6.egg/pelican/themes/notmyidea
/usr/local/lib/python2.6/dist-packages/pelican-2.6.0-py2.6.egg/pelican/themes/two-column (symbolic link to `/home/skami/Dev/Python/pelican-themes/two-column')
@ -118,7 +118,7 @@ Creating symbolic links
To symbolically link a theme, you can use the ``-s`` or ``--symlink``, which works exactly as the ``--install`` option:
.. code-block:: console
# pelican-themes --symlink ~/Dev/Python/pelican-themes/two-column
In this example, the ``two-column`` theme is now symbolically linked to the Pelican themes path, so we can use it, but we can also modify it without having to reinstall it after each modification.
@ -130,11 +130,11 @@ This is useful for theme development:
$ sudo pelican-themes -s ~/Dev/Python/pelican-themes/two-column
$ pelican ~/Blog/content -o /tmp/out -t two-column
$ firefox /tmp/out/index.html
$ vim ~/Dev/Pelican/pelican-themes/two-coumn/static/css/main.css
$ vim ~/Dev/Pelican/pelican-themes/two-coumn/static/css/main.css
$ pelican ~/Blog/content -o /tmp/out -t two-column
$ cp /tmp/bg.png ~/Dev/Pelican/pelican-themes/two-coumn/static/img/bg.png
$ pelican ~/Blog/content -o /tmp/out -t two-column
$ vim ~/Dev/Pelican/pelican-themes/two-coumn/templates/index.html
$ vim ~/Dev/Pelican/pelican-themes/two-coumn/templates/index.html
$ pelican ~/Blog/content -o /tmp/out -t two-column
@ -152,7 +152,7 @@ The ``--install``, ``--remove`` and ``--symlink`` option are not mutually exclus
--symlink ~/Dev/Python/pelican-themes/two-column \
--verbose
In this example, the theme ``notmyidea-cms`` is replaced by the theme ``notmyidea-cms-fr``
In this example, the theme ``notmyidea-cms`` is replaced by the theme ``notmyidea-cms-fr``

View file

@ -16,7 +16,7 @@ To load plugins, you have to specify them in your settings file. You have two
ways to do so.
Either by specifying strings with the path to the callables::
PLUGINS = ['pelican.plugins.gravatar',]
PLUGINS = ['pelican.plugins.gravatar',]
Or by importing them and adding them to the list::
@ -48,35 +48,71 @@ which you map the signals to your plugin logic. Let's take a simple example::
signals.initialized.connect(test)
List of signals
===============
Here is the list of currently implemented signals:
========================= ============================ =========================================
Signal Arguments Description
========================= ============================ =========================================
initialized pelican object
article_generate_context article_generator, metadata
article_generator_init article_generator invoked in the ArticlesGenerator.__init__
pages_generate_context pages_generator, metadata
pages_generator_init pages_generator invoked in the PagesGenerator.__init__
========================= ============================ =========================================
============================= ============================ ===========================================================================
Signal Arguments Description
============================= ============================ ===========================================================================
initialized pelican object
finalized pelican object invoked after all the generators are executed and just before pelican exits
usefull for custom post processing actions, such as:
- minifying js/css assets.
- notify/ping search engines with an updated sitemap.
article_generate_context article_generator, metadata
article_generator_init article_generator invoked in the ArticlesGenerator.__init__
article_generator_finalized article_generator invoked at the end of ArticlesGenerator.generate_context
get_generators generators invoked in Pelican.get_generator_classes,
can return a Generator, or several
generator in a tuple or in a list.
pages_generate_context pages_generator, metadata
pages_generator_init pages_generator invoked in the PagesGenerator.__init__
============================= ============================ ===========================================================================
The list is currently small, don't hesitate to add signals and make a pull
request if you need them!
.. note::
The signal ``content_object_init`` can send different type of object as
argument. If you want to register only one type of object then you will
need to specify the sender when you are connecting to the signal.
::
from pelican import signals
from pelican import contents
def test(sender, instance):
print "%s : %s content initialized !!" % (sender, instance)
def register():
signals.content_object_init.connect(test, sender=contents.Article)
List of plugins
===============
Not all the list are described here, but a few of them have been extracted from
the Pelican core and provided in ``pelican.plugins``. They are described here:
The following plugins are currently included with Pelican under ``pelican.plugins``:
Tag cloud
---------
* `GitHub activity`_
* `Global license`_
* `Gravatar`_
* `HTML tags for reStructuredText`_
* `Related posts`_
* `Sitemap`_
Translation
-----------
Ideas for plugins that haven't been written yet:
* Tag cloud
* Translation
Plugin descriptions
===================
GitHub activity
---------------
@ -108,3 +144,131 @@ variable, as in the example::
``github_activity`` is a list of lists. The first element is the title
and the second element is the raw HTML from GitHub.
Global license
--------------
This plugin allows you to define a LICENSE setting and adds the contents of that
license variable to the article's context, making that variable available to use
from within your theme's templates.
Gravatar
--------
This plugin assigns the ``author_gravatar`` variable to the Gravatar URL and
makes the variable available within the article's context. You can add
AUTHOR_EMAIL to your settings file to define the default author's email
address. Obviously, that email address must be associated with a Gravatar
account.
Alternatively, you can provide an email address from within article metadata::
:email: john.doe@example.com
If the email address is defined via at least one of the two methods above,
the ``author_gravatar`` variable is added to the article's context.
HTML tags for reStructuredText
------------------------------
This plugin allows you to use HTML tags from within reST documents. Following
is a usage example, which is in this case a contact form::
.. html::
<form method="GET" action="mailto:some email">
<p>
<input type="text" placeholder="Subject" name="subject">
<br />
<textarea name="body" placeholder="Message">
</textarea>
<br />
<input type="reset"><input type="submit">
</p>
</form>
Related posts
-------------
This plugin adds the ``related_posts`` variable to the article's context.
To enable, add the following to your settings file::
from pelican.plugins import related_posts
PLUGINS = [related_posts]
You can then use the ``article.related_posts`` variable in your templates.
For example::
{% if article.related_posts %}
<ul>
{% for related_post in article.related_posts %}
<li>{{ related_post }}</li>
{% endfor %}
</ul>
{% endif %}
Sitemap
-------
The sitemap plugin generates plain-text or XML sitemaps. You can use the
``SITEMAP`` variable in your settings file to configure the behavior of the
plugin.
The ``SITEMAP`` variable must be a Python dictionary, it can contain three keys:
- ``format``, which sets the output format of the plugin (``xml`` or ``txt``)
- ``priorities``, which is a dictionary with three keys:
- ``articles``, the priority for the URLs of the articles and their
translations
- ``pages``, the priority for the URLs of the static pages
- ``indexes``, the priority for the URLs of the index pages, such as tags,
author pages, categories indexes, archives, etc...
All the values of this dictionary must be decimal numbers between ``0`` and ``1``.
- ``changefreqs``, which is a dictionary with three items:
- ``articles``, the update frequency of the articles
- ``pages``, the update frequency of the pages
- ``indexes``, the update frequency of the index pages
Valid frequency values are ``always``, ``hourly``, ``daily``, ``weekly``, ``monthly``,
``yearly`` and ``never``.
If a key is missing or a value is incorrect, it will be replaced with the
default value.
The sitemap is saved in ``<output_path>/sitemap.<format>``.
.. note::
``priorities`` and ``changefreqs`` are informations for search engines.
They are only used in the XML sitemaps.
For more information: <http://www.sitemaps.org/protocol.html#xmlTagDefinitions>
**Example**
Here is an example configuration (it's also the default settings):
.. code-block:: python
PLUGINS=['pelican.plugins.sitemap',]
SITEMAP = {
'format': 'xml',
'priorities': {
'articles': 0.5,
'indexes': 0.5,
'pages': 0.5
},
'changefreqs': {
'articles': 'monthly',
'indexes': 'daily',
'pages': 'monthly'
}
}

View file

@ -4,7 +4,7 @@ Some history about Pelican
.. warning::
This page comes from a report the original author (Alexis Métaireau) wrote
right after writing Pelican, in December 2010. The information may not be
right after writing Pelican, in December 2010. The information may not be
up-to-date.
Pelican is a simple static blog generator. It parses markup files
@ -113,7 +113,7 @@ concepts. Here is what happens when calling the ``generate_context``
method:
* Read the folder “path”, looking for restructured text files, load
each of them, and construct a content object (``Article``) with it. To do so,
each of them, and construct a content object (``Article``) with it. To do so,
use ``Reader`` objects.
* Update the ``context`` with all those articles.

View file

@ -16,6 +16,9 @@ False, None, etc.), dictionaries, or tuples should *not* be enclosed in
quotation marks. All other values (i.e., strings) *must* be enclosed in
quotation marks.
Unless otherwise specified, settings that refer to paths can be either absolute or relative to the
configuration file.
The settings you define in the configuration file will be passed to the
templates, which allows you to use your settings to add site-wide content.
@ -43,9 +46,12 @@ Setting name (default value) What doe
If tuple object, it will instead generate the
default datetime object by passing the tuple to
the datetime.datetime constructor.
`JINJA_EXTENSIONS` (``[]``) A list of any Jinja2 extensions you want to use.
`DELETE_OUTPUT_DIRECTORY` (``False``) Delete the content of the output directory before
generating new files.
`FILES_TO_COPY` (``()``) A list of files to copy from the source (inside the content
directory) to the destination (inside the output directory).
For example: ``(('extra/robots.txt', 'robots.txt'),)``.
`JINJA_EXTENSIONS` (``[]``) A list of any Jinja2 extensions you want to use.
`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
@ -58,14 +64,20 @@ Setting name (default value) What doe
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.
`PATH` (``None``) Path to content directory to be processed by Pelican.
`PAGE_DIR` (``'pages'``) Directory to look at for pages, relative to `PATH`.
`PAGE_EXCLUDES` (``()``) A list of directories to exclude when looking for pages.
`ARTICLE_DIR` (``''``) Directory to look at for articles.
`ARTICLE_DIR` (``''``) Directory to look at for articles, relative to `PATH`.
`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`.
`OUTPUT_SOURCES` (``False``) Set to True if you want to copy the articles and pages in their
original format (e.g. Markdown or ReStructeredText) to the
specified OUTPUT_PATH.
`OUTPUT_SOURCES_EXTENSION` (``.text``) Controls the extension that will be used by the SourcesGenerator.
Defaults to ``.text``. If not a valid string the default value
will be used.
`RELATIVE_URLS` (``True``) Defines whether Pelican should use document-relative URLs or
not. If set to ``False``, Pelican will use the SITEURL
setting to construct absolute URLs.
@ -100,7 +112,12 @@ Setting name (default value) What doe
This only applies if your content does not otherwise
specify a summary. Setting to None will cause the summary
to be a copy of the original content.
`EXTRA_TEMPLATES_PATHS` (``[]``) A list of paths you want Jinja2 to look for the templates.
Can be used to separate templates from the theme.
Example: projects, resume, profile ...
This templates need to use ``DIRECT_TEMPLATES`` setting
`MARKDOWN_EXTENSIONS` (``['toc',]``) A list of any Markdown extensions you want to use.
===================================================================== =====================================================================
.. [#] Default is the system locale.
@ -144,37 +161,37 @@ Also, you can use other file metadata attributes as well:
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'
* ARTICLE_URL = ``'posts/{date:%Y}/{date:%b}/{date:%d}/{slug}/'``
* ARTICLE_SAVE_AS = ``'posts/{date:%Y}/{date:%b}/{date:%d}/{slug}/index.html'``
This would save your articles in something like '/posts/2011/Aug/07/sample-post/index.html',
and the URL to this would be '/posts/2011/Aug/07/sample-post/'.
================================================ =====================================================
Setting name (default value) what does it do?
================================================ =====================================================
`ARTICLE_URL` ('{slug}.html') The URL to refer to an ARTICLE.
`ARTICLE_SAVE_AS` ('{slug}.html') The place where we will save an article.
`ARTICLE_LANG_URL` ('{slug}-{lang}.html') The URL to refer to an ARTICLE which doesn't use the
default language.
`ARTICLE_LANG_SAVE_AS` ('{slug}-{lang}.html' The place where we will save an article which
doesn't use the default language.
`PAGE_URL` ('pages/{slug}.html') The URL we will use to link to a page.
`PAGE_SAVE_AS` ('pages/{slug}.html') The location we will save the page.
`PAGE_LANG_URL` ('pages/{slug}-{lang}.html') The URL we will use to link to a page which doesn't
use the default language.
`PAGE_LANG_SAVE_AS` ('pages/{slug}-{lang}.html') The location we will save the page which doesn't
use the default language.
`AUTHOR_URL` ('author/{name}.html') The URL to use for an author.
`AUTHOR_SAVE_AS` ('author/{name}.html') The location to save an author.
`CATEGORY_URL` ('category/{name}.html') The URL to use for 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_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.
================================================ =====================================================
==================================================== =====================================================
Setting name (default value) What does it do?
==================================================== =====================================================
`ARTICLE_URL` (``'{slug}.html'``) The URL to refer to an ARTICLE.
`ARTICLE_SAVE_AS` (``'{slug}.html'``) The place where we will save an article.
`ARTICLE_LANG_URL` (``'{slug}-{lang}.html'``) The URL to refer to an ARTICLE which doesn't use the
default language.
`ARTICLE_LANG_SAVE_AS` (``'{slug}-{lang}.html'``) The place where we will save an article which
doesn't use the default language.
`PAGE_URL` (``'pages/{slug}.html'``) The URL we will use to link to a page.
`PAGE_SAVE_AS` (``'pages/{slug}.html'``) The location we will save the page.
`PAGE_LANG_URL` (``'pages/{slug}-{lang}.html'``) The URL we will use to link to a page which doesn't
use the default language.
`PAGE_LANG_SAVE_AS` (``'pages/{slug}-{lang}.html'``) The location we will save the page which doesn't
use the default language.
`AUTHOR_URL` (``'author/{name}.html'``) The URL to use for an author.
`AUTHOR_SAVE_AS` (``'author/{name}.html'``) The location to save an author.
`CATEGORY_URL` (``'category/{name}.html'``) The URL to use for 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_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::
@ -197,14 +214,14 @@ Have a look at `the wikipedia page`_ to get a list of valid timezone values.
Date format and locale
----------------------
If no DATE_FORMAT is set, fall back to DEFAULT_DATE_FORMAT. If you need to
If no DATE_FORMATS is set, fall back to DEFAULT_DATE_FORMAT. If you need to
maintain multiple languages with different date formats, you can set this dict
using language name (``lang`` in your posts) as key. Regarding available format
codes, see `strftime document of python`_ :
.. parsed-literal::
DATE_FORMAT = {
DATE_FORMATS = {
'en': '%a, %d %b %Y',
'jp': '%Y-%m-%d(%a)',
}
@ -223,13 +240,13 @@ above:
.. parsed-literal::
# On Unix/Linux
DATE_FORMAT = {
DATE_FORMATS = {
'en': ('en_US','%a, %d %b %Y'),
'jp': ('ja_JP','%Y-%m-%d(%a)'),
}
# On Windows
DATE_FORMAT = {
DATE_FORMATS = {
'en': ('usa','%a, %d %b %Y'),
'jp': ('jpn','%Y-%m-%d(%a)'),
}
@ -313,10 +330,10 @@ You can use the following settings to configure the pagination.
================================================ =====================================================
Setting name (default value) What does it do?
================================================ =====================================================
`DEFAULT_ORPHANS` (0) The minimum number of articles allowed on the
`DEFAULT_ORPHANS` (``0``) The minimum number of articles allowed on the
last page. Use this when you don't want to
have a last page with very few articles.
`DEFAULT_PAGINATION` (False) The maximum number of articles to include on a
`DEFAULT_PAGINATION` (``False``) The maximum number of articles to include on a
page, not including orphans. False to disable
pagination.
================================================ =====================================================
@ -330,9 +347,9 @@ following settings.
================================================ =====================================================
Setting name (default value) What does it do?
================================================ =====================================================
`TAG_CLOUD_STEPS` (4) Count of different font sizes in the tag
`TAG_CLOUD_STEPS` (``4``) Count of different font sizes in the tag
cloud.
`TAG_CLOUD_MAX_ITEMS` (100) Maximum number of tags in the cloud.
`TAG_CLOUD_MAX_ITEMS` (``100``) Maximum number of tags in the cloud.
================================================ =====================================================
The default theme does not support tag clouds, but it is pretty easy to add::
@ -352,12 +369,13 @@ Translations
Pelican offers a way to translate articles. See the Getting Started section for
more information.
================================================ =====================================================
Setting name (default value) What does it do?
================================================ =====================================================
`DEFAULT_LANG` (``'en'``) The default language to use.
`TRANSLATION_FEED` ('feeds/all-%s.atom.xml'[3]_) Where to put the feed for translations.
================================================ =====================================================
===================================================== =====================================================
Setting name (default value) What does it do?
===================================================== =====================================================
`DEFAULT_LANG` (``'en'``) The default language to use.
`TRANSLATION_FEED_ATOM` ('feeds/all-%s.atom.xml'[3]_) Where to put the Atom feed for translations.
`TRANSLATION_FEED_RSS` (``None``, i.e. no RSS) Where to put the RSS feed for translations.
===================================================== =====================================================
.. [3] %s is the language
@ -373,19 +391,19 @@ Setting name (default value) What does it do?
alphabetical order; default lists alphabetically.)
================================================ =====================================================
Theming
=======
Themes
======
Theming is addressed in a dedicated section (see :ref:`theming-pelican`).
However, here are the settings that are related to theming.
Creating Pelican themes is addressed in a dedicated section (see :ref:`theming-pelican`).
However, here are the settings that are related to themes.
================================================ =====================================================
Setting name (default value) What does it do?
================================================ =====================================================
`THEME` Theme to use to produce the output. Can be the
complete static path to a theme folder, or
chosen between the list of default themes (see
below)
`THEME` Theme to use to produce the output. Can be a relative
or absolute path to a theme folder, or the name of a
default theme or a theme installed via
``pelican-themes`` (see below).
`THEME_STATIC_PATHS` (``['static']``) Static theme paths you want to copy. Default
value is `static`, but if your theme has
other static paths, you can put them here.
@ -393,22 +411,32 @@ Setting name (default value) What does it do?
`WEBASSETS` (``False``) Asset management with `webassets` (see below)
================================================ =====================================================
By default, two themes are available. You can specify them using the `-t` option:
By default, two themes are available. You can specify them using the `THEME` setting or by passing the
``-t`` option to the ``pelican`` command:
* notmyidea
* simple (a synonym for "full text" :)
You can define your own theme too, and specify its placement in the same
manner. (Be sure to specify the full absolute path to it.)
Here is :doc:`a guide on how to create your theme <themes>`
You can find a list of themes at http://github.com/getpelican/pelican-themes.
* simple (a synonym for "plain text" :)
There are a number of other themes available at http://github.com/getpelican/pelican-themes.
Pelican comes with :doc:`pelican-themes`, a small script for managing themes.
The `notmyidea` theme can make good use of the following settings. I recommend
using them in your themes as well.
You can define your own theme, either by starting from scratch or by duplicating
and modifying a pre-existing theme. Here is :doc:`a guide on how to create your theme <themes>`.
Following are example ways to specify your preferred theme::
# Specify name of a built-in theme
THEME = "notmyidea"
# Specify name of a theme installed via the pelican-themes tool
THEME = "chunk"
# Specify a customized theme, via path relative to the settings file
THEME = "themes/mycustomtheme"
# Specify a customized theme, via absolute path
THEME = "~/projects/mysite/themes/mycustomtheme"
The built-in `notmyidea` theme can make good use of the following settings. Feel
free to use them in your themes as well.
======================= =======================================================
Setting name What does it do ?
@ -444,26 +472,27 @@ adding the following to your configuration::
Asset management
----------------
The `WEBASSETS` setting allows to use the `webassets`_ module to manage assets
(css, js). The module must first be installed::
The `WEBASSETS` setting allows you to use the `webassets`_ module to manage
assets such as CSS and JS files. The module must first be installed::
pip install webassets
`webassets` allows to concatenate your assets and to use almost all of the
hype tools of the moment (see the `documentation`_):
The `webassets` module allows you to perform a number of useful asset management
functions, including:
* css minifier (`cssmin`, `yuicompressor`, ...)
* css compiler (`less`, `sass`, ...)
* js minifier (`uglifyjs`, `yuicompressor`, `closure`, ...)
* CSS minifier (`cssmin`, `yuicompressor`, ...)
* CSS compiler (`less`, `sass`, ...)
* JS minifier (`uglifyjs`, `yuicompressor`, `closure`, ...)
Others filters include gzip compression, integration of images in css with
`datauri` and more. Webassets also append a version identifier to your asset
url to convince browsers to download new versions of your assets when you use
far future expires headers.
Others filters include gzip compression, integration of images in CSS via data
URIs, and more. `webassets` can also append a version identifier to your asset
URL to convince browsers to download new versions of your assets when you use
far-future expires headers. Please refer to the `webassets documentation`_ for
more information.
When using it with Pelican, `webassets` is configured to process assets in the
``OUTPUT_PATH/theme`` directory. You can use it in your templates with a
template tag, for example:
When using with Pelican, `webassets` is configured to process assets in the
``OUTPUT_PATH/theme`` directory. You can use `webassets` in your templates by
including one or more template tags. For example...
.. code-block:: jinja
@ -471,43 +500,43 @@ template tag, for example:
<link rel="stylesheet" href="{{ ASSET_URL }}">
{% endassets %}
will produce a minified css file with the version identifier:
... will produce a minified css file with a version identifier:
.. code-block:: html
<link href="http://{SITEURL}/theme/css/style.min.css?b3a7c807" rel="stylesheet">
The filters can be combined, for example to use the `sass` compiler and minify
the output::
These filters can be combined. Here is an example that uses the SASS compiler
and minifies the output:
.. code-block:: jinja
{% assets filters="sass,cssmin", output="css/style.min.css", "css/style.scss" %}
<link rel="stylesheet" href="{{ ASSET_URL }}">
{% endassets %}
{% assets filters="sass,cssmin", output="css/style.min.css", "css/style.scss" %}
<link rel="stylesheet" href="{{ ASSET_URL }}">
{% endassets %}
Another example for javascript:
Another example for Javascript:
.. code-block:: jinja
{% assets filters="uglifyjs,gzip", output="js/packed.js", "js/jquery.js", "js/base.js", "js/widgets.js" %}
<script src="{{ ASSETS_URL }}"></script>
<script src="{{ ASSET_URL }}"></script>
{% endassets %}
will produce a minified and gzipped js file:
The above will produce a minified and gzipped JS file:
.. code-block:: html
<script src="http://{SITEURL}/theme/js/packed.js?00703b9d"></script>
Pelican's debug mode is propagated to webassets to disable asset packaging,
Pelican's debug mode is propagated to `webassets` to disable asset packaging
and instead work with the uncompressed assets. However, this also means that
the `less` and `sass` files are not compiled, this should be fixed in a future
version of webassets (cf. the related `bug report
the LESS and SASS files are not compiled. This should be fixed in a future
version of `webassets` (cf. the related `bug report
<https://github.com/getpelican/pelican/issues/481>`_).
.. _webassets: https://github.com/miracle2k/webassets
.. _documentation: http://webassets.readthedocs.org/en/latest/builtin_filters.html
.. _webassets documentation: http://webassets.readthedocs.org/en/latest/builtin_filters.html
Example settings
================

View file

@ -35,7 +35,7 @@ To make your own theme, you must follow the following structure::
* `templates` contains all the templates that will be used to generate the content.
I've just put the mandatory templates here; you can define your own if it helps
you keep things organized while creating your theme.
Templates and variables
=======================
@ -44,7 +44,7 @@ This document describes which templates should exist in a theme, and which
variables will be passed to each template at generation time.
All templates will receive the variables defined in your settings file, if they
are in all-caps. You can access them directly.
are in all-caps. You can access them directly.
Common variables
----------------
@ -55,14 +55,14 @@ All of these settings will be available to all templates.
Variable Description
============= ===================================================
articles The list of articles, ordered descending by date
All the elements are `Article` objects, so you can
All the elements are `Article` objects, so you can
access their attributes (e.g. title, summary, author
etc.)
dates The same list of articles, but ordered by date,
ascending
tags A key-value dict containing the tags (the keys) and
the list of respective articles (the values)
categories A key-value dict containing the categories (keys)
categories A key-value dict containing the categories (keys)
and the list of respective articles (values)
pages The list of pages
============= ===================================================
@ -92,8 +92,8 @@ author.html
This template will be processed for each of the existing authors, with
output generated at output/author/`author_name`.html.
If pagination is active, subsequent pages will reside at
output/author/`author_name``n`.html.
If pagination is active, subsequent pages will reside as defined by setting
AUTHOR_SAVE_AS (`Default:` output/author/`author_name'n'`.html).
=================== ===================================================
Variable Description
@ -108,8 +108,8 @@ dates_paginator A paginator object for the article list, ordered by
date, ascending.
dates_page The current page of articles, ordered by date,
ascending.
page_name 'author/`author_name`' -- useful for pagination
links
page_name AUTHOR_URL where everything after `{slug}` is
removed -- useful for pagination links
=================== ===================================================
category.html
@ -118,8 +118,8 @@ category.html
This template will be processed for each of the existing categories, with
output generated at output/category/`category_name`.html.
If pagination is active, subsequent pages will reside at
output/category/`category_name``n`.html.
If pagination is active, subsequent pages will reside as defined by setting
CATEGORY_SAVE_AS (`Default:` output/category/`category_name'n'`.html).
=================== ===================================================
Variable Description
@ -134,8 +134,8 @@ dates_paginator A paginator object for the list of articles,
ordered by date, ascending
dates_page The current page of articles, ordered by date,
ascending
page_name 'category/`category_name`' -- useful for pagination
links
page_name CATEGORY_URL where everything after `{slug}` is
removed -- useful for pagination links
=================== ===================================================
article.html
@ -170,8 +170,8 @@ tag.html
This template will be processed for each tag, with corresponding .html files
saved as output/tag/`tag_name`.html.
If pagination is active, subsequent pages will reside at
output/tag/`tag_name``n`.html.
If pagination is active, subsequent pages will reside as defined in setting
TAG_SAVE_AS (`Default:` output/tag/`tag_name'n'`.html).
=================== ===================================================
Variable Description
@ -182,11 +182,12 @@ dates Articles related to this tag, but ordered by date,
ascending
articles_paginator A paginator object for the list of articles
articles_page The current page of articles
dates_paginator A paginator object for the list of articles,
dates_paginator A paginator object for the list of articles,
ordered by date, ascending
dates_page The current page of articles, ordered by date,
ascending
page_name 'tag/`tag_name`' -- useful for pagination links
page_name TAG_URL where everything after `{slug}` is removed
-- useful for pagination links
=================== ===================================================
Feeds
@ -202,7 +203,8 @@ Here is a complete list of the feed variables::
CATEGORY_FEED_RSS
TAG_FEED_ATOM
TAG_FEED_RSS
TRANSLATION_FEED
TRANSLATION_FEED_ATOM
TRANSLATION_FEED_RSS
Inheritance

View file

@ -12,11 +12,11 @@ file generator, we can take advantage of this.
User Pages
----------
GitHub allows you to create user pages in the form of ``username.github.com``.
GitHub allows you to create user pages in the form of ``username.github.com``.
Whatever is created in the master branch will be published. For this purpose,
just the output generated by Pelican needs to pushed to GitHub.
So given a repository containing your articles, just run Pelican over the posts
So given a repository containing your articles, just run Pelican over the posts
and deploy the master branch to GitHub::
$ pelican -s pelican.conf.py ./path/to/posts -o /path/to/output
@ -35,7 +35,7 @@ really easy, which can be installed via::
$ pip install ghp-import
Then, given a repository containing your articles, you would simply run
Then, given a repository containing your articles, you would simply run
Pelican and upload the output to GitHub::
$ pelican -s pelican.conf.py .

View file

@ -1,4 +1,3 @@
import copy
import os
import re
import sys
@ -9,10 +8,12 @@ import argparse
from pelican import signals
from pelican.generators import (ArticlesGenerator, PagesGenerator,
StaticGenerator, PdfGenerator, LessCSSGenerator)
StaticGenerator, PdfGenerator,
LessCSSGenerator, SourceFileGenerator)
from pelican.log import init
from pelican.settings import read_settings, _DEFAULT_CONFIG
from pelican.utils import clean_output_dir, files_changed, file_changed, NoFilesError
from pelican.settings import read_settings
from pelican.utils import (clean_output_dir, files_changed, file_changed,
NoFilesError)
from pelican.writers import Writer
__major__ = 3
@ -24,42 +25,21 @@ logger = logging.getLogger(__name__)
class Pelican(object):
def __init__(self, settings=None, path=None, theme=None, output_path=None,
markup=None, delete_outputdir=False, plugin_path=None):
"""Read the settings, and performs some checks on the environment
before doing anything else.
def __init__(self, settings):
"""
Pelican initialisation, performs some checks on the environment before
doing anything else.
"""
if settings is None:
settings = copy.deepcopy(_DEFAULT_CONFIG)
self.path = path or settings['PATH']
if not self.path:
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]
# 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)
self.markup = markup or settings['MARKUP']
self.delete_outputdir = delete_outputdir \
or settings['DELETE_OUTPUT_DIRECTORY']
# find the theme in pelican.theme if the given one does not exists
if not os.path.exists(self.theme):
theme_path = os.sep.join([os.path.dirname(
os.path.abspath(__file__)), "themes/%s" % self.theme])
if os.path.exists(theme_path):
self.theme = theme_path
else:
raise Exception("Impossible to find the theme %s" % theme)
self.path = settings['PATH']
self.theme = settings['THEME']
self.output_path = settings['OUTPUT_PATH']
self.markup = settings['MARKUP']
self.delete_outputdir = settings['DELETE_OUTPUT_DIRECTORY']
self.init_path()
self.init_plugins()
@ -78,7 +58,7 @@ class Pelican(object):
logger.debug("Loading plugin `{0}' ...".format(plugin))
plugin = __import__(plugin, globals(), locals(), 'module')
logger.debug("Registering plugin `{0}' ...".format(plugin.__name__))
logger.debug("Registering plugin `{0}'".format(plugin.__name__))
plugin.register()
def _handle_deprecation(self):
@ -139,8 +119,16 @@ class Pelican(object):
'Modify CATEGORY_FEED to CATEGORY_FEED_ATOM in your settings and '
'theme for the same behavior. Temporarily setting '
'CATEGORY_FEED_ATOM for backwards compatibility.')
self.settings['CATEGORY_FEED_ATOM'] = self.settings['CATEGORY_FEED']
self.settings['CATEGORY_FEED_ATOM'] =\
self.settings['CATEGORY_FEED']
if self.settings.get('TRANSLATION_FEED', False):
logger.warning('Found deprecated `TRANSLATION_FEED` in settings. '
'Modify TRANSLATION_FEED to TRANSLATION_FEED_ATOM in your '
'settings and theme for the same behavior. Temporarily setting '
'TRANSLATION_FEED_ATOM for backwards compatibility.')
self.settings['TRANSLATION_FEED_ATOM'] =\
self.settings['TRANSLATION_FEED']
def run(self):
"""Run the generators and return"""
@ -179,12 +167,28 @@ class Pelican(object):
if hasattr(p, 'generate_output'):
p.generate_output(writer)
signals.finalized.send(self)
def get_generator_classes(self):
generators = [StaticGenerator, ArticlesGenerator, PagesGenerator]
if self.settings['PDF_GENERATOR']:
generators.append(PdfGenerator)
if self.settings['LESS_GENERATOR']: # can be True or PATH to lessc
generators.append(LessCSSGenerator)
if self.settings['OUTPUT_SOURCES']:
generators.append(SourceFileGenerator)
for pair in signals.get_generators.send(self):
(funct, value) = pair
if not isinstance(value, (tuple, list)):
value = (value, )
for v in value:
if isinstance(v, type):
logger.debug('Found generator: {0}'.format(v))
generators.append(v)
return generators
def get_writer(self):
@ -241,11 +245,26 @@ def parse_arguments():
return parser.parse_args()
def get_instance(args):
markup = [a.strip().lower() for a in args.markup.split(',')]\
if args.markup else None
def get_config(args):
config = {}
if args.path:
config['PATH'] = os.path.abspath(os.path.expanduser(args.path))
if args.output:
config['OUTPUT_PATH'] = \
os.path.abspath(os.path.expanduser(args.output))
if args.markup:
config['MARKUP'] = [a.strip().lower() for a in args.markup.split(',')]
if args.theme:
abstheme = os.path.abspath(os.path.expanduser(args.theme))
config['THEME'] = abstheme if os.path.exists(abstheme) else args.theme
if args.delete_outputdir is not None:
config['DELETE_OUTPUT_DIRECTORY'] = args.delete_outputdir
return config
settings = read_settings(args.settings)
def get_instance(args):
settings = read_settings(args.settings, override=get_config(args))
cls = settings.get('PELICAN_CLASS')
if isinstance(cls, basestring):
@ -253,15 +272,12 @@ def get_instance(args):
module = __import__(module)
cls = getattr(module, cls_name)
return cls(settings, args.path, args.theme, args.output, markup,
args.delete_outputdir)
return cls(settings)
def main():
args = parse_arguments()
init(args.verbosity)
# Split the markup languages only if some have been given. Otherwise,
# populate the variable with None.
pelican = get_instance(args)
try:
@ -276,7 +292,7 @@ def main():
# have.
if files_changed(pelican.path, pelican.markup) or \
files_changed(pelican.theme, ['']):
if files_found_error == False:
if not files_found_error:
files_found_error = True
pelican.run()
@ -292,9 +308,11 @@ def main():
logger.warning("Keyboard interrupt, quitting.")
break
except NoFilesError:
if files_found_error == True:
logger.warning("No valid files found in content. Nothing to generate.")
if files_found_error:
logger.warning("No valid files found in content. "
"Nothing to generate.")
files_found_error = False
time.sleep(1) # sleep to avoid cpu load
except Exception, e:
logger.warning(
"Caught exception \"{}\". Reloading.".format(e)

View file

@ -11,7 +11,7 @@ from sys import platform, stdin
from pelican.settings import _DEFAULT_CONFIG
from pelican.utils import slugify, truncate_html_words
from pelican import signals
logger = logging.getLogger(__name__)
@ -106,6 +106,8 @@ class Page(object):
if 'summary' in metadata:
self._summary = metadata['summary']
signals.content_object_init.send(self.__class__, instance=self)
def check_properties(self):
"""test that each mandatory property is set."""
for prop in self.mandatory_properties:
@ -195,15 +197,23 @@ class URLWrapper(object):
def __unicode__(self):
return self.name
def _from_settings(self, key):
def _from_settings(self, key, get_page_name=False):
"""Returns URL information as defined in settings.
When get_page_name=True returns URL without anything after {slug}
e.g. if in settings: CATEGORY_URL="cat/{slug}.html" this returns "cat/{slug}"
Useful for pagination."""
setting = "%s_%s" % (self.__class__.__name__.upper(), key)
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())
if get_page_name:
return unicode(value[:value.find('{slug}') + len('{slug}')]).format(**self.as_dict())
else:
return unicode(value).format(**self.as_dict())
page_name = property(functools.partial(_from_settings, key='URL', get_page_name=True))
url = property(functools.partial(_from_settings, key='URL'))
save_as = property(functools.partial(_from_settings, key='SAVE_AS'))

View file

@ -6,6 +6,7 @@ import logging
import datetime
import subprocess
from codecs import open
from collections import defaultdict
from functools import partial
from itertools import chain
@ -16,7 +17,7 @@ from jinja2.exceptions import TemplateNotFound
from pelican.contents import Article, Page, Category, is_valid_content
from pelican.readers import read_file
from pelican.utils import copy, process_translations, open
from pelican.utils import copy, process_translations
from pelican import signals
@ -36,8 +37,11 @@ class Generator(object):
# templates cache
self._templates = {}
self._templates_path = os.path.expanduser(
os.path.join(self.theme, 'templates'))
self._templates_path = []
self._templates_path.append(os.path.expanduser(
os.path.join(self.theme, 'templates')))
self._templates_path += self.settings.get('EXTRA_TEMPLATES_PATHS', [])
theme_path = os.path.dirname(os.path.abspath(__file__))
@ -124,7 +128,8 @@ class ArticlesGenerator(Generator):
def generate_feeds(self, writer):
"""Generate the feeds from the current context, and output files."""
if self.settings.get('FEED_ATOM') is None and self.settings.get('FEED_RSS') is None:
if self.settings.get('FEED_ATOM') is None \
and self.settings.get('FEED_RSS') is None:
return
elif self.settings.get('SITEURL') is '':
logger.warning(
@ -150,7 +155,8 @@ class ArticlesGenerator(Generator):
self.settings['CATEGORY_FEED_RSS'] % cat,
feed_type='rss')
if self.settings.get('TAG_FEED_ATOM') or self.settings.get('TAG_FEED_RSS'):
if self.settings.get('TAG_FEED_ATOM') \
or self.settings.get('TAG_FEED_RSS'):
for tag, arts in self.tags.items():
arts.sort(key=attrgetter('date'), reverse=True)
if self.settings.get('TAG_FEED_ATOM'):
@ -162,15 +168,21 @@ class ArticlesGenerator(Generator):
self.settings['TAG_FEED_RSS'] % tag,
feed_type='rss')
if self.settings.get('TRANSLATION_FEED'):
if self.settings.get('TRANSLATION_FEED_ATOM') or \
self.settings.get('TRANSLATION_FEED_RSS'):
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)
if self.settings.get('TRANSLATION_FEED_ATOM'):
writer.write_feed(items, self.context,
self.settings['TRANSLATION_FEED_ATOM'] % lang)
if self.settings.get('TRANSLATION_FEED_RSS'):
writer.write_feed(items, self.context,
self.settings['TRANSLATION_FEED_RSS'] % lang,
feed_type='rss')
def generate_articles(self, write):
"""Generate the articles."""
@ -188,7 +200,7 @@ class ArticlesGenerator(Generator):
save_as = self.settings.get("%s_SAVE_AS" % template.upper(),
'%s.html' % template)
if not save_as:
continue
continue
write(save_as, self.get_template(template),
self.context, blog=True, paginated=paginated,
@ -203,7 +215,7 @@ class ArticlesGenerator(Generator):
write(tag.save_as, tag_template, self.context, tag=tag,
articles=articles, dates=dates,
paginated={'articles': articles, 'dates': dates},
page_name=u'tag/%s' % tag)
page_name=tag.page_name)
def generate_categories(self, write):
"""Generate category pages."""
@ -213,7 +225,7 @@ class ArticlesGenerator(Generator):
write(cat.save_as, category_template, self.context,
category=cat, articles=articles, dates=dates,
paginated={'articles': articles, 'dates': dates},
page_name=u'category/%s' % cat)
page_name=cat.page_name)
def generate_authors(self, write):
"""Generate Author pages."""
@ -223,7 +235,7 @@ class ArticlesGenerator(Generator):
write(aut.save_as, author_template, self.context,
author=aut, articles=articles, dates=dates,
paginated={'articles': articles, 'dates': dates},
page_name=u'author/%s' % aut)
page_name=aut.page_name)
def generate_drafts(self, write):
"""Generate drafts pages."""
@ -269,7 +281,7 @@ class ArticlesGenerator(Generator):
if 'category' not in metadata:
if os.path.dirname(f) == article_path: # if the article is not in a subdirectory
category = self.settings['DEFAULT_CATEGORY']
category = self.settings['DEFAULT_CATEGORY']
else:
category = os.path.basename(os.path.dirname(f))\
.decode('utf-8')
@ -352,10 +364,12 @@ class ArticlesGenerator(Generator):
self.authors = list(self.authors.items())
self.authors.sort(key=lambda item: item[0].name)
self._update_context(('articles', 'dates', 'tags', 'categories',
'tag_cloud', 'authors', 'related_posts'))
signals.article_generator_finalized.send(self)
def generate_output(self, writer):
self.generate_feeds(writer)
self.generate_pages(writer)
@ -370,7 +384,7 @@ class PagesGenerator(Generator):
self.hidden_translations = []
super(PagesGenerator, self).__init__(*args, **kwargs)
signals.pages_generator_init.send(self)
def generate_context(self):
all_pages = []
hidden_pages = []
@ -378,7 +392,7 @@ class PagesGenerator(Generator):
os.path.join(self.path, self.settings['PAGE_DIR']),
exclude=self.settings['PAGE_EXCLUDES']):
try:
content, metadata = read_file(f)
content, metadata = read_file(f, settings=self.settings)
except Exception, e:
logger.warning(u'Could not process %s\n%s' % (f, str(e)))
continue
@ -429,7 +443,23 @@ class StaticGenerator(Generator):
# Define the assets environment that will be passed to the
# generators. The StaticGenerator must then be run first to have
# the assets in the output_path before generating the templates.
assets_url = self.settings['SITEURL'] + '/theme/'
# Let ASSET_URL honor Pelican's RELATIVE_URLS setting.
# Hint for templates:
# Current version of webassets seem to remove any relative
# paths at the beginning of the URL. So, if RELATIVE_URLS
# is on, ASSET_URL will start with 'theme/', regardless if we
# set assets_url here to './theme/' or to 'theme/'.
# XXX However, this breaks the ASSET_URL if user navigates to
# a sub-URL, e.g. if he clicks on a category. To workaround this
# issue, I use
# <link rel="stylesheet" href="{{ SITEURL }}/{{ ASSET_URL }}">
# instead of
# <link rel="stylesheet" href="{{ ASSET_URL }}">
if self.settings.get('RELATIVE_URLS'):
assets_url = './theme/'
else:
assets_url = self.settings['SITEURL'] + '/theme/'
assets_src = os.path.join(self.output_path, 'theme')
self.assets_env = AssetsEnvironment(assets_src, assets_url)
@ -453,13 +483,20 @@ class PdfGenerator(Generator):
"""Generate PDFs on the output dir, for all articles and pages coming from
rst"""
def __init__(self, *args, **kwargs):
super(PdfGenerator, self).__init__(*args, **kwargs)
try:
from rst2pdf.createpdf import RstToPdf
pdf_style_path = os.path.join(self.settings['PDF_STYLE_PATH']) \
if 'PDF_STYLE_PATH' in self.settings.keys() \
else ''
pdf_style = self.settings['PDF_STYLE'] if 'PDF_STYLE' \
in self.settings.keys() \
else 'twelvepoint'
self.pdfcreator = RstToPdf(breakside=0,
stylesheets=['twelvepoint'])
stylesheets=[pdf_style],
style_path=[pdf_style_path])
except ImportError:
raise Exception("unable to find rst2pdf")
super(PdfGenerator, self).__init__(*args, **kwargs)
def _create_pdf(self, obj, output_path):
if obj.filename.endswith(".rst"):
@ -467,7 +504,7 @@ class PdfGenerator(Generator):
output_pdf = os.path.join(output_path, filename)
# print "Generating pdf for", obj.filename, " in ", output_pdf
with open(obj.filename) as f:
self.pdfcreator.createPdf(text=f, output=output_pdf)
self.pdfcreator.createPdf(text=f.read(), output=output_pdf)
logger.info(u' [ok] writing %s' % output_pdf)
def generate_context(self):
@ -491,6 +528,19 @@ class PdfGenerator(Generator):
for page in self.context['pages']:
self._create_pdf(page, pdf_path)
class SourceFileGenerator(Generator):
def generate_context(self):
self.output_extension = self.settings['OUTPUT_SOURCES_EXTENSION']
def _create_source(self, obj, output_path):
filename = os.path.splitext(obj.save_as)[0]
dest = os.path.join(output_path, filename + self.output_extension)
copy('', obj.filename, dest)
def generate_output(self, writer=None):
logger.info(u' Generating source files...')
for object in chain(self.context['articles'], self.context['pages']):
self._create_source(object, self.output_path)
class LessCSSGenerator(Generator):
"""Compile less css files."""

View file

@ -4,13 +4,14 @@ from pelican import signals
License plugin for Pelican
==========================
Simply add license variable in article's context, which contain
the license text.
This plugin allows you to define a LICENSE setting and adds the contents of that
license variable to the article's context, making that variable available to use
from within your theme's templates.
Settings:
---------
Add LICENSE to your settings file to define default license.
Define LICENSE in your settings file with the contents of your default license.
"""

View file

@ -5,20 +5,22 @@ from pelican import signals
Gravatar plugin for Pelican
===========================
Simply add author_gravatar variable in article's context, which contains
the gravatar url.
This plugin assigns the ``author_gravatar`` variable to the Gravatar URL and
makes the variable available within the article's context.
Settings:
---------
Add AUTHOR_EMAIL to your settings file to define default author email.
Add AUTHOR_EMAIL to your settings file to define the default author's email
address. Obviously, that email address must be associated with a Gravatar
account.
Article metadata:
------------------
:email: article's author email
If one of them are defined, the author_gravatar variable is added to
If one of them are defined, the author_gravatar variable is added to the
article's context.
"""

View file

@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) FELD Boris <lothiraldan@gmail.com>
Multiple part support
=====================
Create a navigation menu for multi-part related_posts
Article metadata:
------------------
:parts: a unique identifier for multi-part posts, must be the same in each
post part.
Usage
-----
{% if article.metadata.parts_articles %}
<ol>
{% for part_article in article.metadata.parts_articles %}
{% if part_article == article %}
<li>
<a href='{{ SITEURL }}/{{ part_article.url }}'><b>{{ part_article.title }}</b>
</a>
</li>
{% else %}
<li>
<a href='{{ SITEURL }}/{{ part_article.url }}'>{{ part_article.title }}
</a>
</li>
{% endif %}
{% endfor %}
</ol>
{% endif %}
"""
from collections import defaultdict
from pelican import signals
def aggregate_multi_part(generator):
multi_part = defaultdict(list)
for article in generator.articles:
if 'parts' in article.metadata:
multi_part[article.metadata['parts']].append(article)
for part_id in multi_part:
parts = multi_part[part_id]
# Sort by date
parts.sort(key=lambda x: x.metadata['date'])
for article in parts:
article.metadata['parts_articles'] = parts
def register():
signals.article_generator_finalized.connect(aggregate_multi_part)

190
pelican/plugins/sitemap.py Normal file
View file

@ -0,0 +1,190 @@
import collections
import os.path
from datetime import datetime
from logging import warning, info
from codecs import open
from pelican import signals, contents
TXT_HEADER = u"""{0}/index.html
{0}/archives.html
{0}/tags.html
{0}/categories.html
"""
XML_HEADER = u"""<?xml version="1.0" encoding="utf-8"?>
<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
"""
XML_URL = u"""
<url>
<loc>{0}/{1}</loc>
<lastmod>{2}</lastmod>
<changefreq>{3}</changefreq>
<priority>{4}</priority>
</url>
"""
XML_FOOTER = u"""
</urlset>
"""
def format_date(date):
if date.tzinfo:
tz = date.strftime('%s')
tz = tz[:-2] + ':' + tz[-2:]
else:
tz = "-00:00"
return date.strftime("%Y-%m-%dT%H:%M:%S") + tz
class SitemapGenerator(object):
def __init__(self, context, settings, path, theme, output_path, *null):
self.output_path = output_path
self.context = context
self.now = datetime.now()
self.siteurl = settings.get('SITEURL')
self.format = 'xml'
self.changefreqs = {
'articles': 'monthly',
'indexes': 'daily',
'pages': 'monthly'
}
self.priorities = {
'articles': 0.5,
'indexes': 0.5,
'pages': 0.5
}
config = settings.get('SITEMAP', {})
if not isinstance(config, dict):
warning("sitemap plugin: the SITEMAP setting must be a dict")
else:
fmt = config.get('format')
pris = config.get('priorities')
chfreqs = config.get('changefreqs')
if fmt not in ('xml', 'txt'):
warning("sitemap plugin: SITEMAP['format'] must be `txt' or `xml'")
warning("sitemap plugin: Setting SITEMAP['format'] on `xml'")
elif fmt == 'txt':
self.format = fmt
return
valid_keys = ('articles', 'indexes', 'pages')
valid_chfreqs = ('always', 'hourly', 'daily', 'weekly', 'monthly',
'yearly', 'never')
if isinstance(pris, dict):
for k, v in pris.iteritems():
if k in valid_keys and not isinstance(v, (int, float)):
default = self.priorities[k]
warning("sitemap plugin: priorities must be numbers")
warning("sitemap plugin: setting SITEMAP['priorities']"
"['{0}'] on {1}".format(k, default))
pris[k] = default
self.priorities.update(pris)
elif pris is not None:
warning("sitemap plugin: SITEMAP['priorities'] must be a dict")
warning("sitemap plugin: using the default values")
if isinstance(chfreqs, dict):
for k, v in chfreqs.iteritems():
if k in valid_keys and v not in valid_chfreqs:
default = self.changefreqs[k]
warning("sitemap plugin: invalid changefreq `{0}'".format(v))
warning("sitemap plugin: setting SITEMAP['changefreqs']"
"['{0}'] on '{1}'".format(k, default))
chfreqs[k] = default
self.changefreqs.update(chfreqs)
elif chfreqs is not None:
warning("sitemap plugin: SITEMAP['changefreqs'] must be a dict")
warning("sitemap plugin: using the default values")
def write_url(self, page, fd):
if getattr(page, 'status', 'published') != 'published':
return
page_path = os.path.join(self.output_path, page.url)
if not os.path.exists(page_path):
return
lastmod = format_date(getattr(page, 'date', self.now))
if isinstance(page, contents.Article):
pri = self.priorities['articles']
chfreq = self.changefreqs['articles']
elif isinstance(page, contents.Page):
pri = self.priorities['pages']
chfreq = self.changefreqs['pages']
else:
pri = self.priorities['indexes']
chfreq = self.changefreqs['indexes']
if self.format == 'xml':
fd.write(XML_URL.format(self.siteurl, page.url, lastmod, chfreq, pri))
else:
fd.write(self.siteurl + '/' + loc + '\n')
def generate_output(self, writer):
path = os.path.join(self.output_path, 'sitemap.{0}'.format(self.format))
pages = self.context['pages'] + self.context['articles'] \
+ [ c for (c, a) in self.context['categories']] \
+ [ t for (t, a) in self.context['tags']] \
+ [ a for (a, b) in self.context['authors']]
for article in self.context['articles']:
pages += article.translations
info('writing {0}'.format(path))
with open(path, 'w', encoding='utf-8') as fd:
if self.format == 'xml':
fd.write(XML_HEADER)
else:
fd.write(TXT_HEADER.format(self.siteurl))
FakePage = collections.namedtuple('FakePage',
['status',
'date',
'url'])
for standard_page_url in ['index.html',
'archives.html',
'tags.html',
'categories.html']:
fake = FakePage(status='published',
date=self.now,
url=standard_page_url)
self.write_url(fake, fd)
for page in pages:
self.write_url(page, fd)
if self.format == 'xml':
fd.write(XML_FOOTER)
def get_generators(generators):
return SitemapGenerator
def register():
signals.get_generators.connect(get_generators)

View file

@ -16,7 +16,7 @@ except ImportError:
import re
from pelican.contents import Category, Tag, Author
from pelican.utils import get_date, open
from pelican.utils import get_date, pelican_open
_METADATA_PROCESSORS = {
@ -102,7 +102,7 @@ class RstReader(Reader):
def _get_publisher(self, filename):
extra_params = {'initial_header_level': '2'}
pub = docutils.core.Publisher(
destination_class=docutils.io.StringOutput)
destination_class=docutils.io.StringOutput)
pub.set_components('standalone', 'restructuredtext', 'html')
pub.writer.translator_class = PelicanHTMLTranslator
pub.process_programmatic_settings(None, extra_params, None)
@ -129,8 +129,13 @@ class MarkdownReader(Reader):
def read(self, filename):
"""Parse content and metadata of markdown files"""
text = open(filename)
md = Markdown(extensions=set(self.extensions + ['meta']))
markdown_extensions = self.settings.get('MARKDOWN_EXTENSIONS', [])
if isinstance(markdown_extensions, (str, unicode)):
markdown_extensions = [m.strip() for m in
markdown_extensions.split(',')]
text = pelican_open(filename)
md = Markdown(extensions=set(
self.extensions + markdown_extensions + ['meta']))
content = md.convert(text)
metadata = {}
@ -146,7 +151,7 @@ class HtmlReader(Reader):
def read(self, filename):
"""Parse content and metadata of (x)HTML files"""
with open(filename) as content:
with pelican_open(filename) as content:
metadata = {'title': 'unnamed'}
for i in self._re.findall(content):
key = i.split(':')[0][5:].strip()

View file

@ -26,12 +26,14 @@ _DEFAULT_CONFIG = {'PATH': '.',
'THEME_STATIC_PATHS': ['static', ],
'FEED_ATOM': 'feeds/all.atom.xml',
'CATEGORY_FEED_ATOM': 'feeds/%s.atom.xml',
'TRANSLATION_FEED': 'feeds/all-%s.atom.xml',
'TRANSLATION_FEED_ATOM': 'feeds/all-%s.atom.xml',
'FEED_MAX_ITEMS': '',
'SITEURL': '',
'SITENAME': 'A Pelican Blog',
'DISPLAY_PAGES_ON_MENU': True,
'PDF_GENERATOR': False,
'OUTPUT_SOURCES': False,
'OUTPUT_SOURCES_EXTENSION': '.text',
'DEFAULT_CATEGORY': 'misc',
'DEFAULT_DATE': 'fs',
'WITH_FUTURE_DATES': True,
@ -58,6 +60,7 @@ _DEFAULT_CONFIG = {'PATH': '.',
'TAG_CLOUD_STEPS': 4,
'TAG_CLOUD_MAX_ITEMS': 100,
'DIRECT_TEMPLATES': ('index', 'tags', 'categories', 'archives'),
'EXTRA_TEMPLATES_PATHS' : [],
'PAGINATED_DIRECT_TEMPLATES': ('index', ),
'PELICAN_CLASS': 'pelican.Pelican',
'DEFAULT_DATE_FORMAT': '%a %d %B %Y',
@ -75,16 +78,28 @@ _DEFAULT_CONFIG = {'PATH': '.',
'SUMMARY_MAX_LENGTH': 50,
'WEBASSETS': False,
'PLUGINS': [],
'MARKDOWN_EXTENSIONS': ['toc', ],
}
def read_settings(filename=None):
def read_settings(filename=None, override=None):
if filename:
local_settings = get_settings_from_file(filename)
# Make the paths relative to the settings file
for p in ['PATH', 'OUTPUT_PATH', 'THEME']:
if p in local_settings and local_settings[p] is not None \
and not isabs(local_settings[p]):
absp = os.path.abspath(os.path.normpath(os.path.join(
os.path.dirname(filename), local_settings[p])))
if p != 'THEME' or os.path.exists(p):
local_settings[p] = absp
else:
local_settings = copy.deepcopy(_DEFAULT_CONFIG)
configured_settings = configure_settings(local_settings, None, filename)
return configured_settings
if override:
local_settings.update(override)
return configure_settings(local_settings)
def get_settings_from_module(module=None, default_settings=_DEFAULT_CONFIG):
@ -94,9 +109,8 @@ def get_settings_from_module(module=None, default_settings=_DEFAULT_CONFIG):
context = copy.deepcopy(default_settings)
if module is not None:
context.update(
(k, v) for k, v in inspect.getmembers(module) if k.isupper()
)
context.update(
(k, v) for k, v in inspect.getmembers(module) if k.isupper())
return context
@ -111,19 +125,23 @@ def get_settings_from_file(filename, default_settings=_DEFAULT_CONFIG):
return get_settings_from_module(module, default_settings=default_settings)
def configure_settings(settings, default_settings=None, filename=None):
"""Provide optimizations, error checking, and warnings for loaded settings"""
if default_settings is None:
default_settings = copy.deepcopy(_DEFAULT_CONFIG)
def configure_settings(settings):
"""
Provide optimizations, error checking, and warnings for loaded settings
"""
if not 'PATH' in settings or not os.path.isdir(settings['PATH']):
raise Exception('You need to specify a path containing the content'
' (see pelican --help for more information)')
# Make the paths relative to the settings file
if filename:
for path in ['PATH', 'OUTPUT_PATH']:
if path in settings:
if settings[path] is not None and not isabs(settings[path]):
settings[path] = os.path.abspath(os.path.normpath(
os.path.join(os.path.dirname(filename), settings[path]))
)
# find the theme in pelican.theme if the given one does not exists
if not os.path.isdir(settings['THEME']):
theme_path = os.sep.join([os.path.dirname(
os.path.abspath(__file__)), "themes/%s" % settings['THEME']])
if os.path.exists(theme_path):
settings['THEME'] = theme_path
else:
raise Exception("Impossible to find the theme %s"
% settings['THEME'])
# if locales is not a list, make it one
locales = settings['LOCALE']
@ -174,4 +192,11 @@ def configure_settings(settings, default_settings=None, filename=None):
logger.warn("You must install the webassets module to use WEBASSETS.")
settings['WEBASSETS'] = False
if 'OUTPUT_SOURCES_EXTENSION' in settings:
if not isinstance(settings['OUTPUT_SOURCES_EXTENSION'], str):
settings['OUTPUT_SOURCES_EXTENSION'] = _DEFAULT_CONFIG['OUTPUT_SOURCES_EXTENSION']
logger.warn("Detected misconfiguration with OUTPUT_SOURCES_EXTENSION."
" falling back to the default extension " +
_DEFAULT_CONFIG['OUTPUT_SOURCES_EXTENSION'])
return settings

View file

@ -1,7 +1,11 @@
from blinker import signal
initialized = signal('pelican_initialized')
finalized = signal('pelican_finalized')
article_generate_context = signal('article_generate_context')
article_generator_init = signal('article_generator_init')
article_generator_finalized = signal('article_generate_finalized')
get_generators = signal('get_generators')
pages_generate_context = signal('pages_generate_context')
pages_generator_init = signal('pages_generator_init')
content_object_init = signal('content_object_init')

View file

@ -97,7 +97,7 @@ dl {margin: 0 0 1.5em 0;}
dt {font-weight: bold;}
dd {margin-left: 1.5em;}
pre{background-color: #000; padding: 10px; color: #fff; margin: 10px; overflow: auto;}
pre{background-color: rgb(238, 238, 238); padding: 10px; margin: 10px; overflow: auto;}
/* Quotes */
blockquote {

View file

@ -1,5 +1,5 @@
.hll {
background-color:#FFFFCC;
background-color:#eee;
}
.c {
color:#408090;

View file

@ -11,16 +11,16 @@
<link href="{{ FEED_DOMAIN }}/{{ FEED_RSS }}" type="application/rss+xml" rel="alternate" title="{{ SITENAME }} RSS Feed" />
{% endif %}
{% if CATEGORY_FEED_ATOM %}
<link href="{{ FEED_DOMAIN }}/{{ CATEGORY_FEED_ATOM }}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} Categories Atom Feed" />
<link href="{{ FEED_DOMAIN }}/{{ CATEGORY_FEED_ATOM|format(category) }}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} Categories Atom Feed" />
{% endif %}
{% if CATEGORY_FEED_RSS %}
<link href="{{ FEED_DOMAIN }}/{{ CATEGORY_FEED_RSS }}" type="application/rss+xml" rel="alternate" title="{{ SITENAME }} Categories RSS Feed" />
<link href="{{ FEED_DOMAIN }}/{{ CATEGORY_FEED_RSS|format(category) }}" type="application/rss+xml" rel="alternate" title="{{ SITENAME }} Categories RSS Feed" />
{% endif %}
{% if TAG_FEED_ATOM %}
<link href="{{ FEED_DOMAIN }}/{{ TAG_FEED_ATOM }}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} Tags Atom Feed" />
<link href="{{ FEED_DOMAIN }}/{{ TAG_FEED_ATOM|format(tag) }}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} Tags Atom Feed" />
{% endif %}
{% if TAG_FEED_RSS %}
<link href="{{ FEED_DOMAIN }}/{{ TAG_FEED_RSS }}" type="application/rss+xml" rel="alternate" title="{{ SITENAME }} Tags RSS Feed" />
<link href="{{ FEED_DOMAIN }}/{{ TAG_FEED_RSS|format(tag) }}" type="application/rss+xml" rel="alternate" title="{{ SITENAME }} Tags RSS Feed" />
{% endif %}
{% endblock head %}
</head>

View file

@ -184,6 +184,8 @@ def build_header(title, date, author, categories, tags):
header = '%s\n%s\n' % (title, '#' * len(title))
if date:
header += ':date: %s\n' % date
if author:
header += ':author: %s\n' % author
if categories:
header += ':category: %s\n' % ', '.join(categories)
if tags:
@ -196,6 +198,8 @@ def build_markdown_header(title, date, author, categories, tags):
header = 'Title: %s\n' % title
if date:
header += 'Date: %s\n' % date
if author:
header += 'Author: %s\n' % author
if categories:
header += 'Category: %s\n' % ', '.join(categories)
if tags:
@ -216,7 +220,7 @@ def fields2pelican(fields, out_markup, output_path, dircat=False, strip_raw=Fals
filename = os.path.basename(filename)
# option to put files in directories with categories names
if dircat and (len(categories) == 1):
if dircat and (len(categories) > 0):
catname = slugify(categories[0])
out_filename = os.path.join(output_path, catname, filename+ext)
if not os.path.isdir(os.path.join(output_path, catname)):

View file

@ -9,13 +9,12 @@ import codecs
from pelican import __version__
_TEMPLATES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), \
_TEMPLATES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)),
"templates")
CONF = {
'pelican' : 'pelican',
'pelicanopts' : '',
'pelican': 'pelican',
'pelicanopts': '',
'basedir': '.',
'ftp_host': 'localhost',
'ftp_user': 'anonymous',
@ -24,8 +23,8 @@ CONF = {
'ssh_port': 22,
'ssh_user': 'root',
'ssh_target_dir': '/var/www',
'dropbox_dir' : '~/Dropbox/Public/',
'default_pagination' : 10,
'dropbox_dir': '~/Dropbox/Public/',
'default_pagination': 10,
'siteurl': '',
'lang': 'en'
}
@ -77,7 +76,7 @@ def ask(question, answer=str, default=None, l=None):
if l and len(r) != l:
print('You must enter a {0} letters long string'.format(l))
else:
break
break
return r
@ -148,14 +147,16 @@ def main():
This script will help you create a new Pelican-based website.
Please answer the following questions so this script can generate the files needed by Pelican.
Please answer the following questions so this script can generate the files
needed by Pelican.
'''.format(v=__version__))
project = os.path.join(os.environ.get('VIRTUAL_ENV', '.'), '.project')
if os.path.isfile(project):
CONF['basedir'] = open(project, 'r').read().rstrip("\n")
print('Using project associated with current virtual environment. Will save to:\n%s\n' % CONF['basedir'])
print('Using project associated with current virtual environment.'
'Will save to:\n%s\n' % CONF['basedir'])
else:
CONF['basedir'] = os.path.abspath(ask('Where do you want to create your new web site?', answer=str, default=args.path))
@ -201,9 +202,13 @@ Please answer the following questions so this script can generate the files need
try:
with codecs.open(os.path.join(CONF['basedir'], 'pelicanconf.py'), 'w', 'utf-8') as fd:
conf_python = dict()
for key, value in CONF.iteritems():
conf_python[key] = repr(value)
for line in get_template('pelicanconf.py'):
template = string.Template(line)
fd.write(template.safe_substitute(CONF))
fd.write(template.safe_substitute(conf_python))
fd.close()
except OSError, e:
print('Error: {0}'.format(e))
@ -228,11 +233,16 @@ Please answer the following questions so this script can generate the files need
print('Error: {0}'.format(e))
if develop:
conf_shell = dict()
for key, value in CONF.iteritems():
if isinstance(value, basestring) and ' ' in value:
value = '"' + value.replace('"', '\\"') + '"'
conf_shell[key] = value
try:
with codecs.open(os.path.join(CONF['basedir'], 'develop_server.sh'), 'w', 'utf-8') as fd:
for line in get_template('develop_server.sh'):
template = string.Template(line)
fd.write(template.safe_substitute(CONF))
fd.write(template.safe_substitute(conf_shell))
fd.close()
os.chmod((os.path.join(CONF['basedir'], 'develop_server.sh')), 0755)
except OSError, e:

View file

@ -61,7 +61,7 @@ ssh_upload: publish
scp -P $$(SSH_PORT) -r $$(OUTPUTDIR)/* $$(SSH_USER)@$$(SSH_HOST):$$(SSH_TARGET_DIR)
rsync_upload: publish
rsync -e "ssh -p $(SSH_PORT)" -P -rvz --delete $(OUTPUTDIR)/* $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR)
rsync -e "ssh -p $(SSH_PORT)" -P -rvz --delete $(OUTPUTDIR) $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR)
dropbox_upload: publish
cp -r $$(OUTPUTDIR)/* $$(DROPBOX_DIR)

View file

@ -1,13 +1,13 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- #
AUTHOR = u"$author"
SITENAME = u"$sitename"
AUTHOR = $author
SITENAME = $sitename
SITEURL = ''
TIMEZONE = 'Europe/Paris'
DEFAULT_LANG = '$lang'
DEFAULT_LANG = $lang
# Blogroll
LINKS = (('Pelican', 'http://docs.notmyidea.org/alexis/pelican/'),

View file

@ -6,7 +6,7 @@ import shutil
import logging
from collections import defaultdict
from codecs import open as _open
from codecs import open
from datetime import datetime
from itertools import groupby
from jinja2 import Markup
@ -14,6 +14,7 @@ from operator import attrgetter
logger = logging.getLogger(__name__)
class NoFilesError(Exception):
pass
@ -37,9 +38,9 @@ def get_date(string):
raise ValueError("'%s' is not a valid date" % string)
def open(filename):
def pelican_open(filename):
"""Open a file and return it's content"""
return _open(filename, encoding='utf-8').read()
return open(filename, encoding='utf-8').read()
def slugify(value):
@ -86,16 +87,32 @@ def copy(path, source, destination, destination_path=None, overwrite=False):
if overwrite:
shutil.rmtree(destination_)
shutil.copytree(source_, destination_)
logger.info('replacement of %s with %s' % (source_, destination_))
logger.info('replacement of %s with %s' % (source_,
destination_))
elif os.path.isfile(source_):
dest_dir = os.path.dirname(destination_)
if not os.path.exists(dest_dir):
os.makedirs(dest_dir)
shutil.copy(source_, destination_)
logger.info('copying %s to %s' % (source_, destination_))
else:
logger.warning('skipped copy %s to %s' % (source_, destination_))
def clean_output_dir(path):
"""Remove all the files from the output directory"""
if not os.path.exists(path):
logger.debug("Directory already removed: %s" % path)
return
if not os.path.isdir(path):
try:
os.remove(path)
except Exception, e:
logger.error("Unable to delete file %s; %e" % path, e)
return
# remove all the existing content from the output folder
for filename in os.listdir(path):
file = os.path.join(path, filename)

View file

@ -148,9 +148,9 @@ class Writer(object):
paginators[key] = Paginator(object_list, len(object_list))
# generated pages, and write
name_root, ext = os.path.splitext(name)
for page_num in range(paginators.values()[0].num_pages):
paginated_localcontext = localcontext.copy()
paginated_name = name
for key in paginators.iterkeys():
paginator = paginators[key]
page = paginator.page(page_num + 1)
@ -158,9 +158,10 @@ class Writer(object):
{'%s_paginator' % key: paginator,
'%s_page' % key: page})
if page_num > 0:
ext = '.' + paginated_name.rsplit('.')[-1]
paginated_name = paginated_name.replace(ext,
'%s%s' % (page_num + 1, ext))
paginated_name = '%s%s%s' % (
name_root, page_num + 1, ext)
else:
paginated_name = name
_write_file(template, paginated_localcontext, self.output_path,
paginated_name)

View file

@ -0,0 +1,8 @@
Title: Test Markdown extensions
[TOC]
## Level1
### Level2

View file

@ -55,8 +55,8 @@
<div class="entry-content">
<footer class="post-info">
<abbr class="published" title="2012-07-26T22:04:43.481849">
Thu 26 July 2012
<abbr class="published" title="2012-09-17T15:02:39.591671">
Mon 17 September 2012
</abbr>

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>A Pelican Blog</title><link href="/" rel="alternate"></link><link href="/feeds/all-fr.atom.xml" rel="self"></link><id>/</id><updated>2012-07-26T22:04:43Z</updated><entry><title>Trop bien !</title><link href="/oh-yeah-fr.html" rel="alternate"></link><updated>2012-07-26T22:04:43Z</updated><author><name>Dummy Author</name></author><id>tag:,2012-07-26:oh-yeah-fr.html</id><summary type="html">&lt;p&gt;Et voila du contenu en français&lt;/p&gt;
<feed xmlns="http://www.w3.org/2005/Atom"><title>A Pelican Blog</title><link href="/" rel="alternate"></link><link href="/feeds/all-fr.atom.xml" rel="self"></link><id>/</id><updated>2012-09-17T15:02:39Z</updated><entry><title>Trop bien !</title><link href="/oh-yeah-fr.html" rel="alternate"></link><updated>2012-09-17T15:02:39Z</updated><author><name>Dummy Author</name></author><id>tag:,2012-09-17:oh-yeah-fr.html</id><summary type="html">&lt;p&gt;Et voila du contenu en français&lt;/p&gt;
</summary></entry><entry><title>Deuxième article</title><link href="/second-article-fr.html" rel="alternate"></link><updated>2012-02-29T00:00:00Z</updated><author><name>Dummy Author</name></author><id>tag:,2012-02-29:second-article-fr.html</id><summary type="html">&lt;p&gt;Ceci est un article, en français.&lt;/p&gt;
</summary><category term="foo"></category><category term="bar"></category><category term="baz"></category></entry></feed>

View file

@ -55,8 +55,8 @@
<div class="entry-content">
<footer class="post-info">
<abbr class="published" title="2012-07-26T22:04:43.481849">
Thu 26 July 2012
<abbr class="published" title="2012-09-17T15:02:39.591671">
Mon 17 September 2012
</abbr>

View file

@ -51,7 +51,7 @@
<aside id="featured" class="body">
<article>
<h1 class="entry-title"><a href=".././second-article.html">Second article</a></h1>
<h1 class="entry-title"><a href=".././second-article-fr.html">Deuxième article</a></h1>
<footer class="post-info">
<abbr class="published" title="2012-02-29T00:00:00">
Wed 29 February 2012
@ -68,10 +68,10 @@
Translations:
<a href=".././second-article-fr.html">fr</a>
<a href=".././second-article.html">en</a>
</footer><!-- /.post-info --><p>This is some article, in english</p>
</footer><!-- /.post-info --><p>Ceci est un article, en français.</p>
</article>
@ -92,8 +92,8 @@ Translations:
<li><article class="hentry">
<header>
<h1><a href=".././second-article-fr.html" rel="bookmark"
title="Permalink to Deuxième article">Deuxième article</a></h1>
<h1><a href=".././second-article.html" rel="bookmark"
title="Permalink to Second article">Second article</a></h1>
</header>
<div class="entry-content">
@ -113,13 +113,13 @@ Translations:
Translations:
<a href=".././second-article.html">en</a>
<a href=".././second-article-fr.html">fr</a>
</footer><!-- /.post-info -->
<p>Ceci est un article, en français.</p>
<p>This is some article, in english</p>
<a class="readmore" href=".././second-article-fr.html">read more</a>
<a class="readmore" href=".././second-article.html">read more</a>
</div><!-- /.entry-content -->
</article></li>

View file

@ -51,7 +51,7 @@
<aside id="featured" class="body">
<article>
<h1 class="entry-title"><a href=".././second-article.html">Second article</a></h1>
<h1 class="entry-title"><a href=".././second-article-fr.html">Deuxième article</a></h1>
<footer class="post-info">
<abbr class="published" title="2012-02-29T00:00:00">
Wed 29 February 2012
@ -68,10 +68,10 @@
Translations:
<a href=".././second-article-fr.html">fr</a>
<a href=".././second-article.html">en</a>
</footer><!-- /.post-info --><p>This is some article, in english</p>
</footer><!-- /.post-info --><p>Ceci est un article, en français.</p>
</article>
@ -92,8 +92,8 @@ Translations:
<li><article class="hentry">
<header>
<h1><a href=".././second-article-fr.html" rel="bookmark"
title="Permalink to Deuxième article">Deuxième article</a></h1>
<h1><a href=".././second-article.html" rel="bookmark"
title="Permalink to Second article">Second article</a></h1>
</header>
<div class="entry-content">
@ -113,13 +113,13 @@ Translations:
Translations:
<a href=".././second-article.html">en</a>
<a href=".././second-article-fr.html">fr</a>
</footer><!-- /.post-info -->
<p>Ceci est un article, en français.</p>
<p>This is some article, in english</p>
<a class="readmore" href=".././second-article-fr.html">read more</a>
<a class="readmore" href=".././second-article.html">read more</a>
</div><!-- /.entry-content -->
</article></li>

View file

@ -51,7 +51,7 @@
<aside id="featured" class="body">
<article>
<h1 class="entry-title"><a href=".././second-article.html">Second article</a></h1>
<h1 class="entry-title"><a href=".././second-article-fr.html">Deuxième article</a></h1>
<footer class="post-info">
<abbr class="published" title="2012-02-29T00:00:00">
Wed 29 February 2012
@ -68,10 +68,10 @@
Translations:
<a href=".././second-article-fr.html">fr</a>
<a href=".././second-article.html">en</a>
</footer><!-- /.post-info --><p>This is some article, in english</p>
</footer><!-- /.post-info --><p>Ceci est un article, en français.</p>
</article>
@ -92,8 +92,8 @@ Translations:
<li><article class="hentry">
<header>
<h1><a href=".././second-article-fr.html" rel="bookmark"
title="Permalink to Deuxième article">Deuxième article</a></h1>
<h1><a href=".././second-article.html" rel="bookmark"
title="Permalink to Second article">Second article</a></h1>
</header>
<div class="entry-content">
@ -113,13 +113,13 @@ Translations:
Translations:
<a href=".././second-article.html">en</a>
<a href=".././second-article-fr.html">fr</a>
</footer><!-- /.post-info -->
<p>Ceci est un article, en français.</p>
<p>This is some article, in english</p>
<a class="readmore" href=".././second-article-fr.html">read more</a>
<a class="readmore" href=".././second-article.html">read more</a>
</div><!-- /.entry-content -->
</article></li>

View file

@ -97,7 +97,7 @@ dl {margin: 0 0 1.5em 0;}
dt {font-weight: bold;}
dd {margin-left: 1.5em;}
pre{background-color: #000; padding: 10px; color: #fff; margin: 10px; overflow: auto;}
pre{background-color: rgb(238, 238, 238); padding: 10px; margin: 10px; overflow: auto;}
/* Quotes */
blockquote {
@ -308,7 +308,8 @@ img.left, figure.left {float: left; margin: 0 2em 2em 0;}
.social a[type$='atom+xml'], .social a[type$='rss+xml'] {background-image: url('../images/icons/rss.png');}
.social a[href*='twitter.com'] {background-image: url('../images/icons/twitter.png');}
.social a[href*='linkedin.com'] {background-image: url('../images/icons/linkedin.png');}
.social a[href*='gitorious.org'] {background-image: url('../images/icons/gitorious.org');}
.social a[href*='gitorious.org'] {background-image: url('../images/icons/gitorious.png');}
.social a[href*='gittip.com'] {background-image: url('../images/icons/gittip.png');}
/*
About

View file

@ -1,5 +1,5 @@
.hll {
background-color:#FFFFCC;
background-color:#eee;
}
.c {
color:#408090;

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

View file

@ -59,7 +59,7 @@
<aside id="featured" class="body">
<article>
<h1 class="entry-title"><a href=".././second-article.html">Second article</a></h1>
<h1 class="entry-title"><a href=".././second-article-fr.html">Deuxième article</a></h1>
<footer class="post-info">
<abbr class="published" title="2012-02-29T00:00:00">
Wed 29 February 2012
@ -76,11 +76,11 @@
Translations:
<a href=".././second-article-fr.html">fr</a>
<a href=".././second-article.html">en</a>
</footer><!-- /.post-info --><p>This is some article, in english</p>
<p>There are <a href=".././second-article.html#disqus_thread">comments</a>.</p>
</footer><!-- /.post-info --><p>Ceci est un article, en français.</p>
<p>There are <a href=".././second-article-fr.html#disqus_thread">comments</a>.</p>
</article>
</aside><!-- /#featured -->
@ -100,8 +100,8 @@ Translations:
<li><article class="hentry">
<header>
<h1><a href=".././second-article-fr.html" rel="bookmark"
title="Permalink to Deuxième article">Deuxième article</a></h1>
<h1><a href=".././second-article.html" rel="bookmark"
title="Permalink to Second article">Second article</a></h1>
</header>
<div class="entry-content">
@ -121,14 +121,14 @@ Translations:
Translations:
<a href=".././second-article.html">en</a>
<a href=".././second-article-fr.html">fr</a>
</footer><!-- /.post-info -->
<p>Ceci est un article, en français.</p>
<p>This is some article, in english</p>
<a class="readmore" href=".././second-article-fr.html">read more</a>
<p>There are <a href=".././second-article-fr.html#disqus_thread">comments</a>.</p>
<a class="readmore" href=".././second-article.html">read more</a>
<p>There are <a href=".././second-article.html#disqus_thread">comments</a>.</p>
</div><!-- /.entry-content -->
</article></li>

View file

@ -59,7 +59,7 @@
<aside id="featured" class="body">
<article>
<h1 class="entry-title"><a href=".././second-article.html">Second article</a></h1>
<h1 class="entry-title"><a href=".././second-article-fr.html">Deuxième article</a></h1>
<footer class="post-info">
<abbr class="published" title="2012-02-29T00:00:00">
Wed 29 February 2012
@ -76,11 +76,11 @@
Translations:
<a href=".././second-article-fr.html">fr</a>
<a href=".././second-article.html">en</a>
</footer><!-- /.post-info --><p>This is some article, in english</p>
<p>There are <a href=".././second-article.html#disqus_thread">comments</a>.</p>
</footer><!-- /.post-info --><p>Ceci est un article, en français.</p>
<p>There are <a href=".././second-article-fr.html#disqus_thread">comments</a>.</p>
</article>
</aside><!-- /#featured -->
@ -100,8 +100,8 @@ Translations:
<li><article class="hentry">
<header>
<h1><a href=".././second-article-fr.html" rel="bookmark"
title="Permalink to Deuxième article">Deuxième article</a></h1>
<h1><a href=".././second-article.html" rel="bookmark"
title="Permalink to Second article">Second article</a></h1>
</header>
<div class="entry-content">
@ -121,14 +121,14 @@ Translations:
Translations:
<a href=".././second-article.html">en</a>
<a href=".././second-article-fr.html">fr</a>
</footer><!-- /.post-info -->
<p>Ceci est un article, en français.</p>
<p>This is some article, in english</p>
<a class="readmore" href=".././second-article-fr.html">read more</a>
<p>There are <a href=".././second-article-fr.html#disqus_thread">comments</a>.</p>
<a class="readmore" href=".././second-article.html">read more</a>
<p>There are <a href=".././second-article.html#disqus_thread">comments</a>.</p>
</div><!-- /.entry-content -->
</article></li>

View file

@ -59,7 +59,7 @@
<aside id="featured" class="body">
<article>
<h1 class="entry-title"><a href=".././second-article.html">Second article</a></h1>
<h1 class="entry-title"><a href=".././second-article-fr.html">Deuxième article</a></h1>
<footer class="post-info">
<abbr class="published" title="2012-02-29T00:00:00">
Wed 29 February 2012
@ -76,11 +76,11 @@
Translations:
<a href=".././second-article-fr.html">fr</a>
<a href=".././second-article.html">en</a>
</footer><!-- /.post-info --><p>This is some article, in english</p>
<p>There are <a href=".././second-article.html#disqus_thread">comments</a>.</p>
</footer><!-- /.post-info --><p>Ceci est un article, en français.</p>
<p>There are <a href=".././second-article-fr.html#disqus_thread">comments</a>.</p>
</article>
</aside><!-- /#featured -->
@ -100,8 +100,8 @@ Translations:
<li><article class="hentry">
<header>
<h1><a href=".././second-article-fr.html" rel="bookmark"
title="Permalink to Deuxième article">Deuxième article</a></h1>
<h1><a href=".././second-article.html" rel="bookmark"
title="Permalink to Second article">Second article</a></h1>
</header>
<div class="entry-content">
@ -121,14 +121,14 @@ Translations:
Translations:
<a href=".././second-article.html">en</a>
<a href=".././second-article-fr.html">fr</a>
</footer><!-- /.post-info -->
<p>Ceci est un article, en français.</p>
<p>This is some article, in english</p>
<a class="readmore" href=".././second-article-fr.html">read more</a>
<p>There are <a href=".././second-article-fr.html#disqus_thread">comments</a>.</p>
<a class="readmore" href=".././second-article.html">read more</a>
<p>There are <a href=".././second-article.html#disqus_thread">comments</a>.</p>
</div><!-- /.entry-content -->
</article></li>

View file

@ -97,7 +97,7 @@ dl {margin: 0 0 1.5em 0;}
dt {font-weight: bold;}
dd {margin-left: 1.5em;}
pre{background-color: #000; padding: 10px; color: #fff; margin: 10px; overflow: auto;}
pre{background-color: rgb(238, 238, 238); padding: 10px; margin: 10px; overflow: auto;}
/* Quotes */
blockquote {
@ -308,7 +308,8 @@ img.left, figure.left {float: left; margin: 0 2em 2em 0;}
.social a[type$='atom+xml'], .social a[type$='rss+xml'] {background-image: url('../images/icons/rss.png');}
.social a[href*='twitter.com'] {background-image: url('../images/icons/twitter.png');}
.social a[href*='linkedin.com'] {background-image: url('../images/icons/linkedin.png');}
.social a[href*='gitorious.org'] {background-image: url('../images/icons/gitorious.org');}
.social a[href*='gitorious.org'] {background-image: url('../images/icons/gitorious.png');}
.social a[href*='gittip.com'] {background-image: url('../images/icons/gittip.png');}
/*
About

View file

@ -1,5 +1,5 @@
.hll {
background-color:#FFFFCC;
background-color:#eee;
}
.c {
color:#408090;

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

View file

@ -5,7 +5,7 @@ from .support import unittest
from pelican.contents import Page, Article
from pelican.settings import _DEFAULT_CONFIG
from pelican.utils import truncate_html_words
from pelican.signals import content_object_init
from jinja2.utils import generate_lorem_ipsum
# generate one paragraph, enclosed with <p>
@ -158,6 +158,17 @@ class TestPage(unittest.TestCase):
return page_kwargs
def test_signal(self):
"""If a title is given, it should be used to generate the slug."""
def receiver_test_function(sender,instance):
pass
content_object_init.connect(receiver_test_function ,sender=Page)
page = Page(**self.page_kwargs)
self.assertTrue(content_object_init.has_receivers_for(Page))
class TestArticle(TestPage):
def test_template(self):
"""

View file

@ -69,6 +69,7 @@ class TestArticlesGenerator(unittest.TestCase):
[u'Article title', 'published', 'Default', 'article'],
[u'Article with template', 'published', 'Default', 'custom'],
[u'Test md File', 'published', 'test', 'article'],
[u'Test Markdown extensions', 'published', u'Default', 'article'],
[u'This is a super article !', 'published', 'Yeah', 'article'],
[u'This is an article with category !', 'published', 'yeah', 'article'],
[u'This is an article without category !', 'published', 'Default', 'article'],

View file

@ -55,7 +55,11 @@ class TestPelican(unittest.TestCase):
with patch("pelican.contents.getenv") as mock_getenv:
# force getenv('USER') to always return the same value
mock_getenv.return_value = "Dummy Author"
pelican = Pelican(path=INPUT_PATH, output_path=self.temp_path)
settings = read_settings(filename=None, override={
'PATH': INPUT_PATH,
'OUTPUT_PATH': self.temp_path,
})
pelican = Pelican(settings=settings)
pelican.run()
diff = dircmp(
self.temp_path, os.sep.join((OUTPUT_PATH, "basic")))
@ -63,8 +67,11 @@ class TestPelican(unittest.TestCase):
def test_custom_generation_works(self):
# the same thing with a specified set of settings should work
pelican = Pelican(path=INPUT_PATH, output_path=self.temp_path,
settings=read_settings(SAMPLE_CONFIG))
settings = read_settings(filename=SAMPLE_CONFIG, override={
'PATH': INPUT_PATH,
'OUTPUT_PATH': self.temp_path,
})
pelican = Pelican(settings=settings)
pelican.run()
diff = dircmp(self.temp_path, os.sep.join((OUTPUT_PATH, "custom")))
self.assertFilesEqual(diff)

View file

@ -70,7 +70,7 @@ class RstReaderTest(unittest.TestCase):
class MdReaderTest(unittest.TestCase):
@unittest.skipUnless(readers.Markdown, "markdown isn't installed")
def test_article_with_md_extention(self):
def test_article_with_md_extension(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'))
@ -90,3 +90,22 @@ class MdReaderTest(unittest.TestCase):
"<p>This is another markdown test file. Uses the mkd extension.</p>"
self.assertEqual(content, expected)
@unittest.skipUnless(readers.Markdown, "markdown isn't installed")
def test_article_with_markdown_markup_extension(self):
# test to ensure the markdown markup extension is being processed as expected
reader = readers.MarkdownReader({})
reader.settings.update(dict(MARKDOWN_EXTENSIONS=['toc', ]))
content, metadata = reader.read(_filename('article_with_markdown_markup_extensions.md'))
expected = '<div class="toc">\n'\
'<ul>\n'\
'<li><a href="#level1">Level1</a><ul>\n'\
'<li><a href="#level2">Level2</a></li>\n'\
'</ul>\n'\
'</li>\n'\
'</ul>\n'\
'</div>\n'\
'<h2 id="level1">Level1</h2>\n'\
'<h3 id="level2">Level2</h3>'
self.assertEqual(content, expected)

View file

@ -56,16 +56,19 @@ class TestSettingsConfiguration(unittest.TestCase):
def test_configure_settings(self):
"""Manipulations to settings should be applied correctly."""
# SITEURL should not have a trailing slash
settings = {'SITEURL': 'http://blog.notmyidea.org/', 'LOCALE': ''}
settings = {
'SITEURL': 'http://blog.notmyidea.org/',
'LOCALE': '',
'PATH': '.',
'THEME': DEFAULT_THEME,
}
configure_settings(settings)
# SITEURL should not have a trailing slash
self.assertEqual(settings['SITEURL'], 'http://blog.notmyidea.org')
# FEED_DOMAIN, if undefined, should default to SITEURL
settings = {'SITEURL': 'http://blog.notmyidea.org', 'LOCALE': ''}
configure_settings(settings)
self.assertEqual(settings['FEED_DOMAIN'], 'http://blog.notmyidea.org')
settings = {'FEED_DOMAIN': 'http://feeds.example.com', 'LOCALE': ''}
settings['FEED_DOMAIN'] = 'http://feeds.example.com'
configure_settings(settings)
self.assertEqual(settings['FEED_DOMAIN'], 'http://feeds.example.com')

View file

@ -112,3 +112,16 @@ class TestUtils(unittest.TestCase):
self.assertTrue(os.path.isdir(test_directory))
self.assertListEqual([], os.listdir(test_directory))
shutil.rmtree(test_directory)
def test_clean_output_dir_not_there(self):
test_directory = os.path.join(os.path.dirname(__file__), 'does_not_exist')
utils.clean_output_dir(test_directory)
self.assertTrue(not os.path.exists(test_directory))
def test_clean_output_dir_is_file(self):
test_directory = os.path.join(os.path.dirname(__file__), 'this_is_a_file')
f = open(test_directory, 'w')
f.write('')
f.close()
utils.clean_output_dir(test_directory)
self.assertTrue(not os.path.exists(test_directory))

10
tox.ini
View file

@ -2,14 +2,14 @@
envlist = py26,py27
[testenv]
commands = nosetests -s tests
commands =
nosetests -s tests
unit2 discover []
deps =
nose
Jinja2
Pygments
docutils
feedgenerator
unittest2
mock
Markdown
BeautifulSoup
typogrify
webassets