Merge branch 'master' into html_list_tags

This commit is contained in:
Justin Mayer 2018-11-01 15:43:14 +01:00 committed by GitHub
commit 11de7b2e47
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
209 changed files with 3907 additions and 1398 deletions

3
.gitignore vendored
View file

@ -13,3 +13,6 @@ tags
htmlcov
six-*.egg/
*.orig
venv
samples/output
*.pem

View file

@ -27,7 +27,7 @@ Before you ask for help, please make sure you do the following:
* no plugins or only those related to the issue
**NOTE:** The most common sources of problems are anomalies in (1) themes,
(2) settings files, and (3) ``make``/``fab`` automation wrappers. If you can't
(2) settings files, and (3) ``make``/``invoke`` automation wrappers. If you can't
reproduce your problem when using the following steps to generate your site,
then the problem is almost certainly with your chosen theme and/or settings
file (and not Pelican itself)::
@ -76,8 +76,8 @@ The #pelican IRC channel
can manually join the ``#pelican`` IRC channel on the `freenode IRC network`_.
.. _webchat: https://kiwiirc.com/client/irc.freenode.net/?#pelican
.. _`IRC link`: irc://irc.freenode.org/pelican
.. _`freenode IRC network`: http://www.freenode.org/
.. _`IRC link`: irc://irc.freenode.net/pelican
.. _`freenode IRC network`: https://freenode.net/
Contributing code
@ -114,15 +114,21 @@ Using Git and GitHub
`install hub <https://github.com/github/hub/#installation>`_ and then run
`hub pull-request <https://github.com/github/hub/#git-pull-request>`_ to
turn your GitHub issue into a pull request containing your code.
* After you have issued a pull request, Travis will run the test suite for all
supported Python versions and check for PEP8 compliance. If any of these
checks fail, you should fix them. (If tests fail on Travis but seem to pass
locally, ensure that local test runs aren't skipping any tests.)
Contribution quality standards
------------------------------
* Adhere to `PEP8 coding standards`_ whenever possible. This can be eased via
the `pep8 <http://pypi.python.org/pypi/pep8>`_ or `flake8
* Adhere to `PEP8 coding standards`_. This can be eased via the `pycodestyle
<https://pypi.org/project/pycodestyle>`_ or `flake8
<http://pypi.python.org/pypi/flake8/>`_ tools, the latter of which in
particular will give you some useful hints about ways in which the
code/formatting can be improved.
code/formatting can be improved. If you are relying on your editor for PEP8
compliance, note that the line length specified by PEP8 is 79 (excluding the
line break).
* Ensure your code is compatible with the latest Python 2.7 and 3.x releases — see our
`compatibility cheatsheet`_ for more details.
* Add docs and tests for your changes. Undocumented and untested features will

View file

@ -1,3 +1,3 @@
include *.rst
recursive-include pelican *.html *.css *png *.in *.rst *.markdown *.md *.mkd *.xml *.py
recursive-include pelican *.html *.css *png *.rst *.markdown *.md *.mkd *.xml *.py
include LICENSE THANKS docs/changelog.rst

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Before After
Before After

View file

@ -5,7 +5,16 @@ Next release
============
* New signal: ``feed_generated``
* Make the HTML reader parse multiple occurences of metadata tags as list
* Replace Fabric by Invoke and ``fabfile.py`` template by ``tasks.py``.
* Replace ``SLUG_SUBSTITUTIONS`` (and friends) by ``SLUG_REGEX_SUBSTITUTIONS``
for more finegrained control
* ``'{base_name}'`` value in ``PAGINATION_PATTERNS`` setting no longer strips
``'bar'`` from ``'foo/bar.html'`` (unless ``'bar' == 'index'``).
* ``ARTICLE_ORDER_BY`` and ``PAGE_ORDER_BY`` now also affect 1) category, tag
and author pages 2) feeds 3) draft and hidden articles and pages
* New ``ARTICLE_TRANSLATION_ID`` and ``PAGE_TRANSLATION_ID`` settings to specify
metadata attributes used to identify translations; or to disable translations
* Make the HTML reader parse multiple occurrences of metadata tags as a list
3.7.1 (2017-01-10)
==================

View file

@ -11,7 +11,7 @@ The idea behind "pages" is that they are usually not temporal in nature and are
used for content that does not change very often (e.g., "About" or "Contact"
pages).
You can find sample content in the repository at: ``pelican/samples/content/``
You can find sample content in the repository at ``samples/content/``.
.. _internal_metadata:
@ -70,6 +70,22 @@ Metadata syntax for Markdown posts should follow this pattern::
This is the content of my super blog post.
You can also have your own metadata keys (so long as they don't conflict with reserved metadata keywords) for use in your python templates. The following is the list of reserved metadata keywords:
* `Title`
* `Tags`
* `Date`
* `Modified`
* `Status`
* `Category`
* `Author`
* `Authors`
* `Slug`
* `Summary`
* `Template`
* `Save_as`
* `Url`
Readers for additional formats (such as AsciiDoc_) are available via plugins.
Refer to `pelican-plugins`_ repository for those.
@ -370,8 +386,9 @@ of available translations for that article.
language. For such advanced functionality the `i18n_subsites
plugin`_ can be used.
Pelican uses the article's URL "slug" to determine if two or more articles are
translations of one another. The slug can be set manually in the file's
By default, Pelican uses the article's URL "slug" to determine if two or more
articles are translations of one another. (This can be changed with the
``ARTICLE_TRANSLATION_ID`` setting.) The slug can be set manually in the file's
metadata; if not set explicitly, Pelican will auto-generate the slug from the
title of the article.
@ -517,9 +534,9 @@ your settings file.
Publishing drafts
=================
If you want to publish an article as a draft (for friends to review before
publishing, for example), you can add a ``Status: draft`` attribute to its
metadata. That article will then be output to the ``drafts`` folder and not
If you want to publish an article or a page as a draft (for friends to review
before publishing, for example), you can add a ``Status: draft`` attribute to
its metadata. That article will then be output to the ``drafts`` folder and not
listed on the index page nor on any category or tag page.
If your articles should be automatically published as a draft (to not accidentally
@ -535,6 +552,6 @@ metadata to include ``Status: published``.
.. _W3C ISO 8601: http://www.w3.org/TR/NOTE-datetime
.. _AsciiDoc: http://www.methods.co.nz/asciidoc/
.. _pelican-plugins: http://github.com/getpelican/pelican-plugins
.. _Markdown Extensions: http://pythonhosted.org/Markdown/extensions/
.. _CodeHilite extension: http://pythonhosted.org/Markdown/extensions/code_hilite.html#syntax
.. _Markdown Extensions: https://python-markdown.github.io/extensions/
.. _CodeHilite extension: https://python-markdown.github.io/extensions/code_hilite/#syntax
.. _i18n_subsites plugin: http://github.com/getpelican/pelican-plugins/tree/master/i18n_subsites

View file

@ -14,32 +14,32 @@ When doing so, please adhere to the following guidelines.
Setting up the development environment
======================================
While there are many ways to set up one's development environment, following
is a method that uses `virtualenv <https://virtualenv.pypa.io/en/stable/>`_.
While there are many ways to set up one's development environment, we recommend
using `Virtualenv <https://virtualenv.pypa.io/en/stable/>`_. This tool allows
you to set up separate environments for separate Python projects that are
isolated from one another so you can use different packages (and package
versions) for each.
If you don't have ``virtualenv`` installed, you can install it via::
$ pip install virtualenv
Virtual environments allow you to work on Python projects which are isolated
from one another so you can use different packages (and package versions) with
different projects.
To create and activate a virtual environment, use the following syntax::
Use ``virtualenv`` to create and activate a virtual environment::
$ virtualenv ~/virtualenvs/pelican
$ cd ~/virtualenvs/pelican
$ . bin/activate
To clone the Pelican source::
Clone the Pelican source into a subfolder called ``src/pelican``::
$ git clone https://github.com/getpelican/pelican.git src/pelican
To install the development dependencies::
$ cd src/pelican
Install the development dependencies::
$ pip install -r requirements/developer.pip
To install Pelican and its dependencies::
Install Pelican and its dependencies::
$ python setup.py develop
@ -47,7 +47,8 @@ Or using ``pip``::
$ pip install -e .
To conveniently test on multiple Python versions, we also provide a .tox file.
To conveniently test on multiple Python versions, we also provide a ``.tox``
file.
Building the docs
@ -57,7 +58,7 @@ If you make changes to the documentation, you should preview your changes
before committing them::
$ pip install -r requirements/docs.pip
$ cd src/pelican/docs
$ cd docs
$ make html
Open ``_build/html/index.html`` in your browser to preview the documentation.
@ -79,8 +80,8 @@ mentioning that "some generated files differ from the expected functional tests
output." If you have made changes that affect the HTML output generated by
Pelican, and the changes to that output are expected and deemed correct given
the nature of your changes, then you should update the output used by the
functional tests. To do so, **make sure you have both ``en_EN.utf8`` and
``fr_FR.utf8`` locales installed**, and then run the following two commands::
functional tests. To do so, **make sure you have both** ``en_EN.utf8`` **and**
``fr_FR.utf8`` **locales installed**, and then run the following two commands::
$ LC_ALL=en_US.utf8 pelican -o pelican/tests/output/custom/ \
-s samples/pelican.conf.py samples/content/
@ -89,56 +90,60 @@ functional tests. To do so, **make sure you have both ``en_EN.utf8`` and
$ LC_ALL=en_US.utf8 pelican -o pelican/tests/output/basic/ \
samples/content/
Testing on Python 2 and 3
-------------------------
You may also find that some tests are skipped because some dependency (e.g.,
Pandoc) is not installed. This does not automatically mean that these tests
have passed; you should at least verify that any skipped tests are not
affected by your changes.
Testing on Python 3 currently requires some extra steps: installing
Python 3-compatible versions of dependent packages and plugins.
You should run the test suite under each of the supported versions of Python.
This is best done by creating a separate Python environment for each version.
Tox_ is a useful tool to automate running tests inside ``virtualenv``
environments.
Tox_ is a useful tool to run tests on both versions. It will install the
Python 3-compatible version of dependent packages.
.. _Tox: https://tox.readthedocs.io/en/latest/
.. _Tox: http://testrun.org/tox/latest/
Python 2/3 compatibility development tips
=========================================
Python 3 development tips
=========================
Here are some tips that may be useful for writing code that is compatible with
both Python 2.7 and Python 3:
Here are some tips that may be useful when doing some code for both Python 2.7
and Python 3 at the same time:
- Use new syntax. For example:
- Assume every string and literal is unicode (import unicode_literals):
- ``print .. -> print(..)``
- ``except .., e -> except .. as e``
- Do not use prefix ``u'``.
- Do not encode/decode strings in the middle of sth. Follow the code to the
source (or target) of a string and encode/decode at the first/last possible
point.
- In other words, write your functions to expect and to return unicode.
- Encode/decode strings if e.g. the source is a Python function that is known
to handle this badly, e.g. strftime() in Python 2.
- Use new methods. For example:
- Use new syntax: print function, "except ... *as* e" (not comma), etc.
- Refactor method calls like ``dict.iteritems()``, ``xrange()``, etc. in a way
that runs without code change in both Python versions.
- Do not use magic method ``__unicode()__`` in new classes. Use only ``__str()__``
and decorate the class with ``@python_2_unicode_compatible``.
- Do not start int literals with a zero. This is a syntax error in Py3k.
- Unfortunately I did not find an octal notation that is valid in both
Pythons. Use decimal instead.
- use six, e.g.:
- ``dict.iteritems() -> dict.items()``
- ``xrange(..) - > list(range(..))``
- Use ``six`` where necessary. For example:
- ``isinstance(.., basestring) -> isinstance(.., six.string_types)``
- ``isinstance(.., unicode) -> isinstance(.., six.text_type)``
- ``setlocale()`` in Python 2 bails when we give the locale name as unicode,
- Assume every string and literal is Unicode:
- Use ``from __future__ import unicode_literals``
- Do not use the prefix ``u'`` before strings.
- Do not encode/decode strings in the middle of something. Follow the code to
the source/target of a string and encode/decode at the first/last possible
point.
- In particular, write your functions to expect and to return Unicode.
- Encode/decode strings if the string is the output of a Python function that
is known to handle this badly. For example, ``strftime()`` in Python 2.
- Do not use the magic method ``__unicode()__`` in new classes. Use only
``__str()__`` and decorate the class with ``@python_2_unicode_compatible``.
- ``setlocale()`` in Python 2 fails when we give the locale name as Unicode,
and since we are using ``from __future__ import unicode_literals``, we do
that everywhere! As a workaround, I enclosed the localename with ``str()``;
in Python 2 this casts the name to a byte string, in Python 3 this should do
nothing, because the locale name already had been unicode.
- Kept range() almost everywhere as-is (2to3 suggests list(range())), just
changed it where I felt necessary.
- Changed xrange() back to range(), so it is valid in both Python versions.
that everywhere! As a workaround, enclose the locale name with ``str()``;
in Python 2 this casts the name to a byte string, while in Python 3 this
should do nothing, because the locale name was already Unicode.
- Do not start integer literals with a zero. This is a syntax error in Python 3.
- Unfortunately there seems to be no octal notation that is valid in both
Python 2 and 3. Use decimal notation instead.
Logging tips

View file

@ -9,10 +9,11 @@ Description
``pelican-import`` is a command-line tool for converting articles from other
software to reStructuredText or Markdown. The supported import formats are:
- WordPress XML export
- Blogger XML export
- Dotclear export
- Posterous API
- Tumblr API
- WordPress XML export
- RSS/Atom feed
The conversion from HTML to reStructuredText or Markdown relies on `Pandoc`_.
@ -40,9 +41,9 @@ Usage
::
pelican-import [-h] [--wpfile] [--dotclear] [--posterous] [--tumblr] [--feed] [-o OUTPUT]
[-m MARKUP] [--dir-cat] [--dir-page] [--strip-raw] [--disable-slugs]
[-e EMAIL] [-p PASSWORD] [-b BLOGNAME]
pelican-import [-h] [--blogger] [--dotclear] [--posterous] [--tumblr] [--wpfile] [--feed]
[-o OUTPUT] [-m MARKUP] [--dir-cat] [--dir-page] [--strip-raw] [--wp-custpost]
[--wp-attach] [--disable-slugs] [-e EMAIL] [-p PASSWORD] [-b BLOGNAME]
input|api_token|api_key
Positional arguments
@ -57,10 +58,11 @@ Optional arguments
------------------
-h, --help Show this help message and exit
--wpfile WordPress XML export (default: False)
--blogger Blogger XML export (default: False)
--dotclear Dotclear export (default: False)
--posterous Posterous API (default: False)
--tumblr Tumblr API (default: False)
--wpfile WordPress XML export (default: False)
--feed Feed to parse (default: False)
-o OUTPUT, --output OUTPUT
Output path (default: content)
@ -70,11 +72,24 @@ Optional arguments
--dir-cat Put files in directories with categories name
(default: False)
--dir-page Put files recognised as pages in "pages/" sub-
directory (wordpress import only) (default: False)
--filter-author Import only post from the specified author.
directory (blogger and wordpress import only)
(default: False)
--filter-author Import only post from the specified author
--strip-raw Strip raw HTML code that can't be converted to markup
such as flash embeds or iframes (wordpress import
only) (default: False)
--wp-custpost Put wordpress custom post types in directories. If
used with --dir-cat option directories will be created
as "/post_type/category/" (wordpress import only)
--wp-attach Download files uploaded to wordpress as attachments.
Files will be added to posts as a list in the post
header and links to the files within the post will be
updated. All files will be downloaded, even if they
aren't associated with a post. Files will be downloaded
with their original path inside the output directory,
e.g. "output/wp-uploads/date/postname/file.jpg".
(wordpress import only) (requires an internet
connection)
--disable-slugs Disable storing slugs from imported posts within
output. With this disabled, your Pelican URLs may not
be consistent with your original posts. (default:
@ -90,9 +105,9 @@ Optional arguments
Examples
========
For WordPress::
For Blogger::
$ pelican-import --wpfile -o ~/output ~/posts.xml
$ pelican-import --blogger -o ~/output ~/posts.xml
For Dotclear::
@ -106,10 +121,14 @@ For Tumblr::
$ pelican-import --tumblr -o ~/output --blogname=<blogname> <api_token>
For WordPress::
$ pelican-import --wpfile -o ~/output ~/posts.xml
Tests
=====
To test the module, one can use sample files:
- for WordPress: http://wpcandy.com/made/the-sample-post-collection
- for WordPress: http://www.wpbeginner.com/wp-themes/how-to-add-dummy-content-for-theme-development-in-wordpress/
- for Dotclear: http://media.dotaddict.org/tda/downloads/lorem-backup.txt

View file

@ -101,10 +101,9 @@ can optionally add yourself if you plan to create non-chronological content)::
yourproject/
├── content
   └── (pages)
└── (pages)
├── output
├── develop_server.sh
├── fabfile.py
├── tasks.py
├── Makefile
├── pelicanconf.py # Main settings file
└── publishconf.py # Settings to use when ready to publish

View file

@ -108,6 +108,7 @@ page_generator_preread page_generator invoked befor
use if code needs to do something before every page is parsed.
page_generator_init page_generator invoked in the PagesGenerator.__init__
page_generator_finalized page_generator invoked at the end of PagesGenerator.generate_context
page_generator_write_page page_generator, content invoked before writing each page, the page is passed as content
page_writer_finalized page_generator, writer invoked after all pages have been written, but before the page generator
is closed.
static_generator_context static_generator, metadata
@ -215,6 +216,7 @@ Adding a new generator is also really easy. You might want to have a look at
# define a new generator here if you need to
return MyGenerator
signals.get_generators.connect(get_generators)
def register():
signals.get_generators.connect(get_generators)
.. _pelican-plugins bug #314: https://github.com/getpelican/pelican-plugins/issues/314

View file

@ -25,8 +25,8 @@ argument, like so::
pelican --write-selected output/posts/my-post-title.html
Note that you must specify the path to the generated *output* file — not the
source content. To determine the output file path, use the ``--debug`` flag to
determine the correct file name and location. If desired, ``--write-selected``
source content. To determine the output file name and location, use the
``--debug`` flag. If desired, ``--write-selected``
can take a comma-separated list of paths or can be configured as a setting.
(See: :ref:`writing_only_selected_content`)
@ -101,7 +101,7 @@ While the ``pelican`` command is the canonical way to generate your site,
automation tools can be used to streamline the generation and publication
flow. One of the questions asked during the ``pelican-quickstart`` process
pertains to whether you want to automate site generation and publication.
If you answered "yes" to that question, a ``fabfile.py`` and
If you answered "yes" to that question, a ``tasks.py`` and
``Makefile`` will be generated in the root of your project. These files,
pre-populated with certain information gleaned from other answers provided
during the ``pelican-quickstart`` process, are meant as a starting point and
@ -113,55 +113,43 @@ files can deleted at any time and will not affect usage of the canonical
Following are automation tools that "wrap" the ``pelican`` command and can
simplify the process of generating, previewing, and uploading your site.
Fabric
Invoke
------
The advantage of Fabric_ is that it is written in Python and thus can be used
The advantage of Invoke_ is that it is written in Python and thus can be used
in a wide range of environments. The downside is that it must be installed
separately. Use the following command to install Fabric, prefixing with
separately. Use the following command to install Invoke, prefixing with
``sudo`` if your environment requires it::
pip install Fabric
pip install invoke
.. note:: Installing PyCrypto on Windows
Fabric depends upon PyCrypto_, which is tricky to install
if your system doesn't have a C compiler.
For Windows users, before installing Fabric, use
``easy_install http://www.voidspace.org.uk/downloads/pycrypto26/pycrypto-2.6.win32-py2.7.exe``
per this `StackOverflow suggestion <http://stackoverflow.com/a/11405769/6364>`_
You're more likely to have success
with the Win32 versions of Python 2.7 and PyCrypto,
than with the Win64—\
even if your operating system is a 64-bit version of Windows.
Take a moment to open the ``fabfile.py`` file that was generated in your
Take a moment to open the ``tasks.py`` file that was generated in your
project root. You will see a number of commands, any one of which can be
renamed, removed, and/or customized to your liking. Using the out-of-the-box
configuration, you can generate your site via::
fab build
invoke build
If you'd prefer to have Pelican automatically regenerate your site every time a
change is detected (which is handy when testing locally), use the following
command instead::
fab regenerate
invoke regenerate
To serve the generated site so it can be previewed in your browser at
http://localhost:8000/::
fab serve
invoke serve
If during the ``pelican-quickstart`` process you answered "yes" when asked
whether you want to upload your site via SSH, you can use the following command
to publish your site via rsync over SSH::
fab publish
invoke publish
These are just a few of the commands available by default, so feel free to
explore ``fabfile.py`` and see what other commands are available. More
importantly, don't hesitate to customize ``fabfile.py`` to suit your specific
explore ``tasks.py`` and see what other commands are available. More
importantly, don't hesitate to customize ``tasks.py`` to suit your specific
needs and preferences.
Make
@ -201,10 +189,7 @@ separate terminal sessions, but you can run both at once via::
make devserver
The above command will simultaneously run Pelican in regeneration mode as well
as serve the output at http://localhost:8000. Once you are done testing your
changes, you should stop the development server via::
./develop_server.sh stop
as serve the output at http://localhost:8000.
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
@ -219,5 +204,4 @@ That's it! Your site should now be live.
executables, such as ``python3``, you can set the ``PY`` and ``PELICAN``
environment variables, respectively, to override the default executable names.)
.. _Fabric: http://fabfile.org/
.. _PyCrypto: http://pycrypto.org
.. _Invoke: http://www.pyinvoke.org

View file

@ -61,11 +61,10 @@ ignored for now.)
Preview your site
-----------------
Open a new terminal session and run the following commands to switch to your
``output`` directory and launch Pelican's web server::
Open a new terminal session, navigate to your site directory and run the
following command to launch Pelican's web server::
cd ~/projects/yoursite/output
python -m pelican.server
pelican --listen
Preview your site by navigating to http://localhost:8000/ in your browser.

