Merge branch 'master' into htmlparser

This commit is contained in:
dave mankoff 2012-07-09 22:15:36 -04:00
commit c87cf2d2cf
27 changed files with 485 additions and 243 deletions

View file

@ -1,4 +1,4 @@
Jinja2 Jinja2>=2.4
Pygments Pygments
docutils docutils
feedgenerator feedgenerator

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. 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 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? 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 Pygments adds some classes to the generated content. These classes are used by
themes to style code syntax highlighting via CSS. Specifically, you can 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 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 Django code, for example, you can use the demo `on the project website
<http://pygments.org/demo/15101/>`_. <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 There are several ways to help out. First, you can use Pelican and report any
suggestions or problems you might have on `the bugtracker 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 If you want to contribute, please fork `the git repository
<https://github.com/ametaireau/pelican/>`_, make your changes, and issue <https://github.com/ametaireau/pelican/>`_, make your changes, and issue

View file

@ -1,15 +1,15 @@
Getting started Getting started
############### ###############
Installing Installing Pelican
========== ==================
You're ready? Let's go! You can install Pelican via several different methods. You're ready? Let's go! You can install Pelican via several different methods.
The simplest is via `pip <http://www.pip-installer.org/>`_:: The simplest is via `pip <http://www.pip-installer.org/>`_::
$ pip install pelican $ pip install pelican
If you don't have pip installed, an alternative method is easy_install:: If you don't have ``pip`` installed, an alternative method is ``easy_install``::
$ easy_install pelican $ easy_install pelican
@ -18,12 +18,13 @@ a virtual environment for Pelican via `virtualenv <http://www.virtualenv.org/>`_
and `virtualenvwrapper <http://www.doughellmann.com/projects/virtualenvwrapper/>`_ and `virtualenvwrapper <http://www.doughellmann.com/projects/virtualenvwrapper/>`_
before installing Pelican:: before installing Pelican::
$ pip install virtualenvwrapper $ sudo pip install --upgrade virtualenv virtualenvwrapper
$ mkvirtualenv pelican $ mkvirtualenv pelican
$ pip install pelican
Once the virtual environment has been created and activated, Pelican can be 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 be installed via ``pip`` or ``easy_install`` as noted above. Alternatively, if
have the project source, you can install Pelican using the distutils you have the project source, you can install Pelican using the distutils
method:: method::
$ cd path-to-Pelican-source $ cd path-to-Pelican-source
@ -34,11 +35,16 @@ version of Pelican rather than a stable release, use the following command::
$ pip install -e git://github.com/ametaireau/pelican#egg=pelican $ pip install -e git://github.com/ametaireau/pelican#egg=pelican
If you plan on using Markdown as a markup format, you'll need to install the
Markdown library as well::
$ pip install Markdown
Upgrading Upgrading
--------- ---------
If you installed a stable Pelican release via pip or easy_install and wish to 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 upgrade to the latest stable release, you can do so by adding ``--upgrade`` to
the relevant command. For pip, that would be:: the relevant command. For pip, that would be::
$ pip install --upgrade pelican $ pip install --upgrade pelican
@ -55,13 +61,66 @@ At this time, Pelican is dependent on the following Python packages:
* jinja2, for templating support * jinja2, for templating support
* docutils, for supporting reStructuredText as an input format * 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: Optionally:
* pygments, for syntax highlighting * pygments, for syntax highlighting
* Markdown, for supporting Markdown as an input format * Markdown, for supporting Markdown as an input format
Kickstart a blog
================
Following is a brief tutorial for those who want to get started right away.
We're going to assume Pelican was installed in a virtual environment via the
following steps (if you're not using a virtual environment for Pelican, you can
skip to the ``pelican-quickstart`` command)::
$ sudo pip install --upgrade virtualenv virtualenvwrapper
$ mkvirtualenv pelican
$ pip install pelican Markdown
Next we'll create a directory to house our site content and configuration files,
which can be located any place you prefer, and associate this new project with
the currently-active virtual environment::
$ mkdir ~/code/yoursitename
$ cd ~/code/yoursitename
$ setvirtualenvproject
Now we can run the ``pelican-quickstart`` command, which will ask some questions
about your site::
$ pelican-quickstart
Once you finish answering all the questions, you can begin adding content to the
*content* folder that has been created for you. (See *Writing articles using
Pelican* section below for more information about how to format your content.)
Once you have some content to generate, you can convert it to HTML via the
following command::
$ make html
If you'd prefer to have Pelican automatically regenerate your site every time a
change is detected (handy when testing locally), use the following command
instead::
$ make regenerate
To serve the site so it can be previewed in your browser::
$ make serve
Visit http://localhost:8000 in your browser to see your site.
When you're ready to publish your site, you can upload it via the method(s) you
chose during the ``pelican-quickstart`` questionnaire. For this example, we'll
use rsync over ssh::
$ make rsync_upload
That's it! Your site should now be live.
Writing articles using Pelican Writing articles using Pelican
============================== ==============================
@ -73,7 +132,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. information you need to provide in the form of metadata inside your files.
You can provide this metadata in reStructuredText text files via the 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 My super title
############## ##############
@ -83,10 +142,9 @@ following syntax (give your file the `.rst` extension)::
:category: yeah :category: yeah
:author: Alexis Metaireau :author: Alexis Metaireau
You can also use Markdown syntax (with a file ending in ``.md``).
You can also use Markdown syntax (with a file ending in `.md`). Markdown generation will not work until you explicitly install the ``Markdown``
Markdown generation will not work until you explicitly install the `Markdown` package, which can be done via ``pip install Markdown``. Metadata syntax for
package, which can be done via `pip install Markdown`. Metadata syntax for
Markdown posts should follow this pattern:: Markdown posts should follow this pattern::
Date: 2010-12-03 Date: 2010-12-03
@ -99,43 +157,48 @@ Markdown posts should follow this pattern::
Note that, aside from the title, none of this metadata is mandatory: if the date 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 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 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 example, a file located at ``python/foobar/myfoobar.rst`` will have a category of
`foobar`. ``foobar``.
Generate your blog Generate your blog
------------------ ------------------
To launch Pelican, just use the `pelican` command:: The ``make`` shortcut commands mentioned in the ``Kickstart a blog`` section
are mostly wrappers around the ``pelican`` command that generates the HTML from
the content. The ``pelican`` command can also be run directly::
$ pelican /path/to/your/content/ [-s path/to/your/settings.py] $ 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/` The above command will generate your weblog and save it in the ``content/``
folder. folder, using the default theme to produce a simple site. The default theme is
simple HTML without styling and is provided so folks may use it as a basis for
creating their own themes.
The above command will use the default theme to produce a simple site. It's not Pelican has other command-line switches available. Have a look at the help to
very sexy, as it's just simple HTML output (without any style). see all the options you can use::
You can create your own style if you want. Have a look at the help to see all
the options you can use::
$ pelican --help $ pelican --help
Kickstart a blog Auto-reload
---------------- -----------
You also can use the `pelican-quickstart` script to start a new blog in It's possible to tell Pelican to watch for your modifications, instead of
seconds by just answering a few questions. Just run `pelican-quickstart` and manually re-running it every time you want to see your changes. To enable this,
you're done! (Added in Pelican 3.0) run the ``pelican`` command with the ``-r`` or ``--autoreload`` option.
Pages 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. 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. 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 Importing an existing blog
-------------------------- --------------------------
@ -145,8 +208,8 @@ a simple script. See :ref:`import`.
Translations Translations
------------ ------------
It is possible to translate articles. To do so, you need to add a `lang` meta 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 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 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 default language will be listed, and each article will be accompanied by a list
of available translations for that article. of available translations for that article.
@ -205,13 +268,6 @@ For Markdown, format your code blocks thusly::
The specified identifier should be one that appears on the The specified identifier should be one that appears on the
`list of available lexers <http://pygments.org/docs/lexers/>`_. `list of available lexers <http://pygments.org/docs/lexers/>`_.
Auto-reload
-----------
It's possible to tell Pelican to watch for your modifications, instead of
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 Publishing drafts
----------------- -----------------
@ -234,5 +290,3 @@ Or run a simple web server using Python::
cd output && python -m SimpleHTTPServer cd output && python -m SimpleHTTPServer
(Tip: If using the latter method in conjunction with the auto-reload feature,
ensure that `DELETE_OUTPUT_DIRECTORY` is set to `False` in your settings file.)

