-
- (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 `_.
Publishing drafts
diff --git a/docs/importer.rst b/docs/importer.rst
index ccf3ffe2..ba96d9c2 100644
--- a/docs/importer.rst
+++ b/docs/importer.rst
@@ -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 `_.
diff --git a/docs/index.rst b/docs/index.rst
index 477b4342..3fc1cf9f 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -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
\ No newline at end of file
+.. _webchat: http://webchat.freenode.net/?channels=pelican&uio=d4
diff --git a/docs/internals.rst b/docs/internals.rst
index 6b6f991f..a6264476 100644
--- a/docs/internals.rst
+++ b/docs/internals.rst
@@ -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,
diff --git a/docs/pelican-themes.rst b/docs/pelican-themes.rst
index a074a0a2..23be8355 100644
--- a/docs/pelican-themes.rst
+++ b/docs/pelican-themes.rst
@@ -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``
diff --git a/docs/plugins.rst b/docs/plugins.rst
index 53858668..c275c57c 100644
--- a/docs/plugins.rst
+++ b/docs/plugins.rst
@@ -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::
+
+
+
+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 %}
+
+ {% for related_post in article.related_posts %}
+ - {{ related_post }}
+ {% endfor %}
+
+ {% 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 ``/sitemap.``.
+
+.. note::
+ ``priorities`` and ``changefreqs`` are informations for search engines.
+ They are only used in the XML sitemaps.
+ For more information:
+
+**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'
+ }
+ }
diff --git a/docs/report.rst b/docs/report.rst
index f12f3048..f3ddff31 100644
--- a/docs/report.rst
+++ b/docs/report.rst
@@ -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.
diff --git a/docs/settings.rst b/docs/settings.rst
index ad08f020..af6bc8c0 100644
--- a/docs/settings.rst
+++ b/docs/settings.rst
@@ -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.
-`_SAVE_AS` The location to save content generated from direct
- templates. Where 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.
+`_SAVE_AS` The location to save content generated from direct
+ templates. Where 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 `
-
-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 `.
+
+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:
{% endassets %}
-will produce a minified css file with the version identifier:
+... will produce a minified css file with a version identifier:
.. code-block:: html
-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" %}
-
-{% endassets %}
+ {% assets filters="sass,cssmin", output="css/style.min.css", "css/style.scss" %}
+
+ {% 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" %}
-
+
{% endassets %}
-will produce a minified and gzipped js file:
+The above will produce a minified and gzipped JS file:
.. code-block:: html
-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
`_).
.. _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
================
diff --git a/docs/themes.rst b/docs/themes.rst
index d3dd4d9e..d06c97a8 100644
--- a/docs/themes.rst
+++ b/docs/themes.rst
@@ -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
diff --git a/docs/tips.rst b/docs/tips.rst
index 8905103b..abb739b1 100644
--- a/docs/tips.rst
+++ b/docs/tips.rst
@@ -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 .
diff --git a/pelican/__init__.py b/pelican/__init__.py
index a69752d8..9809b19b 100644
--- a/pelican/__init__.py
+++ b/pelican/__init__.py
@@ -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)
diff --git a/pelican/contents.py b/pelican/contents.py
index 851607a5..0dee19f3 100644
--- a/pelican/contents.py
+++ b/pelican/contents.py
@@ -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'))
diff --git a/pelican/generators.py b/pelican/generators.py
index ae9334da..fdec93fa 100644
--- a/pelican/generators.py
+++ b/pelican/generators.py
@@ -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
+ #
+ # instead of
+ #
+ 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."""
diff --git a/pelican/plugins/global_license.py b/pelican/plugins/global_license.py
index 463a93b3..9a0f5206 100644
--- a/pelican/plugins/global_license.py
+++ b/pelican/plugins/global_license.py
@@ -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.
"""
diff --git a/pelican/plugins/gravatar.py b/pelican/plugins/gravatar.py
index 4ab8ea9c..a4d11456 100644
--- a/pelican/plugins/gravatar.py
+++ b/pelican/plugins/gravatar.py
@@ -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.
"""
diff --git a/pelican/plugins/multi_part.py b/pelican/plugins/multi_part.py
new file mode 100644
index 00000000..0581b501
--- /dev/null
+++ b/pelican/plugins/multi_part.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+"""
+Copyright (c) FELD Boris
+
+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 %}
+
+ {% for part_article in article.metadata.parts_articles %}
+ {% if part_article == article %}
+ -
+ {{ part_article.title }}
+
+
+ {% else %}
+ -
+ {{ part_article.title }}
+
+
+ {% endif %}
+ {% endfor %}
+
+ {% 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)
diff --git a/pelican/plugins/sitemap.py b/pelican/plugins/sitemap.py
new file mode 100644
index 00000000..ebce1f04
--- /dev/null
+++ b/pelican/plugins/sitemap.py
@@ -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_URL = u"""
+
+{0}/{1}
+{2}
+{3}
+{4}
+
+"""
+
+XML_FOOTER = u"""
+
+"""
+
+
+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)
diff --git a/pelican/readers.py b/pelican/readers.py
index e3ea154d..dab829b9 100644
--- a/pelican/readers.py
+++ b/pelican/readers.py
@@ -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()
diff --git a/pelican/settings.py b/pelican/settings.py
index 92c68ddc..4d1ed81e 100644
--- a/pelican/settings.py
+++ b/pelican/settings.py
@@ -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
diff --git a/pelican/signals.py b/pelican/signals.py
index 4d9ab512..73e718b5 100644
--- a/pelican/signals.py
+++ b/pelican/signals.py
@@ -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')
diff --git a/pelican/themes/notmyidea/static/css/main.css b/pelican/themes/notmyidea/static/css/main.css
index b4ba7888..3d94200b 100644
--- a/pelican/themes/notmyidea/static/css/main.css
+++ b/pelican/themes/notmyidea/static/css/main.css
@@ -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 {
diff --git a/pelican/themes/notmyidea/static/css/pygment.css b/pelican/themes/notmyidea/static/css/pygment.css
index 594b0fa3..fdd056f6 100644
--- a/pelican/themes/notmyidea/static/css/pygment.css
+++ b/pelican/themes/notmyidea/static/css/pygment.css
@@ -1,5 +1,5 @@
.hll {
-background-color:#FFFFCC;
+background-color:#eee;
}
.c {
color:#408090;
diff --git a/pelican/themes/simple/templates/base.html b/pelican/themes/simple/templates/base.html
index 1f55a40f..c1d9cb78 100644
--- a/pelican/themes/simple/templates/base.html
+++ b/pelican/themes/simple/templates/base.html
@@ -11,16 +11,16 @@
{% endif %}
{% if CATEGORY_FEED_ATOM %}
-
+
{% endif %}
{% if CATEGORY_FEED_RSS %}
-
+
{% endif %}
{% if TAG_FEED_ATOM %}
-
+
{% endif %}
{% if TAG_FEED_RSS %}
-
+
{% endif %}
{% endblock head %}
diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py
index b6437c92..fc28c6a4 100755
--- a/pelican/tools/pelican_import.py
+++ b/pelican/tools/pelican_import.py
@@ -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)):
diff --git a/pelican/tools/pelican_quickstart.py b/pelican/tools/pelican_quickstart.py
index 388718ec..447a80c5 100755
--- a/pelican/tools/pelican_quickstart.py
+++ b/pelican/tools/pelican_quickstart.py
@@ -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:
diff --git a/pelican/tools/templates/Makefile.in b/pelican/tools/templates/Makefile.in
index 4c5a4fcb..8e5c80f9 100644
--- a/pelican/tools/templates/Makefile.in
+++ b/pelican/tools/templates/Makefile.in
@@ -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)
diff --git a/pelican/tools/templates/pelicanconf.py.in b/pelican/tools/templates/pelicanconf.py.in
index 07e286cd..d59a7989 100644
--- a/pelican/tools/templates/pelicanconf.py.in
+++ b/pelican/tools/templates/pelicanconf.py.in
@@ -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/'),
diff --git a/pelican/utils.py b/pelican/utils.py
index ca3015ce..767dc117 100644
--- a/pelican/utils.py
+++ b/pelican/utils.py
@@ -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)
diff --git a/pelican/writers.py b/pelican/writers.py
index bac06bc5..53b6c48a 100644
--- a/pelican/writers.py
+++ b/pelican/writers.py
@@ -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)
diff --git a/tests/content/article_with_markdown_markup_extensions.md b/tests/content/article_with_markdown_markup_extensions.md
new file mode 100644
index 00000000..6cf56403
--- /dev/null
+++ b/tests/content/article_with_markdown_markup_extensions.md
@@ -0,0 +1,8 @@
+Title: Test Markdown extensions
+
+[TOC]
+
+## Level1
+
+### Level2
+
diff --git a/tests/output/basic/drafts/a-draft-article.html b/tests/output/basic/drafts/a-draft-article.html
index 24c1c3bc..7da9c416 100644
--- a/tests/output/basic/drafts/a-draft-article.html
+++ b/tests/output/basic/drafts/a-draft-article.html
@@ -55,8 +55,8 @@