View file

@ -21,6 +21,12 @@ Settings are configured in the form of a Python module (a file). There is an
<https://github.com/getpelican/pelican/raw/master/samples/pelican.conf.py>`_
available for reference.
To see a list of current settings in your environment, including both default
and any customized values, run the following command (append one or more
specific setting names as arguments to see values for those settings only)::
pelican --print-settings
All the setting identifiers must be set in all-caps, otherwise they will not be
processed. Setting values that are numbers (5, 20, etc.), booleans (True,
False, None, etc.), dictionaries, or tuples should *not* be enclosed in
@ -28,10 +34,9 @@ quotation marks. All other values (i.e., strings) *must* be enclosed in
quotation marks.
Unless otherwise specified, settings that refer to paths can be either absolute
or relative to the configuration file.
The settings you define in the configuration file will be passed to the
templates, which allows you to use your settings to add site-wide content.
or relative to the configuration file. The settings you define in the
configuration file will be passed to the templates, which allows you to use your
settings to add site-wide content.
Here is a list of settings for Pelican:
@ -131,7 +136,7 @@ Basic settings
Extra configuration settings for the Markdown processor. Refer to the Python
Markdown documentation's `Options section
<http://pythonhosted.org/Markdown/reference.html#markdown>`_ for a complete
<https://python-markdown.github.io/reference/#markdown>`_ for a complete
list of supported options. The ``extensions`` option will be automatically
computed from the ``extension_configs`` option.
@ -328,6 +333,15 @@ Basic settings
A list of metadata fields containing reST/Markdown content to be parsed and
translated to HTML.
.. data:: PORT = 8000
The TCP port to serve content from the output folder via HTTP when pelican
is run with --listen
.. data:: BIND = ''
The IP to which to bind the HTTP server.
URL settings
============
@ -436,6 +450,24 @@ respectively.
The location we will save the page which doesn't use the default language.
.. data:: DRAFT_PAGE_URL = 'drafts/pages/{slug}.html'
The URL used to link to a page draft.
.. data:: DRAFT_PAGE_SAVE_AS = 'drafts/pages/{slug}.html'
The actual location a page draft is saved at.
.. data:: DRAFT_PAGE_LANG_URL = 'drafts/pages/{slug}-{lang}.html'
The URL used to link to a page draft which doesn't use the default
language.
.. data:: DRAFT_PAGE_LANG_SAVE_AS = 'drafts/pages/{slug}-{lang}.html'
The actual location a page draft which doesn't use the default language is
saved at.
.. data:: CATEGORY_URL = 'category/{slug}.html'
The URL to use for a category.
@ -464,34 +496,28 @@ respectively.
The location to save per-year archives of your posts.
.. data:: YEAR_ARCHIVE_URL = ''
The URL to use for per-year archives of your posts. Used only if you have
the ``{url}`` placeholder in ``PAGINATION_PATTERNS``.
.. data:: MONTH_ARCHIVE_SAVE_AS = ''
The location to save per-month archives of your posts.
.. data:: MONTH_ARCHIVE_URL = ''
The URL to use for per-month archives of your posts. Used only if you have
the ``{url}`` placeholder in ``PAGINATION_PATTERNS``.
.. data:: DAY_ARCHIVE_SAVE_AS = ''
The location to save per-day archives of your posts.
.. data:: SLUG_SUBSTITUTIONS = ()
.. data:: DAY_ARCHIVE_URL = ''
Substitutions to make prior to stripping out non-alphanumerics when
generating slugs. Specified as a list of 3-tuples of ``(from, to, skip)``
which are applied in order. ``skip`` is a boolean indicating whether or not
to skip replacement of non-alphanumeric characters. Useful for backward
compatibility with existing URLs.
.. data:: AUTHOR_SUBSTITUTIONS = ()
Substitutions for authors. ``SLUG_SUBSTITUTIONS`` is not taken into account
here!
.. data:: CATEGORY_SUBSTITUTIONS = ()
Added to ``SLUG_SUBSTITUTIONS`` for categories.
.. data:: TAG_SUBSTITUTIONS = ()
Added to ``SLUG_SUBSTITUTIONS`` for tags.
The URL to use for per-day archives of your posts. Used only if you have the
``{url}`` placeholder in ``PAGINATION_PATTERNS``.
.. note::
@ -500,24 +526,6 @@ respectively.
set the corresponding ``*_SAVE_AS`` setting to ``''`` to prevent the
relevant page from being generated.
.. note::
Substitutions are applied in order with the side effect that keeping
non-alphanum characters applies to the whole string when a replacement
is made.
For example if you have the following setting::
SLUG_SUBSTITUTIONS = (('C++', 'cpp'), ('keep dot', 'keep.dot', True))
the string ``Keep Dot`` will be converted to ``keep.dot``, however
``C++ will keep dot`` will be converted to ``cpp will keep.dot`` instead
of ``cpp-will-keep.dot``!
If you want to keep non-alphanum characters only for tags or categories
but not other slugs then configure ``TAG_SUBSTITUTIONS`` and
``CATEGORY_SUBSTITUTIONS`` respectively!
Pelican can optionally create per-year, per-month, and per-day archives of your
posts. These secondary archives are disabled by default but are automatically
enabled if you supply format strings for their respective ``_SAVE_AS`` settings.
@ -579,6 +587,33 @@ URLs for direct template pages are theme-dependent. Some themes use
corresponding ``*_URL`` setting as string, while others hard-code them:
``'archives.html'``, ``'authors.html'``, ``'categories.html'``, ``'tags.html'``.
.. data:: SLUG_REGEX_SUBSTITUTIONS = [
(r'[^\w\s-]', ''), # remove non-alphabetical/whitespace/'-' chars
(r'(?u)\A\s*', ''), # strip leading whitespace
(r'(?u)\s*\Z', ''), # strip trailing whitespace
(r'[-\s]+', '-'), # reduce multiple whitespace or '-' to single '-'
]
Regex substitutions to make when generating slugs of articles and pages.
Specified as a list of pairs of ``(from, to)`` which are applied in order,
ignoring case. The default substitutions have the effect of removing
non-alphanumeric characters and converting internal whitespace to dashes.
Apart from these substitutions, slugs are always converted to lowercase
ascii characters and leading and trailing whitespace is stripped. Useful for
backward compatibility with existing URLs.
.. data:: AUTHOR_REGEX_SUBSTITUTIONS = SLUG_REGEX_SUBSTITUTIONS
Regex substitutions for author slugs. Defaults to ``SLUG_REGEX_SUBSTITUTIONS``.
.. data:: CATEGORY_REGEX_SUBSTITUTIONS = SLUG_REGEX_SUBSTITUTIONS
Regex substitutions for category slugs. Defaults to ``SLUG_REGEX_SUBSTITUTIONS``.
.. data:: TAG_REGEX_SUBSTITUTIONS = SLUG_REGEX_SUBSTITUTIONS
Regex substitutions for tag slugs. Defaults to ``SLUG_REGEX_SUBSTITUTIONS``.
Time and Date
=============
@ -699,6 +734,10 @@ Template pages
'src/resume.html': 'dest/resume.html',
'src/contact.html': 'dest/contact.html'}
.. data:: TEMPLATE_EXTENSION = ['.html']
The extensions to use when looking up template files from template names.
.. data:: DIRECT_TEMPLATES = ['index', 'categories', 'authors', 'archives']
List of templates that are used directly to render content. Typically direct
@ -706,15 +745,8 @@ Template pages
tags and category index pages). If the tag and category collections are not
needed, set ``DIRECT_TEMPLATES = ['index', 'archives']``
.. data:: PAGINATED_DIRECT_TEMPLATES = ['index']
Provides the direct templates that should be paginated.
.. data:: EXTRA_TEMPLATES_PATHS = []
A list of paths you want Jinja2 to search for templates. Can be used to
separate templates from the theme. Example: projects, resume, profile ...
These templates need to use ``DIRECT_TEMPLATES`` setting.
``DIRECT_TEMPLATES`` are searched for over paths maintained in
``THEME_TEMPLATES_OVERRIDES``.
Metadata
@ -811,46 +843,95 @@ the ``TAG_FEED_ATOM`` and ``TAG_FEED_RSS`` settings:
.. data:: FEED_ATOM = None, i.e. no Atom feed
Relative URL to output the Atom feed.
The location to save the Atom feed.
.. data:: FEED_ATOM_URL = None
Relative URL of the Atom feed. If not set, ``FEED_ATOM`` is used both for
save location and URL.
.. data:: FEED_RSS = None, i.e. no RSS
Relative URL to output the RSS feed.
The location to save the RSS feed.
.. data:: FEED_RSS_URL = None
Relative URL of the RSS feed. If not set, ``FEED_RSS`` is used both for save
location and URL.
.. data:: FEED_ALL_ATOM = 'feeds/all.atom.xml'
Relative URL to output the all-posts Atom feed: this feed will contain all
The location to save the all-posts Atom feed: this feed will contain all
posts regardless of their language.
.. data:: FEED_ALL_ATOM_URL = None
Relative URL of the all-posts Atom feed. If not set, ``FEED_ALL_ATOM`` is
used both for save location and URL.
.. data:: FEED_ALL_RSS = None, i.e. no all-posts RSS
Relative URL to output the all-posts RSS feed: this feed will contain all
The location to save the the all-posts RSS feed: this feed will contain all
posts regardless of their language.
.. data:: FEED_ALL_RSS_URL = None
Relative URL of the all-posts RSS feed. If not set, ``FEED_ALL_RSS`` is used
both for save location and URL.
.. data:: CATEGORY_FEED_ATOM = 'feeds/%s.atom.xml'
Where to put the category Atom feeds. [2]_
The location to save the category Atom feeds. [2]_
.. data:: CATEGORY_FEED_ATOM_URL = None
Relative URL of the category Atom feeds, including the ``%s`` placeholder.
[2]_ If not set, ``CATEGORY_FEED_ATOM`` is used both for save location and
URL.
.. data:: CATEGORY_FEED_RSS = None, i.e. no RSS
Where to put the category RSS feeds.
The location to save the category RSS feeds, including the ``%s``
placeholder. [2]_
.. data:: CATEGORY_FEED_RSS_URL = None
Relative URL of the category RSS feeds, including the ``%s`` placeholder.
[2]_ If not set, ``CATEGORY_FEED_RSS`` is used both for save location and
URL.
.. data:: AUTHOR_FEED_ATOM = 'feeds/%s.atom.xml'
Where to put the author Atom feeds. [2]_
The location to save the author Atom feeds. [2]_
.. data:: AUTHOR_FEED_ATOM_URL = None
Relative URL of the author Atom feeds, including the ``%s`` placeholder.
[2]_ If not set, ``AUTHOR_FEED_ATOM`` is used both for save location and
URL.
.. data:: AUTHOR_FEED_RSS = 'feeds/%s.rss.xml'
Where to put the author RSS feeds. [2]_
The location to save the author RSS feeds. [2]_
.. data:: AUTHOR_FEED_RSS_URL = None
Relative URL of the author RSS feeds, including the ``%s`` placeholder. [2]_
If not set, ``AUTHOR_FEED_RSS`` is used both for save location and URL.
.. data:: TAG_FEED_ATOM = None, i.e. no tag feed
Relative URL to output the tag Atom feed. It should be defined using a "%s"
match in the tag name.
The location to save the tag Atom feed, including the ``%s`` placeholder.
[2]_
.. data:: TAG_FEED_ATOM_URL = None
Relative URL of the tag Atom feed, including the ``%s`` placeholder. [2]_
.. data:: TAG_FEED_RSS = None, i.e. no RSS tag feed
Relative URL to output the tag RSS feed
Relative URL to output the tag RSS feed, including the ``%s`` placeholder.
If not set, ``TAG_FEED_RSS`` is used both for save location and URL.
.. data:: FEED_MAX_ITEMS
@ -865,7 +946,7 @@ the ``TAG_FEED_ATOM`` and ``TAG_FEED_RSS`` settings:
If you don't want to generate some or any of these feeds, set the above variables to ``None``.
.. [2] %s is the name of the category.
.. [2] ``%s`` is replaced by name of the category / author / tag.
FeedBurner
@ -908,7 +989,15 @@ You can use the following settings to configure the pagination.
The maximum number of articles to include on a page, not including orphans.
False to disable pagination.
.. data:: PAGINATION_PATTERNS
.. data:: PAGINATED_TEMPLATES = {'index': None, 'tag': None, 'category': None, 'author': None}
The templates to use pagination with, and the number of articles to include
on a page. If this value is ``None``, it defaults to ``DEFAULT_PAGINATION``.
.. data:: PAGINATION_PATTERNS = (
(1, '{name}{extension}', '{name}{extension}'),
(2, '{name}{number}{extension}', '{name}{number}{extension}'),
)
A set of patterns that are used to determine advanced pagination output.
@ -916,25 +1005,28 @@ You can use the following settings to configure the pagination.
Using Pagination Patterns
-------------------------
The ``PAGINATION_PATTERNS`` setting can be used to configure where
subsequent pages are created. The setting is a sequence of three
element tuples, where each tuple consists of::
By default, pages subsequent to ``.../foo.html`` are created as
``.../foo2.html``, etc. The ``PAGINATION_PATTERNS`` setting can be used to
change this. It takes a sequence of triples, where each triple consists of::
(minimum page, URL setting, SAVE_AS setting,)
(minimum_page, page_url, page_save_as,)
For example, if you wanted the first page to just be ``/``, and the
second (and subsequent) pages to be ``/page/2/``, you would set
``PAGINATION_PATTERNS`` as follows::
For ``page_url`` and ``page_save_as``, you may use a number of variables.
``{url}`` and ``{save_as}`` correspond respectively to the ``*_URL`` and
``*_SAVE_AS`` values of the corresponding page type (e.g. ``ARTICLE_SAVE_AS``).
If ``{save_as} == foo/bar.html``, then ``{name} == foo/bar`` and
``{extension} == .html``. ``{base_name}`` equals ``{name}`` except that it
strips trailing ``/index`` if present. ``{number}`` equals the page number.
For example, if you want to leave the first page unchanged, but place
subsequent pages at ``.../page/2/`` etc, you could set ``PAGINATION_PATTERNS``
as follows::
PAGINATION_PATTERNS = (
(1, '{base_name}/', '{base_name}/index.html'),
(1, '{url}', '{save_as}`,
(2, '{base_name}/page/{number}/', '{base_name}/page/{number}/index.html'),
)
This would cause the first page to be written to
``{base_name}/index.html``, and subsequent ones would be written into
``page/{number}`` directories.
Translations
============
@ -946,14 +1038,38 @@ more information.
The default language to use.
.. data:: ARTICLE_TRANSLATION_ID = 'slug'
The metadata attribute(s) used to identify which articles are translations
of one another. May be a string or a collection of strings. Set to ``None``
or ``False`` to disable the identification of translations.
.. data:: PAGE_TRANSLATION_ID = 'slug'
The metadata attribute(s) used to identify which pages are translations
of one another. May be a string or a collection of strings. Set to ``None``
or ``False`` to disable the identification of translations.
.. data:: TRANSLATION_FEED_ATOM = 'feeds/all-%s.atom.xml'
Where to put the Atom feed for translations. [3]_
The location to save the Atom feed for translations. [3]_
.. data:: TRANSLATION_FEED_ATOM_URL = None
Relative URL of the Atom feed for translations, including the ``%s``
placeholder. [3]_ If not set, ``TRANSLATION_FEED_ATOM`` is used both for
save location and URL.
.. data:: TRANSLATION_FEED_RSS = None, i.e. no RSS
Where to put the RSS feed for translations.
.. data:: TRANSLATION_FEED_RSS_URL = None
Relative URL of the RSS feed for translations, including the ``%s``
placeholder. [3]_ If not set, ``TRANSLATION_FEED_RSS`` is used both for save
location and URL.
.. [3] %s is the language
@ -981,7 +1097,7 @@ Ordering content
.. data:: PAGE_ORDER_BY = 'basename'
Defines how the pages (``PAGES`` variable in the template) are sorted.
Defines how the pages (``pages`` variable in the template) are sorted.
Options are same as ``ARTICLE_ORDER_BY``. The default value, ``'basename'``
will sort pages by their basename.
@ -1011,6 +1127,21 @@ However, here are the settings that are related to themes.
with the same names are included in the paths defined in this settings, they
will be progressively overwritten.
.. data:: THEME_TEMPLATES_OVERRIDES = []
A list of paths you want Jinja2 to search for templates before searching the
theme's ``templates/`` directory. Allows for overriding individual theme
template files without having to fork an existing theme. Jinja2 searches in
the following order: files in ``THEME_TEMPLATES_OVERRIDES`` first, then the
theme's ``templates/``.
You can also extend templates from the theme using the ``{% extends %}``
directive utilizing the ``!theme`` prefix as shown in the following example:
.. parsed-literal::
{% extends '!theme/article.html' %}
.. data:: CSS_FILE = 'main.css'
Specify the CSS file you want to load.
@ -1130,15 +1261,17 @@ Simply populate the list with the log messages you want to hide, and they will
be filtered out.
For example::
[(logging.WARN, 'TAG_SAVE_AS is set to False')]
import logging
LOG_FILTER = [(logging.WARN, 'TAG_SAVE_AS is set to False')]
It is possible to filter out messages by a template. Check out source code to
obtain a template.
For example::
[(logging.WARN, 'Empty alt attribute for image %s in %s')]
import logging
LOG_FILTER = [(logging.WARN, 'Empty alt attribute for image %s in %s')]
.. Warning::
Silencing messages by templates is a dangerous feature. It is possible to

View file

@ -86,6 +86,7 @@ categories A list of (category, articles) tuples, containing
all the categories and corresponding articles (values)
pages The list of pages
hidden_pages The list of hidden pages
draft_pages The list of draft pages
============= ===================================================
@ -154,8 +155,8 @@ author.html
This template will be processed for each of the existing authors, with
output generated according to the ``AUTHOR_SAVE_AS`` setting (`Default:`
``author/{author_name}.html``). If pagination is active, subsequent pages will by
default reside at ``author/{author_name}{number}.html``.
``author/{slug}.html``). If pagination is active, subsequent pages will by
default reside at ``author/{slug}{number}.html``.
====================== ===================================================
Variable Description
@ -188,8 +189,8 @@ category.html
This template will be processed for each of the existing categories, with
output generated according to the ``CATEGORY_SAVE_AS`` setting (`Default:`
``category/{category_name}.html``). If pagination is active, subsequent pages will by
default reside at ``category/{category_name}{number}.html``.
``category/{slug}.html``). If pagination is active, subsequent pages will by
default reside at ``category/{slug}{number}.html``.
====================== ===================================================
Variable Description
@ -222,7 +223,7 @@ article.html
This template will be processed for each article, with
output generated according to the ``ARTICLE_SAVE_AS`` setting (`Default:`
``{article_name}.html``). The following variables are available when
``{slug}.html``). The following variables are available when
rendering.
============= ===================================================
@ -263,7 +264,7 @@ page.html
This template will be processed for each page, with
output generated according to the ``PAGE_SAVE_AS`` setting (`Default:`
``pages/{page_name}.html``). The following variables are available when
``pages/{slug}.html``). The following variables are available when
rendering.
============= ===================================================
@ -279,8 +280,8 @@ tag.html
This template will be processed for each tag, with
output generated according to the ``TAG_SAVE_AS`` setting (`Default:`
``tag/{tag_name}.html``). If pagination is active, subsequent pages will by
default reside at ``tag/{tag_name}{number}.html``.
``tag/{slug}.html``). If pagination is active, subsequent pages will by
default reside at ``tag/{slug}{number}.html``.
====================== ===================================================
Variable Description
@ -423,7 +424,7 @@ metadata Page header metadata `dict`.
save_as Location to save the page.
slug Page slug.
source_path Full system path of the page source file.
status The page status, can be any of 'published' or
status The page status, can be any of 'published', 'hidden' or
'draft'.
summary Rendered summary content.
tags List of :ref:`Tag <object-author_cat_tag>`

