1
0
Fork 0
forked from github/pelican

Merge remote-tracking branch 'origin/master' into fix-functional-tests

Conflicts:
	tests/support.py
This commit is contained in:
Bruno Binet 2012-07-05 01:22:31 +02:00
commit fe9388a7a4
47 changed files with 1711 additions and 184 deletions

1
.gitignore vendored
View file

@ -2,6 +2,7 @@
.*.swp
.*.swo
*.pyc
.DS_Store
docs/_build
docs/fr/_build
build

View file

@ -1,4 +1,4 @@
include *.rst
global-include *.py
recursive-include pelican *.html *.css *png
recursive-include pelican *.html *.css *png *.in
include LICENSE

View file

@ -1,4 +1,4 @@
Jinja2
Jinja2>=2.4
Pygments
docutils
feedgenerator
@ -6,3 +6,5 @@ unittest2
pytz
mock
Markdown
blinker
BeautifulSoup

View file

@ -8,14 +8,14 @@ Is it mandatory to have a configuration file?
No, it's not. Configuration files are just an easy way to configure Pelican.
For basic operations, it's possible to specify options while invoking Pelican
via the command line. See `pelican --help` for more information.
via the command line. See ``pelican --help`` for more information.
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/>`_.
@ -30,7 +30,7 @@ How can I help?
There are several ways to help out. First, you can use Pelican and report any
suggestions or problems you might have on `the bugtracker
<http://github.com/ametaireau/pelican/issues>`_.
<https://github.com/ametaireau/pelican/issues>`_.
If you want to contribute, please fork `the git repository
<https://github.com/ametaireau/pelican/>`_, make your changes, and issue

View file

@ -98,6 +98,9 @@ GITHUB_URL :
GOOGLE_ANALYTICS :
'UA-XXXX-YYYY' pour activer Google analytics ;
GOSQUARED_SITENAME :
'XXX-YYYYYY-X' pour activer GoSquared ;
JINJA_EXTENSIONS :
Liste d'extension Jinja2 que vous souhaitez utiliser ;

View file