View file

@ -12,34 +12,34 @@ original author wrote with some software design information.
Overall structure 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 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 files, and the output is a blog, but both input and output can be anything you
want. want.
The logic is separated into different classes and concepts: 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 on. Since those operations are commonly used, the object is created once and
then passed to the generators. 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 reStructuredText for now, but the system is extensible). Given a file, they return
metadata (author, tags, category, etc.) and content (HTML-formatted). metadata (author, tags, category, etc.) and content (HTML-formatted).
* `generators` generate the different outputs. For instance, Pelican comes with * **Generators** generate the different outputs. For instance, Pelican comes with
`ArticlesGenerator` and `PageGenerator`. Given a configuration, they can do ``ArticlesGenerator`` and ``PageGenerator``. Given a configuration, they can do
whatever they want. Most of the time, it's generating files from inputs. 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 * 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 syntax is `Jinja2 <http://jinja.pocoo.org/>`_ and is very easy to learn, so
to jump in and build your own theme. don't hesitate to jump in and build your own theme.
How to implement a new reader? How to implement a new reader?
============================== ==============================
Is there an awesome markup language you want to add to Pelican? 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. method that returns HTML content and some metadata.
Take a look at the Markdown reader:: Take a look at the Markdown reader::
@ -65,8 +65,8 @@ Take a look at the Markdown reader::
Simple, isn't it? Simple, isn't it?
If your new reader requires additional Python dependencies, then you should wrap 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 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. 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 This makes it possible for users to continue using their favourite markup method
without needing to install modules for formats they don't use. 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 Generators have two important methods. You're not forced to create
both; only the existing ones will be called. 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 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 context is shared between all generators, and will be passed to the
templates. For instance, the `PageGenerator` `generate_context` method finds templates. For instance, the ``PageGenerator`` ``generate_context`` method
all the pages, transforms them into objects, and populates the context with finds all the pages, transforms them into objects, and populates the context
them. Be careful *not* to output anything using this context at this stage, with them. Be careful *not* to output anything using this context at this
as it is likely to change by the effect of other generators. 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 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 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 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 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.

View file