View file

@ -58,7 +58,7 @@ repository, and if you want to publish that Pelican site in the form of Project
Pages to this repository, you can then use the following::
$ pelican content -o output -s pelicanconf.py
$ ghp-import output
$ ghp-import output -b gh-pages
$ git push origin gh-pages
The ``ghp-import output`` command updates the local ``gh-pages`` branch with
@ -67,7 +67,7 @@ already exist). The ``git push origin gh-pages`` command updates the remote
``gh-pages`` branch, effectively publishing the Pelican site.
.. note::
The ``github`` target of the Makefile (and the ``gh_pages`` task of the Fabfile)
The ``github`` target of the Makefile (and the ``gh_pages`` task of ``tasks.py``)
created by the ``pelican-quickstart`` command
publishes the Pelican site as Project Pages, as described above.
@ -87,7 +87,7 @@ your ``<username>.github.io`` repository on GitHub.
Again, you can take advantage of ``ghp-import``::
$ pelican content -o output -s pelicanconf.py
$ ghp-import output
$ ghp-import output -b gh-pages
$ git push git@github.com:elemoine/elemoine.github.io.git gh-pages:master
The ``git push`` command pushes the local ``gh-pages`` branch (freshly updated
@ -98,6 +98,18 @@ by the ``ghp-import`` command) to the ``elemoine.github.io`` repository's
To publish your Pelican site as User Pages, feel free to adjust the
``github`` target of the Makefile.
Another option for publishing to User Pages is to generate the output files in the root directory of the project.
For example, your main project folder is ``<username>.github.io`` and you can create the Pelican project in a subdirectory called ``Pelican``. Then from inside the ``Pelican`` folder you can run::
$ pelican content -o .. -s pelicanconf.py
Now you can push the whole project ``<username>.github.io`` to the master branch of your GitHub repository::
$ git push origin master
(assuming origin is set to your remote repository).
Custom 404 Pages
----------------
@ -147,3 +159,25 @@ embed videos in the markup. You can use `reST video directive
<https://gist.github.com/dbrgn/2922648>`_ for reST or `mdx_video plugin
<https://github.com/italomaia/mdx-video>`_ for Markdown.
Develop Locally Using SSL
==================================
Here's how you can set up your local pelican server to support SSL.
First, create a self-signed certificate and key using ``openssl`` (this creates ``cert.pem`` and ``key.pem``)::
$ openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
And use this command to launch the server (the server starts within your ``output`` directory)::
python -m pelican.server 8443 --key=../key.pem --cert=../cert.pem
If you are using ``develop-server.sh``, add this to the top::
CERT="$BASEDIR/cert.pem"
KEY="$BASEDIR/key.pem"
and modify the ``pelican.server`` line as follows::
$PY -m pelican.server $port --ssl --cert="$CERT" --key="$KEY" &

View file