@ -4,17 +4,48 @@ Getting started
Installing
==========
You're ready? Let's go! You can install Pelican via several different methods. The simplest is via `pip <http://pip.openplans.org/>`_::
You're ready? Let's go! You can install Pelican via several different methods.
The simplest is via `pip <http://www.pip-installer.org/>`_::
$ pip install pelican
If you have the project source, you can install Pelican using the distutils
method. I recommend doing so in a virtualenv::
If you don't have pip installed, an alternative method is easy_install::
$ virtualenv pelican_venv
$ source bin/activate
$ easy_install pelican
While the above is the simplest method, the recommended approach is to create
a virtual environment for Pelican via `virtualenv <http://www.virtualenv.org/>`_
and `virtualenvwrapper <http://www.doughellmann.com/projects/virtualenvwrapper/>`_
before installing Pelican::
$ pip install virtualenvwrapper
$ mkvirtualenv 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
method::
$ cd path-to-Pelican-source
$ python setup.py install
If you have Git installed and prefer to install the latest bleeding-edge
version of Pelican rather than a stable release, use the following command::
$ pip install -e git://github.com/ametaireau/pelican#egg=pelican
Upgrading
---------
If you installed a stable Pelican release via pip or easy_install and wish to
upgrade to the latest stable release, you can do so by adding ``--upgrade`` to
the relevant command. For pip, that would be::
$ pip install --upgrade pelican
If you installed Pelican via distutils or the bleeding-edge method, simply
perform the same step to install the most recent version.
Dependencies
------------
@ -24,7 +55,7 @@ At this time, Pelican is dependent on the following Python packages:
* jinja2, for templating support
* docutils, for supporting reStructuredText as an input format
If you're not using Python 2.7, you will also need `argparse`.
If you're not using Python 2.7, you will also need the ``argparse`` package.
Optionally:
@ -42,7 +73,7 @@ file system (for instance, about the category of your articles), but some
information you need to provide in the form of metadata inside your files.
You can provide this metadata in reStructuredText text files via the
following syntax (give your file the `.rst` extension)::
following syntax (give your file the ``.rst`` extension)::
My super title
##############
@ -53,11 +84,10 @@ following syntax (give your file the `.rst` extension)::
:author: Alexis Metaireau
You can also use Markdown syntax (with a file ending in `.md`).
Markdown generation will not work until you explicitly install the `markdown`
distribution. You can do so on a normal system using `pip install markdown`
::
You can also use Markdown syntax (with a file ending in ``.md``).
Markdown generation will not work until you explicitly install the ``Markdown``
package, which can be done via ``pip install Markdown``. Metadata syntax for
Markdown posts should follow this pattern::
Date: 2010-12-03
Title: My super title
@ -69,17 +99,17 @@ distribution. You can do so on a normal system using `pip install markdown`
Note that, aside from the title, none of this metadata is mandatory: if the date
is not specified, Pelican will rely on the file's "mtime" timestamp, and the
category can be determined by the directory in which the file resides. For
example, a file located at `python/foobar/myfoobar.rst` will have a category of
`foobar`.
example, a file located at ``python/foobar/myfoobar.rst`` will have a category of
``foobar``.
Generate your blog
------------------
To launch Pelican, just use the `pelican` command::
To launch Pelican, just use the ``pelican`` command::
$ pelican /path/to/your/content/ [-s path/to/your/settings.py]
And… that's all! Your weblog will be generated and saved in the `content/`
And… that's all! Your weblog will be generated and saved in the ``content/``
folder.
The above command will use the default theme to produce a simple site. It's not
@ -93,19 +123,23 @@ the options you can use::
Kickstart a blog
----------------
You also can use the `pelican-quickstart` script to start a new blog in
seconds, by just answering few questions. Just run `pelican-quickstart` and
You also can use the ``pelican-quickstart`` script to start a new blog in
seconds by just answering a few questions. Just run ``pelican-quickstart`` and
you're done! (Added in Pelican 3.0)
Pages
-----
If you create a folder named `pages`, all the files in it will be used to
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
then add a ``status: hidden`` attribute to its metadata. This is useful for
things like making error pages that fit the generated theme of your site.
Importing an existing blog
--------------------------
@ -115,8 +149,8 @@ a simple script. See :ref:`import`.
Translations
------------
It is possible to translate articles. To do so, you need to add a `lang` meta
attribute to your articles/pages and set a `DEFAULT_LANG` setting (which is
It is possible to translate articles. To do so, you need to add a ``lang`` meta
attribute to your articles/pages and set a ``DEFAULT_LANG`` setting (which is
English [en] by default). With those settings in place, only articles with the
default language will be listed, and each article will be accompanied by a list
of available translations for that article.
@ -169,18 +203,18 @@ For RestructuredText::
For Markdown, format your code blocks thusly::
::identifier
:::identifier
your code goes here
The specified identifier should be one that appears on the
`list of available lexers <http://pygments.org/docs/lexers/>`_.
Autoreload
----------
Auto-reload
-----------
It's possible to tell Pelican to watch for your modifications, instead of
manually launching it every time you want to see your changes. To enable this,
run the `pelican` command with the `-r` or `--autoreload` options.
manually re-running it every time you want to see your changes. To enable this,
run the ``pelican`` command with the ``-r`` or ``--autoreload`` option.
Publishing drafts
-----------------
@ -203,3 +237,4 @@ You can either use your browser to open the files on your disk::
Or run a simple web server using Python::
cd output && python -m SimpleHTTPServer

View file

@ -61,6 +61,7 @@ A French version of the documentation is available at :doc:`fr/index`.
getting_started
settings
themes
plugins
internals
pelican-themes
importer

View file

@ -12,34 +12,34 @@ original author wrote with some software design information.
Overall structure
=================
What `pelican` does is take a list of files and process them into some
What Pelican does is take a list of files and process them into some
sort of output. Usually, the input files are reStructuredText and Markdown
files, and the output is a blog, but both input and output can be anything you
want.
The logic is separated into different classes and concepts:
* `writers` are responsible for writing files: .html files, RSS feeds, and so
* **Writers** are responsible for writing files: .html files, RSS feeds, and so
on. Since those operations are commonly used, the object is created once and
then passed to the generators.
* `readers` are used to read from various formats (Markdown and
* **Readers** are used to read from various formats (Markdown and
reStructuredText for now, but the system is extensible). Given a file, they return
metadata (author, tags, category, etc.) and content (HTML-formatted).
* `generators` generate the different outputs. For instance, Pelican comes with
`ArticlesGenerator` and `PageGenerator`. Given a configuration, they can do
* **Generators** generate the different outputs. For instance, Pelican comes with
``ArticlesGenerator`` and ``PageGenerator``. Given a configuration, they can do
whatever they want. Most of the time, it's generating files from inputs.
* `pelican` also uses `templates`, so it's easy to write your own theme. The
syntax is `jinja2`, and, trust me, really easy to learn, so don't hesitate
to jump in and build your own theme.
* Pelican also uses templates, so it's easy to write your own theme. The
syntax is `Jinja2 <http://jinja.pocoo.org/>`_ and is very easy to learn, so
don't hesitate to jump in and build your own theme.
How to implement a new reader?
==============================
Is there an awesome markup language you want to add to Pelican?
Well, the only thing you have to do is to create a class with a `read`
Well, the only thing you have to do is to create a class with a ``read``
method that returns HTML content and some metadata.
Take a look at the Markdown reader::
@ -65,8 +65,8 @@ Take a look at the Markdown reader::
Simple, isn't it?
If your new reader requires additional Python dependencies, then you should wrap
their `import` statements in a `try...except` block. Then inside the reader's
class, set the `enabled` class attribute to mark import success or failure.
their ``import`` statements in a ``try...except`` block. Then inside the reader's
class, set the ``enabled`` class attribute to mark import success or failure.
This makes it possible for users to continue using their favourite markup method
without needing to install modules for formats they don't use.
@ -76,17 +76,17 @@ How to implement a new generator?
Generators have two important methods. You're not forced to create
both; only the existing ones will be called.
* `generate_context`, that is called first, for all the generators.
* ``generate_context``, that is called first, for all the generators.
Do whatever you have to do, and update the global context if needed. This
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 stage,
as it is likely to change by the effect of other generators.
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
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,
* ``generate_output`` is then called. And guess what is it made for? Oh,
generating the output. :) It's here that you may want to look at the context
and call the methods of the `writer` object that is passed as the first
argument of this function. In the `PageGenerator` example, this method will
and call the methods of the ``writer`` object that is passed as the first
argument of this function. In the ``PageGenerator`` example, this method will
look at all the pages recorded in the global context and output a file on
the disk (using the writer method `write_file`) for each page encountered.
the disk (using the writer method ``write_file``) for each page encountered.

108
docs/plugins.rst Normal file
View file

@ -0,0 +1,108 @@
.. _plugins:
Plugins
#######
Since version 3.0, Pelican manages plugins. Plugins are a way to add features
to Pelican without having to directly hack Pelican code.
Pelican is shipped with a set of core plugins, but you can easily implement
your own (and this page describes how).
How to use plugins
==================
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',]
Or by importing them and adding them to the list::
from pelican.plugins import gravatar
PLUGINS = [gravatar, ]
If your plugins are not in an importable path, you can specify a ``PLUGIN_PATH``
in the settings::
PLUGIN_PATH = "plugins"
PLUGINS = ["list", "of", "plugins"]
How to create plugins
=====================
Plugins are based on the concept of signals. Pelican sends signals, and plugins
subscribe to those signals. The list of signals are defined in a following
section.
The only rule to follow for plugins is to define a ``register`` callable, in
which you map the signals to your plugin logic. Let's take a simple example::
from pelican import signals
def test(sender):
print "%s initialized !!" % sender
def register():
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__
========================= ============================ =========================================
The list is currently small, don't hesitate to add signals and make a pull
request if you need them!
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:
Tag cloud
---------
Translation
-----------
Github Activity
---------------
This plugin makes use of the ``feedparser`` library that you'll need to
install.
Set the ``GITHUB_ACTIVITY_FEED`` parameter to your Github activity feed.
For example, my setting would look like::
GITHUB_ACTIVITY_FEED = 'https://github.com/kpanic.atom'
On the templates side, you just have to iterate over the ``github_activity``
variable, as in the example::
{% if GITHUB_ACTIVITY_FEED %}
<div class="social">
<h2>Github Activity</h2>
<ul>
{% for entry in github_activity %}
<li><b>{{ entry[0] }}</b><br /> {{ entry[1] }}</li>
{% endfor %}
</ul>
</div><!-- /.github_activity -->
{% endif %}
``github_activity`` is a list of lists. The first element is the title
and the second element is the raw HTML from Github.

View file

@ -1,40 +1,40 @@
Some history about pelican
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
up to date.
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
(markdown or restructured text for now), and generate a HTML folder
(Markdown or reStructuredText for now) and generates an HTML folder
with all the files in it.
I've chosen to use python to implement pelican because it seemed to
I've chosen to use Python to implement Pelican because it seemed to
be simple and to fit to my needs. I did not wanted to define a class for
each thing, but still wanted to keep my things loosely coupled.
It turns out that it was exactly what I wanted. From time to time,
thanks to the feedback of some users, it took me a very few time to
provide fixes on it. So far, I've re-factored the pelican code by two
times, each time took less than 30 minutes.
provide fixes on it. So far, I've re-factored the Pelican code by two
times; each time took less than 30 minutes.
Use case
========
I was previously using wordpress, a solution you can host on a web
I was previously using WordPress, a solution you can host on a web
server to manage your blog. Most of the time, I prefer using markup
languages such as Markdown or RestructuredText to type my articles.
languages such as Markdown or reStructuredText to type my articles.
To do so, I use vim. I think it is important to let the people choose the
tool they want to write the articles. In my opinion, a blog manager
should just allow you to take any kind of input and transform it to a
weblog. That's what pelican does.
weblog. That's what Pelican does.
You can write your articles using the tool you want, and the markup
language you want, and then generate a static HTML weblog
language you want, and then generate a static HTML weblog.
.. image:: _static/overall.png
To be flexible enough, pelican have a template support, so you can
easily write you own themes if you want to.
To be flexible enough, Pelican has template support, so you can easily write
your own themes if you want to.
Design process
==============
@ -42,19 +42,18 @@ Design process
Pelican came from a need I have. I started by creating a single file
application, and I have make it grow to support what it does by now.
To start, I wrote a piece of documentation about what I wanted to do.
Then, I have created the content I wanted to parse (the restructured
text files), and started experimenting with the code.
Pelican was 200 lines long, and contained almost ten functions and one
class when it was first usable.
Then, I created the content I wanted to parse (the reStructuredText files)
and started experimenting with the code. Pelican was 200 lines long and
contained almost ten functions and one class when it was first usable.
I have been facing different problems all over the time, and wanted to
add features to pelican while using it. The first change I have done was
I have been facing different problems all over the time and wanted to
add features to Pelican while using it. The first change I have done was
to add the support of a settings file. It is possible to pass the options to
the command line, but can be tedious if there is a lot of them.
In the same way, I have added the support of different things over
time: atom feeds, multiple themes, multiple markup support, etc.
At some point, it appears that the “only one file” mantra was not good
enough for pelican, so I decided to rework a bit all that, and split this in
time: Atom feeds, multiple themes, multiple markup support, etc.
At some point, it appears that the "only one file" mantra was not good
enough for Pelican, so I decided to rework a bit all that, and split this in
multiple different files.
Ive separated the logic in different classes and concepts:
@ -64,59 +63,59 @@ Ive separated the logic in different classes and concepts:
Since those operations are commonly used, the object is created
once, and then passed to the generators.
* *readers* are used to read from various formats (Markdown, and
Restructured Text for now, but the system is extensible). Given a
file, they return metadata (author, tags, category etc) and
content (HTML formated).
* *readers* are used to read from various formats (Markdown and
reStructuredText for now, but the system is extensible). Given a
file, they return metadata (author, tags, category, etc) and
content (HTML formatted).
* *generators* generate the different outputs. For instance, pelican
* *generators* generate the different outputs. For instance, Pelican
comes with an ArticlesGenerator and PagesGenerator, into
others. Given a configuration, they can do whatever you want
them to do. Most of the time its generating files from inputs
them to do. Most of the time it's generating files from inputs
(user inputs and files).
I also deal with contents objects. They can be `Articles`, `Pages`, `Quotes`,
or whatever you want. They are defined in the contents.py module,
and represent some content to be used by the program.
I also deal with contents objects. They can be ``Articles``, ``Pages``,
``Quotes``, or whatever you want. They are defined in the ``contents.py``
module and represent some content to be used by the program.
In more details
===============
In more detail
==============
Here is an overview of the classes involved in pelican.
Here is an overview of the classes involved in Pelican.
.. image:: _static/uml.jpg
The interface do not really exists, and I have added it only to clarify the
whole picture. I do use duck typing, and not interfaces.
The interface does not really exist, and I have added it only to clarify the
whole picture. I do use duck typing and not interfaces.
Internally, the following process is followed:
* First of all, the command line is parsed, and some content from
the user are used to initialize the different generator objects.
the user is used to initialize the different generator objects.
* A `context` is created. It contains the settings from the command
* A ``context`` is created. It contains the settings from the command
line and a settings file if provided.
* The `generate_context` method of each generator is called, updating
* The ``generate_context`` method of each generator is called, updating
the context.
* The writer is created, and given to the `generate_output` method of
* The writer is created and given to the ``generate_output`` method of
each generator.
I make two calls because it is important that when the output is
generated by the generators, the context will not change. In other
words, the first method `generate_context` should modify the context,
whereas the second `generate_output` method should not.
words, the first method ``generate_context`` should modify the context,
whereas the second ``generate_output`` method should not.
Then, it is up to the generators to do what the want, in the
`generate_context` and `generate_content` method.
Taking the `ArticlesGenerator` class will help to understand some others
concepts. Here is what happens when calling the `generate_context`
``generate_context`` and ``generate_content`` method.
Taking the ``ArticlesGenerator`` class will help to understand some others
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,
use `Reader` objects.
* Update the `context` with all those articles.
each of them, and construct a content object (``Article``) with it. To do so,
use ``Reader`` objects.
* Update the ``context`` with all those articles.
Then, the `generate_content` method uses the `context` and the `writer` to
generate the wanted output
Then, the ``generate_content`` method uses the ``context`` and the ``writer`` to
generate the wanted output.

View file

@ -41,8 +41,8 @@ Setting name (default value) What doe
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 output directory as well as
the generated files.
`DELETE_OUTPUT_DIRECTORY` (``False``) Delete the content of the output directory before
generating new files.
`LOCALE` (''[#]_) Change the locale. A list of locales can be provided
here or a single string representing one locale.
When providing a list, all the locales will be tried
@ -65,6 +65,7 @@ Setting name (default value) What doe
`rst2pdf`.
`RELATIVE_URLS` (``True``) Defines whether Pelican should use relative URLs or
not.
`PLUGINS` (``[]``) The list of plugins to load. See :ref:`plugins`.
`SITENAME` (``'A Pelican Blog'``) Your site name
`SITEURL` Base URL of your website. Not defined by default,
which means the base URL is assumed to be "/" with a
@ -94,6 +95,12 @@ Setting name (default value) What doe
index pages for collections of content e.g. tags and
category index pages.
`PAGINATED_DIRECT_TEMPLATES` (``('index',)``) Provides the direct templates that should be paginated.
`SUMMARY_MAX_LENGTH` (``50``) When creating a short summary of an article, this will
be the default length in words of the text created.
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.
===================================================================== =====================================================================
.. [#] Default is the system locale.
@ -373,6 +380,7 @@ Setting name (default value) What does it do?
value is `static`, but if your theme has
other static paths, you can put them here.
`CSS_FILE` (``'main.css'``) Specify the CSS file you want to load.
`WEBASSETS` (``False``) Asset management with `webassets` (see below)
================================================ =====================================================
By default, two themes are available. You can specify them using the `-t` option:
@ -400,6 +408,7 @@ Setting name What does it do ?
`GITHUB_URL` Your GitHub URL (if you have one). It will then
use this information to create a GitHub ribbon.
`GOOGLE_ANALYTICS` 'UA-XXXX-YYYY' to activate Google Analytics.
`GOSQUARED_SITENAME` 'XXX-YYYYYY-X' to activate GoSquared.
`MENUITEMS` A list of tuples (Title, URL) for additional menu
items to appear at the beginning of the main menu.
`PIWIK_URL` URL to your Piwik server - without 'http://' at the
@ -422,7 +431,58 @@ adding the following to your configuration::
CSS_FILE = "wide.css"
.. _pelican-themes: :doc:`pelican-themes`
Asset management
----------------
The `WEBASSETS` setting allows to use the `webassets`_ module to manage assets
(css, js). 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`_):
* 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.
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:
.. code-block:: jinja
{% assets filters="cssmin", output="css/style.min.css", "css/inuit.css", "css/pygment-monokai.css", "css/main.css" %}
<link rel="stylesheet" href="{{ ASSET_URL }}">
{% endassets %}
will produce a minified css file with the version identifier:
.. code-block:: html
<link href="http://{SITEURL}/theme/css/style.min.css?b3a7c807" rel="stylesheet">
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>
{% endassets %}
will produce a minified and gzipped js file:
.. code-block:: html
<script src="http://{SITEURL}/theme/js/packed.js?00703b9d"></script>
.. _webassets: https://github.com/miracle2k/webassets
.. _documentation: http://webassets.readthedocs.org/en/latest/builtin_filters.html
Example settings
================

View file

@ -3,11 +3,10 @@
How to create themes for Pelican
################################
Pelican uses the great `jinja2 <http://jinja.pocoo.org>`_ templating engine to
generate its HTML output. The jinja2 syntax is really simple. If you want to
create your own theme, feel free to take inspiration from the "simple" theme,
which is available `here
<https://github.com/ametaireau/pelican/tree/master/pelican/themes/simple/templates>`_
Pelican uses the great `Jinja2 <http://jinja.pocoo.org/>`_ templating engine to
generate its HTML output. Jinja2 syntax is really simple. If you want to
create your own theme, feel free to take inspiration from the `"simple" theme
<https://github.com/ametaireau/pelican/tree/master/pelican/themes/simple/templates>`_.
Structure
=========

View file

@ -10,13 +10,33 @@ GitHub comes with an interesting "pages" feature: you can upload things there
and it will be available directly from their servers. As Pelican is a static
file generator, we can take advantage of this.
User Pages
----------
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
and deploy the master branch to GitHub::
$ pelican -s pelican.conf.py ./path/to/posts -o /path/to/output
Now add all the files in the output directory generated by Pelican::
$ git add /path/to/output/*
$ git commit -am "Your Message"
$ git push origin master
Project Pages
-------------
For creating Project pages, a branch called ``gh-pages`` is used for publishing.
The excellent `ghp-import <https://github.com/davisp/ghp-import>`_ makes this
really easy. You will have to install it::
really easy, which can be installed via::
$ pip install ghp-import
Then, given a repository containing your articles, you would simply have
to run Pelican and upload the output to GitHub::
Then, given a repository containing your articles, you would simply run
Pelican and upload the output to GitHub::
$ pelican -s pelican.conf.py .
$ ghp-import output
@ -25,9 +45,8 @@ to run Pelican and upload the output to GitHub::
And that's it.
If you want, you can put that directly into a post-commit hook, so each time you
commit, your blog is up to date on GitHub!
commit, your blog is up-to-date on GitHub!
Put the following into `.git/hooks/post-commit`::
Put the following into ``.git/hooks/post-commit``::
pelican -s pelican.conf.py . && ghp-import output && git push origin
gh-pages
pelican -s pelican.conf.py . && ghp-import output && git push origin gh-pages

View file

@ -5,11 +5,13 @@ import time
import logging
import argparse
from pelican import signals
from pelican.generators import (ArticlesGenerator, PagesGenerator,
StaticGenerator, PdfGenerator, LessCSSGenerator)
from pelican.log import init
from pelican.settings import read_settings, _DEFAULT_CONFIG
from pelican.utils import clean_output_dir, files_changed
from pelican.utils import clean_output_dir, files_changed, file_changed
from pelican.writers import Writer
__major__ = 3
@ -22,7 +24,7 @@ logger = logging.getLogger(__name__)
class Pelican(object):
def __init__(self, settings=None, path=None, theme=None, output_path=None,
markup=None, delete_outputdir=False):
markup=None, delete_outputdir=False, plugin_path=None):
"""Read the settings, and performs some checks on the environment
before doing anything else.
"""
@ -58,6 +60,20 @@ class Pelican(object):
else:
raise Exception("Impossible to find the theme %s" % theme)
self.init_plugins()
signals.initialized.send(self)
def init_plugins(self):
self.plugins = self.settings['PLUGINS']
for plugin in self.plugins:
# if it's a string, then import it
if isinstance(plugin, basestring):
logger.debug("Loading plugin `{0}' ...".format(plugin))
plugin = __import__(plugin, globals(), locals(), 'module')
logger.debug("Registering plugin `{0}' ...".format(plugin.__name__))
plugin.register()
def _handle_deprecation(self):
if self.settings.get('CLEAN_URLS', False):
@ -126,15 +142,20 @@ class Pelican(object):
writer = self.get_writer()
# pass the assets environment to the generators
if self.settings['WEBASSETS']:
generators[1].env.assets_environment = generators[0].assets_env
generators[2].env.assets_environment = generators[0].assets_env
for p in generators:
if hasattr(p, 'generate_output'):
p.generate_output(writer)
def get_generator_classes(self):
generators = [ArticlesGenerator, PagesGenerator, StaticGenerator]
generators = [StaticGenerator, ArticlesGenerator, PagesGenerator]
if self.settings['PDF_GENERATOR']:
generators.append(PdfGenerator)
if self.settings['LESS_GENERATOR']: # can be True or PATH to lessc
if self.settings['LESS_GENERATOR']: # can be True or PATH to lessc
generators.append(LessCSSGenerator)
return generators
@ -192,11 +213,7 @@ def parse_arguments():
return parser.parse_args()
def main():
args = parse_arguments()
init(args.verbosity)
# Split the markup languages only if some have been given. Otherwise,
# populate the variable with None.
def get_instance(args):
markup = [a.strip().lower() for a in args.markup.split(',')]\
if args.markup else None
@ -208,9 +225,18 @@ def main():
module = __import__(module)
cls = getattr(module, cls_name)
return cls(settings, args.path, args.theme, args.output, markup,
args.delete_outputdir)
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:
pelican = cls(settings, args.path, args.theme, args.output, markup,
args.delete_outputdir)
if args.autoreload:
while True:
try:
@ -222,6 +248,14 @@ def main():
if files_changed(pelican.path, pelican.markup) or \
files_changed(pelican.theme, ['']):
pelican.run()
# reload also if settings.py changed
if file_changed(args.settings):
logger.info('%s changed, re-generating' %
args.settings)
pelican = get_instance(args)
pelican.run()
time.sleep(.5) # sleep to avoid cpu load
except KeyboardInterrupt:
break

View file

@ -139,7 +139,9 @@ class Page(object):
if hasattr(self, '_summary'):
return self._summary
else:
return truncate_html_words(self.content, 50)
if self.settings['SUMMARY_MAX_LENGTH']:
return truncate_html_words(self.content, self.settings['SUMMARY_MAX_LENGTH'])
return self.content
def _set_summary(self, summary):
"""Dummy function"""

View file

@ -17,6 +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 import signals
logger = logging.getLogger(__name__)
@ -42,7 +43,7 @@ class Generator(object):
simple_loader = FileSystemLoader(os.path.join(theme_path,
"themes", "simple", "templates"))
self._env = Environment(
self.env = Environment(
loader=ChoiceLoader([
FileSystemLoader(self._templates_path),
simple_loader, # implicit inheritance
@ -51,11 +52,11 @@ class Generator(object):
extensions=self.settings.get('JINJA_EXTENSIONS', []),
)
logger.debug('template list: {0}'.format(self._env.list_templates()))
logger.debug('template list: {0}'.format(self.env.list_templates()))
# get custom Jinja filters from user settings
custom_filters = self.settings.get('JINJA_FILTERS', {})
self._env.filters.update(custom_filters)
self.env.filters.update(custom_filters)
def get_template(self, name):
"""Return the template by name.
@ -64,7 +65,7 @@ class Generator(object):
"""
if name not in self._templates:
try:
self._templates[name] = self._env.get_template(name + '.html')
self._templates[name] = self.env.get_template(name + '.html')
except TemplateNotFound:
raise Exception('[templates] unable to load %s.html from %s' \
% (name, self._templates_path))
@ -118,6 +119,7 @@ class ArticlesGenerator(Generator):
self.authors = defaultdict(list)
super(ArticlesGenerator, self).__init__(*args, **kwargs)
self.drafts = []
signals.article_generator_init.send(self)
def generate_feeds(self, writer):
"""Generate the feeds from the current context, and output files."""
@ -245,7 +247,9 @@ class ArticlesGenerator(Generator):
def generate_context(self):
"""change the context"""
article_path = os.path.join(self.path, self.settings['ARTICLE_DIR'])
article_path = os.path.normpath( # we have to remove trailing slashes
os.path.join(self.path, self.settings['ARTICLE_DIR'])
)
all_articles = []
for f in self.get_files(
article_path,
@ -259,8 +263,8 @@ class ArticlesGenerator(Generator):
# if no category is set, use the name of the path as a category
if 'category' not in metadata:
if os.path.dirname(f) == article_path:
category = self.settings['DEFAULT_CATEGORY']
if os.path.dirname(f) == article_path: # if the article is not in a subdirectory
category = self.settings['DEFAULT_CATEGORY']
else:
category = os.path.basename(os.path.dirname(f))\
.decode('utf-8')
@ -276,6 +280,7 @@ class ArticlesGenerator(Generator):
metadata['date'] = datetime.datetime(
*self.settings['DEFAULT_DATE'])
signals.article_generate_context.send(self, metadata=metadata)
article = Article(content, metadata, settings=self.settings,
filename=f)
if not is_valid_content(article, f):
@ -288,6 +293,10 @@ class ArticlesGenerator(Generator):
all_articles.append(article)
elif article.status == "draft":
self.drafts.append(article)
else:
logger.warning(u"Unknown status %s for file %s, skipping it." %
(repr(unicode.encode(article.status, 'utf-8')),
repr(f)))
self.articles, self.translations = process_translations(all_articles)
@ -352,10 +361,13 @@ class PagesGenerator(Generator):
def __init__(self, *args, **kwargs):
self.pages = []
self.hidden_pages = []
self.hidden_translations = []
super(PagesGenerator, self).__init__(*args, **kwargs)
def generate_context(self):
all_pages = []
hidden_pages = []
for f in self.get_files(
os.path.join(self.path, self.settings['PAGE_DIR']),
exclude=self.settings['PAGE_EXCLUDES']):
@ -368,15 +380,25 @@ class PagesGenerator(Generator):
filename=f)
if not is_valid_content(page, f):
continue
all_pages.append(page)
if page.status == "published":
all_pages.append(page)
elif page.status == "hidden":
hidden_pages.append(page)
else:
logger.warning(u"Unknown status %s for file %s, skipping it." %
(repr(unicode.encode(page.status, 'utf-8')),
repr(f)))
self.pages, self.translations = process_translations(all_pages)
self.hidden_pages, self.hidden_translations = process_translations(hidden_pages)
self._update_context(('pages', ))
self.context['PAGES'] = self.pages
def generate_output(self, writer):
for page in chain(self.translations, self.pages):
for page in chain(self.translations, self.pages,
self.hidden_translations, self.hidden_pages):
writer.write_file(page.save_as, self.get_template('page'),
self.context, page=page,
relative_urls=self.settings.get('RELATIVE_URLS'))
@ -393,7 +415,23 @@ class StaticGenerator(Generator):
copy(path, source, os.path.join(output_path, destination),
final_path, overwrite=True)
def generate_context(self):
if self.settings['WEBASSETS']:
from webassets import Environment as AssetsEnvironment
# 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/'
assets_src = os.path.join(self.output_path, 'theme')
self.assets_env = AssetsEnvironment(assets_src, assets_url)
if logging.getLevelName(logger.getEffectiveLevel()) == "DEBUG":
self.assets_env.debug = True
def generate_output(self, writer):
self._copy_paths(self.settings['STATIC_PATHS'], self.path,
'static', self.output_path)
self._copy_paths(self.settings['THEME_STATIC_PATHS'], self.theme,

View file

View file

@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) Marco Milanesi <kpanic@gnufunk.org>
A plugin to list your Github Activity
To enable it set in your pelican config file the GITHUB_ACTIVITY_FEED
parameter pointing to your github activity feed.
for example my personal activity feed is:
https://github.com/kpanic.atom
in your template just write a for in jinja2 syntax against the
github_activity variable.
i.e.
<div class="social">
<h2>Github Activity</h2>
<ul>
{% for entry in github_activity %}
<li><b>{{ entry[0] }}</b><br /> {{ entry[1] }}</li>
{% endfor %}
</ul>
</div><!-- /.github_activity -->
github_activity is a list containing a list. The first element is the title
and the second element is the raw html from github
"""
from pelican import signals
class GitHubActivity():
"""
A class created to fetch github activity with feedparser
"""
def __init__(self, generator):
try:
import feedparser
self.activities = feedparser.parse(
generator.settings['GITHUB_ACTIVITY_FEED'])
except ImportError:
raise Exception("Unable to find feedparser")
def fetch(self):
"""
returns a list of html snippets fetched from github actitivy feed
"""
entries = []
for activity in self.activities['entries']:
entries.append(
[element for element in [activity['title'],
activity['content'][0]['value']]])
return entries
def fetch_github_activity(gen, metadata):
"""
registered handler for the github activity plugin
it puts in generator.context the html needed to be displayed on a
template
"""
if 'GITHUB_ACTIVITY_FEED' in gen.settings.keys():
gen.context['github_activity'] = gen.plugin_instance.fetch()
def feed_parser_initialization(generator):
"""
Initialization of feed parser
"""
generator.plugin_instance = GitHubActivity(generator)
def register():
"""
Plugin registration
"""
signals.article_generator_init.connect(feed_parser_initialization)
signals.article_generate_context.connect(fetch_github_activity)

View file

@ -0,0 +1,23 @@
from pelican import signals
"""
License plugin for Pelican
==========================
Simply add license variable in article's context, which contain
the license text.
Settings:
---------
Add LICENSE to your settings file to define default license.
"""
def add_license(generator, metadata):
if 'license' not in metadata.keys()\
and 'LICENSE' in generator.settings.keys():
metadata['license'] = generator.settings['LICENSE']
def register():
signals.article_generate_context.connect(add_license)

View file

@ -0,0 +1,40 @@
import hashlib
from pelican import signals
"""
Gravatar plugin for Pelican
===========================
Simply add author_gravatar variable in article's context, which contains
the gravatar url.
Settings:
---------
Add AUTHOR_EMAIL to your settings file to define default author email.
Article metadata:
------------------
:email: article's author email
If one of them are defined, the author_gravatar variable is added to
article's context.
"""
def add_gravatar(generator, metadata):
#first check email
if 'email' not in metadata.keys()\
and 'AUTHOR_EMAIL' in generator.settings.keys():
metadata['email'] = generator.settings['AUTHOR_EMAIL']
#then add gravatar url
if 'email' in metadata.keys():
gravatar_url = "http://www.gravatar.com/avatar/" + \
hashlib.md5(metadata['email'].lower()).hexdigest()
metadata["author_gravatar"] = gravatar_url
def register():
signals.article_generate_context.connect(add_gravatar)

View file

@ -0,0 +1,63 @@
from docutils import nodes
from docutils.parsers.rst import directives, Directive
from pelican import log
"""
HTML tags for reStructuredText
==============================
Directives
----------
.. html::
(HTML code)
Example
-------
A search engine:
.. html::
<form action="http://seeks.fr/search" method="GET">
<input type="text" value="Pelican v2" title="Search" maxlength="2048" name="q" autocomplete="on" />
<input type="hidden" name="lang" value="en" />
<input type="submit" value="Seeks !" id="search_button" />
</form>
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>
"""
class RawHtml(Directive):
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = True
has_content = True
def run(self):
html = u' '.join(self.content)
node = nodes.raw('', html, format='html')
return [node]
def register():
directives.register_directive('html', RawHtml)

View file

@ -0,0 +1,7 @@
from pelican import signals
def test(sender):
print "%s initialized !!" % sender
def register():
signals.initialized.connect(test)

View file

@ -36,3 +36,61 @@ class Pygments(Directive):
directives.register_directive('code-block', Pygments)
directives.register_directive('sourcecode', Pygments)
class YouTube(Directive):
""" Embed YouTube video in posts.
Courtesy of Brian Hsu: https://gist.github.com/1422773
VIDEO_ID is required, with / height are optional integer,
and align could be left / center / right.
Usage:
.. youtube:: VIDEO_ID
:width: 640
:height: 480
:align: center
"""
def align(argument):
"""Conversion function for the "align" option."""
return directives.choice(argument, ('left', 'center', 'right'))
required_arguments = 1
optional_arguments = 2
option_spec = {
'width': directives.positive_int,
'height': directives.positive_int,
'align': align
}
final_argument_whitespace = False
has_content = False
def run(self):
videoID = self.arguments[0].strip()
width = 420
height = 315
align = 'left'
if 'width' in self.options:
width = self.options['width']
if 'height' in self.options:
height = self.options['height']
if 'align' in self.options:
align = self.options['align']
url = 'http://www.youtube.com/embed/%s' % videoID
div_block = '<div class="youtube" align="%s">' % align
embed_block = '<iframe width="%s" height="%s" src="%s" '\
'frameborder="0"></iframe>' % (width, height, url)
return [
nodes.raw('', div_block, format='html'),
nodes.raw('', embed_block, format='html'),
nodes.raw('', '</div>', format='html')]
directives.register_directive('youtube', YouTube)

View file

@ -68,6 +68,9 @@ _DEFAULT_CONFIG = {'PATH': '.',
'ARTICLE_PERMALINK_STRUCTURE': '',
'TYPOGRIFY': False,
'LESS_GENERATOR': False,
'SUMMARY_MAX_LENGTH': 50,
'WEBASSETS': False,
'PLUGINS': [],
}
@ -150,4 +153,12 @@ def configure_settings(settings, default_settings=None, filename=None):
"http://docs.notmyidea.org/alexis/pelican/settings.html#timezone "
"for more information")
if 'WEBASSETS' in settings and settings['WEBASSETS'] is not False:
try:
from webassets.ext.jinja2 import AssetsExtension
settings['JINJA_EXTENSIONS'].append(AssetsExtension)
except ImportError:
logger.warn("You must install the webassets module to use WEBASSETS.")
settings['WEBASSETS'] = False
return settings

5
pelican/signals.py Normal file
View file

@ -0,0 +1,5 @@
from blinker import signal
initialized = signal('pelican_initialized')
article_generate_context = signal('article_generate_context')
article_generator_init = signal('article_generator_init')

View file

@ -312,6 +312,7 @@ img.left, figure.left {float: right; margin: 0 0 2em 2em;}
.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');}
/*
About

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -29,7 +29,8 @@
{% endif %}
<li><article class="hentry">
<header>
<h1><a href="{{ SITEURL }}/{{ article.url }}" rel="bookmark" title="Permalink to {{ article.title}}">{{ article.title }}</a></h1>
<h1><a href="{{ SITEURL }}/{{ article.url }}" rel="bookmark"
title="Permalink to {{ article.title|striptags }}">{{ article.title }}</a></h1>
</header>
<div class="entry-content">
@ -40,12 +41,12 @@
</div><!-- /.entry-content -->
</article></li>
{% endif %}
{% if loop.last and (articles_page.has_previous()
or not articles_page.has_previous() and loop.length > 1) %}
{% include 'pagination.html' %}
{% endif %}
{% if loop.last %}
</ol><!-- /#posts-list -->
{% if loop.last and (articles_page.has_previous()
or not articles_page.has_previous() and loop.length > 1) %}
{% include 'pagination.html' %}
{% endif %}
</section><!-- /#content -->
{% endif %}
{% endfor %}

View file

@ -0,0 +1,14 @@
{% if GOSQUARED_SITENAME %}
<script type="text/javascript">
var GoSquared={};
GoSquared.acct = "{{ GOSQUARED_SITENAME }}";
(function(w){
function gs(){
w._gstc_lt=+(new Date); var d=document;
var g = d.createElement("script"); g.type = "text/javascript"; g.async = true; g.src = "//d1l6p2sc9645hc.cloudfront.net/tracker.js";
var s = d.getElementsByTagName("script")[0]; s.parentNode.insertBefore(g, s);
}
w.addEventListener?w.addEventListener("load",gs,false):w.attachEvent("onload",gs);
})(window);
</script>
{% endif %}

View file

@ -25,8 +25,14 @@ def wp2fields(xml):
items = soup.rss.channel.findAll('item')
for item in items:
if item.fetch('wp:status')[0].contents[0] == "publish":
title = item.title.contents[0]
try:
title = item.title.contents[0]
except IndexError:
continue
content = item.fetch('content:encoded')[0].contents[0]
filename = item.fetch('wp:post_name')[0].contents[0]
@ -197,7 +203,7 @@ def build_markdown_header(title, date, author, categories, tags):
header += '\n'
return header
def fields2pelican(fields, out_markup, output_path, dircat=False):
def fields2pelican(fields, out_markup, output_path, dircat=False, strip_raw=False):
for title, content, filename, date, author, categories, tags, in_markup in fields:
if (in_markup == "markdown") or (out_markup == "markdown") :
ext = '.md'
@ -230,22 +236,26 @@ def fields2pelican(fields, out_markup, output_path, dircat=False):
paragraphs = [u'<p>{}</p>'.format(p) for p in paragraphs]
new_content = ''.join(paragraphs)
fp.write(content)
fp.write(new_content)
cmd = 'pandoc --normalize --reference-links --from=html --to={0} -o "{1}" "{2}"'.format(
out_markup, out_filename, html_filename)
parse_raw = '--parse-raw' if not strip_raw else ''
cmd = ('pandoc --normalize --reference-links {0} --from=html'
' --to={1} -o "{2}" "{3}"').format(
parse_raw, out_markup, out_filename, html_filename)
try:
rc = subprocess.call(cmd, shell=True)
if rc < 0:
print("Child was terminated by signal %d" % -rc)
exit()
error = "Child was terminated by signal %d" % -rc
exit(error)
elif rc > 0:
print("Please, check your Pandoc installation.")
exit()
error = "Please, check your Pandoc installation."
exit(error)
except OSError, e:
print("Pandoc execution failed: %s" % e)
exit()
error = "Pandoc execution failed: %s" % e
exit(error)
os.remove(html_filename)
@ -279,6 +289,10 @@ def main():
help='Output markup format (supports rst & markdown)')
parser.add_argument('--dir-cat', action='store_true', dest='dircat',
help='Put files in directories with categories name')
parser.add_argument('--strip-raw', action='store_true', dest='strip_raw',
help="Strip raw HTML code that can't be converted to "
"markup such as flash embeds or iframes (wordpress import only)")
args = parser.parse_args()
input_type = None
@ -289,15 +303,15 @@ def main():
elif args.feed:
input_type = 'feed'
else:
print("You must provide either --wpfile, --dotclear or --feed options")
exit()
error = "You must provide either --wpfile, --dotclear or --feed options"
exit(error)
if not os.path.exists(args.output):
try:
os.mkdir(args.output)
except OSError:
print("Unable to create the output folder: " + args.output)
exit()
error = "Unable to create the output folder: " + args.output
exit(error)
if input_type == 'wordpress':
fields = wp2fields(args.input)
@ -306,4 +320,6 @@ def main():
elif input_type == 'feed':
fields = feed2fields(args.input)
fields2pelican(fields, args.markup, args.output, dircat=args.dircat or False)
fields2pelican(fields, args.markup, args.output,
dircat=args.dircat or False,
strip_raw=args.strip_raw or False)

View file

@ -48,9 +48,11 @@ def main():
parser.add_argument('-i', '--install', dest='to_install', nargs='+', metavar="theme path",
help='The themes to install ')
help='The themes to install')
parser.add_argument('-r', '--remove', dest='to_remove', nargs='+', metavar="theme name",
help='The themes to remove')
parser.add_argument('-U', '--upgrade', dest='to_upgrade', nargs='+',
metavar="theme path", help='The themes to upgrade')
parser.add_argument('-s', '--symlink', dest='to_symlink', nargs='+', metavar="theme path",
help="Same as `--install', but create a symbolic link instead of copying the theme. Useful for theme development")
parser.add_argument('-c', '--clean', dest='clean', action="store_true",
@ -62,6 +64,9 @@ def main():
args = parser.parse_args()
to_install = args.to_install or args.to_upgrade
to_sym = args.to_symlink or args.clean
if args.action:
@ -69,8 +74,7 @@ def main():
list_themes(args.verbose)
elif args.action is 'path':
print(_THEMES_PATH)
elif args.to_install or args.to_remove or args.to_symlink or args.clean:
elif to_install or args.to_remove or to_sym:
if args.to_remove:
if args.verbose:
print('Removing themes...')
@ -85,6 +89,13 @@ def main():
for i in args.to_install:
install(i, v=args.verbose)
if args.to_upgrade:
if args.verbose:
print('Upgrading themes...')
for i in args.to_upgrade:
install(i, v=args.verbose, u=True)
if args.to_symlink:
if args.verbose:
print('Linking themes...')
@ -149,17 +160,21 @@ def remove(theme_name, v=False):
err(target + ' : no such file or directory')
def install(path, v=False):
def install(path, v=False, u=False):
"""Installs a theme"""
if not os.path.exists(path):
err(path + ' : no such file or directory')
elif not os.path.isdir(path):
err(path + ' : no a directory')
err(path + ' : not a directory')
else:
theme_name = os.path.basename(os.path.normpath(path))
theme_path = os.path.join(_THEMES_PATH, theme_name)
if os.path.exists(theme_path):
exists = os.path.exists(theme_path)
if exists and not u:
err(path + ' : already exists')
elif exists and u:
remove(theme_name, v)
install(path, v)
else:
if v:
print("Copying `{p}' to `{t}' ...".format(p=path, t=theme_path))
@ -174,7 +189,7 @@ def symlink(path, v=False):
if not os.path.exists(path):
err(path + ' : no such file or directory')
elif not os.path.isdir(path):
err(path + ' : no a directory')
err(path + ' : not a directory')
else:
theme_name = os.path.basename(os.path.normpath(path))
theme_path = os.path.join(_THEMES_PATH, theme_name)

View file

@ -4,6 +4,7 @@ import re
import pytz
import shutil
import logging
from collections import defaultdict
from codecs import open as _open
from datetime import datetime
@ -91,10 +92,22 @@ def clean_output_dir(path):
"""Remove all the files from the output directory"""
# remove all the existing content from the output folder
try:
shutil.rmtree(path)
except Exception:
pass
for filename in os.listdir(path):
file = os.path.join(path, filename)
if os.path.isdir(file):
try:
shutil.rmtree(file)
logger.debug("Deleted directory %s" % file)
except Exception, e:
logger.error("Unable to delete directory %s; %e" % file, e)
elif os.path.isfile(file) or os.path.islink(file):
try:
os.remove(file)
logger.debug("Deleted file/link %s" % file)
except Exception, e:
logger.error("Unable to delete file %s; %e" % file, e)
else:
logger.error("Unable to delete %s, file type unknown" % file)
def get_relative_path(filename):
@ -221,9 +234,9 @@ def files_changed(path, extensions):
"""Return the last time files have been modified"""
for root, dirs, files in os.walk(path):
dirs[:] = [x for x in dirs if x[0] != '.']
for file in files:
if any(file.endswith(ext) for ext in extensions):
yield os.stat(os.path.join(root, file)).st_mtime
for f in files:
if any(f.endswith(ext) for ext in extensions):
yield os.stat(os.path.join(root, f)).st_mtime
global LAST_MTIME
mtime = max(file_times(path))
@ -233,6 +246,21 @@ def files_changed(path, extensions):
return False
FILENAMES_MTIMES = defaultdict(int)
def file_changed(filename):
mtime = os.stat(filename).st_mtime
if FILENAMES_MTIMES[filename] == 0:
FILENAMES_MTIMES[filename] = mtime
return False
else:
if mtime > FILENAMES_MTIMES[filename]:
FILENAMES_MTIMES[filename] = mtime
return True
return False
def set_date_tzinfo(d, tz_name=None):
""" Date without tzinfo shoudbe utc.
This function set the right tz to date that aren't utc and don't have

View file

@ -0,0 +1,9 @@
This is a test hidden page
##########################
:category: test
:status: hidden
This is great for things like error(404) pages
Anyone can see this page but it's not linked to anywhere!

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python
from setuptools import setup
requires = ['feedgenerator', 'jinja2', 'pygments', 'docutils', 'pytz']
requires = ['feedgenerator', 'jinja2 >= 2.4', 'pygments', 'docutils', 'pytz', 'blinker']
try:
import argparse
@ -25,7 +25,7 @@ setup(
author_email = 'alexis@notmyidea.org',
description = "A tool to generate a static blog from reStructuredText or Markdown input files.",
long_description=open('README.rst').read(),
packages = ['pelican', 'pelican.tools'],
packages = ['pelican', 'pelican.tools', 'pelican.plugins'],
include_package_data = True,
install_requires = requires,
entry_points = entry_points,

View file

@ -0,0 +1,8 @@
This is a test bad page
#######################
:status: invalid
The quick brown fox jumped over the lazy dog's back.
The status here is invalid, the page should not render.

View file

@ -0,0 +1,8 @@
This is a test hidden page
##########################
:status: hidden
The quick brown fox jumped over the lazy dog's back.
This page is hidden

View file

@ -0,0 +1,12 @@
title: This is a markdown test hidden page
status: hidden
Test Markdown File Header
=========================
Used for pelican test
---------------------
The quick brown fox jumped over the lazy dog's back.
This page is hidden

4
tests/TestPages/page.rst Normal file
View file

@ -0,0 +1,4 @@
This is a test page
###################
The quick brown fox jumped over the lazy dog's back.

View file

@ -0,0 +1,9 @@
title: This is a markdown test page
Test Markdown File Header
=========================
Used for pelican test
---------------------
The quick brown fox jumped over the lazy dog's back.

View file

@ -0,0 +1,578 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!-- This is a WordPress eXtended RSS file generated by WordPress as an export of your site. -->
<!-- It contains information about your site's posts, pages, comments, categories, and other content. -->
<!-- You may use this file to transfer that content from one site to another. -->
<!-- This file is not intended to serve as a complete backup of your site. -->
<!-- To import this information into a WordPress site follow these steps: -->
<!-- 1. Log in to that site as an administrator. -->
<!-- 2. Go to Tools: Import in the WordPress admin panel. -->
<!-- 3. Install the "WordPress" importer from the list. -->
<!-- 4. Activate & Run Importer. -->
<!-- 5. Upload this file using the form provided on that page. -->
<!-- 6. You will first be asked to map the authors in this export file to users -->
<!-- on the site. For each author, you may choose to map to an -->
<!-- existing user on the site or to create a new user. -->
<!-- 7. WordPress will then import each of the posts, pages, comments, categories, etc. -->
<!-- contained in this file into your site. -->
<!-- generator="WordPress/3.3.1" created="2012-05-13 01:13" -->
<rss version="2.0"
xmlns:excerpt="http://wordpress.org/export/1.1/excerpt/"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:wp="http://wordpress.org/export/1.1/"
>
<channel>
<title>Pelican test channel</title>
<link>http://thisisa.test</link>
<description>Not a real feed, just for test</description>
<pubDate>Sun, 13 May 2012 01:13:52 +0000</pubDate>
<language>en</language>
<wp:wxr_version>1.1</wp:wxr_version>
<wp:base_site_url>http://thisisa.test</wp:base_site_url>
<wp:base_blog_url>http://thisisa.test</wp:base_blog_url>
<wp:author><wp:author_id>2</wp:author_id><wp:author_login>Bob</wp:author_login><wp:author_email>bob@thisisa.test</wp:author_email><wp:author_display_name><![CDATA[Bob]]></wp:author_display_name><wp:author_first_name><![CDATA[Bob]]></wp:author_first_name><wp:author_last_name><![CDATA[]]></wp:author_last_name></wp:author>
<wp:author><wp:author_id>3</wp:author_id><wp:author_login>Jonh</wp:author_login><wp:author_email>jonh@thisisa.test</wp:author_email><wp:author_display_name><![CDATA[Jonh]]></wp:author_display_name><wp:author_first_name><![CDATA[Jonh]]></wp:author_first_name><wp:author_last_name><![CDATA[]]></wp:author_last_name></wp:author>
<wp:category><wp:term_id>7</wp:term_id><wp:category_nicename>categ-1</wp:category_nicename><wp:category_parent></wp:category_parent><wp:cat_name><![CDATA[Category 1]]></wp:cat_name></wp:category>
<wp:category><wp:term_id>11</wp:term_id><wp:category_nicename>categ-2</wp:category_nicename><wp:category_parent></wp:category_parent><wp:cat_name><![CDATA[Category 2]]></wp:cat_name></wp:category>
<wp:category><wp:term_id>1</wp:term_id><wp:category_nicename>uncategorized</wp:category_nicename><wp:category_parent></wp:category_parent><wp:cat_name><![CDATA[Uncategorized]]></wp:cat_name></wp:category>
<wp:category><wp:term_id>15</wp:term_id><wp:category_nicename>categ-3</wp:category_nicename><wp:category_parent></wp:category_parent><wp:cat_name><![CDATA[Category 3]]></wp:cat_name></wp:category>
<wp:tag><wp:term_id>25</wp:term_id><wp:tag_slug>tag-1</wp:tag_slug><wp:tag_name><![CDATA[tag 1]]></wp:tag_name></wp:tag>
<wp:tag><wp:term_id>122</wp:term_id><wp:tag_slug>tag2</wp:tag_slug><wp:tag_name><![CDATA[Tag2]]></wp:tag_name></wp:tag>
<wp:tag><wp:term_id>68</wp:term_id><wp:tag_slug>tag-3</wp:tag_slug><wp:tag_name><![CDATA[Tag 3]]></wp:tag_name></wp:tag>
<generator>http://wordpress.org/?v=3.3.1</generator>
<item>
<title>Empty post</title>
<link>http://thisisa.test/?attachment_id=24</link>
<pubDate>Sat, 04 Feb 2012 03:17:33 +0000</pubDate>
<dc:creator>bob</dc:creator>
<guid isPermaLink="false">https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/Pelican_lakes_entrance02.jpg/240px-Pelican_lakes_entrance02.jpg</guid>
<description></description>
<content:encoded><![CDATA[]]></content:encoded>
<excerpt:encoded><![CDATA[]]></excerpt:encoded>
<wp:post_id>24</wp:post_id>
<wp:post_date>2012-02-04 03:17:33</wp:post_date>
<wp:post_date_gmt>2012-02-04 03:17:33</wp:post_date_gmt>
<wp:comment_status>open</wp:comment_status>
<wp:ping_status>open</wp:ping_status>
<wp:post_name>empty-post</wp:post_name>
<wp:status>inherit</wp:status>
<wp:post_parent>0</wp:post_parent>
<wp:menu_order>0</wp:menu_order>
<wp:post_type>attachment</wp:post_type>
<wp:post_password></wp:post_password>
<wp:is_sticky>0</wp:is_sticky>
<wp:attachment_url>https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/Pelican_lakes_entrance02.jpg/240px-Pelican_lakes_entrance02.jpg</wp:attachment_url>
<wp:postmeta>
<wp:meta_key>_wp_attachment_metadata</wp:meta_key>
<wp:meta_value><![CDATA[a:5:{s:5:"width";s:3:"150";s:6:"height";s:3:"186";s:14:"hwstring_small";s:22:"height='96' width='77'";s:4:"file";s:20:"2012/02/pelican.png";s:10:"image_meta";a:10:{s:8:"aperture";s:1:"0";s:6:"credit";s:0:"";s:6:"camera";s:0:"";s:7:"caption";s:0:"";s:17:"created_timestamp";s:1:"0";s:9:"copyright";s:0:"";s:12:"focal_length";s:1:"0";s:3:"iso";s:1:"0";s:13:"shutter_speed";s:1:"0";s:5:"title";s:0:"";}}]]></wp:meta_value>
</wp:postmeta>
<wp:postmeta>
<wp:meta_key>_wp_attached_file</wp:meta_key>
<wp:meta_value><![CDATA[2012/02/stuff.png]]></wp:meta_value>
</wp:postmeta>
<wp:postmeta>
<wp:meta_key>_wp_attachment_image_alt</wp:meta_key>
<wp:meta_value><![CDATA[Stuff]]></wp:meta_value>
</wp:postmeta>
</item>
<item>
<title></title>
<link>http://thisisa.test/?p=168</link>
<pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate>
<dc:creator>bob</dc:creator>
<guid isPermaLink="false">http://thisisa.test/?p=168</guid>
<description></description>
<content:encoded><![CDATA[This is a draft with no title]]></content:encoded>
<excerpt:encoded><![CDATA[]]></excerpt:encoded>
<wp:post_id>168</wp:post_id>
<wp:post_date>2012-02-15 21:23:57</wp:post_date>
<wp:post_date_gmt>0000-00-00 00:00:00</wp:post_date_gmt>
<wp:comment_status>open</wp:comment_status>
<wp:ping_status>open</wp:ping_status>
<wp:post_name></wp:post_name>
<wp:status>draft</wp:status>
<wp:post_parent>0</wp:post_parent>
<wp:menu_order>0</wp:menu_order>
<wp:post_type>post</wp:post_type>
<wp:post_password></wp:post_password>
<wp:is_sticky>0</wp:is_sticky>
<category domain="category" nicename="categ-1"><![CDATA[Category 1]]></category>
<wp:postmeta>
<wp:meta_key>_edit_last</wp:meta_key>
<wp:meta_value><![CDATA[3]]></wp:meta_value>
</wp:postmeta>
</item>
<item>
<title>A normal post</title>
<link>http://thisisa.test/?p=173</link>
<pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate>
<dc:creator>bob</dc:creator>
<guid isPermaLink="false">http://thisisa.test/?p=173</guid>
<description></description>
<content:encoded><![CDATA[Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
<ul>
<li>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</li>
<li>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</li>
</ul>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.]]></content:encoded>
<excerpt:encoded><![CDATA[]]></excerpt:encoded>
<wp:post_id>173</wp:post_id>
<wp:post_date>2012-02-16 15:52:55</wp:post_date>
<wp:post_date_gmt>0000-00-00 00:00:00</wp:post_date_gmt>
<wp:comment_status>open</wp:comment_status>
<wp:ping_status>open</wp:ping_status>
<wp:post_name></wp:post_name>
<wp:status>draft</wp:status>
<wp:post_parent>0</wp:post_parent>
<wp:menu_order>0</wp:menu_order>
<wp:post_type>post</wp:post_type>
<wp:post_password></wp:post_password>
<wp:is_sticky>0</wp:is_sticky>
<category domain="category" nicename="category-2"><![CDATA[Category 2]]></category>
<wp:postmeta>
<wp:meta_key>_edit_last</wp:meta_key>
<wp:meta_value><![CDATA[3]]></wp:meta_value>
</wp:postmeta>
</item>
<item>
<title>Complete draft</title>
<link>http://thisisa.test/?p=176</link>
<pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate>
<dc:creator>bob</dc:creator>
<guid isPermaLink="false">http://thisisa.test/?p=176</guid>
<description></description>
<content:encoded><![CDATA[Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.]]></content:encoded>
<excerpt:encoded><![CDATA[]]></excerpt:encoded>
<wp:post_id>176</wp:post_id>
<wp:post_date>2012-02-17 15:11:55</wp:post_date>
<wp:post_date_gmt>0000-00-00 00:00:00</wp:post_date_gmt>
<wp:comment_status>open</wp:comment_status>
<wp:ping_status>open</wp:ping_status>
<wp:post_name></wp:post_name>
<wp:status>draft</wp:status>
<wp:post_parent>0</wp:post_parent>
<wp:menu_order>0</wp:menu_order>
<wp:post_type>post</wp:post_type>
<wp:post_password></wp:post_password>
<wp:is_sticky>0</wp:is_sticky>
<category domain="category" nicename="category-3"><![CDATA[Category 3]]></category>
<wp:postmeta>
<wp:meta_key>_edit_last</wp:meta_key>
<wp:meta_value><![CDATA[3]]></wp:meta_value>
</wp:postmeta>
</item>
<item>
<title>Page</title>
<link>http://thisisa.test/contact/</link>
<pubDate>Wed, 11 Apr 2012 11:38:08 +0000</pubDate>
<dc:creator>bob</dc:creator>
<guid isPermaLink="false">http://thisisa.test/?page_id=334</guid>
<description></description>
<content:encoded><![CDATA[Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.]]></content:encoded>
<excerpt:encoded><![CDATA[]]></excerpt:encoded>
<wp:post_id>334</wp:post_id>
<wp:post_date>2012-04-11 06:38:08</wp:post_date>
<wp:post_date_gmt>2012-04-11 11:38:08</wp:post_date_gmt>
<wp:comment_status>open</wp:comment_status>
<wp:ping_status>open</wp:ping_status>
<wp:post_name>contact</wp:post_name>
<wp:status>publish</wp:status>
<wp:post_parent>0</wp:post_parent>
<wp:menu_order>0</wp:menu_order>
<wp:post_type>page</wp:post_type>
<wp:post_password></wp:post_password>
<wp:is_sticky>0</wp:is_sticky>
<wp:postmeta>
<wp:meta_key>sharing_disabled</wp:meta_key>
<wp:meta_value><![CDATA[1]]></wp:meta_value>
</wp:postmeta>
<wp:postmeta>
<wp:meta_key>_wp_page_template</wp:meta_key>
<wp:meta_value><![CDATA[default]]></wp:meta_value>
</wp:postmeta>
<wp:postmeta>
<wp:meta_key>_edit_last</wp:meta_key>
<wp:meta_value><![CDATA[3]]></wp:meta_value>
</wp:postmeta>
</item>
<item>
<title>Empty Page</title>
<link>http://thisisa.test/empty/</link>
<pubDate>Wed, 11 Apr 2012 11:38:08 +0000</pubDate>
<dc:creator>bob</dc:creator>
<guid isPermaLink="false">http://thisisa.test/?page_id=334</guid>
<description></description>
<content:encoded><![CDATA[]]></content:encoded>
<excerpt:encoded><![CDATA[]]></excerpt:encoded>
<wp:post_id>334</wp:post_id>
<wp:post_date>2012-04-11 06:38:08</wp:post_date>
<wp:post_date_gmt>2012-04-11 11:38:08</wp:post_date_gmt>
<wp:comment_status>open</wp:comment_status>
<wp:ping_status>open</wp:ping_status>
<wp:post_name>empty</wp:post_name>
<wp:status>publish</wp:status>
<wp:post_parent>0</wp:post_parent>
<wp:menu_order>0</wp:menu_order>
<wp:post_type>page</wp:post_type>
<wp:post_password></wp:post_password>
<wp:is_sticky>0</wp:is_sticky>
<wp:postmeta>
<wp:meta_key>sharing_disabled</wp:meta_key>
<wp:meta_value><![CDATA[1]]></wp:meta_value>
</wp:postmeta>
<wp:postmeta>
<wp:meta_key>_wp_page_template</wp:meta_key>
<wp:meta_value><![CDATA[default]]></wp:meta_value>
</wp:postmeta>
<wp:postmeta>
<wp:meta_key>_edit_last</wp:meta_key>
<wp:meta_value><![CDATA[3]]></wp:meta_value>
</wp:postmeta>
</item>
<item>
<title>Special chars: l&#039;é</title>
<link>http://thisisa.test/?p=471</link>
<pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate>
<dc:creator>bob</dc:creator>
<guid isPermaLink="false">http://thisisa.test/?p=471</guid>
<description></description>
<content:encoded><![CDATA[l&#039;é]]></content:encoded>
<excerpt:encoded><![CDATA[]]></excerpt:encoded>
<wp:post_id>471</wp:post_id>
<wp:post_date>2012-04-29 09:44:27</wp:post_date>
<wp:post_date_gmt>0000-00-00 00:00:00</wp:post_date_gmt>
<wp:comment_status>open</wp:comment_status>
<wp:ping_status>open</wp:ping_status>
<wp:post_name></wp:post_name>
<wp:status>draft</wp:status>
<wp:post_parent>0</wp:post_parent>
<wp:menu_order>0</wp:menu_order>
<wp:post_type>post</wp:post_type>
<wp:post_password></wp:post_password>
<wp:is_sticky>0</wp:is_sticky>
<category domain="category" nicename="category-1"><![CDATA[Category 1]]></category>
<wp:postmeta>
<wp:meta_key>_edit_last</wp:meta_key>
<wp:meta_value><![CDATA[3]]></wp:meta_value>
</wp:postmeta>
</item>
<item>
<title>With excerpt</title>
<link>http://thisisa.test/with-excerpt/</link>
<pubDate>Sat, 04 Feb 2012 02:03:06 +0000</pubDate>
<dc:creator>bob</dc:creator>
<guid isPermaLink="false">http://thisisa.test/?p=8</guid>
<description></description>
<content:encoded><![CDATA[Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.]]></content:encoded>
<excerpt:encoded><![CDATA[Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.]]></excerpt:encoded>
<wp:post_id>8</wp:post_id>
<wp:post_date>2012-02-04 02:03:06</wp:post_date>
<wp:post_date_gmt>2012-02-04 02:03:06</wp:post_date_gmt>
<wp:comment_status>open</wp:comment_status>
<wp:ping_status>open</wp:ping_status>
<wp:post_name>with-excerpt</wp:post_name>
<wp:status>publish</wp:status>
<wp:post_parent>0</wp:post_parent>
<wp:menu_order>0</wp:menu_order>
<wp:post_type>post</wp:post_type>
<wp:post_password></wp:post_password>
<wp:is_sticky>0</wp:is_sticky>
<category domain="category" nicename="Category 1"><![CDATA[Category 1]]></category>
<category domain="post_tag" nicename="tag-1"><![CDATA[tag 1]]></category>
<category domain="post_tag" nicename="tag2"><![CDATA[Tag2]]></category>
<wp:postmeta>
<wp:meta_key>_edit_last</wp:meta_key>
<wp:meta_value><![CDATA[3]]></wp:meta_value>
</wp:postmeta>
<wp:postmeta>
<wp:meta_key>et_bigpost</wp:meta_key>
<wp:meta_value><![CDATA[0]]></wp:meta_value>
</wp:postmeta>
<wp:postmeta>
<wp:meta_key>_thumbnail_id</wp:meta_key>
<wp:meta_value><![CDATA[32]]></wp:meta_value>
</wp:postmeta>
</item>
<item>
<title>With tags</title>
<link>http://thisisa.test/tags/</link>
<pubDate>Sat, 04 Feb 2012 21:05:25 +0000</pubDate>
<dc:creator>bob</dc:creator>
<guid isPermaLink="false">http://thisisa.test/?p=25</guid>
<description></description>
<content:encoded><![CDATA[Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.]]></content:encoded>
<excerpt:encoded><![CDATA[]]></excerpt:encoded>
<wp:post_id>25</wp:post_id>
<wp:post_date>2012-02-04 21:05:25</wp:post_date>
<wp:post_date_gmt>2012-02-04 21:05:25</wp:post_date_gmt>
<wp:comment_status>open</wp:comment_status>
<wp:ping_status>open</wp:ping_status>
<wp:post_name>with-tags</wp:post_name>
<wp:status>publish</wp:status>
<wp:post_parent>0</wp:post_parent>
<wp:menu_order>0</wp:menu_order>
<wp:post_type>post</wp:post_type>
<wp:post_password></wp:post_password>
<wp:is_sticky>0</wp:is_sticky>
<category domain="category" nicename="category-3"><![CDATA[Category 3]]></category>
<category domain="post_tag" nicename="tag-1"><![CDATA[tag 1]]></category>
<category domain="post_tag" nicename="tag-2"><![CDATA[Tag2]]></category>
<category domain="post_tag" nicename="tag-3"><![CDATA[Tag 3]]></category>
<wp:postmeta>
<wp:meta_key>_edit_last</wp:meta_key>
<wp:meta_value><![CDATA[3]]></wp:meta_value>
</wp:postmeta>
<wp:postmeta>
<wp:meta_key>et_bigpost</wp:meta_key>
<wp:meta_value><![CDATA[0]]></wp:meta_value>
</wp:postmeta>
<wp:postmeta>
<wp:meta_key>_thumbnail_id</wp:meta_key>
<wp:meta_value><![CDATA[29]]></wp:meta_value>
</wp:postmeta>
</item>
<item>
<title>With comments</title>
<link>http://thisisa.test/with-comments/</link>
<pubDate>Wed, 18 Apr 2012 08:36:26 +0000</pubDate>
<dc:creator>john</dc:creator>
<guid isPermaLink="false">http://thisisa.test/?p=422</guid>
<description></description>
<content:encoded><![CDATA[Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.]]></content:encoded>
<excerpt:encoded><![CDATA[]]></excerpt:encoded>
<wp:post_id>422</wp:post_id>
<wp:post_date>2012-04-18 03:36:26</wp:post_date>
<wp:post_date_gmt>2012-04-18 08:36:26</wp:post_date_gmt>
<wp:comment_status>open</wp:comment_status>
<wp:ping_status>open</wp:ping_status>
<wp:post_name>with-comments</wp:post_name>
<wp:status>publish</wp:status>
<wp:post_parent>0</wp:post_parent>
<wp:menu_order>0</wp:menu_order>
<wp:post_type>post</wp:post_type>
<wp:post_password></wp:post_password>
<wp:is_sticky>0</wp:is_sticky>
<category domain="category" nicename="category-1"><![CDATA[Category 1]]></category>
<wp:postmeta>
<wp:meta_key>_edit_last</wp:meta_key>
<wp:meta_value><![CDATA[2]]></wp:meta_value>
</wp:postmeta>
<wp:postmeta>
<wp:meta_key>_thumbnail_id</wp:meta_key>
<wp:meta_value><![CDATA[423]]></wp:meta_value>
</wp:postmeta>
<wp:comment>
<wp:comment_id>116</wp:comment_id>
<wp:comment_author><![CDATA[User2]]></wp:comment_author>
<wp:comment_author_email>User2@mail.test</wp:comment_author_email>
<wp:comment_author_url></wp:comment_author_url>
<wp:comment_author_IP>127.0.0.1</wp:comment_author_IP>
<wp:comment_date>2012-05-06 15:46:06</wp:comment_date>
<wp:comment_date_gmt>2012-05-06 20:46:06</wp:comment_date_gmt>
<wp:comment_content><![CDATA[Comment content.]]></wp:comment_content>
<wp:comment_approved>1</wp:comment_approved>
<wp:comment_type></wp:comment_type>
<wp:comment_parent>0</wp:comment_parent>
<wp:comment_user_id>0</wp:comment_user_id>
<wp:commentmeta>
<wp:meta_key>akismet_result</wp:meta_key>
<wp:meta_value><![CDATA[false]]></wp:meta_value>
</wp:commentmeta>
<wp:commentmeta>
<wp:meta_key>akismet_history</wp:meta_key>
<wp:meta_value><![CDATA[a:4:{s:4:"time";s:15:"1336337189.7974";s:7:"message";s:28:"Akismet cleared this comment";s:5:"event";s:9:"check-ham";s:4:"user";s:0:"";}]]></wp:meta_value>
</wp:commentmeta>
<wp:commentmeta>
<wp:meta_key>akismet_as_submitted</wp:meta_key>
<wp:meta_value><![CDATA[a:64:{s:15:"comment_post_ID";s:3:"422";s:14:"comment_author";s:8:"Baronsed";s:20:"comment_author_email";s:15:"User2@mail.test";s:18:"comment_author_url";s:0:"";s:15:"comment_content";s:118:"Dans les listes d'articles où celui-ci apparaît, l'image est trop grande et ralentit le chargement de toute la page.";s:12:"comment_type";s:0:"";s:14:"comment_parent";s:1:"0";s:7:"user_ID";s:1:"0";s:7:"user_ip";s:14:"127.0.0.1";s:10:"user_agent";s:74:"Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:12.0) Gecko/20100101 Firefox/12.0";s:8:"referrer";s:0:"";s:4:"blog";s:19:"http://thisisa.test";s:9:"blog_lang";s:5:"en_US";s:12:"blog_charset";s:5:"UTF-8";s:9:"permalink";s:62:"http://thisisa.test/htop-un-visualiseur-interactif-de-process/";s:9:"user_role";s:0:"";s:21:"akismet_comment_nonce";s:6:"passed";s:11:"POST_author";s:8:"Baronsed";s:10:"POST_email";s:15:"User2@mail.test";s:8:"POST_url";s:0:"";s:12:"POST_comment";s:118:"Dans les listes d'articles où celui-ci apparaît, l'image est trop grande et ralentit le chargement de toute la page.";s:11:"POST_submit";s:14:"Submit Comment";s:20:"POST_comment_post_ID";s:3:"422";s:19:"POST_comment_parent";s:1:"0";s:26:"POST_akismet_comment_nonce";s:10:"96b66769dc";s:15:"SERVER_SOFTWARE";s:12:"nginx/0.8.55";s:11:"REQUEST_URI";s:21:"/wp-comments-post.php";s:4:"TERM";s:5:"xterm";s:17:"PHP_FCGI_CHILDREN";s:1:"4";s:4:"PATH";s:29:"/sbin:/usr/sbin:/bin:/usr/bin";s:1:"_";s:25:"/usr/local/bin/spawn-fcgi";s:3:"PWD";s:1:"/";s:4:"LANG";s:11:"en_US.UTF-8";s:5:"SHLVL";s:1:"2";s:21:"PHP_FCGI_MAX_REQUESTS";s:4:"1000";s:9:"FCGI_ROLE";s:9:"RESPONDER";s:12:"QUERY_STRING";s:0:"";s:14:"REQUEST_METHOD";s:4:"POST";s:12:"CONTENT_TYPE";s:33:"application/x-www-form-urlencoded";s:14:"CONTENT_LENGTH";s:3:"277";s:11:"SCRIPT_NAME";s:21:"/wp-comments-post.php";s:12:"DOCUMENT_URI";s:21:"/wp-comments-post.php";s:13:"DOCUMENT_ROOT";s:14:"/home/blog";s:15:"SERVER_PROTOCOL";s:8:"HTTP/1.1";s:17:"GATEWAY_INTERFACE";s:7:"CGI/1.1";s:11:"REMOTE_ADDR";s:14:"127.0.0.1";s:11:"REMOTE_PORT";s:5:"52826";s:11:"SERVER_ADDR";s:13:"127.0.0.1";s:11:"SERVER_PORT";s:2:"80";s:11:"SERVER_NAME";s:12:"thisisa.test";s:15:"REDIRECT_STATUS";s:3:"200";s:15:"SCRIPT_FILENAME";s:35:"/home/blog/wp-comments-post.php";s:9:"HTTP_HOST";s:12:"thisisa.test";s:15:"HTTP_USER_AGENT";s:74:"Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:12.0) Gecko/20100101 Firefox/12.0";s:11:"HTTP_ACCEPT";s:63:"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";s:20:"HTTP_ACCEPT_LANGUAGE";s:35:"fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3";s:20:"HTTP_ACCEPT_ENCODING";s:13:"gzip, deflate";s:8:"HTTP_DNT";s:1:"1";s:15:"HTTP_CONNECTION";s:10:"keep-alive";s:11:"HTTP_COOKIE";s:0:"";s:17:"HTTP_CONTENT_TYPE";s:33:"application/x-www-form-urlencoded";s:19:"HTTP_CONTENT_LENGTH";s:3:"277";s:8:"PHP_SELF";s:21:"/wp-comments-post.php";s:12:"REQUEST_TIME";s:10:"1336337164";}]]></wp:meta_value>
</wp:commentmeta>
</wp:comment>
<wp:comment>
<wp:comment_id>117</wp:comment_id>
<wp:comment_author><![CDATA[Bob]]></wp:comment_author>
<wp:comment_author_email>bob@thisisa.test</wp:comment_author_email>
<wp:comment_author_url></wp:comment_author_url>
<wp:comment_author_IP>127.0.0.1</wp:comment_author_IP>
<wp:comment_date>2012-05-06 17:44:06</wp:comment_date>
<wp:comment_date_gmt>2012-05-06 22:44:06</wp:comment_date_gmt>
<wp:comment_content><![CDATA[Comment content.]]></wp:comment_content>
<wp:comment_approved>1</wp:comment_approved>
<wp:comment_type></wp:comment_type>
<wp:comment_parent>116</wp:comment_parent>
<wp:comment_user_id>3</wp:comment_user_id>
<wp:commentmeta>
<wp:meta_key>akismet_result</wp:meta_key>
<wp:meta_value><![CDATA[false]]></wp:meta_value>
</wp:commentmeta>
<wp:commentmeta>
<wp:meta_key>akismet_history</wp:meta_key>
<wp:meta_value><![CDATA[a:4:{s:4:"time";s:15:"1336344263.8658";s:7:"message";s:28:"Akismet cleared this comment";s:5:"event";s:9:"check-ham";s:4:"user";s:3:"bob";}]]></wp:meta_value>
</wp:commentmeta>
<wp:commentmeta>
<wp:meta_key>akismet_as_submitted</wp:meta_key>
<wp:meta_value><![CDATA[a:74:{s:15:"comment_post_ID";s:3:"422";s:14:"comment_author";s:3:"bob";s:20:"comment_author_email";s:25:"bob@thisisa.test";s:18:"comment_author_url";s:0:"";s:15:"comment_content";s:26:"Merci de l'avoir signalé.";s:14:"comment_parent";s:3:"116";s:7:"user_ID";s:1:"3";s:7:"user_ip";s:14:"127.0.0.1";s:10:"user_agent";s:139:"Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.19 (KHTML, like Gecko) Ubuntu/12.04 Chromium/127.0.0.1 Chrome/127.0.0.1 Safari/535.19";s:8:"referrer";s:29:"http://thisisa.test/wp-admin/";s:4:"blog";s:19:"http://thisisa.test";s:9:"blog_lang";s:5:"en_US";s:12:"blog_charset";s:5:"UTF-8";s:9:"permalink";s:62:"http://thisisa.test/htop-un-visualiseur-interactif-de-process/";s:9:"user_role";s:13:"administrator";s:21:"akismet_comment_nonce";s:6:"passed";s:22:"POST_newcomment_author";s:0:"";s:28:"POST_newcomment_author_email";s:0:"";s:26:"POST_newcomment_author_url";s:0:"";s:12:"POST_user_ID";s:1:"3";s:11:"POST_action";s:15:"replyto-comment";s:15:"POST_comment_ID";s:3:"116";s:20:"POST_comment_post_ID";s:3:"422";s:11:"POST_status";s:0:"";s:13:"POST_position";s:2:"-1";s:13:"POST_checkbox";s:1:"0";s:9:"POST_mode";s:9:"dashboard";s:32:"POST__ajax_nonce-replyto-comment";s:10:"d1ad3bd917";s:32:"POST__wp_unfiltered_html_comment";s:10:"fc11aee860";s:12:"POST_content";s:26:"Merci de l'avoir signalé.";s:7:"POST_id";s:3:"422";s:21:"POST_comments_listing";s:0:"";s:15:"SERVER_SOFTWARE";s:12:"nginx/0.8.55";s:11:"REQUEST_URI";s:24:"/wp-admin/admin-ajax.php";s:4:"TERM";s:5:"xterm";s:17:"PHP_FCGI_CHILDREN";s:1:"4";s:4:"PATH";s:29:"/sbin:/usr/sbin:/bin:/usr/bin";s:1:"_";s:25:"/usr/local/bin/spawn-fcgi";s:3:"PWD";s:1:"/";s:4:"LANG";s:11:"en_US.UTF-8";s:5:"SHLVL";s:1:"2";s:21:"PHP_FCGI_MAX_REQUESTS";s:4:"1000";s:9:"FCGI_ROLE";s:9:"RESPONDER";s:12:"QUERY_STRING";s:0:"";s:14:"REQUEST_METHOD";s:4:"POST";s:12:"CONTENT_TYPE";s:33:"application/x-www-form-urlencoded";s:14:"CONTENT_LENGTH";s:3:"322";s:11:"SCRIPT_NAME";s:24:"/wp-admin/admin-ajax.php";s:12:"DOCUMENT_URI";s:24:"/wp-admin/admin-ajax.php";s:13:"DOCUMENT_ROOT";s:14:"/home/blog";s:15:"SERVER_PROTOCOL";s:8:"HTTP/1.1";s:17:"GATEWAY_INTERFACE";s:7:"CGI/1.1";s:11:"REMOTE_ADDR";s:14:"127.0.0.1";s:11:"REMOTE_PORT";s:5:"10252";s:11:"SERVER_ADDR";s:13:"127.0.0.1";s:11:"SERVER_PORT";s:2:"80";s:11:"SERVER_NAME";s:12:"thisisa.test";s:15:"REDIRECT_STATUS";s:3:"200";s:15:"SCRIPT_FILENAME";s:38:"/home/blog/wp-admin/admin-ajax.php";s:9:"HTTP_HOST";s:12:"thisisa.test";s:15:"HTTP_CONNECTION";s:10:"keep-alive";s:19:"HTTP_CONTENT_LENGTH";s:3:"322";s:11:"HTTP_ORIGIN";s:19:"http://thisisa.test";s:21:"HTTP_X_REQUESTED_WITH";s:14:"XMLHttpRequest";s:15:"HTTP_USER_AGENT";s:139:"Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.19 (KHTML, like Gecko) Ubuntu/12.04 Chromium/127.0.0.1 Chrome/127.0.0.1 Safari/535.19";s:17:"HTTP_CONTENT_TYPE";s:33:"application/x-www-form-urlencoded";s:11:"HTTP_ACCEPT";s:3:"*/*";s:12:"HTTP_REFERER";s:29:"http://thisisa.test/wp-admin/";s:20:"HTTP_ACCEPT_ENCODING";s:17:"gzip,deflate,sdch";s:20:"HTTP_ACCEPT_LANGUAGE";s:35:"fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4";s:19:"HTTP_ACCEPT_CHARSET";s:30:"ISO-8859-1,utf-8;q=0.7,*;q=0.3";s:11:"HTTP_COOKIE";s:0:"";s:8:"PHP_SELF";s:24:"/wp-admin/admin-ajax.php";s:12:"REQUEST_TIME";s:10:"1336344246";}]]></wp:meta_value>
</wp:commentmeta>
</wp:comment>
<wp:comment>
<wp:comment_id>156</wp:comment_id>
<wp:comment_author><![CDATA[Ping back comment author]]></wp:comment_author>
<wp:comment_author_email></wp:comment_author_email>
<wp:comment_author_url>http://thisisa.test/to-article-you-ping-back/</wp:comment_author_url>
<wp:comment_author_IP>127.0.0.1</wp:comment_author_IP>
<wp:comment_date>2012-05-09 19:30:19</wp:comment_date>
<wp:comment_date_gmt>2012-05-10 00:30:19</wp:comment_date_gmt>
<wp:comment_content><![CDATA[[...]this is a ping pack [...] ]]></wp:comment_content>
<wp:comment_approved>trash</wp:comment_approved>
<wp:comment_type>pingback</wp:comment_type>
<wp:comment_parent>0</wp:comment_parent>
<wp:comment_user_id>0</wp:comment_user_id>
<wp:commentmeta>
<wp:meta_key>akismet_history</wp:meta_key>
<wp:meta_value><![CDATA[a:4:{s:4:"time";s:15:"1336610793.7343";s:7:"message";s:39:"bob changed the comment status to trash";s:5:"event";s:12:"status-trash";s:4:"user";s:3:"bob";}]]></wp:meta_value>
</wp:commentmeta>
<wp:commentmeta>
<wp:meta_key>_wp_trash_meta_status</wp:meta_key>
<wp:meta_value><![CDATA[0]]></wp:meta_value>
</wp:commentmeta>
<wp:commentmeta>
<wp:meta_key>_wp_trash_meta_time</wp:meta_key>
<wp:meta_value><![CDATA[1336610793]]></wp:meta_value>
</wp:commentmeta>
</wp:comment>
<wp:comment>
<wp:comment_id>122</wp:comment_id>
<wp:comment_author><![CDATA[bob]]></wp:comment_author>
<wp:comment_author_email>bob@thisisa.test</wp:comment_author_email>
<wp:comment_author_url></wp:comment_author_url>
<wp:comment_author_IP>127.0.0.1</wp:comment_author_IP>
<wp:comment_date>2012-05-07 14:11:34</wp:comment_date>
<wp:comment_date_gmt>2012-05-07 19:11:34</wp:comment_date_gmt>
<wp:comment_content><![CDATA[Comment content.]]></wp:comment_content>
<wp:comment_approved>1</wp:comment_approved>
<wp:comment_type></wp:comment_type>
<wp:comment_parent>121</wp:comment_parent>
<wp:comment_user_id>3</wp:comment_user_id>
<wp:commentmeta>
<wp:meta_key>akismet_result</wp:meta_key>
<wp:meta_value><![CDATA[false]]></wp:meta_value>
</wp:commentmeta>
<wp:commentmeta>
<wp:meta_key>akismet_history</wp:meta_key>
<wp:meta_value><![CDATA[a:4:{s:4:"time";s:15:"1336417895.1821";s:7:"message";s:28:"Akismet cleared this comment";s:5:"event";s:9:"check-ham";s:4:"user";s:3:"bob";}]]></wp:meta_value>
</wp:commentmeta>
<wp:commentmeta>
<wp:meta_key>akismet_as_submitted</wp:meta_key>
<wp:meta_value><![CDATA[a:65:{s:15:"comment_post_ID";s:3:"422";s:14:"comment_author";s:3:"bob";s:20:"comment_author_email";s:25:"bob@thisisa.test";s:18:"comment_author_url";s:0:"";s:15:"comment_content";s:832:"Comment content";s:12:"comment_type";s:0:"";s:14:"comment_parent";s:3:"121";s:7:"user_ID";s:1:"3";s:7:"user_ip";s:14:"127.0.0.1";s:10:"user_agent";s:139:"Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.19 (KHTML, like Gecko) Ubuntu/12.04 Chromium/127.0.0.1 Chrome/127.0.0.1 Safari/535.19";s:8:"referrer";s:62:"http://thisisa.test/htop-un-visualiseur-interactif-de-process/";s:4:"blog";s:19:"http://thisisa.test";s:9:"blog_lang";s:5:"en_US";s:12:"blog_charset";s:5:"UTF-8";s:9:"permalink";s:62:"http://thisisa.test/htop-un-visualiseur-interactif-de-process/";s:9:"user_role";s:13:"administrator";s:21:"akismet_comment_nonce";s:6:"passed";s:12:"POST_comment";s:832:"Comment content.";s:11:"POST_submit";s:14:"Submit Comment";s:20:"POST_comment_post_ID";s:3:"422";s:19:"POST_comment_parent";s:3:"121";s:32:"POST__wp_unfiltered_html_comment";s:10:"9dacc22ee8";s:26:"POST_akismet_comment_nonce";s:10:"b9cbdae553";s:15:"SERVER_SOFTWARE";s:12:"nginx/0.8.55";s:11:"REQUEST_URI";s:21:"/wp-comments-post.php";s:4:"TERM";s:5:"xterm";s:17:"PHP_FCGI_CHILDREN";s:1:"4";s:4:"PATH";s:29:"/sbin:/usr/sbin:/bin:/usr/bin";s:1:"_";s:25:"/usr/local/bin/spawn-fcgi";s:3:"PWD";s:1:"/";s:4:"LANG";s:11:"en_US.UTF-8";s:5:"SHLVL";s:1:"2";s:21:"PHP_FCGI_MAX_REQUESTS";s:4:"1000";s:9:"FCGI_ROLE";s:9:"RESPONDER";s:12:"QUERY_STRING";s:0:"";s:14:"REQUEST_METHOD";s:4:"POST";s:12:"CONTENT_TYPE";s:33:"application/x-www-form-urlencoded";s:14:"CONTENT_LENGTH";s:4:"1143";s:11:"SCRIPT_NAME";s:21:"/wp-comments-post.php";s:12:"DOCUMENT_URI";s:21:"/wp-comments-post.php";s:13:"DOCUMENT_ROOT";s:14:"/home/blog";s:15:"SERVER_PROTOCOL";s:8:"HTTP/1.1";s:17:"GATEWAY_INTERFACE";s:7:"CGI/1.1";s:11:"REMOTE_ADDR";s:14:"127.0.0.1";s:11:"REMOTE_PORT";s:5:"10834";s:11:"SERVER_ADDR";s:13:"127.0.0.1";s:11:"SERVER_PORT";s:2:"80";s:11:"SERVER_NAME";s:12:"thisisa.test";s:15:"REDIRECT_STATUS";s:3:"200";s:15:"SCRIPT_FILENAME";s:35:"/home/blog/wp-comments-post.php";s:9:"HTTP_HOST";s:12:"thisisa.test";s:15:"HTTP_CONNECTION";s:10:"keep-alive";s:19:"HTTP_CONTENT_LENGTH";s:4:"1143";s:18:"HTTP_CACHE_CONTROL";s:9:"john-age=0";s:11:"HTTP_ORIGIN";s:19:"http://thisisa.test";s:15:"HTTP_USER_AGENT";s:139:"Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.19 (KHTML, like Gecko) Ubuntu/12.04 Chromium/127.0.0.1 Chrome/127.0.0.1 Safari/535.19";s:17:"HTTP_CONTENT_TYPE";s:33:"application/x-www-form-urlencoded";s:11:"HTTP_ACCEPT";s:63:"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";s:12:"HTTP_REFERER";s:62:"http://thisisa.test/htop-un-visualiseur-interactif-de-process/";s:20:"HTTP_ACCEPT_ENCODING";s:17:"gzip,deflate,sdch";s:20:"HTTP_ACCEPT_LANGUAGE";s:35:"fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4";s:19:"HTTP_ACCEPT_CHARSET";s:30:"ISO-8859-1,utf-8;q=0.7,*;q=0.3";s:11:"HTTP_COOKIE";s:0:"";s:8:"PHP_SELF";s:21:"/wp-comments-post.php";s:12:"REQUEST_TIME";s:10:"1336417893";}]]></wp:meta_value>
</wp:commentmeta>
</wp:comment>
</item>
<item>
<title>Post with raw data</title>
<link>http://thisisa.test/?p=173</link>
<pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate>
<dc:creator>bob</dc:creator>
<guid isPermaLink="false">http://thisisa.test/?p=173</guid>
<description></description>
<content:encoded><![CDATA[<h1>Pelicans are scary</h1>
Pelicans are supposed to eat fish, damn it!
<iframe width="420" height="315" src="http://www.youtube.com/embed/QNNl_uWmQXE" frameborder="0" allowfullscreen></iframe>
Bottom line: don't mess up with birds]]></content:encoded>
<excerpt:encoded><![CDATA[]]></excerpt:encoded>
<wp:post_id>173</wp:post_id>
<wp:post_date>2012-02-16 15:52:55</wp:post_date>
<wp:post_date_gmt>0000-00-00 00:00:00</wp:post_date_gmt>
<wp:comment_status>open</wp:comment_status>
<wp:ping_status>open</wp:ping_status>
<wp:post_name>post-with-raw-data</wp:post_name>
<wp:status>publish</wp:status>
<wp:post_parent>0</wp:post_parent>
<wp:menu_order>0</wp:menu_order>
<wp:post_type>post</wp:post_type>
<wp:post_password></wp:post_password>
<wp:is_sticky>0</wp:is_sticky>
<category domain="category" nicename="category-2"><![CDATA[Category 2]]></category>
<wp:postmeta>
<wp:meta_key>_edit_last</wp:meta_key>
<wp:meta_value><![CDATA[3]]></wp:meta_value>
</wp:postmeta>
</item>
</channel>
</rss>

View file

@ -5,6 +5,14 @@ __all__ = [
import os
import subprocess
import re
import sys
import cStringIO
from functools import wraps
from contextlib import contextmanager
from tempfile import mkdtemp
from shutil import rmtree
from pelican.contents import Article
@ -14,6 +22,99 @@ except ImportError:
import unittest
@contextmanager
def temporary_folder():
"""creates a temporary folder, return it and delete it afterwards.
This allows to do something like this in tests:
>>> with temporary_folder() as d:
# do whatever you want
"""
tempdir = mkdtemp()
try:
yield tempdir
finally:
rmtree(tempdir)
def isplit(s, sep=None):
"""
Behave like str.split but returns a generator instead of a list.
>>> list(isplit('\tUse the force\n')) == '\tUse the force\n'.split()
True
>>> list(isplit('\tUse the force\n')) == ['Use', 'the', 'force']
True
>>> list(isplit('\tUse the force\n', "e")) == '\tUse the force\n'.split("e")
True
>>> list(isplit('Use the force', "e")) == 'Use the force'.split("e")
True
>>> list(isplit('Use the force', "e")) == ['Us', ' th', ' forc', '']
True
"""
sep, hardsep = r'\s+' if sep is None else re.escape(sep), sep is not None
exp, pos, l = re.compile(sep), 0, len(s)
while True:
m = exp.search(s, pos)
if not m:
if pos < l or hardsep:
# ^ mimic "split()": ''.split() returns []
yield s[pos:]
break
start = m.start()
if pos < start or hardsep:
# ^ mimic "split()": includes trailing empty string
yield s[pos:start]
pos = m.end()
def mute(returns_output=False):
"""
Decorate a function that prints to stdout, intercepting the output.
If "returns_output" is True, the function will return a generator
yielding the printed lines instead of the return values.
The decorator litterally hijack sys.stdout during each function
execution, so be careful with what you apply it to.
>>> def numbers():
print "42"
print "1984"
...
>>> numbers()
42
1984
>>> mute()(numbers)()
>>> list(mute(True)(numbers)())
['42', '1984']
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
saved_stdout = sys.stdout
sys.stdout = cStringIO.StringIO()
try:
out = func(*args, **kwargs)
if returns_output:
out = isplit(sys.stdout.getvalue().strip())
finally:
sys.stdout = saved_stdout
return out
return wrapper
return decorator
def get_article(title, slug, content, lang, extra_metadata=None):
metadata = {'slug': slug, 'title': title, 'lang': lang}
if extra_metadata is not None:

View file

@ -4,6 +4,7 @@ from .support import unittest
from pelican.contents import Page
from pelican.settings import _DEFAULT_CONFIG
from pelican.utils import truncate_html_words
from jinja2.utils import generate_lorem_ipsum
@ -48,6 +49,20 @@ class TestPage(unittest.TestCase):
page = Page(**self.page_kwargs)
self.assertEqual(page.summary, TEST_SUMMARY)
def test_summary_max_length(self):
"""If a :SUMMARY_MAX_LENGTH: is set, and there is no other summary, generated summary
should not exceed the given length."""
page_kwargs = self._copy_page_kwargs()
settings = _DEFAULT_CONFIG.copy()
page_kwargs['settings'] = settings
del page_kwargs['metadata']['summary']
settings['SUMMARY_MAX_LENGTH'] = None
page = Page(**page_kwargs)
self.assertEqual(page.summary, TEST_CONTENT)
settings['SUMMARY_MAX_LENGTH'] = 10
page = Page(**page_kwargs)
self.assertEqual(page.summary, truncate_html_words(TEST_CONTENT, 10))
def test_slug(self):
"""If a title is given, it should be used to generate the slug."""
page = Page(**self.page_kwargs)
@ -83,14 +98,9 @@ class TestPage(unittest.TestCase):
from datetime import datetime
from sys import platform
dt = datetime(2015, 9, 13)
# make a deep copy of page_kawgs
page_kwargs = dict([(key, self.page_kwargs[key]) for key in
self.page_kwargs])
for key in page_kwargs:
if not isinstance(page_kwargs[key], dict):
break
page_kwargs[key] = dict([(subkey, page_kwargs[key][subkey])
for subkey in page_kwargs[key]])
page_kwargs = self._copy_page_kwargs()
# set its date to dt
page_kwargs['metadata']['date'] = dt
page = Page(**page_kwargs)
@ -124,3 +134,15 @@ class TestPage(unittest.TestCase):
# Until we find some other method to test this functionality, we
# will simply skip this test.
unittest.skip("There is no locale %s in this system." % locale)
def _copy_page_kwargs(self):
# make a deep copy of page_kwargs
page_kwargs = dict([(key, self.page_kwargs[key]) for key in
self.page_kwargs])
for key in page_kwargs:
if not isinstance(page_kwargs[key], dict):
break
page_kwargs[key] = dict([(subkey, page_kwargs[key][subkey])
for subkey in page_kwargs[key]])
return page_kwargs

View file

@ -6,7 +6,7 @@ import re
from tempfile import mkdtemp
from shutil import rmtree
from pelican.generators import ArticlesGenerator, LessCSSGenerator
from pelican.generators import ArticlesGenerator, LessCSSGenerator, PagesGenerator
from pelican.settings import _DEFAULT_CONFIG
from .support import unittest, skipIfNoExecutable
@ -96,6 +96,48 @@ class TestArticlesGenerator(unittest.TestCase):
write.assert_called_count == 0
class TestPageGenerator(unittest.TestCase):
"""
Every time you want to test for a new field;
Make sure the test pages in "TestPages" have all the fields
Add it to distilled in distill_pages_for_test
Then update the assertItemsEqual in test_generate_context to match expected
"""
def distill_pages_for_test(self, pages):
distilled = []
for page in pages:
distilled.append([
page.title,
page.status
]
)
return distilled
def test_generate_context(self):
settings = _DEFAULT_CONFIG.copy()
settings['PAGE_DIR'] = 'TestPages'
generator = PagesGenerator(settings.copy(), settings, CUR_DIR,
_DEFAULT_CONFIG['THEME'], None,
_DEFAULT_CONFIG['MARKUP'])
generator.generate_context()
pages = self.distill_pages_for_test(generator.pages)
hidden_pages = self.distill_pages_for_test(generator.hidden_pages)
pages_expected = [
[u'This is a test page', 'published'],
[u'This is a markdown test page', 'published']
]
hidden_pages_expected = [
[u'This is a test hidden page', 'hidden'],
[u'This is a markdown test hidden page', 'hidden']
]
self.assertItemsEqual(pages_expected,pages)
self.assertItemsEqual(hidden_pages_expected,hidden_pages)
class TestLessCSSGenerator(unittest.TestCase):
LESS_CONTENT = """

52
tests/test_importer.py Normal file
View file

@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
import os
from pelican.tools.pelican_import import wp2fields, fields2pelican
from .support import unittest, temporary_folder, mute
CUR_DIR = os.path.dirname(__file__)
WORDPRESS_XML_SAMPLE = os.path.join(CUR_DIR, 'content', 'wordpressexport.xml')
PANDOC = os.system('pandoc --version') == 0
try:
import BeautifulSoup
except ImportError:
BeautifulSoup = False # NOQA
class TestWordpressXmlImporter(unittest.TestCase):
def setUp(self):
self.posts = wp2fields(WORDPRESS_XML_SAMPLE)
@unittest.skipUnless(PANDOC and BeautifulSoup,
'Needs Pandoc and BeautifulSoup')
def test_ignore_empty_posts(self):
posts = list(self.posts)
self.assertTrue(posts)
for title, content, fname, date, author, categ, tags, format in posts:
self.assertTrue(title.strip())
@unittest.skipUnless(PANDOC and BeautifulSoup,
'Needs Pandoc and BeautifulSoup')
def test_can_toggle_raw_html_code_parsing(self):
posts = list(self.posts)
r = lambda f: open(f).read()
silent_f2p = mute(True)(fields2pelican)
with temporary_folder() as temp:
rst_files = (r(f) for f in silent_f2p(posts, 'markdown', temp))
self.assertTrue(any('<iframe' in rst for rst in rst_files))
rst_files = (r(f) for f in silent_f2p(posts, 'markdown', temp,
strip_raw=True))
self.assertFalse(any('<iframe' in rst for rst in rst_files))
# no effect in rst
rst_files = (r(f) for f in silent_f2p(posts, 'rst', temp))
self.assertFalse(any('<iframe' in rst for rst in rst_files))
rst_files = (r(f) for f in silent_f2p(posts, 'rst', temp,
strip_raw=True))
self.assertFalse(any('<iframe' in rst for rst in rst_files))

View file

@ -62,8 +62,10 @@ class RstReaderTest(unittest.TestCase):
except ImportError:
return unittest.skip('need the typogrify distribution')
class MdReaderTest(unittest.TestCase):
@unittest.skipUnless(readers.Markdown, "markdown isn't installed")
def test_article_with_md_extention(self):
# test to ensure the md extension is being processed by the correct reader
reader = readers.MarkdownReader({})
@ -74,6 +76,7 @@ class MdReaderTest(unittest.TestCase):
self.assertEqual(content, expected)
@unittest.skipUnless(readers.Markdown, "markdown isn't installed")
def test_article_with_mkd_extension(self):
# test to ensure the mkd extension is being processed by the correct reader
reader = readers.MarkdownReader({})

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
import shutil
import os
import datetime
import time
@ -86,3 +87,12 @@ class TestUtils(unittest.TestCase):
changed = utils.files_changed(path, 'rst')
self.assertEquals(changed, True)
self.assertAlmostEqual(utils.LAST_MTIME, t, delta=1)
def test_clean_output_dir(self):
test_directory = os.path.join(os.path.dirname(__file__), 'clean_output')
content = os.path.join(os.path.dirname(__file__), 'content')
shutil.copytree(content, test_directory)
utils.clean_output_dir(test_directory)
self.assertTrue(os.path.isdir(test_directory))
self.assertListEqual([], os.listdir(test_directory))
shutil.rmtree(test_directory)

View file

@ -12,3 +12,4 @@ deps =
unittest2
mock
Markdown
BeautifulSoup