@ -3,14 +3,14 @@
Plugins Plugins
####### #######
Since version 3.0, pelican manages plugins. Plugins are a way to add features Since version 3.0, Pelican manages plugins. Plugins are a way to add features
to pelican without having to directly hack pelican code. to Pelican without having to directly hack Pelican code.
Pelican is shipped with a set of core plugins, but you can easily implement Pelican is shipped with a set of core plugins, but you can easily implement
your own (and this page describes how). your own (and this page describes how).
How to use plugins? How to use plugins
==================== ==================
To load plugins, you have to specify them in your settings file. You have two To load plugins, you have to specify them in your settings file. You have two
ways to do so. ways to do so.
@ -23,21 +23,21 @@ Or by importing them and adding them to the list::
from pelican.plugins import gravatar from pelican.plugins import gravatar
PLUGINS = [gravatar, ] PLUGINS = [gravatar, ]
If your plugins are not in an importable path, you can specify a `PLUGIN_PATH` If your plugins are not in an importable path, you can specify a ``PLUGIN_PATH``
in the settings:: in the settings::
PLUGIN_PATH = "plugins" PLUGIN_PATH = "plugins"
PLUGINS = ["list", "of", "plugins"] PLUGINS = ["list", "of", "plugins"]
How to create plugins? How to create plugins
====================== =====================
Plugins are based on the concept of signals. Pelican sends signals and 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 subscribe to those signals. The list of signals are defined in a following
section. section.
The only rule to follow for plugins is to define a `register` callable, in 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 exemple:: which you map the signals to your plugin logic. Let's take a simple example::
from pelican import signals from pelican import signals
@ -68,7 +68,7 @@ List of plugins
=============== ===============
Not all the list are described here, but a few of them have been extracted from Not all the list are described here, but a few of them have been extracted from
pelican core and provided in pelican.plugins. They are described here: the Pelican core and provided in ``pelican.plugins``. They are described here:
Tag cloud Tag cloud
--------- ---------
@ -82,7 +82,7 @@ Github Activity
This plugin makes use of the ``feedparser`` library that you'll need to This plugin makes use of the ``feedparser`` library that you'll need to
install. install.
Set the GITHUB_ACTIVITY_FEED parameter to your github activity feed. Set the ``GITHUB_ACTIVITY_FEED`` parameter to your Github activity feed.
For example, my setting would look like:: For example, my setting would look like::
GITHUB_ACTIVITY_FEED = 'https://github.com/kpanic.atom' GITHUB_ACTIVITY_FEED = 'https://github.com/kpanic.atom'
@ -105,4 +105,4 @@ variable, as in the example::
``github_activity`` is a list of lists. The first element is the title ``github_activity`` is a list of lists. The first element is the title
and the second element is the raw html from github. 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:: .. warning::
This page comes from a report the original author (Alexis Métaireau) wrote This page comes from a report the original author (Alexis Métaireau) wrote
right after writing pelican, in december 2010. The information may not be right after writing Pelican, in December 2010. The information may not be
up to date. up-to-date.
Pelican is a simple static blog generator. It parses markup files 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. 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 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. 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, 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 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 provide fixes on it. So far, I've re-factored the Pelican code by two
times, each time took less than 30 minutes. times; each time took less than 30 minutes.
Use case 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 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 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 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 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 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 .. image:: _static/overall.png
To be flexible enough, pelican have a template support, so you can To be flexible enough, Pelican has template support, so you can easily write
easily write you own themes if you want to. your own themes if you want to.
Design process Design process
============== ==============
@ -42,19 +42,18 @@ Design process
Pelican came from a need I have. I started by creating a single file 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. 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. 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 Then, I created the content I wanted to parse (the reStructuredText files)
text files), and started experimenting with the code. and started experimenting with the code. Pelican was 200 lines long and
Pelican was 200 lines long, and contained almost ten functions and one contained almost ten functions and one class when it was first usable.
class when it was first usable.
I have been facing different problems all over the time, and wanted to 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 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 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. 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 In the same way, I have added the support of different things over
time: atom feeds, multiple themes, multiple markup support, etc. time: Atom feeds, multiple themes, multiple markup support, etc.
At some point, it appears that the “only one file” mantra was not good 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 enough for Pelican, so I decided to rework a bit all that, and split this in
multiple different files. multiple different files.
Ive separated the logic in different classes and concepts: 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 Since those operations are commonly used, the object is created
once, and then passed to the generators. 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
Restructured Text for now, but the system is extensible). Given a reStructuredText for now, but the system is extensible). Given a
file, they return metadata (author, tags, category etc) and file, they return metadata (author, tags, category, etc) and
content (HTML formated). 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 comes with an ArticlesGenerator and PagesGenerator, into
others. Given a configuration, they can do whatever you want 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). (user inputs and files).
I also deal with contents objects. They can be `Articles`, `Pages`, `Quotes`, I also deal with contents objects. They can be ``Articles``, ``Pages``,
or whatever you want. They are defined in the contents.py module, ``Quotes``, or whatever you want. They are defined in the ``contents.py``
and represent some content to be used by the program. 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 .. image:: _static/uml.jpg
The interface do not really exists, and I have added it only to clarify the 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. whole picture. I do use duck typing and not interfaces.
Internally, the following process is followed: Internally, the following process is followed:
* First of all, the command line is parsed, and some content from * 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. 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 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. each generator.
I make two calls because it is important that when the output is I make two calls because it is important that when the output is
generated by the generators, the context will not change. In other generated by the generators, the context will not change. In other
words, the first method `generate_context` should modify the context, words, the first method ``generate_context`` should modify the context,
whereas the second `generate_output` method should not. whereas the second ``generate_output`` method should not.
Then, it is up to the generators to do what the want, in the Then, it is up to the generators to do what the want, in the
`generate_context` and `generate_content` method. ``generate_context`` and ``generate_content`` method.
Taking the `ArticlesGenerator` class will help to understand some others Taking the ``ArticlesGenerator`` class will help to understand some others
concepts. Here is what happens when calling the `generate_context` concepts. Here is what happens when calling the ``generate_context``
method: method:
* Read the folder “path”, looking for restructured text files, load * Read the folder “path”, looking for restructured text files, load
each of them, and construct a content object (`Article`) with it. To do so, each of them, and construct a content object (``Article``) with it. To do so,
use `Reader` objects. use ``Reader`` objects.
* Update the `context` with all those articles. * Update the ``context`` with all those articles.
Then, the `generate_content` method uses the `context` and the `writer` to Then, the ``generate_content`` method uses the ``context`` and the ``writer`` to
generate the wanted output generate the wanted output.