@ -5,21 +5,25 @@ import argparse
import collections
import locale
import logging
import multiprocessing
import os
import pprint
import re
import sys
import time
import traceback
import six
# pelican.log has to be the first pelican module to be loaded
# because logging.setLoggerClass has to be called before logging.getLogger
from pelican.log import init
from pelican.log import init as init_logging
from pelican import signals # noqa
from pelican.generators import (ArticlesGenerator, PagesGenerator,
SourceFileGenerator, StaticGenerator,
TemplatePagesGenerator)
from pelican.readers import Readers
from pelican.server import ComplexHTTPRequestHandler, RootedHTTPServer
from pelican.settings import read_settings
from pelican.utils import (clean_output_dir, file_watcher,
folder_watcher, maybe_pluralize)
@ -170,6 +174,10 @@ class Pelican(object):
if hasattr(p, 'generate_context'):
p.generate_context()
for p in generators:
if hasattr(p, 'refresh_metadata_intersite_links'):
p.refresh_metadata_intersite_links()
signals.all_generators_finalized.send(generators)
writer = self.get_writer()
@ -205,13 +213,20 @@ class Pelican(object):
len(pages_generator.hidden_translations)),
'hidden page',
'hidden pages')
pluralized_draft_pages = maybe_pluralize(
(len(pages_generator.draft_pages) +
len(pages_generator.draft_translations)),
'draft page',
'draft pages')
print('Done: Processed {}, {}, {} and {} in {:.2f} seconds.'.format(
pluralized_articles,
pluralized_drafts,
pluralized_pages,
pluralized_hidden_pages,
time.time() - start_time))
print('Done: Processed {}, {}, {}, {} and {} in {:.2f} seconds.'
.format(
pluralized_articles,
pluralized_drafts,
pluralized_pages,
pluralized_hidden_pages,
pluralized_draft_pages,
time.time() - start_time))
def get_generator_classes(self):
generators = [ArticlesGenerator, PagesGenerator]
@ -255,6 +270,32 @@ class Pelican(object):
return writer(self.output_path, settings=self.settings)
class PrintSettings(argparse.Action):
def __call__(self, parser, namespace, values, option_string):
instance, settings = get_instance(namespace)
if values:
# One or more arguments provided, so only print those settings
for setting in values:
if setting in settings:
# Only add newline between setting name and value if dict
if isinstance(settings[setting], dict):
setting_format = '\n{}:\n{}'
else:
setting_format = '\n{}: {}'
print(setting_format.format(
setting,
pprint.pformat(settings[setting])))
else:
print('\n{} is not a recognized setting.'.format(setting))
break
else:
# No argument was given to --print-settings, so print all settings
pprint.pprint(settings)
parser.exit()
def parse_arguments():
parser = argparse.ArgumentParser(
description='A tool to generate a static blog, '
@ -305,6 +346,12 @@ def parse_arguments():
help='Relaunch pelican each time a modification occurs'
' on the content files.')
parser.add_argument('--print-settings', dest='print_settings', nargs='*',
action=PrintSettings, metavar='SETTING_NAME',
help='Print current configuration settings and exit. '
'Append one or more setting name arguments to see the '
'values for specific settings only.')
parser.add_argument('--relative-urls', dest='relative_paths',
action='store_true',
help='Use relative urls in output, '
@ -327,7 +374,29 @@ def parse_arguments():
help=('Exit the program with non-zero status if any '
'errors/warnings encountered.'))
return parser.parse_args()
parser.add_argument('--logs-dedup-min-level', default='WARNING',
choices=('DEBUG', 'INFO', 'WARNING', 'ERROR'),
help=('Only enable log de-duplication for levels equal'
' to or above the specified value'))
parser.add_argument('-l', '--listen', dest='listen', action='store_true',
help='Serve content files via HTTP and port 8000.')
parser.add_argument('-p', '--port', dest='port', type=int,
help='Port to serve HTTP files at. (default: 8000)')
parser.add_argument('-b', '--bind', dest='bind',
help='IP to bind to when serving files via HTTP '
'(default: 127.0.0.1)')
args = parser.parse_args()
if args.port is not None and not args.listen:
logger.warning('--port without --listen has no effect')
if args.bind is not None and not args.listen:
logger.warning('--bind without --listen has no effect')
return args
def get_config(args):
@ -350,6 +419,10 @@ def get_config(args):
config['WRITE_SELECTED'] = args.selected_paths.split(',')
if args.relative_paths:
config['RELATIVE_URLS'] = args.relative_paths
if args.port is not None:
config['PORT'] = args.port
if args.bind is not None:
config['BIND'] = args.bind
config['DEBUG'] = args.verbosity == logging.DEBUG
# argparse returns bytes in Py2. There is no definite answer as to which
@ -382,16 +455,118 @@ def get_instance(args):
return cls(settings), settings
def autoreload(watchers, args, old_static, reader_descs, excqueue=None):
while True:
try:
# Check source dir for changed files ending with the given
# extension in the settings. In the theme dir is no such
# restriction; all files are recursively checked if they
# have changed, no matter what extension the filenames
# have.
modified = {k: next(v) for k, v in watchers.items()}
if modified['settings']:
pelican, settings = get_instance(args)
# Adjust static watchers if there are any changes
new_static = settings.get("STATIC_PATHS", [])
# Added static paths
# Add new watchers and set them as modified
new_watchers = set(new_static).difference(old_static)
for static_path in new_watchers:
static_key = '[static]%s' % static_path
watchers[static_key] = folder_watcher(
os.path.join(pelican.path, static_path),
[''],
pelican.ignore_files)
modified[static_key] = next(watchers[static_key])
# Removed static paths
# Remove watchers and modified values
old_watchers = set(old_static).difference(new_static)
for static_path in old_watchers:
static_key = '[static]%s' % static_path
watchers.pop(static_key)
modified.pop(static_key)
# Replace old_static with the new one
old_static = new_static
if any(modified.values()):
print('\n-> Modified: {}. re-generating...'.format(
', '.join(k for k, v in modified.items() if v)))
if modified['content'] is None:
logger.warning(
'No valid files found in content for '
+ 'the active readers:\n'
+ '\n'.join(reader_descs))
if modified['theme'] is None:
logger.warning('Empty theme folder. Using `basic` '
'theme.')
pelican.run()
except KeyboardInterrupt as e:
logger.warning("Keyboard interrupt, quitting.")
if excqueue is not None:
excqueue.put(traceback.format_exception_only(type(e), e)[-1])
return
except Exception as e:
if (args.verbosity == logging.DEBUG):
if excqueue is not None:
excqueue.put(
traceback.format_exception_only(type(e), e)[-1])
else:
raise
logger.warning(
'Caught exception "%s". Reloading.', e)
finally:
time.sleep(.5) # sleep to avoid cpu load
def listen(server, port, output, excqueue=None):
RootedHTTPServer.allow_reuse_address = True
try:
httpd = RootedHTTPServer(
output, (server, port), ComplexHTTPRequestHandler)
except OSError as e:
logging.error("Could not listen on port %s, server %s.", port, server)
if excqueue is not None:
excqueue.put(traceback.format_exception_only(type(e), e)[-1])
return
logging.info("Serving at port %s, server %s.", port, server)
try:
httpd.serve_forever()
except Exception as e:
if excqueue is not None:
excqueue.put(traceback.format_exception_only(type(e), e)[-1])
return
def main():
args = parse_arguments()
init(args.verbosity, args.fatal)
logs_dedup_min_level = getattr(logging, args.logs_dedup_min_level)
init_logging(args.verbosity, args.fatal,
logs_dedup_min_level=logs_dedup_min_level)
logger.debug('Pelican version: %s', __version__)
logger.debug('Python version: %s', sys.version.split()[0])
try:
pelican, settings = get_instance(args)
readers = Readers(settings)
reader_descs = sorted(set(['%s (%s)' %
(type(r).__name__,
', '.join(r.file_extensions))
for r in readers.readers.values()
if r.enabled]))
watchers = {'content': folder_watcher(pelican.path,
readers.extensions,
@ -410,76 +585,34 @@ def main():
[''],
pelican.ignore_files)
if args.autoreload:
if args.autoreload and args.listen:
excqueue = multiprocessing.Queue()
p1 = multiprocessing.Process(
target=autoreload,
args=(watchers, args, old_static, reader_descs, excqueue))
p2 = multiprocessing.Process(
target=listen,
args=(settings.get('BIND'), settings.get('PORT'),
settings.get("OUTPUT_PATH"), excqueue))
p1.start()
p2.start()
exc = excqueue.get()
p1.terminate()
p2.terminate()
logger.critical(exc)
elif args.autoreload:
print(' --- AutoReload Mode: Monitoring `content`, `theme` and'
' `settings` for changes. ---')
while True:
try:
# Check source dir for changed files ending with the given
# extension in the settings. In the theme dir is no such
# restriction; all files are recursively checked if they
# have changed, no matter what extension the filenames
# have.
modified = {k: next(v) for k, v in watchers.items()}
if modified['settings']:
pelican, settings = get_instance(args)
# Adjust static watchers if there are any changes
new_static = settings.get("STATIC_PATHS", [])
# Added static paths
# Add new watchers and set them as modified
new_watchers = set(new_static).difference(old_static)
for static_path in new_watchers:
static_key = '[static]%s' % static_path
watchers[static_key] = folder_watcher(
os.path.join(pelican.path, static_path),
[''],
pelican.ignore_files)
modified[static_key] = next(watchers[static_key])
# Removed static paths
# Remove watchers and modified values
old_watchers = set(old_static).difference(new_static)
for static_path in old_watchers:
static_key = '[static]%s' % static_path
watchers.pop(static_key)
modified.pop(static_key)
# Replace old_static with the new one
old_static = new_static
if any(modified.values()):
print('\n-> Modified: {}. re-generating...'.format(
', '.join(k for k, v in modified.items() if v)))
if modified['content'] is None:
logger.warning('No valid files found in content.')
if modified['theme'] is None:
logger.warning('Empty theme folder. Using `basic` '
'theme.')
pelican.run()
except KeyboardInterrupt:
logger.warning("Keyboard interrupt, quitting.")
break
except Exception as e:
if (args.verbosity == logging.DEBUG):
raise
logger.warning(
'Caught exception "%s". Reloading.', e)
finally:
time.sleep(.5) # sleep to avoid cpu load
autoreload(watchers, args, old_static, reader_descs)
elif args.listen:
listen(settings.get('BIND'), settings.get('PORT'),
settings.get("OUTPUT_PATH"))
else:
if next(watchers['content']) is None:
logger.warning('No valid files found in content.')
logger.warning(
'No valid files found in content for '
+ 'the active readers:\n'
+ '\n'.join(reader_descs))
if next(watchers['theme']) is None:
logger.warning('Empty theme folder. Using `basic` theme.')

View file

@ -11,7 +11,7 @@ import sys
import pytz
import six
from six.moves.urllib.parse import urlparse, urlunparse
from six.moves.urllib.parse import urljoin, urlparse, urlunparse
from pelican import signals
from pelican.settings import DEFAULT_CONFIG
@ -98,14 +98,16 @@ class Content(object):
if not hasattr(self, 'slug'):
if (settings['SLUGIFY_SOURCE'] == 'title' and
hasattr(self, 'title')):
self.slug = slugify(self.title,
settings.get('SLUG_SUBSTITUTIONS', ()))
self.slug = slugify(
self.title,
regex_subs=settings.get('SLUG_REGEX_SUBSTITUTIONS', []))
elif (settings['SLUGIFY_SOURCE'] == 'basename' and
source_path is not None):
basename = os.path.basename(
os.path.splitext(source_path)[0])
self.slug = slugify(
basename, settings.get('SLUG_SUBSTITUTIONS', ()))
basename,
regex_subs=settings.get('SLUG_REGEX_SUBSTITUTIONS', []))
self.source_path = source_path
@ -140,9 +142,8 @@ class Content(object):
if not hasattr(self, 'status'):
self.status = getattr(self, 'default_status', None)
# store the summary metadata if it is set
if 'summary' in metadata:
self._summary = metadata['summary']
if len(self._context.get('filenames', [])) > 0:
self.refresh_metadata_intersite_links()
signals.content_object_init.send(self)
@ -228,6 +229,87 @@ class Content(object):
key = key if self.in_default_lang else 'lang_%s' % key
return self._expand_settings(key)
def _link_replacer(self, siteurl, m):
what = m.group('what')
value = urlparse(m.group('value'))
path = value.path
origin = m.group('path')
# urllib.parse.urljoin() produces `a.html` for urljoin("..", "a.html")
# so if RELATIVE_URLS are enabled, we fall back to os.path.join() to
# properly get `../a.html`. However, os.path.join() produces
# `baz/http://foo/bar.html` for join("baz", "http://foo/bar.html")
# instead of correct "http://foo/bar.html", so one has to pick a side
# as there is no silver bullet.
if self.settings['RELATIVE_URLS']:
joiner = os.path.join
else:
joiner = urljoin
# However, it's not *that* simple: urljoin("blog", "index.html")
# produces just `index.html` instead of `blog/index.html` (unlike
# os.path.join()), so in order to get a correct answer one needs to
# append a trailing slash to siteurl in that case. This also makes
# the new behavior fully compatible with Pelican 3.7.1.
if not siteurl.endswith('/'):
siteurl += '/'
# XXX Put this in a different location.
if what in {'filename', 'attach'}:
if path.startswith('/'):
path = path[1:]
else:
# relative to the source path of this content
path = self.get_relative_source_path(
os.path.join(self.relative_dir, path)
)
if path not in self._context['filenames']:
unquoted_path = path.replace('%20', ' ')
if unquoted_path in self._context['filenames']:
path = unquoted_path
linked_content = self._context['filenames'].get(path)
if linked_content:
if what == 'attach':
if isinstance(linked_content, Static):
linked_content.attach_to(self)
else:
logger.warning(
"%s used {attach} link syntax on a "
"non-static file. Use {filename} instead.",
self.get_relative_source_path())
origin = joiner(siteurl, linked_content.url)
origin = origin.replace('\\', '/') # for Windows paths.
else:
logger.warning(
"Unable to find '%s', skipping url replacement.",
value.geturl(), extra={
'limit_msg': ("Other resources were not found "
"and their urls not replaced")})
elif what == 'category':
origin = joiner(siteurl, Category(path, self.settings).url)
elif what == 'tag':
origin = joiner(siteurl, Tag(path, self.settings).url)
elif what == 'index':
origin = joiner(siteurl, self.settings['INDEX_SAVE_AS'])
elif what == 'author':
origin = joiner(siteurl, Author(path, self.settings).url)
else:
logger.warning(
"Replacement Indicator '%s' not recognized, "
"skipping replacement",
what)
# keep all other parts, such as query, fragment, etc.
parts = list(value)
parts[2] = origin
origin = urlunparse(parts)
return ''.join((m.group('markup'), m.group('quote'), origin,
m.group('quote')))
def _update_content(self, content, siteurl):
"""Update the content attribute.
@ -251,69 +333,7 @@ class Content(object):
\2""".format(instrasite_link_regex)
hrefs = re.compile(regex, re.X)
def replacer(m):
what = m.group('what')
value = urlparse(m.group('value'))
path = value.path
origin = m.group('path')
# XXX Put this in a different location.
if what in {'filename', 'attach'}:
if path.startswith('/'):
path = path[1:]
else:
# relative to the source path of this content
path = self.get_relative_source_path(
os.path.join(self.relative_dir, path)
)
if path not in self._context['filenames']:
unquoted_path = path.replace('%20', ' ')
if unquoted_path in self._context['filenames']:
path = unquoted_path
linked_content = self._context['filenames'].get(path)
if linked_content:
if what == 'attach':
if isinstance(linked_content, Static):
linked_content.attach_to(self)
else:
logger.warning(
"%s used {attach} link syntax on a "
"non-static file. Use {filename} instead.",
self.get_relative_source_path())
origin = '/'.join((siteurl, linked_content.url))
origin = origin.replace('\\', '/') # for Windows paths.
else:
logger.warning(
"Unable to find '%s', skipping url replacement.",
value.geturl(), extra={
'limit_msg': ("Other resources were not found "
"and their urls not replaced")})
elif what == 'category':
origin = '/'.join((siteurl, Category(path, self.settings).url))
elif what == 'tag':
origin = '/'.join((siteurl, Tag(path, self.settings).url))
elif what == 'index':
origin = '/'.join((siteurl, self.settings['INDEX_SAVE_AS']))
elif what == 'author':
origin = '/'.join((siteurl, Author(path, self.settings).url))
else:
logger.warning(
"Replacement Indicator '%s' not recognized, "
"skipping replacement",
what)
# keep all other parts, such as query, fragment, etc.
parts = list(value)
parts[2] = origin
origin = urlunparse(parts)
return ''.join((m.group('markup'), m.group('quote'), origin,
m.group('quote')))
return hrefs.sub(replacer, content)
return hrefs.sub(lambda m: self._link_replacer(siteurl, m), content)
def get_siteurl(self):
return self._context.get('localsiteurl', '')
@ -337,8 +357,8 @@ class Content(object):
This is based on the summary metadata if set, otherwise truncate the
content.
"""
if hasattr(self, '_summary'):
return self._update_content(self._summary, siteurl)
if 'summary' in self.metadata:
return self.metadata['summary']
if self.settings['SUMMARY_MAX_LENGTH'] is None:
return self.content
@ -413,13 +433,27 @@ class Content(object):
os.path.abspath(self.source_path),
os.path.abspath(self.settings['PATH']))))
def refresh_metadata_intersite_links(self):
for key in self.settings['FORMATTED_FIELDS']:
if key in self.metadata:
value = self._update_content(
self.metadata[key],
self.get_siteurl()
)
self.metadata[key] = value
setattr(self, key.lower(), value)
class Page(Content):
mandatory_properties = ('title',)
allowed_statuses = ('published', 'hidden')
allowed_statuses = ('published', 'hidden', 'draft')
default_status = 'published'
default_template = 'page'
def _expand_settings(self, key):
klass = 'draft_page' if self.status == 'draft' else None
return super(Page, self)._expand_settings(key, klass)
class Article(Content):
mandatory_properties = ('title', 'date', 'category')
@ -444,7 +478,7 @@ class Article(Content):
self.date = SafeDatetime.max
def _expand_settings(self, key):
klass = 'article' if self.status == 'published' else 'draft'
klass = 'draft' if self.status == 'draft' else 'article'
return super(Article, self)._expand_settings(key, klass)

View file

@ -21,8 +21,9 @@ from pelican import signals
from pelican.cache import FileStampDataCacher
from pelican.contents import Article, Page, Static
from pelican.readers import Readers
from pelican.utils import (DateFormatter, copy, mkdir_p, posixize_path,
process_translations, python_2_unicode_compatible)
from pelican.utils import (DateFormatter, copy, mkdir_p, order_content,
posixize_path, process_translations,
python_2_unicode_compatible)
logger = logging.getLogger(__name__)
@ -51,20 +52,25 @@ class Generator(object):
# templates cache
self._templates = {}
self._templates_path = []
self._templates_path.append(os.path.expanduser(
os.path.join(self.theme, 'templates')))
self._templates_path += self.settings['EXTRA_TEMPLATES_PATHS']
self._templates_path = list(self.settings['THEME_TEMPLATES_OVERRIDES'])
theme_path = os.path.dirname(os.path.abspath(__file__))
theme_templates_path = os.path.expanduser(
os.path.join(self.theme, 'templates'))
self._templates_path.append(theme_templates_path)
theme_loader = FileSystemLoader(theme_templates_path)
simple_theme_path = os.path.dirname(os.path.abspath(__file__))
simple_loader = FileSystemLoader(
os.path.join(simple_theme_path, "themes", "simple", "templates"))
simple_loader = FileSystemLoader(os.path.join(theme_path,
"themes", "simple", "templates"))
self.env = Environment(
loader=ChoiceLoader([
FileSystemLoader(self._templates_path),
simple_loader, # implicit inheritance
PrefixLoader({'!simple': simple_loader}) # explicit one
PrefixLoader({
'!simple': simple_loader,
'!theme': theme_loader
}) # explicit ones
]),
**self.settings['JINJA_ENVIRONMENT']
)
@ -86,12 +92,19 @@ class Generator(object):
templates ready to use with Jinja2.
"""
if name not in self._templates:
try:
self._templates[name] = self.env.get_template(name + '.html')
except TemplateNotFound:
for ext in self.settings['TEMPLATE_EXTENSIONS']:
try:
self._templates[name] = self.env.get_template(name + ext)
break
except TemplateNotFound:
continue
if name not in self._templates:
raise PelicanTemplateNotFound(
'[templates] unable to load {}.html from {}'.format(
name, self._templates_path))
'[templates] unable to load {}[{}] from {}'.format(
name, ', '.join(self.settings['TEMPLATE_EXTENSIONS']),
self._templates_path))
return self._templates[name]
def _include_path(self, path, extensions=None):
@ -255,7 +268,7 @@ class TemplatePagesGenerator(Generator):
template = self.env.get_template(source)
rurls = self.settings['RELATIVE_URLS']
writer.write_file(dest, template, self.context, rurls,
override_output=True)
override_output=True, url='')
finally:
del self.env.loader.loaders[0]
@ -282,67 +295,100 @@ class ArticlesGenerator(CachingGenerator):
if self.settings.get('FEED_ATOM'):
writer.write_feed(self.articles, self.context,
self.settings['FEED_ATOM'])
self.settings['FEED_ATOM'],
self.settings.get('FEED_ATOM_URL',
self.settings['FEED_ATOM']))
if self.settings.get('FEED_RSS'):
writer.write_feed(self.articles, self.context,
self.settings['FEED_RSS'], feed_type='rss')
self.settings['FEED_RSS'],
self.settings.get('FEED_RSS_URL',
self.settings['FEED_RSS']),
feed_type='rss')
if (self.settings.get('FEED_ALL_ATOM') or
self.settings.get('FEED_ALL_RSS')):
all_articles = list(self.articles)
for article in self.articles:
all_articles.extend(article.translations)
all_articles.sort(key=attrgetter('date'), reverse=True)
order_content(all_articles,
order_by=self.settings['ARTICLE_ORDER_BY'])
if self.settings.get('FEED_ALL_ATOM'):
writer.write_feed(all_articles, self.context,
self.settings['FEED_ALL_ATOM'])
self.settings['FEED_ALL_ATOM'],
self.settings.get(
'FEED_ALL_ATOM_URL',
self.settings['FEED_ALL_ATOM']))
if self.settings.get('FEED_ALL_RSS'):
writer.write_feed(all_articles, self.context,
self.settings['FEED_ALL_RSS'],
self.settings.get(
'FEED_ALL_RSS_URL',
self.settings['FEED_ALL_RSS']),
feed_type='rss')
for cat, arts in self.categories:
arts.sort(key=attrgetter('date'), reverse=True)
if self.settings.get('CATEGORY_FEED_ATOM'):
writer.write_feed(arts, self.context,
self.settings['CATEGORY_FEED_ATOM']
% cat.slug,
self.settings.get(
'CATEGORY_FEED_ATOM_URL',
self.settings['CATEGORY_FEED_ATOM'])
% cat.slug, feed_title=cat.name)
if self.settings.get('CATEGORY_FEED_RSS'):
writer.write_feed(arts, self.context,
self.settings['CATEGORY_FEED_RSS']
% cat.slug,
self.settings.get(
'CATEGORY_FEED_RSS_URL',
self.settings['CATEGORY_FEED_RSS'])
% cat.slug, feed_title=cat.name,
feed_type='rss')
for auth, arts in self.authors:
arts.sort(key=attrgetter('date'), reverse=True)
if self.settings.get('AUTHOR_FEED_ATOM'):
writer.write_feed(arts, self.context,
self.settings['AUTHOR_FEED_ATOM']
% auth.slug,
self.settings.get(
'AUTHOR_FEED_ATOM_URL',
self.settings['AUTHOR_FEED_ATOM'])
% auth.slug, feed_title=auth.name)
if self.settings.get('AUTHOR_FEED_RSS'):
writer.write_feed(arts, self.context,
self.settings['AUTHOR_FEED_RSS']
% auth.slug,
self.settings.get(
'AUTHOR_FEED_RSS_URL',
self.settings['AUTHOR_FEED_RSS'])
% auth.slug, feed_title=auth.name,
feed_type='rss')
if (self.settings.get('TAG_FEED_ATOM') or
self.settings.get('TAG_FEED_RSS')):
for tag, arts in self.tags.items():
arts.sort(key=attrgetter('date'), reverse=True)
if self.settings.get('TAG_FEED_ATOM'):
writer.write_feed(arts, self.context,
self.settings['TAG_FEED_ATOM']
% tag.slug,
self.settings.get(
'TAG_FEED_ATOM_URL',
self.settings['TAG_FEED_ATOM'])
% tag.slug, feed_title=tag.name)
if self.settings.get('TAG_FEED_RSS'):
writer.write_feed(arts, self.context,
self.settings['TAG_FEED_RSS'] % tag.slug,
feed_title=tag.name, feed_type='rss')
self.settings.get(
'TAG_FEED_RSS_URL',
self.settings['TAG_FEED_RSS'])
% tag.slug, feed_title=tag.name,
feed_type='rss')
if (self.settings.get('TRANSLATION_FEED_ATOM') or
self.settings.get('TRANSLATION_FEED_RSS')):
@ -351,15 +397,22 @@ class ArticlesGenerator(CachingGenerator):
translations_feeds[article.lang].append(article)
for lang, items in translations_feeds.items():
items.sort(key=attrgetter('date'), reverse=True)
items = order_content(
items, order_by=self.settings['ARTICLE_ORDER_BY'])
if self.settings.get('TRANSLATION_FEED_ATOM'):
writer.write_feed(
items, self.context,
self.settings['TRANSLATION_FEED_ATOM'] % lang)
self.settings['TRANSLATION_FEED_ATOM'] % lang,
self.settings.get(
'TRANSLATION_FEED_ATOM_URL',
self.settings['TRANSLATION_FEED_ATOM']) % lang)
if self.settings.get('TRANSLATION_FEED_RSS'):
writer.write_feed(
items, self.context,
self.settings['TRANSLATION_FEED_RSS'] % lang,
self.settings.get(
'TRANSLATION_FEED_RSS_URL',
self.settings['TRANSLATION_FEED_RSS']) % lang,
feed_type='rss')
def generate_articles(self, write):
@ -369,7 +422,7 @@ class ArticlesGenerator(CachingGenerator):
write(article.save_as, self.get_template(article.template),
self.context, article=article, category=article.category,
override_output=hasattr(article, 'override_save_as'),
blog=True)
url=article.url, blog=True)
def generate_period_archives(self, write):
"""Generate per-year, per-month, and per-day archives."""
@ -384,24 +437,32 @@ class ArticlesGenerator(CachingGenerator):
'day': self.settings['DAY_ARCHIVE_SAVE_AS'],
}
period_url = {
'year': self.settings['YEAR_ARCHIVE_URL'],
'month': self.settings['MONTH_ARCHIVE_URL'],
'day': self.settings['DAY_ARCHIVE_URL'],
}
period_date_key = {
'year': attrgetter('date.year'),
'month': attrgetter('date.year', 'date.month'),
'day': attrgetter('date.year', 'date.month', 'date.day')
}
def _generate_period_archives(dates, key, save_as_fmt):
def _generate_period_archives(dates, key, save_as_fmt, url_fmt):
"""Generate period archives from `dates`, grouped by
`key` and written to `save_as`.
"""
# `dates` is already sorted by date
for _period, group in groupby(dates, key=key):
archive = list(group)
articles = [a for a in self.articles if a in archive]
# arbitrarily grab the first date so that the usual
# format string syntax can be used for specifying the
# period archive dates
date = archive[0].date
save_as = save_as_fmt.format(date=date)
url = url_fmt.format(date=date)
context = self.context.copy()
if key == period_date_key['year']:
@ -418,62 +479,60 @@ class ArticlesGenerator(CachingGenerator):
month_name,
_period[2])
write(save_as, template, context,
dates=archive, blog=True)
write(save_as, template, context, articles=articles,
dates=archive, template_name='period_archives',
blog=True, url=url)
for period in 'year', 'month', 'day':
save_as = period_save_as[period]
url = period_url[period]
if save_as:
key = period_date_key[period]
_generate_period_archives(self.dates, key, save_as)
_generate_period_archives(self.dates, key, save_as, url)
def generate_direct_templates(self, write):
"""Generate direct templates pages"""
PAGINATED_TEMPLATES = self.settings['PAGINATED_DIRECT_TEMPLATES']
for template in self.settings['DIRECT_TEMPLATES']:
paginated = {}
if template in PAGINATED_TEMPLATES:
paginated = {'articles': self.articles, 'dates': self.dates}
save_as = self.settings.get("%s_SAVE_AS" % template.upper(),
'%s.html' % template)
url = self.settings.get("%s_URL" % template.upper(),
'%s.html' % template)
if not save_as:
continue
write(save_as, self.get_template(template),
self.context, blog=True, paginated=paginated,
page_name=os.path.splitext(save_as)[0])
write(save_as, self.get_template(template), self.context,
articles=self.articles, dates=self.dates, blog=True,
template_name=template,
page_name=os.path.splitext(save_as)[0], url=url)
def generate_tags(self, write):
"""Generate Tags pages."""
tag_template = self.get_template('tag')
for tag, articles in self.tags.items():
articles.sort(key=attrgetter('date'), reverse=True)
dates = [article for article in self.dates if article in articles]
write(tag.save_as, tag_template, self.context, tag=tag,
articles=articles, dates=dates,
paginated={'articles': articles, 'dates': dates}, blog=True,
page_name=tag.page_name, all_articles=self.articles)
url=tag.url, articles=articles, dates=dates,
template_name='tag', blog=True, page_name=tag.page_name,
all_articles=self.articles)
def generate_categories(self, write):
"""Generate category pages."""
category_template = self.get_template('category')
for cat, articles in self.categories:
articles.sort(key=attrgetter('date'), reverse=True)
dates = [article for article in self.dates if article in articles]
write(cat.save_as, category_template, self.context,
write(cat.save_as, category_template, self.context, url=cat.url,
category=cat, articles=articles, dates=dates,
paginated={'articles': articles, 'dates': dates}, blog=True,
page_name=cat.page_name, all_articles=self.articles)
template_name='category', blog=True, page_name=cat.page_name,
all_articles=self.articles)
def generate_authors(self, write):
"""Generate Author pages."""
author_template = self.get_template('author')
for aut, articles in self.authors:
articles.sort(key=attrgetter('date'), reverse=True)
dates = [article for article in self.dates if article in articles]
write(aut.save_as, author_template, self.context,
author=aut, articles=articles, dates=dates,
paginated={'articles': articles, 'dates': dates}, blog=True,
url=aut.url, author=aut, articles=articles, dates=dates,
template_name='author', blog=True,
page_name=aut.page_name, all_articles=self.articles)
def generate_drafts(self, write):
@ -482,7 +541,7 @@ class ArticlesGenerator(CachingGenerator):
write(draft.save_as, self.get_template(draft.template),
self.context, article=draft, category=draft.category,
override_output=hasattr(draft, 'override_save_as'),
blog=True, all_articles=self.articles)
blog=True, all_articles=self.articles, url=draft.url)
def generate_pages(self, writer):
"""Generate the pages on the disk"""
@ -538,11 +597,14 @@ class ArticlesGenerator(CachingGenerator):
all_drafts.append(article)
self.add_source_path(article)
self.articles, self.translations = process_translations(
all_articles,
order_by=self.settings['ARTICLE_ORDER_BY'])
self.drafts, self.drafts_translations = \
process_translations(all_drafts)
def _process(arts):
origs, translations = process_translations(
arts, translation_id=self.settings['ARTICLE_TRANSLATION_ID'])
origs = order_content(origs, self.settings['ARTICLE_ORDER_BY'])
return origs, translations
self.articles, self.translations = _process(all_articles)
self.drafts, self.drafts_translations = _process(all_drafts)
signals.article_generator_pretaxonomy.send(self)
@ -581,20 +643,32 @@ class ArticlesGenerator(CachingGenerator):
self.generate_pages(writer)
signals.article_writer_finalized.send(self, writer=writer)
def refresh_metadata_intersite_links(self):
for e in chain(self.articles,
self.translations,
self.drafts,
self.drafts_translations):
if hasattr(e, 'refresh_metadata_intersite_links'):
e.refresh_metadata_intersite_links()
class PagesGenerator(CachingGenerator):
"""Generate pages"""
def __init__(self, *args, **kwargs):
self.pages = []
self.translations = []
self.hidden_pages = []
self.hidden_translations = []
self.draft_pages = []
self.draft_translations = []
super(PagesGenerator, self).__init__(*args, **kwargs)
signals.page_generator_init.send(self)
def generate_context(self):
all_pages = []
hidden_pages = []
draft_pages = []
for f in self.get_files(
self.settings['PAGE_PATHS'],
exclude=self.settings['PAGE_EXCLUDES']):
@ -625,15 +699,21 @@ class PagesGenerator(CachingGenerator):
all_pages.append(page)
elif page.status == "hidden":
hidden_pages.append(page)
elif page.status == "draft":
draft_pages.append(page)
self.add_source_path(page)
self.pages, self.translations = process_translations(
all_pages,
order_by=self.settings['PAGE_ORDER_BY'])
self.hidden_pages, self.hidden_translations = \
process_translations(hidden_pages)
def _process(pages):
origs, translations = process_translations(
pages, translation_id=self.settings['PAGE_TRANSLATION_ID'])
origs = order_content(origs, self.settings['PAGE_ORDER_BY'])
return origs, translations
self._update_context(('pages', 'hidden_pages'))
self.pages, self.translations = _process(all_pages)
self.hidden_pages, self.hidden_translations = _process(hidden_pages)
self.draft_pages, self.draft_translations = _process(draft_pages)
self._update_context(('pages', 'hidden_pages', 'draft_pages'))
self.save_cache()
self.readers.save_cache()
@ -641,14 +721,26 @@ class PagesGenerator(CachingGenerator):
def generate_output(self, writer):
for page in chain(self.translations, self.pages,
self.hidden_translations, self.hidden_pages):
self.hidden_translations, self.hidden_pages,
self.draft_translations, self.draft_pages):
signals.page_generator_write_page.send(self, content=page)
writer.write_file(
page.save_as, self.get_template(page.template),
self.context, page=page,
relative_urls=self.settings['RELATIVE_URLS'],
override_output=hasattr(page, 'override_save_as'))
override_output=hasattr(page, 'override_save_as'),
url=page.url)
signals.page_writer_finalized.send(self, writer=writer)
def refresh_metadata_intersite_links(self):
for e in chain(self.pages,
self.hidden_pages,
self.hidden_translations,
self.draft_pages,
self.draft_translations):
if hasattr(e, 'refresh_metadata_intersite_links'):
e.refresh_metadata_intersite_links()
class StaticGenerator(Generator):
"""copy static paths (what you want to copy, like images, medias etc.
@ -696,14 +788,21 @@ class StaticGenerator(Generator):
final_path=None):
"""Copy all the paths from source to destination"""
for path in paths:
source_path = os.path.join(source, path)
if final_path:
copy(os.path.join(source, path),
os.path.join(output_path, destination, final_path),
self.settings['IGNORE_FILES'])
if os.path.isfile(source_path):
destination_path = os.path.join(output_path, destination,
final_path,
os.path.basename(path))
else:
destination_path = os.path.join(output_path, destination,
final_path)
else:
copy(os.path.join(source, path),
os.path.join(output_path, destination, path),
self.settings['IGNORE_FILES'])
destination_path = os.path.join(output_path, destination, path)
copy(source_path, destination_path,
self.settings['IGNORE_FILES'])
def _file_update_required(self, staticfile):
source_path = os.path.join(self.path, staticfile.source_path)
@ -726,7 +825,7 @@ class StaticGenerator(Generator):
save_as = os.path.join(self.output_path, staticfile.save_as)
s_mtime = os.path.getmtime(source_path)
d_mtime = os.path.getmtime(save_as)
return s_mtime > d_mtime
return s_mtime - d_mtime > 0.000001
def _link_or_copy_staticfile(self, sc):
if self.settings['STATIC_CREATE_LINKS']:

View file

@ -91,6 +91,8 @@ class LimitFilter(logging.Filter):
E.g.: log.warning(('43 is not the answer', 'More erroneous answers'))
"""
LOGS_DEDUP_MIN_LEVEL = logging.WARNING
_ignore = set()
_raised_messages = set()
_threshold = 5
@ -98,7 +100,7 @@ class LimitFilter(logging.Filter):
def filter(self, record):
# don't limit log messages for anything above "warning"
if record.levelno > logging.WARN:
if record.levelno > self.LOGS_DEDUP_MIN_LEVEL:
return True
# extract group
@ -226,7 +228,8 @@ def get_formatter():
return TextFormatter()
def init(level=None, fatal='', handler=logging.StreamHandler(), name=None):
def init(level=None, fatal='', handler=logging.StreamHandler(), name=None,
logs_dedup_min_level=None):
FatalLogger.warnings_fatal = fatal.startswith('warning')
FatalLogger.errors_fatal = bool(fatal)
@ -237,6 +240,8 @@ def init(level=None, fatal='', handler=logging.StreamHandler(), name=None):
if level:
logger.setLevel(level)
if logs_dedup_min_level:
LimitFilter.LOGS_DEDUP_MIN_LEVEL = logs_dedup_min_level
def log_warnings():

View file

@ -17,14 +17,14 @@ PaginationRule = namedtuple(
class Paginator(object):
def __init__(self, name, object_list, settings):
def __init__(self, name, url, object_list, settings, per_page=None):
self.name = name
self.url = url
self.object_list = object_list
self.settings = settings
if settings.get('DEFAULT_PAGINATION'):
self.per_page = settings.get('DEFAULT_PAGINATION')
self.orphans = settings.get('DEFAULT_ORPHANS')
if per_page:
self.per_page = per_page
self.orphans = settings['DEFAULT_ORPHANS']
else:
self.per_page = len(object_list)
self.orphans = 0
@ -37,8 +37,8 @@ class Paginator(object):
top = bottom + self.per_page
if top + self.orphans >= self.count:
top = self.count
return Page(self.name, self.object_list[bottom:top], number, self,
self.settings)
return Page(self.name, self.url, self.object_list[bottom:top], number,
self, self.settings)
def _get_count(self):
"Returns the total number of objects, across all pages."
@ -65,8 +65,12 @@ class Paginator(object):
class Page(object):
def __init__(self, name, object_list, number, paginator, settings):
def __init__(self, name, url, object_list, number, paginator, settings):
self.full_name = name
self.name, self.extension = os.path.splitext(name)
dn, fn = os.path.split(name)
self.base_name = dn if fn in ('index.htm', 'index.html') else self.name
self.base_url = url
self.object_list = object_list
self.number = number
self.paginator = paginator
@ -133,24 +137,16 @@ class Page(object):
# URL or SAVE_AS is a string, format it with a controlled context
context = {
'name': self.name.replace(os.sep, '/'),
'object_list': self.object_list,
'number': self.number,
'paginator': self.paginator,
'settings': self.settings,
'base_name': os.path.dirname(self.name),
'number_sep': '/',
'save_as': self.full_name,
'url': self.base_url,
'name': self.name,
'base_name': self.base_name,
'extension': self.extension,
'number': self.number,
}
if self.number == 1:
# no page numbers on the first page
context['number'] = ''
context['number_sep'] = ''
ret = prop_value.format(**context)
if ret[0] == '/':
ret = ret[1:]
ret = ret.lstrip('/')
return ret
url = property(functools.partial(_from_settings, key='URL'))

View file

@ -31,6 +31,20 @@ except ImportError:
# This means that _filter_discardable_metadata() must be called on processed
# metadata dicts before use, to remove the items with the special value.
_DISCARD = object()
DUPLICATES_DEFINITIONS_ALLOWED = {
'tags': False,
'date': False,
'modified': False,
'status': False,
'category': False,
'author': False,
'save_as': False,
'url': False,
'authors': False,
'slug': False
}
METADATA_PROCESSORS = {
'tags': lambda x, y: ([
Tag(tag, y)
@ -203,11 +217,18 @@ class RstReader(BaseReader):
def __init__(self, *args, **kwargs):
super(RstReader, self).__init__(*args, **kwargs)
def _parse_metadata(self, document):
def _parse_metadata(self, document, source_path):
"""Return the dict containing document metadata"""
formatted_fields = self.settings['FORMATTED_FIELDS']
output = {}
if document.first_child_matching_class(docutils.nodes.title) is None:
logger.warning(
'Document title missing in file %s: '
'Ensure exactly one top level section',
source_path)
for docinfo in document.traverse(docutils.nodes.docinfo):
for element in docinfo.children:
if element.tagname == 'field': # custom fields (e.g. summary)
@ -234,6 +255,7 @@ class RstReader(BaseReader):
extra_params = {'initial_header_level': '2',
'syntax_highlight': 'short',
'input_encoding': 'utf-8',
'language_code': self.settings.get('DEFAULT_LANG'),
'exit_status_level': 2,
'embed_stylesheet': False}
user_params = self.settings.get('DOCUTILS_SETTINGS')
@ -256,7 +278,7 @@ class RstReader(BaseReader):
parts = pub.writer.parts
content = parts.get('body')
metadata = self._parse_metadata(pub.document)
metadata = self._parse_metadata(pub.document, source_path)
metadata.setdefault('title', parts.get('title'))
return content, metadata
@ -294,7 +316,7 @@ class MarkdownReader(BaseReader):
self._md.reset()
formatted = self._md.convert(formatted_values)
output[name] = self.process_metadata(name, formatted)
elif name in METADATA_PROCESSORS:
elif not DUPLICATES_DEFINITIONS_ALLOWED.get(name, True):
if len(value) > 1:
logger.warning(
'Duplicate definition of `%s` '

View file

@ -1,8 +1,11 @@
# -*- coding: utf-8 -*-
from __future__ import print_function, unicode_literals
import argparse
import logging
import os
import posixpath
import ssl
import sys
try:
@ -10,37 +13,81 @@ try:
except ImportError:
magic_from_file = None
from six.moves import BaseHTTPServer
from six.moves import SimpleHTTPServer as srvmod
from six.moves import socketserver
from six.moves import urllib
def parse_arguments():
parser = argparse.ArgumentParser(
description='Pelican Development Server',
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument("port", default=8000, type=int, nargs="?",
help="Port to Listen On")
parser.add_argument("server", default="", nargs="?",
help="Interface to Listen On")
parser.add_argument('--ssl', action="store_true",
help='Activate SSL listener')
parser.add_argument('--cert', default="./cert.pem", nargs="?",
help='Path to certificate file. ' +
'Relative to current directory')
parser.add_argument('--key', default="./key.pem", nargs="?",
help='Path to certificate key file. ' +
'Relative to current directory')
parser.add_argument('--path', default=".",
help='Path to pelican source directory to serve. ' +
'Relative to current directory')
return parser.parse_args()
class ComplexHTTPRequestHandler(srvmod.SimpleHTTPRequestHandler):
SUFFIXES = ['', '.html', '/index.html']
SUFFIXES = ['.html', '/index.html', '/', '']
def translate_path(self, path):
# abandon query parameters
path = path.split('?', 1)[0]
path = path.split('#', 1)[0]
# Don't forget explicit trailing slash when normalizing. Issue17324
trailing_slash = path.rstrip().endswith('/')
path = urllib.parse.unquote(path)
path = posixpath.normpath(path)
words = path.split('/')
words = filter(None, words)
path = self.base_path
for word in words:
if os.path.dirname(word) or word in (os.curdir, os.pardir):
# Ignore components that are not a simple file/directory name
continue
path = os.path.join(path, word)
if trailing_slash:
path += '/'
return path
def do_GET(self):
# cut off a query string
if '?' in self.path:
self.path, _ = self.path.split('?', 1)
original_path = self.path.split('?', 1)[0]
# try to find file
self.path = self.get_path_that_exists(original_path)
if not self.path:
logging.warning("Unable to find `%s` or variations.",
original_path)
return
logging.info("Found `%s`.", self.path)
srvmod.SimpleHTTPRequestHandler.do_GET(self)
def get_path_that_exists(self, original_path):
# Try to strip trailing slash
original_path = original_path.rstrip('/')
# Try to detect file by applying various suffixes
for suffix in self.SUFFIXES:
if not hasattr(self, 'original_path'):
self.original_path = self.path
self.path = self.original_path + suffix
path = self.translate_path(self.path)
if os.path.exists(path):
srvmod.SimpleHTTPRequestHandler.do_GET(self)
logging.info("Found `%s`." % self.path)
break
logging.info("Tried to find `%s`, but it doesn't exist.",
self.path)
else:
# Fallback if there were no matches
logging.warning("Unable to find `%s` or variations.",
self.original_path)
path = original_path + suffix
if os.path.exists(self.translate_path(path)):
return path
logging.info("Tried to find `%s`, but it doesn't exist.", path)
return None
def guess_type(self, path):
"""Guess at the mime type for the specified file.
@ -54,19 +101,36 @@ class ComplexHTTPRequestHandler(srvmod.SimpleHTTPRequestHandler):
return mimetype
if __name__ == '__main__':
PORT = len(sys.argv) in (2, 3) and int(sys.argv[1]) or 8000
SERVER = len(sys.argv) == 3 and sys.argv[2] or ""
class RootedHTTPServer(BaseHTTPServer.HTTPServer):
def __init__(self, base_path, *args, **kwargs):
BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
self.RequestHandlerClass.base_path = base_path
socketserver.TCPServer.allow_reuse_address = True
if __name__ == '__main__':
logging.warning("'python -m pelican.server' is deprecated. The "
"Pelican development server should be run via "
"'pelican --listen' or 'pelican -l' (this can be combined "
"with regeneration as 'pelican -lr'). Rerun 'pelican-"
"quickstart' to get new Makefile and tasks.py files.")
args = parse_arguments()
RootedHTTPServer.allow_reuse_address = True
try:
httpd = socketserver.TCPServer(
(SERVER, PORT), ComplexHTTPRequestHandler)
except OSError as e:
logging.error("Could not listen on port %s, server %s.", PORT, SERVER)
httpd = RootedHTTPServer(
args.path, (args.server, args.port), ComplexHTTPRequestHandler)
if args.ssl:
httpd.socket = ssl.wrap_socket(
httpd.socket, keyfile=args.key,
certfile=args.cert, server_side=True)
except ssl.SSLError as e:
logging.error("Couldn't open certificate file %s or key file %s",
args.cert, args.key)
logging.error("Could not listen on port %s, server %s.",
args.port, args.server)
sys.exit(getattr(e, 'exitcode', 1))
logging.info("Serving at port %s, server %s.", PORT, SERVER)
logging.info("Serving at port %s, server %s.",
args.port, args.server)
try:
httpd.serve_forever()
except KeyboardInterrupt as e:

View file

@ -6,6 +6,7 @@ import inspect
import locale
import logging
import os
import re
from os.path import isabs
from posixpath import join as posix_join
@ -80,6 +81,11 @@ DEFAULT_CONFIG = {
'PAGE_ORDER_BY': 'basename',
'PAGE_LANG_URL': 'pages/{slug}-{lang}.html',
'PAGE_LANG_SAVE_AS': posix_join('pages', '{slug}-{lang}.html'),
'DRAFT_PAGE_URL': 'drafts/pages/{slug}.html',
'DRAFT_PAGE_SAVE_AS': posix_join('drafts', 'pages', '{slug}.html'),
'DRAFT_PAGE_LANG_URL': 'drafts/pages/{slug}-{lang}.html',
'DRAFT_PAGE_LANG_SAVE_AS': posix_join('drafts', 'pages',
'{slug}-{lang}.html'),
'STATIC_URL': '{path}',
'STATIC_SAVE_AS': '{path}',
'STATIC_CREATE_LINKS': False,
@ -91,16 +97,23 @@ DEFAULT_CONFIG = {
'AUTHOR_URL': 'author/{slug}.html',
'AUTHOR_SAVE_AS': posix_join('author', '{slug}.html'),
'PAGINATION_PATTERNS': [
(0, '{name}{number}{extension}', '{name}{number}{extension}'),
(1, '{name}{extension}', '{name}{extension}'),
(2, '{name}{number}{extension}', '{name}{number}{extension}'),
],
'YEAR_ARCHIVE_URL': '',
'YEAR_ARCHIVE_SAVE_AS': '',
'MONTH_ARCHIVE_URL': '',
'MONTH_ARCHIVE_SAVE_AS': '',
'DAY_ARCHIVE_URL': '',
'DAY_ARCHIVE_SAVE_AS': '',
'RELATIVE_URLS': False,
'DEFAULT_LANG': 'en',
'ARTICLE_TRANSLATION_ID': 'slug',
'PAGE_TRANSLATION_ID': 'slug',
'DIRECT_TEMPLATES': ['index', 'tags', 'categories', 'authors', 'archives'],
'EXTRA_TEMPLATES_PATHS': [],
'PAGINATED_DIRECT_TEMPLATES': ['index'],
'THEME_TEMPLATES_OVERRIDES': [],
'PAGINATED_TEMPLATES': {'index': None, 'tag': None, 'category': None,
'author': None},
'PELICAN_CLASS': 'pelican.Pelican',
'DEFAULT_DATE_FORMAT': '%a %d %B %Y',
'DATE_FORMATS': {},
@ -134,8 +147,14 @@ DEFAULT_CONFIG = {
'PLUGINS': [],
'PYGMENTS_RST_OPTIONS': {},
'TEMPLATE_PAGES': {},
'TEMPLATE_EXTENSIONS': ['.html'],
'IGNORE_FILES': ['.#*'],
'SLUG_SUBSTITUTIONS': (),
'SLUG_REGEX_SUBSTITUTIONS': [
(r'[^\w\s-]', ''), # remove non-alphabetical/whitespace/'-' chars
(r'(?u)\A\s*', ''), # strip leading whitespace
(r'(?u)\s*\Z', ''), # strip trailing whitespace
(r'[-\s]+', '-'), # reduce multiple whitespace or '-' to single '-'
],
'INTRASITE_LINK_REGEX': '[{|](?P<what>.*?)[|}]',
'SLUGIFY_SOURCE': 'title',
'CACHE_CONTENT': False,
@ -146,85 +165,70 @@ DEFAULT_CONFIG = {
'LOAD_CONTENT_CACHE': False,
'WRITE_SELECTED': [],
'FORMATTED_FIELDS': ['summary'],
'PORT': 8000,
'BIND': '',
}
PYGMENTS_RST_OPTIONS = None
def read_settings(path=None, override=None):
settings = override or {}
if path:
local_settings = get_settings_from_file(path)
# Make the paths relative to the settings file
settings = dict(get_settings_from_file(path), **settings)
if settings:
settings = handle_deprecated_settings(settings)
if path:
# Make relative paths absolute
def getabs(maybe_relative, base_path=path):
if isabs(maybe_relative):
return maybe_relative
return os.path.abspath(os.path.normpath(os.path.join(
os.path.dirname(base_path), maybe_relative)))
for p in ['PATH', 'OUTPUT_PATH', 'THEME', 'CACHE_PATH']:
if p in local_settings and local_settings[p] is not None \
and not isabs(local_settings[p]):
absp = os.path.abspath(os.path.normpath(os.path.join(
os.path.dirname(path), local_settings[p])))
if settings.get(p) is not None:
absp = getabs(settings[p])
# THEME may be a name rather than a path
if p != 'THEME' or os.path.exists(absp):
local_settings[p] = absp
settings[p] = absp
if 'PLUGIN_PATH' in local_settings:
logger.warning('PLUGIN_PATH setting has been replaced by '
'PLUGIN_PATHS, moving it to the new setting name.')
local_settings['PLUGIN_PATHS'] = local_settings['PLUGIN_PATH']
del local_settings['PLUGIN_PATH']
if 'JINJA_EXTENSIONS' in local_settings:
logger.warning('JINJA_EXTENSIONS setting has been deprecated, '
'moving it to JINJA_ENVIRONMENT setting.')
local_settings['JINJA_ENVIRONMENT']['extensions'] = \
local_settings['JINJA_EXTENSIONS']
del local_settings['JINJA_EXTENSIONS']
if isinstance(local_settings['PLUGIN_PATHS'], six.string_types):
logger.warning("Defining PLUGIN_PATHS setting as string "
"has been deprecated (should be a list)")
local_settings['PLUGIN_PATHS'] = [local_settings['PLUGIN_PATHS']]
elif local_settings['PLUGIN_PATHS'] is not None:
def getabs(path, pluginpath):
if isabs(pluginpath):
return pluginpath
else:
path_dirname = os.path.dirname(path)
path_joined = os.path.join(path_dirname, pluginpath)
path_normed = os.path.normpath(path_joined)
path_absolute = os.path.abspath(path_normed)
return path_absolute
if settings.get('PLUGIN_PATHS') is not None:
settings['PLUGIN_PATHS'] = [getabs(pluginpath)
for pluginpath
in settings['PLUGIN_PATHS']]
pluginpath_list = [getabs(path, pluginpath)
for pluginpath
in local_settings['PLUGIN_PATHS']]
local_settings['PLUGIN_PATHS'] = pluginpath_list
else:
local_settings = copy.deepcopy(DEFAULT_CONFIG)
settings = dict(copy.deepcopy(DEFAULT_CONFIG), **settings)
settings = configure_settings(settings)
if override:
local_settings.update(override)
parsed_settings = configure_settings(local_settings)
# This is because there doesn't seem to be a way to pass extra
# parameters to docutils directive handlers, so we have to have a
# variable here that we'll import from within Pygments.run (see
# rstdirectives.py) to see what the user defaults were.
global PYGMENTS_RST_OPTIONS
PYGMENTS_RST_OPTIONS = parsed_settings.get('PYGMENTS_RST_OPTIONS', None)
return parsed_settings
PYGMENTS_RST_OPTIONS = settings.get('PYGMENTS_RST_OPTIONS', None)
return settings
def get_settings_from_module(module=None, default_settings=DEFAULT_CONFIG):
def get_settings_from_module(module=None):
"""Loads settings from a module, returns a dictionary."""
context = copy.deepcopy(default_settings)
context = {}
if module is not None:
context.update(
(k, v) for k, v in inspect.getmembers(module) if k.isupper())
return context
def get_settings_from_file(path, default_settings=DEFAULT_CONFIG):
def get_settings_from_file(path):
"""Loads settings from a file path, returning a dict."""
name, ext = os.path.splitext(os.path.basename(path))
module = load_source(name, path)
return get_settings_from_module(module, default_settings=default_settings)
return get_settings_from_module(module)
def get_jinja_environment(settings):
@ -241,6 +245,149 @@ def get_jinja_environment(settings):
return settings
def handle_deprecated_settings(settings):
"""Converts deprecated settings and issues warnings. Issues an exception
if both old and new setting is specified.
"""
# PLUGIN_PATH -> PLUGIN_PATHS
if 'PLUGIN_PATH' in settings:
logger.warning('PLUGIN_PATH setting has been replaced by '
'PLUGIN_PATHS, moving it to the new setting name.')
settings['PLUGIN_PATHS'] = settings['PLUGIN_PATH']
del settings['PLUGIN_PATH']
# PLUGIN_PATHS: str -> [str]
if isinstance(settings.get('PLUGIN_PATHS'), six.string_types):
logger.warning("Defining PLUGIN_PATHS setting as string "
"has been deprecated (should be a list)")
settings['PLUGIN_PATHS'] = [settings['PLUGIN_PATHS']]
# JINJA_EXTENSIONS -> JINJA_ENVIRONMENT > extensions
if 'JINJA_EXTENSIONS' in settings:
logger.warning('JINJA_EXTENSIONS setting has been deprecated, '
'moving it to JINJA_ENVIRONMENT setting.')
settings['JINJA_ENVIRONMENT']['extensions'] = \
settings['JINJA_EXTENSIONS']
del settings['JINJA_EXTENSIONS']
# {ARTICLE,PAGE}_DIR -> {ARTICLE,PAGE}_PATHS
for key in ['ARTICLE', 'PAGE']:
old_key = key + '_DIR'
new_key = key + '_PATHS'
if old_key in settings:
logger.warning(
'Deprecated setting %s, moving it to %s list',
old_key, new_key)
settings[new_key] = [settings[old_key]] # also make a list
del settings[old_key]
# EXTRA_TEMPLATES_PATHS -> THEME_TEMPLATES_OVERRIDES
if 'EXTRA_TEMPLATES_PATHS' in settings:
logger.warning('EXTRA_TEMPLATES_PATHS is deprecated use '
'THEME_TEMPLATES_OVERRIDES instead.')
if ('THEME_TEMPLATES_OVERRIDES' in settings and
settings['THEME_TEMPLATES_OVERRIDES']):
raise Exception(
'Setting both EXTRA_TEMPLATES_PATHS and '
'THEME_TEMPLATES_OVERRIDES is not permitted. Please move to '
'only setting THEME_TEMPLATES_OVERRIDES.')
settings['THEME_TEMPLATES_OVERRIDES'] = \
settings['EXTRA_TEMPLATES_PATHS']
del settings['EXTRA_TEMPLATES_PATHS']
# MD_EXTENSIONS -> MARKDOWN
if 'MD_EXTENSIONS' in settings:
logger.warning('MD_EXTENSIONS is deprecated use MARKDOWN '
'instead. Falling back to the default.')
settings['MARKDOWN'] = DEFAULT_CONFIG['MARKDOWN']
# LESS_GENERATOR -> Webassets plugin
# FILES_TO_COPY -> STATIC_PATHS, EXTRA_PATH_METADATA
for old, new, doc in [
('LESS_GENERATOR', 'the Webassets plugin', None),
('FILES_TO_COPY', 'STATIC_PATHS and EXTRA_PATH_METADATA',
'https://github.com/getpelican/pelican/'
'blob/master/docs/settings.rst#path-metadata'),
]:
if old in settings:
message = 'The {} setting has been removed in favor of {}'.format(
old, new)
if doc:
message += ', see {} for details'.format(doc)
logger.warning(message)
# PAGINATED_DIRECT_TEMPLATES -> PAGINATED_TEMPLATES
if 'PAGINATED_DIRECT_TEMPLATES' in settings:
message = 'The {} setting has been removed in favor of {}'.format(
'PAGINATED_DIRECT_TEMPLATES', 'PAGINATED_TEMPLATES')
logger.warning(message)
for t in settings['PAGINATED_DIRECT_TEMPLATES']:
if t not in settings['PAGINATED_TEMPLATES']:
settings['PAGINATED_TEMPLATES'][t] = None
del settings['PAGINATED_DIRECT_TEMPLATES']
# {SLUG,CATEGORY,TAG,AUTHOR}_SUBSTITUTIONS ->
# {SLUG,CATEGORY,TAG,AUTHOR}_REGEX_SUBSTITUTIONS
url_settings_url = \
'http://docs.getpelican.com/en/latest/settings.html#url-settings'
flavours = {'SLUG', 'CATEGORY', 'TAG', 'AUTHOR'}
old_values = {f: settings[f + '_SUBSTITUTIONS']
for f in flavours if f + '_SUBSTITUTIONS' in settings}
new_values = {f: settings[f + '_REGEX_SUBSTITUTIONS']
for f in flavours if f + '_REGEX_SUBSTITUTIONS' in settings}
if old_values and new_values:
raise Exception(
'Setting both {new_key} and {old_key} (or variants thereof) is '
'not permitted. Please move to only setting {new_key}.'
.format(old_key='SLUG_SUBSTITUTIONS',
new_key='SLUG_REGEX_SUBSTITUTIONS'))
if old_values:
message = ('{} and variants thereof are deprecated and will be '
'removed in the future. Please use {} and variants thereof '
'instead. Check {}.'
.format('SLUG_SUBSTITUTIONS', 'SLUG_REGEX_SUBSTITUTIONS',
url_settings_url))
logger.warning(message)
if old_values.get('SLUG'):
for f in {'CATEGORY', 'TAG'}:
if old_values.get(f):
old_values[f] = old_values['SLUG'] + old_values[f]
old_values['AUTHOR'] = old_values.get('AUTHOR', [])
for f in flavours:
if old_values.get(f) is not None:
regex_subs = []
# by default will replace non-alphanum characters
replace = True
for tpl in old_values[f]:
try:
src, dst, skip = tpl
if skip:
replace = False
except ValueError:
src, dst = tpl
regex_subs.append(
(re.escape(src), dst.replace('\\', r'\\')))
if replace:
regex_subs += [
(r'[^\w\s-]', ''),
(r'(?u)\A\s*', ''),
(r'(?u)\s*\Z', ''),
(r'[-\s]+', '-'),
]
else:
regex_subs += [
(r'(?u)\A\s*', ''),
(r'(?u)\s*\Z', ''),
]
settings[f + '_REGEX_SUBSTITUTIONS'] = regex_subs
settings.pop(f + '_SUBSTITUTIONS', None)
return settings
def configure_settings(settings):
"""Provide optimizations, error checking, and warnings for the given
settings.
@ -365,23 +512,12 @@ def configure_settings(settings):
key=lambda r: r[0],
)
# move {ARTICLE,PAGE}_DIR -> {ARTICLE,PAGE}_PATHS
for key in ['ARTICLE', 'PAGE']:
old_key = key + '_DIR'
new_key = key + '_PATHS'
if old_key in settings:
logger.warning(
'Deprecated setting %s, moving it to %s list',
old_key, new_key)
settings[new_key] = [settings[old_key]] # also make a list
del settings[old_key]
# Save people from accidentally setting a string rather than a list
path_keys = (
'ARTICLE_EXCLUDES',
'DEFAULT_METADATA',
'DIRECT_TEMPLATES',
'EXTRA_TEMPLATES_PATHS',
'THEME_TEMPLATES_OVERRIDES',
'FILES_TO_COPY',
'IGNORE_FILES',
'PAGINATED_DIRECT_TEMPLATES',
@ -399,12 +535,6 @@ def configure_settings(settings):
PATH_KEY)
settings[PATH_KEY] = DEFAULT_CONFIG[PATH_KEY]
# Deprecated warning of MD_EXTENSIONS
if 'MD_EXTENSIONS' in settings:
logger.warning('MD_EXTENSIONS is deprecated use MARKDOWN '
'instead. Falling back to the default.')
settings['MARKDOWN'] = DEFAULT_CONFIG['MARKDOWN']
# Add {PAGE,ARTICLE}_PATHS to {ARTICLE,PAGE}_EXCLUDES
mutually_exclusive = ('ARTICLE', 'PAGE')
for type_1, type_2 in [mutually_exclusive, mutually_exclusive[::-1]]:
@ -417,17 +547,4 @@ def configure_settings(settings):
except KeyError:
continue # setting not specified, nothing to do
for old, new, doc in [
('LESS_GENERATOR', 'the Webassets plugin', None),
('FILES_TO_COPY', 'STATIC_PATHS and EXTRA_PATH_METADATA',
'https://github.com/getpelican/pelican/'
'blob/master/docs/settings.rst#path-metadata'),
]:
if old in settings:
message = 'The {} setting has been removed in favor of {}'.format(
old, new)
if doc:
message += ', see {} for details'.format(doc)
logger.warning(message)
return settings

View file

@ -27,6 +27,7 @@ article_writer_finalized = signal('article_writer_finalized')
page_generator_init = signal('page_generator_init')
page_generator_finalized = signal('page_generator_finalized')
page_generator_write_page = signal('page_generator_write_page')
page_writer_finalized = signal('page_writer_finalized')
static_generator_init = signal('static_generator_init')

View file

@ -0,0 +1,8 @@
This is a test draft page
##########################
:status: draft
The quick brown fox .
This page is a draft.

View file

@ -0,0 +1,12 @@
title: This is a markdown test draft page
status: draft
Test Markdown File Header
=========================
Used for pelican test
---------------------
The quick brown fox .
This page is a draft

View file

@ -0,0 +1,11 @@
This is a test draft page with a custom template
#################################################
:status: draft
:template: custom
The quick brown fox .
This page is a draft
This page has a custom template to be called when rendered

1067
pelican/tests/content/bloggerexport.xml vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -554,7 +554,11 @@ 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>
Bottom line: don't mess up with birds
"That's a 'wonderful' shoe."
“Thats a magic sock.”]]></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>

View file

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<title>A Pelican Blog</title>
<title>A Pelican Blog - Categories</title>
<link rel="stylesheet" href="/theme/css/main.css" />
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
</head>
@ -20,12 +20,13 @@
<li><a href="/category/yeah.html">yeah</a></li>
</ul></nav>
</header><!-- /#banner -->
<ul>
<li><a href="/category/bar.html">bar</a></li>
<li><a href="/category/cat1.html">cat1</a></li>
<li><a href="/category/misc.html">misc</a></li>
<li><a href="/category/yeah.html">yeah</a></li>
</ul>
<h1>Categories on A Pelican Blog</h1>
<ul>
<li><a href="/category/bar.html">bar</a> (1)</li>
<li><a href="/category/cat1.html">cat1</a> (4)</li>
<li><a href="/category/misc.html">misc</a> (4)</li>
<li><a href="/category/yeah.html">yeah</a> (1)</li>
</ul>
<section id="extras" class="body">
<div class="social">
<h2>social</h2>

View file

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="fr">
<head>
<meta charset="utf-8" />
<title>Deuxième article</title>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 751 B

After

Width:  |  Height:  |  Size: 411 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 958 B

After

Width:  |  Height:  |  Size: 827 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 202 B

After

Width:  |  Height:  |  Size: 150 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 606 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 B

After

Width:  |  Height:  |  Size: 223 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 487 B

After

Width:  |  Height:  |  Size: 402 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 803 B

After

Width:  |  Height:  |  Size: 420 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 527 B

After

Width:  |  Height:  |  Size: 511 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 975 B

After

Width:  |  Height:  |  Size: 840 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 896 B

After

Width:  |  Height:  |  Size: 625 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 693 B

After

Width:  |  Height:  |  Size: 458 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 879 B

After

Width:  |  Height:  |  Size: 751 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 535 B

After

Width:  |  Height:  |  Size: 435 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 580 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 916 B

After

Width:  |  Height:  |  Size: 414 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 416 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

After

Width:  |  Height:  |  Size: 349 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 458 B

After

Width:  |  Height:  |  Size: 316 B

Before After
Before After

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="./">Alexis' log </a></h1>
<h1><a href="./">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="./tag/oh.html">Oh Oh Oh</a></li>
<li><a href="./override/">Override url/save_as</a></li>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="./">Alexis' log </a></h1>
<h1><a href="./">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="./tag/oh.html">Oh Oh Oh</a></li>
<li><a href="./override/">Override url/save_as</a></li>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="./">Alexis' log </a></h1>
<h1><a href="./">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="./tag/oh.html">Oh Oh Oh</a></li>
<li><a href="./override/">Override url/save_as</a></li>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="./">Alexis' log </a></h1>
<h1><a href="./">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="./tag/oh.html">Oh Oh Oh</a></li>
<li><a href="./override/">Override url/save_as</a></li>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="./">Alexis' log </a></h1>
<h1><a href="./">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="./tag/oh.html">Oh Oh Oh</a></li>
<li><a href="./override/">Override url/save_as</a></li>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="../">Alexis' log </a></h1>
<h1><a href="../">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="../tag/oh.html">Oh Oh Oh</a></li>
<li><a href="../override/">Override url/save_as</a></li>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="../">Alexis' log </a></h1>
<h1><a href="../">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="../tag/oh.html">Oh Oh Oh</a></li>
<li><a href="../override/">Override url/save_as</a></li>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="../">Alexis' log </a></h1>
<h1><a href="../">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="../tag/oh.html">Oh Oh Oh</a></li>
<li><a href="../override/">Override url/save_as</a></li>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="./">Alexis' log </a></h1>
<h1><a href="./">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="./tag/oh.html">Oh Oh Oh</a></li>
<li><a href="./override/">Override url/save_as</a></li>

View file

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Alexis' log</title>
<title>Alexis' log - Categories</title>
<link rel="stylesheet" href="./theme/css/main.css" />
<link href="http://blog.notmyidea.org/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="Alexis' log Atom Feed" />
<link href="http://blog.notmyidea.org/feeds/all.rss.xml" type="application/rss+xml" rel="alternate" title="Alexis' log RSS Feed" />
@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="./">Alexis' log </a></h1>
<h1><a href="./">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="./tag/oh.html">Oh Oh Oh</a></li>
<li><a href="./override/">Override url/save_as</a></li>
@ -24,12 +24,13 @@
<li><a href="./category/bar.html">bar</a></li>
</ul></nav>
</header><!-- /#banner -->
<ul>
<li><a href="./category/yeah.html">yeah</a></li>
<li><a href="./category/misc.html">misc</a></li>
<li><a href="./category/cat1.html">cat1</a></li>
<li><a href="./category/bar.html">bar</a></li>
</ul>
<h1>Categories on Alexis' log</h1>
<ul>
<li><a href="./category/bar.html">bar</a> (1)</li>
<li><a href="./category/cat1.html">cat1</a> (4)</li>
<li><a href="./category/misc.html">misc</a> (4)</li>
<li><a href="./category/yeah.html">yeah</a> (1)</li>
</ul>
<section id="extras" class="body">
<div class="blogroll">
<h2>links</h2>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="../">Alexis' log </a></h1>
<h1><a href="../">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="../tag/oh.html">Oh Oh Oh</a></li>
<li><a href="../override/">Override url/save_as</a></li>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="../">Alexis' log </a></h1>
<h1><a href="../">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="../tag/oh.html">Oh Oh Oh</a></li>
<li><a href="../override/">Override url/save_as</a></li>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="../">Alexis' log </a></h1>
<h1><a href="../">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="../tag/oh.html">Oh Oh Oh</a></li>
<li><a href="../override/">Override url/save_as</a></li>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="../">Alexis' log </a></h1>
<h1><a href="../">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="../tag/oh.html">Oh Oh Oh</a></li>
<li><a href="../override/">Override url/save_as</a></li>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="../">Alexis' log </a></h1>
<h1><a href="../">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="../tag/oh.html">Oh Oh Oh</a></li>
<li><a href="../override/">Override url/save_as</a></li>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Alexis' log - Alexis Métaireau</title><link href="http://blog.notmyidea.org/" rel="alternate"></link><link href="http://blog.notmyidea.org/feeds/alexis-metaireau.atom.xml" rel="self"></link><id>http://blog.notmyidea.org/</id><updated>2013-11-17T23:29:00+01:00</updated><entry><title>FILENAME_METADATA example</title><link href="http://blog.notmyidea.org/filename_metadata-example.html" rel="alternate"></link><published>2012-11-30T00:00:00+01:00</published><updated>2012-11-30T00:00:00+01:00</updated><author><name>Alexis Métaireau</name></author><id>tag:blog.notmyidea.org,2012-11-30:/filename_metadata-example.html</id><summary type="html">&lt;p&gt;Some cool stuff!&lt;/p&gt;
<feed xmlns="http://www.w3.org/2005/Atom"><title>Alexis' log - Alexis Métaireau</title><link href="http://blog.notmyidea.org/" rel="alternate"></link><link href="http://blog.notmyidea.org/feeds/alexis-metaireau.atom.xml" rel="self"></link><id>http://blog.notmyidea.org/</id><updated>2013-11-17T23:29:00+01:00</updated><subtitle>A personal blog.</subtitle><entry><title>FILENAME_METADATA example</title><link href="http://blog.notmyidea.org/filename_metadata-example.html" rel="alternate"></link><published>2012-11-30T00:00:00+01:00</published><updated>2012-11-30T00:00:00+01:00</updated><author><name>Alexis Métaireau</name></author><id>tag:blog.notmyidea.org,2012-11-30:/filename_metadata-example.html</id><summary type="html">&lt;p&gt;Some cool stuff!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Some cool stuff!&lt;/p&gt;
</content></entry><entry><title>Second article</title><link href="http://blog.notmyidea.org/second-article.html" rel="alternate"></link><published>2012-02-29T00:00:00+01:00</published><updated>2012-02-29T00:00:00+01:00</updated><author><name>Alexis Métaireau</name></author><id>tag:blog.notmyidea.org,2012-02-29:/second-article.html</id><summary type="html">&lt;p&gt;This is some article, in english&lt;/p&gt;
</summary><content type="html">&lt;p&gt;This is some article, in english&lt;/p&gt;

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"><channel><title>Alexis' log - Alexis Métaireau</title><link>http://blog.notmyidea.org/</link><description></description><lastBuildDate>Sun, 17 Nov 2013 23:29:00 +0100</lastBuildDate><item><title>FILENAME_METADATA example</title><link>http://blog.notmyidea.org/filename_metadata-example.html</link><description>&lt;p&gt;Some cool stuff!&lt;/p&gt;
<rss version="2.0"><channel><title>Alexis' log - Alexis Métaireau</title><link>http://blog.notmyidea.org/</link><description>A personal blog.</description><lastBuildDate>Sun, 17 Nov 2013 23:29:00 +0100</lastBuildDate><item><title>FILENAME_METADATA example</title><link>http://blog.notmyidea.org/filename_metadata-example.html</link><description>&lt;p&gt;Some cool stuff!&lt;/p&gt;
</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Alexis Métaireau</dc:creator><pubDate>Fri, 30 Nov 2012 00:00:00 +0100</pubDate><guid isPermaLink="false">tag:blog.notmyidea.org,2012-11-30:/filename_metadata-example.html</guid></item><item><title>Second article</title><link>http://blog.notmyidea.org/second-article.html</link><description>&lt;p&gt;This is some article, in english&lt;/p&gt;
</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Alexis Métaireau</dc:creator><pubDate>Wed, 29 Feb 2012 00:00:00 +0100</pubDate><guid isPermaLink="false">tag:blog.notmyidea.org,2012-02-29:/second-article.html</guid><category>foo</category><category>bar</category><category>baz</category></item><item><title>A markdown powered article</title><link>http://blog.notmyidea.org/a-markdown-powered-article.html</link><description>&lt;p&gt;You're mutually oblivious.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://blog.notmyidea.org/unbelievable.html"&gt;a root-relative link to unbelievable&lt;/a&gt;

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Alexis' log</title><link href="http://blog.notmyidea.org/" rel="alternate"></link><link href="http://blog.notmyidea.org/feeds/all-en.atom.xml" rel="self"></link><id>http://blog.notmyidea.org/</id><updated>2013-11-17T23:29:00+01:00</updated><entry><title>FILENAME_METADATA example</title><link href="http://blog.notmyidea.org/filename_metadata-example.html" rel="alternate"></link><published>2012-11-30T00:00:00+01:00</published><updated>2012-11-30T00:00:00+01:00</updated><author><name>Alexis Métaireau</name></author><id>tag:blog.notmyidea.org,2012-11-30:/filename_metadata-example.html</id><summary type="html">&lt;p&gt;Some cool stuff!&lt;/p&gt;
<feed xmlns="http://www.w3.org/2005/Atom"><title>Alexis' log</title><link href="http://blog.notmyidea.org/" rel="alternate"></link><link href="http://blog.notmyidea.org/feeds/all-en.atom.xml" rel="self"></link><id>http://blog.notmyidea.org/</id><updated>2013-11-17T23:29:00+01:00</updated><subtitle>A personal blog.</subtitle><entry><title>FILENAME_METADATA example</title><link href="http://blog.notmyidea.org/filename_metadata-example.html" rel="alternate"></link><published>2012-11-30T00:00:00+01:00</published><updated>2012-11-30T00:00:00+01:00</updated><author><name>Alexis Métaireau</name></author><id>tag:blog.notmyidea.org,2012-11-30:/filename_metadata-example.html</id><summary type="html">&lt;p&gt;Some cool stuff!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Some cool stuff!&lt;/p&gt;
</content></entry><entry><title>Second article</title><link href="http://blog.notmyidea.org/second-article.html" rel="alternate"></link><published>2012-02-29T00:00:00+01:00</published><updated>2012-02-29T00:00:00+01:00</updated><author><name>Alexis Métaireau</name></author><id>tag:blog.notmyidea.org,2012-02-29:/second-article.html</id><summary type="html">&lt;p&gt;This is some article, in english&lt;/p&gt;
</summary><content type="html">&lt;p&gt;This is some article, in english&lt;/p&gt;

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Alexis' log</title><link href="http://blog.notmyidea.org/" rel="alternate"></link><link href="http://blog.notmyidea.org/feeds/all-fr.atom.xml" rel="self"></link><id>http://blog.notmyidea.org/</id><updated>2012-03-02T14:01:01+01:00</updated><entry><title>Trop bien !</title><link href="http://blog.notmyidea.org/oh-yeah-fr.html" rel="alternate"></link><published>2012-03-02T14:01:01+01:00</published><updated>2012-03-02T14:01:01+01:00</updated><author><name>Alexis Métaireau</name></author><id>tag:blog.notmyidea.org,2012-03-02:/oh-yeah-fr.html</id><summary type="html">&lt;p&gt;Et voila du contenu en français&lt;/p&gt;
<feed xmlns="http://www.w3.org/2005/Atom"><title>Alexis' log</title><link href="http://blog.notmyidea.org/" rel="alternate"></link><link href="http://blog.notmyidea.org/feeds/all-fr.atom.xml" rel="self"></link><id>http://blog.notmyidea.org/</id><updated>2012-03-02T14:01:01+01:00</updated><subtitle>A personal blog.</subtitle><entry><title>Trop bien !</title><link href="http://blog.notmyidea.org/oh-yeah-fr.html" rel="alternate"></link><published>2012-03-02T14:01:01+01:00</published><updated>2012-03-02T14:01:01+01:00</updated><author><name>Alexis Métaireau</name></author><id>tag:blog.notmyidea.org,2012-03-02:/oh-yeah-fr.html</id><summary type="html">&lt;p&gt;Et voila du contenu en français&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Et voila du contenu en français&lt;/p&gt;
</content></entry><entry><title>Deuxième article</title><link href="http://blog.notmyidea.org/second-article-fr.html" rel="alternate"></link><published>2012-02-29T00:00:00+01:00</published><updated>2012-02-29T00:00:00+01:00</updated><author><name>Alexis Métaireau</name></author><id>tag:blog.notmyidea.org,2012-02-29:/second-article-fr.html</id><summary type="html">&lt;p&gt;Ceci est un article, en français.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Ceci est un article, en français.&lt;/p&gt;

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Alexis' log</title><link href="http://blog.notmyidea.org/" rel="alternate"></link><link href="http://blog.notmyidea.org/feeds/all.atom.xml" rel="self"></link><id>http://blog.notmyidea.org/</id><updated>2013-11-17T23:29:00+01:00</updated><entry><title>FILENAME_METADATA example</title><link href="http://blog.notmyidea.org/filename_metadata-example.html" rel="alternate"></link><published>2012-11-30T00:00:00+01:00</published><updated>2012-11-30T00:00:00+01:00</updated><author><name>Alexis Métaireau</name></author><id>tag:blog.notmyidea.org,2012-11-30:/filename_metadata-example.html</id><summary type="html">&lt;p&gt;Some cool stuff!&lt;/p&gt;
<feed xmlns="http://www.w3.org/2005/Atom"><title>Alexis' log</title><link href="http://blog.notmyidea.org/" rel="alternate"></link><link href="http://blog.notmyidea.org/feeds/all.atom.xml" rel="self"></link><id>http://blog.notmyidea.org/</id><updated>2013-11-17T23:29:00+01:00</updated><subtitle>A personal blog.</subtitle><entry><title>FILENAME_METADATA example</title><link href="http://blog.notmyidea.org/filename_metadata-example.html" rel="alternate"></link><published>2012-11-30T00:00:00+01:00</published><updated>2012-11-30T00:00:00+01:00</updated><author><name>Alexis Métaireau</name></author><id>tag:blog.notmyidea.org,2012-11-30:/filename_metadata-example.html</id><summary type="html">&lt;p&gt;Some cool stuff!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Some cool stuff!&lt;/p&gt;
</content></entry><entry><title>Trop bien !</title><link href="http://blog.notmyidea.org/oh-yeah-fr.html" rel="alternate"></link><published>2012-03-02T14:01:01+01:00</published><updated>2012-03-02T14:01:01+01:00</updated><author><name>Alexis Métaireau</name></author><id>tag:blog.notmyidea.org,2012-03-02:/oh-yeah-fr.html</id><summary type="html">&lt;p&gt;Et voila du contenu en français&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Et voila du contenu en français&lt;/p&gt;

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"><channel><title>Alexis' log</title><link>http://blog.notmyidea.org/</link><description></description><lastBuildDate>Sun, 17 Nov 2013 23:29:00 +0100</lastBuildDate><item><title>FILENAME_METADATA example</title><link>http://blog.notmyidea.org/filename_metadata-example.html</link><description>&lt;p&gt;Some cool stuff!&lt;/p&gt;
<rss version="2.0"><channel><title>Alexis' log</title><link>http://blog.notmyidea.org/</link><description>A personal blog.</description><lastBuildDate>Sun, 17 Nov 2013 23:29:00 +0100</lastBuildDate><item><title>FILENAME_METADATA example</title><link>http://blog.notmyidea.org/filename_metadata-example.html</link><description>&lt;p&gt;Some cool stuff!&lt;/p&gt;
</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Alexis Métaireau</dc:creator><pubDate>Fri, 30 Nov 2012 00:00:00 +0100</pubDate><guid isPermaLink="false">tag:blog.notmyidea.org,2012-11-30:/filename_metadata-example.html</guid></item><item><title>Trop bien !</title><link>http://blog.notmyidea.org/oh-yeah-fr.html</link><description>&lt;p&gt;Et voila du contenu en français&lt;/p&gt;
</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Alexis Métaireau</dc:creator><pubDate>Fri, 02 Mar 2012 14:01:01 +0100</pubDate><guid isPermaLink="false">tag:blog.notmyidea.org,2012-03-02:/oh-yeah-fr.html</guid></item><item><title>Second article</title><link>http://blog.notmyidea.org/second-article.html</link><description>&lt;p&gt;This is some article, in english&lt;/p&gt;
</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Alexis Métaireau</dc:creator><pubDate>Wed, 29 Feb 2012 00:00:00 +0100</pubDate><guid isPermaLink="false">tag:blog.notmyidea.org,2012-02-29:/second-article.html</guid><category>foo</category><category>bar</category><category>baz</category></item><item><title>Deuxième article</title><link>http://blog.notmyidea.org/second-article-fr.html</link><description>&lt;p&gt;Ceci est un article, en français.&lt;/p&gt;

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Alexis' log - bar</title><link href="http://blog.notmyidea.org/" rel="alternate"></link><link href="http://blog.notmyidea.org/feeds/bar.atom.xml" rel="self"></link><id>http://blog.notmyidea.org/</id><updated>2010-10-20T10:14:00+02:00</updated><entry><title>Oh yeah !</title><link href="http://blog.notmyidea.org/oh-yeah.html" rel="alternate"></link><published>2010-10-20T10:14:00+02:00</published><updated>2010-10-20T10:14:00+02:00</updated><author><name>Alexis Métaireau</name></author><id>tag:blog.notmyidea.org,2010-10-20:/oh-yeah.html</id><summary type="html">&lt;div class="section" id="why-not"&gt;
<feed xmlns="http://www.w3.org/2005/Atom"><title>Alexis' log - bar</title><link href="http://blog.notmyidea.org/" rel="alternate"></link><link href="http://blog.notmyidea.org/feeds/bar.atom.xml" rel="self"></link><id>http://blog.notmyidea.org/</id><updated>2010-10-20T10:14:00+02:00</updated><subtitle>A personal blog.</subtitle><entry><title>Oh yeah !</title><link href="http://blog.notmyidea.org/oh-yeah.html" rel="alternate"></link><published>2010-10-20T10:14:00+02:00</published><updated>2010-10-20T10:14:00+02:00</updated><author><name>Alexis Métaireau</name></author><id>tag:blog.notmyidea.org,2010-10-20:/oh-yeah.html</id><summary type="html">&lt;div class="section" id="why-not"&gt;
&lt;h2&gt;Why not ?&lt;/h2&gt;
&lt;p&gt;After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst !
YEAH !&lt;/p&gt;

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"><channel><title>Alexis' log - bar</title><link>http://blog.notmyidea.org/</link><description></description><lastBuildDate>Wed, 20 Oct 2010 10:14:00 +0200</lastBuildDate><item><title>Oh yeah !</title><link>http://blog.notmyidea.org/oh-yeah.html</link><description>&lt;div class="section" id="why-not"&gt;
<rss version="2.0"><channel><title>Alexis' log - bar</title><link>http://blog.notmyidea.org/</link><description>A personal blog.</description><lastBuildDate>Wed, 20 Oct 2010 10:14:00 +0200</lastBuildDate><item><title>Oh yeah !</title><link>http://blog.notmyidea.org/oh-yeah.html</link><description>&lt;div class="section" id="why-not"&gt;
&lt;h2&gt;Why not ?&lt;/h2&gt;
&lt;p&gt;After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst !
YEAH !&lt;/p&gt;

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Alexis' log - cat1</title><link href="http://blog.notmyidea.org/" rel="alternate"></link><link href="http://blog.notmyidea.org/feeds/cat1.atom.xml" rel="self"></link><id>http://blog.notmyidea.org/</id><updated>2011-04-20T00:00:00+02:00</updated><entry><title>A markdown powered article</title><link href="http://blog.notmyidea.org/a-markdown-powered-article.html" rel="alternate"></link><published>2011-04-20T00:00:00+02:00</published><updated>2011-04-20T00:00:00+02:00</updated><author><name>Alexis Métaireau</name></author><id>tag:blog.notmyidea.org,2011-04-20:/a-markdown-powered-article.html</id><summary type="html">&lt;p&gt;You're mutually oblivious.&lt;/p&gt;
<feed xmlns="http://www.w3.org/2005/Atom"><title>Alexis' log - cat1</title><link href="http://blog.notmyidea.org/" rel="alternate"></link><link href="http://blog.notmyidea.org/feeds/cat1.atom.xml" rel="self"></link><id>http://blog.notmyidea.org/</id><updated>2011-04-20T00:00:00+02:00</updated><subtitle>A personal blog.</subtitle><entry><title>A markdown powered article</title><link href="http://blog.notmyidea.org/a-markdown-powered-article.html" rel="alternate"></link><published>2011-04-20T00:00:00+02:00</published><updated>2011-04-20T00:00:00+02:00</updated><author><name>Alexis Métaireau</name></author><id>tag:blog.notmyidea.org,2011-04-20:/a-markdown-powered-article.html</id><summary type="html">&lt;p&gt;You're mutually oblivious.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://blog.notmyidea.org/unbelievable.html"&gt;a root-relative link to unbelievable&lt;/a&gt;
&lt;a href="http://blog.notmyidea.org/unbelievable.html"&gt;a file-relative link to unbelievable&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;You're mutually oblivious.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://blog.notmyidea.org/unbelievable.html"&gt;a root-relative link to unbelievable&lt;/a&gt;

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"><channel><title>Alexis' log - cat1</title><link>http://blog.notmyidea.org/</link><description></description><lastBuildDate>Wed, 20 Apr 2011 00:00:00 +0200</lastBuildDate><item><title>A markdown powered article</title><link>http://blog.notmyidea.org/a-markdown-powered-article.html</link><description>&lt;p&gt;You're mutually oblivious.&lt;/p&gt;
<rss version="2.0"><channel><title>Alexis' log - cat1</title><link>http://blog.notmyidea.org/</link><description>A personal blog.</description><lastBuildDate>Wed, 20 Apr 2011 00:00:00 +0200</lastBuildDate><item><title>A markdown powered article</title><link>http://blog.notmyidea.org/a-markdown-powered-article.html</link><description>&lt;p&gt;You're mutually oblivious.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://blog.notmyidea.org/unbelievable.html"&gt;a root-relative link to unbelievable&lt;/a&gt;
&lt;a href="http://blog.notmyidea.org/unbelievable.html"&gt;a file-relative link to unbelievable&lt;/a&gt;&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Alexis Métaireau</dc:creator><pubDate>Wed, 20 Apr 2011 00:00:00 +0200</pubDate><guid isPermaLink="false">tag:blog.notmyidea.org,2011-04-20:/a-markdown-powered-article.html</guid></item><item><title>Article 1</title><link>http://blog.notmyidea.org/article-1.html</link><description>&lt;p&gt;Article 1&lt;/p&gt;
</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Alexis Métaireau</dc:creator><pubDate>Thu, 17 Feb 2011 00:00:00 +0100</pubDate><guid isPermaLink="false">tag:blog.notmyidea.org,2011-02-17:/article-1.html</guid></item><item><title>Article 2</title><link>http://blog.notmyidea.org/article-2.html</link><description>&lt;p&gt;Article 2&lt;/p&gt;

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Alexis' log - misc</title><link href="http://blog.notmyidea.org/" rel="alternate"></link><link href="http://blog.notmyidea.org/feeds/misc.atom.xml" rel="self"></link><id>http://blog.notmyidea.org/</id><updated>2012-11-30T00:00:00+01:00</updated><entry><title>FILENAME_METADATA example</title><link href="http://blog.notmyidea.org/filename_metadata-example.html" rel="alternate"></link><published>2012-11-30T00:00:00+01:00</published><updated>2012-11-30T00:00:00+01:00</updated><author><name>Alexis Métaireau</name></author><id>tag:blog.notmyidea.org,2012-11-30:/filename_metadata-example.html</id><summary type="html">&lt;p&gt;Some cool stuff!&lt;/p&gt;
<feed xmlns="http://www.w3.org/2005/Atom"><title>Alexis' log - misc</title><link href="http://blog.notmyidea.org/" rel="alternate"></link><link href="http://blog.notmyidea.org/feeds/misc.atom.xml" rel="self"></link><id>http://blog.notmyidea.org/</id><updated>2012-11-30T00:00:00+01:00</updated><subtitle>A personal blog.</subtitle><entry><title>FILENAME_METADATA example</title><link href="http://blog.notmyidea.org/filename_metadata-example.html" rel="alternate"></link><published>2012-11-30T00:00:00+01:00</published><updated>2012-11-30T00:00:00+01:00</updated><author><name>Alexis Métaireau</name></author><id>tag:blog.notmyidea.org,2012-11-30:/filename_metadata-example.html</id><summary type="html">&lt;p&gt;Some cool stuff!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Some cool stuff!&lt;/p&gt;
</content></entry><entry><title>Second article</title><link href="http://blog.notmyidea.org/second-article.html" rel="alternate"></link><published>2012-02-29T00:00:00+01:00</published><updated>2012-02-29T00:00:00+01:00</updated><author><name>Alexis Métaireau</name></author><id>tag:blog.notmyidea.org,2012-02-29:/second-article.html</id><summary type="html">&lt;p&gt;This is some article, in english&lt;/p&gt;
</summary><content type="html">&lt;p&gt;This is some article, in english&lt;/p&gt;

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"><channel><title>Alexis' log - misc</title><link>http://blog.notmyidea.org/</link><description></description><lastBuildDate>Fri, 30 Nov 2012 00:00:00 +0100</lastBuildDate><item><title>FILENAME_METADATA example</title><link>http://blog.notmyidea.org/filename_metadata-example.html</link><description>&lt;p&gt;Some cool stuff!&lt;/p&gt;
<rss version="2.0"><channel><title>Alexis' log - misc</title><link>http://blog.notmyidea.org/</link><description>A personal blog.</description><lastBuildDate>Fri, 30 Nov 2012 00:00:00 +0100</lastBuildDate><item><title>FILENAME_METADATA example</title><link>http://blog.notmyidea.org/filename_metadata-example.html</link><description>&lt;p&gt;Some cool stuff!&lt;/p&gt;
</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Alexis Métaireau</dc:creator><pubDate>Fri, 30 Nov 2012 00:00:00 +0100</pubDate><guid isPermaLink="false">tag:blog.notmyidea.org,2012-11-30:/filename_metadata-example.html</guid></item><item><title>Second article</title><link>http://blog.notmyidea.org/second-article.html</link><description>&lt;p&gt;This is some article, in english&lt;/p&gt;
</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Alexis Métaireau</dc:creator><pubDate>Wed, 29 Feb 2012 00:00:00 +0100</pubDate><guid isPermaLink="false">tag:blog.notmyidea.org,2012-02-29:/second-article.html</guid><category>foo</category><category>bar</category><category>baz</category></item><item><title>Unbelievable !</title><link>http://blog.notmyidea.org/unbelievable.html</link><description>&lt;p&gt;Or completely awesome. Depends the needs.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://blog.notmyidea.org/a-markdown-powered-article.html"&gt;a root-relative link to markdown-article&lt;/a&gt;

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Alexis' log - yeah</title><link href="http://blog.notmyidea.org/" rel="alternate"></link><link href="http://blog.notmyidea.org/feeds/yeah.atom.xml" rel="self"></link><id>http://blog.notmyidea.org/</id><updated>2013-11-17T23:29:00+01:00</updated><entry><title>This is a super article !</title><link href="http://blog.notmyidea.org/this-is-a-super-article.html" rel="alternate"></link><published>2010-12-02T10:14:00+01:00</published><updated>2013-11-17T23:29:00+01:00</updated><author><name>Alexis Métaireau</name></author><id>tag:blog.notmyidea.org,2010-12-02:/this-is-a-super-article.html</id><summary type="html">&lt;p class="first last"&gt;Multi-line metadata should be supported
<feed xmlns="http://www.w3.org/2005/Atom"><title>Alexis' log - yeah</title><link href="http://blog.notmyidea.org/" rel="alternate"></link><link href="http://blog.notmyidea.org/feeds/yeah.atom.xml" rel="self"></link><id>http://blog.notmyidea.org/</id><updated>2013-11-17T23:29:00+01:00</updated><subtitle>A personal blog.</subtitle><entry><title>This is a super article !</title><link href="http://blog.notmyidea.org/this-is-a-super-article.html" rel="alternate"></link><published>2010-12-02T10:14:00+01:00</published><updated>2013-11-17T23:29:00+01:00</updated><author><name>Alexis Métaireau</name></author><id>tag:blog.notmyidea.org,2010-12-02:/this-is-a-super-article.html</id><summary type="html">&lt;p class="first last"&gt;Multi-line metadata should be supported
as well as &lt;strong&gt;inline markup&lt;/strong&gt;.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Some content here !&lt;/p&gt;
&lt;div class="section" id="this-is-a-simple-title"&gt;

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"><channel><title>Alexis' log - yeah</title><link>http://blog.notmyidea.org/</link><description></description><lastBuildDate>Sun, 17 Nov 2013 23:29:00 +0100</lastBuildDate><item><title>This is a super article !</title><link>http://blog.notmyidea.org/this-is-a-super-article.html</link><description>&lt;p class="first last"&gt;Multi-line metadata should be supported
<rss version="2.0"><channel><title>Alexis' log - yeah</title><link>http://blog.notmyidea.org/</link><description>A personal blog.</description><lastBuildDate>Sun, 17 Nov 2013 23:29:00 +0100</lastBuildDate><item><title>This is a super article !</title><link>http://blog.notmyidea.org/this-is-a-super-article.html</link><description>&lt;p class="first last"&gt;Multi-line metadata should be supported
as well as &lt;strong&gt;inline markup&lt;/strong&gt;.&lt;/p&gt;
</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Alexis Métaireau</dc:creator><pubDate>Thu, 02 Dec 2010 10:14:00 +0100</pubDate><guid isPermaLink="false">tag:blog.notmyidea.org,2010-12-02:/this-is-a-super-article.html</guid><category>foo</category><category>bar</category><category>foobar</category></item></channel></rss>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="./">Alexis' log </a></h1>
<h1><a href="./">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="./tag/oh.html">Oh Oh Oh</a></li>
<li><a href="./override/">Override url/save_as</a></li>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="./">Alexis' log </a></h1>
<h1><a href="./">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="./tag/oh.html">Oh Oh Oh</a></li>
<li><a href="./override/">Override url/save_as</a></li>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="./">Alexis' log </a></h1>
<h1><a href="./">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="./tag/oh.html">Oh Oh Oh</a></li>
<li><a href="./override/">Override url/save_as</a></li>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="./">Alexis' log </a></h1>
<h1><a href="./">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="./tag/oh.html">Oh Oh Oh</a></li>
<li><a href="./override/">Override url/save_as</a></li>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="./">Alexis' log </a></h1>
<h1><a href="./">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="./tag/oh.html">Oh Oh Oh</a></li>
<li><a href="./override/">Override url/save_as</a></li>

View file

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="fr">
<head>
<meta charset="utf-8" />
<title>Trop bien !</title>
@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="./">Alexis' log </a></h1>
<h1><a href="./">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="./tag/oh.html">Oh Oh Oh</a></li>
<li><a href="./override/">Override url/save_as</a></li>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="./">Alexis' log </a></h1>
<h1><a href="./">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="./tag/oh.html">Oh Oh Oh</a></li>
<li><a href="./override/">Override url/save_as</a></li>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="../">Alexis' log </a></h1>
<h1><a href="../">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="../tag/oh.html">Oh Oh Oh</a></li>
<li class="active"><a href="../override/">Override url/save_as</a></li>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="../">Alexis' log </a></h1>
<h1><a href="../">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="../tag/oh.html">Oh Oh Oh</a></li>
<li><a href="../override/">Override url/save_as</a></li>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="../">Alexis' log </a></h1>
<h1><a href="../">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="../tag/oh.html">Oh Oh Oh</a></li>
<li><a href="../override/">Override url/save_as</a></li>

View file

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="fr">
<head>
<meta charset="utf-8" />
<title>Deuxième article</title>
@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="./">Alexis' log </a></h1>
<h1><a href="./">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="./tag/oh.html">Oh Oh Oh</a></li>
<li><a href="./override/">Override url/save_as</a></li>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="./">Alexis' log </a></h1>
<h1><a href="./">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="./tag/oh.html">Oh Oh Oh</a></li>
<li><a href="./override/">Override url/save_as</a></li>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="../">Alexis' log </a></h1>
<h1><a href="../">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="../tag/oh.html">Oh Oh Oh</a></li>
<li><a href="../override/">Override url/save_as</a></li>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="../">Alexis' log </a></h1>
<h1><a href="../">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="../tag/oh.html">Oh Oh Oh</a></li>
<li><a href="../override/">Override url/save_as</a></li>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="../">Alexis' log </a></h1>
<h1><a href="../">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="../tag/oh.html">Oh Oh Oh</a></li>
<li><a href="../override/">Override url/save_as</a></li>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="../">Alexis' log </a></h1>
<h1><a href="../">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="../tag/oh.html">Oh Oh Oh</a></li>
<li><a href="../override/">Override url/save_as</a></li>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="../">Alexis' log </a></h1>
<h1><a href="../">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li class="active"><a href="../tag/oh.html">Oh Oh Oh</a></li>
<li><a href="../override/">Override url/save_as</a></li>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="../">Alexis' log </a></h1>
<h1><a href="../">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="../tag/oh.html">Oh Oh Oh</a></li>
<li><a href="../override/">Override url/save_as</a></li>

View file

@ -13,7 +13,7 @@
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" />
</a>
<header id="banner" class="body">
<h1><a href="./">Alexis' log </a></h1>
<h1><a href="./">Alexis' log <strong>A personal blog.</strong></a></h1>
<nav><ul>
<li><a href="./tag/oh.html">Oh Oh Oh</a></li>
<li><a href="./override/">Override url/save_as</a></li>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 751 B

After

Width:  |  Height:  |  Size: 411 B

Before After
Before After

Some files were not shown because too many files have changed in this diff Show more