View file

@ -37,8 +37,8 @@ Setting name (default value) What doe
timestamp information (mtime) if it can't get timestamp information (mtime) if it can't get
date information from the metadata. date information from the metadata.
`JINJA_EXTENSIONS` (``[]``) A list of any Jinja2 extensions you want to use. `JINJA_EXTENSIONS` (``[]``) A list of any Jinja2 extensions you want to use.
`DELETE_OUTPUT_DIRECTORY` (``False``) Delete the output directory as well as `DELETE_OUTPUT_DIRECTORY` (``False``) Delete the content of the output directory before
the generated files. generating new files.
`LOCALE` (''[#]_) Change the locale. A list of locales can be provided `LOCALE` (''[#]_) Change the locale. A list of locales can be provided
here or a single string representing one locale. here or a single string representing one locale.
When providing a list, all the locales will be tried When providing a list, all the locales will be tried
@ -59,18 +59,16 @@ Setting name (default value) What doe
`PDF_GENERATOR` (``False``) Set to True if you want to have PDF versions `PDF_GENERATOR` (``False``) Set to True if you want to have PDF versions
of your documents. You will need to install of your documents. You will need to install
`rst2pdf`. `rst2pdf`.
`RELATIVE_URLS` (``True``) Defines whether Pelican should use relative URLs or `RELATIVE_URLS` (``True``) Defines whether Pelican should use document-relative URLs or
not. not. If set to ``False``, Pelican will use the SITEURL
setting to construct absolute URLs.
`PLUGINS` (``[]``) The list of plugins to load. See :ref:`plugins`. `PLUGINS` (``[]``) The list of plugins to load. See :ref:`plugins`.
`SITENAME` (``'A Pelican Blog'``) Your site name `SITENAME` (``'A Pelican Blog'``) Your site name
`SITEURL` Base URL of your website. Not defined by default, `SITEURL` Base URL of your website. Not defined by default,
which means the base URL is assumed to be "/" with a so it is best to specify your SITEURL; if you do not, feeds
root-relative URL structure. If `SITEURL` is specified will not be generated with properly-formed URLs. You should
explicitly, there should be no trailing slash at the end, include ``http://`` and your domain, with no trailing
and URLs will be generated with an absolute URL structure slash at the end. Example: ``SITEURL = 'http://mydomain.com'``
(including the domain). If you want to use relative URLs
instead of root-relative or absolute URLs, you should
instead use the `RELATIVE_URL` setting.
`STATIC_PATHS` (``['images']``) The static paths you want to have accessible `STATIC_PATHS` (``['images']``) The static paths you want to have accessible
on the output path "static". By default, on the output path "static". By default,
Pelican will copy the 'images' folder to the Pelican will copy the 'images' folder to the
@ -107,6 +105,15 @@ Setting name (default value) What doe
URL settings URL settings
------------ ------------
The first thing to understand is that there are currently two supported methods
for URL formation: *relative* and *absolute*. Document-relative URLs are useful
when testing locally, and absolute URLs are reliable and most useful when
publishing. One method of supporting both is to have one Pelican configuration
file for local development and another for publishing. To see an example of this
type of setup, use the ``pelican-quickstart`` script as described at the top of
the :doc:`Getting Started<getting_started>` page, which will produce two separate
configuration files for local development and publishing, respectively.
You can customize the URLs and locations where files will be saved. The URLs and You can customize the URLs and locations where files will be saved. The URLs and
SAVE_AS variables use Python's format strings. These variables allow you to place SAVE_AS variables use Python's format strings. These variables allow you to place
your articles in a location such as '{slug}/index.html' and link to them as your articles in a location such as '{slug}/index.html' and link to them as

View file

@ -3,11 +3,10 @@
How to create themes for Pelican How to create themes for Pelican
################################ ################################
Pelican uses the great `jinja2 <http://jinja.pocoo.org>`_ templating engine to 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 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, 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>`_.
<https://github.com/ametaireau/pelican/tree/master/pelican/themes/simple/templates>`_
Structure Structure
========= =========

View file

@ -12,16 +12,16 @@ file generator, we can take advantage of this.
User Pages User Pages
---------- ----------
Github allows you to create user pages in the form of ``username.github.com``. GitHub allows you to create user pages in the form of ``username.github.com``.
Whatever is created in master branch will be published. For this purposes just Whatever is created in the master branch will be published. For this purpose,
the output generated by pelican needs to pushed at github. just the output generated by Pelican needs to pushed to GitHub.
So given a repository containing your articles, just run pelican over the posts So given a repository containing your articles, just run Pelican over the posts
and deploy the master branch at github:: and deploy the master branch to GitHub::
$ pelican -s pelican.conf.py ./path/to/posts -o /path/to/output $ pelican -s pelican.conf.py ./path/to/posts -o /path/to/output
Now add all the files in the output directory generated by pelican:: Now add all the files in the output directory generated by Pelican::
$ git add /path/to/output/* $ git add /path/to/output/*
$ git commit -am "Your Message" $ git commit -am "Your Message"
@ -31,12 +31,12 @@ Project Pages
------------- -------------
For creating Project pages, a branch called ``gh-pages`` is used for publishing. 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 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 $ pip install ghp-import
Then, given a repository containing your articles, you would simply have Then, given a repository containing your articles, you would simply run
to run Pelican and upload the output to GitHub:: Pelican and upload the output to GitHub::
$ pelican -s pelican.conf.py . $ pelican -s pelican.conf.py .
$ ghp-import output $ ghp-import output
@ -45,10 +45,8 @@ to run Pelican and upload the output to GitHub::
And that's it. And that's it.
If you want, you can put that directly into a post-commit hook, so each time you 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

@ -68,10 +68,10 @@ class Pelican(object):
for plugin in self.plugins: for plugin in self.plugins:
# if it's a string, then import it # if it's a string, then import it
if isinstance(plugin, basestring): if isinstance(plugin, basestring):
log.debug("Loading plugin `{0}' ...".format(plugin)) logger.debug("Loading plugin `{0}' ...".format(plugin))
plugin = __import__(plugin, globals(), locals(), 'module') plugin = __import__(plugin, globals(), locals(), 'module')
log.debug("Registering plugin `{0}' ...".format(plugin.__name__)) logger.debug("Registering plugin `{0}' ...".format(plugin.__name__))
plugin.register() plugin.register()
def _handle_deprecation(self): def _handle_deprecation(self):

View file

@ -357,10 +357,13 @@ class PagesGenerator(Generator):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.pages = [] self.pages = []
self.hidden_pages = []
self.hidden_translations = []
super(PagesGenerator, self).__init__(*args, **kwargs) super(PagesGenerator, self).__init__(*args, **kwargs)
def generate_context(self): def generate_context(self):
all_pages = [] all_pages = []
hidden_pages = []
for f in self.get_files( for f in self.get_files(
os.path.join(self.path, self.settings['PAGE_DIR']), os.path.join(self.path, self.settings['PAGE_DIR']),
exclude=self.settings['PAGE_EXCLUDES']): exclude=self.settings['PAGE_EXCLUDES']):
@ -373,15 +376,25 @@ class PagesGenerator(Generator):
filename=f) filename=f)
if not is_valid_content(page, f): if not is_valid_content(page, f):
continue 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.pages, self.translations = process_translations(all_pages)
self.hidden_pages, self.hidden_translations = process_translations(hidden_pages)
self._update_context(('pages', )) self._update_context(('pages', ))
self.context['PAGES'] = self.pages self.context['PAGES'] = self.pages
def generate_output(self, writer): 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'), writer.write_file(page.save_as, self.get_template('page'),
self.context, page=page, self.context, page=page,
relative_urls=self.settings.get('RELATIVE_URLS')) relative_urls=self.settings.get('RELATIVE_URLS'))

View file

@ -18,11 +18,13 @@ CONF = {
'ftp_host': 'localhost', 'ftp_host': 'localhost',
'ftp_user': 'anonymous', 'ftp_user': 'anonymous',
'ftp_target_dir': '/', 'ftp_target_dir': '/',
'ssh_host': 'locahost', 'ssh_host': 'localhost',
'ssh_port': 22,
'ssh_user': 'root', 'ssh_user': 'root',
'ssh_target_dir': '/var/www', 'ssh_target_dir': '/var/www',
'dropbox_dir' : '~/Dropbox/Public/', 'dropbox_dir' : '~/Dropbox/Public/',
'default_pagination' : 10, 'default_pagination' : 10,
'siteurl': '',
'lang': 'en' 'lang': 'en'
} }
@ -88,7 +90,7 @@ def ask(question, answer=str, default=None, l=None):
r = default r = default
break break
else: else:
print("You must answer `yes' or `no'") print("You must answer 'yes' or 'no'")
return r return r
elif answer == int: elif answer == int:
r = None r = None
@ -111,12 +113,12 @@ def ask(question, answer=str, default=None, l=None):
print('You must enter an integer') print('You must enter an integer')
return r return r
else: else:
raise NotImplemented('Arguent `answer` must be str, bool or integer') raise NotImplemented('Argument `answer` must be str, bool, or integer')
def main(): def main():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="A kickstarter for pelican", description="A kickstarter for Pelican",
formatter_class=argparse.ArgumentDefaultsHelpFormatter) formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('-p', '--path', default=".", parser.add_argument('-p', '--path', default=".",
help="The path to generate the blog into") help="The path to generate the blog into")
@ -125,7 +127,7 @@ def main():
parser.add_argument('-a', '--author', metavar="author", parser.add_argument('-a', '--author', metavar="author",
help='Set the author name of the website') help='Set the author name of the website')
parser.add_argument('-l', '--lang', metavar="lang", parser.add_argument('-l', '--lang', metavar="lang",
help='Set the default lang of the website') help='Set the default web site language')
args = parser.parse_args() args = parser.parse_args()
@ -137,36 +139,44 @@ Please answer the following questions so this script can generate the files need
'''.format(v=__version__)) '''.format(v=__version__))
CONF['basedir'] = os.path.abspath(ask('Where do you want to create your new Web site ?', answer=str, default=args.path)) project = os.path.join(os.environ['VIRTUAL_ENV'], '.project')
CONF['sitename'] = ask('What will be the title of this Web site ?', answer=str, default=args.title) if os.path.isfile(project):
CONF['author'] = ask('Who will be the author of this Web site ?', answer=str, default=args.author) CONF['basedir'] = open(project, 'r').read().rstrip("\n")
CONF['lang'] = ask('What will be the default language of this Web site ?', str, args.lang or CONF['lang'], 2) print('Using project associated with current virtual environment. Will save to:\n%s\n' % CONF['basedir'])
else:
CONF['basedir'] = os.path.abspath(ask('Where do you want to create your new web site?', answer=str, default=args.path))
CONF['with_pagination'] = ask('Do you want to enable article pagination ?', bool, bool(CONF['default_pagination'])) CONF['sitename'] = ask('What will be the title of this web site?', answer=str, default=args.title)
CONF['author'] = ask('Who will be the author of this web site?', answer=str, default=args.author)
CONF['lang'] = ask('What will be the default language of this web site?', str, args.lang or CONF['lang'], 2)
if ask('Do you want to specify a URL prefix? e.g., http://example.com ', answer=bool, default=True):
CONF['siteurl'] = ask('What is your URL prefix? (see above example; no trailing slash)', str, CONF['siteurl'])
CONF['with_pagination'] = ask('Do you want to enable article pagination?', bool, bool(CONF['default_pagination']))
if CONF['with_pagination']: if CONF['with_pagination']:
CONF['default_pagination'] = ask('So how many articles per page do you want ?', int, CONF['default_pagination']) CONF['default_pagination'] = ask('How many articles per page do you want?', int, CONF['default_pagination'])
else: else:
CONF['default_pagination'] = False CONF['default_pagination'] = False
mkfile = ask('Do you want to generate a Makefile to easily manage your website ?', bool, True) mkfile = ask('Do you want to generate a Makefile to easily manage your website?', bool, True)
if mkfile: if mkfile:
if ask('Do you want to upload your website using FTP ?', answer=bool, default=False): if ask('Do you want to upload your website using FTP?', answer=bool, default=False):
CONF['ftp_host'] = ask('What is the hostname of your FTP server ?', str, CONF['ftp_host']) CONF['ftp_host'] = ask('What is the hostname of your FTP server?', str, CONF['ftp_host'])
CONF['ftp_user'] = ask('What is your username on this server ?', str, CONF['ftp_user']) CONF['ftp_user'] = ask('What is your username on that server?', str, CONF['ftp_user'])
CONF['ftp_target_dir'] = ask('Where do you want to put your website on this server ?', str, CONF['ftp_target_dir']) CONF['ftp_target_dir'] = ask('Where do you want to put your web site on that server?', str, CONF['ftp_target_dir'])
if ask('Do you want to upload your website using SSH?', answer=bool, default=False):
if ask('Do you want to upload your website using SSH ?', answer=bool, default=False): CONF['ssh_host'] = ask('What is the hostname of your SSH server?', str, CONF['ssh_host'])
CONF['ssh_host'] = ask('What is the hostname of your SSH server ?', str, CONF['ssh_host']) CONF['ssh_port'] = ask('What is the port of your SSH server?', int, CONF['ssh_port'])
CONF['ssh_user'] = ask('What is your username on this server ?', str, CONF['ssh_user']) CONF['ssh_user'] = ask('What is your username on that server?', str, CONF['ssh_user'])
CONF['ssh_target_dir'] = ask('Where do you want to put your website on this server ?', str, CONF['ssh_target_dir']) CONF['ssh_target_dir'] = ask('Where do you want to put your web site on that server?', str, CONF['ssh_target_dir'])
if ask('Do you want to upload your website using Dropbox?', answer=bool, default=False):
if ask('Do you want to upload your website using Dropbox ?', answer=bool, default=False): CONF['dropbox_dir'] = ask('Where is your Dropbox directory?', str, CONF['dropbox_dir'])
CONF['dropbox_dir'] = ask('Where is your Dropbox directory ?', str, CONF['dropbox_dir'])
try: try:
os.makedirs(os.path.join(CONF['basedir'], 'src')) os.makedirs(os.path.join(CONF['basedir'], 'content'))
except OSError, e: except OSError, e:
print('Error: {0}'.format(e)) print('Error: {0}'.format(e))
@ -176,8 +186,17 @@ Please answer the following questions so this script can generate the files need
print('Error: {0}'.format(e)) print('Error: {0}'.format(e))
try: try:
with open(os.path.join(CONF['basedir'], 'pelican.conf.py'), 'w') as fd: with open(os.path.join(CONF['basedir'], 'pelicanconf.py'), 'w') as fd:
for line in get_template('pelican.conf.py'): for line in get_template('pelicanconf.py'):
template = string.Template(line)
fd.write(template.safe_substitute(CONF))
fd.close()
except OSError, e:
print('Error: {0}'.format(e))
try:
with open(os.path.join(CONF['basedir'], 'publishconf.py'), 'w') as fd:
for line in get_template('publishconf.py'):
template = string.Template(line) template = string.Template(line)
fd.write(template.safe_substitute(CONF)) fd.write(template.safe_substitute(CONF))
fd.close() fd.close()

View file

@ -48,9 +48,11 @@ def main():
parser.add_argument('-i', '--install', dest='to_install', nargs='+', metavar="theme path", 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", parser.add_argument('-r', '--remove', dest='to_remove', nargs='+', metavar="theme name",
help='The themes to remove') 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", 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") 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", parser.add_argument('-c', '--clean', dest='clean', action="store_true",
@ -62,6 +64,9 @@ def main():
args = parser.parse_args() args = parser.parse_args()
to_install = args.to_install or args.to_upgrade
to_sym = args.to_symlink or args.clean
if args.action: if args.action:
@ -69,8 +74,7 @@ def main():
list_themes(args.verbose) list_themes(args.verbose)
elif args.action is 'path': elif args.action is 'path':
print(_THEMES_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.to_remove:
if args.verbose: if args.verbose:
print('Removing themes...') print('Removing themes...')
@ -85,6 +89,13 @@ def main():
for i in args.to_install: for i in args.to_install:
install(i, v=args.verbose) 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.to_symlink:
if args.verbose: if args.verbose:
print('Linking themes...') print('Linking themes...')
@ -149,17 +160,21 @@ def remove(theme_name, v=False):
err(target + ' : no such file or directory') err(target + ' : no such file or directory')
def install(path, v=False): def install(path, v=False, u=False):
"""Installs a theme""" """Installs a theme"""
if not os.path.exists(path): if not os.path.exists(path):
err(path + ' : no such file or directory') err(path + ' : no such file or directory')
elif not os.path.isdir(path): elif not os.path.isdir(path):
err(path + ' : no a directory') err(path + ' : not a directory')
else: else:
theme_name = os.path.basename(os.path.normpath(path)) theme_name = os.path.basename(os.path.normpath(path))
theme_path = os.path.join(_THEMES_PATH, theme_name) 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') err(path + ' : already exists')
elif exists and u:
remove(theme_name, v)
install(path, v)
else: else:
if v: if v:
print("Copying `{p}' to `{t}' ...".format(p=path, t=theme_path)) 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): if not os.path.exists(path):
err(path + ' : no such file or directory') err(path + ' : no such file or directory')
elif not os.path.isdir(path): elif not os.path.isdir(path):
err(path + ' : no a directory') err(path + ' : not a directory')
else: else:
theme_name = os.path.basename(os.path.normpath(path)) theme_name = os.path.basename(os.path.normpath(path))
theme_path = os.path.join(_THEMES_PATH, theme_name) theme_path = os.path.join(_THEMES_PATH, theme_name)

View file

@ -2,15 +2,17 @@ PELICAN=$pelican
PELICANOPTS=$pelicanopts PELICANOPTS=$pelicanopts
BASEDIR=$$(PWD) BASEDIR=$$(PWD)
INPUTDIR=$$(BASEDIR)/src INPUTDIR=$$(BASEDIR)/content
OUTPUTDIR=$$(BASEDIR)/output OUTPUTDIR=$$(BASEDIR)/output
CONFFILE=$$(BASEDIR)/pelican.conf.py CONFFILE=$$(BASEDIR)/pelicanconf.py
PUBLISHCONF=$$(BASEDIR)/publishconf.py
FTP_HOST=$ftp_host FTP_HOST=$ftp_host
FTP_USER=$ftp_user FTP_USER=$ftp_user
FTP_TARGET_DIR=$ftp_target_dir FTP_TARGET_DIR=$ftp_target_dir
SSH_HOST=$ssh_host SSH_HOST=$ssh_host
SSH_PORT=$ssh_port
SSH_USER=$ssh_user SSH_USER=$ssh_user
SSH_TARGET_DIR=$ssh_target_dir SSH_TARGET_DIR=$ssh_target_dir
@ -22,10 +24,11 @@ help:
@echo 'Usage: ' @echo 'Usage: '
@echo ' make html (re)generate the web site ' @echo ' make html (re)generate the web site '
@echo ' make clean remove the generated files ' @echo ' make clean remove the generated files '
@echo ' ftp_upload upload the web site using FTP ' @echo ' make publish generate using production settings '
@echo ' ssh_upload upload the web site using SSH ' @echo ' ftp_upload upload the web site via FTP '
@echo ' dropbox_upload upload the web site using Dropbox ' @echo ' ssh_upload upload the web site via SSH '
@echo ' rsync_upload upload the web site using rsync/ssh' @echo ' dropbox_upload upload the web site via Dropbox '
@echo ' rsync_upload upload the web site via rsync/ssh '
@echo ' ' @echo ' '
@ -36,23 +39,31 @@ $$(OUTPUTDIR)/%.html:
$$(PELICAN) $$(INPUTDIR) -o $$(OUTPUTDIR) -s $$(CONFFILE) $$(PELICANOPTS) $$(PELICAN) $$(INPUTDIR) -o $$(OUTPUTDIR) -s $$(CONFFILE) $$(PELICANOPTS)
clean: clean:
rm -fr $$(OUTPUTDIR) find $$(OUTPUTDIR) -mindepth 1 -delete
mkdir $$(OUTPUTDIR)
dropbox_upload: $$(OUTPUTDIR)/index.html regenerate: clean
$$(PELICAN) -r $$(INPUTDIR) -o $$(OUTPUTDIR) -s $$(CONFFILE) $$(PELICANOPTS)
serve:
cd $$(OUTPUTDIR) && python -m SimpleHTTPServer
publish:
$$(PELICAN) $$(INPUTDIR) -o $$(OUTPUTDIR) -s $$(PUBLISHCONF) $$(PELICANOPTS)
dropbox_upload: publish
cp -r $$(OUTPUTDIR)/* $$(DROPBOX_DIR) cp -r $$(OUTPUTDIR)/* $$(DROPBOX_DIR)
ssh_upload: $$(OUTPUTDIR)/index.html ssh_upload: publish
scp -r $$(OUTPUTDIR)/* $$(SSH_USER)@$$(SSH_HOST):$$(SSH_TARGET_DIR) scp -P $$(SSH_PORT) -r $$(OUTPUTDIR)/* $$(SSH_USER)@$$(SSH_HOST):$$(SSH_TARGET_DIR)
rsync_upload: $$(OUTPUTDIR)/index.html rsync_upload: publish
rsync -e ssh -P -rvz --delete $(OUTPUTDIR)/* $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR) rsync -e "ssh -p $(SSH_PORT)" -P -rvz --delete $(OUTPUTDIR)/* $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR)
ftp_upload: $$(OUTPUTDIR)/index.html ftp_upload: publish
lftp ftp://$$(FTP_USER)@$$(FTP_HOST) -e "mirror -R $$(OUTPUTDIR) $$(FTP_TARGET_DIR) ; quit" lftp ftp://$$(FTP_USER)@$$(FTP_HOST) -e "mirror -R $$(OUTPUTDIR) $$(FTP_TARGET_DIR) ; quit"
github: $$(OUTPUTDIR)/index.html github: publish
ghp-import $$(OUTPUTDIR) ghp-import $$(OUTPUTDIR)
git push origin gh-pages git push origin gh-pages
.PHONY: html help clean ftp_upload ssh_upload rsync_upload dropbox_upload github .PHONY: html help clean regenerate serve publish ftp_upload ssh_upload rsync_upload dropbox_upload github

View file

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

View file

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

View file

@ -0,0 +1,16 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- #
from pelicanconf import *
SITEURL = '$siteurl'
DELETE_OUTPUT_DIRECTORY = True
# Following items are often useful when publishing
# Uncomment following line for absolute URLs in production:
#RELATIVE_URLS = False
#DISQUS_SITENAME = ""
#GOOGLE_ANALYTICS = ""

View file

@ -97,10 +97,22 @@ def clean_output_dir(path):
"""Remove all the files from the output directory""" """Remove all the files from the output directory"""
# remove all the existing content from the output folder # remove all the existing content from the output folder
try: for filename in os.listdir(path):
shutil.rmtree(path) file = os.path.join(path, filename)
except Exception: if os.path.isdir(file):
pass 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): def get_relative_path(filename):

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 #!/usr/bin/env python
from setuptools import setup from setuptools import setup
requires = ['feedgenerator', 'jinja2', 'pygments', 'docutils', 'pytz', 'blinker'] requires = ['feedgenerator', 'jinja2 >= 2.4', 'pygments', 'docutils', 'pytz', 'blinker']
try: try:
import argparse import argparse

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

@ -4,7 +4,7 @@ from mock import MagicMock
import os import os
import re import re
from pelican.generators import ArticlesGenerator, LessCSSGenerator from pelican.generators import ArticlesGenerator, LessCSSGenerator, PagesGenerator
from pelican.settings import _DEFAULT_CONFIG from pelican.settings import _DEFAULT_CONFIG
from .support import unittest, temporary_folder, skipIfNoExecutable from .support import unittest, temporary_folder, skipIfNoExecutable
@ -94,6 +94,48 @@ class TestArticlesGenerator(unittest.TestCase):
write.assert_called_count == 0 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
Then update the assertItemsEqual in test_generate_context to match expected
"""
def distill_pages(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(generator.pages)
hidden_pages = self.distill_pages(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): class TestLessCSSGenerator(unittest.TestCase):
LESS_CONTENT = """ LESS_CONTENT = """

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import shutil
import os import os
import datetime import datetime
import time import time
@ -86,3 +87,12 @@ class TestUtils(unittest.TestCase):
changed = utils.files_changed(path, 'rst') changed = utils.files_changed(path, 'rst')
self.assertEquals(changed, True) self.assertEquals(changed, True)
self.assertAlmostEqual(utils.LAST_MTIME, t, delta=1) 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)